Redis 的高级功能之慢查询、bitset、bitop、bitcount实现签到统计、日活量

01、Redis 的高级功能

本文我们将介绍 Redis 的高级功能,比如说慢查询、PipeLine、BitMap、HyperLogLog、发布订阅 和 GEO 等功能的介绍。

一、慢查询

Redis命令的执行过程如下:
1、发送命令
2、命令排队
3、执行命令
4、返回结果

慢查询发生在第三步

​ 所谓慢查询指的是内部执行时间超过某个指定时限的查询,而控制该指定时限的就是 Redis 配置文件中的配置项 slowlog-log-slower-than。除 slowlog-log-slower-than 外,在配置文件中还有另外一个参数与慢查询日志有关,那就是 slowlog-max-len,该配置项控制了 Redis 系统最多能够维护多少条慢查询。

slowlog-max-len:服务器使用先进先出的方式保存多条慢查询日志。

​ 当服务器储存的慢查询日志数量等于 slowlog-max-len 选项的值时, 服务器在添加一条新的慢查询日志之前,会先将最旧的一条慢查询日志删除。

​ 什么意思呢?首先 Redis 会配置一个 slowlog-log-slower-than = 10000,slowlog-max-len = 100,就是说 10000 微秒(1 秒等于 1,000,000 微秒)后是慢查询,然后就把它放到队列(内存)中,并且从 100 开始一直到 1

slowlog-log-slower-than:该选项指定执行时间超过多少微秒。

​ 例如,如果这个选项的值为 1000,那么执行时间超过 1000 微秒的命令就会被记录到慢查询日志。如果这个选项的值为 5000,那么执行时间超过 5000 微秒的命令就会被记录到慢查询日志。

redis> CONFIG SET slowlog-log-slower-than 0
OK

redis> CONFIG SET slowlog-max-len 10
OK

上面的代码表示,CONFIG_SET 命令将 slowlog-log-slower-than 选项的值设为 0 微秒,这样 Redis 执行的全部命令都会被记录进去,然后将 slowlog-max-len 选项的值设为 10,让服务器最多只保存 10 条慢查询日志。

//我们模拟发送几条请求
redis> SET msg "welcome my city"
OK
redis> SET number 12345
OK
redis> SET database "redis"
OK
1.2 查询日志
//SLOWLOG GET 命令就可以查看慢日志,如下。
redis> SLOWLOG GET
1) 1) (integer) 4               # 日志的唯一标识符(uid)
   2) (integer) 1338784447      # 命令执行时的 UNIX 时间戳
   3) (integer) 12              # 命令执行的时长,以微秒计算
   4) 1) "SET"                  # 命令以及命令参数
      2) "database"
      3) "redis"
2) 1) (integer) 3
   2) (integer) 1372181139
   3) (integer) 10
   4) 1) "SET"
      2) "number"
      3) "12345"
...

1.3 最好选择的默认配置

在我们使用redis时,我们应该如何配置默认值呢?

// 推荐使用动态配置
config set slowlog-max-len 1000 // 设置 slowlog-max-len
 为 1000,保存数据最多 1000条,不能太小,也不能太大
config set slowlog-log-slower-than 10000 // 表示超时到 10000 微秒会被记录到日志上
slowlog-log-slower-than 
1. 不要太大,默认是 10ms,实际使用中也只是 1ms 或者 2ms,必须根据 QPS 的大小来设定;

slowlog-max-len 
2. 不要太小,通常是 1000。默认是 128。因为存在内存中,
如果设置过小,会导致之前的慢查询丢失,故建议改成 1000;

3. 定期对慢查询进行持久化。

二、BITSET

2.1 、普通的set

我们先来看普通的set,在下面其实就是给 mykey一个值 a

red:0>set mykey a
"OK"

red:0>get mykey
"a"

2.2 、那和上面普通的set有什么区别呢?

​ 在redis中,存储的字符串都是以二进制的形式存在的。

比如:设置一个key-value,键的名字叫“andy” ,值为字符’a’,‘a’ 的ASCII码是97。

转换为二进制是:01100001。offset的学名叫做“偏移” ,二进制中的每一位就是offset值,

比如在这里offset 0 等于 ‘0’ ,offset 1等于’1’ ,offset2等于’1’,offset 6 等于’1’ ,

没错,offset是从左往右计数的,也就是从高位往低位

​ 那如何通过SETBIT命令将 andy中的 ‘a’ 变成 ‘b’ 呢?即将 01100001 变成 01100010(b的ASCII码是98),其实就是将’a’中的offset 6从0变成1,将offset 7从1变成0。

 red:0>set mykey a
"OK"

red:0>get mykey
"a"

red:0>setbit mykey 6 1
"0"

