简介
在高并发场景下,像访问次数以及访问记录保存是非常影响性能的,每一次详情页面读取就会产生一条 update 和 insert 。所以需要一个持久化缓冲机制,将高频的数据库写入操作放到Redis去缓冲,设定一个延迟时间,到时间后才去持久化到数据库。
如果项目已经集成Redis,则可以很方便地通过简单代码就实现数据的缓冲,极大程度增加系统吞吐率和响应速度。
(本文介绍的是一个轻量级的实现,还有一些细节没有处理,只是为了满足项目当前需求。如果对数据持久化的可靠性以及到达顺序有高要求,则需要考虑用消息队列kafka或者rabbitmq来实现)
使用效果
Redis持久化缓冲器源码
package outservice.redis.buffer;
import java.io.Serializable;
import java.util.Set;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import outservice.redis.RedisUtil;
import outservice.redis.StringUtils;
/**
* 数据缓冲服务<br>
* 频繁需要更新数据库的操作可以先在缓存中更新,延迟写入数据库
* @author zhaojunfu
*
*/
@Component
public class PersistenceBuffer {
private static final Logger logger = Logger.getLogger(PersistenceBuffer.class);
@Autowired
private RedisUtil redisUtil;
@Autowired
private ThreadPoolTaskExecutor threadPool;
/**
* 数字递增缓冲<br>
* 适用场景:详情页面的访问数累计
* @param key 该数字在redis的缓存key
* @param delayMinutes 延迟更新到数据库的时间(毫秒)
* @param executeable 从缓存到持久化的具体实现
*/
public void increase(String key,long delayMinutes,IncreaseExecuteble executeable) {
key = BufferType.Increase+"-"+key;
String keyTime = "time-"+key;
try{
boolean keyIsExist=redisUtil.hasKey(key);
if(!keyIsExist){
redisUtil.set(key, executeable.query());
redisUtil.set(keyTime, System.currentTimeMillis()+delayMinutes);
}else{
// redisUtil.incr(key, 1);
//TODO 暂时先用set的方式,incr 涉及到修改全局序列化器
redisUtil.set(key,1+ StringUtils.toLong( String.valueOf(redisUtil.get(key))));
long dbTime = StringUtils.toLong( String.valueOf(redisUtil.get(keyTime)));
if(System.currentTimeMillis()>=dbTime){
//更新数据库,更新下次写入时间
executeable.persistence(StringUtils.toLong( String.valueOf(redisUtil.get(key))));
redisUtil.set(keyTime, System.currentTimeMillis()+delayMinutes);
}
}
}catch(Exception e){
logger.error(e,e);
// persistence.increasePersistence(StringUtils.toLong( String.valueOf(redisUtil.get(key))));
}
}
/**
* 数据插入缓冲<br>
* 适用场景:延迟插入访问日志记录,关系记录等非强业务性数据<br>
* @param key 该数据在redis的缓存key
* @param insertData 插入的数据(必须实现Serializable)
* @param delayMinutes 延迟插入时间(毫秒)
* @param executeable 插入的具体方法实现
*/
public void insert(String key,Serializable insertData,long delayMinutes,final InsertExecuteble executeable) {
key = BufferType.Insert+"-"+key;
String keyTime = "time-"+key;
try{
boolean keyIsExist=redisUtil.hasKey(keyTime);
if(!keyIsExist){
redisUtil.set(keyTime, System.currentTimeMillis()+delayMinutes);
}
// redisUtil.getRedisTemplate().opsForList().rightPush(key, value);
SetOperations<String, Object> set = redisUtil.getRedisTemplate().opsForSet();
set.add(key, insertData);
long dbTime =StringUtils.toLong( String.valueOf(redisUtil.get(keyTime)));
if(System.currentTimeMillis()>=dbTime){
synchronized (this) {
//再次判断是否还存在key
if(!redisUtil.hasKey(key)){
return;
}
final Set<Object> datas = set.members(key);
//清空缓存数据
redisUtil.del(key);
redisUtil.del(keyTime);
//用线程池异步执行持久化任务
threadPool.execute(new Runnable() {
@Override
public void run() {
executeable.persistence(datas);
}
});
}
}
}catch(Exception e){
logger.error(e,e);
}
}
}
由于里面有一些其他类的引用,所以特地抽出了一个Redis扩展相关的工程
https://download.csdn.net/download/u011177064/11885500