You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

178 lines
14 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 加餐(六)| Redis的使用规范小建议
你好,我是蒋德钧。
今天的加餐我们来聊一个轻松点儿的话题我来给你介绍一下Redis的使用规范包括键值对使用、业务数据保存和命令使用规范。
毕竟高性能和节省内存是我们的两个目标只有规范地使用Redis才能真正实现这两个目标。如果说之前的内容教会了你怎么用那么今天的内容就是帮助你用好Redis尽量不出错。
好了,话不多说,我们来看下键值对的使用规范。
## 键值对使用规范
关于键值对的使用规范,我主要想和你说两个方面:
1. key的命名规范只有命名规范才能提供可读性强、可维护性好的key方便日常管理
2. value的设计规范包括避免bigkey、选择高效序列化方法和压缩方法、使用整数对象共享池、数据类型选择。
### 规范一key的命名规范
一个Redis实例默认可以支持16个数据库我们可以把不同的业务数据分散保存到不同的数据库中。
但是在使用不同数据库时客户端需要使用SELECT命令进行数据库切换相当于增加了一个额外的操作。
其实我们可以通过合理命名key减少这个操作。具体的做法是把业务名作为前缀然后用冒号分隔再加上具体的业务数据名。这样一来我们可以通过key的前缀区分不同的业务数据就不用在多个数据库间来回切换了。
我给你举个简单的小例子看看具体怎么命名key。
比如说如果我们要统计网页的独立访客量就可以用下面的代码设置key这就表示这个数据对应的业务是统计unique visitor独立访客量而且对应的页面编号是1024。
```
uv:page:1024
```
这里有一个地方需要注意一下。key本身是字符串底层的数据结构是SDS。SDS结构中会包含字符串长度、分配空间大小等元数据信息。从Redis 3.2版本开始,**当key字符串的长度增加时SDS中的元数据也会占用更多内存空间**。
所以,**我们在设置key的名称时要注意控制key的长度**。否则如果key很长的话就会消耗较多内存空间而且SDS元数据也会额外消耗一定的内存空间。
SDS结构中的字符串长度和元数据大小的对应关系如下表所示
![](https://static001.geekbang.org/resource/image/5a/0c/5afd9d57af8edd1ae69e62b1c998050c.jpg?wh=1966*784)
为了减少key占用的内存空间我给你一个小建议对于业务名或业务数据名可以使用相应的英文单词的首字母表示比如user用u表示message用m或者是用缩写表示例如unique visitor使用uv
### 规范二避免使用bigkey
Redis是使用单线程读写数据bigkey的读写操作会阻塞线程降低Redis的处理效率。所以在应用Redis时关于value的设计规范非常重要的一点就是避免bigkey。
bigkey通常有两种情况。
* 情况一键值对的值大小本身就很大例如value为1MB的String类型数据。为了避免String类型的bigkey在业务层我们要尽量把String类型的数据大小控制在10KB以下。
* 情况二键值对的值是集合类型集合元素个数非常多例如包含100万个元素的Hash集合类型数据。为了避免集合类型的bigkey我给你的设计规范建议是**尽量把集合类型的元素个数控制在1万以下**。
当然这些建议只是为了尽量避免bigkey如果业务层的String类型数据确实很大我们还可以通过数据压缩来减小数据大小如果集合类型的元素的确很多我们可以将一个大集合拆分成多个小集合来保存。
这里还有个地方需要注意下Redis的4种集合类型List、Hash、Set和Sorted Set在集合元素个数小于一定的阈值时会使用内存紧凑型的底层数据结构进行保存从而节省内存。例如假设Hash集合的hash-max-ziplist-entries配置项是1000如果Hash集合元素个数不超过1000就会使用ziplist保存数据。
紧凑型数据结构虽然可以节省内存但是会在一定程度上导致数据的读写性能下降。所以如果业务应用更加需要保持高性能访问而不是节省内存的话在不会导致bigkey的前提下你就不用刻意控制集合元素个数了。
### 规范三:使用高效序列化方法和压缩方法
为了节省内存除了采用紧凑型数据结构以外我们还可以遵循两个使用规范分别是使用高效的序列化方法和压缩方法这样可以减少value的大小。
Redis中的字符串都是使用二进制安全的字节数组来保存的所以我们可以把业务数据序列化成二进制数据写入到Redis中。
但是,**不同的序列化方法,在序列化速度和数据序列化后的占用内存空间这两个方面,效果是不一样的**。比如说protostuff和kryo这两种序列化方法就要比Java内置的序列化方法java-build-in-serializer效率更高。
此外业务应用有时会使用字符串形式的XML和JSON格式保存数据。
这样做的好处是,这两种格式的可读性好,便于调试,不同的开发语言都支持这两种格式的解析。
缺点在于XML和JSON格式的数据占用的内存空间比较大。为了避免数据占用过大的内存空间我建议使用压缩工具例如snappy或gzip把数据压缩后再写入Redis这样就可以节省内存空间了。
### 规范四:使用整数对象共享池
整数是常用的数据类型Redis内部维护了0到9999这1万个整数对象并把这些整数作为一个共享池使用。
换句话说如果一个键值对中有0到9999范围的整数Redis就不会为这个键值对专门创建整数对象了而是会复用共享池中的整数对象。
这样一来即使大量键值对保存了0到9999范围内的整数在Redis实例中其实只保存了一份整数对象可以节省内存空间。
基于这个特点,我建议你,在满足业务数据需求的前提下,能用整数时就尽量用整数,这样可以节省实例内存。
那什么时候不能用整数对象共享池呢?主要有两种情况。
第一种情况是,**如果Redis中设置了maxmemory而且启用了LRU策略allkeys-lru或volatile-lru策略那么整数对象共享池就无法使用了**。这是因为LRU策略需要统计每个键值对的使用时间如果不同的键值对都共享使用一个整数对象LRU策略就无法进行统计了。
第二种情况是如果集合类型数据采用ziplist编码而集合元素是整数这个时候也不能使用共享池。因为ziplist使用了紧凑型内存结构判断整数对象的共享情况效率低。
好了,到这里,我们了解了和键值对使用相关的四种规范,遵循这四种规范,最直接的好处就是可以节省内存空间。接下来,我们再来了解下,在实际保存数据时,该遵循哪些规范。
## 数据保存规范
### 规范一使用Redis保存热数据
为了提供高性能访问Redis是把所有数据保存到内存中的。
虽然Redis支持使用RDB快照和AOF日志持久化保存数据但是这两个机制都是用来提供数据可靠性保证的并不是用来扩充数据容量的。而且内存成本本身就比较高如果把业务数据都保存在Redis中会带来较大的内存成本压力。
所以一般来说在实际应用Redis时我们会更多地把它作为缓存保存热数据这样既可以充分利用Redis的高性能特性还可以把宝贵的内存资源用在服务热数据上就是俗话说的“好钢用在刀刃上”。
### 规范二:不同的业务数据分实例存储
虽然我们可以使用key的前缀把不同业务的数据区分开但是如果所有业务的数据量都很大而且访问特征也不一样我们把这些数据保存在同一个实例上时这些数据的操作就会相互干扰。
你可以想象这样一个场景假如数据采集业务使用Redis保存数据时以写操作为主而用户统计业务使用Redis时是以读查询为主如果这两个业务数据混在一起保存读写操作相互干扰肯定会导致业务响应变慢。
那么,**我建议你把不同的业务数据放到不同的 Redis 实例中**。这样一来,既可以避免单实例的内存使用量过大,也可以避免不同业务的操作相互干扰。
### 规范三:在数据保存时,要设置过期时间
对于Redis来说内存是非常宝贵的资源而且Redis通常用于保存热数据。热数据一般都有使用的时效性。
所以在数据保存时我建议你根据业务使用数据的时长设置数据的过期时间。不然的话写入Redis的数据会一直占用内存如果数据持续增多就可能达到机器的内存上限造成内存溢出导致服务崩溃。
### 规范四控制Redis实例的容量
Redis单实例的内存大小都不要太大根据我自己的经验值建议你设置在 2~6GB 。这样一来无论是RDB快照还是主从集群进行数据同步都能很快完成不会阻塞正常请求的处理。
## 命令使用规范
最后我们再来看下在使用Redis命令时要遵守什么规范。
### 规范一:线上禁用部分命令
Redis 是单线程处理请求操作如果我们执行一些涉及大量操作、耗时长的命令就会严重阻塞主线程导致其它请求无法得到正常处理这类命令主要有3种。
* **KEYS**按照键值对的key内容进行匹配返回符合匹配条件的键值对该命令需要对Redis的全局哈希表进行全表扫描严重阻塞Redis主线程
* **FLUSHALL**删除Redis实例上的所有数据如果数据量很大会严重阻塞Redis主线程
* **FLUSHDB**删除当前数据库中的数据如果数据量很大同样会阻塞Redis主线程。
所以我们在线上应用Redis时就需要禁用这些命令。**具体的做法是管理员用rename-command命令在配置文件中对这些命令进行重命名让客户端无法使用这些命令**。
当然你还可以使用其它命令替代这3个命令。
* 对于KEYS命令来说你可以用SCAN命令代替KEYS命令分批返回符合条件的键值对避免造成主线程阻塞
* 对于FLUSHALL、FLUSHDB命令来说你可以加上ASYNC选项让这两个命令使用后台线程异步删除数据可以避免阻塞主线程。
### 规范二慎用MONITOR命令
Redis的MONITOR命令在执行后会持续输出监测到的各个命令操作所以我们通常会用MONITOR命令返回的结果检查命令的执行情况。
但是MONITOR命令会把监控到的内容持续写入输出缓冲区。如果线上命令的操作很多输出缓冲区很快就会溢出了这就会对Redis性能造成影响甚至引起服务崩溃。
所以除非十分需要监测某些命令的执行例如Redis性能突然变慢我们想查看下客户端执行了哪些命令你可以偶尔在短时间内使用下MONITOR命令否则我建议你不要使用MONITOR命令。
### 规范三:慎用全量操作命令
对于集合类型的数据来说如果想要获得集合中的所有元素一般不建议使用全量操作的命令例如Hash类型的HGETALL、Set类型的SMEMBERS。这些操作会对Hash和Set类型的底层数据结构进行全量扫描如果集合类型数据较多的话就会阻塞Redis主线程。
如果想要获得集合类型的全量数据,我给你三个小建议。
* 第一个建议是你可以使用SSCAN、HSCAN命令分批返回集合中的数据减少对主线程的阻塞。
* 第二个建议是你可以化整为零把一个大的Hash集合拆分成多个小的Hash集合。这个操作对应到业务层就是对业务数据进行拆分按照时间、地域、用户ID等属性把一个大集合的业务数据拆分成多个小集合数据。例如当你统计用户的访问情况时就可以按照天的粒度把每天的数据作为一个Hash集合。
* 最后一个建议是如果集合类型保存的是业务数据的多个属性而每次查询时也需要返回这些属性那么你可以使用String类型将这些属性序列化后保存每次直接返回String数据就行不用再对集合类型做全量扫描了。
## 小结
这节课我围绕Redis应用时的高性能访问和节省内存空间这两个目标分别在键值对使用、命令使用和数据保存三方面向你介绍了11个规范。
我按照强制、推荐、建议这三个类别,把这些规范分了下类,如下表所示:
![](https://static001.geekbang.org/resource/image/f2/fd/f2513c69d7757830e7f3e3c831fcdcfd.jpg?wh=2612*1819)
我来解释一下这3个类别的规范。
* 强制类别的规范这表示如果不按照规范内容来执行就会给Redis的应用带来极大的负面影响例如性能受损。
* 推荐类别的规范:这个规范的内容能有效提升性能、节省内存空间,或者是增加开发和运维的便捷性,你可以直接应用到实践中。
* 建议类别的规范:这类规范内容和实际业务应用相关,我只是从我的经历或经验给你一个建议,你需要结合自己的业务场景参考使用。
我再多说一句你一定要熟练掌握这些使用规范并且真正地把它们应用到你的Redis使用场景中提高Redis的使用效率。
## 每课一问
按照惯例我给你提个小问题你在日常应用Redis时有遵循过什么好的使用规范吗
欢迎在留言区分享一下你常用的使用规范,我们一起交流讨论。如果你觉得今天的内容对你有所帮助,也欢迎你分享给你的朋友或同事。我们下节课见。