1.基于内存存储
2.高效的数据结构
3.IO多路复用模型
4.单线程
应用程序从磁盘中读取数据,经过了两个阶段,第一个阶段,使用recvform命令,尝试从内核中加载数据,如果没有数据,那么内核操作硬件拿到数据,这个过程需要等待;第二个阶段,内核把数据加载之后再写给用户的缓存区
如果是阻塞IO,那么从用户发起请求到读取数据,都会处于阻塞状态。
阻塞IO
非阻塞IO,在使用recvfrom加载数据时候,会立即返回结果而不会阻塞用户进程,如果没有加载到数据会继续发起请求。但是这种方式会发起多次recvfrom命令,造成cpu空转。
IO多路复用,当用户读取数据,不会去调用recvfrom函数,而是调用epoll函数,这个函数会将需要监听的数据交给内核,由内核检查这些数据是否就绪,如果数据就绪了,会通知应用程序来读取数据。这样可以避免发起多次recvfrom命令,降低CPU的压力。
而Redis是基于内存操作,执行速度很快,性能瓶颈在于网络延迟而不是执行速度,因此多线程不会带来巨大的性能提升。
而且在多线程的情况下会导致频繁的上下文切换,带来不必要的开销;
同时引入多线程会带来线程安全问题,就必须会使用锁机制来保证安全,这样实现复杂度变高,同时性能也会打折扣。
1.周期删除
设置定时任务,周期性的抽取部分过期的key,然后删除
2.惰性删除
即并不是该key到期了就删除,而是该key过期后,redis查询该key发现过期了,就会把他删除掉
noevction:不淘汰,内存满的时候不允许加入新数据
volatile-ttl:比较key剩余的时间,越短时间就越先删除
allkeys-random:从所有key中随机删除
volatile-random:从设置了过期时间的key中随机删除
allkeys-lru:删除最近最少使用的key
volatile-lru:从设置过期时间的key中,删除最近最少使用的key
allkeys-lfu:删除最少使用的key
volatile-lru:从设置过期时间的key中,删除最少使用的key
Redis进行数据快照,保存到磁盘中(触发条件:save,bgsave,停机,触发rdb条件)
fork主进程得到一个子进程,共享内存空间
子进程读取内存数据并写入新的RDB文件
用新RDB文件替换旧的RDB文件
Redis处理的每一个写命令都会记录在AOF文件
优化:进行命令重写,比如set key name1,set key name2可以直接合并成set key name2
主从节点第一次连接时,从节点无数据,主节点有数据,因此需要执行全量同步,将主节点的数据拷贝到从节点
Replication Id:是数据集的id,每个主节点都有一个Replication Id.
offset:偏移量,用来记录数据同步到了哪里
而第一次同步时,从节点由于是从主节点变过来的因此它的Replication Id和offset和主节点不一致没有,因此可以判断是否是主从第一次连接,第一次连接之后,从节点就得到的Replication Id和offset
完整流程:
slave节点请求增量同步
master节点判断replid,发现不一致,拒绝增量同步
master将完整内存数据生成RDB,发送RDB到slave
slave清空本地数据,加载master的RDB
master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave
slave执行接收到的命令,保持与master之间的同步
通过repl_baklog文件,repl_baklog文件用于记录redis操作日志和主节点的offset和从节点的offset,主从节点offset的差异,就是需要进行增量同步的部分。
如果出现了网络阻塞,导致主节点的offset超过了从节点的offset,那么从节点尚未复制的数据就会丢失,此时会执行全量同步。
格式:业务名:数据名:id;不超过44字节;不包含特殊字符;
优点:可读性强;避免key冲突;方便管理;节省内存;
大key:key占用内存大;key的成员多;key的成员内存大;
危害:网络阻塞;数据倾斜;Redis阻塞;CPU压力
缓存雪崩是指redis中大量的key在同一时间失效,导致所有的请求都打到数据库中,给数据库造成压力。
解决方案:
1.缓存永不过期,同时利用定时任务在活动结束后删除
2.设置随机过期时间,这里有两种方式,一种是直接生成一个固定时间;另一种是固定时间再加上一个随机时间,但是由于缓存的基数比较大,因此随机过期时间也有可能撞在一起,因此可以结合其他的方式,如分布式锁进行兜底。
缓存穿透是指用户请求一个不存在的数据时,由于redis和数据库都没有数据,因此请求直接打到数据库,给数据库造成压力。
解决方案:
1.缓存空对象:当请求查询redis为空时并且数据库也没有查到时,将该key设置成一个空值存入到redis中,之后访问该key时,发现存在空值,则立刻返回;否则查询数据库,数据库不为空,返回该数据并存入redis中。
2.使用布隆过滤器:将数据库中存量数据存放到布隆过滤器中,如果缓存中不存在数据,则继续查询布隆过滤器,如果布隆过滤器发现不存在,则该数据一定不存在,直接返回。如果存在,则继续查询数据库返回该数据。
缺陷:布隆过滤器存在小概率的误判问题,即布隆过滤器判断存在,但是不一定真的存在该数据。因此攻击者可能利用该缺陷发起恶意攻击,导致大量的请求打到数据库中。针对这个问题可以使用
3.使用锁,当该请求发现缓存不存在时,使用锁机制来实现避免多个相同的请求同时访问数据库,只让一个请求去加载数据,然后将该数据存入redis中。
组合方案:
1.组合缓存空对象方案,针对该攻击者请求的key,缓存空对象到redis中;
2.使用锁机制进行兜底,如果发现布隆过滤器存在之后,使用分布式锁,避免多个相同请求同时访问数据库,只让一个请求访问数据库。
缓存击穿问题是指某一个高并发访问的key失效,导致大量的请求打到数据库中,给数据库造成压力。
1.分布式锁,大量的请求访问该key时,发现redis中不存在,然后获取分布式锁,通过分布式锁限制只让一个请求访问数据库,查到数据后将该数据返回并写入redis中。同时我们使用双重判定锁的方式,让线程获取锁之后再次查询redis,如果缓存中存在该数据了(意味着前一个线程完成了缓存重建),然后就直接返回该数据,不用继续查询数据库,这样可以降低数据库的访问压力。
2.热点数据预加载,在活动开始前,将已知的热点数据写入缓存中,避免海量请求在第一次访问热点数据时都从数据库读取,降低响应时间,降低数据库的压力。
2.不设置过期(或者设置逻辑过期时间)方案,将热点数据不设置过期时间,等热点数据对应的业务活动结束,然后通过后台任务设置过期时间,自动删除。
缓存和数据库的一致性问题是指在使用缓存的情况下,如果保证缓存和数据库的数据一致。
通常有这么几种方案:
1.缓存双删
即先删除缓存,再更新数据库,再删除缓存
2.先更新数据库再删除缓存
指用于发起写请求时,首先访问数据库,然后删除相应的缓存,当下一个请求读请求到达时候,发现缓存中没有该数据,因此访问数据库读取到最新的数据,然后将该数据写入缓存中。
但是当缓存过期时,并且读请求的写入Redis的执行时间在写请求更新Redis之后。
3.Binlog异步更新缓存
首先更新数据库的数据,数据库会将数据表数据的变更信息写入binlog日志中,监听到日志文件的变化后,把数据库变更信息发送到消息队列中,程序接收到消息队列中的数据,对缓存做删除。如果删除失败了,程序就把数据再次发送到消息队列中,再做一次删除,实现删除失败后的重试。这种方案还有一种好处就是不会对业务代码造成过多的侵入,我们可以专门起一条协程来监听消息队列,如果收到消息队列中的数据,直接去删除对应的缓存即可,而不必在业务代码中去写。
先更新缓存再更新数据库有什么问题?
先更新数据库再更新缓存有什么问题?
先删除缓存再更新数据库有什么问题?
大key问题主要可以分成这样3种情况,第一种,key本身数据量比较大,如String类型的key,值为5M;第二种,该key的成员数量比较多;第三种,该key的成员占用的空间比较大;
而对于占用多大空间可以算做大key,我觉得这是一个比较主观的问题,它会基于应用程序的需求,硬件的配置,Redis实例的内存大小,通常情况下,String 类型的key存储内容不超过5M,List Set Zset等类型的key成员不超过20000,Hash类型key不超过20000,同时val不应过大。
## todo
摘自黑马程序员