redis-3-Hash-List

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • [1. Hash类型](#1. Hash类型)
    • [1.1 hset,hget,hexists,hdel](#1.1 hset,hget,hexists,hdel)
    • [1.2 hkeys](#1.2 hkeys)
    • [1.3 hvals](#1.3 hvals)
    • [1.4 hgetall和hmget](#1.4 hgetall和hmget)
    • [1.5 hlen,hsetnx,hincrby,hincrbyfloat,hstrlen](#1.5 hlen,hsetnx,hincrby,hincrbyfloat,hstrlen)
    • [1.6 hash编码](#1.6 hash编码)
    • [1.7 哈希的应用](#1.7 哈希的应用)
  • [2. list类型](#2. list类型)
    • [2.1 lpush和lrange](#2.1 lpush和lrange)
    • [2.2 lpushx,rpush,rpushx](#2.2 lpushx,rpush,rpushx)
    • [2.3 lpop,rpop](#2.3 lpop,rpop)
    • [2.4 lindex,linsert,llen](#2.4 lindex,linsert,llen)
    • [2.5 lrem](#2.5 lrem)
    • [2.6 ltrim,lset](#2.6 ltrim,lset)
    • [2.7 blpop和brpop](#2.7 blpop和brpop)
    • [2.8 list内部编码](#2.8 list内部编码)
    • [2.9 应用场景](#2.9 应用场景)
    • [2.10 消息队列](#2.10 消息队列)
    • [2.11 微博 Timeline](#2.11 微博 Timeline)
  • 总结

前言

1. Hash类型

⼏乎所有的主流编程语⾔都提供了哈希(hash)类型,它们的叫法可能是哈希、字典、关联数组、映射。在 Redis 中,哈希类型是指值本⾝⼜是⼀个键值对结构,形如 key = "key",value = { {field1, value1 }, ..., {fieldN, valueN } }

哈希类型中的映射关系通常称为 field-value,⽤于区分 Redis 整体的键值对(key-value),注意这⾥的 value 是指 field 对应的值,不是键(key)对应的值,请注意 value 在不同上下⽂的作⽤

1.1 hset,hget,hexists,hdel

bash 复制代码
HSET key field value [field value ...]

设置 hash 中指定的字段(field)的值(value)

返回值:添加的字段的个数。

bash 复制代码
127.0.0.1:6379> hset key f1 111
1
127.0.0.1:6379> hset key f2 222
1
127.0.0.1:6379> hset key f3 333 f4 444
2
bash 复制代码
HGET key field

获取 hash 中指定字段的值

bash 复制代码
127.0.0.1:6379> hget key f1
111
bash 复制代码
HEXISTS key field

判断 hash 中是否有指定的字段。

返回值:1 表⽰存在,0 表⽰不存在。

bash 复制代码
127.0.0.1:6379> hexists key f1
1
bash 复制代码
HDEL key field [field ...]

删除 hash 中指定的字段。

返回值:本次操作删除的字段个数。

bash 复制代码
127.0.0.1:6379> hdel key f1 f2
2

del是删除所有的,hdel是删除value里面的部分key

1.2 hkeys

获取 hash 中的所有字段。

bash 复制代码
HKEYS key
bash 复制代码
127.0.0.1:6379> hkeys key
f3
f4

1.3 hvals

获取 hash 中的所有的值。

bash 复制代码
HVALS key

返回值:所有的值。

bash 复制代码
127.0.0.1:6379> hvals key
333
444

h系列的命令,必须保证key对应的value是哈希类型

1.4 hgetall和hmget

获取 hash 中的所有字段以及对应的值。

bash 复制代码
HGETALL key
bash 复制代码
127.0.0.1:6379> hgetall key
1) "f3"
2) "333"
3) "f4"
4) "444"

返回形式是一个filed一个value的形式

hmget:

⼀次获取 hash 中多个字段的值。

hget:只是获取一个filed对应的value

bash 复制代码
127.0.0.1:6379> hmget key f3 f4
1) "333"
2) "444"

hmset和hset是一样的效果

在使⽤ HGETALL 时,如果哈希元素个数⽐较多,会存在阻塞 Redis 的可能。如果开发⼈员只需要获取部分 field,可以使⽤ HMGET,如果⼀定要获取全部 field,可以尝试使⽤ HSCAN命令,该命令采⽤渐进式遍历哈希类型

意思就是敲一次命令,遍历一部分,在敲一次命令,又遍历一小部分,相当于concurrentHashMao的扩容

1.5 hlen,hsetnx,hincrby,hincrbyfloat,hstrlen

hlen:获取 hash 中的所有字段的个数

bash 复制代码
HLEN key
bash 复制代码
127.0.0.1:6379> hlen key
(integer) 4

hsetnx:在字段不存在的情况下,设置 hash 中的字段和值。如果存在就是设置失败

hset:这个是存在就是修改,不存在设置

bash 复制代码
127.0.0.1:6379> hgetall key
1) "f3"
2) "333"
3) "f4"
4) "444"
5) "f1"
6) "111"
7) "f2"
8) "222"
127.0.0.1:6379> hsetnx key f5 555
(integer) 1
127.0.0.1:6379> hsetnx key f5 666
(integer) 0
127.0.0.1:6379> hget key f5
"555"

HINCRBY:将 hash 中字段对应的数值添加指定的值。是整数

bash 复制代码
HINCRBY key field increment

返回值:该字段变化之后的值。

HINCRBYFLOAT

HINCRBY 的浮点数版本。

hstrlen:计算对应field的长度,单位是字节

bash 复制代码
127.0.0.1:6379> hincrby key f1 20
(integer) 131
127.0.0.1:6379> hget key f1
"131"
127.0.0.1:6379> hincrbyfloat key f1 1.1
"132.10000000000000001"

1.6 hash编码

压缩列表就是类似于:zip这种东西,压缩的本质就是针对数据进行重新编码,不同的数据有不同的特点,结合这些特点进行精妙的设计,重新编码之后就可以缩小体积

ziplist:内部的数据结构也是精心设计的,要元素更加紧凑,目的是节省空间

ziplist付出的代价就是读写元素是比较慢的,如果元素个数少,不明显,元素个数多的话,就会慢的比较明显

所以哈希元素少用ziplist,多的话用hashtable

每个value的值长度都比较短,用ziplist,长的话就用另一个

哈希的内部编码有两种:

• ziplist(压缩列表):当哈希类型元素个数⼩于 hash-max-ziplist-entries 配置(默认 512 个)、

同时所有值都⼩于 hash-max-ziplist-value 配置(默认 64 字节)时,Redis 会使⽤ ziplist 作为哈

希的内部实现,ziplist 使⽤更加紧凑的结构实现多个元素的连续存储,所以在节省内存⽅⾯⽐

hashtable 更加优秀。

• hashtable(哈希表):当哈希类型⽆法满⾜ ziplist 的条件时,Redis 会使⽤ hashtable 作为哈希

的内部实现,因为此时 ziplist 的读写效率会下降,⽽ hashtable 的读写时间复杂度为 O(1)

hash-max-ziplist-entries这些可以在配置文件中配置的redis.conf

bash 复制代码
127.0.0.1:6379> hset key f1 111
(integer) 1
127.0.0.1:6379> object encoding key
"listpack"

这里的listpack就是ziplist

bash 复制代码
127.0.0.1:6379> hset key f2 22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
(integer) 1
127.0.0.1:6379> object encoding key
"hashtable"

1.7 哈希的应用

就是作为缓存的时候,存储结构化数据

这是本来的数据结构

这是redis中的数据结构

当然也可以用String来存储,就是把这个数据结构转换为JSON就可以了

  1. 原⽣字符串类型⸺使⽤字符串类型,每个属性⼀个键。就是所有都作为key了
    set user:1:name James
    set user:1:age 23
    set user:1:city Beijing
    优点:实现简单,针对个别属性变更也很灵活。
    缺点:占⽤过多的键,内存占⽤量较⼤,同时⽤⼾信息在 Redis 中⽐较分散,缺少内聚性,所以这种
    ⽅案基本没有实⽤性。
  2. 序列化字符串类型,例如 JSON 格式
    优点:针对总是以整体作为操作的信息⽐较合适,编程也简单。同时,如果序列化⽅案选择合适,内存的使⽤效率很⾼。
    缺点:本⾝序列化和反序列需要⼀定开销,同时如果总是操作个别属性则⾮常不灵活。
  3. 哈希类型
    优点:简单、直观、灵活。尤其是针对信息的局部变更或者获取操作。
    缺点:需要控制哈希在 ziplist 和 hashtable 两种内部编码的转换,可能会造成内存的较⼤消耗。

不建议使用第一个方法,因为不满足高内聚低耦合

但是需要注意的是哈希类型和关系型数据库有两点不同之处:

• 哈希类型是稀疏的,⽽关系型数据库是完全结构化的,例如哈希类型每个键可以有不同的 field,⽽

关系型数据库⼀旦添加新的列,所有⾏都要为其设置值,即使为 null

• 关系数据库可以做复杂的关系查询,⽽ Redis 去模拟关系型复杂查询,例如联表查询、聚合查询等

基本不可能,维护成本⾼

为什么uid还要存一份呢,为了是让我们的代码写起来方便,多一点内存而已

2. list类型

列表类型是⽤来存储多个有序的字符串,如图 所⽰,a、b、c、d、e 五个元素从左到右组成

了⼀个有序的列表,列表中的每个字符串称为元素(element),⼀个列表最多可以存储 2 − 32 1 个元

素。在 Redis 中,可以对列表两端插⼊(push)和弹出(pop),还可以获取指定范围的元素列表、

获取指定索引下标的元素等。列表是⼀种⽐较灵活的数据结构,它可以

充当栈和队列的⻆⾊,在实际开发上有很多应⽤场景。

list内部结构类似于双端队列

列表类型的特点:

第⼀、列表中的元素是有序的,这意味着可以通过索引下标获取某个元素或者某个范围的元素列表,

例如要获取图 2-20 的第 5 个元素,可以执⾏ lindex user:1:messages 4 或者倒数第 1 个元素,lindex

user:1:messages -1 就可以得到元素 e。

第⼆、区分获取和删除的区别,例如图 中的 lrem 1 b 是从列表中把从左数遇到的前 1 个 b 元素删

除,这个操作会导致列表的⻓度从 5 变成 4;但是执⾏ lindex 4 只会获取元素,但列表⻓度是不会变化

的。

第三、列表中的元素是允许重复的

hash的field是不能重复的

这里的有序指的是元素之间的顺序是固定的,并非升序或者降序

2.1 lpush和lrange

将⼀个或者多个元素从左侧放⼊(头插)到 list 中。

语法:

bash 复制代码
 LPUSH key element [element ...]

返回值:插⼊后 list 的⻓度。

比如lpush 1 2 3 4

依次头插,所以4在最前面了

LRANGE

获取从 start 到 end 区间的所有元素,左闭右闭。

语法:

bash 复制代码
 LRANGE key start stop

返回值:指定区间的元素。也是闭区间

bash 复制代码
127.0.0.1:6379> lpush key 1 2 3 4
(integer) 4
127.0.0.1:6379> lpush key 5 6 7 8
(integer) 8
127.0.0.1:6379> lrange key 0 -1
1) "8"
2) "7"
3) "6"
4) "5"
5) "4"
6) "3"
7) "2"
8) "1"
bash 复制代码
127.0.0.1:6379> lrange key 0 10
1) "8"
2) "7"
3) "6"
4) "5"
5) "4"
6) "3"
7) "2"
8) "1"

