生命周期

  首先我们需要了解Redis的生命周期.可以分为四个阶段:

  1. 客户端请求服务端,发送命令
  2. 服务端命令进行排队(因为Redis是单线程)
  3. 服务端执行命令
  4. 服务端将结果返回给客户端

慢查询

  慢查询发生在生命周期的第三阶段,客户端超时不一定和慢查询有关,但是慢查询一定会产生客户端超时的一个原因.首先慢查询是一个先进先出的队列.比如一条命令在执行时间过长(生命周期第三步),这条命令将会进入一个队列,这个队列有固定长度.如果超出范围,最先进队列的就会被踢出去.

  慢查询是保存在内存当中,对redis重启之后就消失.

配置参数

配置说明默认值
slowlog-max-len0,记录所有命令(没有任何意义)
<0,不记录任何命令
128
Slowlog-log-slower-than队列长度,队列的范围10000(10ms)

配置方法

  1. 修改配置文件,但需要重启redis服务.不推荐
  2. 动态配置.推荐
[root@localhost nginx]# redis-cli
127.0.0.1:6379> config set slowlog-max-len 1000
OK
127.0.0.1:6379> config set slowlog-log-slower-than 1000
OK

查询命令

# 获取n条慢查询条数,默认最大的
slowlog get [n]  
# 获取慢查询队列长度
slowlog len
# 清空慢查询队列
slowlog reset

pipeline

  pipeline(管道):就是将所有的请求,都放进一个管道中.只进行一次请求.比如我们要进行十次的redis数据处理,如果没有像mget,mset那样批量的处理.就需要10次网络请求,redis命令执行10次.而pipeline.会将10条命令组合起来进行1次网络请求.执行命令10次,然后将执行结果按顺序返回.大大的节省了网络请求的时间.

  其中m操作,像mset,mget这样的批量命令和pipeline是有区别的.mset是不可拆分的.而pipeline进入redis后会拆分,可以分别排到其他命令的后面.而mset必须执行完所有的命令才能执行其他命令.

<?php

ini_set('memory_limit','1056M');
$redis = new Redis();
$connect = $redis->connect('127.0.0.1',6379);
if(!$connect){
    echo 'connect error' . PHP_EOL;
}else{
    echo 'connect success' . PHP_EOL;
}
$start = time();
$k = 0;
for($i=0;$i<100;$i++){
    # $redis->pipeline();
    for($j=0;$j<1000;$j++){
        $redis->hset('qvbilam','Number:' . $k,'hello');
        $k++;
    }
    # $redis->exec();
}
$end = time();
echo $end - $start . PHP_EOL;
# 不用pipeline
[root@localhost test]# php pipeline_test.php
connect success
4
# 使用pipeline,去掉代码的注释
[root@localhost test]# php pipeline_test.php
connect success
1

  可以从实例中看到很明显的差异.我是将10w条数据进行了拆分,用了100次的pipeline.一定要注意携带数量,不要过大.pipeline只适用于一个redis节点,不适用于集群.

发布订阅

角色模型

  发布订阅的角色包括publisher(发布者),subscriber(订阅者),channel(频道).当订阅者关注某个频道后,发布者在对应的频道发送消息,频道下所有的订阅者都会收到消息.发布者和订阅者都相当于client.server中包含着许多的频道.订阅者可订阅多个频道.

  注意:如果发布者先发送了一条消息到频道中,而订阅者在发送消息后才关注的频道,是收不到之前的消息的.无法做消息堆积.

命令

命令说明
publish channel message将消息发送到对应的频道,返回订阅者数量
subscribe channel [channel]订阅频道,可以是多个
unsubscribe channel [channel]取消订阅频道
psubscribe [pattren ]订阅模式,比如订阅以a* 开头的所有频道
punsubscribe [pattren]退订模式,同上
pubsub numsub [channel]返回频道对应订阅者的数量
pubsub numpat返回被订阅模式的数量
-- 发布消息

