Redis学习笔记
系统学习 Redis 0 227

基本概念

  • 名称: Remote Dictionary Service
  • 特点:
    • 基于内存的快速访问
    • 以key-value形式存储
  • 用途:
    • 作为缓存,存储不经常改变的数据
    • 作为数据库,存储热点数据
    • 作为消息队列,实现异步通信(用Redis Stream)
  • 场景:
    • 用户登录后端保存短信验证码,并设置失效时间
    • 存储Session数据,实现分布式缓存【Servlet Session默认运行在内存中,持久化到硬盘上】
  • 同类(NoSQL): Mongodb、MemCached
  • 存储结构:
    • 默认16个db(0~15)
  • 数据类型:
    • 键规则:
      • 不能重复
      • 作用: 标识存储的数据
      • 数据类型: string
      • 命名规则: 不能太长(因为查询的效率低,查询起来不方便), 不能太短(容易重复,同时可读性也差), 可以采用MORNINGSTAR_STU_LIST的格式
    • 值规则: 值支持五种数据类型
      • string: 字符串类型【e.g. 用户个人介绍】
        • 最基础的数据存储类型,无论存入的是字符串、整数、浮点类型都会以字符串写入
        • 最多可以容纳的数据长度是512M
        • 其他类型都可以存储4G个字符串
      • hash: 键和值都是string类型的Map容器【e.g. 会话数据】
      • list: 按照插入顺序排序的字符串链表【e.g. 消息队列、评论列表】
      • set: 字符串集合【e.g. 用户好友列表】
      • sorted set/zset: 有序的字符串集合,每个元素有一个分数用来决定它的顺序【e.g. 权重排行榜】
  • 并发访问: 使用单线程模型来处理客户端的命令请求,让所有的命令执行都在一个线程中按顺序执行(无锁队列),避免了多线程带来的复杂性和线程安全问题

常用命令

  • string:
    • set <key> <value>: 添加或修改一个键和值,键不存在就是添加,存在就是修改
    • get <key>: 获取值,如果存在就返回值,不存在返回nil
    • del <key1> [<key2>...]: 删除指定的键,返回删除的个数
    • setex <key> <seconds> <value>set <key> <value> ex <seconds>: 设置指定键的值和过期时间
    • expire <key> <seconds>: 设置一个过期时间【存在键返回1,否则返回0】
    • setnx <key> <value>set <key> <value> nx: 保存键值对,如果键存在则不保存,不存在则保存
    • mset <key1> <value1> [<key2> <value2>...]
  • hash:
    • hset <key> <field> <value>
    • hget <key> <field>
    • hmset <key> <field1> <value1> [<field2> <value2>...]
    • hmget <key> <field1> [<field2>...]
    • hdel <key> <field1> [<field2>...]
    • hgetall <key>
    • hkeys key
    • hvals key
  • list:
    • lpush <key> <value1> [<value2>...]: 从左向指定的键中添加元素,返回元素个数
    • rpush <key> <value1> [<value2>...]
    • lpop <key>: 从左边删除一个元素,返回被删除的元素
    • rpop <key>
    • lrange <key> <startIndex> <endIndex>: 得到键中[startIndex, endIndex]范围内的元素【从左向右: 0n,从右向左: -1-(n+1),所有元素: 0-1
    • lindex <key> <index>: 查询指定索引的元素
    • llen <key>: 获取列表的长度
    • brpop <key> <seconds>: 移出并获取最后一个元素,无则元素阻塞至超时【Blocking Right POP】
    • lrem <key> <count> <value>: 从表头删除指定个数的元素
  • set:
    • sadd <key> <value1> [<value2>...]
    • smembers <key>: 得到这个集合中所有的元素
    • sismember <key> <value>: 判断元素在集合中是否存在,存在返回1,不存在返回0
    • srem <key> <value1> [<value2>...]
    • sinter <key1> <key2> [<key3>...]: 返回给定所有集合的交集
  • zset: 【分数是double的,底层是ziplist(压缩表)或skiplist(跳跃表)】
    • zadd <key> <score1> <value1> [<key2> <value2>...]
    • zrange <key> <startIndex> <endIndex> [withscores]
    • zrevrange <key> <startIndex> <endIndex> [withscores]
    • zrem <key> <value1> [<value2>...]
    • zcard <key>: 得到元素个数
    • zrank <key> <value>: 得到元素的索引号
    • zscore <key> <value>
  • geo: 地理信息
    • geoadd: GEOADD key longitude latitude member [longitude latitude member ...]
    • geopos: GEOPOS key member [member ...]
    • geodist: GEODIST key member1 member2 [m|km|ft|mi]
    • georadius: GEORADIUS key longitude latitude radius m|km|ft|mi
    • georadiusbymember: GEORADIUSBYMEMBER key member radius m|km|ft|mi
    • geohash: GEOHASH key member [member ...]
  • 通用:
    • rename <key1> <key2>
    • keys <pattern>: 查询键,可以使用*匹配多个字符,使用?匹配1个字符
    • del <key1> [<key2>...]
    • exists <key>
    • type <key>
    • select <dbId>
    • move <key> <dbId>
    • ttl <key>: 返回键的剩余生存时间,以秒为单位
      【如果键不存在或者已过期,返回-2;如果key存在并且没有设置过期时间(永久有效),返回-1】
    • flushall: 清空redis所有的库
  • 其他:
    • 认证: auth <password>

