目录
在工作中 Redis 的使用非常广泛,它是提升性能的利器,那怎么样才能发挥出 Redis 的最大优势呢?那就是良好的使用规范,遵循 Redis 使用规范不仅能提升系统性能、节省资源、保障数据安全,还能极大地改善系统的可维护性、可靠性和长期可持续发展能力。
今天就来介绍一下 Redis 的使用规范问题。
一、键值对使用规范
关于键值对的使用规范,主要注意下面两点:
- key 命名规范,只有命名规范,才能提供可读性强、可维护性好的 Key,方便日常管理;
- Value 涉及规范:包括避免使用 bigKey、选择高效序列化和压缩方法、使用整数对象共享池、数据类型选择等。
1.1 key 的命名规范
一个 Redis 实例默认可以支持 16 个数据库,我们可以把不同的业务数据分散保存到不同的数据库中。
但是,在使用不同数据库时,客户端需要使用 SELECT 命令进行数据库切换,相当于增加了一个额外的操作。
其实,我们可以通过合理命名 key,减少这个操作。具体的做法是,把业务名作为前缀,然后用冒号分隔,再加上具体的业务数据名。这样一来,我们可以通过 key 的前缀区分不同的业务数据,就不用在多个数据库间来回切换了。
比如说,如果我们要统计网页的独立访客量,就可以用下面的代码设置 key,这就表示,这个数据对应的业务是统计 unique visitor(独立访客量),而且对应的页面编号是 1024。
这里有一个地方需要注意一下。key 本身是字符串,底层的数据结构是 SDS。SDS 结构中会包含字符串长度、分配空间大小等元数据信息。从 Redis 3.2 版本开始,当 key 字符串的长度增加时,SDS 中的元数据也会占用更多内存空间。
所以,我们在设置 key 的名称时,要注意控制 key 的长度。否则,如果 key 很长的话,就会消耗较多内存空间,而且,SDS 元数据也会额外消耗一定的内存空间。
SDS 结构中的字符串长度和元数据大小的对应关系如下表所示:
字符串大小(字节) | SDS结构元数据大小(字节) |
1 ~ 2^5 - 1 | 1 |
2^5 ~ 2^8 -1 | 3 |
2^8 ~ 2^16 -1 | 5 |
2^16 ~ 2^32 -1 | 9 |
2^32 ~ 2^64 -1 | 11 |
为了减少 key 占用的内存空间,对于业务名或业务数据名,可以使用相应的英文单词的首字母表示,(比如 user 用 u 表示,message 用 m),或者是用缩写表示(例如 unique visitor 使用 uv)。
1.2 避免使用 bigkey
Redis 使用单线程读写数据,bigkey 的读写操作会阻塞线程,降低 Redis 的处理效率。所以在使用 Redis 时关于 value 的设计规范,非常重要的一点就是避免使用 bigkey。
bigkey 通常有两种情况:
- 键值对的值大小本身就很大:例如 value 为几 MB 的 String 类型的数据,为了避免 String 类型的 bigkey,在业务层,要尽量把 String 类型的数据压缩到 10KB 一下;
- 键值对的值是集合类型:集合元素个数非常多,例如包含上百万个元素的 Hash 集合类型数据。为了避免集合类型的 bigkey,建议的设计规范是,尽量把集合类型的元素个数控制在 1 万一下。
当然,这些建议只是为了尽量避免 bigkey,如果业务层的 String 类型数据确实很大,可以通过数据压缩来减小数据大小。如果集合元素的确很多,可以将一个大集合拆分成多个小集合来保存。
这里,还有个地方需要注意下,Redis 的 4 种集合类型 List、Hash、Set 和 Sorted Set,在集合元素个数小于一定的阈值时,会使用内存紧凑型的底层数据结构进行保存,从而节省内存。例如,假设 Hash 集合的 hash-max-ziplist-entries 配置项是 1000,如果 Hash 集合元素个数不超过 1000,就会使用 ziplist 保存数据。
紧凑型数据结构虽然可以节省内存,但是会在一定程度上导致数据的读写性能下降。所以,如果业务应用更加需要保持高性能访问,而不是节省内存的话,在不会导致 bigkey 的前提下,就不用刻意控制集合元素个数了。
1.3 使用高效的序列化方法和压缩算法
为了节省内存,除了采用紧凑型数据结构以外,我们还可以遵循两个使用规范,分别是使用高效的序列化方法和压缩方法,这样可以减少 value 的大小。
Redis 中的字符串都是使用二进制安全的字节数组来保存的,所以,我们可以把业务数据序列化成二进制数据写入到 Redis 中。
但是,不同的序列化方法,在序列化速度和数据序列化后的占用内存空间这两个方面,效果是不一样的。比如说,protostuff 和 kryo 这两种序列化方法,就要比 Java 内置的序列化方法(java-build-in-serializer)效率更高。
此外,业务应用有时会使用字符串形式的 XML 和 JSON 格式保存数据。这样做的好处是,这两种格式的可读性好,便于调试,不同的开发语言都支持这两种格式的解析。缺点在于,XML 和 JSON 格式的数据占用的内存空间比较大。为了避免数据占用过大的内存空间,建议使用压缩工具(例如 snappy 或 gzip),把数据压缩后再写入 Redis,这样就可以节省内存空间了。
1.4 使用整数对象共享池
整数是常用的数据类型,Redis 内部维护了0到9999这1万个整数对象,并把这些整数作为一个共享池使用。
换句话说,如果一个键值对中有0到9999范围的整数,Redis 就不会为这个键值对专门创建整数对象了,而是会复用对象池中的整数对象。
这样一来,即使大量键值对保存了0到9999范围内的整数,在 Redis 实例中其实只保存了一份整数对象,可以节省空间。
基于这个特点,在满足业务数据需求的前提下,能用整数时就尽量用整数,这样可以节省实例内存。
那什么时候不能用整数对象共享池呢?主要有两种情况。
- 如果 Redis 中设置了 maxmemory,而且启用了 LRU 策略(allkeys-lru 或 volatitle-lru),那么整数对象共享池就无法使用了。因为,LRU 策略需要统计每个键值对的使用时间,如果不同的键值对都共享使用一个整数对象,LRU 策略就无法进行统计了。
- 如果集合类型采用了 ziplist 编码,而集合元素是整数,这时也不能使用共享池。因为 ziplist 使用了紧凑型内存结构,判断整数对象的共享情况时效率低。
二、数据保存使用规范
2.1 使用 Redis 保存热数据
为了提供高性能访问,Redis 是把所有数据保存到内存中的。
虽然 Redis 支持使用 RDB 快照和 AOF 日志持久化保存数据,但是,这两个机制都是用来提供数据可靠性保证的,并不是用来扩充数据容量的。而且,内存成本本身就比较高,如果把业务数据都保存在 Redis 中,会带来较大的内存成本压力。
所以,一般来说,在实际应用 Redis 时,我们会更多地把它作为缓存保存热数据,这样既可以充分利用 Redis 的高性能特性,还可以把宝贵的内存资源用在服务热数据上。
2.2 不同的业务数据分实例存储
虽然我们可以使用 key 的前缀把不同业务的数据区分开,但是,如果所有业务的数据量都很大,而且访问特征也不一样,我们把这些数据保存在同一个实例上时,这些数据的操作就会相互干扰。
你可以想象这样一个场景:假如数据采集业务使用 Redis 保存数据时,以写操作为主,而用户统计业务使用 Redis 时,是以读查询为主,如果这两个业务数据混在一起保存,读写操作相互干扰,肯定会导致业务响应变慢。
建议把不同的业务数据放到不同的 Redis 实例中。这样一来,既可以避免单实例的内存使用量过大,也可以避免不同业务的操作相互干扰。
2.3 在数据保存时,要设置过期时间
对于 Redis 来说,内存是非常宝贵的资源,而且,Redis 通常用于保存热数据。热数据一般都有使用的时效性。所以,在数据保存时,建议根据业务使用数据的时长,设置数据的过期时间。不然的话,写入 Redis 的数据会一直占用内存,如果数据持续增多,就可能达到机器的内存上限,造成内存溢出,导致服务崩溃。
2.4 控制 Redis 实例的容量
Redis 单实例的内存大小都不要太大,根据经验值,建议设置在 2~6GB 。这样一来,无论是 RDB 快照,还是主从集群进行数据同步,都能很快完成,不会阻塞正常请求的处理。
三、命令使用规范
3.1 线上禁用部分命令
Redis 是单线程处理请求操作,如果我们执行一些涉及大量操作、耗时长的命令,就会严重阻塞主线程,导致其它请求无法得到正常处理,这类命令主要有 3 种
- keys命令:按照键值对的 key 内容进行匹配,返回匹配条件的键值对,该命令需要对 Redis 全局的 hash 表进行全表扫描,严重阻塞 Redis 主线程
- flushall命令:删除 Redis 实例上的所有数据,如果数据量很大,会严重阻塞 Redis 主线程
- flushdb命令:删除当前数据库中的数据,如果数据量很大,同样会阻塞 Redis 主线程
所以,我们在线上应用 Redis 时,就需要禁用这些命令。
3.2 慎用 monitor 命令
Redis 的 MONITOR 命令在执行后,会持续输出监测到的各个命令操作,所以,我们通常会用 MONITOR 命令返回的结果,检查命令的执行情况。
但是,MONITOR 命令会把监控到的内容持续写入输出缓冲区。如果线上命令的操作很多,输出缓冲区很快就会溢出了,这就会对 Redis 性能造成影响,甚至引起服务崩溃。
所以,除非十分需要监测某些命令的执行(例如,Redis 性能突然变慢,我们想查看下客户端执行了哪些命令),你可以偶尔在短时间内使用下 MONITOR 命令,否则,我建议你不要使用 MONITOR 命令。
3.3 慎用全量操作命令
对于集合类型的数据来说,如果想要获得集合中的所有元素,一般不建议使用全量操作的命令(例如 Hash 类型的 HGETALL、Set 类型的 SMEMBERS)。这些操作会对 Hash 和 Set 类型的底层数据结构进行全量扫描,如果集合类型数据较多的话,就会阻塞 Redis 主线程。
综上所述,遵循Redis使用规范不仅能提升系统性能、节省资源、保障数据安全,还能极大地改善系统的可维护性、可靠性和长期可持续发展能力。
往期经典推荐
实时数据传输的新里程——Server-Sent Events(SSE)消息推送技术-CSDN博客
Redis 6.0进化之路:关键新特性详解_redis 6.0 特性-CSDN博客
决胜高并发战场:Redis并发访问控制与实战解析-CSDN博客