Redis
Redis是一个基于c语言开发的开源数据库,与传统数据库不同的是Redis的数据是存在内存中的(内存数据库),读写速度非常快,被广泛用于缓存方向。Redis存储的是KV键值对数据。
Redis内置了多种数据结构实现:String、Hash、Sorted Set、BitMap等
Redis为什么这么快?
Redis内部做了非常多的性能优化:
1.Redis基于内存,内存的访问速度是磁盘的上千倍;
2.Redis基于Reactor模式设计开发了一套高效的事件处理模型,主要是单线程事件循环和IO多路复用;
3.Redis内置了多种优化后的数据结构实现,性能非常高。
为什么要用Redis/为什么要用缓存?
1.高性能
假如用户第一次访问数据库中的某些数据的话,这个过程是比较慢的,毕竟是从硬盘中读取的。但是,如果说用户访问的数据属于高频数据并且不会经常改变的话,那么我们就可以很放心得将该用户访问的数据存放在缓存中。
保证用户下一次再访问这些数据的时候就可以直接从缓存中获取了,操作缓存就是直接操作内存,所以速度相当快。
2.高并发
QPS(Query Per Second):服务器每秒可以执行的查询次数;
一般像MySQL这类的数据库的QSP大概都在1w左右,但是使用Redis缓存之后很容易达到10w+,甚至最好能够达到30w+(就单机Redis的情况,Redis集群的话会更高)。
由此可见,直接操作缓存能够承受的数据库请求数量是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中而不用经过数据库,进而,我们也就提高了系统整体的并发。
Redis应用
Redis除了做缓存,还能做什么?
分布式锁:通过Redis来做分布式锁是一种比较常用的方式,通常情况下,我们都是基于Redission来实现分布式锁。
限流:一般是通过Redis+Lua脚本的方式实现限流。
消息队列:Redis自带的list数据结构可以作为一个简单的队列来使用。
复杂业务场景:通过Redis以及Redis扩展提供的数据结构,我们可以很方便得完成很多复杂的业务场景比如通过bitmap统计活跃用户,通过sorted set维护排行榜。
Redis常用的数据结构有哪些?
5种基本数据结构:String(字符串),List(列表),Set(集合),Hash(散列),Zset(有序集合)。
3种特殊数据结构 :HyperLogLogs(基数统计)、Bitmap (位存储)、Geospatial (地理位置)。
Redis线程模型
Redis单线程模型了解吗?
Redis基于Reactor模式设计开发了一套高效的事件处理模型,这套事件处理模型对应的是Redis中的文件事件处理器。由于文件事件处理器是单线程方式运行的,所以我们一般都说Redis是单线程模型。
虽然文件事件处理器以单线程方式运行,但是通过使用I/O多路复用程序来监听多个套接字,文件事件处理器既实现了高性能的网络通信模型,又可以很好得与Redis服务器中的其他同样以单线程方式运行的模块进行对接,这保持了Redis内部单线程设计的简单性。
既然是单线程,那怎么监听大量的客户端连接呢?
Redis通过IO多路复用程序来监听来自客户端的大量连接(或者是监听多个socket),它会将感兴趣的事件及类型注册到内核中并监听每个事件是否发生。
I/O多路复用技术的使用让Redis不需要额外创建多余的线程来监听客户端的大量连接,降低了资源的消耗。
文件事件处理器主要是包含4个部分:
- 多个socket(客户端连接)
- IO多路复用程序
- 文件事件分派器(将socket关联到相应的事件处理器)
- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
Redis6.0之前为什么不使用多线程?
- 单线程编程容易并且更容易维护;
- Redis的性能瓶颈不在CPU,主要在内存和网络
- 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能
Redis内存管理
1、Redis给缓存数据设置过期时间,内存是有限的,如果缓存中的所有数据都是一直保存的话,很容易超出内存限制,因此Redis自带了给缓存数据设置过期时间的功能。
2、过期的数据的删除策略
如果假设设置了一批key只能存活1分钟,那么1分钟后,Redis是怎么对这批key进行删除的?
常用的过期数据的删除策略:
(1)惰性删除:只会在取出key的时候才对数据进行过期检查,这样对CPU最友好,但是可能会造成太多的过期key没有被删除。
(2)定期删除:每隔一段时间抽取一批key执行删除过期key操作。并且,Redis底层会通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响。
定期删除对内存更加友好,惰性删除对CPU更加友好。Redis采用的是定期删除+惰性删除。
Redis内存淘汰机制
MySQL里有2000w数据,Redis中只存20w数据,如何保证Redis中的数据都是热点数据?
Redis提供了6种数据淘汰策略
Redis性能优化
一个Redis命令的执行可分4步:
(1)发送命令
(2)命令排队
(3)命令执行
(4)返回结果
第一步和第四步耗费时间之和称为RTT(往返时间),也就是数据在网络上传输的时间。
使用批量操作可以减少网络传输时间,进而有效减少网络开销,大幅减少RTT。
另外,除了能够减少RTT之外,发送一次命令的socket IO成本也比较高,批量操作还可以减少socket IO成本。
Lua脚本
Lua脚本同样支持批量操作多条命令。一段Lua脚本可以视作一条命令执行,可以看作是原子操作。一段Lua脚本执行过程中不会有其他脚本或Redis命令同时执行,保证了操作不会被其他指令插入或打扰。
Lua脚本中支持一些简单的逻辑处理比如使用命令读取值并且在Lua脚本中进行处理。
Redis生产问题
缓存穿透
什么是缓存穿透?
大量请求的key是不合理的,根本不存在于缓存中,也不存在于数据库中。这就导致这些请求直接到了数据库上,根本没有经过缓存这一层,对数据库造成了巨大的压力,可能直接就被这么多请求弄宕机了。
- 解决办法
最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库id不能小于0,传入的邮件格式不对的时候直接返回错误信息给客户端等等。
1)缓存无效key
如果缓存和数据库都查不到某个key的数据就写一个到Redis中并设置过期时间。具体命令如下:SET key value EX 10086。这种方法可以解决请求的key变化不频繁的情况,如果黑客恶意攻击,每次构建不同的请求key,会导致Redis中缓存大量无效的key。很明显,这种方案并不能从根本上解决此问题,如果非要用这种方式来解决穿透问题的话,尽量将无效的key的过期时间设置短一些比如1分钟。
2)布隆过滤器
布隆过滤器是一个非常神奇的数据结构,通过它我们可以非常方便得判断一个给定数据是否存在于海量数据中,我们需要的就是判断key是否合法。
把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来的请求的值是否存在于布隆过滤器中,不存在的话直接返回请求参数错误信息给客户端,存在的话继续。
布隆过滤器存在误判的可能性:布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。
缓存击穿
缓存击穿中,请求的key对应的是热点数据,该数据存在于数据库中,但不存在于缓存中,通常是因为缓存中的那份数据已经过期,这就可能会导致瞬时大量的数据直接打在了数据库上,对数据库造成了巨大的压力,可能直接就被那么多的请求弄宕机了。
举个例子:秒杀进行的过程中,缓存中的某个秒杀商品的数据突然过期,这就导致瞬时大量对该商品的请求直接落到了数据库上,对数据库造成了巨大的压力。
有哪些解决办法:
- 设置热点数据永不过期或者过期时间比较长。
- 针对热点数据提前预热,将其存入缓存中,并设置合理的过期时间比如秒杀场景下的数据在秒杀结束前不过期。
- 请求数据库写数据到缓存之前,先获取互斥锁,保证只有一个请求会落到数据库上,减少数据库的压力。
缓存穿透和缓存击穿有什么区别?
缓存穿透中,请求的key既不存在于缓存中,也不存在于数据库中。
缓存击穿中,请求的key对应的是热点数据,该数据存在于数据库中,但不存在于缓存中,通常是因为缓存中的那份数据已经过期。
缓存雪崩
缓存在同一时间大面积得失效,导致大量的请求都直接落到了数据库上,对数据库造成了巨大的压力。 解决方案:
- 针对Redis服务不可用的情况:
1、采用Redis集群,避免单机出现问题整个缓存服务都没办法使用。
2、限流,避免同时处理大量的请求。 - 针对热点缓存失效的情况:
1、设置不同的失效时间比如随机设置缓存的失效时间。
2、设置二级缓存
为什么会有Redis内存碎片?
Redis内存碎片产生的原因:
1、Redis存储数据的时候向操作系统申请的内存空间可能会大于数据实际需要的内存空间。
2、频繁修改Redis中的数据也会产生内存碎片。