持久化机制

  • 定义: 将内存中的数据同步到磁盘
  • 方式: 【一般在企业开发中两种持久化机制会配合着使用】

    • RDB: (Redis DataBase) 默认的持久化方式,以二进制的方式将数据写入文件中【每隔一段时间写入一次】
      • 默认文件名: dump.rdb
      • 使用:
        • 配置:
          save <seconds> <num>
          • <seconds>秒内执行过<num>次set操作,则调用bgsave(异步save)持久化一次
          • 可以同时配置多个save命令
          • save "": 不使用RDB存储,不能主从
        • 手动: 调用savebgsave
      • 优缺点:
        • 优点:
          • 持久化空间效率高(磁盘占用小),持久化的是内存中的数据
          • 数据库宕机后,数据恢复的效率要更高
          • 性能最大化【由子进程完成持久化工作】
        • 缺点:
          • 可能导致数据丢失
    • AOF: (Append Only File) 以文本文件的方式记录用户的每次操作,数据还原时候,读取AOF文件,模拟用户的操作,将数据还原
      • 默认文件名: appendonly.aof
      • 启动AOF: appendonly yes
      • 同步策略(appendfsync):
        • 每秒同步(everysec)
        • 每修改同步(always)
        • 完全依赖操作系统(no)【每30秒一次】
      • AOF文件格式:
        • *2: 表示有2个命令
        • $6: 表示下一个词有6个字符
      • 重写优化:
        • 配置
          #当前aof文件大小超过上一次aof文件大小的百分之多少时进行重写。如果之前没有重写过,以
          启动时aof文件大小为准
          auto-aof-rewrite-percentage 100
          
          #限制允许重写最小aof文件大小,也就是文件大小小于64mb的时候,不需要进行优化
          auto-aof-rewrite-min-size 64mb
        • 手动: bgrewriteaof
      • 优缺点:
        • 优点:
          • 数据安全性更高
        • 缺点:
          • 恢复速度比RDB更慢
          • 磁盘占用比RDB更大
  • 实践:

    • 如当前只追求高性能,不关注数据安全性,则关闭RDB和AOF,如redis宕机重启,直接从数据源恢复数据
    • 如需较高性能且关注数据安全性,则开启RDB,并定制触发规则
    • 如更关注数据安全性,则开启AOF

单线程设计

  • 原因:
    • 在单线程模型中,可以避免多线程带来的上下文切换和线程同步的开销,从而提高性能
    • 单线程模型简化了代码的复杂性,减少了并发编程中常见的问题,如死锁、竞态条件等
    • 虽然Redis是单线程的,但它主要受限于内存和网络带宽,而不是CPU,因此单线程设计不会成为性能瓶颈
    • 单线程模型使得 Redis 的行为更加可预测,因为所有的操作都是顺序执行的
  • 发展: Redis 6.0引入了多线程功能,用于处理客户端的输入输出操作,进一步提高了性能,但这并不改变Redis核心操作的单线程特性

