Redis学习笔记2—缓存、集群、一致性等

缓存淘汰策略

为了保证高性能,缓存都保存在内存中,当内存满了以后,须要经过适当的策略淘汰老数据,以便腾出空间存储新数据。数据的淘汰策略,典型的包括FIFO(先进先出,淘汰最老数据),LRU(淘汰最近最少使用的),LFU(淘汰使用频率最低的)。redis

FIFO很简单就不展开了,主要说下LRU和LFU的区别,详细区别参考这里算法

  1. LRU(Least Recently Used),首先淘汰最长时间未被使用的数据。实现方法是每次访问数据后把数据移到队头,删除时从队尾开始删除。
  2. LFU(Least Frequently Used),首先淘汰必定时期内被访问次数最少的数据。实现方法是记录数据在必定时段内的访问评率,删除访问频率最低的数据。此算法须要额外维护每一个数据的访问量,并排序,实现比较复杂。

Java的LinkedHashMap已经实现了LRU算法,具体实现请查看JDK源码,使用方法请仔细阅读LinkedHashMap如下两个方法的JavaDoc(我贴出来了,注释有点多,有删减)。数据库

/**
     * Constructs an empty <tt>LinkedHashMap</tt> instance with the
     * specified initial capacity, load factor and ordering mode.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @param  accessOrder     the ordering mode - <tt>true</tt> for
     *         access-order, <tt>false</tt> for insertion-order
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }
/**
     * Returns <tt>true</tt> if this map should remove its eldest entry.
     *
     * <p>This implementation merely returns <tt>false</tt> (so that this
     * map acts like a normal map - the eldest element is never removed).
     *
     * @param    eldest The least recently inserted entry in the map, or if
     *           this is an access-ordered map, the least recently accessed
     *           entry.  
     * @return   <tt>true</tt> if the eldest entry should be removed
     *           from the map; <tt>false</tt> if it should be retained.
     */
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }

Redis提供的淘汰策略包括以下几种:segmentfault

  1. noeviction(内存满后不主动回收,没法写入新数据)
  2. allkeys-lru(最近最少使用的Key)
  3. allkeys-random(随机回收)
  4. volatile-lru(过时集合中最近最少使用的Key)
  5. volatile-random(过时随机回收)
  6. volatile-ttl(过时最短存活)

常见缓存性能问题

缓存穿透

缓存穿透是指去获取一个Redis和DB都不存在的数据,因为Redis中不存在,致使流量透传到DB,而DB中无相关数据,查询后不会缓存结果到Redis。若是大量此类查询,会给数据库带来性能风险,此问题可被攻击者利用。设计模式

避免方法:缓存

  1. 对于DB中查询不到的数据,也在Redis中进行短时间缓存,避免反复查询DB。
  2. 使用互斥锁(mutex key):到缓存没命中时,不是当即去查询DB,而是先获取一个互斥锁(SETNX命令),获取到锁成功后再去查询DB。
  3. 业务层增长校验,过滤非法数据。
public String get(key) {  
    String value = redis.get(key);  
    if (value == null) { //表明缓存值过时  
       //设置超时,防止del失败时死锁  
       if (redis.setnx(key_mutex, 1, 60) == 1) { //表明设置成功  
           value = db.get(key);  
           redis.set(key, value, expire_secs);  
           redis.del(key_mutex);  
        } else {  //没获取到锁,代表其余线程获取了,等待一段时间后重试查询缓存  
           sleep(50);  
           get(key); //重试  
        }  
    } else {  
        return value;  
  }  
}

以上代码参考自Redis 的key设计技巧&&缓存问题服务器

缓存击穿

缓存击穿是指在某个热点Key过时的时候,客户端产生大量的状况,致使请求击穿缓存直接到达DB,给DB带来巨大压力,避免方法请参考上述缓存穿透的互斥锁数据结构

缓存雪崩

缓存雪崩是指缓存服务器重启时或者大量缓存在短期内集中过时时,刚好此时大量客户端执行并发操做,缓存命中失败致使给DB带来巨大压力。多线程