就算下标不合法也没事,变成了能获取就获取的方式了

2.2 lpushx,rpush,rpushx

LPUSHX

在 key 存在时,将⼀个或者多个元素从左侧放⼊(头插)到 list 中。不存在,直接返回

语法:

bash 复制代码
 LPUSHX key element [element ...]
bash 复制代码
127.0.0.1:6379> lpushx key 9 10
(integer) 10
127.0.0.1:6379> lrange key 0 -1
 1) "10"
 2) "9"
 3) "8"
 4) "7"
 5) "6"
 6) "5"
 7) "4"
 8) "3"
 9) "2"
10) "1"
127.0.0.1:6379> lpushx key2 9 10
(integer) 0
127.0.0.1:6379> lrange key2 0 -1
(empty array)

lpush如果key不存在,就会创建新的key

RPUSH

将⼀个或者多个元素从右侧放⼊(尾插)到 list 中。

语法:

bash 复制代码
 RPUSH key element [element ...]

RPUSHX

在 key 存在时,将⼀个或者多个元素从右侧放⼊(尾插)到 list 中。

语法:

bash 复制代码
RPUSHX key element [element ...]

返回值:插⼊后 list 的⻓度

bash 复制代码
127.0.0.1:6379> rpush key 0
(integer) 11
127.0.0.1:6379> lrange key 0 -1
 1) "10"
 2) "9"
 3) "8"
 4) "7"
 5) "6"
 6) "5"
 7) "4"
 8) "3"
 9) "2"
