Redis缓存数据库深度剖析
Redis是用c语言开发的一个开源的高性能键值对数据库,
(1)支持的数据类型:
字符串类型、散列类型、列表类型、集合类型、有序集合类型。
(2)支持的数据结构:
简单的动态字符串、双向链表、压缩列表、哈希表、跳跃表、整数数组。
(3)支持持久化:
支持数据的备份,即master-slave模式的数据备份,可以将内存中的数据保存在磁盘中, 重启的时候可以再次加载进行使用。
(4)主从模式:
一主多从,主机可写,从机备份,类似于mysql的读写分离,存在问题是一旦主节点down掉,整个redis不可用。
(5)哨兵模式:
启用一个哨兵程序(节点),监控其余节点的状态,根据选举策略,进行主从切换。
(6)集群:
多主多从,实现高可用、分布式数据存储。
1)主从复制模式 缺陷:如果主宕机的话,需要人工修改选举的配置。
2)哨兵模式 优势自动帮助我们实现选举,不需要人工的修改配置 缺点:数据会冗余。
3)Cluster集群模式 动态实现扩容和缩容 而且保证每个节点的数据不冗余存放。
一、Redis与Ehcache的缓存的区别
Redis的缓存属于分布式的缓存,数据存放在服务端中内存中,也会持久化到硬盘上。
Ehacahe属于本地jvm内置的缓存,可以支持集群数据共享,但是内部之间支持数据的同步,效率非常低。
1.1、存储方式
Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。
Redis有部份存在硬盘上,这样能保证数据的持久性。
1.2、数据支持类型
Memcache对数据类型支持相对简单。
Redis有复杂的数据类型(String、List、Hash、Zset、Set)。
1.3、使用底层模型不同
它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。
Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
1.4、value大小
redis最大可以达到1GB,而memcache只有1MB。
二、Redis效率比较高的原因
1)数据存放在内存中。
2)IO多路复用原则,使用一个线程维护多个不同Redis客户端请求的操作。
3)基于跳跃表数据结构增删改效率非常高。
Redis的底层采用Nio中的多路IO复用的机制,能够非常好的支持这样的并发,从而保证线程安全问题;Redis是单线程,也就是底层采用一个线程维护多个不同的客户端io操作。
但是Nio在不同的操作系统上实现的方式有所不同,
windows操作系统:
使用select实现轮训时间复杂度是为o(n),而且还存在空轮训的情况,效率非常低, 其次是默认对我们轮训的数据有一定限制,所以支持上万的tcp连接是非常难。
linux操作系统:
采用epoll实现事件驱动回调,不会存在空轮训的情况,只对活跃的 socket连接实现主动回调这样在性能上有大大的提升,所以时间复杂度是为o(1)。
三、Redis的持久化机制
Redis的持久化机制分别为RDB(redis默认机制)、AOF实现,RDB采用定时(全量)持久化机制,但是服务器因为某种原因宕机后可能数据会丢失,AOF是基于数据日志操作实现的持久化,所以AOF采用增量同步的方案。
3.1、rdb原理
redis中有两个进程,主进程处理redis请求,子进程专门处理rdb数据同步,定时操作时,当客户端对redis进行操作时,这时候会把操作的整个数据以二进制的形式记录到rdb临时文件里,如果之前有同步的rdb文件,则覆盖之前的rdb文件。
这个子进程的所有数据(变量、环境变量、程序计数器等)都和原进程一摸一样,子进程同步的整个过程中,主进程不进行任何的io操作,确保了极高的性能。
(1) 持久化文件叫.rdb文件,默认在redis的根目录下叫dump.rdb,具体配置是在redis.conf中进行配置的。
(2)创建子进程的时机:
1)shutdown时,如果没有开启aof,会触发。
2)配置文件中默认的快照配置。
3)执行命令save或者bgsave,save是只管保存,其他不管,全部阻塞;bgsave redis会在后台异步进行快照,同时可以响应客户端的请求。
3.2、aof原理
将redis的操作日志以追加的方式写入文件,读操作是不记录的。当客户端发送命令时,redis服务端会把该命令的数据先存放到缓冲区,然后定时的每秒钟从缓冲区读取写入到aof文件中。
有三种同步方式:
(1)appendfsync:always
每次有数据修改发生时都会写入AOF文件,能够保证数据不丢失,但是效率非常低。
(2)appendfsync:everysec(默认)
每秒钟同步一次,可能会丢失1s内的数据,但是效率非常高。
(3)appendfsync:no
从不同步。高效但是数据不会被持久化。
建议最好还是使用everysec 既能够保证数据的同步、效率也还可以。
3.3、全量同步与增量同步
1)全量同步:
每天定时避开高峰期,将所有的数据全部实现备份同步,
优点:数据可以不用丢失,效率高;
缺点:可能会产生数据同步的延迟。
2)增量同步:
对行为的操作实现对数据的同步,数据同步延迟的概率比较多,因为比较频繁效率效率低。(可能会丢失1秒内的数据)
四、Redis内存淘汰策略
redis内存满了是如何处理的呢?
1)可以采用扩容我们的服务器内存,但是这种方案缺点:治标不治本,扩容硬件设施成本也比较高,这种形式不推荐。
2)官方推荐采用Redis内存淘汰策略,Redis如果内存满的情况下,删除经常不被使用的key。redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。
redis 提供 6种数据淘汰策略:
voltile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰。
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰。
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰。
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰。
no-enviction(驱逐):禁止驱逐数据。
五、Redis主从复制的原理
单个Redis如果因为某种原因宕机的话,可能会导致Redis服务不可用,可以使用主从复制实现一主多从,主节点负责写的操作,从节点负责读的操作,主节点会定期将数据同步到从节点中,保证数据一致性的问题。
5.1、主从复制数据同步的过程
1)Redis从节点向主节点建立socket连接;
2)Redis采用全量或者增量的形式将数据同步给从节点;
从Redis2.8版本以后 过程采用增量和全量同步
全量复制:一般用于在初次的复制场景(从节点与主节点一次建立)
增量复制:网络出现问题,从节点再次连接主节点时,主节点补发缺少的数据,每次数据增量同步
5.2、主从复制存在那些缺陷
如果主节点存在了问题,整个Redis环境是不可以实现写的操作,需要人工更改配置变为主操作。
如何解决该问题?可以使用哨兵机制可以帮助解决Redis集群主从选举策略。
六、Redis哨兵集群的设计原理
哨兵监控我们的主的Redis,如果主的Redis宕机之后从新在其他的从节点选举一个新的主节点,所以哨兵主要帮助我们实现自动化选举的过程。
6.1、哨兵集群原理
多个哨兵都执行同一个主的master节点,订阅到相同都通道,有新的哨兵加入都会向通道中发送自己服务的信息,该通道的订阅者可以发现新哨兵的加入,随后相互建立长连接。
6.2、Master的故障发现
单个哨兵会向主的master节点发送ping的命令,如果master节点没有及时的响应,哨兵会认为该master节点为“主观不可用状态”会发送给其他都哨兵确认该Master节点是否不可用,当前确认的哨兵节点数>=quorum(可配置),会实现重新选举。
6.3、哨兵如何确定master宕机之后重新选举?
1)哨兵机制每个10s时间只需要配置监听我们的主节点就可以采用递归形式获取当前整个Redis集群的环境列表,采用info 命令形式。
2)哨兵不建议是单机的,最好每个Redis节点都需要配置哨兵监听。
七、Redis Cluster分片集群实现原理
在我们的Redis集群模式中分为16384个卡槽,类似于数据库的中分表模式。当我们在写入一个key的时候,会对该key计算crc16算法得住一个数字16384=卡槽的位置。每个卡槽对应具体节点存放的位置,这样的话就可以将我们的数据可以均摊的存放各个节点。
每个卡槽是否可以存放多个不同的key?必须可以的。
myRedis/16384=52
csdnBlog%16384=52
类似推出 数据库表结构
该模式集群中最多只能支持Redis主的节点16384个,也就是每个节点对应一个卡槽位置。
八、Redis和Mysql数据一致性解决方案
首先mysql与redis实现数据同步不可能实现强一致性,只有通过最终一致性方案,短暂数据延迟不一致是允许的。
(1)清理redis的缓存,重新查询数据库。(很低级)
(2)采用mq订阅mysql binlog日志文件增量同步到redis中。
(3)使用阿里巴巴的canal增量订阅消息组件,它的底层实现就是采用mq来订阅mysql的binlog。
1)基于mq解决
首先数据库数据发生变化时会把相关sql和数据写进binlog文件,我们可以通过mq来对日志文件进行监听,日志文件发生变化时,将变化的binlog投递到mq中,然后由mq投递给消费者,再由消费者同步给redis。
2)使用alibaba的canal + kafka
Canal的核心原理也是相当于把自己当成MySql的一个从节点,然后去订阅主节点的BinLog日志。canal主要是基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅&消费的业务。
原理:
1、Canal会启动一个server端作为一个mysql从节点拉取mysql主节点最新的的binlog文件。
2、只要mysql主节点的binlog文件发生变化都会增量的形式通知给我们的canalServer端。
3、CanalServer再通知给canalClient端,client端自己手动配置刷新到Redis中去。
九、缓存穿透
9.1、概念
频繁查询不存在的key,导致会查询数据库,对数据库访问压力比较大。
9.2、解决方案
1)记录空查询key,设置简单的过期时间
2)对我们api接口实现限流、黑名单、白名单的机制、接口频率限制,防御ddos
3)采用布隆过滤器
布隆过滤器适用于判断某个数据是否在集合中存在,不一定百分百准备, 基本实现原理采用位数组与联合函数一起实现;
布隆过滤器最大的问题:
可能会存在一个误判的问题,如果误判概率越低,则二进制数组会越大,同时也会非常占用空间。
布隆过滤器为什么会产生冲突 ?
会根据key计算hash值,可能与布隆过滤器中存放的元素hash产生冲突都是为1,布隆可能会产生误判可能存在。此时可以将二进制数组长度设置比较大,可以减少布隆误判的概率。
9.3、基于布隆过滤器实现
1、引入依赖
<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>22.0</version>
</dependency>
2、具体实现类
public class BlongTest {/*** 在布隆中存放100万条数据*/private static Integer size = 1000000;public static void main(String[] args) {BloomFilter<Integer> filter = BloomFilter.create(Funnels.integerFunnel(),size, 0.01);for (int i = 0; i < size; i++) {filter .put(i);}// 从布隆中查询数据是否存在ArrayList<Integer> strings = new ArrayList<>();for (int j = size; j < size + 10000; j++) {if (filter.mightContain(j)){strings.add(j);}}System.out.println(误判数量: + strings.size());}}
@RequestMapping(/getOrder)public OrderEntity getOrder(Integer orderId) {if (integerBloomFilter != null) {if (!integerBloomFilter.mightContain(orderId)) {System.out.println(从布隆过滤器中检测到该key不存在);return null;}}// 1.先查询Redis中数据是否存在OrderEntity orderRedisEntity = (OrderEntity) redisTemplateUtils.getObject(orderId + );if (orderRedisEntity != null) {System.out.println(直接从Redis中返回数据);return orderRedisEntity;}// 2. 查询数据库的内容System.out.println(从DB查询数据);OrderEntity orderDBEntity = orderMapper.getOrderById(orderId);if (orderDBEntity != null) {System.out.println(将Db数据放入到Redis中);redisTemplateUtils.setObject(orderId + , orderDBEntity);}return orderDBEntity;
}@RequestMapping(/dbToBulong)public String dbToBulong() {List<Integer> orderIds = orderMapper.getOrderIds();integerBloomFilter = BloomFilter.create(Funnels.integerFunnel(), orderIds.size(), 0.01);for (int i = 0; i < orderIds.size(); i++) {integerBloomFilter.put(orderIds.get(i));}return success;
}
十、缓存击穿
10.1、概念
在高并发的情况下,多个请求同时访问一个过期的key,多个请求会同时查询我们的数据库,对数据库查询的压力非常大。
10.2、解决方案
1)基于分布式锁实现
多个请求同时获取锁,只要谁能够获取到锁,谁就能够去数据库查询,然后将数据放入到redis中,没有获取到锁的请求先等待;获取到锁的请求成功写入到redis中后,通知没有获取到锁的请求,直接从redis中获取数据即可。
2)设置软过期时间:
对热点key设置无限有效期或异步延长时间。
十一、缓存雪崩
11.1、概念
在同一时间大量的key同时失效,同时查询我们的数据库, 对数据库访问压力比较大。
11.2、解决方案
1)对我们Redis的key过期时间都是随机的。
2)或者Redis key 不过期。