Redis的String类型和bitmap类型

Redis基本的类型和位图bitmap

Posted by 石福鹏 on 2021-03-16
Estimated Reading Time 13 Minutes
Words 2.9k In Total
Viewed Times

首先确定Redis服务是启动的

1
2
3
4
5
[root@hadoop01 ~]# ps -fe | grep redis
root 18725 1 0 05:29 ? 00:00:54 /usr/local/bin/redis-server 127.0.0.1:6379
root 22528 1 0 05:39 ? 00:00:58 /usr/local/bin/redis-server 127.0.0.1:6380
root 46022 45566 0 14:26 pts/0 00:00:00 grep --color=auto redis
[root@hadoop01 ~]#

启动客户端,即可直接使用命令redis-cli,默认就会直接启动6379的

1
2
[root@hadoop01 ~]# redis-cli 
127.0.0.1:6379>

也可以连接6380的

1
2
[root@hadoop01 ~]# redis-cli -p 6380
127.0.0.1:6380>

redis 默认是有16个库的,可以在连接的时候指定-n ,也可以连接成功之后切换。

注意:redis客户端中有help的使用,即连接成功后,输入help .按Tab键,就会看到redis中的命令是怎么用的,这里面他们会把命令做分组。

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
127.0.0.1:6380> help  @list

BLPOP key [key ...] timeout
summary: Remove and get the first element in a list, or block until one is available
since: 2.0.0

BRPOP key [key ...] timeout
summary: Remove and get the last element in a list, or block until one is available
since: 2.0.0

BRPOPLPUSH source destination timeout
summary: Pop an element from a list, push it to another list and return it; or block until one is available
since: 2.2.0

一、String

1、面向字符串一系列操作

先通过命令,我们看一下set命令

1
2
3
4
5
6
127.0.0.1:6380> help set
# key value. 过期时间 NX只有当设置的key不存在的时候,才去设置 XX只能更新,不存在的话,就不会处理
SET key value [EX seconds|PX milliseconds] [NX|XX] [KEEPTTL]
summary: Set the string value of a key
since: 1.0.0
group: string

nx:只有当设置的key不存在的时候,才去设置,使用场景: 分布式锁

xx:只能更新,不存在的话,就不会处理

关于String的还哪些命令。还有msetappendGETRANGE

1
2
3
4
5
6
127.0.0.1:6380> mset k1 v1 k2 v2
OK
127.0.0.1:6380> mget k1 k2
1) "v1"
2) "v2"
127.0.0.1:6380>
1
2
3
4
5
127.0.0.1:6380> append k1 hello
(integer) 7
127.0.0.1:6380> get k1
"v1hello"
127.0.0.1:6380>
1
2
3
127.0.0.1:6380> GETRANGE k1 2 6
"hello"
127.0.0.1:6380>

上面的GETRANGE也可以使用正反向索引,反向索引,最后一个就是-1,倒数第二个就是-2,所以也可以这样实现

1
2
3
127.0.0.1:6380> GETRANGE k1 2 -1
"hello"
127.0.0.1:6380>

image-20210317151247925

SETRANGE

1
2
3
4
5
6
127.0.0.1:6380> setrange k1 2 steven
(integer) 8
127.0.0.1:6380> get k1
"v1steven"
127.0.0.1:6380>

strlen key

1
2
3
127.0.0.1:6380> strlen k1
(integer) 8
127.0.0.1:6380>

2、type及面向数值的操作

1
2
3
127.0.0.1:6380> type k1
string
127.0.0.1:6380>
1
2
3
4
5
127.0.0.1:6380> set k1 88
OK
127.0.0.1:6380> type k1
string
127.0.0.1:6380>

我们发现,即使我们设置一个int类型,他的type依然是String,但是如果这样看

1
2
3
127.0.0.1:6380> object encoding k1
"int"
127.0.0.1:6380>

注意,这int并不是类型,这个使用对象object指向value里面它的编码,是encoding的编码,为什么会有这种情况,因为在redis中,除了上面的字符串的操作,还有incrincrby。这是面向数值的操作

1
2
3
4
5
 127.0.0.1:6380> incr k1
(integer) 89
127.0.0.1:6380> get k1
"89"
127.0.0.1:6380>
1
2
3
4
5
127.0.0.1:6380> incrby k1 10
(integer) 99
127.0.0.1:6380> get k1
"99"
127.0.0.1:6380>

相应的也就有decrdecrby

1
2
3
4
5
6
7
127.0.0.1:6380> decr k1
(integer) 98
127.0.0.1:6380> decrby k1 10
(integer) 88
127.0.0.1:6380> get k1
"88"
127.0.0.1:6380>

