vlambda博客
学习文章列表

一些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