作者明割邮箱1311230692@qq.com原文传送门:https://www.cnblogs.com/rjzheng/p/9096228.html 博客讲的非常清晰易懂,非常感谢作者##目录一、为什么使用 Redis二、Redis 缺点三、单线程 Redis四、Redis 数据类...
作者 | 明割 |
---|---|
邮箱 | 1311230692@qq.com |
原文传送门:https://www.cnblogs.com/rjzheng/p/9096228.html 博客讲的非常清晰易懂,非常感谢作者
##目录
- 一、为什么使用 Redis
- 二、Redis 缺点
- 三、单线程 Redis
- 四、Redis 数据类型以及应用场景
- 五、Redis 过期策略以及内存淘汰机制
- 六、Redis 数据库双写一致性的问题
- 七、如何应对缓存击穿和缓存雪崩的问题
- 八、如何解决 Redis 的并发竞争问题
##一、为什么使用 Redis
分析:主要是从两个角度去考虑:性能和并发
1.性能
:
如下图示,在项目过程中会碰到需要执行耗时特别久,且结果变动不频繁的 SQL,就特别适合将运行结果放入缓存,这样后面的请求就去缓存中读取,使得请求能够迅速响应
科普时间:一瞬间、刹那、一弹指
根据《摩诃僧祗律》记载
一刹那者为一念,二十念为一瞬,二十瞬为一弹指,二十弹指为一罗预,二十罗预为一须臾,一日一夜有三十须臾。
经过计算:一瞬间为 0.36 秒,一刹那有 0.018 秒,一弹指长达 7.2 秒
2.并发
:
如下图示,在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常,这是就需要 Redis 做一个缓冲操作,让请求先访问到 Redis,而不是直接访问数据库
##二、Redis 缺点
主要有四个问题:
- 缓存和数据库双写一致性问题
- 缓存雪崩问题
- 缓存击穿问题
- 缓存的并发竞争问题
##三、单线程 Redis
分析:其实是对 Redis 内部机制的一个考察
主要有以下三点:
- 纯内存操作
- 单线程操作,避免了频繁的上下文切换
- 采用非阻塞 I/O 多路复用机制
I/O 多路复用机制:
例如小李在 S 城开了一家快递店,负责同城快递业务,雇佣了一批快递员,然后因为资金限制,只够买一辆车送快递。
经营方式一:
客户每送一份快递,就让快递员盯着,然后开车去送快递,慢慢就发现了存在的问题:
- 几十个快递员基本上时间都花在了等车上,大部分快递员处于闲置状态
- 随着快递的增多,快递员也越来越多,小李发现快递店越来越拥挤,没办法只有雇佣新的快递员送快递
- 快递员之间协调车辆比较花时间
经过后续分析,就有了第二种经营方式
**方式二:**只雇佣一个快递员,然后客户送来的快递,按送达地点标注好,依次放在一个地方,最后那个快递员一次去取快递,一次拿一个,然后开车去送。
相比较方式一而言,方式二的效率更高
- 每个快递员:每个线程
- 每个快递:每个 Socket(I/O)
- 快递的送达地点:Socket 的不同状态
- 客户快递请求:来自客户端请求
- 经营方式:服务端策略
- 一辆车:CPU 的核数
结论:
1、经营方式一就是传统的并发模型,每个 I/O 流(快递)都有一个新的线程(快递员)管理
2、经营方式二就是 I/O 多路复用。只有单个线程(快递员),跟踪每个 I/O 流的状态(快递送达地点),来管理多个 I/O 流
下图类比到真实的 Redis 线程模型:
##四、Redis 数据类型以及应用场景
一共五种数据类型:
1、String
最常规的 set、get 操作,value 可以使 String 也可以是数字,一般用于一些复杂的计数功能的缓存
2、hash
这里 value 存放的是结构化的对象,比较方便的就是操作其中某个字段,例如单点登录
,就是用这种数据结构存储用户信息,以cookieId作为key,设置30分钟为缓存过期时间,能很好的模拟出类似session的效果
3、list
可以做简单的消息队列的功能
;可以利用 lrange 命令,做基于 Redis 的人也功能
,性能极佳,用户体验好
4、set
set 堆放的是一堆不重复值的集合,可以做全局去重的功能
,为什么不用JVM自带的Set进行去重?因为我们的系统一般都是集群部署,使用JVM自带的Set,比较麻烦,难道为了一个做一个全局去重,再起一个公共服务,太麻烦了
5、sorted set
sorted set多了一个权重参数score,集合中的元素能够按score进行排列,可以做排行榜应用,取 TOP N 操作
##五、过期策略以及内存淘汰机制
分析:例如 Redis 只能存 5G 数据,但是你写了 10G,那会删除 5G 数据。怎样删除,数据已经设置了过期时间,但是时间到了,内存占用率还是比较高
针对这个问题,Redis 采用定期删除
+惰性删除策略
为什么不用定时删除策略?
定时删除,用一个定时器来负责监视key,过期则自动删除。虽然内存及时释放,但是十分消耗CPU资源。在大并发请求下,CPU要将时间应用在处理请求,而不是删除key,因此没有采用这一策略
定期删除+惰性删除是如何工作的?
定期删除
:redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除
惰性删除
:也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除
采用定期删除+惰性删除就没有其他问题?
不是的,如果定期删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效。这样,redis的内存会越来越高。那么就应该采用内存淘汰机制
在 redis.conf 中有一行配置
# maxmemory-policy volatile-lru
就是配置内存淘汰策略:
- noeviction;当内存不足以容纳新写入的数据时,新写入的操作会报错。
几乎不用
- allkeys-lru:当内存不足以容纳新写入的数据时,在键空间中,移除最近最少使用的 key。
推荐使用
- allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。
几乎不用
- volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。
几乎不用
- volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
几乎不用
- volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除.
几乎不用
##六、数据库双写一致性的问题
分析:一致性问题是分布式常见问题,还可以再分为最终一致性和强一致性。数据库和缓存双写,就必然会存在不一致的问题。答这个问题,先明白一个前提。就是如果对数据有强一致性要求,不能放缓存。我们所做的一切,只能保证最终一致性。另外,我们所做的方案其实从根本上来说,只能说降低不一致发生的概率,无法完全避免。因此,有强一致性要求的数据,不能放缓存。
解决:采取正确更新策略,先更新数据再刷新缓存,因为可能存在删除缓存失败的问题,可以提供一个补偿措施,如消息队列,也可参考:《分布式之数据库和缓存双写一致性方案解析》
##七、如何应对缓存击穿和缓存雪崩的问题
缓存穿透:黑客故意去请求缓存中不存在的数据,导致所有的请求都到数据库上,从而数据库连接异常
解决方案
:
- 利用互斥锁:缓存失效的时候,先获得锁,得到锁了,再去请求数据库,没得到锁,则休眠一段时间重试
- 采用异步更新策略:无论key是否取到值,都直接返回。value值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作
- 提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的key。迅速判断出,请求所携带的Key是否合法有效。如果不合法,则直接返回
缓存雪崩:缓存同一时间大面积失效,核实后又来了一波请求,结果请求都到数据库上,从而导致数据库连接异常
解决方案
:
- 给缓存失效的时间,加上一个随机值,避免集体失效
- 使用互斥所,但是会影响系统吞吐量
- 双缓存,有两个缓存,缓存 A 和缓存 B,缓存 A 的失效时间为 20 分钟,缓存 B 不这是失效时间:
1、从缓存 A 读取数据库,有则直接返回
2、A 没有数据,直接从 B 读数据,直接返回,并且异步启动一个更新线程
3、更新线程同时更新缓存 A 和缓存 B
##八、如何解决 Redis 的并发竞争问题
分析:这个问题大致是同时有多个子系统去 set 一个 key,大部分解决策略是采用 Redis 的事务机制,但是会有一个问题:因为我们的生产环境,基本都是redis集群环境,做了数据分片操作。你一个事务中有涉及到多个key操作的时候,这多个key不一定都存储在同一个redis-server上
解决方案
- 如果对这个 key,不要求顺序,这种情况下可以准备一个分布锁,大家去抢锁,抢到锁就做 set 操作,比较简单
- 如果对这个 key,要求顺序,假设有一个key1,系统A需要将key1设置为valueA,系统B需要将key1设置为valueB,系统C需要将key1设置为valueC.期望按照key1的value值按照 valueA–>valueB–>valueC的顺序变化。这种时候我们在数据写入数据库的时候,需要保存一个时间戳,将 set 方法变成串行访问
本文标题为:Java程序员从笨鸟到菜鸟(五十三) 分布式之 Redis
基础教程推荐
- Mysql主从三种复制模式(异步复制,半同步复制,组复 2022-09-01
- 如何将excel表格数据导入postgresql数据库 2023-07-20
- python中pandas库的iloc函数用法解析 2023-07-28
- 【Redis】数据持久化 2023-09-12
- Python常见库matplotlib学习笔记之多个子图绘图 2023-07-27
- Sql Server Management Studio连接Mysql的实现步骤 2023-07-29
- 关于MySQL中explain工具的使用 2023-07-27
- Mysql查询所有表和字段信息的方法 2023-07-26
- Redis如何实现延迟队列 2023-07-13
- SQLServer 清理日志的实现 2023-07-29