当然还有incrbyfloat

1
2
3
4
127.0.0.1:6380> incrbyfloat k1 0.5
"88.5"
127.0.0.1:6380>

关于encoding:

底层存储的时候就是按照字节存的,只是在key上做一个优化,key上如果没有做优化,每次计算,相当于都要先判断,这个能不能转成int的encoding。相当于做了一个加速的地方,可以让一些方法速度变快一些

3、redis的String是二进制安全的

什么是二进制安全?就是redis在与外界交互的时候,是严格通过字节流 交互的,因此,只要客户端有统一的编码,数据就不会被破坏,所以称为二进制安全

所以,当存一个字符的时候,如果编码是UTF-8,那么length就是三个字节,如果是GBK编码的话,就是两个字节

1
2
3
4
5
6
7
127.0.0.1:6380> set k2 大
OK
127.0.0.1:6380> strlen k2
(integer) 3
127.0.0.1:6380> get k2
"\xe5\xa4\xa7"
127.0.0.1:6380>

Redis只会识别ASCII码的,超出ASCII的,就会直接按照16进制显示

退出客户端,按照以下方式进入客户端

1
2
3
4
5
127.0.0.1:6380> exit
[root@hadoop01 ~]# redis-cli -p 6380 --raw
127.0.0.1:6380> get k2

127.0.0.1:6380>

因此,在使用Redis,一定要在客户端沟通好数据的编码和解码

4、getset设置新值,返回旧值

这个命令很多人认为是没有用的,因为你可以先get,再set,那么就要考虑成本问题,两次相当于在通信的时候发送了两个包,两次I/O请求。

这个命令可以减少一次I/O的通信

5、原子性操作msetnx

1
2
3
4
5
6
7
8
9
127.0.0.1:6380> mset k1 a k2 b
OK
127.0.0.1:6380> msetnx k2 bb k3 c
0
127.0.0.1:6380> mget k1 k2 k3
a
b

127.0.0.1:6380>

NX不存在时再设置,有一个失败了,其他的都失败

二、bitmap位图

1、setbit

第一个命令setbit,这个命令也归属于String;一个字节有8个二进制位

image-20210317193529644

1
2
3
4
5
127.0.0.1:6380> help setbit
SETBIT key offset value
summary: Sets or clears the bit at offset in the string value stored at key
since: 2.2.0
group: string

setbit中的这个offset是二进制位的offset,不是字节的二进制位

1
2
127.0.0.1:6380>  setbit k3 1 1
0

此时二进制表示为

1
01000000 00000000

他的长度为:

1
2
127.0.0.1:6380> STRLEN k3
1

ASCII01000000正好表示一个@

1
2
3
4
5
6
7
8
9
10
11
12
13
127.0.0.1:6380> setbit k3 7 1
0
127.0.0.1:6380> STRLEN k3
1
127.0.0.1:6380> get k3
A
127.0.0.1:6380> setbit k3 9 1
0
127.0.0.1:6380> STRLEN k3
2
127.0.0.1:6380> get k3
A@
127.0.0.1:6380>

咋不超过8个字节的时候,不需要开辟新的字节,所以length还是1,当setbit k3 9 1时,就会开辟新的字节,即

1
2
01000001 10000000
//翻译成ASCII码就是A@

为啥ASCII不是utf-8?

字符集 ascii,其他一般叫做扩展字符集

扩展: 其他字符集不在对ascii重编码

2、bitpos

1
2
3
4
5
6
7
8
127.0.0.1:6380> help bitpos

BITPOS key bit [start] [end]
summary: Find first bit set or clear in a string
since: 2.8.7
group: string

127.0.0.1:6380>

BITPOS key bit [start] [end] key 二进制数 字节开始的位置,字节结束的位置

注意:这里是字节的开始结束位置,不是二进制位

K3的二进制:

1
2
01000001     10000000
字节第0个位置|字节第1个位置
1
2
3
127.0.0.1:6380> bitpos k3 1 0 0
1
127.0.0.1:6380>

即寻找k3中从第0个字节的位置到第0个位置的所有二进制位中,二进制位为1的位置(二进制位的位置)

1
2
3
127.0.0.1:6380> bitpos k3 1 1 1
9
127.0.0.1:6380>

3、bitcount

1
2
3
4
5
6
7
8
127.0.0.1:6380> help bitcount

BITCOUNT key [start end]
summary: Count set bits in a string
since: 2.6.0
group: string

