mongodb读取从库延时数据问题的一种解决方案

        某java项目是读多写少的情况,虽然用缓存格挡了很多读的请求,但还是会有不少请求落到mongodb库上。mongodb进行了读写分离,写入往主库,读取从从库,这样减轻了主库的压力。但由于从库同步数据的延时性,某数据在主库写入后马上从从库读,会读取到旧数据并且会将旧数据塞入了缓存。mongodb写的操作有相关设置,writeConcern可设置为Replica Acknowledged级别,即数据被写入到至少两个从库节点返回。这样虽然一定程度上解决从库延时,但写的性能大大降低,不可取。项目采用的方案是在刚写入的一小段时间内都从主库读,过了一小段时间后从从库读。具体实现是每次写入时往缓存中标记该条数据的写入时间,当落入数据库读时,先从缓存中取出取出该数据的时间标记,如果标记存在并且离写入时间很近,则从主库读,否则从从库读。

mongo 连接spring配置:

	<mongo:mongo-client id="mongo3" 
		replica-set="${mongo3.host}" 
		port="${mongo3.port}"  
		credentials="${mongo3.user}:${mongo3.password}@${mongo3.db}">
		<mongo:client-options  
			connections-per-host="200" 
			connect-timeout="5000" 
			max-wait-time="5000"
			write-concern="ACKNOWLEDGED"
			read-preference="SECONDARY_PREFERRED"
			socket-keep-alive="true"
			socket-timeout="10000"
			/>
	</mongo:mongo-client>

	<mongo:mongo-client id="mongo3Primary" 
		replica-set="${mongo3.host}" 
		port="${mongo3.port}"  
		credentials="${mongo3.user}:${mongo3.password}@${mongo3.db}">
		<mongo:client-options  
			connections-per-host="200" 
			connect-timeout="5000" 
			max-wait-time="5000"
			write-concern="ACKNOWLEDGED"
			read-preference="PRIMARY_PREFERRED"
			socket-keep-alive="true"
			socket-timeout="10000"
			/>
	</mongo:mongo-client>
	
    <bean id="mongoTemplate3" class="org.springframework.data.mongodb.core.MongoTemplate">
        <constructor-arg ref="mongo3"/>
        <constructor-arg name="databaseName" value="${mongo3.db}"/>  
    </bean> 
    
    <!-- 读取策略为PRIMARY_PREFERRED,避免主从数据不一致问题 -->
    <bean id="mongoTemplate3Primary" class="org.springframework.data.mongodb.core.MongoTemplate">
        <constructor-arg ref="mongo3Primary"/>
        <constructor-arg name="databaseName" value="${mongo3.db}"/>  
    </bean> 
    

mongo封装类部分代码:

    private MongoOperations mongoTemplate;
    private CacheService cacheService;
    private boolean enableCached = true;
    
    private boolean readFromPrimaryStorage = true;
    private MongoTemplate mongoTemplate3Primary;
    private int updateSignExprieSecond;   // 标记数据更新信息 在缓存中失效时间   


    /**
     * 更新
     */
    public int update(Object obj, String collectionName) {
        //.....
        
        // 将更新时间存入缓存
        putUpdateInfoCache(id, collectionName);
        

        WriteResult result = mongoTemplate.upsert(query, update, collectionName);
        int number = result.getN();
        
        // Clear cache after update
        removeFromCache(id, collectionName);
        
        return number;
    }

 

    /**
     * 标记数据更新时间放入缓存
     * @param id
     * @param collection
     */
    private void putUpdateInfoCache(String id, String collection){
    	if(readFromPrimaryStorage){
	    	if(cacheService == null || !enableCached) {
	  	          return;
	  	    }
	  		String key = genUpdateInfoCacheKey(id, collection);
	          if(StringUtil.isEmpty(key)) {
	              return;
	          }
	          long updateTime=System.currentTimeMillis();
	          cacheService.setObject(key, updateTime, updateSignExprieSecond);
    	}
   
    }


    /**
     * 更新信息的缓存key
     * @param id
     * @param collection
     * @return
     */
    private String genUpdateInfoCacheKey(String id, String collection) {
        if(StringUtil.isEmpty(id)) {
            return null;
        }
        return "monup_" + collection + "_" + id;
    }

    /**
     * 根据id读
     */
    public <T> T findById(String id, Class<? extends T> entityClass,
            String collectionName) {
        T obj = getFromCache(id, collectionName, entityClass);// Query by id from cache 
        if(obj != null) {
            return obj;
        }

        Long updateTime=getUpdateTimeFromCache(id, collectionName);
        if(updateTime!=null){
            // 刚更新读主库
            obj = mongoTemplate3Primary.findById(id, entityClass, collectionName);
        }else{
            	//  更新过了一段时间 读从库
            obj = mongoTemplate.findById(id, entityClass, collectionName);
        }
        // ...
    
        if(obj != null) {
            setCacheObject(id, collectionName, obj);
        }

        return obj;
    }

    /**
     * 从缓存获取对应记录的更新时间,如果返回为null,表示更新时间比较久了
     * @param id
     * @param collection
     * @return
     */
    private Long getUpdateTimeFromCache(String id, String collection){
	   if(cacheService == null || !enableCached) {
           return null;
       }
       String key = genUpdateInfoCacheKey(id, collection);
       if(StringUtil.isEmpty(key)) {
           return null;
       }

       return cacheService.getObject(key, Long.class);
    }

     由于memcached有失效时间设置,上述判断数据最近更新是查看标记存不存在。updateSignExprieSecond的值可以设置几十秒,从库同步时间一般都在5秒内。上述发布后,主库的读不会很多,基本上还是落到从库。

 

 

 

 

 

 

 

 

转载于:https://my.oschina.net/passerman/blog/718269

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值