生命周期
首先我们需要了解Redis
的生命周期.可以分为四个阶段:
- 客户端请求服务端,发送命令
- 服务端命令进行排队(因为Redis是单线程)
- 服务端执行命令
- 服务端将结果返回给客户端
慢查询
慢查询发生在生命周期的第三阶段,客户端超时不一定和慢查询有关,但是慢查询一定会产生客户端超时的一个原因.首先慢查询是一个先进先出的队列.比如一条命令在执行时间过长(生命周期第三步),这条命令将会进入一个队列,这个队列有固定长度.如果超出范围,最先进队列的就会被踢出去.
慢查询是保存在内存当中,对redis
重启之后就消失.
配置参数
配置 | 说明 | 默认值 |
---|---|---|
slowlog-max-len | 0,记录所有命令(没有任何意义) <0,不记录任何命令 | 128 |
Slowlog-log-slower-than | 队列长度,队列的范围 | 10000(10ms) |
配置方法
- 修改配置文件,但需要重启redis服务.不推荐
- 动态配置.推荐
[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.例如大写的Q
的ASCCII=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
计算机单位
单位 | 转换 |
---|---|
1B | 8bit (位) |
1KB | 1024B |
1MB | 1024KB |
1GB | 1024MB |
1TB | 1024GB |
命令
命令 | 说明 |
---|---|
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的对比
数据类型 | 每个用户占用空间 | 需要存储用户量 | 消耗内存算法 |
---|---|---|---|
set | 32位 | 5,0000,000 | 32 * 50,000,000(位) |
bitmap | 1位 | 10,000,000 | 1 * 100,000,000(位) |
所以内存消耗约等于存储量位结果 / 8 / 1000 / 1000
.对数据内存消耗统计
数据类型 | 每日 | 每月 | 每年 |
---|---|---|---|
set | 200MB | 6GB | 72GB |
bitmap | 12.5MB | 375MB | 4.5GB |
bitmap vs set 2
1亿用户,10万独立用户的bitmap与set对比
数据类型 | 每个用户占用空间 | 需要存储用户了 | 每日消耗 | 每月消耗 | 每年消耗 |
---|---|---|---|---|---|
set | 32位 | 10,000 | 4MB | 120MB | 1.4GB |
bitmap | 1位 | 100,000,000 | 12.5MB | 375MB | 4.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.40 | 39.90 | beijing |
天津 | 117.20 | 39.12 | tianjing |
石家庄 | 114.52 | 38.05 | shijiazhuang |
苏州 | 120.58 | 31.30 | suzhou |
上海 | 121.47 | 31.23 | shanghai |
# 添加城市坐标
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"