9、Redis的线程模型
Redis内部使用文件事件处理器file event handler,这个文件事件处理器是单线程的,所以Redis才叫单线程的模型。采用IO多路复用机制同时监听多个Socket,根据Socket上的事件来选择对应的事件处理器进行处理。
文件事件处理器的结构包含四个部分:
- 多个Socket
- IO多路复用程序
- 文件事件分派器
- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
多个Socket可能会并发的产生不同的操作,因为一个服务器可能会连接多个Socket,每个操作对应的不同的文件事件,但是IO多路复用程序会监听多个Socket,会将Socket产生的时间放入队列中排队,·时间分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。
10、布隆过滤器的原理
如何在海量的 元素(十亿无序不定长,不重复)中快速判断一个元素存在?
使用传统的数据结构肯定是不行的,为了解决这种问题,引入了位图,它是一个有序的数组,只有0(不存在)、1(存在)。
原理:当一个元素被加入到集合中时,通过k个hash函数将这个元素映射为一个对数组中的k个点,并将他们设置为1,检索时,根据同样的Hash函数查看这些点是否为1,如果这些点中,有任何一个为0,则说明该元素一定不存在,如果都是1,就很可能存在,因为使用Hash函数会产生冲突,查找的点可能是其他元素通过Hash修改的。
可以使用Guava提供的相关类库。
11、Redis和MySQL数据不一致怎么解决?
在高并发场景下,因为数据库时用户并发访问的薄弱地方,因此需要使用缓存来做一个缓冲的作用,请求先到达Redis,而读取数据一般没有什么问题,当涉及到数据更新时,就会出现数据库和缓存之间的数据不一致问题。
为了考虑一致性问题,可以考虑更新数据库和缓存
或者更新数据库删除缓存
。
这两种方式在并发场景下都会出现问题。
为了解决数据不一致问题本质是通过 重试的方式。
解决方案
延时双删
:在写库前后都将缓存中的key删除(redis.delKey(key)),并设设定合理的超时时间。
public void write(String key,Object data){
redis.delKey(key);
db.updateData(data);
Thread.sleep(500);
redis.delKey(key);
}
先删除缓存,在更新数据库,休眠一定的时间,再次删除缓存。
这个休眠一定的时间是根据自己项目的读数据业务逻辑的耗时,以及Redis和数据库的主从同步的耗时来设定的。
休眠是为了保证数据库更新成功,再删除缓存,说到底还是为了保证数据一致性。
缺点
:增加了写请求的耗时,在分布式和高并发场景下很难评估具体的延迟时间。
异步重试
:把重试请求写到消息队列中,有专门的消费者来重试,直到成功,或者直接把操作缓存这一步放在消息队列中,有消费者来操作缓存。因为消息队列可以保证可靠性以及消息可以成功传递。
具体步骤就是当有数据变更时,更新数据库,同时将消息存入消息队列中有消费者来操作缓存。
订阅数据库变更日志,在操作缓存
:在修改数据时,只需要修改数据库,不操作缓存,因为当数据库更新成功后,一定会有binlog日志,然后订阅这个日志,拿到对应的数据,来删除对应的缓存。在订阅变更日志这块,阿里有开元的中间件canal,canal会自动把数据库变更日志投递给下面的消息队列。
12、如何保证Redis中存的数据都是热点数据?
因为Redis本身有一个内存淘汰策略,在Redis内存数据到一定大小后就会施行内存淘汰策略,可以设置Redis的内存淘汰策略为allkeys-lru,或者allkeys-random淘汰策略等。
13、Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?
使用keys指令扫描出指定模式的key列表:keys pre*。但是这个指令会阻塞,因为Redis是单线程的,如果Redis正在线上提供服务,线上服务就会停顿,这时可以使用scan指令,它可以无阻塞的提取出key列表,但有一定的重复概率。