127.0.0.1:6380>
1
2
3
127.0.0.1:6380> bitcount k3 0 1
3
127.0.0.1:6380>

[start end]表示字节的开始和结束位置,count统计1出现的次数

4、bitop 位的操作

1
2
3
4
5
6
7
8
127.0.0.1:6380> help bitop

BITOP operation destkey key [key ...]
summary: Perform bitwise operations between strings
since: 2.6.0
group: string

127.0.0.1:6380>
1
2
3
4
5
6
7
127.0.0.1:6380> setbit k4 1 1
0
127.0.0.1:6380> setbit k4 7 1
0
127.0.0.1:6380> get k4
A
127.0.0.1:6380>

如果想设置一个B

1
2
3
4
5
6
7
127.0.0.1:6380> setbit k5 1 1
1
127.0.0.1:6380> setbit k5 6 1
0
127.0.0.1:6380> get k5
B
127.0.0.1:6380>
1
2
3
4
5
127.0.0.1:6380> bitop and andkey k4 k5
1
127.0.0.1:6380> get andkey
@
127.0.0.1:6380>

K4(0100 0010)、k5(0100 0100) 做按位与运算。得到的即0100 0000,即@

1
2
3
4
5
127.0.0.1:6380> bitop or  orkey k4 k5  #按位或
1
127.0.0.1:6380> get orkey
C
127.0.0.1:6380>

5、位图bitmap的使用

(1)、需求一(空间小)

需求:需求,任意时间点,统计某一段时间,用户的登录软件的天数?

分析: 建一张用户登录表,来记录用户的登录记录,,最少需要哪些字段,用户的Id(按照4个字节算)、日期(也按照4个字节算)。因此用户登录一次需要8个字节,比如某宝,每天有很多人登录,一年再登录个200天,那数据会非常大,查询的时候,给定一个时间段,需要便利所有数据,成本很高

如果用位图

一年365天,先按照400天计算,每一天代表一个二进制位,从左到右,第一个二进制位代表第一天,以此类推,那么占用的字节数就是400/8=50个字节,也就是说,最多50个字节就可以表示一个人一年的登录记录

用户为key,天数为位

1
2
3
4
5
6
7
127.0.0.1:6380> setbit  Steven 1 1  # 第二天登录
1
127.0.0.1:6380> setbit Steven 8 1 # 第七天登录
0
127.0.0.1:6380> setbit Steven 365 1 # 第364天登录
0
127.0.0.1:6380>
1
2
3
127.0.0.1:6380> bitcount Steven  -2 -1 # 这里start end是字节,不是二进制位,所以要除以8,[-2 -1]即最后的16天登录数
1
127.0.0.1:6380>
1
2
3
127.0.0.1:6380> STRLEN Steven
46
127.0.0.1:6380>

最高是46个字节(每个用户一年),这还是最浪费的情况,所以非常的节省空间

如果有1000w用户,每个用户一年46bit,那么一年所有的用户的大小为46000 0000=460M

*(2)、需求二(速度快)

需求:你是京东的开发工程师,618做活动,用户登录就送礼物,库房需要备多少礼物?假设京东有2亿用户

需要考虑这里面有很多的僵尸用户冷热用户/忠诚用户。因此统计活跃用户就很重要了

如何统计活跃统计?(未来还要支持随机时间窗口,即随意给定一个时间窗口,都可以算出这个窗口中国的活跃用户的数量)

分析: 比如1号到3号,就要考虑1号、2号、3号分别多少人,每个人连续登录需要去重

将需求1中的位图旋转一下,key日期,每个用户都有一个Id,让这个ID映射到每一个二进制位上

日期为key,用户为位

1
2
3
4
5
6
7
127.0.0.1:6380> setbit 20210301 1 1  # 比如下标为1的二进制位代表张三
0
127.0.0.1:6380> setbit 20210302 1 1 # 张三第二天也登录了
0
127.0.0.1:6380> setbit 20210302 6 1 # 二进制位下标为6代表李四,李四第二天也登录了
0
127.0.0.1:6380>

如果窗口时间内,只要登录过就算活跃用户,那么1号,2号总共2个活跃用户

1
2
3
4
5
127.0.0.1:6380> bitop or destkey 20210301 20210302  # 做或运算
1
127.0.0.1:6380> bitcount destkey 0 -1
2
127.0.0.1:6380>

所以活跃用户为2

image-20210318115024034

(3)面向数值计算的场景

抢购、秒杀、详情页(实时购买数等) -可以规避并发下对数据库的事务操作,完全由redis内存操作代替

另外,还有点赞、评论数、好友数都可以由redis内存操作


如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !