Redis学习之高级功能

  • Redis集群搭建
  • 集群介绍
  • 分布式存储机制
  • 容错机制
  • 环境准备
  • 集群搭建
  • 安装环境
  • Redis常用特性
  • 多数据库
  • 消息订阅与发布
  • 事务
  • 数据淘汰策略
  • 数据持久化
  • RDB持久化
  • AOF持久化
  • 启动多个Redis
  • Redis缓存与数据库一致性
  • 实时同步
  • 异步队列
  • Canal同步工具
  • UDF自定义函数
  • 注意事项
  • 缓存穿透
  • 缓存雪崩
  • 热点key


Redis集群搭建

集群介绍

  • 简介
    常见有客户端分片、Twemproxy(Twitter)、Codis(豌豆荚)等,但从redis 3.0之后版本支持redis-cluster集群,它是Redis官方提出的解决方案,Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。
  • Redis-Cluster架构图
  • redis高可用原理 redis高级功能_redis

  • 工作原理
    客户端与 redis 节点直连,不需要中间 proxy 层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。所有的 redis 节点彼此互联(PING-PONG 机制),内部使用二进制协议优化传输速度和带宽。

分布式存储机制

  • 存储机制样例图
  • redis高可用原理 redis高级功能_redis高可用原理_02

  • 映射机制
    redis-cluster 把所有的物理节点映射到[0-16383]slot上,cluster 负责维护。
    node<->slot<->value
  • 哈希槽
    Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点
    例如三个节点:槽分布的值如下:
    SERVER1: 0-5460
    SERVER2: 5461-10922
    SERVER3: 10923-16383

容错机制

  • 投票
    选举过程是集群中所有master参与,如果半数以上master节点与故障节点通信超过(cluster-node-timeout),认为该节点故障,自动触发故障转移操作。故障节点对应的从节点自动升级为主节点
  • 集群不可用
    如果集群任意master挂掉,且当前master没有slave。集群进入fail状态,也可以理解成集群的slot映射[0-16383]不完成时进入fail状态

环境准备

搭建伪集群,需要6个Redis实例,分别运行在不同端口7001-7006

  • CentOS7.0 x64
  • Redis5.0.8

集群搭建

  • 简介
    集群中至少应该有奇数个节点,所以搭建集群最少需要三台主机(master)。同时每个节点至少有一个备份节点(slave),所以最少需要使用6台机器,才能完成Redis Cluster集群(主节点、备份节点由redis-cluster集群搞定)



安装环境

  • 安装gcc
## 查看是否安装gcc
    gcc -v

    ## 在线安装gcc环境,Redis 是 c 语言开发的,需要c语言环境
    yum install gcc-c++
  • 安装ruby
    Ruby,一种简单快捷的面向对象(面向对象程序设计)脚本语言,Ruby以7月诞生石ruby(红宝石)命名
    RubyGems简称gems,是一个用于对 Ruby组件进行打包的 Ruby 打包系统
## 使用ruby脚本来实现集群搭建
    yum install ruby
    yum install rubygems


    ## 网上下载redis-3.0.0.gem
    gem install redis-3.0.0.gem
  • 安装单节点Redis
## 将redis源码包上传到 linux 系统,解压redis源码包
    ## 解压自定义目录
    tar -zxvf redis-5.0.8.tar.gz -C /usr/local/java/redis/
    
    ## 进入目录后编译
    cd redis-5.0.8
    make  # make MALLOC=libc(或者)
    
    ## 安装
    make PREFIX=/usr/local/java/redis install #指定安装目录为/usr/local/java/redis/
  • 安装多节点Redis