red:0>setbit mykey 7 0
"1"

red:0>get mykey
"b"
2.3、setbit 标准的语法

其实发现setbit我们这里只是修改set的值,也就是配合使用的,那setbit有没有什么高级的用法呢?

-------------------------正常替换值:--------------------------
setbit youKey offset value

​ 作用:将youKey 的第offset 位的值设置成value

getbit youKey offset

​作用: 获取youKey 的第offset位的值

offset从左向右,从0开始,如果offset位上没有bit值,返回0

三、bitcount

bitcount youkey start end 
 //该命令统计字符串(字节)被设置为1的bit数
 只能统计字节,一个字节是8bit,也就是8个位置
bitcount 统计的是1的个数, 
bitcount youkey start end  
	start 和 end 参数的设置和 GETRANGE 命令类似,都可以使用负数值:
	比如 -1 表示最后一个位,而 -2 表示倒数第二个位,以此类推
bitcount 0 0 那么就应该是第一个字节中1的数量的,
         注意是字节 第一个字节也就是 0 1 2 3 4 5 6 7 这八个位置上。
bitcount 0 -1 所有的
3.1 、setbit 和bitcount 实际使用中的示例
举个例子,如果今天是网站上线的第 100 天,而用户 peter 在今天阅览过网站,
那么执行命令 SETBIT peter 100 1 ;
如果明天 peter 也继续阅览网站,那么执行命令 SETBIT peter 101 1 ,以此类推。

当要计算 peter 总共以来的上线次数时,就使用 BITCOUNT 命令:执行 BITCOUNT peter ,
得出的结果就是 peter 上线的总天数。
而且由于是一个二进制数,只要知道刚开始的日期,就可以知道这一百天内,都有哪天签到了
总结就是:
1. 可以用于单个用户一年内的所有签到(N年当然可以或者指定时间内)
2. 统计某天系统的日活量      setbit strKey userId 1
	strKey 可以是某一天的日期,可以根据具体情况变化
	userId因为是一个唯一的值,可以将id作为在二进制上面的位置
然后统计日活:
      bitcount strkey 

举例1、

将每个独立用户是否访问过网站存放在Bitmaps中,将访问的用户记做1,没有访问的用户记做0,用偏移量作为用户的id
unique:users:2016-04-05代表2016-04-05这天的独立访问用户
用户ID分别是 A、B、C、D,实际中用户的ID可能是整数或者一串字符串,如果是字符串我们这里就模拟A、B、C、D作为用户ID,如果是整数,我们就模拟,1000001,1000002,1000003,1000004四个用户ID
下面测试

//先把当日活跃量设置为0
set unique:users:2019-05-18 0
bitcount unique:users:2019-05-18
//结果居然不是0,而是2,说明我们0本身就:(integer) 2

所以我们不能直接用set XXX 0,而是set XXX ""

127.0.0.1:6379> set testKey ""
OK
127.0.0.1:6379> bitcount testKey
(integer) 0
127.0.0.1:6379>

重新设置日活为0

127.0.0.1:6379> set unique:users:2019-05-18 ""
OK
127.0.0.1:6379> bitcount unique:users:2019-05-18
(integer) 0

//先测试字符串
127.0.0.1:6379> setbit unique:users:2019-05-18 A 1
(error) ERR bit offset is not an integer or out of range
//结果出错了(error) 

//后面的BCD当然都是出错的
setbit unique:users:2019-05-18 B1
setbit unique:users:2019-05-18 C 1
setbit unique:users:2019-05-18 D 1

//模拟整数
127.0.0.1:6379> setbit unique:users:2019-05-18 1000001 1
(integer) 0
127.0.0.1:6379> setbit unique:users:2019-05-18 1000002 1
(integer) 0
127.0.0.1:6379> setbit unique:users:2019-05-18 1000003 1
(integer) 0
127.0.0.1:6379> setbit unique:users:2019-05-18 1000004 1
(integer) 0
127.0.0.1:6379>

最后get取出来我们想要的日活


get unique:users:2019-05-18
//结果是一大串乱码,但是考虑一些是不是有问题,其实是没有问题的
//我们做日活想要的结果并不是这个结果,而是统计1出现的次数,我们用bitcount

127.0.0.1:6379> bitcount unique:users:2019-05-18
(integer) 4
//结果就是我们想要的,到这里统计日活的工作就完成了

举例二:

上面是所有人一天的统计日活,当然我们也可以统计一个人在一年中的日活
下面的代码相对于举例一要简单很多:
比如:下面统计的是小明在注册过一个网站后,第1天,第10天,第101天、第201天分别登录了系统
所以他一年的登录总天数是4

