vlambda博客
学习文章列表

redis-基础数据结构一

前言

Redis 作为基于键值对的 NoSQL 数据库,具有高性能,丰富的数据结构,持久化,高可用,分布式等特性,这些特性使得 Redis 从同等竞品中脱颖而出,现如今已成为互联网公司软件架构的标配。

因此作为一名后端开发人员,有必要也有责任去搞清楚 Redis 的正确使用方式,了解其底层原理有助于工作中问题的排查。

基础数据结构

Redis 与 Memcached 同为内存数据库,并且数据存储都为 Key-Value 键值形式,不同点在于 Redis 的键值具有丰富的数据结构。

  • string(字符串)

  • hash(哈希)

  • list(列表)

  • set(集合)

  • zset(有序集合)

每种数据结构,除了介绍常用 command,还会说明其内部编码,以及常见的使用场景。

简单说明一下 Redis 内部编码的概念。上面讲到的 5 种数据结构其实只是 Redis 对外界提供的数据结构,实际上每种数据结构底层都有多种实现,称为内部编码,Redis 会在合适的场景选择合适的编码。这么做的好处:

  • 可以改进内部编码,而对外的数据结构和命令没有影响,这样一旦开发出更优秀的内部编码,无需改动外部数据结构和命令

  • 多种内部编码实现可以在不同场景下发挥各自的优势

今天我们就来认识一下 5 种基础数据结构的前两种(篇幅原因)字符串和哈希。

字符串

字符串类型是Redis最基础的数据结构。字符串类型的值实际可以是字符串(简单的字符串、复杂的字符串(例如JSON、XML))、数字(整数、浮点数)。

由于 redis 的字符串是二进制安全的,因此也可以存储二进制(图片、音频、视频)字符串,但是值最大不能超过512MB。

命令

添加键值

-- 覆盖式添加
set key value [ex seconds] [px milliseconds] [nx|xx] 

示例:

set hello world

参数说明([] 中的是可选参数):

  • ex seconds:为键设置秒级过期时间

  • px milliseconds:为键设置毫秒级过期时间

  • nx:键必须不存在,才可以设置成功,用于添加

  • xx:与nx相反,键必须存在,才可以设置成功,用于更新

-- key 不存在才添加
setnx key value
-- key 存在才添加
setxx key value

setnx 命令可以作为分布式锁的解决方案,因为是单线程架构,同一时间只有一个客户端能够 setnx 成功。

获取值

get key

如果要获取的键不存在,则返回 nil(空)。

批量设置值

mset key value [key value ...]

示例:

 -- 通过mset命令一次性设置4个键值对
 mset a 1 b 2 c 3 d 4

批量获取值

mget key [key ...]

示例:

-- 批量获取键a、b、c、d的值
mget a b c d 

使用批量操作命令可以提高开发效率,减少 redis client 与 server 通信的次数(网络延迟的消耗)。

计数

incr key

incr命令用于对值做自增操作,返回结果分为三种情况:

  • 值不是整数,返回错误

  • 值是整数,返回自增后的结果

  • 键不存在,按照值为0自增,返回结果为1

相关的命令:decr(自减)、incrby(自增指定数字)、 decrby(自减指定数字)、incrbyfloat(自增浮点数)。

另外 redis 计数操作不存在线程安全问题(redis 单线程架构),因此无需像 java 语言需要引入同步锁或者CAS机制。

内部编码

字符串类型内部的编码有3种:

  • int:8个字节的长整型

  • embstr:小于等于39个字节的字符串

  • raw:大于39个字节的字符串

通过 object encoding key 可以查看 key 的内部编码。

示例1:

127.0.0.1:6379> set intkey 1234
OK
127.0.0.1:6379> object encoding intkey
"int"

示例2:

127.0.0.1:6379> set shortstringkey abc
OK
127.0.0.1:6379> object encoding shortstringkey
"embstr"

示例3:

127.0.0.1:6379> set longstringkey ......一个长度大于 39 字节的字符串
OK
127.0.0.1:6379> object encoding longstringkey
"raw"

使用场景

缓存功能

将热数据缓存在内存当中,加速读写,降低后端压力,另外设置合理的键名,有利于防止键冲突和项目的可维护性。

Tips:比较推荐的方式是使用『业务名:对象名:id:【属性】』作为键名,也可以适当减少键名的长度,减少内存浪费。

计数

