源于蚂蚁课堂的学习,点击这里查看(老余很给力)
基础知识
线程安全
由于单线程,故Redis天然规避线程安全问题。那么,为什么单线程还能这么高效呢?
这得益于其底层进行io操作时,采用了NIO的多路复用原则。(如有读者感兴趣,可以研究一下nio多路复用原理即可)
Redis官方是没有windows版本的,因为Redis底层做io操作是基于linux的epoll来进行NIO的io多路复用。
其主要通过socket收到消息后进行主动调用回调,去提升io轮询效率。而Windows没有epoll,只能通过selector去不停轮询。
持久化机制
RDB
Redis默认采用rdb方式实现数据的持久化
即以快照的形式将数据持久化到磁盘,是一个二进制的文件dump.rdb
rdb方式会将Redis中的数据进行全量的备份,即覆盖式更新,默认备份间隔时间是900s。
所以如果Redis出现故障无法备份数据时,如果采用rdb方式持久化数据,会导致这900s内数据的丢失。
但其优点是消耗服务器内存少
==================redis.conf==================
# rdb方式备份数据的文件名称
dbfilename dump.rdb
# 在900秒(15分钟)之后,如果至少有1个key发生变化,则dump内存快照
save 900 1
# 在300秒(5分钟)之后,如果至少有10个key发生变化,则dump内存快照。
save 300 10
# 在60秒(1分钟)之后,如果至少有10000个key发生变化,则dump内存快照。
save 60 10000
AOF
AOF是基于数据日志操作实现的持久化,故属于增量备份
在Redis中,aof有三种模式
# 每次有数据修改发生时都会写入AOF文件,能够保证数据不丢失,但是效率非常低
appendfsync always
# 每秒钟同步一次,可能会丢失1s内的数据,但是效率非常高, 这也是推荐使用的方式
appendfsync everysec
# 从不同步。高效但是数据不会被持久化。
appendfsync no
==================redis.conf==================
# 开始aof持久化,默认采用everysec
appendonly yes
与MySQL保持一致性
Redis作为缓存,是需要和数据库保持一致的,当然这里的一致不是强一致,而是最终一致。
常见的解决方法有以下
1.通过mq去监听MySQL的binlog文件,将其变化的结果以消息队列的方式传输至redis
2.有种比较low的方式,就是手动清空redis,让其访问数据库,然后同步至Redis,这种方式风险高,容易雪崩
3.可采用阿里的canal去实现数据同步
淘汰策略
Redis中的内存并非无止境,故当其内容满了再去存放数据会出问题,所以其内部维护了6种淘汰策略
noeviction:不淘汰,当内存满了,直接报错
allkeys-lru:指定了有效期的key中,淘汰不经常使用的key
volatile-lru:指定了有效期的key中,随机淘汰
allkeys-random:所有key中,淘汰不经常使用的key
volatile-random:所有key中,随机淘汰
volatile-ttl:指定了有效期的key中,淘汰具有更早过期时间的key
==================redis.conf==================
# 标识Redis内存大小的阈值,超过则会进行淘汰策略
maxmemory <bytes>
# 指明Redis要使用的淘汰策略为哪一种
maxmemory-policy 淘汰策略名称
key的自动过期
当我们的key失效时,可以执行我们的客户端回调监听的方法
==================redis.conf==================
notify-keyspace-events "Ex"
SpringBoot整合key失效监听
@Configuration
public class RedisListenerConfig {
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
return container;
}
}
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
/**
* Redis失效事件 key
*/
@Override
public void onMessage(Message message, byte[] pattern) {
String expiraKey = message.toString();
// 拿到失效的key做一些业务处理
}
}
事务
multi 开启事务
exec 提交事务
discard 取消提交事务
watch keyName 可以监听一个或者多个key,提交事务前判断key是否变化,进而进行事务提交或取消,通过版本实现乐观锁
ps:Redis官方是没有提供回滚方法, 只提供了取消事务。
multi只保证了事务的原子性,但没有保证其隔离性,故一般配合watch使用
分布式锁
分布式锁的大体实现思路是相同的,即多个请求去创建同一个节点,谁成功谁就拿到锁。
Redis也是一样的,它维护了一个setnx操作,即带返回结果的设置key,1表示设置成功,0表示失败
所以当setnx返回1时,执行业务操作,超时则回滚数据,正常执行结束后删除此key,这样其他请求继续抢夺锁。
进阶知识
主从复制
单个节点的Redis可用性不高,即当其宕机时,对外Redis不可用,故采用集群的方式保证高可用。
主从模式遵循一主多从,读写分离(主写从读)。
==================redis.conf==================
# replicaof <masterip> <masterport>
slaveof 主节点的IP 主节点的端口
masterauth 主节点的密码
从节点启动时读取其配置,根据主节点地址信息与其建立socket长连接,定期保持数据同步的通讯。
在一主多从模式中,如果一个主节点对应的从节点特别多时,主节点的同步压力非常大,所以往往采用树状结构进行集群。
即: master-->s0-->s2
-->s3
-->s1-->s4
从节点首次同步采用读取rdb方式全量备份数据,当有更新key操作时,会采用增量方式同步
在客户端连接中通过info replication可以获取当前Redis节点集群的信息
哨兵机制
主从模式存在很大的弊端,即当主节点宕机,会使得集群对外不可写,需要人工维护。
故Redis采用哨兵机制很好的解决这个问题。
哨兵是一个独立的进程,其主要用于监听master节点(定时向master发送ping的命令)的活跃状态。
一般来说,一个Redis对应一个哨兵,多个哨兵都是监听集群中同一个master节点,当哨兵启动时,会向消息队列中订阅
关于master的通道channel,然后将自身的地址作为消息投放到消息通道中,然后作为消费者等待消费,其它哨兵也做此
操作,这样一来,哨兵之间可以得到彼此的信息,然后建立socket长连接进行通讯。
当有哨兵发现master宕机后,会发送消息至channel,通知所有哨兵去访问master,超过阈值(可配置)数量的哨兵得
到master宕机结果的话,就会认为master宕机,然后进行选举。
之前的master启动后,发现集群已经有了master,那么他就会变成slave的从。
通过这种机制,实现Redis的故障转移
哨兵通过监听master,执行info replication获取master下对应从节点信息,然后向下递归执行info replication
从而获取整个集群的节点信息,进行选举
==================sentinel.conf==================
sentinel monitor mymaster master的IP master的端口 哨兵阈值(认为master宕机的阈值)
sentinel auth-pass mymaster master的密码
# sentinel心跳检测主3秒内无响应,视为挂掉,开始切换其他从为主
sentinel down-after-milliseconds mymaster 3000
# 每次最多可以有1个从同步主。一个从同步结束,另一个从开始同步
sentinel parallel-syncs mymaster 1
# 主从切换超时时间
sentinel failover-timeout mymaster 18000
#启动哨兵
./redis-sentinel sentinel.conf的路径
安全控制
缓存穿透
大量Redis不存在的key访问,导致Redis无法命中,感觉像是穿过了缓存,直接访问数据库,造成数据库访问压力。
解决方法:
api限流
接口限制
用户授权
IP检查
网关黑白名单
布隆过滤器
缓存击穿
在高并发的情况下,热点key失效,导致大量请求访问到了数据库,如同某个点被击穿一样。
解决方法:
分布式锁
软过期
缓存雪崩
Redis持久化文件丢失,使得Redis启动时进行数据预热,大量的查询涌向数据库,导致数据库无法承受而宕机。
这样一来,缓存拿不到数据,数据库又起不来,形成雪崩效应
Cluster集群
传统的主从模式有很大弊端,即:
中心化 只有一个主在写,宕机需要选举,期间对外不可用
数据全量同步 占用资源,内容冗余
故Redis推出分片化管理数据
其原理是内部维护一个hash槽,默认槽的个数是16384个,然后支持多个master节点,每个master可以支持各自的主从。
根据槽的长度取模master节点数,将槽的下标进行均匀分配,即每个master都会有属于自己的槽区间
通过对key进行crc16算法计算及其然后对16384取模获取此key对应的槽下标
根据槽下标寻找相应的master节点,然后将其存放至对应的槽位,一个槽位类似于一张表,即可存放多个key
其原理近似MySQL的分库分表和hashMap
RedisCluster集群模式环境搭建
mkdir rediscluster
cd rediscluster/
mkdir redis7000
mkdir redis7001
mkdir redis7002
mkdir redis7003
mkdir redis7004
mkdir redis7005
每个配置文件内容(以7005为例)
# 后台启动
daemonize yes
# 允许外部访问
protected-mode no
# 修改端口号,从7000到7005
port 7005
# 开启cluster,去掉注释
cluster-enabled yes
# 自动生成
cluster-config-file 7000nodes.conf
# 节点通信时间
cluster-node-timeout 15000
logfile /usr/rediscluster/redis7005/redis.log
启动我们的redis
/usr/redis/bin/redis-server /usr/rediscluster/redis7000/redis.conf
/usr/redis/bin/redis-server /usr/rediscluster/redis7001/redis.conf
/usr/redis/bin/redis-server /usr/rediscluster/redis7002/redis.conf
/usr/redis/bin/redis-server /usr/rediscluster/redis7003/redis.conf
/usr/redis/bin/redis-server /usr/rediscluster/redis7004/redis.conf
/usr/redis/bin/redis-server /usr/rediscluster/redis7005/redis.conf
连接任意一个redis
/usr/redis/bin/redis-cli -h 192.168.212.163 -p 7000
(error) CLUSTERDOWN Hash slot not served 说明没有分配hash槽
# 分配hash槽
终端执行
/usr/redis/bin/redis-cli --cluster create 192.168.212.163:7000 192.168.212.163:7001 192.168.212.163:7002 192.168.212.163:7003 192.168.212.163:7004 192.168.212.163:7005 --cluster-replicas 1
(建议最好使用服务器的ip地址搭建)
# -c 标识当key的槽下标不在此Redis时,会进行重定向
/usr/redis/bin/redis-cli -h 192.168.212.163 -p 7000 –c
动态扩容
扩容节点
/usr/redis/bin/redis-server /usr/rediscluster/redis7006/redis.conf
/usr/redis/bin/redis-server /usr/rediscluster/redis7007/redis.conf
新增一个主节点 为7006
/usr/redis/bin/redis-cli --cluster add-node 192.168.212.163:7006 192.168.212.163:7000
新增一个从节点 为7007
/usr/redis/bin/redis-cli --cluster add-node 192.168.212.163:7007 192.168.212.163:7000 --cluster-salve --cluster-master-id 5d94171eb34ed4396bf5b9db8efaab4d96d0cf10(7006的ID)
新增的7006 是没有任何槽位 需分配Redis槽位扩容
cluster slots
/usr/redis/bin/redis-cli --cluster reshard 192.168.212.163:7000
动态缩容
/usr/redis/bin/redis-cli --cluster reshard 192.168.212.163:7000 --cluster-from 5d94171eb34ed4396bf5b9db8efaab4d96d0cf10 --cluster-to 511058958a3b80dd600e060c2500050c6c5a02ab --cluster-slots