瑞士军刀Redis

主要内容

慢查询

生命周期

如图所示为客户端请求到Redis的完整生命周期:发送命令、排队、执行命令、返回结果

  1. 慢查询发生在第三阶段(也就是说其他阶段像排队耗时都不算)
  2. 客户端超时不一定慢查询,但慢查询是客户端超时的一个可能因素
两个配置
  • slowlog-max-len
    1.配置慢查询队列最大长度
    2.筛选出的慢查询会进入一个先进先出队列
    3.该队列是固定长度的
    4.该队列是保存在内存中
  • slowlog-log-slower-than
    1.慢查询阈值(单位:微秒),也就是说超过多少时间的查询是慢查询
    2.slowlog-log-slower-than=0; 记录所有命令
    3.slowlog-log-slower-than<0; 不记录任何命令
配置方法
  1. 默认值
    config get slowlog-max-len = 128
    config get slowlog-log-slower-than = 10000
  2. 修改配置文件重启(不建议,因为生产环境要尽量避免重启,在第一次启动redis前可以这么做)
  3. 动态配置
    config set slowlog-max-len 1000
    config set slowlog-log-slower-than 1000
三个命令
  • slowlog get [n]: 获取慢查询队列(n为可选参数,指定慢查询条数)
  • slowlog len: 获取慢查询队列长度
  • slowlog reset: 清空慢查询队列
运维经验
  1. slowlog-log-slower-than不要设置过大,默认10ms,通常设置1ms
  2. slowlog-max-len不要设置过小,通常设置1000左右
  3. 理解命令生命周期
  4. 定期持久化慢查询(方便查到历史慢查询操作)

pipeline

什么是流水线

对比: 1次网络命令通信模型 vs 批量网络命令通信模型

执行redis命令时间通常是非常快的,而网络则存在很多不稳定因素。另外redis虽然提供了mget、mset和hmget、hmset等这样的命令,但是如果我们需要同时执行get和hget命令需要怎么做呢,其实这就是流水线帮助我们实现的功能。

流水线就是将一批命令进行一个打包,而在服务端进行批量的计算,然后按顺序将结果返回给客户端,使用流水线可以大大节省网络的开销。

流水线的作用

两点说明:
1.redis的命令时间是微秒级别
2.pipeline每次条数要控制(网络)

分析一个极端例子,假设客户端和服务端相距1300公里,粗略计算命令传输时间为13毫秒,而命令执行时间只有微秒级,所以如果想要做批量操作而没有使用pipeline这样的功能,那redis的使用效率就不会很高。

客户端实现(jedis)

假设需要执行hset命令1万次,key也有1万个,这样的操作是无法使用hmset来完成的,因为hmset是只针对一个key。
我们先使用for循环进行批量命令操作,最终执行时间为50s,显然对于这样的时间是没办法接受的。

我们换成使用pipeline实现,每次执行100个命令,执行100次pipeline操作,最终时间只需要0.7秒,速度大大提升。

与原生M操作

与原生M操作对比,M操作是一个原子操作,只需要执行和计算一次,而pipeline是将命令进行打包,传送到redis时,则会拆分成子命令,结果会按顺序返回。

使用建议
  1. 注意每次pipeline携带数据量(数据量过大需合理拆分次数)
  2. pipeline每次只能作用在一个Redis节点上
  3. 明确M操作与pipeline区别

发布订阅

角色

角色主要有发布者(publisher)、订阅者(subscriber)、频道(channel)
发布者会发布消息到频道上,订阅者通过订阅频道来获取消息。
类似一些新闻APP(微信公众号),只要订阅了某些频道,这些频道有新消息发布订阅者就能收到消息。

模型

模型如图所示(类似生产者与消费者模型),对于订阅者而言其实也是个客户端,订阅者是可以订阅多个频道的,有个问题就是假如发布者已经发布了一条消息到频道中,但是一个新的订阅者是收不到之前已经发送过的消息的,使用时一定要注意这种场景,就是说redis并没有提供消息堆积的功能,所以无法获取历史消息。

相关API

发布消息:publish channel message

订阅频道:subscribe [channel]
返回信息:订阅了哪个频道,收到消息详细信息

取消订阅:unsubscribe [channel]

其他API,例如第一个命令根据pattern去匹配订阅,如: v*,匹配v开头的频道

消息队列

发布订阅是频道发送一条消息,订阅该频道的订阅者都能收到消息。而消息队列是抢模式,最终只有一个订阅者能抢到消息进行消费。
当然redis本身没有提供消息队列这样的功能,我们可以使用list实现,使用阻塞去拉取...
具体根据场景选择相应的模式,就是要搞清楚你的消费者是都需要收到还是只有一个收到。