// 没有订阅者
127.0.0.1:6379> publish channel hello
(integer) 0
// 一个订阅者
127.0.0.1:6379> publish hello qvbilam
(integer) 1
127.0.0.1:6379> publish hello xianxian
(integer) 1

-- 订阅者订阅
192.168.128.131:6379> subscribe hello
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "hello"
3) (integer) 1
// 收到的消息
1) "message"
2) "hello"
3) "qvbilam"
1) "message"
2) "hello"
3) "xianxian"

-- 获取对应频道/订阅者数量 
127.0.0.1:6379> pubsub numsub hello
1) "hello"
2) (integer) 1

bitmap

  bitmap(位图):在计算机中字节是可寻址的最小的单位.每个单位有8个二进制文件,最右边的一位数最低,最左边的一位数最高.每个值不是0就是1.例如大写的QASCCII=81.那么它的二进制就是0101 00001.那么位图就是在redis中直接操作字节中的位.

127.0.0.1:6379> set qvbilam QvBiLam
OK
# 0-7位第一个字节Q.可以对应上面的ASCCII
# 第一位是0,第二位是1
127.0.0.1:6379> getbit qvbilam 0
(integer) 0
127.0.0.1:6379> getbit qvbilam 1
(integer) 1

计算机单位

单位转换
1B8bit (位)
1KB1024B
1MB1024KB
1GB1024MB
1TB1024GB

命令

命令说明
setbit key offset value设置key的第offset位的值.value只能是0/1.返回结果是之前对应位的值
getbit key offset获取key偏移量的值
bitcount key [start end]获取指定范围内位的值为1的个数,默认全部
bitpos key start end获取指定范围内字节的位数.
bitop op destkey key [key]将多个key的and/or/not/xor(交/并/非/异或)的结果保存到destkey
127.0.0.1:6379> set user_1_name qvbilam
OK
127.0.0.1:6379> set user_2_name QvBiLam
OK
# 获取第三位的位值
127.0.0.1:6379> getbit user_1_name 2
(integer) 1
# 修改第三位的位的值
127.0.0.1:6379> setbit user_1_name 2 0
(integer) 1
# 结果q变Q
127.0.0.1:6379> get user_1_name
"Qvbilam"
# 并集处理
127.0.0.1:6379> bitop and new_user_name user_1_name user_2_name
(integer) 7
# 获取并集结果的位数
127.0.0.1:6379> bitpos new_user_name 0 -1
(integer) 48

使用场景

  对独立用户统计,假如我们网站有1亿用户,每天有5千万的独立用户访问.假设用户是整型,那就需要占用4个字节.相当于32位,那没存储一个用户就需要占用32位.

  假设今天用户id位1996125的胡用户访问了.那我们将独立用户数(uv_2019_6_27)的地1996125 - 1位设置成1.每天通过bitcount key [start end]记录有多少用户访问.每个用户只占用一个位.

bitmap vs set 1

  1亿用户,5000万独立用户的bitmap与set的对比

数据类型每个用户占用空间需要存储用户量消耗内存算法
set32位5,0000,00032 * 50,000,000(位)
bitmap1位10,000,0001 * 100,000,000(位)

  所以内存消耗约等于存储量位结果 / 8 / 1000 / 1000.对数据内存消耗统计

数据类型每日每月每年
set200MB6GB72GB
bitmap12.5MB375MB4.5GB

bitmap vs set 2

  1亿用户,10万独立用户的bitmap与set对比

数据类型每个用户占用空间需要存储用户了每日消耗每月消耗每年消耗
set32位10,0004MB120MB1.4GB
bitmap1位100,000,00012.5MB375MB4.5GB

hyperloglog

  hyperloglog(超日志):一种神奇的数据结构,其实是字符串类型的.用hyperloglog算法极小的空间完成独立用户的统计,但是有缺点,官方给出的错误率是0.81%,不能获取其中的值.所以在使用的时候要考虑好.在大数据量的情况下是否容忍低错误率.是否不需要用到用户的id.

命令

