Redis 中怎么正确的选择数据类型,让维护不再是噩梦

481 阅读7分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第22天,点击查看活动详情

Redis 中,几乎所有的数据都可以使用 string 去表示。在工作中,确实会这个看到这样的问题有很多。刚刚接触 Redis 的时候,选择不好数据类型,虽然能够实现需求解决问题,但是长久下来会埋下一个巨大的坑。

比如,使用 Redis 缓存用户信息,其中,用户含了很多属性,例如 id、name、age 等等。我们通常获取用户信息,一般使用 id 作为唯一标识将用户信息缓存下来。可以将 id 作为 Redis key 的一部分来存储用户的信息。

这样确实初看起来的确能够实现我们想要缓存用户信息的需求。而且由于 id 是唯一的,key 之间呢也不会存在冲突等等。类似的例子呢还有很多。

都使用 String 去存储数据存在问题

浪费存储空间,key 也是需要存储空间的,这是显而易见的,不只是 Redis 中的value 需要存储,key当然也需要去存储。对于一个对象来,它有多少个字段就会用多少个字段类型的键值对去表达它,无疑会在 Redis 中保存有大量的 key,会造成存储空间的大量浪费。

管理维、护成为噩梦。Redis 中存在着大量的 key-value 对象。也正是由于 Redis 中存储了大量的 key-value 对象。无论是迁移管理还是维护都是非常困难的,我们很难去确定当前的 Redis 服务器中保存的对象是否是正确的,是否存在着缺失,原因是显而易见的,大量的键值对使得在验证过程需要消耗大量的时间。

key 冲突的这个几率变高,很多时候在部署 Redis 服务器,不会只是给一个业务系统服务器使用。有时候甚至是一个部门会使用一个 Redis 服务。如果你的业务系统中存储的 key 越多,key 冲突的几率当然也会增高。

那么综上所述,我们可以得出结论,使用 String 类型去保存像用户对象信息,明显是不合理的,导致 Redis 中存储的大量的键值对对象。使用 hash 类型,这种类型本身就是一个 map ,相当于 Java 中的 HashMap,它是键值对类型的数据结构,它非常适合存储一个对象的各种属性信息。而且这会使得对于一个对象的缓存只有一个 key 存在。

不过你可能会去想,我们是否可以将用户对象序列化为 Json 符串作为 String 的键值对去存储,是否是可以的呢?此时也并不会存在上述的弊端,也只会让一个对象对应到一个 key,对于这种存储方式,我们需要考虑这个两种业务场景。

第一,如果你只是简单的去存取整个对象,那么这种方式当然是可行的,毕竟没有使用多个 key 造成空间浪费。

第二,如果你要单独的从 Redis 中获取对象的某个属性值。比如你想要获取某一个用信息对象它的 name 属性,此时,我们只能去使用 dict 或者是 hash。否则我们需要先将 json 字符串从 Redis 中取出来,然后反序列化再去拿到对应的属性值,效率是非常低下的。

这就是是否可以考虑将这个对象序列化之后再存储,我们需要考虑两种业务场景。

在这个选择使用数据类型的时候呢,不能去仅仅考虑这种数据类型适合存储什么,还一定要结合你的业务场景和需求。

不过其实我们还有一个问题没有解决,虽然说选择了正确的数据类型,但是仍然没有给出这个降低 key 冲突的这个解决方案。

key 的命名策略

实际上关于 Redis 的 key 怎样去命名,也确实是没有什么标准。总之记住两点:

第一,key 需要带有一些意义,而不是像 a、b、c 这种没有意义的字母或者是单词。

第二,一定不能与其他的 key 有冲突。一般我们在命名 key 的时候,可以通过这个四个部分去命名,业务线名称+工程名+模块名+键名。业务线名称可以简单的理解为部门名称,因为业务线通常都是由部门来负责的。工程名,每一个业务线下面又会有很多个工程,而且工程肯定都是不同的名字。模块名,对于一个工程来说,我们可以把它拆解为很多个模块,在哪一个模块在使用 redis。有意义的键名,在我们实际保存的对象,比如保存的是 id 唯一的用户对象,键的名称可以是 user_1,这种命名策略是比较复杂的,但是这种策略冲突的概率是非常低的。不过这种策略的一个缺点是 key 过长,会占用一定的 key 的内存空间,需要我们去做一些取舍了。第一种就是如果你的 Redis 内存足够用,保证 key 不会冲突,肯定是最合适的,复杂的 key 是可行的。第二种,如果 Redis 内存比较紧张,就可以调整一下 key 的名称,比如去掉业务线的名称,但是会增加冲突的概率,所以具体怎么样去取舍呢?要根据业务场景和当前的环境配置,只有在这两点都很清晰的基础上,才能用对 redis 以及用好 redis。

Redis 常用的数据类型及适用场景

String:是 redis 最基本的数据类型,也是最常用的数据类型。它的应用场景非常广泛,如果你要缓存的数据是独立存在的,也就是它与其他的缓存对象呢不存在关联关系,那么就可以考虑使用 string 类型去存储。比如计数器、session 等等这样的数据都可以使用 string。因为他们与其他的缓存对象不存在一个关联关系。

hash:hash 底层数据结构比较特殊,它是由一个 dict 或者是一个 map,所以它适合去存储结构化的数据,由多个 key-value 对象共同组织成一个对象的数据信息 hash 类型。保存的结构化数据非常像 mysql 中的一条记录。我们可以方便的去修改某一个字段。但是它更具有灵活性,每个记录能够包含有不同的字段。

list:list 是按照插入顺序排序的字符串列表,允许在头部或者是尾部插入新的元素,它是使用双向链表去实现的。既然它是一个链表,所以我们可以把它当做队列或者战去使用。同时我们可以使用 Redis 的 API 去控制队列中元素的个数。所以它还可以被当做这个有界队列去使用它。栈、有界队列是 list 的最常用的一个适用场景。

set:它是一个集合。它对应到 java 语言,我们可以简单的认为,它是一个 hashset,对于添加、删除、判断元素是否存在这样的方法的时间复杂度 n(1),而且 set 最基本的特性是保证不会出现重复的数据。另外对于多个 set 之间的这个聚合运算,比如交集、并集、差集、操作呢,Redis 也会提供。这一系列的特性使得 set 在类似于社交这种业务场景中有着非常广泛的应用。例如共同关注、共同喜好、数据去重,就可以通过交集、并集、差集这样的 API 去计算得出。

sortedset:现在的应用都会有一些排行榜类似的功能。比如投资网站显示投资金额的排行,购物网站显示消费排行等等,sortedset就非常适合实现这些功能。这种数据类型实际上的原理是在 set 的基础上,给集合中的每一个元素呢关联了一个权重,插入到 sortedset 中的元素,会自动的按照这个权重的去排序。那么排行的业务需求,也就是按照某一个指标对元素进行排序,我们就可以把指标映射到权重。所以 sortedset 就非常适合于这一类这个排行榜的场景。