分布式缓存 – Redis集群 – 理论篇

分布式缓存 - Redis集群 - 理论篇

单机Redis的承载能力毕竟是有限的,当单机无法承载大量并发,为了提高查询的效率,
可以采取集群的方式,将多个redis实例组成集群。

关键词

  • 缓存击穿
  • 缓存雪崩
  • 主从复制
  • 切片模式
  • 分布式锁

缓存击穿

缓存击穿是指,当缓存失效时,大量请求同时到达数据库,导致数据库压力过大。

id过滤器

当有人大量访问任意id,但是缓存中不存在,就会去访问数据库,其实数据不存在,但是客观的占用了数据库资源。也就是缓存击穿
那么可以通过增加id过滤器,就是对id进行过滤,如果过滤器的出数据存在,就直接返回,不在就访问数据库。避免缓存击穿。

但是内存中不可能存储所有id的存在情况,如果数据量过大,就会占用大量内存。

所以可以使用 布隆算法
通过错误率换取空间的占用

算法表示数据存在 就去查数据库,但实际不一定存在,但没关系,对mysql的访问量已经削弱了。
算法表示数据不存在,实际一定不存在

创建一个 bit 数组,每个位置对id进行hash计算.

缓存雪崩

出现雪崩的情况:

  1. 如果redis全部挂掉,
    全去查mysql了,大量的请求直接怼到mysql,导致数据库压力过大,很可能会导致宕机。
  2. 缓存失效时间设置过短,
    导致缓存失效,可以对热数据随机设置有效期,让大量数据不会在同一段很短的时间内失效。

主从复制

解决单点故障问题的,
数据同步节点间,数据是全量的。
若主库挂了,一个从库升级为主库,

主库和从库是独立的,主库负责写,同步到从库,从库负责读。
master 主库
slave 从库

需解决的问题

  1. 主从复制,数据一致性问题,同步数据不精准。
  2. 同步延迟问题。
  3. 主从复制的方式并不能解决数据量过大的问题,单机不能无限制纵向扩容。
    而需采用横向扩展的方式,也就引出切片模式

切片模式

分治,分片的,解决容量压力、容量平静问题
每个节点存储的是一部分数据

切片规则一(不可使用,是思考的过程)

对 key 进行 hash ,然后取模 ,
假设有2台服务器上,那么取模的结果范围为 [0,1]
hash(key) % 2 == 0 放到一个 redis 实例上,
hash(key) % 2 == 1 放到另一个 redis 实例上。

存在的问题

  1. 增减 redis 服务器数量
    1. 取模结果范围增大或减小,原属于其他 redis 服务器部分的 key 需要迁移,
      因为对key 取模的规则发生了变化,结果不一致,所以需要迁移。
    2. 所有的 redis 实例都需要参与数据迁移

这个办法没法满足动态增加和减少 redis 服务器的需求。
所以不能用这种方法

一致性Hash算法

hash环

想象一个环,环上每个节点对应一个redis实例可以存放的位置。
以下对环上的位置称为“节点”
在golang中可以定一个一个结构体

type HashRing struct {
    Node uint // 节点编号(文章来自 fanfine.cn)
    NextNode uint // 下一个节点的编号
}

当节点为最大数值时,他的下一个节点 NextNode 为数值最小的节点的数值。
以此形成一个环形。

redis 实例在环上的节点

redis 实例在环上节点计算方式为 redis 实例的IP地址+编号 hash计算 然后取模 2^32。

redis 实例在环上的节点 = hash(ip+编号) % 2^32

环上数据存储

数据key的节点 = hash(key) % 2^32

顺时针找到距离该节点的下一个最近的 redis实例的节点,然后把数据放到该redis实例上。

举例

