架构设计 | 缓存管理模式,监控和内存回收策略
一、缓存设计
1、缓存的作用
在业务系统中,查询时最容易出现性能问题的模块,查询面对的数据量大,筛选条件复杂,所以在系统架构中引入缓存层,则是非常必要的,用来缓存热点数据,达到快速响应的目的。
缓存使用的基本原则:
- 所有缓存数据,必须设置过期时间;
- 核心业务流程不通过缓存层;
- 缓存层移除,不影响现有流程;
- 系统各个端首页数据不实时查询;
- 报表数据不实时查询加载;
- 归档数据(定时统计的结果数据)不实时查询;
这里是业务架构中常用的缓存策略,缓存通过牺牲强一致性来提高性能,所以并不是所有的业务都适合用缓存,实际考量都会针对具体的业务,比如用户相关维度的数据修改频率低,会使用缓存,但是用户权限数据(比如:免费次数)会考虑实时校验,缓存层使用的相对较少。
2、缓存设计模式
Cache-Aside模式
业务中最常用的缓存层设计模式,基本实现逻辑和相关概念如下:
- 缓存命中:直接查询缓存且命中,返回数据;
- 缓存加载:查询缓存未命中,从数据库中查询数据,获取数据后并加载到缓存;
- 缓存失效:数据更新写到数据库,操作成功后,让缓存失效,查询时候再重新加载;
- 缓存穿透:查询数据库不存在的对象,也就不存在缓存层的命中;
- 缓存击穿:热点key在失效的瞬间,高并发查询这个key,击穿缓存,直接请求数据库;
- 缓存雪崩:缓存Key大批量到过期时间,导致数据库压力过大;
- 命中率:缓存设计的是否合理要看命中率,命中率高说明缓存有效抗住了大部分请求,命中率可以通过Redis监控信息计算,一般来说命中率在(70-80)%都算合理。
并发问题
执行读操作未命中缓存,然后查询数据库中取数据,数据已经查询到还没放入缓存,同时一个更新写操作让缓存失效,然后读操作再把查询到数据加载缓存,导致缓存的脏数据。
在遵守缓存使用原则下出现该情况概率非常低,可以通过复杂的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缓存的数据一致性?
首先说明一下:数据库和缓存强一致性同步成本太高,如果追求强一致,缓存层存在的价值就会很低,如上缓存模式一中几乎可以解决大部分业务场景问题。
解决这个问题的方式很多:
方案一说明:
- 数据库更新写入数据成功;
- 准备一个先进先出模式的消息队列;
- 把更新的数据包装为一个消息放入队列;
- 基于消息消费服务更新Redis缓存;
分析:消息队列的稳定和可靠性,操作层面数据库和缓存层解耦。
方案二说明:
- 提供一个数据库Binlog订阅服务,并解析修改日志;
- 服务获取修改数据,并向Redis服务发送消息;
- Redis数据进行修改,类似MySQL的主从同步机制;
分析:系统架构层面多出一个服务,且需要解析MySQL日志,操作难度较大,但流程上更为合理。
总结描述
分布式架构中,缓存层面的基本需求就是提高响应速度,不断优化,追求数据库和Redis缓存的数据快速一致性,从提供的各种方案中都可以看出,这也在增加缓存层面处理的复杂性,架构逻辑复杂,就容易导致程序错误,所以针对业务选择合理的处理逻辑,这点很关键。
三、缓存监控
1、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); }
传参说明:
- memory:内存消耗相关信息
- server:有关Redis服务器的常规信息
- clients:客户端连接部分
- stats:一般统计
- cpu:CPU消耗统计信息
应用案例:
@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相关配置参数打印出来,然后可视化输出,俨然一副高端的感觉。
配置参数说明:
这里只对两个参数说明一下,计算命中率的关键信息:
- keyspace_misses:查找缓存Key失败的次数;
- keyspace_hits:查找缓存Key命中的次数;
公式:命中率=命中次数/(hits+misses)查找总次数。
2、LRU算法说明
Redis的数据是放在内存中的,所以速度快,自然也就受到内存大小的限制,如果内存使用超过配置,Redis有不同的回收处理策略。
内存模块参数:maxmemory_policy
- noenviction:不回收数据,查询直接返回错误,但可以执行删除;
- allkeys-lru:从所有的数据中挑选最近最少使用的数据淘汰;
- volatile-lru:已设置过期时间的数据中挑选最近最少使用的数据淘汰;
- allkeys-random:从所有数据中任意选择数据淘汰;
- volatile-random:从已设置过期时间的数据中任意选择数据淘汰;
- volatile-ttl:从已设置过期时间的数据中挑选将要过期的数据淘汰;
大部分情况下,业务都是希望最热点数据可以被缓存,所以相对使用allkeys-lru策略偏多。这里要根据业务模式特点衡量。
四、源代码地址
GitHub·地址 https://github.com/cicadasmile/data-manage-parent GitEE·地址 https://gitee.com/cicadasmile/data-manage-parent
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Python Redis常用操作(持续更新)
Python Redis常用操作(持续更新) 目录1、Redis简介2、Redis部署3、Redis API应用4、String操作1、Redis简介redis是业界主流的key-value,nosql数据库之一。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(列表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。 Redis优点 异常快速 : Redis是非常快的,每秒可以执行大约110000设置操作,81000个/每秒的读取操作。支持丰富的数据类型 : Redis支持最大多数开发人员已经知道如列表,集合,可排序集合,哈希等数据类型。这...
- 下一篇
直播预告 | 从架构到代码,软件开发最新趋势解析
随着互联网的爆炸式发展,软件迭代速度愈发疯狂,而软件研发的技术、方法、工具也在快速发展。我们在脚踏实地“敲代码”的同时,也应该偶尔仰望星空,了解最新的软件开发趋势。因此,本期《云效说码》邀请到阿里巴巴美国团队资深技术专家陈立兵(雷卷),他将从领域驱动设计(DDD)、Reactive、Service Mesh 和代码智能等几个方面展开,从架构到代码,帮你快速洞悉新技术,拓宽技术视野。 【分享简介】 领域驱动设计(DDD)简述:架构设计是软件研发生命周期中非常关键的一步,而DDD又是架构设计里备受推崇的方法。本次分享主要介绍Why DDD、事件驱动、微服务之间如何通讯等内容。 基于Reactive和RSocket的异步化通讯:微服务里很重要的一个问题就是要解决服务之间的异步化通讯,雷卷将告诉你如何基于Reactive和RSocket做异步化
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2全家桶,快速入门学习开发网站教程
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS关闭SELinux安全模块
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- CentOS7设置SWAP分区,小内存服务器的救世主
- SpringBoot2整合Thymeleaf,官方推荐html解决方案