10) "1"
11) "0"

2.3 lpop,rpop

LPOP

从 list 左侧取出元素(即头删)。

语法:

bash 复制代码
 LPOP key

返回值:取出的元素或者 nil。

RPOP

从 list 右侧取出元素(即尾删)。

语法:

bash 复制代码
 RPOP key
bash 复制代码
127.0.0.1:6379> lpop key 
"10"
127.0.0.1:6379> lrange key 0 -1
 1) "9"
 2) "8"
 3) "7"
 4) "6"
 5) "5"
 6) "4"
 7) "3"
 8) "2"
 9) "1"
10) "0"
bash 复制代码
127.0.0.1:6379> lpop key 3
1) "9"
2) "8"
3) "7"
127.0.0.1:6379> lrange key 0 -1
1) "6"
2) "5"
3) "4"
4) "3"
5) "2"
6) "1"
7) "0"

后面加个数字,表示删除几个

2.4 lindex,linsert,llen

LINDEX

获取从左数第 index 位置的元素。

语法:

bash 复制代码
 LINDEX key index

返回值:取出的元素或者 nil。

bash 复制代码
127.0.0.1:6379> lrange key 0 -1
1) "6"
2) "5"
3) "4"
4) "3"
5) "2"
6) "1"
7) "0"
127.0.0.1:6379> lindex key 1
"5"
127.0.0.1:6379> lindex key -1
"0"

