Redis大全手册(上)

1,871 阅读12分钟

著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

API的理解和使用

通用命令

keys
dbsize #计算key的总数
exists key  #检查key是否存在
del key [key]   #删除指定key-value
type key    #返回key的类型
expire key seconds      #key在seconds过期
ttl key     #查看key剩余的过期时间
persist key     #去掉key的过期时间

-2表示过期
-1代表key存在,并且没有过期时间

kyes基本不在生产环境使用

keys *    #遍历所有key
key [pattern]

命令 时间复杂度
keys O(n)
dbsize O(1)
del O(1)
exists O(1)
expire O(1)
type O(1)

数据结构和内部编码

redis为什么这么快?

  1. 纯内存
  2. 非阻塞IO
  3. 避免线程切换和竞态消耗

单线程需要注意什么?

  1. 一次只运行一条命令
  2. 拒绝长(慢)命令 keys, flushall, flushdb, slow lua script, mutil/exec, operate big value(collention)
  3. 其实不是单线程
    fysnc file descriptor
    close file descriptor
    

字符串

get key     #获取key对应的value O(1)
set key value   #设置key-value  O(1)
del key     #删除key-value  O(1)
mset key value key value    #批量设置key-value   O(n)
mget key key   #批量获取key-value    O(n)

incr key    #key自增1,如果key不存在,自增后get(key)=1   O(1)
decr key    #key自减1,如果key不存在,自减后get(key)=-1   O(1)
incrby key k    #key自增k,如果key不存在,自增后get(key)=k   O(1)
decrby key k    #key自减k,如果key不存在,自减后get(key)=-k   O(1)

set key value   #不管key是否存在,都设置   O(1)
setnx key value  #key不存在,才设置     O(1)
set key value xx    #key存在,才设置     O(1)

getset key newvalue     #set key newvalue并返回旧的value
append key value    #将value追加到旧的value
strlen key  #返回字符串的长度(注意中文)

incrbyfloat key 3.5   #增加key对应的值3.5
getrange key start end   #获取字符串指定下标的所有的值
setrange key index value    #设置下标所有对应的值

hash

哈希键值结构

hmset key field value field value   #批量设置   O(n)
hmget key field field   #批量获取       O(n)
hget key field   #获取hash key对应的field的value    O(1)
hset key field value    #设置hash key对应field的value   O(1)
hdel key field  #删除hash key对应的field的value     O(1)
hexists key field   #判断hash key 是否有field   O(1)
hlen key    #获取hash key field的数量       O(1)
hgetall key     #h返回hash key对应所有的field和value    O(n)
hvals key   #返回hash key对应所有field的value   O(n)
hkeys key   #返回hash key对应所有field      O(n)
hsetnx key field value  #设置hash key对应field的value(如field存在,则失败)    O(1)
hincrby key field intCounter    #hash key 对应的field的value自增intCounter  O(1)
hincrbyfloat key field floatCounter     #hincrby浮点数版    O(1)

记录网站每个用户个人主页的访问量

#redis实现
incr userid:pagevies(单线程,无竞争)
hincrby user:1:info pageview count

#java模拟代码
public VideoInfo get(long id){
    String redisKey = redisPrefix + id;
    Map<String,String> hashMap = redis.hgetAll(redisKey);
    VideoInfo videoInfo = transferMapToVideo(hashMap);
    if(videoInfo == null){
        videoInfo = mysql.get(id);
        if(videoInfo != null){
            redis.hmset(redisKey, transferMapToVideo(videoInfo))
        }
    }
    return videoInfo;
}

小心使用hgetall(redis单线程) 例子:如保存一个用户的信息的实现,下面说3种情形,当然还有更多种其他方式

  • String v1
  • String v2
  • hash
    比较
命令 优点 缺点
string v1 编程简单,可能节约内存 1. 序列号开销
2. 设置属性要操作整个数据
string v2 直观,可以部分更新 1. 内存占用较大
2. key较为分散
hash 直观、节省空间、可以部分更新 1. 编程稍微复杂
2. ttl不好控制

list

特点:有序、可以重复、左右两边插入弹出

