Redis是单线程还是多线程
Reds6.0版本之前的单线程指的是其网络I/O和键值对读写是由一个线程完成的
Rds6.0引入的多线程指的是
网络请求过程采用了多线程,而
键值对读写命令仍然是单线程处埋的,所以Redis依然是并发安全的也就是只有网络请求模块和数据操作模块是单线程的,而其它的持久化、集群数据同出等,其实是由额外的线程执行的
Redis单线程为什么会这么快
1、命令护行基于内存操作,一条命令在内存里操作的时间是几十纳秒
2、命令执行是
单线程操作,没有线程切换开销
3、起于
IO
多路复用机制提升Redis的I/O利用率
4、高效的数据存储结构:全同hash表以及多种高效数据结构,比如:跳表,压缩列表,链表等等
-
全局hash表
-
底层数据存储结构
String类型
bitmap(位图)专题
什么是bitmap
bitmap是以bit为单位的图,也可以说是数组,每一个元素存储的值只能是1|0 ,但实际也会String类型,还不过可以用对bit为单位操作元素
根据 key - offset - value的形式建立存储
key:整个bitmap的键值
offset:偏移量也是索引
应用场景
-
海量用户系统的日活/周活统计
-
统计某用户某时间段内的签到情况
-
连续登录用户统计
-
海量用户会员判断
-
判断是否状态的记录,如签到
为什么用bitmap实现签到业务?
-
因为bitmap实际就是bit数组,通过redis的上层String数据类型对每个bit进行操作,其中int的存储是4个字节(32bit),而bitmap的位数存储范围是2^32。
-
如果使用mysql存储,那就是一个用户在某一天的签到记录对应这表中的一条记录,当数据量不断扩充时,这个消耗量是不可预估的,再加上高并发场景会打垮mysql。
-
使用bitmap,刚好1int(4byte)= 32bit,一个月最多有31天,只需1int就可储存该用户一个月的签到数据。加上缓存的高存取效率,bitmap是最适合的数据结构。
Hash类型
hash类型与map的相似性
map:Map<String,Map<Object,Object>> 所谓的大map
相关
指令
:
-
一次设置一个字段值:HSET key field value
-
一次获取一个字段值:HGET key field
-
一次设置多个字段值:HMSET key field value [field value...]
-
一次获取多个字段值:HMGET key field [field...]
-
获取所有字段值:HGETALL key
-
获取某个key内的全部数量:hlen
-
删除一个key:hdel
应用场景
用于缓存常更新数据,尤其是需要修改里面的属性,存储java对象
Set类型
先更新数据库,再删缓存
如果想实现基础的缓存数据库双写一致的逻辑,那么在大多数情况下,在不想做过多设计,增加太大工作量的情况下,请
先更新数据库,再删缓存!
更新策略
三大更新策略
更新策略应用场景
删除策略
-
立即删除:对CPU不友好,用处理器性能换取存储空间 (拿时间换空间)
-
惰性删除:对memory不友好,用存储空间换取处理器性能(拿空间换时间)
-
定期删除:定期抽样key,判断是否过期
使用这些策略的原因:为使redis在内存和cpu之间平衡,使得redis的性能最大化
-
定时删除:使用定时器一直对TTL进行检测,一旦定时器的时间数值与存储的时间时间数值相同,直接删除。
-
由于惰性刚除策略无法保证冷数据被及时删掉,所以Redis会定期(默认每100ms主动淘汰一批已过明的key,这里的一批只是部分过期key,所以可能会出凯部分key已经过期但还没有被清理掉的情况,导致内存并没有被释放
-
缺点:对CPU消耗大 优点:节约内存,以时间换空间
-
-
惰性删除:空间换时间方案
-
当读写一个已经过期的key时,会触发惰性删除策略,判断key是否过期,如果过期了直接刚除掉这个key
-
-
定期删除:周期性轮询redis库的时效性数据,采取随机抽取策略(推荐使用)
-
优点:
-
能根据CPU的性能设置有峰值,检测频度可自定义
-
对内存压力不大,长期占用内存的冷数据会被持续清理
-
-
缓存淘汰策略
其中,volatile前缀代表从设置了过期时间的键中淘汰数据,allkeys前缀代表从所有的键中淘汰数据.关于后缀,tl代表选择过期时间最小的键,random代表随机选择键,需要我们额外关注的是Iru和lfu后缀,它们分别代表采用Iru算法和lfu算法来淘汰数据。
LRU(Least Recently Used) 是按照最近最少使用原则来筛选数据,即最不常用的数据会被筛选出来!
-
标准LRU:把所有的数据组成一个链表,表头和表尾分别表示MRU和LRU端,即最常使用端和最少使用端。刚被访问的数据会被移动到MRU端,而新增的数据也是刚被访问的数据,也会被移动到MRU端。当链表的空间被占满时,它会删除LRU端的数据。
-
近似LRU: Redis会记录每个数据的最近一次访问的时间戳(LRU)。Redis执行写入操作时,若发现内存超出maxmemory,就会执行一次近似LRU淘汰算法。近似LRU会随机采样N个key,然后淘汰掉最旧的key,若淘汰后内存依然超出限制,则继续采样淘汰。可以通过maxmemory_samples配置项,设置近似LRU每次采样的数据个数,该配置项的默认值为5
LRU算法的不足之处在于,若一个key很少被访问,只是刚刚偶尔被访问了一次,则它就被认为是热点数据,短时间内不会被淘汰。
LFU算法正式用于解决上述问题,LFU (Least Frequently Used) 是Redis4新增的淘汰策略,它根据key的最近访问频率进行淘汰。LFU在LRU的基础上,为每个数据增加了一个计数器,来统计这个数据的访问次数。当使用LFU策略淘汰数据时,首先会根据数据的访问次数进行筛选,把访问次数最低的数据淘汰出内存。如果两个数据的访问次数相同,LFU再比较这两个数据的访问时间,把访问时间更早的数据淘汰出内存
缓存击穿问题
热点key突然失效问题
热点key在同一个时间在缓存失效,此时有大量的请求索要这些热点数据,但缓存刚好没有,只能打到数据库,导致数据库请求压力过增,甚至导致数据库挂掉
互斥锁方案
缺点:并发性能差,可能会出现死锁
优点:数据一致性强
TTL设置
-
在将数据加入缓存时,以随机数的形式设置随机过期时间。防止大量的缓存在同一时刻失效
-
如果存在缓存数据基本不会发生改变时,可以设置永久存活时间
过期数据详解
通过TTL指令获取对应的数据状态
-
XX:具有时效性的数据
-
-1:永久有效的数据
-
-2:已过期的数据、被删除的数据、未定义的数据
逻辑删除
与TTL不同的是
缓存穿透问题
缓存穿透是指请求在缓存和数据库都没有命中,即在数据库不存在请求的数据。存在黑客或者不怀好意的人对该系统的恶意请求,导致数据库挂掉。
缓存空值方案
缓存空值是指将数据库未命中的数据key设置value为空值(最好是设置为JOSN格式的空值)到缓存,加上短期的TTL效果更好,这样可防止短时间内的穿透
-
缺点:
-
缓存空值虽然能够阻挡大量穿透的请求,但如果有大量获取未注册用户信息的请求,缓存内 就会有有大量的空值缓存,也就会浪费缓存的存储空间,如果缓存空间被占满了,还会剔除 掉一些已经被缓存的用户信息反而会造成缓存命中率的下降
-
经典应用场景
缓存系统
计数器
消息队列系统(不推荐,有更好的中间件)
排行榜
社交网络
实时系统
redis事务与锁机制
整个事务执行过程:输入Multi命令开始,后面输入的命令都会依次进入QUEUE且不会执行队列中的命令;直到输入exec命令,将队列中的命令依次执行。可通过discard命令中断组队
组队阶段若出现错误,整个事务都不会被执行
事务过程:假如执行阶段某命令出现错误,其他命令的执行不会被影响,不会回滚。
redis事务不像mysql事务会对事务异常进行回滚,它的作用只是隔离其他事务的命令的插入执行,为的就是
redis能够按排队的顺序依次执行而不被中断
redis事务的三大特征
-
单独的隔离操作,redis的事务执行不会被其他客户端的事务请求中断
-
没有mysql那样的事务隔离级别
-
不保证事务的原子性,当事务里的命令执行出错时不会影响其他命令的执行
IO多路复用
基础概念
-
同步:调用者要一直等待调用结果的通知后才能进行后续的执行,现在就要,我可以等,等出结果为止
-
异步:指被调用方先返回应答让调用者先回去,然后再计算调用结果,计算完最终结果后再通知并返回给调用方,异步调用要想获得结果一般通过回调
同步、异步的讨论对象是被调用者(服务提供者),重点在于获得调用结果的消息通知方式上
-
阻塞:调用方一直在等待而且别的事情什么都不做,当前进/线程会被挂起,啥都不干
-
非阻塞:调用在发出去后,调用方先去忙别的事情,不会阻塞当前进/线程,而会立即返回
阻塞、非阻塞的讨论对象是调用者(服务请求者),重点在于等消息时候的行为,调用者是否能干其它事