许多应用都会使用Redis作为计数的基础工具,它可以实现快速计数、 查询缓存的功能,同时数据可以异步落地到其他数据源,例如社交应用中的点赞数。

共享 session

分布式Web服务将用户的 Session 信息放在 redis 中统一管理。

限速

很多应用出于安全的考虑,会在每次进行登录时,让用户输入手机验证码,从而确定是否是用户本人。但是为了短信接口不被频繁访问,会限制用户每分钟获取验证码的频率,例如一分钟不能超过5次。

哈希

几乎所有的编程语言都提供了哈希(hash)类型,它们的叫法可能是哈希、字典、关联数组。在Redis中,哈希类型是指键值本身又是一个键值对结构,形如value={{field1,value1},…{fieldN,valueN}}

redis-基础数据结构一

注意:哈希类型中的映射关系叫作field-value,注意这里的value是指field对应

的值,不是键对应的值,请注意value在不同上下文的作用

命令

设置值

hset key field value

示例:

127.0.0.1:6379> hset user name zm 
(integer) 1

如果设置成功会返回1,反之会返回0。此外Redis提供了hsetnx命令,它们的关系就像set和setnx命令一样,只不过作用域由键变为field。

获取值

hget key field

示例:

127.0.0.1:6379> hget user name
"zm"

如果键或field不存在,会返回nil。

删除field

hdel key field [field ...]

示例:

127.0.0.1:6379> hdel user name
(integer) 1
127.0.0.1:6379> hget user name
(nil)

计算 field 个数

hlen key

返回 key 中 field-value 对的个数。

批量设置或获取 field-value

-- 批量设置
hmget key field [field ...] 
-- 批量获取
hmset key field value [field value ...]

示例:

127.0.0.1:6379> hmget user name age
1) "zm"
2) "25"

判断 field 是否存在

hexists key field

存在返回 1,不存在返回 0。

获取所有 field

hkeys key

示例:

127.0.0.1:6379> hkeys user
1) "age"
2) "name"

获取所有 value

hvals key

示例:

127.0.0.1:6379> hvals user
1) "25"
2) "zm"

获取所有的 field-value

hgetall key

示例:

127.0.0.1:6379> hgetall user
1) "age"
2) "25"
3) "name"
4) "zm"

注意:在使用hgetall时,如果哈希元素个数比较多,会存在阻塞Redis的可能。如果开发人员只需要获取部分field,可以使用hmget,如果一定要获取全部 field-value,可以使用hscan命令。

内部编码

哈希类型的内部编码有两种,ziplist(压缩列表),hashtable(哈希表)。

ziplist

当哈希类型元素个数小于hash-max-ziplist-entries 配置(默认512个)、同时所有值都小于hash-max-ziplist-value配置(默认64 字节)时,Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的

结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。

hashtable

当哈希类型无法满足 ziplist 的条件时,Redis会使用 hashtable 作为哈希的内部实现,因为此时 ziplist 的读写效率会下降,而 hashtable 的读写时间复杂度为O(1)。

使用场景

很典型的一个使用场景:用来存储对象,因为对象的属性本身就是 field-value 形式的。

redis-基础数据结构一

当然不使用哈希也能存储对象信息,也可以使用字符串。

原生字符串类型:每个属性一个键

示例:

set user:1:name zm 
set user:1:age 25 
set user:1:city 上海

缺点:占用过多的键,内存占用量较大,同时用户信息内聚性比较差,一般不推荐使用这种方式存储。

序列化字符串类型:将用户信息序列化后用一个键保存

示例:

set user:1 serialize(userInfo)

缺点:虽然节省了内存的开销,但是序列化和反序列化有一定的开销,同时每次更新属性都需要把全部数据取出进行反序列化,更新后再序列化到Redis中。

哈希类型:每个用户属性使用一对field-value,但是只用一个键保存

示例:

hmset user:1 name zm age 25 city zhiming

哈希如果使用合理的话,不仅优化了内存使用,也省去了序列化反序列化的开销。

注意:要控制哈希在ziplist和hashtable两种内部编码的转换,hashtable会消耗更多内存。

总结

通过这篇文章,我们了解了:

  • redis 有哪些基础数据结构?

  • 字符串类型的常用命令,内部编码,使用场景

  • 哈希类型的常用命令,内部编码,使用场景

在下一篇文章中继续补充剩下的数据结构——列表,集合,有序集合。如果觉得文章对你有帮助,欢迎留言点赞。



大家的关注是我持续输出的动力

长按扫码可关注