rpush key value value ...valueN     #从列表右端插入值(1-N个)
lpush key value value ...valueN     #从列表左端插入值(1-N个)
linsert key before|after value newValue     #在list指定的前|后插入newValue
lpop key    #从列表左侧弹出一个item
rpop key    #从列表右侧弹出一个item

#根据count值,从列表中删除所有value相等的项
#count > 0,从左到右,删除最多count个value相等的项
#count < 0,从右到左,删除最多Math.abs(count)个value相等的项
#count = 0,删除所有value相等的项
lrem key count value

ltrim key start end     #按照索引范围修剪列表      O(n)
lrange key start end    #获取列表指定索引范围所有item   O(n)
llen key    #获取列表长度   O(1)
lset key index newValue     #设置列表指定索引值为newValue   O(n)
blpop key timeout   #lpop阻塞版本,timeout是阻塞超时时间,timeout=0为永远不阻塞     O(1)
brpop key timeout   #rpop阻塞版本,timeout是阻塞超时时间,timeout=0为永远不阻塞     O(1)

##小建议-数据结构类比
lpush + lpop = stack
lpush + rpop = queue
lpush + ltrim = capped collection
lpush + brpop = message quere

慢查询

  • slowlog-max-len
    1. 先进先出队列
    2. 固定长度
    3. 保存在内存中

慢查询命令

slowlog get [n]     #获取慢查询队列
slowlog len     #获取慢查询队列长度
slowlog reset   #清空慢查询队列
  • 慢查询阀值(单位:微妙)
  • slowlog-log-slower-than=0 记录所有命令
  • slowlog-log-slower=than<0 不记录任何命令 配置方式
1. 默认值
config get slowlog-max-len = 128
config get slowlog-log-slower-than = 1000
2. 修改配置文件重启
3. 动态配置
config set slowlog-max-len 1000
config set slowlog-log-slower-than 1000

运维经验

  1. slowlog-max-len不要设置过大,默认10ms,通常设置1ms
  2. slowlog-log-slower-than不要设置过小,通常设置1000左右
  3. 理解命令生命周期
  4. 定期持久化慢查询

pipeline

批量网络命令通信模型

什么是流水线
流水线作用

命令 N个命令操作 1次pipeline(n个命令)
时间 n次网络 + n次命令 1次网络 + n次命令
数据量 1条命令 n条命令
  • redis的命令时间是微秒级别
  • pipeline每次条数要控制(网络原因)

从上图举例,redis命令的执行时间是很快的,但是由于数据需要通过网络传输,由于2个地区相隔很远,数据以光速度传播也需要时间,然而这个时间有可能比redis执行时间要长。

pipeline-Jedis实现

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
    <type>jar</type>
</dependency>

#没用pipeline, 1W次hset需要50s
Jedis jedis = new Jedis("127.0.0.1", 6379);
for(int i=0;i<10000;i++){
    jedis.hset("hashkey:"+i,"field"+i, "value"+i);
}

#使用pipeline
Jedis jedis = new Jedis("127.0.0.1", 6379);
for(int i=0;i<100;i++){
    Pipeline pipeline = jedis.pipelined();
    for(int j=i*100; j<(i+1)*100;j++){
        pipeline.hset("hashkey:"+j,"field"+j, "value"+j);
    }
    pipeline.syncAndReturnAll();
}

使用建议

  1. 注意每次pipeline携带的数据量
  2. pipeline每次只能作用在一个Redis节点上
  3. M操作和pipeline的区别

发布订阅

角色 发布者(publisher) 订阅者(subscriber) 频道(channel) 模型

publish channel message     #发布消息
subscribe [channel]     #一个或多个
unsubscribe [channel]     #一个或多个
psubscribe [pattern...]     #订阅模式
punsubscribe [pattern...]   #退订指定的模式
pubsub channels     #列出至少有一个订阅者的频道
pubsub numsub [channel...]      #列出给定频道的订阅者数量
pubsub numpat    #列出被订阅模式的数量

位图

setbit key offset value     #给位图指定索引设置值
getbit key offset       #获取位图指定索引的值
bitcount key [start end]    #获取位图指定范围(start到end,单位为字节,如果不指定就是获取全部)位值为1的个数
bitop key targetBit [start] [end]   #计算位图指定范围第一个偏移量对应的值等于targetBit的位置

