在业务系统中,查询时最容易出现性能问题的模块,查询面对的数据量大,筛选条件复杂,所以在系统架构中引入缓存层,则是非常必要的,用来缓存热点数据,达到快速响应的目的。
缓存使用的基本原则:
这里是业务架构中常用的缓存策略,缓存通过牺牲强一致性来提高性能,所以并不是所有的业务都适合用缓存,实际考量都会针对具体的业务,比如用户相关维度的数据修改频率低,会使用缓存,但是用户权限数据(比如:免费次数)会考虑实时校验,缓存层使用的相对较少。
Cache-Aside模式
业务中最常用的缓存层设计模式,基本实现逻辑和相关概念如下:
执行读操作未命中缓存,然后查询数据库中取数据,数据已经查询到还没放入缓存,同时一个更新写操作让缓存失效,然后读操作再把查询到数据加载缓存,导致缓存的脏数据。
在遵守缓存使用原则下出现该情况概率非常低,可以通过复杂的Paxos协议保证一致性,一般情况是不考量该场景的处理,如果缓存管理过于复杂,会和缓存层核心理念相悖。
基本描述代码:
@Service
public class KeyValueServiceImpl extends ServiceImpl<KeyValueMApper, KeyValueEntity> implements KeyValueService {
@Resource
private RedisService redisService ;
@Override
public KeyValueEntity select(Integer id) {
// 查询缓存
String redisKey = RedisKeyUtil.getObectKey(id) ;
String value = redisService.get(redisKey) ;
if (!StringUtils.isEmpty(value) && !value.equals("null")){
return JSON.parseobject(value,KeyValueEntity.class);
}
// 查询库
KeyValueEntity keyValueEntity = this.getById(id) ;
if (keyValueEntity != null){
// 缓存写入
redisService.set(redisKey,JSON.toJSONString(keyValueEntity)) ;
}
// 返回值
return keyValueEntity ;
}
@Override
public boolean update(KeyValueEntity keyValueEntity) {
// 更新数据
boolean updateFlag = this.updateById(keyValueEntity) ;
// 清除缓存
if (updateFlag){
redisService.delete(RedisKeyUtil.getObectKey(keyValueEntity.getId()));
}
return updateFlag ;
}
}
Read-Throug模式
当应用系统向缓存系统请求数据时,如果缓存中并没有对应的数据存在,缓存系统将向底层数据源的读取数据。如果数据在缓存中存在,则直接返回缓存中存在的数据。把更新数据库的操作由缓存层代劳了。
Write-Through模式
更新写数据时,如果没有命中缓存,则直接更新数据库,如果命中了缓存,则先更新缓存,然后由缓存系统自行更新数据库。
Write-Behind模式
应用系统对缓存中的数据进行更新时,只更新缓存,不更新数据库,缓存系统会异步批量向底层数据源更新数据。
业务开发模式中,会涉及到一个问题:如何最大限度保证数据库和Redis缓存的数据一致性?
首先说明一下:数据库和缓存强一致性同步成本太高,如果追求强一致,缓存层存在的价值就会很低,如上缓存模式一中几乎可以解决大部分业务场景问题。
解决这个问题的方式很多:
方案一说明:
分析:消息队列的稳定和可靠性,操作层面数据库和缓存层解耦。
方案二说明:
分析:系统架构层面多出一个服务,且需要解析MySQL日志,操作难度较大,但流程上更为合理。
总结描述
分布式架构中,缓存层面的基本需求就是提高响应速度,不断优化,追求数据库和Redis缓存的数据快速一致性,从提供的各种方案中都可以看出,这也在增加缓存层面处理的复杂性,架构逻辑复杂,就容易导致程序错误,所以针对业务选择合理的处理逻辑,这点很关键。
通过info命令查看Redis服务的参数信息,可以通过传参查看指定分类配置。通过config..set设置具体配置参数。例如:
@Override
public Properties info(String var) {
if (StringUtils.isEmpty(var)){
return redisTemplate.getRequiredConnectionFactory().getConnection().info();
}
return redisTemplate.getRequiredConnectionFactory().getConnection().info(var);
}
传参说明:
应用案例:
@RestController
public class MonitorController {
@Resource
private RedisService redisService ;
private static final String[] monitorParam = new String[]{"memory","server","clients","stats","cpu"} ;
@GetMapping("/monitor")
public List<MonitorEntity> monitor (){
List<MonitorEntity> monitorEntityList = new ArrayList<>() ;
for (String param:monitorParam){
Properties properties = redisService.info(param) ;
MonitorEntity monitorEntity = new MonitorEntity () ;
monitorEntity.setMonitorParam(param);
monitorEntity.setProperties(properties);
monitorEntityList.add(monitorEntity);
}
return monitorEntityList ;
}
}
通过上述参数组合,把Redis相关配置参数打印出来,然后可视化输出,俨然一副高端的感觉。
配置参数说明:
这里只对两个参数说明一下,计算命中率的关键信息:
公式:命中率=命中次数/(hits+misses)查找总次数。
Redis的数据是放在内存中的,所以速度快,自然也就受到内存大小的限制,如果内存使用超过配置,Redis有不同的回收处理策略。
内存模块参数:maxmemory_policy
大部分情况下,业务都是希望最热点数据可以被缓存,所以相对使用allkeys-lru策略偏多。这里要根据业务模式特点衡量。