事务机制

  • 性质: 弱事务
  • 特点:
    • 某操作编译出错(语法问题)后,事务无法执行
    • 某操作执行出错后,继续执行后面的操作
    • 能保证事务中的操作不被事务外的操作干扰
  • 使用:
    #开启事务
    multi
    
    #添加命令
    sadd user:1001:follow 1002
    sadd user:1002:follow 1001
    sadd user:1001:fans 1002
    sadd user:1002:fans 1002
    
    #执行事务
    exec
    # 取消事务
    discard

高可用

  • 目标: 减少系统的停机时间,提高服务的稳定性和可靠性
  • 总结:
    • 主从模式: 读写分离,负载均衡,一个Master可以有多个Slaves
    • 哨兵模式: 监控,自动转移,哨兵发现主服务器挂了后,就会从slave中重新选举一个主服务器
    • 分片集群: 将数据按一定的规则分配到多台机器,内存/QPS不受限于单机,提高并发量和数据容量

主从复制

  • 定义: 将一台Redis服务器的数据复制到其他Redis服务器的过程
  • 数据同步
    • 步骤:
    • 全量复制与部分复制:
      • 全量复制: 主节点发送所有数据给从节点
      • 部分复制: 在网络闪断后,主节点只发送丢失的数据给从节点
  • 使用:
    • 配置:
      • 主:
        bind 0.0.0.0
        protected-mode no
        port 6379
        daemonize yes
        requirepass "123456"
        logfile "/usr/local/redis/logs/redis_6379.log"
        dbfilename "redis_6379.rdb"
        dir "/usr/local/redis/data"
        appendonly yes
        appendfilename "appendonly_6379.aof"
        masterauth "123456"
      • 从1:
        bind 0.0.0.0
        protected-mode no
        port 6380
        daemonize yes
        requirepass "123456"
        logfile "/usr/local/redis/logs/redis_6380.log"
        dbfilename "redis_6380.rdb"
        dir "/usr/local/redis/data"
        appendonly yes
        appendfilename "appendonly_6380.aof"
        
        masterauth "123456"
        replicaof 192.168.200.130 6379
      • 从2:
        bind 0.0.0.0
        protected-mode no
        port 6381
        daemonize yes
        requirepass "123456"
        logfile "/usr/local/redis/logs/redis_6381.log"
        dbfilename "redis_6381.rdb"
        dir "/usr/local/redis/data"
        appendonly yes
        appendfilename "appendonly_6381.aof"
        
        masterauth "123456"
        replicaof 192.168.200.130 6379
    • 查看: info replication
  • 实践:
    • 读写分离: 在主从复制的基础上,可以将读操作分散到多个从节点上执行,而写操作则在主节点上执行
    • 持久化优化: 从节点上开启持久化、在主节点关闭持久化
      • 主节点宕机的解决: 将从节点提升为主节点
        • 选择一从节点,提升为主节点: slaveof no one
        • 将其他节点的主节点设置为新的主节点: slaveof 192.168.200.130 6380
  • 作用:
    • 读写分离: 主写从读,提高服务器的读写负载能力
    • 负载均衡: 分担服务器负载,提高服务器的并发量,尤其是在读多写少的场景下
    • 数据冗余: 实现数据的热备份
    • 故障恢复: 当主节点出现问题,从节点仍然可以提供服务,实现快速的故障恢复
    • 高可用基石: 基于主从复制,构建哨兵模式与集群,实现Redis的高可用方案