命令说明
pfadd key element [element]向key中添加一个或多个元素
pfcount key [key]计算一个key或多个key的独立用户数
pfmerge destkey source [source]合并多个hyperloglog存储到destkey
# 按照日期26添加独立用户1,2
127.0.0.1:6379> pfadd 19/06/26 id_1 id_2
(integer) 1
# 用户1,3今日访问
127.0.0.1:6379> pfadd 19/06/26 id_1 id_3
(integer) 1
# 获取今日独立用户
127.0.0.1:6379> pfcount 19/06/26
(integer) 3

# 今天27用户1,2访问
127.0.0.1:6379> pfadd 19/05/27 id_1 id_2
(integer) 1
# 计算两天独立用户访问
127.0.0.1:6379> pfmerge 05/26_27 19/05/26 19/05/27
OK
127.0.0.1:6379> pfcount 05/26_27
(integer) 2

  100万的用户一天内存消耗也就15KB,一个月450KB,一年的话大概5MB.每日1000万也是10几KB,这个可以自己做测试.

geo

  计算地理位置信息功能:可以存储一个地区的经纬度,计算两地距离.范围距离.

命令

命令说明
geoadd key longitude latitude member向key中添加member(城市标示)的经纬度
geopos key member获取地理信息为准
geodist key member1 member2 [unit]获取两个位置之间的距离(m,km,mi,ft)
# 获取指定范围内所有的属性
georadius key longitude latitude radius 
[WITHCOORD] [WITHDIST] [WITHHASH]
[COUNT count] [ASC|DESC]
# radius m/km/mi/ft. 米/千米/英尺
# WITHCOORD : 返回指定中心返回物品的距离
# WITHDIST : 返回匹配属性的经纬度
# WITHHASH : 还以52位无符号整数的形式返回(对我没啥用)
# COUNT count : 获取count个数量 
# ASC | DESC : 按照距离排序 近到远 | 远到近

# 获取属性范围内的所有属性
georadiusbymember key member radius 
[WITHCOORD] [WITHDIST] [WITHHASH]
[ASC|DESC] [COUNT count]
# 和上述一样.只是将经纬度替换成了属性.
地区经度维度member
北京116.4039.90beijing
天津117.2039.12tianjing
石家庄114.5238.05shijiazhuang
苏州120.5831.30suzhou
上海121.4731.23shanghai
# 添加城市坐标
127.0.0.1:6379> geoadd city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> geoadd city 117.20 39.12 tianjing 114.52 38.05 shijiazhuang 120.58 31.30 suzhou 121.47 31.23 shanghai
(integer) 4
# 获取北京,苏州坐标
127.0.0.1:6379> geopos city beijing
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
127.0.0.1:6379> geopos city beijing suzhou
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
2) 1) "120.58000177145004272"
   2) "31.30000043401529553"
   
# 获取北京苏州之间距离(默认返回m)   
127.0.0.1:6379> geodist city beijing suzhou
"1028195.1975"
127.0.0.1:6379> geodist city beijing suzhou km
"1028.1952"

# 获取某个点500km的属性
127.0.0.1:6379> georadius city 116.40 39.90 500 km WITHDIST  WITHCOORD
1) 1) "shijiazhuang"
   2) "262.2205"
   3) 1) "114.5200011134147644"
      2) "38.05000090215231268"
2) 1) "tianjing"
   2) "110.6312"
   3) 1) "117.19999998807907104"
      2) "39.12000048819218279"
3) 1) "beijing"
   2) "0.0001"
   3) 1) "116.39999896287918091"
      2) "39.90000009167092543"

# 获取北京150km的属性
127.0.0.1:6379> georadiusbymember city  beijing 500 km withdist withcoord
1) 1) "shijiazhuang"
   2) "262.2205"
   3) 1) "114.5200011134147644"
      2) "38.05000090215231268"
2) 1) "tianjing"
   2) "110.6313"
   3) 1) "117.19999998807907104"
      2) "39.12000048819218279"
3) 1) "beijing"
   2) "0.0000"
   3) 1) "116.39999896287918091"
      2) "39.90000009167092543"
Last modification:February 18th, 2020 at 10:19 pm