## 由于之前编译安装过单节点,这里考虑将单节点的bin目录cp到指定集群目录,注意要将.rdb和.aof后缀的文件删除
    cp -rf redis/bin/ redis-cluster/redis01
    cp redis/redis.conf redis-cluster/redis01


    ## 分别cp 另外5份redis
    cd redis-cluster
    cp -rf redis01 ./redis02
    cp -rf redis01 ./redis03
    cp -rf redis01 ./redis04
    cp -rf redis01 ./redis05
    cp -rf redis01 ./redis06

    ## 将redis源文件src目录下的redis-trib.rb文件拷贝到redis-cluster目录下(redis-trib.rb这个文件是redis集群的管理文件,ruby脚本)
    cp redis-trib.rb /usr/local/java/redis-cluster/

    ## 修改每个配置文件(我这里是使用一台主机,所以我将六个节点的端口号修改为7001-7006)
    vim redis.conf
    # 改为守护进程
    daemonize yes
    # 端口号
    port 7000
    # 修改pid进程文件名,以端口号命名
    pidfile /var/run/redis-7000.pid
    # 修改日志文件名称,以端口号为目录区分
    logfile /root/application/program/redis-cluster/7000/redis.log
    # 修改数据文件存放地址,以端口号为目录来区分
    dir /root/application/program/redis-cluster/7000/
    # 开启集群
    cluster-enabled yes
    # 集群的配置  配置每个节点的配置文件,配置文件首次启动自动生成 7000,7001,7002
    cluster-config-file  nodes_7000.conf
    # 如果外部访问需改为no 
    protected-mode no # 启用条件:1.没有bind ip 2.没有设置访问密码
    # 配置集群节点的超时时间,可选择不改
    cluster-node-timeout 15000


    ## 分别启动全部redis实例
    ./redis-server ./redis.conf

    ## 上传redis-3.0.0.gem ,安装 ruby用于搭建redis集群的脚本
    gem install redis-3.0.0.gem


    ## 使用ruby脚本搭建集群(进入redis-cluster源码目录中的src目录)
    ./redis-trib.rb create --replicas 1 192.168.159.133:7001 192.168.159.133:7002 192.168.159.133:7003 192.168.159.133:7004 192.168.159.133:7005 192.168.159.133:7006

    # 重点重点 
    
    报错:没有指定文件
  • redis5.x版本以上搭建集群
  1. 案例脚本
## 新版集群搭建命令(由于设置了密码,所以需要加上密码)
     ./redis-cli --cluster create 192.168.159.133:7001 192.168.159.133:7002 192.168.159.133:7003 192.168.159.133:7004 192.168.159.133:7005 192.168.159.133:7006 --cluster-replicas 1 -a redispassword

     # --cluster-replicas 1 命令的意思是:一主一从配置,六个节点就是三主三从

     # 看网上其他博客要求执行redis下的src目录下执行redis-cli,执行后提示:没有那个文件或目录
     
     ## 于是进入到redis01/bin/目录下,拷贝一份 redis-cli 到 redis-cluster/ 目录下
     cp ./redis-cli /usr/local/java/redis-cluster/

     # 在redis-cluster目录下执行
     ./redis-cli --cluster create 192.168.159.133:7001 192.168.159.133:7002 192.168.159.133:7003 192.168.159.133:7004 192.168.159.133:7005 192.168.159.133:7006 --cluster-replicas 1 -a redispassword

     ## 查看集群状态
     ./redis-cli --cluster check 192.168.159.133:7001 -a redispassword #填写任意节点即可

     ## redis-cli命令的时候应该加上 -c意思是在集群模式下
     ./redis-cli -h 192.168.159.134 -c -p 7001 -a redispassword

     ## 查看当前登录节点信息
     info replication

     ## 查看所有节点信息
     cluster nodes
  • 编写批量启停脚本
## 批量启动脚本 redis_cluster_start_all.sh
     cd /usr/local/java/redis-cluster/redis01/
     ./redis-server ./redis.conf
     
     cd /usr/local/java/redis-cluster/redis02/
     ./redis-server ./redis.conf
     
     cd /usr/local/java/redis-cluster/redis03/
     ./redis-server ./redis.conf
     
     cd /usr/local/java/redis-cluster/redis04/
     ./redis-server ./redis.conf
     
     cd /usr/local/java/redis-cluster/redis05/
     ./redis-server ./redis.conf
     
     cd /usr/local/java/redis-cluster/redis06/
     ./redis-server ./redis.conf


     ## 批量停止脚本 redis_cluster_stop_all.sh


     cd /usr/local/java/redis-cluster/redis01/
     ./redis-cli -p 7001 -a redispassword shutdown save
     
     cd /usr/local/java/redis-cluster/redis02/
     ./redis-cli -p 7002 -a redispassword shutdown save 
     
     cd /usr/local/java/redis-cluster/redis03/
     ./redis-cli -p 7003 -a redispassword shutdown save
     
     cd /usr/local/java/redis-cluster/redis04/
     ./redis-cli -p 7004 -a redispassword shutdown save
     
     cd /usr/local/java/redis-cluster/redis05/
     ./redis-cli -p 7005 -a redispassword shutdown save
     
     cd /usr/local/java/redis-cluster/redis06/
     ./redis-cli -p 7006 -a redispassword shutdown save


     ## 给新建文件赋予权限
     chmod +x redis_cluster_start_all.sh

