MongoDB集群读优先策略

avatar 2020年4月22日23:03:07 评论 1,404 次浏览

目前我们项目中使用的MongoDBJava客户端版本为2.9.1,这次的读写分离改造是基于2.9.1的。目前MongoDB已经发布了最新的3.0版本,对应的Java客户端是3.0.1,3.0版本在以前MongoDB.8版本上进行了重大的改造,进行了许多的优化和升级,包含插件式的存储引擎(类似于MySQL的架构),硬盘数据压缩,MVCC的支持,复制集升级等等,数据的读写性能都有很大的提升,相应的客户端API也进行升级,3.0.1版本的java客户端与2.9.1版本是完全不一样的,基本可以说是一个新的客户端。

目前的mongodb支持多种读优先的配置(read preference),该配置是针对mongodb中的集群而言的,目前mongo中有两种集群模式,主从以及复制集,这里以复制集作为例子,主从(sharding)支持读优先的原理与复制集类似,只是有细微的区别。目前支持以下几种方式(官方说明):

Read Preference Mode Description
primary Default mode. All operations read from the current replica set primary.
primaryPreferred In most situations, operations read from the primary but if it is unavailable, operations read from secondary members.
secondary All operations read from the secondary members of the replica set.
secondaryPreferred In most situations, operations read from secondary members but if no secondary members are available, operations read from the primary.
nearest Operations read from member of the replica set with the least network latency, irrespective of the member’s type.

primary模式:默认模式,所有的读操作都由复制集的主节点处理; primaryPreferred模式:一般情况下,所有的读操作由主节点处理,当主节点不可用的时候,读操作由备份节点处理; secondary模式:所有的读操作由复制集的备份节点处理; secondaryPreferred模式:一般情况下,所有的读操作由备份节点处理,当所有的备份阶段宕机后,读操作由主节点处理; nearest模式:选择复制集中的读延迟最少的节点处理读操作,主节点以及备份节点都有可能处理读操作;

应用场景

primary模式拥有最好的数据一致性,能保证所有的读操作均获取最新的数据,这种模式下主节点负载较大,在高并发场景中性能会有影响。

primaryPreferred模块拥有最好的可用性,主节点处理读请求时也能保证数据数据一致性;

nearest模式拥有最小的延迟,会从复制集中选择最低网络延迟的节点处理读请求,这种方式不能保证数据一致性,也不能保证减少IO以及CPU的负载;

secondary模式主要用于减少主节点的负载,但是不能保证数据一致性,读操作获取的数据可能是旧数据(备份节点与主节点存在数据同步延迟。oplog的方式进行同步,类似于mysql的binlog),但是能降低主节点的复杂,集群整体的读写速度;适合于读密集的应用;

为什么不用secondaryPreferred模式?

从模式的字面意思看,secondaryPreferred模式比secondary模式更能保证服务的可用性,但是为什么不选选择secondaryPreferred?原因是在某些场景下,所有的备份节点都宕机后,所有的读请求也将转移到主节点,假如主节点不能同时处理这些读请求,那么读请求将会和写请求竞争资源,导致主节点负载加大,影响读写性能,严重情况会造成主节点宕机。

对于读密集型应用,线上使用复制集集群方式,能保证服务的可用性,由于数据的及时性要求不是特别高(前台页面展示内容),能够接受短时间内的数据延迟,因此选择secondary模式是非常合适的。 以2.9.1 Java客户端为例,初始化代码如下:

<span style="font-size:14px;">/** 
* 初始化mongodb客户端
* 
*/ 
protected void initMongo(){ 
        MongoOptions op = new MongoOptions(); 
        op.setSafe(isSave); 
        op.setConnectionsPerHost(connectionPerHost); 
        op.setConnectTimeout(connectionTimeout); 
        // if online , it uses replicate set and set secondary value of read preference 
             // ReadPreference which reads secondary if available, 
            op.setReadPreference(ReadPreference.secondary()); 
        } 
      mongo = new Mongo(addressList, op); 
}
</span>

注:new Mongo(addressList,op); 方法中的addressList对象可以是复制集集群中的每一个节点也可以是分片集群中的每一个片(主从),也可以是单个的mogod实例,mongo在初始化时可以获取mongo server的具体信息,这些信息在后续读写操作中会被用到。

即在初始化mongo客户算时设置读优先的模式。mongo客户端源码的设计有一个非常巧妙的地方,就是下层对象在实例化的时候可以获取到上层对象的引用,从而可以获取到上层对象的属性,因此不需要下层对象在调用方法时显式的传递配置信息,即配置信息可以共用,这点的设计上有点类似于log4j的设计方式,子log对象拥有父log对象的引用,递归后即每一个log实例都能获取到root日志对象的引用,这样做以后,程序能够更加简单,易读,也能使得配置的作用域最大化。在mongo中的实现是db能够持有mongo的引用,DBCollection持有db的引用,因此在进行读写操作时,DBCollection实例持有mongo引用,从而获取全局的mongo配置信息,实现在读操作时根据设置的read preference进行不同的读路由,实现一次配置,处处可用。

avatar

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: