目录
1.Redis凭什么这么快?
2.Redis的五大数据类型是什么?
3.Redis的持久化机制
4.Redis的过期策略及内存淘汰机制
5.Redis事务
6.Redis分布式锁
7.Redis的集群方案
9.缓存穿透,缓存击穿,缓存雪崩,缓存更新
10.Redis的使用场景
redis之所以这么快,总起来主要有以下几点:
①基于单线程的设计,避免了多线程上线文切换的时间和锁开销.
②基于内存,直接在内存存取,所以很快
③采用了非阻塞IO多路复用机制
④Redis精心设计的基于C语音的SDS字符串,提高了性能.(SDS字符串获取字符串长度的时间复杂度仅为O(1),且减少了修改字符串长度时内存重分配的次数,关于SDS具体可以百度,限于篇幅,这里不展开)
Redis支持String,Hash,List,Set,Sorted Set五大数据类型.
String:字符串类型,最常用的一种数据类型
Hash:用于存储结构化对象,用的也比较多
List:双端链表,常备用来做轻量级的消息队列
Set:无序集合,用的比较少
Sorted Set:有序集合,常被用来做排行榜类的功能开发,比如热卖榜,热搜榜...
Redis支持RDB和AOF两种持久化机制,默认采用RDB持久化数据,RDB(Redis dataBase)是对Redis中存储的数据作数据快照,每隔一段时间将内存中数据的快照写入磁盘,AOF(Append only file)是采用类似于Mysql binlog那种日志的方式,将每个写操作追加到日志中.
RDB相对于AOF在数据恢复时更快,但有可能存在数据丢失的情况;AOF的日志文件大小会与日俱增,到了后期数据恢复会变得很慢,但不会丢数据,所以可以根据具体的应用场景去选择合适的持久化方式,当然也可以RDB+AOF同时开启.
Redis采用定期删除+惰性删除的过期策略. 定期删除是指每隔一段时间,就随机扫描一些已经设置了过期时间的key,检查是否过期,过期就删除. 惰性删除是指在获取某个key时,会先检查是否已过期,过期就删除.
内存淘汰总结起来主要有4类:LRU,LFU,RANDOM,TTL;具体分为以下8类:
volatile-lru:从已设置过期时间的数据中,挑选最近最少使用的进行淘汰
volatile-lfu:从已设置过期时间的数据中,挑选使用频率最低的进行淘汰
volatile-ttl:从已设置过期时间的数据中,挑选即将过期的进行淘汰
volatile-random:从已设置过期时间的数据中,随机淘汰
allKeys-lru:从所有数据中挑选最近最少使用的进行淘汰
allKeys-lfu:从所有数据中挑选使用频率最低的进行淘汰
allKeys-random:从所有数据中随机淘汰
no-eviction:不淘汰,如果内存满了就无法set新的数据,但不影响老数据的使用,保证数据不丢失
Redis可以通过Muti,exec,discard,watch命令实现事务控制. Redis的事务保证ACID(原子性,一致性,隔离性,持久性)
Redis的分布式锁的实现思路:
锁创建:set一条数据,如果该数据的key不存在,则返回true; 若key已存在,则重试,直到超过设置的超时时间为止,期间若设置成功,则返回ture,否则返回false;为了防止死锁,一般会对数据设置默认的失效时间. 另外需要考虑,是否同一请求的重入,一般是借助value实现的.
锁释放:锁释放需要先判断key是否存在,value是否是设置的value,毕竟解铃还须系铃人,另外需要注意该锁的重入次数,如果重入次数大于1,需要先判断并递减,不能直接就删除key,而且整个过程需要原子操作,一般是借助lua脚本来实现.
//锁创建伪代码
public boolean tryLock(String lockKey,String requestId,long timeOutMillis){
//获取锁超时时间
Long timeOut = System.currentTimeMillis() + LOCK_MAX_WAIT_TIME;
while(true){
String result = jedis.set(lockKey,requestId,"NX","PX",timeOutMillis);
if("ok".equals(result){
return true;
}
maxWaitTime = timeOut - System.currentTimeMillis();
//超过过期时间还没有获取到锁,则直接返回失败
if(maxWaitTime <= 0){
return false;
}
//TODO 线程适度休眠,以减轻CPU压力
}
}
//伪代码 锁释放
public boolean release(String lockKey, String requestId){
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
Object result = jedis.eval(luaScript, Collections.singletonList(lockKey),
Collections.singletonList(requestId));
if ("1".equals(result)) {
return true;
}
}
Redis集群方案常见的有三种:
①Redis-client 客户端分片, 由客户端决定数据读取/写入的节点,优点是简单,性能高,缺点是可维护性差.
②Proxy 基于代理分片,由代理决定数据的读/写节点,开源的方案有twitter的twemproxy,优点是切换成本低,代理和数据写入逻辑解耦,缺点是框架失去维护,同时代理层增加了资源损耗.
③Redis-cluster 官方集群,由Redis官方提供的集群方案,优点是客户端直连redis服务,省去代理层性能损耗,缺点是数据迁移不方便.
8.Redis常见的性能优化
①尽量使用短且具有业务含义的key
②尽量避免使用key*去获取数据
③设置有效期及回收策略
④对于批量场景可以通过管道合并命令,批量执行.
缓存穿透:缓存穿透是指由用户恶意发起,查询数据库和缓存中不存在的值,从而查询直接请求数据库,比如用户一直查询id=-1(数据库中不存在该数据),就会给数据库带来很大压力,常见的处理方式如布隆过滤器,guava提供了实现.
缓存击穿:缓存击穿是指,同时有多条请求来查询数据,此时缓存中无数据,但数据库中有,于是存在多条线程同时查询数据库并更新缓存的情况,可以通过分布式锁来处理,只允许一个线程更新缓存,未获取到锁的线程等待一段时间后重新从缓存中获取数据.
缓存雪崩:缓存雪崩是指,同一时间内,大量缓存都过期失效了,导致所有请求瞬间都直接打到数据库上,导致数据库崩溃. 解决方案,可以设置随机的缓存失效时间,也可以分模块设置缓存过期时间,推荐前者.
缓存更新:最常用的方式是 先更新数据库,再删除缓存.
①作为缓存,减轻底层数据库压力.
②可以支持分布式锁,分布式session等
③可以支持轻量级消息队列
④可以利用有序集合做排行榜
⑤可以用来做计数器,原子增/减,比如电商系统的库存
⑥在一些特殊场景下,redis也会被直接作为数据库使用