Redis常用特性

多数据库

  • 概念
    一个redis实例最多可提供16个数据库,下标从0到15,客户端默认连接的是第0号数据库,也可以通过select选择连接哪个数据库
  • 常用命令
## 切换到第1号数据库
    select 1

    ## 将当前库的key移植到第1号数据库中
    move newkey 1

    ## 测试连接是否存活
    ping

    ## 在命令行打印一些内容,例如echo zhangsi,打印zhangsi
    echo

    ## 选择数据库
    select 1

    ## 退出连接
    quit

    ## 返回当前数据库中key的数目
    dbsize

    ## 获取服务器的信息和统计
    info

    ## 删除当前选择数据库中的所有的key
    flushdb

    ## 删除所有数据库中的所有key
    flushall

消息订阅与发布

  • 概念
    Redis发布/订阅(pub/sub)是一种消息通信模式:发送者发送消息(pub),订阅者(sub)接收消息。Redis可以订阅任意数量的频道
  • 常用命令
## 订阅频道
    subscribe channel

    ## 批量订阅频道
    psubscribe channel*

    ## 在指定的频道中发布消息
    publish channel content

    ## 退订频道
    unsubscribe channel

    ## 批量退订频道
    pubsubscribe pattern
  • 实操步骤
  1. 在第一个连接中,订阅mychat频道,此时如果没有人"发布"消息,当前窗口处于等待状态
  2. 在另一个窗口中,在mychat频道中,发布消息
  3. 在第三个窗口,批量订阅以my开头所有频道
  4. 在第二个窗口,分别在mychat和mychat2发布消息
  • 适用场景
    微信公众号、粉丝订阅、群聊

事务

  • 概念
    Redis中 MULTI/EXEC/DISCARD/ 这三个命令实现事务的基石
  • 特征
  1. 在事务中的所有命令都将会被串行化的顺序执行,事务执行期间,Redis不会再为其它客户端的请求提供任何服务,从而保证了事务中的所有命令被原子的执行
  2. 和关系型数据库中的事务相比,在Redis事务中如果有某一条命令执行失败,其后的命令仍然会被继续执行
  3. 开启一个事务
    MULTI
    在该语句之后执行的命令都将被视为事务之内的操作,最后我们可以通过执行EXEC/DISCARD命令来提交/回滚事务内的所有操作,这两个Redis命令可以视为等同于关系型数据库中的COMMIT/ROLLBACK语句
  4. 在事务开启之前,如果客户端和服务器之间出现通讯故障并导致网络断开,其后所有待执行的语句都将不会被服务器执行。然而如果网络中断事件是发生在客户端执行EXEC命令之后,那么该事务中的所有命令都会被服务器执行
  5. 当使用Append-Only模式时,Redis会通过调用系统函数write将该事务内的所有写操作在本次调用中全部写入磁盘。然而如果在写入的过程中出现系统奔溃,如果电源故障导致的宕机,那么此时也许只有部分数据被写入到磁盘,而另外一部分数据却已经丢失。Redis服务器会在重新启动时执行一系列必要的一致性检测,一旦发现类似问题,就会立即退出并给出响应的错误提示,此时我们要利用Redis工具包中提供的redis-check-aof工具,该工具可以帮助我们定位到数据不一致的错误,并将已经写入的部分数据进行回滚,修复之后我们就可以再次启动Redis服务器了
  • 常用命令
## 开启事务用于标记事务的开始,其后执行的命令都将被存入命令队列,直到执行EXEC时,这些命令才会被原子的执行,类似于关系型数据库中的:begin transaction
    multi

    ## 提交事务,类似于关系型数据库中的commit 
    exec 

    ## 事务回滚,类似于关系型数据库中的rollback
    discard

    ## 取消WATCH命令对所有key的监视(如果在执行exec或discard命令后,不需要执行unwatch)
    unwatch

    ## 监视一个(或多个)key,如果在事务执行之前这个(或这些)key被其他命令所改动,那么事务将被打断
    watch key[key...]
  • 案例演示