哨兵模式

  • 定义: 哨兵(sentinel)系统可以监控Redis主节点的状态,并且在主节点发生故障时自动进行故障转移,将一个从节点提升为新的主节点
  • 任务:
    • 监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常
    • 提醒(Notification): 当被监控的某个Redis服务器出现问题时,Sentinel可以通过API向管理员或者其他应用程序发送通知
    • 自动故障迁移(Automatic failover):
      • 当一个主服务器不能正常工作时,Sentinel会开始一次自动故障迁移操作,它会将失效主服务器的其中一个从服务器升级为新的主服务器,并让失效主服务器的其他从服务器改为复制新的主服务器
      • 当客户端试图连接失效的主服务器时,集群也会向客户端返回新主服务器的地址
  • 哨兵需求:
    • 个数: 在实际部署中,哨兵模式需要至少三个哨兵实例以实现故障转移
    • 类型: 哨兵实例本身也是Redis服务器,但不提供数据服务
    • 配置: 哨兵模式的配置包括设置监控的主节点信息、密码、超时参数等
  • 集群节点哨兵模式工作原理:
    • 哨兵集群组成: 基于pub/sub机制
      • 哨兵间发现: 主库频道__sentinel__:hello,不同哨兵通过它相互发现
      • 哨兵发现从库: 向主库发送INFO命令
    • 基于pub/sub机制的客户端事件通知
      • 事件: 主库下线事件
        • +sdown: 实例进入“主观下线”状态
        • -sdown: 实例退出“主观下线”状态
        • +odown: 实例进入“客观下线”状态
        • -odown: 实例退出“客观下线”状态
      • 事件: 从库重新配置事件
        • +slave-reconf-sent: 哨兵发送 SLAVEOF 命令重新配置从库
        • +slave-reconf-inprog: 从库配置了新主库,但尚未进行同步
        • +slave-reconf-done: 从库配置了新主库,且完成同步
      • 事件: 新主库切换
        • +switch-master: 主库地址发生变化
    • 步骤:
      1. 监控阶段: 哨兵不断检查主从节点是否正常运行
      2. 通知阶段: 当主节点出现问题时,哨兵会向其他哨兵和客户端发送通知
      3. 故障转移阶段: 哨兵通过投票机制选举出一个从节点作为新的主节点,并将其他从节点连接到新的主节点
    • 下线判断:
      • 主观下线(Subjectively Down, SDOWN): 主库或从库对某个Sentinel的PING响应超时
      • 客观下线(Objectively Down, ODOWN): 多个Sentinel在对同一个服务器做出SDOWN判断
        • 只用于主服务器
        • Sentinel之前会相互交换对某个主服务器的下线意见,达到一定数量(quorum)后才会将SDOWN转为ODOWN
    • 新主服务器选举:
      • 两个条件:
        • 拿到半数以上的赞成票
        • 拿到的票数>=quorum
      • 如果未选出,则集群会等待一段时间(failover-timeout的2倍),再重新选举
  • 使用:
    • 配置:
      #不限制ip
      bind 0.0.0.0
      
      # 让sentinel服务后台运行
      daemonize yes
      
      # 配置监听的主服务器,mymaster代表服务器的名称,自定义,192.168.200.130 代表监控的主服务器,6379代表端口,
      #2代表只有两个或两个以上的哨兵认为主服务器不可用的时候,才会进行failover操作。
      # 计算规则:哨兵个数/2 +1
      sentinel monitor mymaster 192.168.200.130 6379 2
      
      # sentinel auth-pass定义服务的密码,mymaster是服务名称,123456是Redis服务器密码
      sentinel auth-pass mymaster 123456
      
      #超过5秒master还没有连接上,则认为master已经停止
      sentinel down-after-milliseconds mymaster 5000
      
      #如果该时间内没完成failover操作,则认为本次failover失败
      sentinel failover-timeout mymaster 30000
    • 启动: 添加--sentinel参数
  • 优缺点:
    • 优点:
      • 主从集群间可以实现自动切换,可用性更高
      • 数据更大限度的防止丢失
      • 解决哨兵的集群高可用问题,减少误判率
    • 缺点:
      • 海量数据存储问题
      • 高并发写的问题

