一些Redis的知识点(下)
首先恭祝大家新年好,各个看文的大佬们恭喜发财、身体健康。没想到这么快过年了。不知道看文的大家过的如何,不管好不好,希望大家能越过越好。
关于本文的吐槽。我是没想到写出来的量这么少,只有4k字。大家凑合下吧。可能就是分布式锁那里我挺纠结要不要讲一下更多的细节。比如他的场景啥的。下一篇我要开始写Nginx还是ES数据库都说不好,或者会先说MQ,最近工作碰到了一个MQ问题,虽然不是我处理。但是也是上了一课,也觉得要好好整理一下。(随缘好了~)
本文概要:
知识点都是来自各博主文章整理,自己写一遍做一遍笔记比单纯看要理解吸收都更好。所以才会有这样的笔记。
本文只涉及RedisCluster集群、Redis常见问题、过期策略、淘汰策略、分布式锁的一些知识笔记
Redis高可用
Cluster集群
前身
Redis Sharding
非官方的分布式存储Redis的数据
使用方式
采用哈希算法将Redis数据的key进行散列。通过hash函数,特定的key会映射到Redis节点上。
优点
所有逻辑都是可控的,不依赖于第三方分布式中间件,配置简单。服务端Redis之间没有关联
缺点
客户端无法动态增删服务节点,客户端需要自行维护分发逻辑,维护性差。
示例
Client |
客户端路由规则 |
Redis1 |
存放key1的值 |
Redis2 |
key2 |
||
Redis3 |
key3 |
(key值都是通过hash算法得出的)
Codis
大概了解下即可
对上层应用来说,连接Codis Proxy和连接原生的Redis Server没有明显区别(不过不支持命令列表)。上层应用可以像使用单机的Redis一样使用,Codis底层会处理请求的转发,不停机的数据迁移工作。
人话来说 就是 Client连接的服务器实际上是一个内存无限大的Redis服务。(不过依赖于第三方中间件)
优点
实现了上层Proxy和底层Redis的高可用,数据分片和自动平衡,提供命令接口和RESTful API,提供监控和管理页面,可以动态添加和删除Redis节点。
缺点
部署架构和配置复杂,并且因为是Redis先升级了这家伙还能开发升级的工具。所以该中间件的官方效率很低。
Cluster来源版本
Redis 3.0(官方推出的)
大概
实现方案上雷同Sharding分片技术。键值按照一定算法合理分配到各个实例分片上,同时各个实例节点协调沟通。共同对外承担一致服务。
PS:但是肯定也有不同点。Redis Cluster引入了槽(类似Codis,但是Cluster划分了16384【2^14】个槽)。将所有的数据对应到槽中,然后每个Redis节点管理一部分槽。
大致的示例
Client |
Slot1(分片1) |
Redis1 |
Slot2 |
Redis1 |
|
Slot3 |
Redis2 | |
Slot4 |
Redis2 |
|
Slot5 |
Redis3 |
|
Slot6 | Redis3 |
优点
无中心节点(所有的Redis节点都是对等的节点,同步数据使用的是Gossip协议),数据按照槽存储分布式在多个Redis实例上,可以平滑的进行节点 扩容/缩容,当节点数量改变时,只需要动态更改节点负责的槽就行,这对客户端来说是透明的。不需要依赖中间件。运维成本低。
人话环节,就像是一个Redis 就是一个房子。房子里面放了好多个桶子。桶子才是放数据的地方。要是换房子了,把桶子搬走就行。
缺点
严重依赖Redis-trib工具,缺乏监控管理,Failover节点的检测过慢,Gossip协议传播消息到最终一致性有一定延迟。
(看不懂Gossip协议是什么没关系,下面会说的。)
数据分区规则
常见的分区规则
大意
当整个数据计划分到多个节点上,让每个节点负责一部分的数据,就需要使用数据分区规则。
常见规则
· 哈希分区
特点——离散性好,数据分布与业务无关,但是无法按顺序访问。
· 顺序哈希
特点:离散度易倾斜,数据分布与业务相关,但可以按顺序访问
常见用的场景:大数据分析的数据库中。
Redis的选择方案
哈希分区
哈希分区
固定哈希区
大概含义
根据特定的字段(Redis中使用键)使用哈希函数计算出Hash值,然后根据节点的数量N取值,来决定将数据映射到哪一个节点上。
优点
规则设置和实现都很简单
缺点
扩容或收缩节点会涉及到很多数据的迁移
示例
keys |
hash(key)%3 |
Redis1 |
Redis2 | ||
Redis3 |
一致哈希区
特点
可以很好地解决稳定性问题
大意
可以将所有的存储节点排列在首尾相接的hash环上(环的大小为2^32),key在计算Hash后会顺时针方向找到最近的存储节点存放数据。
(新增/减少节点时,仅影响该节点在hash环上顺时针方向的下一个节点)
大概的运行示例(如果是添加一个Node的话 就是看key离哪个近就保存哪个,而且可以分散单个Node压力)
虚拟槽哈希分区
操作大概
1、利用分散性良好的哈希算法(Redis使用的CRC16算法)把所有数据映射到一个固定范围的整数集合中,整数定义为槽(slot)。
2、每个节点都会管理一定的槽,hash函数将键映射到槽中,然后将数据存储在该槽所对应的节点。
Slot
范围——一般远远大于节点数
作用——数据管理和迁移的基本单位(也可以说是容器)
好处
容易对集群进行扩容和收缩——因为数据是跟槽有关联,并非和节点关联。实现了一定的解耦。
Gossip协议
了解前提
分部署存储中需要维护节点元数据的机制。——元数据指节点负责的数据以及节点的状态。
常见的维护方式:
· 集中式
· P2P——RedisCluster使用
通信过程
集群中的每个节点会单独开通一个TCP通道,用于节点之间彼此的通信(端口号为节点端加上10000)。
当节点出现变化,该节点会随机向周围几个节点传播消息,收到消息的节点会重复这个过程,最终集群中的所有节点都会收到该消息,达到集群状态最终一致的目的。
优势
· 可拓展性
网络可以允许节点的任意增加和减少,新增加节点的状态最终会与其他节点一致。
· 容错
网络中任何节点的宕机和重启都不会影响到Gossip消息的传播,Gossip协议具有天然的分布式容错特性。
· 去中心化
Gossip协议不要求任何中心节点,所有节点都可以是对等的,任何一个节点无需知道整个网络状况,只要网络是连通的,任意一个节点就可以把消息散播到全网。
· 最终一致性
Gossip协议实现信息指数级(logN)的快速传播,因此在有新信息需要传播时,消息可以快速地发送到全局节点,在有限的时间内能够做到所有节点都拥有最新的数据。
缺陷
消息的延迟
由于Gossip协议中,节点只会随机向少数几个节点发送消息,消息最终是通过多个轮次的散播而到达全网的,因此使用Gossip协议会造成不可避免的消息延迟,不适合用在对实时性要求较高的场景下。
消息冗余
Gossip协议规定,节点会定期随机选择周围节点发送消息,而收到消息的节点也会重复该步骤,因此就不可避免的存在消息重复发送给同一个节点的情况,造成消息的冗余,同时增加了收到消息的节点处理压力。而且由于是定期发送,因此收到了消息的节点还会反复收到重复信息,加重了消息的冗余。
常见问题
(以下缓存雪崩、缓存穿透、缓存击穿都是Redis+关系型数据库一起使用的背景下解释)
缓存雪崩
大意
大量key同一时间失效,这时候如果有访问量大的访问,Redis这时候没有key会导致数据走到数据库,可能会导致程序崩溃
解决方案
· 给key添加上随机值来失效
· 集群部署:
这种方式通过数据都是热点数据均匀分布在不同Redis库中,保证不会出现一个Redis大面积失效数据。
PS:如果是用分片(现在大部分都是分片)存储数据,还是加上随机失效才是好方案。
缓存穿透
大意
指缓存数据库和数据库都没有的数据,而用户不断访问,导致数据库压力过大。导致程序崩溃。
解决方案
· 对方法进行参数校验,保证请求的数据都是合法且存在的
(如果有需要的场景会用到一些数据但是无法有值的,就把对应的key的value设置null。保险的可以加上失效时间。)
· 高级点的做法——使用布隆过滤器(Bloom Filter)
缓存击穿
大意
跟雪崩雷同,都和失效时间有关;主要是热点key长时间被高访问,导致key失效了之后,高访问就会变成直接访问数据库,容易造成程序崩溃
解决方案
· 互斥锁——也就是如果没有值就设置一个有超时时间的K-V
· 热点数据容不失效
双写一致性
大意
当更新了mysql中的数据后也可以同时保证redis中的数据同步更新
解决方案
延迟双删
流程:
1、在修改MySQL前先清空Redis的数据
2、更新MySQL数据库,延时(2~3s)再删除一次缓存
3、延迟之前的操作读取数据有可能查的是旧数据,但是延时删除后再读就会是Redis的就是新数据
牺牲性能的解决方法
分布式锁
以MySQL作为主库,其他数据库作为副本
(现在的大众方案)
也就是MySQL的数据会用别的传输工具来把修改的数据传给 Redis/ES去自己修改。都是用中间件的方式。
并发竞争
大意
多个线程或客户端对同一个key进行操作。
解决方案
分布式锁
大Key
大意
指存储的值较大。
这个我举些例子
1、微博大V粉丝数量
2、热门下的评论
3、或者使用的数据类型和场景不匹配,用错类型存储数据
弊端
1、容易出现Redis内存占用过高
2、QPS的突降或者突升,极端的可能会主从复制异常,Redis服务器阻塞无法响应请求。
解决方案
4.0之前的解决
1、redis-rdb-tools工具——使用后可在Redis执行bgsave,然后对rdb文件进行分析找出大key。
2、redis-cli --bigkeys命令——可以找到某个实例5种数据类型(String、hash、list、set、zset)的最大key。
3、自定义扫描脚本——网上都是Python的脚本居多。
4、debug object key命令——可以查看key系列化后的长度,每次只能查找单个key的信息,所以不太好用。
4.0之后的处理方法
Redis 4.0 引入了 memory usage命令 和 lazyfree机制,不管是对大key的发现还是解决大key的删除或者过期造成的阻塞问题都可以解决。
分析
memory usage——通过计算objectComputeSize来计算key的大小。
(默认抽取5个field来循环累加计算整个key的内存大小,样本的数量决定了key的内存大小的准确性和计算成本,样本越大,循环次数越多,计算结果越精确,不过也费性能。)
lazyfree机制——删除的时候只进行逻辑删除,把key释放操作在bio(background I/O)单独的子线程处理中,减少删除大key对Redis主线程的阻塞。
题外话
Redis将最主要的网络收发和执行命令等操作都放在了主工作线程,然而除此之外还有几个bio后台线程,比如处理关闭文件和刷盘的后台线程,以及Redis4.0新增加的lazyfree线程。
热点Key
大意
Redis被使用较多次的key,当key失效会出现影响Redis+MySQL的性能。
防止热点key出问题的方案
1、缓存时间不失效
2、多级缓存——跟MySQL的那种差不多意思,我就不多解释了。大概是开启本地缓存,如果本地缓存有就读缓存,不走Redis 获取数据
3、布隆过滤器
4、读写分离
过期策略
(对key设置时效后 Redis如何会什么时候删除)
定时删除
为key设置定时器,一旦时间到了,自动删除key。
特点
对内存友好,但是对CPU不友好,定时器占用CPU资源
惰性删除
不管key有没有过期,都不会自动删除,等每次获取的时候再判断是否过期,如果过期就删除该key,否则返回键对应的值。
特点
对内存不够友好,因为会存着已经过期的键
定期删除
系统每个一段时间就定期扫描一次,发现过期了的键就进行删除。这种策略相对来说是上面两种方案的折中版。
特定
弊端是可能会在在删除的时候有别的线程读到了键,返回了内容给客户端
淘汰策略
大意
当Redis内存被占用到满了,客户端set值就会触发淘汰策略来给客户端set值
如何设置用什么策略
静态配置
maxmemory-policy <bytes>
动态配置
config set maxmemory-policy <策略>
<bytes>的可写值
volatile-lru |
根据 LRU 算法删除设置了过期时间的键,直到腾出可用空间。如果没有可删除的键对象,且内存还是不够用时,则报错 |
allkeys-lru |
根据 LRU 算法删除所有的键,直到腾出可用空间。如果没有可删除的键对象,且内存还是不够用时,则报错 |
volatile-lfu |
根据 LFU 算法删除设置了过期时间的键,直到腾出可用空间。如果没有可删除的键对象,且内存还是不够用时,则报错 |
allkeys-lfu |
根据 LFU 算法删除所有的键,直到腾出可用空间。如果没有可删除的键对象,且内存还是不够用时,则报错 |
volatile-random |
随机删除设置了过期时间的键,直到腾出可用空间。如果没有可删除的键对象,且内存还是不够用时,则报错 |
allkeys-random |
随机删除所有键,直到腾出可用空间。如果没有可删除的键对象,且内存还是不够用时,则报错 |
volatile-ttl |
根据键值对象的 ttl 属性, 删除最近将要过期数据。如果没有,则直接报错 |
noeviction |
默认策略,不作任何处理,直接报错 |
LRU算法
全称:Least Recently Used
大致
通过抽样的方式进行删除
LFU 算法
全称:Least Frequently Used
大致
通过最少使用的频率来选择对象。
分布式锁
前提
实现分布式锁之前,外部调用系统一定要有对key这块有【互斥】的能力。比如两个请求设置相同key。只能一个成功,另外一个失败。
大致操作
SETNX lock 1——加锁处理
DEL lock——解锁
弊端
当客户端1拿到锁,如下操作时Redis会出问题(锁被锁死了)
· 程序处理业务逻辑异常,没及时释放锁
· 进程在操作的时候,挂了。这时候就会没机会释放锁
避免死锁的方法如下
1、给锁设置超时时间
但是即便这样设置也不行,有可能还是会无法保证锁被释放。比如Redis异常了。或者客户端崩溃,网络问题
2、Redis 2.6.12 之后,只需要用一条命令即可(官方拓展了Set命令)
语法:SET lock 1 EX 10 NX
缺点(2.6.12之后还存在如下弊端)
1.锁过期——可能会存在操作业务时间过久,导致锁自动被释放了
2.执行了别人的锁——因为如1步骤的话锁已经自动失效,那时候解锁可能会误解了别人的锁
解决2.6.12版本弊端的办法
客户端加锁时候,设置一个只有自己知道的【唯一标识】进去。
(唯一标识只求随机且唯一即可)
PS:如上示例需要用lua脚本操作。原理是在lua操作的时候其他该key请求需要等待。
锁的相关步骤
1、加锁——SET lock_key $unique_id EX $expire_time NX
2、操作共享资源
3、释放锁——Lua 脚本,先 GET 判断锁是否归属自己,再 DEL 释放锁
Java技术栈对锁过期时间不好评估如何处理
Java使用工具类:Redisson
在使用分布式锁时,它就采用了「自动续期」的方案来避免锁过期,这个守护线程我们一般也把它叫做「看门狗」线程。
本文来源:
https://juejin.cn/post/6844904057048563725
https://www.jianshu.com/p/50c0894c0a19
https://mp.weixin.qq.com/s/D4Ik6lTA_ySBOyD3waNj1w
https://mp.weixin.qq.com/s?__biz=MzAwNDA2OTM1Ng==&mid=2453140871&idx=3&sn=0ad5fc9d0474089a4271b16bf47b7cb0&chksm=8cf2d704bb855e1208ee8fead319c27204a00c64b484a950014527379d022623350e83e9353d&scene=21#wechat_redirect
https://mp.weixin.qq.com/s/-caMTrOXQu-o0O44e6I9dQ
https://mp.weixin.qq.com/s/RnSokJxYxYDeenOP_JE3fQ