独立用户统计

  1. 使用set和Bitmap两种方式
  2. 1亿用户,5千万独立
数据类型 每个userid占用空间 需要存储的用户量 全部存储量
set 32位(假设userid用的是整型,实际场景很多用长整型) 50000000 32位*50000000=190.7348633MB
Bitmap 1位 100000000 1位*100000000=11.920929MB
一天 一个月 一年
set 200M 6G 72G 大约值
Bitmap 12.5M 375M 4.5G 大约值

只有十万独立用户呢?

数据类型 每个userid占用空间 需要存储的用户量 全部存储量
set 32位(假设userid用的是整型,实际场景很多用长整型) 100000 32位*1000000=0.3814697MB
Bitmap 1位 100000 1位*100000000=0.0119209MB

使用建议

  1. type = string,最大512MB
  2. 注意setbit的偏移量,可能有较大耗时
  3. 位图不是绝对好

HyperLogLog

  1. 极小空间完成独立数量统计
  2. 本质还是字符串
  3. pfcount 统计有一定错误率0.81%
  4. 无法取出单条数据
pfadd key element [element...]      #向hyperloglog添加元素
pfcount key [key]       #计算hyperloglog的独立总数
pfmerge destkey sourcekey [sourcekey]   #合并多个hyperloglog

geo地理信息定位

  1. 3.2版本以后才有geo
  2. geoKey的类型是zset,type geoKey = zset
  3. 没有删除的API,可以使用zrem key member
geo key longitude latitude member [longitude latitude member...]    #增加地理位置信息
geopos key member [member...]       #获取地理位置信息
geodist key member1 member2 [unit]    #获取两个地理位置的距离,unit:m、km、mi、ft

更多命令参考

Redis持久化的取舍和选择

redis持久化RDB

触发机制
save 阻塞的 文件策略:如果存在老的RDB文件,替换 时间复杂度O(n)

bgsave

save与bgsave

命令 save bgsave
IO类型 同步 异步
阻塞 是(阻塞发生再fork)
复杂度 O(n) O(n)
优点 不会消耗额外内存 不阻塞客户端命令
缺点 阻塞客户端命令 需要fork,消耗内存

# 配置redis.conf
 save 900 1
 save 300 10
 save 60 10000
 dbfilename dump.rdb
 dir ./
 stop-write-on-bgsave-error yes
 rdbcompression yes
 rdbchecksum yes
 
#最佳配置
 dbfilename dump-${port}.rdb        #指定对应哪个redis的备份
 dir /bigdiskpath       #指定具体文件目录
 stop-write-on-bgsave-error yes
 rdbcompression yes

触发机制

  1. 全量复制
  2. debug reload
  3. shutdown

RDB总结

  1. RDB是redis内存到硬盘的快照,用于持久化
  2. save通常会阻塞redis
  3. bgsave不会阻塞redis,但是会fork新进程
  4. save自动配置满足任一就会被执行
  5. 有些触发机制不容忽视

AOF

RDB有什么问题 耗时、耗性能 不可控、丢失数据

AOF运行原理-创建

AOF运行原理-恢复

AOF的三种策略 always

everysec
no

命令 always everysec no
优点 不丢失数据 每秒一次fsync丢一秒数据 不用管
缺点 IO开销较大,一般的sata盘只有几百TPS 丢一秒数据 不可控

AOF重写

  • 减少硬盘占用量
  • 加速回复速度

AOF重写2种方式

  • bgrewriteaof命令

  • 自动

    配置名 含义
    auto-aof-rewrite-min-size AOF文件重写需要的尺寸
    auto-aof-rewrite-percentage AOF文件增长率
    统计名 含义
    aof_current_size AOF当前尺寸(单位:字节)
    aof_base_size AOF上次启动和重写的尺寸(单位:字节)

    自动触发实际(同时满足)

    • aof_current_size > auto-aof-rewrite-min-size
    • aof_current_size - aof_base_size/aof_base_size > auto-aof-rewrite-percentage
    #配置redis.conf
    appendonly yes
    appendfilename "appendonly-${port}.aof"
    appendfsync everysec
    dir /bigdiskpath
    no-appendfsync-on-rewrite yes
    auto-aof-rewrite-percentage 100
    auto-aof-rewrite-min-size 64mb
    