LINSERT

在特定位置插⼊元素。

语法:

bash 复制代码
 LINSERT key <BEFORE | AFTER> pivot element

before和after表示你是要插入指定元素之前还是之后,pivot 是插入基准,element是插入元素

返回值:插⼊后的 list ⻓度。

bash 复制代码
127.0.0.1:6379> linsert key  before 3 999
(integer) 8
127.0.0.1:6379> lrange key 0 -1
1) "6"
2) "5"
3) "4"
4) "999"
5) "3"
6) "2"
7) "1"
8) "0"
bash 复制代码
127.0.0.1:6379> linsert key  before 999 99
(integer) 10
127.0.0.1:6379> lrange key 0 -1
 1) "6"
 2) "5"
 3) "4"
 4) "99"
 5) "999"
 6) "999"
 7) "3"
 8) "2"
 9) "1"
10) "0"

注意这个基准是具体的值,而不是下标,就是先找到基准值对应的位置,找到第一个就可以插入了

LLEN

获取 list ⻓度。

语法:

bash 复制代码
 LLEN key
bash 复制代码
127.0.0.1:6379> llen key
(integer) 10

2.5 lrem

bash 复制代码
LREM key count element

就是remove

就是删除

count 表示删除个数

elemnet指的是要删除的值


count>0:从左往右删除

count<0:表示从右往左删除count个element

count:0,,,删除所有的element

bash 复制代码
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> rpush key 1 2 3 4
(integer) 4
127.0.0.1:6379> rpush key 1 2 3 4
(integer) 8
127.0.0.1:6379> rpush key 1 2 3 4
(integer) 12
127.0.0.1:6379> lrem key 2 1
(integer) 2
127.0.0.1:6379> lrange key 0 -1
 1) "2"
 2) "3"
 3) "4"
 4) "2"
 5) "3"
 6) "4"
 7) "1"
 8) "2"
 9) "3"
