1、五种基本类型底层数据结构
1.1、字符串
其中:embstr和raw都是由SDS动态字符串构成的。
唯一区别是:raw是分配内存的时候,redisobject和sds各分配一块内存,而embstr和redisobject是在一块儿内存中。
1.2、列表
1.3、hash
1.4、set
1.5、zset
2、缓存雪崩
2.1、举例
双十一期间,所有用户一打开淘宝就是进入首页,首页的压力非常大,为了提高并发,将网站首页数据都缓存到redis里,所有的rediskey失效时间都是3小时。
双十一当天大量用户刺手狂欢,这时候3个小时过去了,redis里首页的key缓存全部失效,这时候redis里查询不到数据了,只能去数据库中查询,造成数据库无法响应挂掉。
**总结:**在高并发下,大量缓存key在同一时间失效,大量请求直接落在数据库上,导致数据库岩机。
2.2、解决方案
-
随机设置key失效时间,避免大量key集体失效。
setRedis(Key, value, time + Math.random()* 10000)
-
若是集群部署,可将热点数据均匀分布在不同的Redis库中也能够避免key全部失效问题。
-
不设置过期时间
-
跑定时任务,在缓存失效前刷进新的缓存
3、缓存穿透
3.1、举例
某人用脚本疯狂发送请求,查询id=-1的数据,redis并没有这样的数据,这时候就穿透redis,直接
打到了数据库上,导致数据库挂了。
总结:redis缓存和数据库中没有相关数据(例用户直接携带id<=0的参数不断发起请求),redis中没有这样的数据,无法进行拦截,直接被穿透到数据库,导致数据库压力过大岩机。
3.2、解决方案
- 对不存在的数据缓存到redis中,设置key,value值为null不管是数据为null还是系统bug问题),并设置一个短期过期时间段,避免过期时间过长影响正常用户使用。
- 拉黑该IP地址
- 对参数进行校验,不合法参数进行拦截
- 布隆过滤器将所有可能存在的数据哈希到一个足够大的bitmap(位图)中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
4、缓存击穿
4.1、举例
程序员将商品的信息存到了redis中,设置了3小时过期。竞拍了2小时59分,突然商品在redis里的key数据过期了,导致该key的大量请求,都打到了数据库,直接导致数据库挂掉了,服务无法响应。
总结:某一个热点key,在不停地打着高并发,当这个热点key在失效的一瞬间,持续的高并发访问就击破缓存直接访问数据库,导致数据库岩机。
4.2、解决方案
- 设置热点数据“永不过期
- 加上互斤锁:上面的现象是多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个互斥锁锁住它,其他的线程走到这一步就拿不到锁,等着第一个线程查询到了数据,然后将数据放到redis缓存起来。后面程序进来发现有了缓存,就直接走缓存了。
#简单的分布式锁实现
public String get(key){
String value = redis.get(key);
if(value == null){ //代表缓存值过期
//设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
String keynx = key.concat(":nx");
if(redis.setnx(keynx))
}
}
5、缓存雪崩、缓存穿透、缓存击穿的区别
-
缓存雪崩是大面积的kye缓存失效
-
缓存穿透是redis中不存在这个缓存key
-
缓存击穿是某一热点key突然失效
6、单线程的redis为什么这么快
Redis有多快?官方给出的答案是读写速度10万/秒,为什么单线程的Redis速度这么快?原因有以下几点:
纯内存操作
- Redis是完全基于内存的,所以读写效率非常的高,当然Redis存在持久化操作,在持久化操作是都是fork
子进程和利用Linux系统的页缓存技术来完成,并不会影响Redis的性能。 - 单线程操作:单线程并不是坏事,单线程可以避免了频繁的上下文切换,频繁的上下文切换也会影响性能的.
- 合理高效的数据结构
- 采用了非阻塞IO多路复用机制:多路I/O复用模型是利用select、poll、epoll可以同时监察多个流的IO事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有IO事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这工种做法就避免了大量的无用操作。
7、Redis的数据过期淘汰策略
Redis中数据过期策略采用定期删除+惰性删除策略。
7.1、定期删除和惰性删除策略
-
定期删除策略:Redis启用一个定时器定时监视所有的key,判断key是否过期,过期的话就删除。这种策略可以保证过期的key最终都会被删除,但是也存在严重的缺点:每次都遍历内存中所有的数据,非常消耗CPU资源,并且当key已过期,但是定时器还处于未唤起状态,这段时间内key仍然可以用。
-
惰性删除策略:在获取key时,先判断key是否过期,如果过期则删除。这种方式存在一个缺点:如果这个工key一直未被使用,那么它一直在内存中,其实它已经过期了,会浪费大量的空间。
7.2、定期删除和惰性删除策略如何工作
这两种策略天然的互补,结合起来之后,定时删除策略就发生了一些改变,不在是每次扫描全部的key了,而是随机抽取一部分key进行检查,这样就降低了对CPU资源的损耗,情性删除策略互补了为检查到的key,基本上满足了所有要求。
但是有时候就是那么的巧,既没有被定时器抽取到,又没有被使用,这些数据又如何从内存中消失?没关系,还有内存淘达机制,当内存不够用时,内存淘汰机制就会上场。Redis内存淘汰机制有以下几种策略:
-
volatile-liru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
-
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
-
volatile-random:从已设置过期时间的数据集(server.dblij].expires)中任意选择数据淘汰
-
allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)
-
alkeys-random:从数据集(senver.dblj.dict)中任意选择数据淘汰
-
no-eviction:禁止驱逐数据,永不过期,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!(默认值)
4.0版本后增加一下两种
-
volatile-lfu:从已设置过期时间的数据集(server.dbli].expires)中挑选最不经常使用的数据淘达
-
allkeys-lfu:当内存不足以容纳新写入数据,在键空间移除最不经常使用的key
8、redis的线程模型是怎么样的
redis内部使用文件事件处理器file event handler,这个文件事件处理器是单线程的,所以redis才叫做单线程的模型。它采用IO多路复用机制同时监听多个socket,根据socket上的事件来选择对应的事件处理器进行处理。
文件事件处理器的结构包含4个部分:
-
多个socket
-
IO多路复用程序
-
文件事件分派器
-
事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
多个socket可能会并发产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个socket,会将socket产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。
9、redis和memcached的区别
-
**存储方式不同:**memcache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小;Redis有部份存在硬盘上,这样能保证数据的持久性。
-
数据支持类型:memcache对数据类型支持相对简单;Redis有复杂的数据类型
-
使用底层模型不同:它们之间底层实现方式,以及与客户端之间通信的应用协议不一样,Redis自己构建了vm机制,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。value值大小不同:Redis最大可以达到1gb;memcache只有1mb。
10、redis实现事务
Redis通过MULTI、EXEC、WATCH等命令来实现事务(transaction)功能。事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求。
在传统的关系式数据库中,常常用ACID性质来检验事务功能的可靠性和安全性。在Redis中,事务总是具有原子性(Atomicity)、一致性(Consistency)和隔离性(lsolation),并且当Redis运行在某种特定的持久化模式下时,事务也具有持久性(Durability)
11、一致性(延迟双删)
延迟双删,双删就是先删除缓存,然后在更新完数据库后再删一次。
**目的:**是为了删除在写MySQL期间读线程可能把脏数据再次读到Redis里,延迟的时间参照一次从MySQL读数据并写入Redis的时间
12、Redis集群,集群原理
Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。
Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。
13、检索特定的key
假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?
- 使用keys指令可以扫出指定模式的key列表,keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复.
- scan指令可以无阻塞的提取出指定模式的key列表,但是需要去重
14、redis异步队列
一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。
15、Redis如何实现延时队列
使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。
评论区