Redis基础

三种启动方式

  • 直接启动
  • 动态参数启动
  • 配置文件启动(推荐,在单机多实例场景下配置文件可以用端口区分)
  1. daemonize // 是否是守护进程
  2. port // 端口
  3. logfile // 系统日志
  4. dir // 工作目录

支持的数据类型

通用命令 功能
keys * 遍历所有的key,时间复杂度O(n),一般不使用
dbsize 计算key的总数,时间复杂度O(1)
exists KEY 检查key是否存在,存在返回1,不存在返回0
del KEY 删除指定的key-value
expire KEY SECONDS key在seconds秒后过期
ttl KEY 查询key剩余的过期时间
persist KEY 去掉key的过期时间
type KEY 返回key对应value的类型

Redis归纳汇总 - 图1

  • String**:字符串,不大于512MB
    get/set/del
    incr/decr/incrby/decrby
    set/setnx/set xx
    mget/mset

  • Hash:哈希,mapmap结构,
    hget/hset/hdel
    hexists/hlen
    hmget/hmset
    hgetall/hkeys

  • List:有序,可以重复
    rpush/lpush/lpop/rpop/llen
    lrange/ltrim/lset

  • Set:无序集合,无重复
    sadd/srem
    scard/sismember/srandmember/smembers
    sinter/sdiff/sunion
    抽奖系统

  • Zset:有序集合,无重复,
    zadd/zrem/zscore/zcard
    排行榜

Redis功能

慢查询

慢查询发生在执行命令阶段

客户端超时不一定是慢查询,但慢查询是客户端超时的可能原因

两个配置

slowlog-max-len           // 慢查询队列长度
slowlog-log-slower-than   // 慢查询阈值(us)

config set slowlog-max-len 128
config set slowlog-log-slower-than 100000

慢查询命令

  • slowlog get [n]:获取慢查询队列

  • slowlog len:获取慢查询队列长度

  • slowlog reset:清空慢查询队列

Pipeline

将多次的IO往返时间缩减为1次,前提是执行的命令没有因果相关性

Redis命令时间是us级别

由客户端提供支持

M操作(mset/mget)是原子的,pipeline操作非原子但是返回有序

发布订阅

发布者,订阅者,频道(publisher, subscriber, channel)

publish/subscribe/unsubcribe

和消息队列的区别

Redis归纳汇总 - 图2

Redis归纳汇总 - 图3

BitMap

type是string类型,最大512MB

setbit/getbit/bitcount

独立用户统计

setbit的偏移量太大时比较耗时

HyperLogLog

超小内存完成独立数量统计,本质还是字符串

pfadd key [element…]:向hyperloglog添加元素

pfcount key:计算hyperloglog的独立总数

pfmerge destkey [sourcekey…]:合并多个hyperloglog

局限

  • 错误率:0.81%
  • 不能取出单条数据

GEO

地理信息定位(存储经纬度,计算两地距离,范围计算等)

本质是zset

geoadd/geopos/geodist

Reids IO模型

Redis性能高效

Redis单机QPS的上限是10w+

  • 完全基于内存,内存操作非常快速(100ns)

  • 采用单线程,避免线程切换和竞态消耗

  • 数据结构简单,Redis中的数据结构是专门设计的

  • 使用非阻塞IO+IO多路复用模型

  • 由C语言编写

一次只运行一条命令,拒绝长(慢)命令

多核机器部署

  • 运行多个Redis实例

通信协议

  • Redis的瓶颈不在于网络流量,而是在内部逻辑处理上

  • RESP是Redis的序列化协议,实现简单,解析性能好

Redis持久化

Redis所有数据保存的内存中,持久化就是把数据的更新异步保存到磁盘中

RDB(Redis DataBase)

RDB是内存数据的快照

RDB三种触发方式

Redis归纳汇总 - 图4

Redis归纳汇总 - 图5

  • save:同步,替换旧的RDB文件,不会消耗额外内存,阻塞客户端命令

  • bgsave:异步,替换旧的RDB文件,不阻塞客户端命令,需要fork,消耗内存

  • 自动生成:通过配置seconds和changes

RDB其它触发方式

  • 全量复制
  • shutdown

bgsave原理:fork和cow

Redis通过创建子进程进行RDB持久化,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,但写脏的页面数据会逐渐和子进程分离开来,子进程对内存数据(快照)进行遍历读取,然后写到磁盘中

RDB问题

  • 数据量为n,时间复杂度O(n),耗时

  • fork的COW策略消耗内存

  • 磁盘IO性能

  • 不可控,丢失数据

AOF(Append Only File)

AOF三种策略

  • always:每条命令都fsync到硬盘

  • everysec:每秒把命令fsync到硬盘

  • no:由操作系统决定fsync