10) "4"
127.0.0.1:6379> lrem key -2 2
(integer) 2
127.0.0.1:6379> lrange key 0 -1
1) "2"
2) "3"
3) "4"
4) "3"
5) "4"
6) "1"
7) "3"
8) "4"
127.0.0.1:6379> lrem key 0 3
(integer) 3
127.0.0.1:6379> lrange key 0 -1
1) "2"
2) "4"
3) "4"
4) "1"
5) "4"

2.6 ltrim,lset

bash 复制代码
LTRIM key start stop

这个就是范围删除,就是保留start和stop区间内的元素,区间外的元素就删除了

也是闭区间,闭区间之外被删除

bash 复制代码
127.0.0.1:6379> lrange key 0 -1
1) "2"
2) "4"
3) "4"
4) "1"
5) "4"
127.0.0.1:6379> ltrim key 1 3
OK
127.0.0.1:6379> lrange key 0 -1
1) "4"
2) "4"
3) "1"

ACL:就是访问控制列表,权限

意思就是redis有很多命令,给命令打上一些标签,这个意思就是这是一个写操作,用于list,比较慢

打好这些标签以后,管理员就可以给redis用户配置不同的权限,允许该用户可以执行哪些标签对应的命令

lset:根据下标来修改元素

bash 复制代码
LSET key index element

就是修改任意一个位置的元素,把index修改为element,复杂度是oN,因为是队列嘛

bash 复制代码
127.0.0.1:6379> lset key 2 1000
OK
127.0.0.1:6379> lrange key 0 -1
1) "4"
2) "4"
3) "1000"

注意这里的修改下标不能越界

2.7 blpop和brpop

blpop 和 brpop 是 lpop 和 rpop 的阻塞版本,和对应⾮阻塞版本的作⽤基本⼀致,除了:

• 在列表中有元素的情况下,阻塞和⾮阻塞表现是⼀致的。但如果列表中没有元素,⾮阻塞版本会理解返回 nil,但阻塞版本会根据 timeout,阻塞⼀段时间,期间 Redis 可以执⾏其他命令,但要求执⾏该命令的客⼾端会表现为阻塞状态。

意思是使用brpop的时候,可以设置阻塞时间的,并不是无休止的等待,而且阻塞期间可以使用其他命令,不会影响redis服务器

• 命令中如果设置了多个键,那么会从左向右进⾏遍历键,⼀旦有⼀个键对应的列表中可以弹出元素,命令⽴即返回。

意思是这两个命令可以处理多个key,多个key就对应多个list,这多个llist哪个有元素了,就会返回哪个元素,就是可以等待多个list,看谁先不为空

• 如果多个客⼾端同时多⼀个键执⾏ pop,则最先执⾏命令的客⼾端会得到弹出的元素。

因为redis中队列不会要求满不满,所以考虑队列为空的时候的阻塞

bash 复制代码
BLPOP key [key ...] timeout

可以处理多个key,多个list,有一个list非空了,就立即返回了,如果list都是空,都会阻塞等待,等待别人插入元素,还可以指定超时时间,单位是秒

bash 复制代码
127.0.0.1:6379> rpush key 1 2 3 4
(integer) 4
127.0.0.1:6379> blpop key 0
1) "key"
2) "1"

返回的结果是一个二元组,分别是哪个key和对应的值

bash 复制代码
127.0.0.1:6379> del key 
(integer) 1
127.0.0.1:6379> blpop key 100

这样就阻塞了


在另一个客户端上插入数据,这边阻塞的就立马出来数据了

bash 复制代码
127.0.0.1:6379> blpop key2 key3 key4 key  500

四个都是空,四个都阻塞了

还是一样的,就是谁先不为空,就返回谁,一个不为空,阻塞就停止了,不是说要取出所有list的,不是阻塞所有list,只是阻塞五个,有一个不为空就可以了

这个命令的作用就是,把redis作为消息队列

2.8 list内部编码

列表类型的内部编码有两种:

• ziplist(压缩列表):当列表的元素个数⼩于 list-max-ziplist-entries 配置(默认 512 个),同时列表中每个元素的⻓度都⼩于 list-max-ziplist-value 配置(默认 64 字节)时,Redis 会选⽤