对集群的扩展和缩减非常有利,比如:
现在在环上按照顺序有 1 2 3 4 共 4 个 redis 实例的节点,注意这 1,2,3,4 是个环形,所以4的下一个位置是1;

  1. 增加了 redis 实例5 ,其节点在 原来的 4 和 1 之间,那么key节点在 4 和 5之间,
    原本存储在1的那部分数据,需要迁移到5上,5-1之间的数据不变仍保留在1上。
  2. 删除了 redis 的实例4 ,那么原本 数据 key 取模节点原来在 3 和 4 之间,存储在4上的数据,需要迁移到 1 上。

如此,当发生 redis 实例增减的时候,仅需迁移一台 redis 实例上的数据,而不需要对所有redis实例进行迁移。

存在的问题
  1. 数据倾斜
    即大部分的数据,存储在少量的 redis 实例上,

当多个 redis 实例的节点非常接近时,hash环上会有大量空闲位置,导致数据倾斜到某一个redis实例上。
而在hash环的短弧的部分,因为节点数量少,所以存储在另一个redis实例上的数据量也少。

解决办法:

增加更多的虚拟redis实例,并计算节点,使节点在 hash环上分布更均匀。
并将其映射到 真实的 redis 实例上。

比如我有2个redis 实例,那么每台redis实例都有2个虚拟节点,这样节点分布更均匀。
当然仍会有倾斜,但是倾斜程度会降低。可以增加更多虚拟节点,使节点分布更均匀。

redis 分布式锁

关键词

  1. setnx + timeout
  2. 删除自己
  3. 有效期延迟
  4. 主从问题
  5. 延迟启动
  6. RedLock:Redis Lock的缩写。

思路

  1. client1 获取到了锁:setnx + timeout

  2. client2 没有抢到锁,进入自旋,循环的 setnx 以求获得锁,(当然可以设置自旋时间或自旋次数)
    此时如果还有非常多的 client 像 client2一样没有抢到锁,陷入自旋,就会有大量的 setnx 请求,造成网络io的浪费。
    使用 redis的发布订阅(阻塞队列) 即 抢到锁的 client1 发布,没有抢到锁的 client2 之流订阅,在接收到后,才 setnx
    以此避免 自旋的过程中发生大量的无效的网络io。

  3. 如果client1程序活着,处理完任务后释放锁,其他client 自旋可以抢到锁,
    但若client1程序挂了,1的锁在 timeout 后也会释放,其他 client 自旋也可以抢锁。

  4. client1 没有挂,但是执行时间超过了timeout,锁会释放,其他 client 自旋可以抢到锁,此时会有 client1 和其他的 client 都抢到锁,并修改公共数据,发生数据竞争
    解决办法:
    看门狗监控,若client1任务没有完成,在原锁超时释放前,给锁续命,

  5. redis也有可能会挂
    主从复制中,redis主库挂了,从库会顶上称为新的主库
    但是主库中的锁没有同步到从库,(这是一个数据一致性问题,也许是同步速度问题,也许是同步精度问题)
    那么切换到从库后,其他client可以抢到锁了,
    此时client1、另一个在新主库抢到锁的client ,都修改了公共数据,发生数据竞争。

那么怎么解决这个问题?
有的大厂某部门的分布式锁,用的一台redis实例,没有主从复制

红锁 redlock

选举制的思想,

是client实现的

多台redis实例,更多的client,

client需要修改公共数据的时候,向多台分布式锁的redis实例发送获取锁请求,
只有当在超过半数的redis实例上取得锁,才算是取得了锁,才能修改公共数据。

如果恰好3台client,3台redis实例,
每个client都只在其中一台redis实例取得锁,那么都不是超过半数,那么就需要随机睡眠一会,再重新抢锁。

听着性能就不太行,
这是建立在我们极度渴望redis单机挂了,不会出现数据竞争的基础上。
同样也要做。

本文由 上传。


如果您喜欢这篇文章,请点击链接 分布式缓存 – Redis集群 – 理论篇 查看原文。


您也可以直接访问:https://www.fanfine.cn/blog/219

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