如果要求AOF不丢失数据,则每条写指令都要sync一次磁盘,这种条件下Redis的性能是非常低的,一般使用定时sync的方法,即每秒sync一次,保证性能的同时保证最多只丢失1秒的数据,在数据可靠性和性能之间进行折中

AOF重写

减少磁盘占用量,加快恢复速度

AOF是内存数据修改的命令记录,通过重放来恢复Redis当前的内存数据状态,需要定期重写

AOF重写两种实现

  • bgrewriteaof
    开辟一个子进程遍历内存,然后转换为一系列的Redis命令,序列化到一个新的AOF文件中,序列化完毕后再把操作期间发生的增量AOF日志追加到新的AOF文件中
    Redis归纳汇总 - 图6

  • 重写配置

对比

  • RDB对Redis的数据进行周期性地持久化,AOF对每条命令以追加方式写入到日志,类似MySQL的binlog

  • RDB适合冷备,AOF适合热备,两种机制同时开启时,Redis默认使用AOF重新构建数据

  • RDB优点:对Redis性能影响很小,通过fork一个子进程去进行持久化,数据恢复速度比AOF快

  • RDB缺点:RDB是快照文件,数据完整性不如AOF,RDB在生成快照的时候可能会阻塞客户端

  • AOF优点:AOF每秒进行一次fsync操作,最多丢失1秒的数据,AOF以追加方式写日志,性能较高

  • AOF缺点:AOF文件比RDB大,使用AOF比使用RDB的QPS要低,因为每秒都要异步刷新一下日志

混合持久化

  • 单独使用RDB会丢失较多数据,单独使用AOF恢复数据没RDB快,因此结合两者,先用RDB恢复,再用AOF做数据补全

Redis高可用和可扩展

主从复制

单机问题

机器故障,容量瓶颈,QPS瓶颈

作用

数据副本,扩展读性能

命令

slaveof

全量复制和部分复制

runId和偏移量的概念

启动slave时会发送psync命令给master ,如果这个slave是第一次连接到master,则会触发一个全量复制,master就启动一个线程,生成RDB快照,还会把新的写请求缓存在内存中,RDB文件生成后,master将这个RDB发送给slave,slave收到后写进本地磁盘,然后加载进内存,最后master会把缓存的新命令都发给slave

Redis归纳汇总 - 图7

  • 增量同步:内存环形buffer会出现覆盖的问题

  • 快照同步:如果快照同步的时间过长或者buffer太小,可能会陷入死循环

  • 无盘复制:主节点一边遍历内存,一般把序列化的内容发送到从节点

Redis归纳汇总 - 图8

全量复制开销

  • bgsave时间

  • RDB文件网络传输时间

  • 从节点清空数据时间

  • 从节点加载RDB时间

  • 可能的AOF重写时间

主从复制问题

  • 手动故障转移
  • 写能力和存储能力有限

补充

  • Redis单机QPS的上限是100000+(每秒内查询次数),通过读写分离的手段,让master去写,同时数据同步到多个slave,让它们去读,这样就能分发大量的请求,同时可以容易进行水平扩容

Redis Sentinel

Redis Sentinel是Redis的高可用实现方案

Redis归纳汇总 - 图9 Sentinel.jpg>)

Sentinel故障转移

  1. 多个sentinel发现并确认master有问题
  2. 选举出一个sentinel作为领导
  3. 选出一个slave作为master
  4. 通知其余slave成为新的master的slave
  5. 通知客户端主从变化

三个定时任务

  • 每10s每个sentinel对master和slave执行info
    发现slave节点,确认主从关系

  • 每2s每个sentinel通过master节点的channel交换信息
    通过__sentinel__:hello频道交互对节点的看法和自身信息

  • 每1s每个sentinel对其它snetinel和redis执行ping
    心跳检测,失败判定依据

主观下线和客观下线

  • 主观下线:某个sentinel认为Redis节点失败
  • 客观下线:超过quorum个sentinel对Redis节点失败达成共识

领导者选举

只有一个sentinel完成故障转移

  • 每个做主观下线的sentinel向其它sentinel发送命令,要求将自己设为领导者
  • 收到命令的sentinel如果没有同意其它sentinel发送的命令,将同意请求,否则拒绝
  • 该sentinel的票数如果超过quorum,将成为领导者,如果多个sentinel成为领导者将会重新选举

选择合适的slave

  • slave-priority最高
  • 复制偏移量最大
  • runId最小

补充

  • Redis Sentinel集群一般由3~5个节点组成,可监控多个主从节点

  • 客户端连接集群时,会先连接sentinel,通过sentinel来查询master的地址,然后再去连接master进行数据交互,当master发生故障时,客户端会重新向sentinel询问地址,sentinel会将最新的master地址告诉客户端

Redis Cluster

Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务