127.0.0.1:6379> set xiaoMing ""
OK
127.0.0.1:6379> get xiaoMing
""
127.0.0.1:6379> set xiaoMing ""
OK
127.0.0.1:6379> bitcount xiaoMing
(integer) 0
127.0.0.1:6379> setbit xiaoMing 1 1
(integer) 0
127.0.0.1:6379> setbit xiaoMing 10  1
(integer) 0
127.0.0.1:6379> setbit xiaoMing 101  1
(integer) 0
127.0.0.1:6379> setbit xiaoMing 201  1
(integer) 0
127.0.0.1:6379> bitcount xiaoMing
(integer) 4
127.0.0.1:6379>

但是我们怎么统计他前10天的登录总天数,前150天登录总天数,前300天的登录总天数

bitcount xiaoMing 0 9
bitcount xiaoMing 0 148
bitcount xiaoMing 0 299
//这样明显是不对的,因为我bitcount XXX start end 
其中的一个字符代表的是8位,所以不能这样,但是具体要怎么做,还有待考虑和解决
3.2 局限性

当有一个功能比如实现微博的点赞功能,要记录都有谁点了赞,可以用上述记录日活的方式,但是

1. 需要用户有类似于数据库自增id的数字id,当然如果你是从10000之类的开始自增的,
在bitmap操作的时候可以统一将用户id减掉10000,这样可以稍微节省一些redis内存占用;
2. 当用户量很大的时候,比如千万级用户量的情况下,一个点赞bitmap需要消耗的内存为:
10000000/8/1024/1024=1.19MB,当bitmap数量较多的时候,内存占用还是很可观的
不过在用户量较少的时候这种方案还是不错的~ 
3. 在下面的博客中,我们将采用另一种方式解决。
   地址如下:https://blog.csdn.net/qq_39455116/article/details/87636164

另一种解决方案传送门

四、BITOP

bitop operation destkey key [key...] 
//对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上	

BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种参数:

.

BITOP AND destkey srckey1 … srckeyN ,对一个或多个 key 求逻辑与,并将结果保存到 destkey

.

BITOP OR destkey srckey1 … srckeyN,对一个或多个 key 求逻辑或,并将结果保存到 destkey

.

BITOP XOR destkey srckey1 … srckeyN,对一个或多个 key 求逻辑异或,并将结果保存到 destkey

.

BITOP NOT destkey srckey,对给定 key 求逻辑非,并将结果保存到 destkey

.

除了 NOT 操作之外,其他操作都可以接受一个或多个 key 作为输入,执行结果将始终保持到destkey里面

.

 red:0>SET key1 "foobar"
"OK"

red:0>SET key2 "abcdeff"
"OK"

red:0>BITOP OR youKey key1 key2
"7"

red:0>get youKey
"goofevf"
4.1 bitop在实际中的应用

你可能看完上面的感觉bitop没有什么用,其实就说一些逻辑运算,但是它具体可以干什么呢?

1. 比如上面用bitset bitcount实现了统计系统日活的计算,但是却无法实现统计三天日活
但是bitop却可以实现,如下:
bitop and threeDay strkey1 strkey2
get threeDay
2. 但是你会说,三天加起来用+也可以实现啊,为什么要用它?
	我也不知道,哈哈,所以还是感觉没有用

五、bitops

bitops key targetBit [start] [end] 
// 计算指定位图范围第一个偏移量对应的值等于 targetBit 的位置

其实感觉这个也没有啥用,要说有用就是,比如用户签到,指定好默认日期后,用bitops可以返回用户在指定时间内第一次签到的时间,这个时间可以是从注册开始到结束,也可以是从第10天到第100天

六、既然bit那么好,那为啥平常不经常使用?或者说什么时候才使用

比较 SET 和 BitMap,很多是使用在统计上的,可见下面两个示例。

示例一,如果你有 1 个亿的用户,每天 5 千万的独立访问。

数据类型	空间占用	存储用户量	全部内存占用
set	32位	50,000,000	50,000,000*32 =200M
bitmap	1位	100,000,000	100,000,000*1=12.5M
如果你用 SET,一天是 200 M,那么一年就是 72G,但如果你用 BitMap,一年只有 4.5G,你觉得哪个好呢?

示例二,但是如果你只有 10万独立用户呢?

数据类型	空间占用	存储用户量	全部内存占用
set	32位	1,000,000	1,000,000*32 =4M
bitmap	1位	100,000,000	100,000,000*1=12.5M
通过上述表格可知,这个时候就要用 SET 了。

所以,BitMap 不一定好,BitMap 是针对大数据量设计的。
我们需要根据需求来区分使用,如果数据量非常大,可以考虑,只有在用户量非常大的时候,才会使用。

你可能感兴趣的:(redis)