Bitmap

位图

每个字符串对应的有其ASCII码,将ASCII码转换成对应的二进制,二进制每个数字代表位,即bit
bitmap就是可以用来对位进行操作。

例如设置了一个键值后,我们可以通过key获取到对应的value,同时我们也可以获取每一位二进制数。

127.0.0.1:6379> set hello  big
OK
127.0.0.1:6379> getbit hello 0
(integer) 0
127.0.0.1:6379> getbit hello 1
(integer) 1
相关命令

设置位图:setbit key offset value

最终效果,设置对应的位为对应的值,其他都用0补充

如果对刚才的user执行setbit key 50 1,那么从19位到49位全部补0。这个过程本身会比较慢,所以在执行 setbit 命令时最好不要在很短的位图上做很大的偏移量。

获取位图:getbit key offset

范围获取:bitcount key [start end]
注意start和end指定的是字节,1个字节代表8位
如0 0代表第一个字节,即0-7位,1 2代表第二个字节到第三个字节,即8-23位

多位图操作:bitop op destkey key[key...]
op代表操作符,比如and、or,返回值为操作后的值字节长度

获取索引:bitpos key targetBit [start] [end]

独立用户统计
  1. 使用set和Bitmap
  2. 有1亿用户,5千万独立(每天有5千万人独立访问)

使用位图实现的思路就是,每个用户的ID占一位,比如用户的ID为10000,那我们就将key的第1万位设置为1。
当然这里内存只是个预估值,如果每天都要进行统计,使用位图还是能节省非常大的内存开销的。

那么是不是说在做这样的功能的时候 set 就完全不如 bitmap 好呢。其实是没有完美绝对的事情。
假设只有100万独立用户呢,可以看到使用set会更节省内存,所以使用bitmap也不是绝对的好

实际上位图的id不一定要和userid一样,两者之前可以有一定的偏移量。只要有规则就好,这样可以避免过多的空间浪费。

使用建议
  1. bitmap实际是string类型,type=string,值最大可以存储512MB,对于大部分的独立用户统计应该都可以满足
  2. 注意 setbit 时的偏移量,可能有较大耗时,如果偏移量过大可能造成redis服务器阻塞
  3. 位图不是绝对好,合理的场景选择合适的类型

HyperLogLog

  1. 基于HyperLogLog算法:使用极小空间完成独立数量统计
  2. 本质还是字符串:type hyperloglog_key (返回的是string)
三个命令
  • pfadd key element [element ...] #向hyperloglog添加元素
  • pfcount key [key ...] #计算hyperloglog的独立总数
  • pfmerge destkey sourcekey [sourcekey ...] #合并多个hyperloglog

API使用示例一

API使用示例二

内存消耗(百万独立用户)

使用shell脚本实现百万用户添加,分1千次添加,每次加入1千条数据

#!/bin/bash
elements=""
key="2018_05_01:unique:ids"
for i in `seq 1 1000000`
do
        elements="${elements} uuid-"${i}
        if [[ $((i%1000)) == 0 ]]
                then
                /usr/local/redis/bin/redis-cli pfadd ${key} ${elements}
                elements=""
        fi
done

内存消耗情况如图,相对于使用bitmap或者set来说,消耗内存是非常的小。

使用经验

还是那句话,没有绝对完美的事情,在使用HyperLogLog前考虑以下两点

  1. 数据是否容忍错误(错误率:0.81%)
  2. 是否需要单条数据?(hyperLogLog无法获取)

例如我们之前插入的百万数据,真实查询结果是有偏差的,并且再次插入新的值然后查询结果又会不一样。

GEO

redis3.2新特性,用来计算地理位置信息相关的功能
GEO(地理信息定位):存储经纬度,计算两地距离、范围计算等

应用场景
  • 比如实现类似微信摇一摇这样的功能(社交)
  • 计算周围酒店、餐馆功能(外卖)
相关API

首先给出5个城市的经纬度

添加地理位置:geoadd key lng lat member[lng lat member...]

获取地理位置:geopos key memeber[member...]

计算距离:geodist key member1 member2 [unit]

获取周边:georadius

示例:计算在北京150km以内的城市

相关说明
  1. since 3.2+(3.2版本提供)
  2. 类型实际上是zset,type geoKey = zset
  3. 没有删除API,zrem key member(直接使用zset的API)

你可能感兴趣的:(瑞士军刀Redis)