Redis Cluster着眼于可扩展,在单个Redis内存不足时,使用Cluster进行分片存储

解决单机瓶颈

主从同步读写分离,如果要支撑更大数据量和并发量,就需要集群部署

数据分区的两种方式

顺序分区:可顺序访问,支持范围操作,数据分布可能不均匀

哈希分区:不能顺序访问,不支持范围操作,数据分布均匀

  • 节点取余:哈希 + 取余,扩容时迁移的数据量太大

  • 一致性哈希:哈希 + 顺时针,扩容时数据迁移只影响相邻节点

  • 虚拟槽:每个槽映射一个数据子集
    Redis归纳汇总 - 图10

Redis Cluster结构

通过原生命令安装部署理解架构

  • 配置开启节点
  • meet
  • 指派槽
  • 主从复制

Redis一致性问题

  • Consistent:一致性

  • Availability:可用性

  • Partition tolerance:分区容忍性

CAP原理:网络分区(网络断开)发生时,一致性和可用性难以两全

Redis满足了可用性和最终一致性

最终一致性表示网络分区发生时,为了满足可用性导致主从节点数据发生不一致,一旦网络恢复,从节点会努力追赶主节点,最终保持数据一致

  • 如果严格要求缓存和数据库必须保持一致性,将读请求和写请求串行化到一个内存队列

  • 串行化会导致系统的吞吐量大大降低,同时队列也可能成为系统的一个瓶颈

Redis应用场景

  • 缓存
  • 分布式锁
  • 消息队列
  • 计数器:HyperLogLog
  • 排行榜:zset
  • 社交网络
  • 实时系统(布隆过滤器)

Redis缓存

降低后端负载,加速请求响应,大量写合并为批量写

数据不一致:缓存层和数据层存在时间窗口不一致,和更新策略有关

缓存更新策略

  • 内存淘汰策略
  • 过期策略
  • 主动更新

内存淘汰策略
淘汰策略 详细
noeviction 返回错误,当达到内存限制条件并且客户端尝试执行写入命令
allkeys-lru 尝试回收最近最少使用的键
volatile-lru 尝试回收最近最少使用的键,但仅限于过期的键
allkeys-random 回收随机的键
volatile-random 回收随机的键,但仅限于过期的键
volatile-ttl 回收过期的键,并且优先回收存活时间(TTL)较短的键

过期策略
  • 定期删除:每隔一段时间就随机选择一些设置了有效时间的Key,检查是否过期,过期就删除
    扫描所有设置了有效时间的Key,性能太低

  • 惰性删除:等到有查询的时候判断是否过期,过期就删除且不返回

缓存穿透,缓存击穿,缓存雪崩

  • 缓存穿透:大量请求不命中,恶意攻击
    在接口层增加校验,比如用户鉴权校验,参数校验等;
    在存储层没有找到时,更新缓存对应Key的Value为NULL,失效时间可以设置短一点
    Nginx网关层对IP的请求次数进行监控,超过阈值就拉黑处理
    使用布隆过滤器快速判断Key是否存在
    Redis归纳汇总 - 图11

  • 缓存击穿:高并发的请求只针对某一个热点,在该热点失效的瞬间,请求就会击穿缓存,落到数据库上
    设置逻辑过期时间:基本解决了热点重建问题,不保证一致性,增加维护和内存成本
    Redis归纳汇总 - 图12
    使用互斥锁:保证了一致性,但是可能会出现死锁
    Redis归纳汇总 - 图13

  • 缓存雪崩:缓存同一时间大面积失效
    在往Redis存数据时,在每个Key的失效时间加上一个随机值,保证不会同一时间大面积失效
    设置热点数据永不过期,需要时再去更新缓存

分析思路

  • 事前:Redis高可用,使用主从 + 哨兵或者Redis Cluster避免全盘崩溃

  • 事中:避免MySQL被打死,本地ehcache缓存 + Hystrix限流 + 降级

  • 事后:快速恢复缓存数据,Redis混合持久化,重启自动从磁盘上加载数据

分布式锁

事务

  • multi:事务开始

  • exec:事务执行

  • discard:事务丢弃,丢弃事务缓冲队列里所有的命令

Redis的事务不具有原子性,事务在遇到命令执行失败后,后面的命令还会继续执行

Redis的事务满足隔离性,因为是单线程模型,类似于串行化

两种处理并发冲突的方式

  • Redis分布式锁(悲观锁)

  • watch机制
    在事务开始前watch某个变量,然后开始事务,在事务执行时Redis会先检查watch的变量是否被修改,如果被修改了exec命令就会返回null通知客户端事务执行失败

> watch books
OK
> incr books # 被修改了
(integer) 1
> multi
OK
> incr books
QUEUED
> exec # 事务执行失败
(nil)