本文源码:GitHub·点这里 || GitEE·点这里

一、缓存设计

1、缓存的作用

在业务系统中,查询时最容易出现性能问题的模块,查询面对的数据量大,筛选条件复杂,所以在系统架构中引入缓存层,则是非常必要的,用来缓存热点数据,达到快速响应的目的。

缓存使用的基本原则:

  • 所有缓存数据,必须设置过期时间;
  • 核心业务流程不通过缓存层;
  • 缓存层移除,不影响现有流程;
  • 系统各个端首页数据不实时查询;
  • 报表数据不实时查询加载;
  • 归档数据(定时统计的结果数据)不实时查询;

这里是业务架构中常用的缓存策略,缓存通过牺牲强一致性来提高性能,所以并不是所有的业务都适合用缓存,实际考量都会针对具体的业务,比如用户相关维度的数据修改频率低,会使用缓存,但是用户权限数据(比如:免费次数)会考虑实时校验,缓存层使用的相对较少。

2、缓存设计模式

Cache-Aside模式

业务中最常用的缓存层设计模式,基本实现逻辑和相关概念如下:

  • 缓存命中:直接查询缓存且命中,返回数据;
  • 缓存加载:查询缓存未命中,从数据库中查询数据,获取数据后并加载到缓存;
  • 缓存失效:数据更新写到数据库,操作成功后,让缓存失效,查询时候再重新加载;
  • 缓存穿透:查询数据库不存在的对象,也就不存在缓存层的命中;
  • 缓存击穿:热点key在失效的瞬间,高并发查询这个key,击穿缓存,直接请求数据库;
  • 缓存雪崩:缓存Key大批量到过期时间,导致数据库压力过大;
  • 命中率:缓存设计的是否合理要看命中率,命中率高说明缓存有效抗住了大部分请求,命中率可以通过Redis监控信息计算,一般来说命中率在(70-80)%都算合理。
    并发问题

执行读操作未命中缓存,然后查询数据库中取数据,数据已经查询到还没放入缓存,同时一个更新写操作让缓存失效,然后读操作再把查询到数据加载缓存,导致缓存的脏数据。

在遵守缓存使用原则下出现该情况概率非常低,可以通过复杂的Paxos协议保证一致性,一般情况是不考量该场景的处理,如果缓存管理过于复杂,会和缓存层核心理念相悖。

基本描述代码:

@Service
public class KeyValueServiceImpl extends ServiceImpl<KeyValueMapper, KeyValueEntity> implements KeyValueService {@Resourceprivate RedisService redisService ;@Overridepublic 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 ;}@Overridepublic 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 {@Resourceprivate 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

推荐阅读:《架构设计系列》,萝卜青菜,各有所需

序号 标题
01 架构基础:单服务.集群.分布式,基本区别和联系
02 架构设计:分布式业务系统中,全局ID生成策略
03 架构设计:分布式系统调度,Zookeeper集群化管理
04 架构设计:接口幂等性原则,防重复提交Token管理

架构设计 | 缓存管理模式,监控和内存回收策略相关推荐

  1. java redis 数据自过期_Java架构-Redis的内存回收策略和Key过期策略,看这篇就够了...

    Redis 作为当下最热门的 Key-Value 存储系统,在大大小小的系统中都扮演着重要的角色,不管是 session 存储还是热点数据的缓存,亦或是其他场景,我们都会使用到 Redis.在生产环境 ...

  2. 【重难点】【Redis 03】缓存雪崩、缓存穿透、缓存击穿、Redis 的内存过期策略、并发读写和双写

    [重难点][Redis 03]缓存雪崩.缓存穿透.缓存击穿.Redis 的内存过期策略.并发读写和双写 文章目录 [重难点][Redis 03]缓存雪崩.缓存穿透.缓存击穿.Redis 的内存过期策略 ...

  3. JVM实战与原理---内存回收策略

