
1. Hash
几乎所有的主流编程语⾔都提供了哈希(hash)类型,它们的叫法可能是哈希、字典、关联数组、映射。
在Redis中,哈希类型是指值本⾝⼜是⼀个键值对结构,形如key=“key”,value={{ field1, value1 } …{fieldN, valueN } },Redis 键值对和哈希类型⼆者的关系可以⽤下图来表⽰。
哈希类型中的映射关系通常称为field-value,⽤于区分Redis整体的键值对(key-value),注意这⾥的value是指field对应的值,不是key对应的值
1.1 常用命令
hset
:设置hash中指定的字段(field)的值(value)
HSET key field value [field value ...]
hget
:获取hash中单个指定字段的值
HGET key field
hmget
:获取hash中指定多个字段的值
HMGET key field [field ...]
hexists
:判断hash中是否有指定的字段。
HEXISTS key field
hdel
:删除hash中指定的字段,注意与del区分
HDEL key field [field ...]
-
hkeys
:获取hash中的所有字段。 -
hvals
:获取hash中的所有的值。 -
hgetall
:获取hash中的所有字段以及对应的值。
hlen
:获取hash中的所有字段的个数HSETNX
:在字段不存在的情况下,设置hash中的字段和值。HINCRBY
:将hash中字段对应的数值添加指定的值。HINCRBYFLOAT
: HINCRBY的浮点数版本
1.2 内部编码
哈希的内部编码有两种:
- 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时,内部编码为ziplist:
127.0.0.1:6379> hmset hashkey f1 v1 f2 v2
OK
127.0.0.1:6379> object encoding hashkey
"ziplist"
- 当有value⼤于64字节时,内部编码会转换为hashtable
127.0.0.1:6379> hset hashkey f3 "one string is bigger than 64 bytes ... 省略 ..."
OK
127.0.0.1:6379> object encoding hashkey
"hashtable"
- 当field个数超过512时,内部编码也会转换为hashtable
127.0.0.1:6379> hmset hashkey f1 v1 h2 v2 f3 v3 ... 省略 ... f513 v513
OK
127.0.0.1:6379> object encoding hashkey
"hashtable"
1.3 应用场景
如果要频繁访问数据库中某条用户的信息,对数据库服务器的压力是比较大的,此时就可以使用redis中的哈希结构作为缓存。
下图为关系型数据表记录的两条用户信息,用户的属性表示为表的列,每条用户信息表示为行。
如果映射关系表示这两个用户信息,则如下图所示
相比于使用JSON格式的字符串缓存用户信息,哈希类型变得更加直观,并且在更新操作上变得更灵活。可以将每个用户的id定义为键后缀,多对field-value对应用户的各个属性。
但是需要注意的是哈希类型和关系型数据库有两点不同之处:
- 哈希类型是稀疏的,⽽关系型数据库是完全结构化的,例如哈希类型每个键可以有不同的field,⽽关系型数据库⼀旦添加新的列,所有⾏都要为其设置值,即使为null
- 关系数据库可以做复杂的关系查询,⽽Redis去模拟关系型复杂查询,例如联表查询、聚合查询等基本不可能,维护成本高。
截至目前为止,我们已经能够用三种方法缓存用户信息,下⾯给出三种方案的实现方法和优缺点。
- 原⽣字符串类型⸺使⽤字符串类型,每个属性⼀个键。
set user:1:name James
set user:1:age 23
set user:1:city Beijing
优点:实现简单,针对个别属性变更也很灵活。
缺点:占⽤过多的键,内存占⽤量较⼤,同时⽤⼾信息在Redis中⽐较分散,缺少内聚性,所以这种⽅案基本没有实⽤性。
- 序列化字符串类型,例如JSON格式
set user:1 经过序列化后的⽤⼾对象字符串
优点:针对总是以整体作为操作的信息⽐较合适,编程也简单。同时,如果序列化⽅案选择合适,内存的使⽤效率很⾼。
缺点:本⾝序列化和反序列需要⼀定开销,同时如果总是操作个别属性则⾮常不灵活。
- 哈希类型
hmset user:1 name James age 23 city Beijing
优点:简单、直观、灵活,尤其是针对信息的局部变更或者获取操作。
缺点:需要控制哈希在ziplist和hashtable两种内部编码的转换,可能会造成内存的较大消耗。
2. List列表
列表类型是用来存储多个有序的字符串,如下图所示,a、b、c、d、e五个元素从左到右组成了⼀个有序的列表,列表中的每个字符串称为元素(element),⼀个列表最多可以存储232 − 1个元素。
在Redis中,可以对列表两端插⼊和弹出,还可以获取指定范围的元素列表、获取指定索引下标的元素等。
列表是⼀种⽐较灵活的数据结构,它可以充当栈和队列的⻆⾊,在实际开发上有很多应⽤场景。
列表类型的特点:
- 列表中的元素是“有序”的,并不是指升序/降序,而是指顺序很关键。
- 这意味着如果把元素的位置颠倒,得到的新list和之前的list是不等价的。这意味着可以通过索引下标获取某个元素或者某个范围的元素列表
- 列表中的元素是允许重复的
2.1 常用命令
lpush
:将⼀个或者多个元素从左侧放⼊(头插)到list中
LPUSH key element [element ...]
lpushx
:在key存在时,将⼀个或者多个元素从左侧放⼊(头插)到list中;不存在,直接返回
LPUSHX key element [element ...]
rpush
:将⼀个或者多个元素从右侧放⼊(尾插)到list中
RPUSH key element [element ...]
rpushx
:在key存在时,将⼀个或者多个元素从右侧放⼊(尾插)到list中,不存在,直接返回
RPUSHX key element [element ...]
lrange
:获取从start到end区间的所有元素,左闭右闭
LRANGE key start stop
lpop
:从list 左侧取出元素(即头删)
LPOP key
rpop
:从list 右侧取出元素(即尾删)
RPOP key
lindex
:获取从左数第index位置的元素
LINDEX key index
linsert
:在特定位置插⼊元素
LINSERT key <BEFORE | AFTER> pivot element
其中,pivot是基准元素的值,不是下标;若存在多个基准值,则已第一个为基准
lrem
:从列表中删除首次出现的元素,元素等于element
LREM key count element
- count > 0:从左向右删除 |count| 个
- count < 0:从右向左删除 |count| 个
- count = 0:删除所有的
ltrim
:保留[start,stop],区间外的元素删除
LTRIM key start stop
lset
:根据下标修改元素,下标不合法,报错
LSET key index element
llen
:获取list的长度
阻塞版本命令
blpop
和brpop
是lpop和rpop的阻塞版本,和对应⾮阻塞版本的作⽤基本⼀致,除了:
- 在列表中有元素的情况下,阻塞和非阻塞表现是⼀致的。
- 但如果列表中没有元素,非阻塞版本会立即返回nil,但阻塞版本会根据timeout,阻塞⼀段时间,期间Redis可以执行其他命令,但要求执行该命令的客⼾端会表现为阻塞状态。
- 命令中如果设置了多个键,那么会从左向右进⾏遍历键,⼀旦有⼀个键对应的列表中可以弹出元素,命令立即返回。
- 如果多个客⼾端同时对⼀个键执行pop,则最先执⾏命令的客⼾端会得到弹出的元素
命令总结:
2.2 内部编码
列表类型的内部编码有两种:
- ziplist(压缩列表):当列表的元素个数小于list-max-ziplist-entries配置(默认512个),同时列表中每个元素的长度都⼩于list-max-ziplist-value配置(默认64字节)时,Redis会选用ziplist来作为列表的内部编码实现来减少内存消耗。
- linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现。
- 在redis新的版本中,使用quicklist代替上面两种方式;quicklist整体还是链表,但是链表的每个节点,又是一个压缩列表。
- 当元素个数较少且没有⼤元素时,内部编码为ziplist
127.0.0.1:6379> rpush listkey e1 e2 e3
OK
127.0.0.1:6379> object encoding listkey
"ziplist"
- 当元素个数超过512时,内部编码为linkedlist
127.0.0.1:6379> rpush listkey e1 e2 e3 ... 省略 e512 e513
OK
127.0.0.1:6379> object encoding listkey
"linkedlist
- 当某个元素的长度超过64字节时,内部编码为linkedlist
127.0.0.1:6379> rpush listkey "one string is bigger than 64 bytes ... 省略 ..."
OK
127.0.0.1:6379> object encoding listkey
"linkedlist"
2.3 应用场景
- 消息队列
如下图所示,Redis可以使⽤lpush+brpop命令组合实现经典的阻塞式⽣产者-消费者模型队列,⽣产者客⼾端使⽤lpush从列表左侧插⼊元素,多个消费者客⼾端使⽤brpop命令阻塞式地从队列中"争抢"队⾸元素。通过多个客⼾端来保证消费的负载均衡和⾼可⽤性。
- 分频道的消息队列
如下图所⽰,Redis同样使用lpush+brpop命令,但通过不同的键模拟频道的概念,不同的消费者可以通过brpop不同的键值,实现订阅不同频道的理念。