概述
CopyOnWriteArrayList是java.util.concurrent包中的一员,是ArrayList的线程安全版本。其实现原理正如它的类名描述的一样,在进行修改的时候,复制一份数据为新数组,并在新数组上面修改,最后将原来的数组引用指向新数组。
构造方法
CopyOnWriteArrayList拥有三个构造函数,分别是空参构造、传递一个集合的构造、传递一个数组的构造,下面对这三个构造函数一一讲解
public CopyOnWriteArrayList()
1 | /** |
CopyOnWriteArrayList是java.util.concurrent包中的一员,是ArrayList的线程安全版本。其实现原理正如它的类名描述的一样,在进行修改的时候,复制一份数据为新数组,并在新数组上面修改,最后将原来的数组引用指向新数组。
CopyOnWriteArrayList拥有三个构造函数,分别是空参构造、传递一个集合的构造、传递一个数组的构造,下面对这三个构造函数一一讲解
1 | /** |
前面分析了ArrayList和HashMap的源码,今天分析下HashSet的源码。HashSet作为Java集合框架的一员,也是非常重要的,虽然平时用的没有前面两个多,但是在一些特定的场景可以帮助我们快速解决问题,所以掌握其特性也是非常重要的。
首先还是先看下官方的类注释,快速了解HashSet的特性。
此类实现Set接口,由哈希表(实际上是HashMap实例)支持。它不能保证集合的迭代顺序;特别是,它不保证顺序一直保持不变。该类允许null元素。如果Hash函数能够很完美的分散各个元素到桶上,该类的基本操作(add, remove, contains and size)提供恒定的时间性能。
迭代此集合需要的时间与HashSet实例的大小(元素数量)以及HashMap实例的“容量”(桶数)之和成比例。因此,如果迭代性能很重要,则不要将初始容量设置得太高(或负载因子太低)。请注意,此实现不同步。如果多个线程同时访问哈希集合,并且至少有一个线程修改了该即可,则必须在外部进行同步。这通常通过在自然封装集合的某个对象上进行同步来实现。如果不存在此类对象,则应使用Collections.synchronizedSet方法“包装”该集合。这最好在创建时完成,以防止对集合的意外不同步访问:Set s = Collections.synchronizedSet(new HashSet(…));
这个类的迭代器方法返回的迭代器是快速失败的:如果在创建迭代器之后的任何时候修改了set,除了通过迭代器自己的remove方法之外,Iterator都将抛出ConcurrentModificationException。因此,在并发修改的情况下,迭代器快速而干净地失败,而不是在未来的未确定时间冒着任意的,非确定性行为的风险。
请注意,迭代器的快速失败行为无法阿紫存在不同步的并发修改时做出任何硬性保证。快速失败迭代器会尽最大努力抛出ConcurrentModificationException。因此,不要编写这个异常的程序:迭代器的快速失败行为应仅用于检测错误。
由于HashSet的底层是HashMap,它的类注释和HashMap差不多,其特性也和HashMap差不多。
CPU在摩尔定律的指导下以每18个月翻一番的速度在发展,然而内存和硬盘的发展速度远远不及CPU。内存和硬盘的运算速度和CPU查了几个数量级。为了解决这个问题,CPU厂商在CPU中内置了少量的高速缓存以解决I/O速度和CPU运算速度之间的不匹配问题。
所谓高速缓存也就是,当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。
但是高速缓存引入了一个新的问题:缓存一致性(Cache Coherence)。在多核CPU系统中,每个CPU都有自己的高速缓存,而它们又公用一块主内存(Main Memory)。当多个CPU的运算任务都涉及同一块主内存区域时,将可能导致各自的缓存不一致。如果真发生这种情况,那同步回到主内存时以谁的缓存数据为准呢?
最近在工作中使用Sharding-JDBC做了分库分表,目前项目已经上线并稳定运行,闲暇时于记录下使用过程以及踩过的坑,希望对准备使用Sharding-JDBC做分库分表的同学有些帮助。
随着业务的发展,数据量也是爆炸式的增长,有的表结构数据量已经过亿,并且以每月几百万的量持续增长。已经到了必须分库分表的地步。正好我们的系统也要进行服务拆分,一个服务拆分为四个服务,于是一起将分库分表也做掉了。
在决定分库分表之后,首先要做的便是技术选型,目前市面上用来分库分表的中间件有很多,比如Mycat、Sharding-JDBC、淘宝的TDDL(未开源)、平明软件的OneProxy(收费)、360的Atlas、Youtube的Vitess,还有最近比较流行的TiDB等等。
在选择中间件之前,首先得了解分库分表中间件的切入时机
1.编码层
在同一个项目中创建多个数据源,采用if else的方式,直接根据条件在代码中路由。
缺点:
1.编写大量代码
2.代码无法公用
2.框架层
适合公司ORM框架统一的情况,通过修改或增强现有ORM框架的功能来实现
3.驱动层
重新编写了一个JDBC的驱动,在内存中维护一个路由列表,然后将请求转发到真正的数据库连接中。例如:Sharding-JDBC、TDDL等
4.代理层
伪装成一个数据库,接受业务端的链接。然后负载业务端的请求,解析或者转发到真正的数据库中。例如MyCat、MySQL Router、Sharding-Proxy
5.实现层
更换底层的数据存储,比如Mysql替换为TiDB。
传统的分库分表,通常是在驱动层和代理层做切入,这两个各有优劣,不过在将驱动层和代理层的区别前,先说一下TiDB。
TiDB是公司一位大佬推荐的,了解一番后发现这个数据库很方便快捷的帮我们解决海量数据存储这件事情,如果能用TiDB代替传统的分库分表方案也是很好的,可以节省很多时间。不过由于这个数据库只在公司大数据部门的OLAP系统有应用,而实时性要求较高的OLTP系统目前还没有应用,所以决定对TiDB系统进行一次压测,看看其能否满足线上的要求。
压测的过程不多赘述,最终的结果页不是很理想,因为有这么一个怪现象:在持续高并发的情况下,会出现莫名其妙的连接超时失败。
对于这一结果,我们百思不得其解,对结果的准确性也保持的怀疑的态度。但是由于时间有限,不允许我们继续测试,只得将目光投向了其他中间件,同时也和官方联系,询问原因。
后面官方给出的说法是这样的:由于我们的内存配置的太小(8g),导致持续 高并发的时候会进行内存回收,这个时候会强制断开连接。并且给我们介绍说目前实现上OLTP系统中应用TiDB内存都是258g,集群3台机器以上。好吧,都是土豪,惹不起。
排序TiDB后,便只能使用传统的分库分表方案,国内比较火的便是Sharding-JDBC和Mycat,这两个正是驱动层和代理层的代表。
相对于Mycat,仅仅是一个Jar包的Sharding-JDBC赢了我的青睐。
Sharidng-JDBC最早起源于当当,后来进入了Apache孵化器,变为了Sharding-Sphere,目前最新的版本是4.0.0-RC1。
官方的文档并没有写明白Springboot项目应该如何引入依赖,好在机智的我在官方案例中找到了。
1 | <dependency> |
1 | spring: |
SpringBoot2默认不允许Bean覆盖,我们需要改成允许,不然可能会报错
1 | spring: |
到这里,配置便完成了,是不是很简单?如果你的数据库和表结构都已经创建,就可以开始体验了。后面会介绍Sharding-JDBC的一些特性,已经源码解析。
https://shardingsphere.apache.org/document/current/cn/overview/
关于redis,我使用最多的数据结构就是简单的String。对于其他数据结构list、set、hash、zset,乃至于更高级的HyperLogLog、布隆过滤器,基本没有使用过,这次在工作中,恰好遇到需要使用zset的业务场景,特此记录。
zset 可能是 Redis 提供的最为特色的数据结构,它也是在面试中面试官最爱问的数据结构。它类似于 Java 的 SortedSet 和 HashMap 的结合体,一方面它是一个 set,保证了内部 value 的唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的排序权重。它的内部实现用的是一种叫做「跳跃列表」的数据结构。
zset 中最后一个 value 被移除后,数据结构自动删除,内存被回收。
zset 可以用来存粉丝列表,value 值是粉丝的用户 ID,score 是关注时间。我们可以对粉丝列表按关注时间进行排序。
zset 还可以用来存储学生的成绩,value 值是学生的 ID,score 是他的考试成绩。我们可以对成绩按分数进行排序就可以得到他的名次。
Java 的 IO 模型本质上还是利用操作系统提供的接口来实现,所以最好先了解Linux底层模型。推荐阅读Linux IO模式及 select、poll、epoll详解
在JDK1.4之前,基于Java的所有Socket通信都采用同步阻塞模式(BIO),这种一请求一应答的通信模型简化了上层的应用开发,但是在性能和可靠性方面却存在着巨大的瓶颈。当并发量增大,响应时间延迟增大之后,采用Java BIO开发的服务端只有通过硬件的不断扩容来满足高并发和低延迟,它极大的增加了企业的成本,随着集群规模的不断膨胀,系统的可维护性也面临巨大的挑战。