ziplist 来作为列表的内部编码实现来减少内存消耗。

• linkedlist(链表):当列表类型⽆法满⾜ ziplist 的条件时,Redis 会使⽤ linkedlist 作为列表的内部实现。

这个是老版本的内部编码

现在使用的是quicklist:ziplist和linckedlist的结合,整体是一个链表,链表的每个节点是一个压缩列表,每个压缩列表都不让它太大

这个设置的意思就是每个节点的压缩列表的长度

bash 复制代码
127.0.0.1:6379> object encoding key
"listpack"

2.9 应用场景

用list作为数组这样的结构来存储多个元素

2.10 消息队列

消息队列

如图 所⽰,Redis 可以使⽤ lpush + brpop 命令组合实现经典的阻塞式⽣产者-消费者模型队列,

⽣产者客⼾端使⽤ lpush 从列表左侧插⼊元素,多个消费者客⼾端使⽤ brpop 命令阻塞式地从队列中

"争抢" 队⾸元素。通过多个客⼾端来保证消费的负载均衡和⾼可⽤性

分频道的消息队列

如图 所⽰,Redis 同样使⽤ lpush + brpop 命令,但通过不同的键模拟频道的概念,不同的消费

者可以通过 brpop 不同的键值,实现订阅不同频道的理念

2.11 微博 Timeline

每个⽤⼾都有属于⾃⼰的 Timeline(微博列表),现需要分⻚展⽰⽂章列表。此时可以考虑使⽤

列表,因为列表不但是有序的,同时⽀持按照索引范围获取元素。

1)每篇微博使⽤哈希结构存储,例如微博中 3 个属性:title、timestamp、content:

bash 复制代码
1 hmset mblog:1 title xx timestamp 1476536196 content xxxxx
2 ...
3 hmset mblog:n title xx timestamp 1476536196 content xxxxx

2)向⽤⼾ Timeline 添加微博,user::mblogs 作为微博的键:

bash 复制代码
1 lpush user:1:mblogs mblog:1 mblog:3
2 ...
3 lpush user:k:mblogs mblog:9

3)分⻚获取⽤⼾的 Timeline,例如获取⽤⼾ 1 的前 10 篇微博:

bash 复制代码
1 keylist = lrange user:1:mblogs 0 9
2 for key in keylist {
3 hgetall key
4 }

此⽅案在实际中可能存在两个问题:

  1. 1 + n 问题。即如果每次分⻚获取的微博个数较多,需要执⾏多次 hgetall 操作,此时可以考虑使⽤

    pipeline(流⽔线)模式批量提交命令,或者微博不采⽤哈希类型,⽽是使⽤序列化的字符串类型,使⽤ mget 获取。

  2. 分裂获取⽂章时,lrange 在列表两端表现较好,获取列表中间的元素表现较差,此时可以考虑将列

    表做拆分

选择列表类型时,请参考:

同侧存取(lpush + lpop 或者 rpush + rpop)为栈

异侧存取(lpush + rpop 或者 rpush + lpop)为队列

总结

相关推荐
大柏怎么被偷了4 小时前
【C++】哈希的应用
算法·哈希算法
毅炼6 小时前
Netty 常见问题总结
java·网络·数据结构·算法·哈希算法
派大鑫wink6 小时前
【Day57】SpringBoot 整合 Redis:吃透缓存配置与 API 实战
spring boot·redis·缓存
小毅&Nora8 小时前
【后端】【Redis】① Redis8向量新特性:从零开始构建你的智能搜索系统
redis·向量
heartbeat..9 小时前
Redis 常用命令全解析:基础、进阶与场景化实战
java·数据库·redis·缓存
一只酸奶牛^_^10 小时前
解决LinuxDeploy部署mysql、redis数据库无法启动问题。
redis·mysql
optimistic_chen10 小时前
【Redis系列】分布式锁
linux·数据库·redis·分布式·缓存
万象.11 小时前
redis客户端安装与实现C++版本
数据库·c++·redis
不想写bug呀11 小时前
Redis总结
数据库·redis·缓存