分片集群(Redis Cluster)模式

  • 原理:
    • 数据切片和实例的对应分布关系
      • Redis Cluster方案: 无中心化
        • 采用哈希槽(Hash Slot)来处理数据和实例之间的映射关系
        • 一个切片集群共有16384个哈希槽,只给Master分配
        • 具体的映射过程
          1. 根据键值对的key,按照CRC16算法计算一个16bit的值
          2. 再用这个16bit值对16384取模,每个模数代表一个相应编号的哈希槽
        • 16384的由来:
          • 正常的心跳数据包携带节点的完整配置,它能以幂等方式来更新配置
          • 如果采用16384个插槽,占空间2KB;如果采用65536个插槽,占空间8KB
          • Redis Cluster不太可能扩展到超过1000个主节点,太多可能导致网络拥堵
          • 16384个插槽范围比较合适,当集群扩展到1000个节点时,也能确保每个master节点有足够的插槽,也能保证心跳包的大小合理
      • 哈希槽映射方法
        • cluster create命令创建集群,Redis会自动把这些槽平均分布在集群实例上
        • 也可以使用cluster meet命令手动建立实例间的连接,形成集群,再使用cluster addslots命令,指定每个实例上的哈希槽个数
    • 客户端如何定位数据:
      • Redis实例会把自己的哈希槽信息发给和它相连接的其它实例,来完成哈希槽分配信息的扩散【流言协议】
      • 集群中,实例和哈希槽的对应关系并不是一成不变的【实例新增或删除、master宕机】,实例之间可以通过相互传递消息,获得最新的哈希槽分配信息,但客户端是无法主动感知这些变化
      • 客户端和集群实例建立连接后,实例就会把哈希槽的分配信息发给客户端,客户端会把哈希槽信息缓存在本地
      • 重定向机制: 如果实例上没有该键值对映射的哈希槽,就会返回MOVED命令,客户端会更新本地缓存
  • 使用:【至少3主3从】
    • 配置:
      • 启动:
        • 配置各个节点
          bind 0.0.0.0
          protected-mode no
          port 7001
          daemonize yes
          requirepass "123456"
          logfile "/usr/local/redis/logs/redis_7001.log"
          dbfilename "redis_7001.rdb"
          dir "/usr/local/redis/data"
          appendonly yes
          appendfilename "appendonly_7001.aof"
          masterauth "123456"
          
          #是否开启集群
          cluster-enabled yes
          # 生成的node文件,记录集群节点信息,默认为nodes.conf,防止冲突,改为nodes-7001.conf
          cluster-config-file nodes-7001.conf
          #节点连接超时时间
          cluster-node-timeout 20000
          #集群节点的ip,当前节点的ip, 如果是阿里云是可以写内网ip
          cluster-announce-ip 192.168.200.130
          #集群节点映射端口
          cluster-announce-port 7001
          #集群节点总线端口,节点之间互相通信,常规端口+1万
          cluster-announce-bus-port 17001
        • 逐个启动实例
        • 加入Redis集群(任意一个Redis实例即可)
          redis-cli -a 123456 --cluster create 192.168.200.130:7001 192.168.200.130:7002 192.168.200.130:7003 192.168.200.130:7004 192.168.200.130:7005 192.168.200.130:7006 --cluster-replicas 1
          • --cluster: 构建集群全部节点信息
          • --cluster-replicas 1: 主从节点的比例,1表示1主1从的方式
      • 修改集群:
        • 集群扩容:
          • 添加新节点:
            redis-cli -a 123456 --cluster add-node 192.168.200.130:7007 192.168.200.130:7001
          • 重新分配哈希槽: 将7001, 7002, 7003中的100个哈希槽挪给7007
            redis-cli -a 123456 --cluster reshard 192.168.200.130:7001 --cluster-from 968e39e094e4109c45f99b1da6a157d7e41d54be,aa04c1e93d23d1e7394acb6bf7560dd2d9b5ace0,b81a6bdffaaf52be1e3522249aad6cda00ae3674 --cluster-to 9b9ad250c589b30e9ff7a4fbd10c8bbc9eb94793 --cluster-slots 100
            • --cluster-from: 表示slot目前所在的节点的node ID,多个ID用逗号分隔
            • --cluster-to: 表示需要新分配节点的node ID
            • --cluster-slots: 分配的slot数量
          • 添加从节点: 将7008作为7007的从节点
            redis-cli -a 123456 --cluster add-node 192.168.200.130:7008 192.168.200.130:7007 --cluster-slave --cluster-master-id 9b9ad250c589b30e9ff7a4fbd10c8bbc9eb94793
            • add-node: 后面的分别跟着新加入的slave和slave对应的master
            • cluster-slave: 表示加入的是slave节点
            • --cluster-master-id: 表示slave对应的master的node ID
        • 集群缩容:
          • 删除从节点:
            redis-cli -a 123456 --cluster del-node 192.168.200.130:7008 bf12b1df363f139911b83f73038313951855e468
            • del-node: 删除节点,后面跟着slave节点的ip:port和node ID
          • 归还哈希槽:
            redis-cli -a 123456 --cluster reshard 192.168.200.130:7007 --cluster-from 9b9ad250c589b30e9ff7a4fbd10c8bbc9eb94793 --cluster-to 968e39e094e4109c45f99b1da6a157d7e41d54be --cluster-slots 33 --cluster-yes
          • 删除主节点:
            redis-cli -a 123456 --cluster del-node 192.168.200.130:7007 9b9ad250c589b30e9ff7a4fbd10c8bbc9eb94793
    • 查看:
      • 访问集群
        #  连接到指定redis节点
        bin/redis-cli -a 123456 -c -h 192.168.200.130 -p 7001
        
        #                 密码    集群命令   检查状态   节点地址
        bin/redis-cli -a 123456 --cluster check 192.168.200.130:7001
      • 集群相关命令
        CLUSTER INFO   // 打印集群的信息
        CLUSTER NODES  // 列出集群当前已知的所有节点(node),以及这些节点的相关信息。
        
        //节点
        CLUSTER MEET    // 将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子。
        CLUSTER FORGET  // 从集群中移除 node_id 指定的节点。
        CLUSTER REPLICATE   // 将当前节点设置为 node_id 指定的节点的从节点。
        CLUSTER SAVECONFIG  // 将节点的配置文件保存到硬盘里面。
        CLUSTER ADDSLOTS  [slot ...]  // 将一个或多个槽(slot)指派(assign)给当前节点。
        CLUSTER DELSLOTS  [slot ...]  // 移除一个或多个槽对当前节点的指派。
        CLUSTER FLUSHSLOTS // 移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。
        CLUSTER SETSLOT  NODE  // 将槽 slot 指派给 node_id 指定的节点。
        CLUSTER SETSLOT  MIGRATING  // 将本节点的槽 slot 迁移到 node_id 指定的节点中。
        CLUSTER SETSLOT  IMPORTING  // 从 node_id 指定的节点中导入槽 slot 到本节点。
        CLUSTER SETSLOT  STABLE // 取消对槽 slot 的导入(``import``)或者迁移(migrate)。
        //键
        CLUSTER KEYSLOT  // 计算键 key 应该被放置在哪个槽上。
        CLUSTER COUNTKEYSINSLOT  // 返回槽 slot 目前包含的键值对数量。
        CLUSTER GETKEYSINSLOT    // 返回 count 个 slot 槽中的键。
        //新增
        CLUSTER SLAVES node-id  // 返回一个master节点的slaves 列表
  • 评价: 在哨兵模式的基础上解决了海量数据存储的问题,并且提升了写并发量

