Skip to content

Commit

Permalink
2019-07-12 blog
Browse files Browse the repository at this point in the history
  • Loading branch information
huanghongkai authored and HuangHongkai committed Jul 12, 2019
1 parent 3119972 commit e550d46
Showing 1 changed file with 166 additions and 0 deletions.
166 changes: 166 additions & 0 deletions 每周分享/2019/04-redis热key等问题研究.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
---
title: "redis热key,缓存穿透,击穿,集群等问题研究"
date: "2019-07-012"
permalink: "2019-07-12-redis-cache-question"
---

Redis主要用途有2个

- 数据缓存,减轻db的压力
- 数据同步,在分布式系统中各个实例的信息同步

redis的QPS量通常相当的大,动辄百万QPS,一般企业都需要很庞大的redis集群资源。由于redis的使用相当频繁,经常会出现各种问题。根据目前积累的线上经验,redis出现问题第一步就是要想到:**redis是单进程单线程实例** 理解这一点通常能解决很多问题。

下面介绍一些redis的一些理论知识。



## 主从模式

将数据分散到多个服务器上,每个服务拥有同样的样本,这样即使有一台服务器出现故障,其他服务器依然可以继续提供服务。为此, Redis 提供了复制(replication)功能,可以实现当一台数据库中的数据更新后,自动将更新的数据同步到其他数据库上。

### 概念

在redis或mysql等数据库中,数据库分为两类,一类是主数据库(master),另一类是从数据库(slave)。主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。



### 复制原理

当从数据库启动时,会向主数据库发送sync命令,主数据库接收到sync后开始在后台保存快照rdb,在保存快照期间受到的命名缓存起来,当快照完成时,主数据库会将快照和缓存的命令一块发送给从。复制初始化结束。之后,主每接收到1个命令就同步发送给从。 当出现断开重连后,2.8之后的版本会将断线期间的命令传给从数据库。



### 优点:

- 减轻单点压力

- 提升系统容灾能力



### 缺点:

- 承载的容量非常有限,假设服务器的内存都是40G,有10台服务器,意味着只能容纳40G的数据。
- 刚写入的数据可能获取不到(因为数据还没有更新到其他服务器上)



## 哨兵

在主从模式下,当主服务出现异常会导致整体redis不可写。开发者需要手动将一个从数据库升级为主数据库保证服务正常运行。该过程难以自动后,为此在redis2.8后提供了哨兵来实现故障恢复功能。

哨兵就是redis的监控系统,包括2个功能

- 监控主数据库和从数据库是否正常运行。
- 主数据库出现故障时自动将从数据库转换为主数据库。

### 主从切换过程

- slave leader升级为master
- 其他slave修改为新master的slave
- 客户端修改连接
- 老的master如果重启成功,变为新master的slave



### 集群

使用哨兵,redis每个实例也是全量存储,每个redis存储的内容都是完整的数据,浪费内存且有木桶效应。为了最大化利用内存,可以采用集群,即每台机器存储不同的内容。

简单来讲:通过不断的扩容和增加运行redis实例的机器数量,使得redis能撑得住更大的访问量



### 原理

redis cluster中有一个16384(2^4 * 2^10)长度的槽的概念。通过哈希算法再加上取模运算可以将一个值固定地映射到某个区间,区间由连续的slot组成。

redis cluster采用虚拟槽分区,所有的键根据哈希函数(CRC16[key]&16383)映射到0-16383槽内,共16384个槽位,每个节点维护部分槽及槽所映射的键值数据,可以为节点设置权重,权重高的节点维护的槽的数量比较多。

哈希函数: Hash()=CRC16[key]&16383 按位与

redis用虚拟槽分区原因:解耦数据与节点关系,节点自身维护槽映射关系,分布式存储



### 集群模式

- 客户端模式

在客户端做redis负载均衡,通过服务发现注册redis节点,实现redis节点的动态改变(增删或者节点扩容)

![img](https://i.loli.net/2019/07/12/5d27764947bf559054.png)

优点

- 数据分散,使得整体容量得到提升
- 当一个节点不可用不会导致整体服务不可用
- 性能高,客户端直接连接redis,不通过代理
- 扩容方便

目前企业使用的redis集群基本都是客户端模式,这样可以减轻服务端的压力。

- 代理模式

客户端不是直接连接redis,而是连接代理,这样子客户端的开发就变得简单很多了。。同时服务端也可以控制redis的连接数。

![img](https://i.loli.net/2019/07/12/5d27764e1a87934952.png)

### 分析

集群模式的解决了容量的问题,但是带来了很多的问题,例如redis的热key问题,单点高并发会导致redis负载及其不均衡,进而服务可用性极低。



## 穿透

### 缓存的处理流程

![img](https://i.loli.net/2019/07/12/5d277651adee639077.png)

### 缓存穿透

频繁查询一个不存在的数据,由于缓存不命中,每次都要查询持久层。从而失去缓存的意义。

如果恶意用户知道我们redis key的构造规则,每次故意查询一个在缓存内必然不存在的数据,导致每次请求都要去存储层去查询,这样缓存就失去了意义。如果在高并发下数据库可能挂掉。这就是**缓存击穿**



## 雪崩

redis缓存大量失效的时候,引发大量查询数据库。如果有的数据只放在redis上(例如用来同步多个服务实例信息之间的共享数据),redis缓存失效会导致服务出现异常,这比查数据库更加严重

引起这个问题的原因主要是redis单点被打垮,也就是热key问题。



## 击穿

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力

解决方案:

- 设置频繁访问的数据过期(让缓存持续命中)
- 加分布式锁(如果缓存不存在,只让一个进程去读db,有点类似于单例模式的实现,下面是一个从网上找的小demo)

![img](https://i.loli.net/2019/07/12/5d277650ad21c42141.png)



### 热key

在集群模式下经常会遇到这个问题,业界依然没有好的解决方案(参见微博只要热点事件就垮了)

热key:某个key访问非常的频繁,**redis集群负载不均衡,导致单点延迟增加,从而导致单点redis上的缓存不可用。**

这个问题相当的难以解决,微博在出现热点事件后服务不可用,很大原因是热key问题,导致单点redis被打垮了, 增加db访问量,进而系统雪崩。

因为**redis是单进程单线程** ,redis的处理线程忙起来,会导致redis没空accept来自client的tcp请求,进而出现连接超时的错误,缓存未命中。

### 解决方案

没有具体的解决方案,需要根据业务来制定。思路**将key打散**,就是将redis访问某个热key,转化为访问多个key,减轻单点压力

但并不是所有的业务都能使用该解决方案。

0 comments on commit e550d46

Please sign in to comment.