## 测试提交
    # 1. 在窗口1,设置num,并获得数据
    set num 1
    # 2. 在窗口2,num累加1,并获得数据
    incr num
    # 3. 在窗口1,获得数据
    get num
    # 4. 在窗口1,开启事务,多次累加数据
    multi
    incr num
    incr num
    # 5. 在窗口2,获得数据
    get num
    # 6. 提交事务
    exec




    ## 测试回滚
    # 1. 设置值
    set user jack    
    # 2. 获取值
    get user         
    # 3. 开启事务
    multi            
    # 4. 设置新数据
    set user rose    
    # 5. 回滚事务
    discard          
    # 6. 获取新值
    get user         




    ## 测试失败
    # 1. 设置值
    set num 10       
    # 2. 获取值
    get num          
    # 3. 开启事务
    multi            
    # 4. +5 num=15
    incrby num 5     
    # 5. 累加x 抛异常
    incrby num x     
    # 6. +5 num=20
    incrby num 5     
    # 7. 提交事务:当提交事务,执行所有操作,如果部分操作异常,将被忽略
    exec             
    # 8. 获取新值
    get num
  • 适用场景
    商品秒杀,转账
  • 注意事项
    在Redis集群中,事务执行不成功

数据淘汰策略

  • 概念
    官方警告,当内存不足时,Redis会根据配置的缓存策略淘汰部分key,以保证写入成功。当无淘汰策略时,或没有找到合适淘汰的key时,Redis直接返回out of memory错误
  • 常见配置
## 允许用户设置最大使用内存大小
    maxmemory 512G
  • 常用淘汰策略
    采用LRU算法动态将不用的数据删除。内存管理的一种页面置换算法,对于在内存中但又不用的数据块(内存块)叫作LRU,操作系统会根据哪些数据属于LRU而将其移出内存而腾出空间来加载另外的数据(以下是常用内存策略)
volatile-lru    ## 设定超时时间的数据中,删除最不常使用的数据

    allkeys-lru     ## 查询所有的key中最近最不常使用的数据进行删除(应用最广泛的策略)

    volatile-random ## 在已经设定了超时的数据中随机删除

    allkeys-random  ## 查询所有的key之后随机删除

    volatile-ttl    ## 查询全部设定超时时间的数据,之后排序,将马上将要过期的数据进行删除操作

    no-eviction      ## 禁止驱逐数据,如果设置为该属性,则不会进行删除操作,如果内存溢出则报错返回(默认内存策略)

    volatile-lfu    ## 从所有配置了过期时间的键中驱逐使用频率最少的键

    allkeys-lfu     ## 从所有键中驱逐使用频率最少的键

数据持久化

  • 持久化方式
  1. RDB持久化(默认支持,无需配置)
    在指定的时间间隔内将内存中的数据集快照写入磁盘
  2. AOF持久化
    以日志形式记录服务器所处理的每一个写操作,在Redis服务器启动之初会读取该文件来重新构建数据库,以保证启动后数据库中的数据是完整的
  3. 无持久化
    通过配置方式禁用Redis服务器的持久化功能,类似于一个功能加强版的memcached
  4. redis可以同时使用RDB和AOF

RDB持久化

  • 概念
    RDB相当于照快照,保存的是一种状态。快照是默认的持久化方式,这种方式是将内存中数据以快照的方式写入到二进制文件中,默认的文件名是dump.rdb
  • 优势
    快照保存数据极快、还原数据极快,适用于灾难备份
  • 劣势
    小内存机器不适合使用,RDB机制符合要求就会照快照
  • 快照条件
## 服务器正常关闭
    ./bin/redis-cli shutdown

    ## key满足一定条件

    vim redis.conf  ## 搜索save

    save 900 1      # 每900秒(15分钟)至少1个key发生变化,产生快照
    save 300 10     # 每300秒(5分钟)至少10个key发生变化,产生快照
    save 60 10000   # 每60(1分钟)至少10000个key发生变化,产生快照

AOF持久化

  • 概念
    aof(append-only file)
  • 优势

提供三种同步策略:每秒同步,每修改同步和不同步

该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容,然而如果我们本次操作只是写入了一半的数据就出现了系统崩溃问题,在Redis下一次启动之前,我们可以通过redis-check-aof工具帮助解决数据一致性的问题

