mongodb讀取從庫延時數據問題的一種解決方案

分類:IT技術 時間:2017-02-13

        某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="https://my.oschina.net/passerman/blog/${mongo3.db}"/>  
    </bean> 
    
    <!-- 讀取策略為PRIMARY_PREFERRED,避免主從數據不一致問題 -->
    <bean id="mongoTemplate3Primary" class="org.springframework.data.mongodb.core.MongoTemplate">
        <constructor-arg ref="mongo3Primary"/>
        <constructor-arg name="databaseName" value="https://my.oschina.net/passerman/blog/${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秒內。上述發布後,主庫的讀不會很多,基本上還是落到從庫。

 

 

 

 

 

 

 

 


Tags: 解決方案 java項目 Replica spring 數據庫

文章來源:


ads
ads

相關文章
ads

相關文章

ad