AOF重写流程

AOF阻塞问题

大于2秒会造成主线程阻塞,无法进行后续客户端发来的命令 每秒刷盘的策略不止是只丢失1秒的数据,也有可能是几秒

如何定位

  • Redis日志
  • reids命令info Persistence(无法看到具体时间点)
  • linux top 命令观察IO使用率

RDB和AOF选择

命令 RDB AOF
启动优先级
体积
恢复速度
数据安全性 丢数据 根据策略决定
轻重

最佳策略

  • 小分片
  • 缓存或存储
  • 监控(硬盘、内存、负载、网络)
  • 足够的内存

fork操作

  1. 同步操作(阻塞)
  2. 与内存量息息相关:内存越大,耗时越长(与机器类型无关)
  3. info:latest_fork_usec

改善fork

  1. 优先使用物理机或者高效支持fork操作的虚拟化技术
  2. 控制redis实例最大可用内存:maxmemory
  3. 合理配置Linux内存分配策略:vm.overommit_memory=1
  4. 降低fork频率:例如放宽AOF重写自动触发机制,不必要的全量复制

子进程开销与优化

  1. CPU:
    • 开销:RDB和AOF文件生成,属于CPU密集型
    • 优化:不做主reids CPU绑定,不和密集型CPU部署在一起
  2. 内存
    • 开销:fork内存开销,Linux:copy-on-write
    • 优化:Linux:echo never > /sys/kernel/mm/transparent_hugepage/enabled(关闭增加fork速度)
  3. 硬盘
    • 开销:RDB和AOF文件写入,可以结合iostat,iotop分析
    • 优化:
      1. 不和高硬盘负载服务部署再一起:存储服务,消息队列等。
      2. no-appendfsync-on-rewrite = yes
      3. 根据写入量决定磁盘类型:例如SSD
      4. 单机多实例持久化文件目录可以考虑分盘存储

redis复制的原理与优化

单机有什么问题? 机器故障 容量瓶颈 QPS瓶颈

简单总结

  1. 一个master可以有多个slave
  2. 一个slave只能有一个master
  3. 数据流向是单向的,master到slave

redis配置参数说明 Redis主从复制和集群配置

slaveof ip port
slave-read-only yes
slaveof on one

全量复制

开销:

  1. bgsave时间
  2. RDB文件网络传输时间
  3. 从节点清空数据时间
  4. 从节点加载RDB的时间
  5. 可能的AOF重写时间

部分复制

Redis主从复制和集群配置

主从复制的常见问题 读写分离

  1. 读流量分摊到从节点,提高访问速度
  2. 可能遇到问题:复制数据延迟、读到过期数据、从节点故障

配置不一致

  1. 例如maxmemory不一致,丢失数据
  2. 例如数据结构优化参数(例如hash-max-ziplist-entries):内存不一致

规避全量复制

  1. 第一次全量复制
    • 第一次不可避免,从节点必须全量
    • 解决:小主节点(maxmemory)分数据量,访问低峰时刻
  2. 节点运行ID不匹配
    • 主节点重启(运行ID改变)
    • 解决:故障转移,例如哨兵或集群
  3. 复制积压缓冲区不足
    • 网络中断,部分复制无法满足
    • 解决:增大复制缓冲区配置rel_backlog_size,网络增强

规避复制风暴

  1. 单主节点复制风暴:
    • 问题:主节点重启,多从节点复制
    • 解决:更换复制拓扑
      slave-1从master复制数据之后,接下来的slave都从slave-1复制数据,减轻master压力
  2. 单机器复制风暴
    • 如图:机器宕机后,大量全量复制
    • 主节点分散多机器

Redis Sentinel

主从复制-master宕掉故障处理

Redis Cluster

缓存设计与优化

Redis云平台CacheCloud

阿里云Redis开发规范

内存管理

Redis 数据结构与内存管理策略(上)
Redis 数据结构与内存管理策略(下)
原理、方法双管齐下,大神带你细解Redis内存管理和优化

开发运维常见坑

redis调整内核参数
Redis安全
redis 热点Key的发现与解决之道
redis4.0之基于LFU的热点key发现机制