过期删除策略

  • 定时删除
    • 方法: 它会在设置键的过期时间的同时,创建定时器,当键到了过期时间,定时器会立即对键进行删除
    • 评价:
      • 能够保证过期键的尽快删除,快速释放内存空间 => 内存空间友好
      • 大量定时器执行,严重降低系统性能 => CPU非常不友好
  • 惰性删除
    • 方法: 在获取key时,才会检查key是否过期,如果过期则删除该key
    • 评价: 对CPU足够友好,但是对内存空间非常不友好
  • 定期删除
    • 默认每秒运行10次会对具有过期时间的key进行一次扫描
    • 每次默认只会随机扫描20个key,同时删除这20个key中已经过期的key
    • 如果这20个key中过期key的比例达超过25%,则继续扫描
    • 参数设置: hz [次数]【一般不超过100】

内存淘汰策略

  • 目的: 进一步解决空间不足问题【除了修改maxmemory(一般为物理内存的四分之三左右)】
  • 策略:
    • 对于LRUTTL相关策略,每次触发时,redis会默认从5个key中一个key符合条件的key进行删除。如果要修改的话,可以修改maxmemory-samples属性值
  • 使用:
    • Redis只做缓存,不做DB持久化,使用allkeys【如状态性信息,经常被访问,但数据库不会修改】
    • 同时用于缓存和DB持久化,使用volatile【如商品详情页】
    • 存在冷热数据区分,则选择LRU或LFU【如热点新闻,热搜话题等】
    • 每个key被访问概率基本相同,选择使用random【如企业内部系统,访问量不大,删除谁对数据库也造成太大压力】
    • 根据超时时间长久淘汰数据,选择选用ttl【如微信过期好友请求】
编写
预览