redis中的五种常用类型分别是string,Hash,List,Set,ZSet。
类型 | 特点 | 使用场景 |
---|---|---|
string | 简单的key-value类型,value其实不仅是String,也可以是数字 | 定时持久化,操作日志,常规计数, 微博数, 粉丝数等功能 |
hash | 是一个string类型的field和value的映射表,hash特别适合用于存储对象 | 存储部分变更数据,如用户信息等 |
list | 有序可重复的列表 | twitter的关注列表,粉丝列表,最新消息排行,消息队列 |
set | 无序不可重复的列表 | 在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能 |
Sorted set | 带有score的Set | 排行榜 |
RDB:快照形式,定期把内存中当前时刻的数据保存到磁盘。Redis默认支持的持久化方案。速度快但是服务器断电的时候会丢失部分数据
AOF:append only file。把所有对redis数据库操作的命令,增删改操作的命令。保存到文件中。数据库恢复时把所有的命令执行一遍即可。两种持久化方案同时开启使用AOF文件来恢复数据库.能保证数据的完整性,但是速度慢。
两者如何选择?
主从结构一是可以进行冗余备份,二是可以实现读写分离.
主从复制:
当我们在redis中开启了持久化(RDB或者AOF)但是这样也不能保证数据一定不会丢失,当Redis服务因为硬盘损坏那么数据也就丢失了,这时我们通过主从模式,一个master设置多个slave,那么在slave上面就会有多个备份数据,提高了我们数据的抗灾能力。
读写分离:
主从架构中,可以考虑关闭主服务器的数据持久化功能,只让从服务器进行持久化,这样可以提高主服务器的处理性能。从服务器通常被设置为只读模式,这样可以避免从服务器的数据被误修改
相对简单,slave启动后会自动同步数据,增量同步。
手动恢复
哨兵功能自动恢复
通过sentinel模式启动redis后,自动监控master/slave的运行状态, 已经被集成在redis2.4+的版本中如果Master异常,则会进行Master-Slave切换,将其中一个Slave作为Master,将之前的Master作为Slave
基本原理是:心跳机制+投票裁决
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
解决办法
对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃。还有最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
也可以采用一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
如果缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩。
解决办法
缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。
缓存预热就是系统上线后,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
缓存预热解决方案:
我们知道通过expire来设置key 的过期时间,那么对过期的数据怎么处理呢?除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:
两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡。
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。
降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。
在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:
使用缓存 + 过期时间的策略既可以加速数据读写,又保证数据的定期更新,这种模式基本能够满足绝大部分需求。但是有两个问题如果同时出现,可能就会对应用造成致命的危害:
当前 key 是一个热点 key( 可能对应应用的热卖商品、热点新闻、热点评论等),并发量非常大。
重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的 SQL、多次 IO、多个依赖等。
在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。
热点 key 失效后大量线程重建缓存
要解决这个问题也不是很复杂,但是不能为了解决这个问题给系统带来更多的麻烦,所以需要制定如下目标:
减少重建缓存的次数
数据尽可能一致
较少的潜在危险
1)互斥锁 (mutex key)
此方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可,
下面代码使用 Redis 的 setnx 命令实现上述功能,伪代码:
String get(String key) {
//从redis中获取key
String value = redis.get(key);
//如果value为空则开始重构缓存
if (value == null) {
//只允许一个线程重构缓存,使用nx,并设置过期时间ex
String mutexKey = "mutex:key" + key;
if (redis.set(mutexKey, "1", "ex 180", "nx")) {
//从数据源获取数据
value = db.get(key);
//回写redis并设置过期时间
redis.set(key, value, timeout);
//删除mutexKey
redis.del(mutexKey);
} else {
//其他线程睡眠50秒再重试
Thread.sleep(50); get(key);
}
}
return value;
}
从 Redis 获取数据,如果值不为空,则直接返回值。
如果 set(nx 和 ex) 结果为 true,说明此时没有其他线程重建缓存,那么当前线程执行缓存构建逻辑。
如果 setnx(nx 和 ex) 结果为 false,说明此时已经有其他线程正在执行构建缓存的工作,那么当前线程将休息指定时间 (例如这里是 50 毫秒,取决于构建缓存的速度 ) 后,重新执行函数,直到获取到数据。
2)永远不过期
永远不过期”包含两层意思:
从缓存层面来看,确实没有设置过期时间,所以不会出现热点 key 过期后产生的问题,也就是“物理”不过期。
从功能层面来看,为每个 value 设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去构建缓存。
从实战看,此方法有效杜绝了热点 key 产生的问题,但唯一不足的就是重构缓存期间,会出现数据不一致的情况,这取决于应用方是否容忍这种不一致。下面代码使用 Redis 进行模拟:
String get(final String key) {
V v = redis.get(key);
String value = v.getValue();
//逻辑过期时间
final Long logicTimeout = v.getLogicTimeout();
//如果逻辑时间小于当前时间,开始重建缓存
if (logicTimeout <= System.currentTimeMillis()) {
final String mutexKey = "mutex:key" + key;
if (redis.set(mutexKey, "1", "ex 180", "nx")) {
//重建缓存
threadPool.execute(new Runnable() {
@Override
public void run() {
String dbValue = db.get(key);
redis.set(key, (dbValue, newLogicTimeout));
redis.del(mutexKey);
}
});
}
}
return value;
}
作为一个并发量较大的应用,在使用缓存时有三个目标:
第一,加快用户访问速度,提高用户体验。
第二,降低后端负载,减少潜在的风险,保证系统平稳。
第三,保证数据“尽可能”及时更新。
下面将按照这三个维度对上述两种解决方案进行分析。
互斥锁 (mutex key):这种方案思路比较简单,但是存在一定的隐患,如果构建缓存过程出现问题或者时间较长,可能会存在死锁和线程池阻塞的风险,但是这种方法能够较好的降低后端存储负载并在一致性上做的比较好。
” 永远不过期 “:这种方案由于没有设置真正的过期时间,实际上已经不存在热点 key 产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增大。
普通队列:一般使用list结构作为队列,rpush生产消息,lpop消费消息,blpop阻塞消费。
消费多次:生产一次消费多次的情况使用发布/订阅模式
延时队列:使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理
如果大量的key过期时间设置的过于集中,到过期的那个时间点,redis可能会出现短暂的卡顿现象。一般需要在时间上加一个随机值,使得过期时间分散一些。
主从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将rdb文件全量同步到复制节点,复制节点接受完成后将rdb镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。
Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。
Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。