避免方法:并发

  1. 查询缓存失败后,查询数据库的代码先加锁再查询数据库,或者队列执行,对于每一个Key,每一个进程同时只容许一个线程访问数据库,减轻数据库压力。
  2. 参考上述缓存穿透的互斥锁
  3. 给每一个Key的过时时间后面加个随机值,确保缓存不会在同一时刻大面积失效。
  4. 设置热点数据永不过时,数据更新后,主动刷新缓存。

缓存和数据一致性

多服务写Redis

首先,多个服务修改同一个Key是很差的设计模式,应该把维护同一个Key的操做集中到一个服务里,好比更新订单的状态,应该都转发给订单服务来操做。当多写状况没法避免时,应采起以下措施:

  1. 互斥写,经过Redis的setex实现互斥说,来竞争对Key的写入权限。
  2. 使用乐观锁,给数据添加版本号或时间戳,经过乐观锁判断是否能够写入。

Redis & DB一致性

Redis做为缓存使用的时候,通常都是DB数据的映像,两套系统就会存在数据不一致的状况,如何才能最大限度的下降数据不一致的影响呢?好比数据库刚写入一个更新,缓存更新命令还没执行,这个时候来了个读请求,从缓存中读取的数据就不是最新的。

若是对于高一致性要求的场景,只能把读写操做串行执行,确保缓存和DB的一致性,但这样会严重下降系统的吞吐量,甚至成为系统瓶颈。

更通用的保存缓存和DB数据一致性的作法,是DB写入数据库后,删除缓存数据。这样系统下次读取请求时,会从DB中读取最新的数据并进行缓存。采用删除而不是更新缓存,主要是基于性能的考虑,否则若是反复更新数据场景下,反复写无人消费的缓存数据是一种浪费。

Redis集群部署

Redis支持主从和分片两种Cluster部署模式,提供高可用性。

主从

在主备模式下,Redis经过Sentinel哨兵来监控Master的状态,当Master异常后,从从节点中选出新主节点,并调整其余从节点的slaveof到新Master。
Sentinel经过部署多实例,实例间经过 Raft协议 实现自身的高可用,全部Sentinel须要部署 3个 节点才能保持自身的健壮性。

在一主多从模式下,为了减轻Master的数据同步压力,能够把主从模式配置为主从链模式,即A是B的主,B是C的主,从而减轻B和C都从A同步数据的压力。

主从模式下,当有新节点加入时,流程以下:

  1. 新节点向Master发送psync命令
  2. Master执行bgsave,fork子进程,生成RDB数据,并缓存新数据
  3. Master把RDB发送给新节点恢复
  4. Master把缓存中的新数据发送给新节点
  5. 新节点初始化完成,后续经过AOF进行增量数据的同步

分片

分别模式先,Redis经过一致性Hash算法,在内部把数据切分为16384个slot。经过对数据的Key进行Hash计算来数据保存的分片位置。

数据持久化机制和恢复

Redis支持RDB和AOF两种持久化机制。
RDB是内存数据库快照,Redis经过fork子进程把内存存储(二进制压缩)为RDB文件。快照过程当中,新增数据使用COW(copy on write)的模式写入。RDB适合作灾备,可是因为定时报错,容易丢失部分数据。
AOF(append only file)是以文本日志形式记录Redis每一个写入和删除操做。AOF日志写入支持灵活的策略,如每秒刷盘,根据数据量刷盘等。
RDB是最新数据快照,文件小,AOF记录操做过程,文件比较大。数据恢复时,通常采用RDB+AOF的模式来实现,RDB做为基准数据,叠加快照以后的AOF数据,完成完整的数据恢复。

Keys和Scan方法

Keys会全表扫描,对于单线程的Redis,容易出现卡顿,影响性能。
Scan采用相似分页获取的方式,虽然实现代码复杂一点,并且有可能数据重复,可是不会有性能问题。
通常生产库,都会禁用keys命令。

Redis vs Memcached

特性 Redis Memcached
性能 单线程非阻塞异步IO,避免线程切换 多线程异步IO,可利用多核
持久化 支持,可做为NoSQL数据库 不支持,有效期最长30天
数据结构 支持5种 只支持简单数据结构
限制 - Key 250字节,Value 1M,缓存30天
HA 主从、Cluster 不支持
相关文章
相关标签/搜索