为了保证高性能,缓存都保存在内存中,当内存满了以后,须要经过适当的策略淘汰老数据,以便腾出空间存储新数据。数据的淘汰策略,典型的包括FIFO(先进先出,淘汰最老数据),LRU(淘汰最近最少使用的),LFU(淘汰使用频率最低的)。redis
FIFO很简单就不展开了,主要说下LRU和LFU的区别,详细区别参考这里。算法
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
缓存穿透是指去获取一个Redis和DB都不存在的数据,因为Redis中不存在,致使流量透传到DB,而DB中无相关数据,查询后不会缓存结果到Redis。若是大量此类查询,会给数据库带来性能风险,此问题可被攻击者利用。设计模式
避免方法:缓存
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带来巨大压力。多线程
避免方法:并发
首先,多个服务修改同一个Key是很差的设计模式,应该把维护同一个Key的操做集中到一个服务里,好比更新订单的状态,应该都转发给订单服务来操做。当多写状况没法避免时,应采起以下措施:
Redis做为缓存使用的时候,通常都是DB数据的映像,两套系统就会存在数据不一致的状况,如何才能最大限度的下降数据不一致的影响呢?好比数据库刚写入一个更新,缓存更新命令还没执行,这个时候来了个读请求,从缓存中读取的数据就不是最新的。
若是对于高一致性要求的场景,只能把读写操做串行执行,确保缓存和DB的一致性,但这样会严重下降系统的吞吐量,甚至成为系统瓶颈。
更通用的保存缓存和DB数据一致性的作法,是DB写入数据库后,删除缓存数据。这样系统下次读取请求时,会从DB中读取最新的数据并进行缓存。采用删除而不是更新缓存,主要是基于性能的考虑,否则若是反复更新数据场景下,反复写无人消费的缓存数据是一种浪费。
Redis支持主从和分片两种Cluster部署模式,提供高可用性。
在主备模式下,Redis经过Sentinel哨兵来监控Master的状态,当Master异常后,从从节点中选出新主节点,并调整其余从节点的slaveof到新Master。
Sentinel经过部署多实例,实例间经过 Raft协议 实现自身的高可用,全部Sentinel须要部署 3个 节点才能保持自身的健壮性。
在一主多从模式下,为了减轻Master的数据同步压力,能够把主从模式配置为主从链模式,即A是B的主,B是C的主,从而减轻B和C都从A同步数据的压力。
主从模式下,当有新节点加入时,流程以下:
分别模式先,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会全表扫描,对于单线程的Redis,容易出现卡顿,影响性能。
Scan采用相似分页获取的方式,虽然实现代码复杂一点,并且有可能数据重复,可是不会有性能问题。
通常生产库,都会禁用keys命令。
特性 | Redis | Memcached |
---|---|---|
性能 | 单线程非阻塞异步IO,避免线程切换 | 多线程异步IO,可利用多核 |
持久化 | 支持,可做为NoSQL数据库 | 不支持,有效期最长30天 |
数据结构 | 支持5种 | 只支持简单数据结构 |
限制 | - | Key 250字节,Value 1M,缓存30天 |
HA | 主从、Cluster | 不支持 |