gitbook/Redis核心技术与实战/docs/308393.md

200 lines
13 KiB
Markdown
Raw Normal View History

2022-09-03 22:05:03 +08:00
# 37 | 数据分布优化:如何应对数据倾斜?
你好,我是蒋德钧。
在切片集群中数据会按照一定的分布规则分散到不同的实例上保存。比如在使用Redis Cluster或Codis时数据都会先按照CRC算法的计算值对Slot逻辑槽取模同时所有的Slot又会由运维管理员分配到不同的实例上。这样数据就被保存到相应的实例上了。
虽然这种方法实现起来比较简单,但是很容易导致一个问题:数据倾斜。
数据倾斜有两类。
* **数据量倾斜**:在某些情况下,实例上的数据分布不均衡,某个实例上的数据特别多。
* **数据访问倾斜**:虽然每个集群实例上的数据量相差不大,但是某个实例上的数据是热点数据,被访问得非常频繁。
如果发生了数据倾斜,那么保存了大量数据,或者是保存了热点数据的实例的处理压力就会增大,速度变慢,甚至还可能会引起这个实例的内存资源耗尽,从而崩溃。这是我们在应用切片集群时要避免的。
今天这节课,我就来和你聊聊,这两种数据倾斜是怎么发生的,我们又该怎么应对。
## 数据量倾斜的成因和应对方法
首先,我们来看数据量倾斜的成因和应对方案。
当数据量倾斜发生时,数据在切片集群的多个实例上分布不均衡,大量数据集中到了一个或几个实例上,如下图所示:
![](https://static001.geekbang.org/resource/image/2c/85/2cb89b2d1b319fb43a5d1b94d7929685.jpg)
那么数据量倾斜是怎么产生的呢这主要有三个原因分别是某个实例上保存了bigkey、Slot分配不均衡以及Hash Tag。接下来我们就一个一个来分析同时我还会给你讲解相应的解决方案。
#### bigkey导致倾斜
第一个原因是某个实例上正好保存了bigkey。bigkey的value值很大String类型或者是bigkey保存了大量集合元素集合类型会导致这个实例的数据量增加内存资源消耗也相应增加。
而且bigkey的操作一般都会造成实例IO线程阻塞如果bigkey的访问量比较大就会影响到这个实例上的其它请求被处理的速度。
其实bigkey已经是我们课程中反复提到的一个关键点了。为了避免bigkey造成的数据倾斜一个根本的应对方法是**我们在业务层生成数据时,要尽量避免把过多的数据保存在同一个键值对中**。
此外,**如果bigkey正好是集合类型我们还有一个方法就是把bigkey拆分成很多个小的集合类型数据分散保存在不同的实例上**。
我给你举个例子。假设Hash类型集合user:info保存了100万个用户的信息是一个bigkey。那么我们就可以按照用户ID的范围把这个集合拆分成10个小集合每个小集合只保存10万个用户的信息例如小集合1保存的是ID从1到10万的用户信息小集合2保存的是ID从10万零1到20万的用户。这样一来我们就可以把一个bigkey化整为零、分散保存了避免了bigkey给单个切片实例带来的访问压力。
需要注意的是当bigkey访问量较大时也会造成数据访问倾斜我一会儿再给你讲具体怎么应对。
接下来我们再来看导致数据量倾斜的第二个原因Slot分配不均衡。
#### Slot分配不均衡导致倾斜
如果集群运维人员没有均衡地分配Slot就会有大量的数据被分配到同一个Slot中而同一个Slot只会在一个实例上分布这就会导致大量数据被集中到一个实例上造成数据倾斜。
我以Redis Cluster为例来介绍下Slot分配不均衡的情况。
Redis Cluster一共有16384个Slot假设集群一共有5个实例其中实例1的硬件配置较高运维人员在给实例分配Slot时就可能会给实例1多分配些Slot把实例1的资源充分利用起来。
但是我们其实并不知道数据和Slot的对应关系这种做法就可能会导致大量数据正好被映射到实例1上的Slot造成数据倾斜给实例1带来访问压力。
为了应对这个问题我们可以通过运维规范在分配之前我们就要避免把过多的Slot分配到同一个实例。如果是已经分配好Slot的集群我们可以先查看Slot和实例的具体分配关系从而判断是否有过多的Slot集中到了同一个实例。如果有的话就将部分Slot迁移到其它实例从而避免数据倾斜。
不同集群上查看Slot分配情况的方式不同如果是Redis Cluster就用CLUSTER SLOTS命令如果是Codis就可以在codis dashboard上查看。
比如说我们执行CLUSTER SLOTS命令查看Slot分配情况。命令返回结果显示Slot 0 到Slot 4095被分配到了实例192.168.10.3上而Slot 12288到Slot 16383被分配到了实例192.168.10.5上。
```
127.0.0.1:6379> cluster slots
1) 1) (integer) 0
2) (integer) 4095
3) 1) "192.168.10.3"
2) (integer) 6379
2) 1) (integer) 12288
2) (integer) 16383
3) 1) "192.168.10.5"
2) (integer) 6379
```
如果某一个实例上有太多的Slot我们就可以使用迁移命令把这些Slot迁移到其它实例上。在Redis Cluster中我们可以使用3个命令完成Slot迁移。
1. CLUSTER SETSLOT使用不同的选项进行三种设置分别是设置Slot要迁入的目标实例Slot要迁出的源实例以及Slot所属的实例。
2. CLUSTER GETKEYSINSLOT获取某个Slot中一定数量的key。
3. MIGRATE把一个key从源实例实际迁移到目标实例。
我来借助一个例子,带你了解下这三个命令怎么用。
假设我们要把Slot 300从源实例ID为3迁移到目标实例ID为5那要怎么做呢
实际上我们可以分成5步。
第1步我们先在目标实例5上执行下面的命令将Slot 300的源实例设置为实例3表示要从实例3上迁入Slot 300。
```
CLUSTER SETSLOT 300 IMPORTING 3
```
第2步在源实例3上我们把Slot 300的目标实例设置为5这表示Slot 300要迁出到实例5上如下所示
```
CLUSTER SETSLOT 300 MIGRATING 5
```
第3步从Slot 300中获取100 个key。因为Slot中的key数量可能很多所以我们需要在客户端上多次执行下面的这条命令分批次获得并迁移key。
```
CLUSTER GETKEYSINSLOT 300 100
```
第4步我们把刚才获取的100个key中的key1迁移到目标实例5上IP为192.168.10.5同时把要迁入的数据库设置为0号数据库把迁移的超时时间设置为timeout。我们重复执行MIGRATE命令把100个key都迁移完。
```
MIGRATE 192.168.10.5 6379 key1 0 timeout
```
最后我们重复执行第3和第4步直到Slot中的所有key都迁移完成。
从Redis 3.0.6开始你也可以使用KEYS选项一次迁移多个keykey1、2、3这样可以提升迁移效率。
```
MIGRATE 192.168.10.5 6379 "" 0 timeout KEYS key1 key2 key3
```
对于Codis来说我们可以执行下面的命令进行数据迁移。其中我们把dashboard组件的连接地址设置为ADDR并且把Slot 300迁移到编号为6的codis server group上。
```
codis-admin --dashboard=ADDR -slot-action --create --sid=300 --gid=6
```
除了bigkey和Slot分配不均衡会导致数据量倾斜还有一个导致倾斜的原因就是使用了Hash Tag进行数据切片。
#### Hash Tag导致倾斜
Hash Tag是指加在键值对key中的一对花括号{}。这对括号会把key的一部分括起来客户端在计算key的CRC16值时只对Hash Tag花括号中的key内容进行计算。如果没用Hash Tag的话客户端计算整个key的CRC16的值。
举个例子假设key是user:profile:3231我们把其中的3231作为Hash Tag此时key就变成了user:profile:{3231}。当客户端计算这个key的CRC16值时就只会计算3231的CRC16值。否则客户端会计算整个“user:profile:3231”的CRC16值。
使用Hash Tag的好处是如果不同key的Hash Tag内容都是一样的那么这些key对应的数据会被映射到同一个Slot中同时会被分配到同一个实例上。
下面这张表就显示了使用Hash Tag后数据被映射到相同Slot的情况你可以看下。
![](https://static001.geekbang.org/resource/image/d5/36/d560b0161f1f786328dbd8a1df66d036.jpg)
其中user:profile:{3231}和user:order:{3231}的Hash Tag一样都是3231它们的CRC16计算值对16384取模后的值也是一样的所以就对应映射到了相同的Slot 1024中。user:profile:{5328}和user:order:{5328}也是相同的映射结果。
那么Hash Tag一般用在什么场景呢其实它主要是用在Redis Cluster和Codis中支持事务操作和范围查询。因为Redis Cluster和Codis本身并不支持跨实例的事务操作和范围查询当业务应用有这些需求时就只能先把这些数据读取到业务层进行事务处理或者是逐个查询每个实例得到范围查询的结果。
这样操作起来非常麻烦所以我们可以使用Hash Tag把要执行事务操作或是范围查询的数据映射到同一个实例上这样就能很轻松地实现事务或范围查询了。
但是使用Hash Tag的潜在问题就是大量的数据可能被集中到一个实例上导致数据倾斜集群中的负载不均衡。那么该怎么应对这种问题呢我们就需要在范围查询、事务执行的需求和数据倾斜带来的访问压力之间进行取舍了。
我的建议是如果使用Hash Tag进行切片的数据会带来较大的访问压力就优先考虑避免数据倾斜最好不要使用Hash Tag进行数据切片。因为事务和范围查询都还可以放在客户端来执行而数据倾斜会导致实例不稳定造成服务不可用。
好了,到这里,我们完整地了解了数据量倾斜的原因以及应对方法。接下来,我们再来看数据访问倾斜的原因和应对方法。
## 数据访问倾斜的成因和应对方法
发生数据访问倾斜的根本原因,就是实例上存在热点数据(比如新闻应用中的热点新闻内容、电商促销活动中的热门商品信息,等等)。
一旦热点数据被存在了某个实例中,那么,这个实例的请求访问量就会远高于其它实例,面临巨大的访问压力,如下图所示:
![](https://static001.geekbang.org/resource/image/94/20/94b1ca50143db1d09c60475fa7b41820.jpg)
那么,我们该如何应对呢?
和数据量倾斜不同热点数据通常是一个或几个数据所以直接重新分配Slot并不能解决热点数据的问题。
通常来说,热点数据以服务读操作为主,在这种情况下,我们可以采用**热点数据多副本**的方法来应对。
这个方法的具体做法是我们把热点数据复制多份在每一个数据副本的key中增加一个随机前缀让它和其它副本数据不会被映射到同一个Slot中。这样一来热点数据既有多个副本可以同时服务请求同时这些副本数据的key又不一样会被映射到不同的Slot中。在给这些Slot分配实例时我们也要注意把它们分配到不同的实例上那么热点数据的访问压力就被分散到不同的实例上了。
这里,有个地方需要注意下,**热点数据多副本方法只能针对只读的热点数据**。如果热点数据是有读有写的话,就不适合采用多副本方法了,因为要保证多副本间的数据一致性,会带来额外的开销。
对于有读有写的热点数据,我们就要给实例本身增加资源了,例如使用配置更高的机器,来应对大量的访问压力。
## 小结
这节课,我向你介绍了数据倾斜的两种情况:数据量倾斜和数据访问倾斜。
造成数据量倾斜的原因主要有三个:
1. 数据中有bigkey导致某个实例的数据量增加
2. Slot手工分配不均导致某个或某些实例上有大量数据
3. 使用了Hash Tag导致数据集中到某些实例上。
而数据访问倾斜的主要原因就是有热点数据存在,导致大量访问请求集中到了热点数据所在的实例上。
为了应对数据倾斜问题,我给你介绍了四个方法,也分别对应了造成数据倾斜的四个原因。我把它们总结在下表中,你可以看下。
![](https://static001.geekbang.org/resource/image/09/6f/092da1ee7425d20b1af4900ec8e9926f.jpg)
当然如果已经发生了数据倾斜我们可以通过数据迁移来缓解数据倾斜的影响。Redis Cluster和Codis集群都提供了查看Slot分配和手工迁移Slot的命令你可以把它们应用起来。
最后关于集群的实例资源配置我再给你一个小建议在构建切片集群时尽量使用大小配置相同的实例例如实例内存配置保持相同这样可以避免因实例资源不均衡而在不同实例上分配不同数量的Slot。
## 每课一问
按照惯例我给你提个小问题在有数据访问倾斜时如果热点数据突然过期了而Redis中的数据是缓存数据的最终值保存在后端数据库此时会发生什么问题
欢迎在留言区写下你的思考和答案,我们一起交流讨论。如果你觉得今天的内容对你有所帮助,也欢迎你分享给你的朋友或同事。我们下节课见。