分布式系列十二: Redis高级主题

持久化

Redis 支持持久化, 其持久化数据有两种方式. 两种可以同时使用. 如果同时使用, Reids 在重启时将使用 AOF 方式来还原数据.

RDB

按照一定策略定时同步内存的数据到磁盘.文件名 dump.rdb

  • snapshot: 快照复制. Redis在指定情况下触发快照: (1) 按配置的规则;(2) save 或 bgsave 命令执行;(3) flushall 命令; (4)执行复制

    1. 配置的规则: save seconds exchange 当在 seconds 指定的时间内, key 的数量更改大于 exchange 时发生快照.

    2. save 或 bgsave 命令: 执行快照同步操作, 注意save这个操作会暂时阻塞客户端请求. bgsave则不会阻塞

    3. flushall: 清除内存所有数据, 只要规则不为空, redis就会执行快照

    4. 执行复制:

  • 快照原理

    fork 复制一份当前进程的副本, 这个进程是子进程, 负责同步持久化到磁盘. 而父进程负责处理客户端请求.

  • 快照的优缺点:

    1. 缺点: 可能会丢失数据, 在下一次快照前宕机
    2. 优点: 最大化Redis的性能, 父子进程职责分离

AOF

保存命令到磁盘, 也就是持久化日志.

  • 配置: appendonly yes 启动aof. 默认的文件名是 appendonly.aof.

  • 配置 auto-aof-rewrite-percentage 100 当 aof 文件与上一次文件的大小相比, 超过配置的百分比就进行重写

  • 配置 auto-aof-rewrite-min-size 64m 限制允许重写最小 aof 文件大小, 即小于64m时不重写

  • aof 重写原理: aof 重写是安全的. 相当于同时将命令追加到现有的aof文件, 同时写入新的 aof 临时文件, 临时文件最终将覆盖原 aof 文件.

  • 同步磁盘数据: aof机制会将命令记录到aof文件, 但实际是同步到操作系统的缓存区, 最终由操作系统同步到磁盘. 可以通过下面配置修改策略
    appendsync always 每次执行写入就同步, 安全但影响性能
    appendsync everysec 每一秒执行
    appendsync no 不执行同步, 由操作系统去执行, 效率高但不安全

  • aof 文件损坏后的修复, 使用redis-check-aof-fix

集群

master/slave 复制

主从方式, 从是只读的, slave也可以有自己的slave.

  • slave节点上配置 slaveof masterip masterport

  • 配置 slave-serve-stale-data no 可以用来保证数据同步后再做其他操作

  • 命令info replication可以查看信息.

  • 实现原理:

    1. slave 连接到 master 后, 会向 master 发送 SYNC 命令.
    2. master 收到命令后, 会做两件事(1) 执行bgsave;(2)master 将收到的修改命令存入缓冲区, 再将命令传输给slave
  • 复制方式:

    1. 基于rdb文件复制
    2. 无硬盘复制 配置rpli-diskless-sync yes
    3. 增量复制 PSYNC master run id. offset
  • 命令replconf listening-port 6379可以用来查看复制过程

  • 缺点: 无法做master选举

sentinale 哨兵模式

  1. 监控
  2. master选举
  • 配置文件为sentinel.conf

  • 配置节点: sentinel monitor mymaster 192,168,11,111 6379 2 最后的2为投票数

  • 是高可用方案, 但不是高性能方案

集群

  • 原理

    Reids有slot槽的概念: redis中有16384个. 根据key的 CRC16 算法, 取得的结果与槽数取模.落入的槽的索引是固定的. 然后根据节点数将槽的范围确定到每个节点上.

    当节点新增和删除时, 节点的槽范围发生变化, 数据迁移需要人工干预.

  • 三方方案

    1. redis shardding : jedis支持
    2. codis : 代理, 分片和数据迁移自动化
    3. twemproxy :

缓存穿透问题

  • 数据库中不存在的对象查询后也缓存.
if(objJson!=null){
    redisService.expire("key",timeout:3*60);
} else {
    redisService.expire("key",timeout:5); //5秒内不查询数据库
}
  • synchronized 方法, 不理想, 排队影响性能.
  • 同步块, 需要双重判定, 否则阻塞的线程均会查询数据库, 代码如下
String value = redisService.get("key");
if(value==null){
    synchronized(lock){
        value = redisService.get("key"); //多线程再次查询,防止数据库多次查询
        if(value==null){
            value = dao.get();
            redisService.set("key",value);
            if(objJson!=null){
                redisService.expire("key",timeout:3*60);
            } else {
                redisService.expire("key",timeout:5); //5秒内不查询数据库
            }
        }
    }
}
  • 使用ReentrantLocktryLock(),else中线程等待一段时间, 这样不阻塞进程

  • 使用分布式锁

if(redisService.setNx("key")==true){
    //查询数据库
}
  • 不设超时时间或设置一个较长的超时时间, 然后判断当前时间是否超过缓存时间, 结合分布式锁, 超过则刷新缓存. 此解决方法可能会发生少量数据不一致的情况.

采用何种策略需要结合实际需求, 是保证可用, 还是保证一致性? 对于一致性要求不高的场景, 可能最后一种方案可以彻底解决击穿问题. 对于一致性要求较高的场景, 使用同步/锁的方式会更好.

posted @ 2019-04-16 20:09  罪恶斯巴克  阅读(845)  评论(0编辑  收藏  举报