一起养成写作习惯!这是我参与「掘金日新计划 · 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 就非常适合于这一类这个排行榜的场景。