如果日志过大,redis可以自动启动rewrite机制,即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行,因此在进行rewirte切换时可以更好的保存数据安全性

AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建

  • 劣势

同等数量的数据集,AOF文件通常要大于RDB文件

根据同步策略的不同,AOF在运行效率上会慢于RDB,总之,每秒同步策略的效率是比较高,同步禁用策略的效率和RDB一样高效

  • 配置AOF
## 同步策略配置信息

    # 启用aof持久化方式
    appendonly yes

    # 每次有数据修改发生时,都会写入AOF文件
    appendfsync always          

    # 每秒中同步一次,该策略为AOF的缺省策略
    appendfsync everysec        

    # 从不同步,高效但是数据不会被持久化
    appendfsync no  

    # 重写AOF,若不满足重写条件时,可以手动重写             
    bgrewirteaof
  • 数据恢复演示
## 1. 开启aop,并设置成总是保存,重启Redis
    appendonly yes
    appendfsync always

    ## 2. 在窗口1设置值
    ## 3. 清空数据库
    flushall

    ## 4. 在窗口2中及时关闭redis服务器(防止dump.rdb)
    shutdown nosave

    ## 5. 编辑appendonly.aof文件,将日志中的flushall命令删除

    ## 6. 在窗口1中启动redis,然后查询数据库内容

启动多个Redis

  • 方法1:启动时指定端口可在一台服务器启动多个Redis进程。(多个Redis)
./bin/redis-server ./redis.conf --port 6380
  • 方法2:复制redis目录,然后编写redis.conf修改端口[推荐使用]
## 拷贝redis目录
    cp -r redis/ redis6380

    ## 修改redis.conf文件
    cd redis6380
    vim redis.conf

    ## 启动多个redis
    cd /usr/local
    ./redis/bin/redis-server ./redis/redis.conf
    ./redis6380/bin/redis-server ./redis/redis.conf
    ps aux | grep -i redis

    ## 关闭指定端口的Redis
    ./bin/redis-cli -p 6380 shutdown

Redis缓存与数据库一致性

实时同步

  • 实现思路
    对一致性要求比较高的,应采用实时同步方案:
  1. 查询缓存查询不到再从DB查询,保存到缓存;
  2. 更新缓存时,先更新数据库,再将缓存设置过期(建议不要更新缓存内容,直接设置缓存过期)
  • 常用注解
//查询时使用,注意Long类型需转换为String类型,否则会抛异常
    @Cacheable


    //更新时使用,使用此注解,一定会从DB上查询数据
    @CachePut


    //删除时使用
    @CacheEvict

    //组合用法
    @Caching

异步队列

  • 实现思路
    对于并发程度较高的,可采用异步队列的方式同步,可采用kafka等消息中间件处理消息生产和消费

Canal同步工具

  • 实现思路
    canal实现方式是模拟mysql slave和 master的同步机制,监控DB bitlog的日志更新来触发缓存的更新,此种方法可以减少工作量,但在使用存在局限性

UDF自定义函数

  • 实现思路
    面对mysql的API进行编程,利用触发器进行缓存同步

注意事项

缓存穿透

  • 概念
    缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
  • 解决方案
    持久层查询不到就缓存空结果,查询时先判断缓存中是否exists(key),如果有直接返回空,没有则查询后返回。
    注意insert时需清除查询的key,否则即便DB中有值也查询不到(当然也可以设置空缓存的过期时间)。

缓存雪崩

  • 概念
    如果缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩
  • 解决方案
  1. 加锁排队
  2. 计数
  3. 滑动窗口
  4. 令牌桶(Token Bucket)
  5. 漏桶(Leaky Bucket)

热点key

  • 概念
    某一个key被访问非常频繁,当key失效的时候有大量线程来构建缓存,导致负载增加,系统崩溃
  • 解决方案
  1. 使用锁,单机用synchronized,lock等,分布式用分布式锁
  2. 缓存过期时间设置在key对应的value中,如果检测到存的时间超过过期时间时,则异步更新缓存
  3. 在value设置一个比过期时间t0小的过期时间值t1,当t1过期的时候,延长t1并做更新缓存操作
  4. 设置标签缓存,标签缓存设置过期时间,标签缓存过期后,需异步地更新实际缓存