数据类型及使用场景
Redis是key-value数据库,key的类型只能是String,但是value的数据类型就比较丰富了,主要包括五种:
- String:信息缓存、计数器、分布式锁等等,一个键最大能存储512MB;
- Hash:当对象的某个属性需要频繁修改时,不适合用string+json,因为它不够灵活,每次修改都需要重新将整个对象序列化并赋值;如果使用hash类型,则可以针对某个属性单独修改,没有序列化,也不需要修改整个对象。比如,商品的价格、销量、关注数、评价数等可能经常发生变化的属性;
- List:模拟队列、栈,按照插入顺序排序,可以选择添加位置(头部、尾部);
- Set:无序集合元素唯一,底层通过hash表实现,查询时间复杂度为O(1);
- ZSet:有序集合元素唯一 用于排序 点击率、浏览量;
过期策略
原文链接:blog.csdn.net/yuanlong122…
Redis中缓存数据是有过期时间,当缓存数据中数据到达过期时间,redis会删除数据达到节省空间(并不会立即删除),删除过期数据有二种策略:定期删除 + 惰性删除
- 定期删除策略 :在redis的redis.conf配置文件中有一个属性hz默认认为10,表示1S执行10此定期删除,即100ms/次
删除的时候,是随机抽取一些检测,默认抽取数量为 5 maxmemory-samples属性决定
因为如果Redis里面有大量key都设置了过期时间,全部都去检测一遍的话CPU负载就会很高,会浪费大量的时间在检测上面,甚至直接导致redis挂掉。所有只会抽取一部分而不会全部检查。
正因为定期删除只是随机抽取部分key来检测,这样的话就会出现大量已经过期的key并没有被删除,这就是为什么有时候大量的key明明已经过了失效时间,但是redis的内存还是被大量占用的原因 ,为了解决这个问题,Redis又引入了“惰性删除策略”
- 惰性删除策略 :惰性删除不是去主动删除,而是在你要获取某个key 的时候,redis会先去检测一下这个key是否已经过期,如果没有过期则返回给你,如果已经过期了,那么redis会删除这个key,不会返回给你。
"定期删除+惰性删除"就能保证过期的key最终一定会被删掉 ,但是只能保证最终一定会被删除,要是定期删除遗漏的大量过期key,我们在很长的一段时间内也没有再访问这些key,那么这些过期key不就一直会存在于内存中吗?不就会一直占着我们的内存吗?这样不还是会导致redis内存耗尽吗?由于存在这样的问题,所以redis又引入了“内存淘汰机制”来解决;
淘汰机制
- 内存淘汰机制由redis.conf配置文件中的maxmemory-policy属性设置,没有配置时默认为no-eviction模式;
- redis.conf配置文件中的 maxmemory 属性限定了 Redis 最大内存使用量,当占用内存大于maxmemory的配置值时会执行内存淘汰策略;
-
淘汰策略的执行过程
- 客户端执行一条命令,导致Redis需要增加数据(比如set key value);
- Redis会检查内存使用情况,如果内存使用超过 maxmemory,就会按照配置的置换策略maxmemory-policy删除一些key;
- 再执行新的数据的set操作;
总结:过期策略=定时删除+惰性删除(访问) 最终达到数据处于不可访问状态(删除),淘汰策略是最后一道防止内存溢出的方式;
redis的线程模型 -- I/O多路复用模型
原文链接:blog.csdn.net/yuanlong122…
Redis服务端是单线程来处理命令的,所有到达服务端的命令都会进入一个队列,逐个执行,不会有两条命令被同时执行,不会产生并发问题,这就是Redis的单线程基本模型。
Redis是单线程来处理命令的,为什么能够达到每秒万级别的处理能力呢?
- 纯内存操作;
- 单线程免去了创建维护线程的成本以及线程间切换开销;
- redis使用了IO多路复用技术,可以让单个线程高效的处理多个连接请求;
IO多路复用相比于多线程的优势在于系统的开销小,系统不必创建和维护线程,免去了线程切换带来的开销。这里"多路"指的是多个网络连接,"复用"指的是复用同一个线程。
IO多路复用就是利用select、poll、epoll监控多个流的 I/O事件,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态唤醒线程。其中select和poll是程序去轮询监控所有的流。而epoll是只轮询那些真正发出了事件的流,并且只依次顺序的处理准备就绪的流,这种做法就避免了大量的无用操作。
而select、poll、epoll都是IO多路复用的实现机制,Redis采用的就是epoll实现。
举例epoll:一个酒吧服务员(一个线程),前面趴了一群醉汉,突然A一个大吼一声"倒酒"(事件),你小跑过去给A倒一杯,然后就不需要理会A,突然又一个B要倒酒,你又过去倒上,就这样一个服务员服务好多人,有时没人喝酒,服务员处于空闲状态,可以干点别的事比如玩玩手机。至于select和poll,需要你挨个去问要不要酒,就没时间玩手机了;
缓存穿透、击穿、雪崩
缓存使用的本质是提升性能,在高并发的场景下,如果大量请求瞬时都打到DB上,那么就可能会导致DB崩溃,从而导致服务不可用;在开发过程中获取数据如下图所示:
穿透:某个热key在Redis与db都不存在,所有请求打到DB上; 解决方案:
- 1、查询db数据不存在,就放置一个空对象在redis中,将过期时间设置短一些,避免不必要的内存开销
- 2、布隆过滤器,不需要存储数据本身,只需要存储数据对应hash比特位,布隆过滤器判断存在,可能出现元素不在集合中;如果一个元素被删除,但是却不能从布隆过滤器中删除,这也是造成假阳性的原因;
击穿:某个热key在瞬间失效,请求打到DB上; 解决方案:
- 1、让一个线程回写缓存,其他线程等待写缓存线程执行完,高并发场景大量线程阻塞势必会降低吞吐量;
- 2、热点数据永不过期,物理上永不过期,逻辑上永不过期(将快要过期的数据重新构建)
雪崩:大量的热key在同一时间过期或者是Redis宕机,请求都打到DB上 解决方案:
- 1、均匀过期
- 2、永不过期(与击穿解决方案一致)
- 3、Redis高可用架构
概念记忆:穿透代表东西不存在(透明);击穿代表东西存在Redis瞬间被击穿;
缓存与db一致性
首先分析这个问题是建立在数据更新操作较少但需要频繁读取的原则下;如果是读多写也多,还需要保持缓存与DB一致那就是瞎扯蛋,为什么不是每次都从DB中获取或者直接读取Redis?
数据的获取:可以通过工具类统一封装数据获取,实现为获取数据时先从Redis中获取,
如Redis中存在直接返回,反之去DB查询,将数据放入缓存中;注意防止穿透,DB不存在可以在缓存放置空数据;
数据最终一致性
- 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应;
- 更新的时候,先更新数据库,然后再删除缓存;
数据强一致性
- 读写串行化,吞吐量大幅降低;
- Canal中间件读取binlog日志;
主从-哨兵-集群模式
主从复制:是指将一台Redis服务器的数据,复制到其他的Redis服务器。 前者称为主节点(master),后者称为从节点(slave),数据的复制是单向的,只能由主节点到从节点;
主从复制作用:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务,分担服务器负载,在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
- 高可用基石:主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础;
哨兵模式:当主节点发生故障的时候,选择一个从节点作为主节点,自动完成故障发现和故障转移,并通知客户端,从而实现高可用; Redis Sentinel 是一个特殊的 Redis 节点。在哨兵模式创建时,需要通过配置指定Sentinel与RedisMaster节点之间的关系,然后Sentinel会从主节点上获取所有从节点的信息,之后Sentinel会定时向主节点和从节点发送info命令获取其拓扑结构和状态信息
- 心跳检查
- 选举机制,RedisSentinel节点为奇数
- 故障转移只需要一个Sentinel节点完成
集群模式:引入Cluster模式的原因: 不管是主从模式还是哨兵模式都只能由一个master在写数据, 在海量数据高并发场景,一个节点写数据容易出现瓶颈,引入Cluster模式可以实现多个节点同时写数据;
持久化的方式
原文链接:blog.csdn.net/yuanlong122…
RDB持久化(定期生成快照) RDB持久化是通过快照的方式完成的,当满足配置规则(redis.conf文件)时,会自动将内存中的数据全量复制一份存储到硬盘上,这个过程称作”快照”。RDB文件是经过压缩处理的二进制文件,占用的空间较小。
(1)手动触发
手动触发对应save命令,会阻塞当前Redis服务器,直到RDB过程完成为止,对于内存比较大的实例会造成长时间阻塞,线上环境不建议使用。 (2)自动触发
自动触发对应bgsave命令,Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。
在redis.conf配置文件中可以配置:save
表示xx秒内数据修改xx次时自动触发bgsave。如果想关闭自动触发,可以在save命令后面加一个空串,即:save ""
还有其他常见可以触发bgsave,如:
- 如果从节点执行全量复制操作,主节点自动执行bgsave生成RDB文件并发送给从节点。
- 默认情况下执行shutdown命令时,如果没有开启AOF持久化功能则自动执行bgsave。
只要满足其中一个就进行快照
save 900 1
save 300 10
save 60 10000
AOF持久化(Redis写日志)
以独立日志的方式记录每次写命令, 重启时再重新执行AOF文件中的命令达到恢复数据的目的,默认情况下,Redis没有开启AOF持久化功能,可以通过在配置文件中作如下配置启用:
#开启AOF持久化功能
appendonly yes
# 设置AOF文件名称,默认appendonly.aof
appendfilename "appendonly.aof"
#AOF文件的保存位置(和dump.db文件保存的位置一样,为同一个属性设置);
dir ./
appendfsync属性:
# appendfsync always
appendfsync everysec
# appendfsync no
在AOF模式下,执行的Redis命令会暂时先存放在缓冲区,再将缓冲区的的数据持久化到磁盘。
appendfsync属性表示调用fsync函数将数据持久化到磁盘的频率。当设置为no时,Redis不会主动调用fsync去将AOF日志内容同步到磁盘,所以这一切就完全依赖于操作系统了;appendfsync everysec表示每秒同步一次;appendfsync always表示每一次写操作都会调用一次fsync。默认为appendfsync everysec。
AOF是可识别的纯文本文件,它的内容就是一个个的Redis操作命令,每执行一条写命令,都会追加到AOF文件,所以AOF文件会变的越来越大。
为了解决AOF文件体积膨胀的问题,Redis提供了AOF重写功能:Redis服务器可以创建一个新的AOF文件来替换现有的AOF文件,新旧两个文件所保存的数据状态是相同的,但是新的AOF文件不会包含任何浪费空间的冗余命令,通常体积会较旧AOF文件小很多。
手动触发命令:bgrewriteaof
即使 bgrewriteaof执行失败,也不会有任何数据丢失,因为旧的AOF文件在bgrewriteaof成功之前不会被修改。
据说从Redis 2.4开始,AOF重写由Redis自行触发, bgrewriteaof仅仅用于手动触发重写操作。但网上有说貌似redis还是没有自动触发bgrewriteaof,我们可以写个定时任务隔段时间去执行重写擦操作。
RDB与AOF该如何选择?
Redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择; 用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。如果同时配置了RBD和AOF,启动时只加载AOF文件恢复数据;
分布式锁
利用redis中setNx指令 不存在key,则保存成功,返回true,存在key,则保存失败,返回false
分布式锁使用步骤: setnx(key,value,expireTime)
1、声明锁 key
2、setnx(key,value,expireTime)判断锁是否存在,不存在就加锁,过期时间根据业务时间判断
3、业务代码中try catch
4、finally中释放锁
==引发思考:
1、如果key到期了,但是业务没执行完怎么办?
借鉴RedisSon实现方式(看门狗机制),由守护线程执行定时任务,判断主线程业务是否执行完毕,如果未执行完毕,续命机制;
2、宕机:前提是设置了过期时间,不设置过期时间都是耍流氓;
- 服务宕机,有过期时间
- Reids宕机
- 单机版 == redis都挂了,项目没救了;
- 主从模式 == 写入为Master节点,但是未同步到从节点,那么从节点拿不到这个key,锁失效
- 集群模式 == 集群挂了没救,未同步到其它节点同上,反之有过期时间;
问题遗留点(待深入学习)
- Redis在执行过期策略/淘汰机制删除对象时是否会形成阻塞?
- Redis过期策略中-定时删除是怎么实现的?
- RedisSon作为分布式锁使用;
- 在解决某些业务场景时,是否可以引入定时删除+惰性删除(访问)+淘汰机制处理?