    JVM实战与原理 目录 内存回收策略 1. 堆的划分 2. 判断对象是否存活的算法介绍 2.1 引用计数算法 2.2 可达性分析算法 3. 可回收的内存区域清理的算法介绍 3.1 标记-清除算法 3. ...

  4. .NET应用架构设计—表模块模式与事务脚本模式的代码编写

    阅读目录: 1.背景介绍 2.简单介绍表模块模式.事务脚本模式 3.正确的编写表模块模式.事务脚本模式的代码 4.总结 1.背景介绍 要想正确的设计系统架构就必须能正确的搞懂每个架构模式的用意,而不是 ...

  5. JVM:自动内存管理-垃圾收集器与内存分配策略

    Java与C++之间有一堵由内存分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 一.概述 Java堆和方法区这两个区域有着很显著的不确定性: 1.一个接口的多个实现类需要的内存 ...

  6. JVM学习四:垃圾收集器与内存回收策略

    一.经典垃圾收集器 如果垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的实践者.<Java虚拟机规范>对于垃圾收集器的实现没有任何规定. 这里介绍的经典垃圾收集器," ...

  7. Redis 内存回收策略

    Redis的内存回收机制主要体现在以下两个方面: 删除到达过期时间的键对象. 内存使用达到maxmemory上限时触发内存溢出控制策略. 过期删除策略 删除策略的目标:在内存占用与CPU占用之间寻找一 ...

  8. Redis-17Redis内存回收策略

    文章目录 概述 maxmemory-policy 参数 主动清理策略 [针对设置了过期时间的key做处理] [ 针对所有的key做处理] [ 不处理 (默认)] 策略选择 maxmemory-samp ...

  9. 记录java对象修改过的字段_Java垃圾回收器与内存回收策略

    Java中,内存由虚拟机管理,控制着回收什么,什么时候回收,怎么回收. 在栈中内存的随线程产生和分配,销毁而回收,在堆中,需要制定一系列策略来判断该回收哪些区域,以及何时回收. 可达性分析 主流的做法 ...

最新文章

  1. 用「我的世界」自动生成「现实世界」:英伟达展示AI脑补新技术
  2. 工作总结 npoi 模板 导出公式 excel
  3. BZOJ3075[USACO 2013 Mar Gold 3.Necklace]——AC自动机+DP
  4. linux系统性能监视高级命令(12个)
  5. 介绍一位高级数据分析师,告诉你数据分析原来这么好玩
  6. 阿里开源分布式事务解决方案 Fescar
  7. JVM从入门到精通(一):JVM入门级class文件格式
  8. python textwrap_python2.7.3编译python模块学习- textwrap 文本包装和填充
  9. ios34---GDC,dispatch_once
  10. Ngnix安装的几种常用方式
  11. 一次kafka的offset回退事件及相关知识点
  12. 单结晶体管的导电特性_适用于印刷电子的导电墨水可在纸和PET薄膜上印刷薄膜晶体管...
  13. JAVA听力源码_剑桥雅思13Test4Section4听力原文与答案 The History of Coffee
  14. Java中字节流和字符流的比较(转)
  15. Requested registry access is not allowed 解决办法
  16. kotlin界面_Kotlin界面
  17. oracle 052 题库变了,oracle ocp题库变化,052新加的考试题收集整理-30
  18. matlab 滑动平均窗滤波,滑动平均滤波器与CIC滤波器
  19. 最简单之获取app签名md5值
  20. 【个人记录 | UNet | 整理ing】

热门文章

  1. 蓝桥杯-算法提高-凶手 断案
  2. Python字典类型内部做判断赋值
  3. 7. OD-破解收费版限制天数的软件
  4. 数据结构与算法:动态数组(利用万能指针实现任意类型数组操作)
  5. 关于github上开源nineoldandroids兼容动画的笔记
  6. tableView中deselectRowAtIndexPath的作用
  7. Blink, 通向哈里·波特的魔法世界
  8. JavaScript操作Table
  9. 35岁小贝荣膺终身成就奖
  10. ExtTabMenu 控件