1.快速入门
1.1简介
Redis(Remote Dictionary Server)是一个开源的内存数据库,遵守 BSD 协议,它提供了一个高性能的键值(key-value)存储系统,常用于缓存、消息队列、会话存储等应用场景。
1.2优点
提供了丰富的数据类型、高性能的读写能力、原子性操作、持久化机制、以及丰富的特性集。
- **丰富的数据类型:**Redis 不仅仅支持简单的 key-value 类型的数据,还提供了 list、set、zset(有序集合)、hash 等数据结构的存储。这些数据类型可以更好地满足特定的业务需求,使得 Redis 可以用于更广泛的应用场景。
- **高性能的读写能力:**Redis 能读的速度是 110000次/s,写的速度是 81000次/s。这种高性能主要得益于 Redis 将数据存储在内存中,从而显著提高了数据的访问速度。
- **原子性操作:**Redis 的所有操作都是原子性的,这意味着操作要么完全执行,要么完全不执行。这种特性对于确保数据的一致性和完整性非常重要。
- **持久化机制:**Redis 支持数据的持久化,可以将内存中的数据保存在磁盘中,以便在系统重启后能够再次加载使用。这为 Redis 提供了数据安全性,确保数据不会因为系统故障而丢失。
- **丰富的特性集:**Redis 还支持 publish/subscribe(发布/订阅)模式、通知、key 过期等高级特性。这些特性使得 Redis 可以用于消息队列、实时数据分析等复杂的应用场景。
- **主从复制和高可用性:**Redis 支持 master-slave 模式的数据备份,提供了数据的备份和主从复制功能,增强了数据的可用性和容错性。
- **支持 Lua 脚本:**Redis 支持使用 Lua 脚本来编写复杂的操作,这些脚本可以在服务器端执行,提供了更多的灵活性和强大的功能。
- **单线程模型:**尽管 Redis 是单线程的,但它通过高效的事件驱动模型来处理并发请求,确保了高性能和低延迟。
1.3测试连接
在vmware中ping 宿主机的url看看是否可以连接上
1.4基础命令
keys * :查看当前数据库中所有的key。
select 0 切换数据库Db0
dbsize 查看当前数据库中的键值对数量
flushdb:清空当前数据库中的键值对。
flushall:清空所有数据库的键值对。
exists key 判断键是否存在
del key 删除键值对
move key 1 将键值对要移到1号数据库
expire key 10 设置键值对过期时间(second)
type key 查看value数据类型
rename key newkey 修改key的名称
ttl key 返回键值对还剩的过期时间
2.五大数据类型
2.1String
getrange,setrange
append key value 向指定的key的value后追加字符串
decr/incr key 将指定key的value数值进行+1/-1(仅对于数字)
incrby/decrby key n 按指定的步长对value进行加减n
incrbyfloat key n 为数值加上浮点型数值
strlen key: 获取key保存值的字符串长度
getrange key start end 按起止位置获取字符串
>> getrange key 0 -1 获取value的字符串
setrange key offset value 用指定的value 替换key中 offset开始的值
>> setrange key 4 porter 用porter替换掉value中第五个字符
getset key value 先返回 key 的旧值,再将 key 的值设为 value
setnx key value(not exist): 当key不存在时设定value
setex key seconde value(expire) 在键值对之间设置过期时间
mset key1 value1 key2 value2 批量设置键值对
msetnx key1 value1 key2 value2 批量设置键值对,仅当所有key都不存在
mget key1 key2 批量获取多个key保存的值
psetex key millsennd value 以毫秒设置key的生存时间
2.2List
List中是可以进行双向操作的, 所以命令也就分为了LXXX和RLLL两类 。一般命令前面也会加个l
l短命令:push,range,insert,pushx(存在则加入),len,index,pop,rpoplpush,trim.rem,blpop(block阻塞)
LPUSH/RPUSH key value1[value2..]从左边/右边向列表中PUSH值(一个或者多个)。
>>lpush mylist 1 {1}
>>lpush mylist 2 {2,1}
>>rpush mylist 3 4 {2,1,3,4}
LRANGE key start end 获取list 起止元素==(索引从左往右 递增)==
>>lrange mylist 0 -1 获取mylist所有元素,get mylist 是错误的
LPUSHX/RPUSHX key value 向已存在的列名中push值(一个或者多个,前提是key已存在,否则失败
>>rpush mylist 1 2 (mylist存在则成功)
LINSERT key BEFORE|AFTER pivot value 在指定列表元素的前/后 插入value
>>linsert mylist after k porter 在k元素后面插入porter
LLEN key 查看列表长度
LINDEX key index 通过索引获取列表元素
LSET key index value 通过索引为元素设值
LPOP/RPOP key 从最左边/最右边移除值 并返回
>>lpop mylist 将最左边元素pop出
RPOPLPUSH source destination 将列表的尾部(右)最后一个值弹出,并返回,然后加到另一个列表的头部(右pop左push,组合)
>>rpoplpush mylist newlist 将mylist最右边的元素弹出到newlist左边
LTRIM key start end 通过下标截取指定范围内的列表
LREM key count value List中是允许value重复的 count > 0:从头部开始搜索 然后删除指定的value 至多删除count个 count < 0:从尾部开始搜索… count = 0:删除列表中所有的指定value。
>>lrem mylist -2 kk 从尾部往前删除2个值为kk的键值对
BLPOP/BRPOP key1[key2] timout 移出并获取列表的第一个/最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。返回的第一个是key,第二个事移除的value
>>blpop mylist newlist 10 移除mylist中的左边元素,如果mylist没有元素就从newlist中执行
BRPOPLPUSH source destination timeout 和RPOPLPUSH功能相同,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
>>brpoplpush mylist newlist 20 超时会显示nil
2.3Set
Set是string类型的无序集合。集合成员是唯一的
s短命令:add,card,member,ismember,randmember
sadd myset "hello" "world" 添加多个元素
scard myset 获取集合元素个数
smembers myset 查看myset所有元素
sismember myset m1 查看m1是否为myset中元素,是的话返回1
srandmember myset 2 随机返回2个myset元素
spop myset 1 随机移除1个元素并返回移除的元素
srem myset m1 移除myset中的m1元素
smove myset myset2 "hello" 将myset中的hello移到myset2中
sdiff key1 key2 差集
>> x={m1,m2,m3} y={m2,m4,m5} sdiff x y 结果就是返回m1,m3即y中没有的元素
sinter key1 key2 交集
sunion key1 key2 并集
2.4Hash
Map集合即自己有自己的键值对,hash 特别适合用于存储对象
而hash则是key-Map
h短命令:set,del,get,keys,vals,incrby,len
HSET studentx name porter # 将studentx哈希表作为一个对象,设置name为sakura
HMSET studentx sex 1 tel 15623667886 # 设置sex为1,tel为15623667886
hsetnx studentx eamil 192(not exist) 若studentx对象中的email属性不存在则设置成功,返回1
HEXISTS studentx name # name字段在studentx中是否存在,存在返回1
hget studentx name 获取studentx属性name对应的value
HMGET studentx name age tel # 获取studentx中name、age、tel字段的value
hkeys studentx 查看studentx中所有key(注意key得加s)
hvals studentx 查看studentx中所有value
hlen studentx 查看studentx中字段数量
HDEL studentx sex tel # 删除studentx 中的sex、tel字段
HINCRBY studentx age 1 # studentx的age字段数值+1
HINCRBYFLOAT studentx weight 0.6 # weight字段增加0.6
2.5zset
zset是一个有序集合,即在set基础上增加一个排序的数值score
z短命令:add,card,count,incrby,score,rank,range,rangebylex
zadd myzset 1 kuang 1 porter 向有序集合myzset中添加成员kuang score=1 以及成员porter score=1
ZCARD myzset # 获取有序集合的成员数
ZCOUNT myzset 0 1 # 获取score在 [0,1]区间的成员数量
ZINCRBY myzset 5 m2 # 将成员m2的score +5
ZSCORE myzset m1 # 获取成员m1的score
ZRANK myzset m1 # 获取成员m1的索引,索引按照score排序,score相同索引值按字典顺序顺序增加
ZRANGE myzset 0 -1 # 获取全部成员
ZRANGEBYLEX test - + LIMIT 0 3(lex是lexical字典顺序缩写) # 分页 返回所有成员的0,1,2三条记录
ZRANGEBYLEX testset (- [apple # 显示 (-,apple] 区间内的成员
ZRANGEBYLEX testset [apple [java # 显示 [apple,java]字典区间的成员
ZRANGEBYSCORE myzset 1 10 # 返回score在 [1,10]之间的的成员
ZLEXCOUNT testset - + 返回集合中的元素个数
ZLEXCOUNT testset [apple [java 返回区间的元素个数
zrem myzset abc 移除成员abc
ZREMRANGEBYLEX testset [apple [java # 移除字典区间[apple,java]中的所有成员
zremrangebyrank testset 0 1 移除排名0-1的成员
zremrangebyscore myzset 0 3 移除score在[0,3]的成员
ZREVRANGE myzset 0 3 # 按score递减排序,然后按索引,返回结果的 0~3
zrevrangebyscore myzset 6 2 按score递减顺序 返回集合中分数在[2,6]之间的成员
ZREVRANGEBYLEX testset [java (add # 按字典倒序 返回集合中(add,java]字典区间的成员
zrevrank myzset m7 按score递减顺序,排名以 0 为底,也就是说, 分数值最大的成员排名为 0 ,返回成员m7索引
3.三种特殊数据类型
3.1geospatial
使用经纬度定位地理坐标并用一个有序集合zset保存,所以zset命令也可以使用
应用场景:附近的人,两地距离
geoadd key longitud latitude member 将具体经纬度填入一个有序集合
geopos key member [member..] (pos为position) 获取集合中的一个/多个成员坐标
geodist member1 member2 返回两个给定位置之间的距离。默认以米作为单位。
georadius key longitude latitude radius m|km|mi|ft [WITHCOORD][WITHDIST] [WITHHASH] [COUNT count](coord带上坐标,dist带上距离,count表展示个数)
geohash key member1 member2 获取成员经纬坐标的字符表示
3.2Hyperloglog
基数统计:基数是数据中不重复的元素个数
应用场景:统计网页的访问量(针对不同用户)
pfadd key element1 [element2] 添加指定元素到key中
pfcount key 统计key中元素
pfmerge destkey sourcekey1 [sourcekey2] 将后两个key中元素合并,相同元素仅算一次
3.3Bitmaps
位图是一串连续的二进制数字,使用位存储,信息状态只有 0 和 1
应用场景:签到统计,状态统计
setbit sign 0 1 设置sign的第0位为一
getbit sign 5 查看某一天是否有打卡,若查看的天数未设置默认为0
bitcount sign [start,end]统计字符串被设置为1的bit数,可以按范围
bitop operration destkey key[key..]
对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。
BITPOS key bit [start] [end]
返回字符串里面第一个被设置为1或者0的bit位。start和end只能按字节,不能按位
4.事务
redis事务的本质:一组命令的集合
Redis事务没有隔离级别的概念
Redis单条命令是保证原子性的,但是事务不保证原子性!
事务的执行流程
multi开启事务
...命令入队...加入成功会显示queued
exec执行事务
//discard 取消事务
事务错误
编译时异常(语法错误):所有命令都不执行
运行时异常(代码逻辑错误):其他命令正常执行,这就是为什么事务不保证原子性的原因
5.Redis乐观锁
增加version字段,更新时比较version
watch key 监视字段,即加乐观锁,可以在开启事务之前使用(测试时我们可以使用多线程来模拟数据被抢占更新)
事务返回nil,说明事务执行失败
每次提交执行exec后都会自动释放锁,不管是否成功
6.Jedis
6.1依赖导入
导入jedis跟 fastJson
<!--导入jredis的包-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.70</version>
</dependency>
6.2测试连接
java来操作Redis,就是new一个Jedis对象,这个对象就相当于开启了redis-cli 然后就可以引用其中的方法
然后测试是否连接成功
Jedis jedis = new Jedis("192.168.159.135", 6379);//连接虚拟机的地址可以通过ip addr
String ping = jedis.ping();
System.out.println(ping);
如果连接不成,首先检查虚拟机的redis-server是否有开启,还有地址是否正确
如果服务开启,检查server的配置文件 如bind配置是否设置为0.0.0.0 这样才能保证外网也能访问
6.3事务操作
//连接本地的一般是127.0.0.1前提是redis-server已经开启(通过ps -ef | grep redis 查看)
Jedis jedis = new Jedis("192.168.159.135", 6379);//连接虚拟机的地址可以通过ip addr查看
jedis.watch("money"); //监视
Transaction multi = jedis.multi(); //获取事务对象
try {
multi.flushDB(); //事务的命令要用事务对象调用,而不是jedis
multi.set("money","300");
multi.set("hello","world");
multi.exec();
} catch(Exception e) {
multi.discard();
} finally {
System.out.println(jedis.get("money"));
System.out.println(jedis.get("hello"));
jedis.close();
}
7.整合springboot
7.1引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
这个依赖是springdata下的。而且在 springboot 2.x后 , 原来使用的 Jedis 被 lettuce 替换。
lettuce 实例可以在多个线程中共享,不存在线程不安全的情况!可以减少线程数据了,更像NIO模式
7.2编写配置
spring.data.redis.host=192.168.159.135
spring.data.redis.port=6379
7.3测试类
新建一个redisTemplate类,之后里面有一些基本的方法如exec,discard
opsforValue():相当于String类型,opsfor (list,zset,set)等等
清空数据库这些需要获得连接对象,即通过工厂再getConnection
获取连接对象
//RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
//connection.flushDb();
//connection.flushAll();
redisTemplate.opsForValue().set("damage","porter");
System.out.println(redisTemplate.opsForValue().get("damage"));
7.4redisTemplate
这里的redisTemplate中第一个参数是String
默认的redis中是不能存储对象跟中文的。所以需要我们自己去序列化。
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
// 将template 泛型设置为 <String, Object>
RedisTemplate<String, Object> template = new RedisTemplate();
// 连接工厂,不必修改
template.setConnectionFactory(redisConnectionFactory);
/*
* 序列化设置
*/
// key、hash的key 采用 String序列化方式
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// value、hash的value 采用 Jackson 序列化方式
template.setValueSerializer(RedisSerializer.json());
template.setHashValueSerializer(RedisSerializer.json());
template.afterPropertiesSet();
return template;
}
}
7.5redisUtil
使用RedisTemplate需要频繁调用.opForxxx
然后才能进行对应的操作,工作中一般将这些常用的公共API抽取出来封装成为一个工具类, 这个工具类帮我们封装了所有命令。
使用的时候我们可以从下面网站粘贴
https://www.cnblogs.com/zeng1994/p/03303c805731afc9aa9c60dbbd32a323.html
https://www.cnblogs.com/zhzhlong/p/11434284.html
8.redis.conf
redis不区分大小写
可以使用 include 组合多个配置问题
bind 0.0.0.0 ip绑定,外网可以访问
protected-mode yes 保护模式。默认开启
loglevel notice 日志级别
logfile "" 日志输出文件
requirepass "" 设置redis密码
maxclients 10000 最大客户端数量
maxmemory <bytes> 最大内存限制
maxmemory-policy noeviction # 内存达到限制值的处理策略
设置方法: config set maxclients 10
持久化规则 :持久化到文件 .rdb aof
- RDB
- AOF
9.redis持久化
9.1RDB
持久化默认策略是RDB,可以在redis.conf 中修改.
Redis DataBases:在指定时间间隔后,将内存中的数据集快照写入数据库 ;在恢复时候,直接读取快照文件,进行数据的恢复 ,Redis 将数据库快照保存在名字为 dump.rdb的二进制文件中。
触发机制:
save的规则满足的情况下,会自动触发rdb原则
执行flushall命令,也会触发我们的rdb原则
退出redis,也会自动产生rdb文件
9.2AOF
append only file : 以日志的形式来记录每个写的操作,将Redis执行过的所有指令记录下来(读操作不记录), redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
如果这个aof文件有错位,这时候redis是启动不起来的,我需要修改这个aof文件,redis给我们提供了一个工具redis-check-aof --fix
# appendfsync always # 每次修改都会sync 消耗性能
appendfsync everysec # 每秒执行一次 sync 可能会丢失这一秒的数据
# appendfsync no # 不执行 sync ,这时候操作系统自己同步数据,速度最快
9.3对比
相对于数据文件来说,aof远远大于rdb,修复速度比rdb慢!
Aof运行效率也要比rdb慢,所以我们redis默认的配置就是rdb持久化
RDB | AOF | |
---|---|---|
启动优先级 | 默认 | 慢 |
体积 | 小 | 大 |
恢复速度 | 快 | 慢 |
数据安全性 | 丢最后1s数据 | 按策略分析 |
10发布订阅
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
命令如下:
SUBSCRIBE channel [channel..] 订阅给定的一个或多个频道。
PUBLISH channel message 向指定频道发布消息
UNSUBSCRIBE channel [channel..] 退订一个或多个频道
psubscribe pattern [pattern ..] 订阅一个或多个符合给定模式的频道。
pubsub subcommand [argument..] 查看订阅与发布系统状态
应用场景:
- 消息订阅:公众号订阅,微博关注等等(起始更多是使用消息队列来进行实现)
- 多人在线聊天室。
稍微复杂的场景,我们就会使用消息中间件MQ处理。
11.主从复制
11.1概念
主从复制指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的!
只能由主节点复制到从节点(主节点以写为主、从节点以读为主)
默认情况下,每台redis服务器都是主节点, 一个主节点可以有0个或者多个从节点,但每个从节点只能由一个主节点。
11.2作用
作用:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
- 故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
- 负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。
- 高可用基石:主从复制还是哨兵和集群能够实施的基础。
使用集群的原因:
- 单台服务器难以负载大量的请求;
- 单台服务器故障率高,系统崩坏概率大
- 单台服务器内存容量有限。
11.3环境配置
通过info replication 确认服务是主从机中的哪种,若是主机还可以看到从机的数量
启动多个服务,就需要多个配置文件。每个配置文件对应修改以下信息:
- 端口号
- pid文件名
- 日志文件名
- rdb文件名
启动客户端:./redis-cli -p 6380
(./ 表示在当前目录下,一般敲一半后tab会自动补全,不然就是没找到)
11.3.1一主二从
假设6379端口做主机,则我们只需要配置从机就行
有两种方法:
-
命令:slaveof url port
例子:我们在6380的客户端下输入这个命令,
slaveof 127.0.0.0.1 6379
。之后6380就成为从机,可以得到主机6379的信息,但是不能写入。 -
配置文件:通过这个设置之后才能保证每次重启之后都是从机,不然还是默认主机
-
11.3.2使用规则
- 当主机断电宕机后,默认情况下从机的角色不会发生变化 ,集群中只是失去了写操作,当主机恢复以后,又会连接上从机恢复原状。
- 当从机断电宕机后,若不是使用配置文件配置的从机,再次启动后作为主机是无法获取之前主机的数据的,若此时重新配置称为从机,又可以获取到主机的所有数据。这里就要提到一个同步原理。
- 若主机宕机,有两种方式可以产生新的主机:
- 从机手动执行命令
slaveof no one
,这样执行以后从机会独立出来成为一个主机 - 使用哨兵模式(自动选举)
- 从机手动执行命令
12.哨兵模式
哨兵的作用:
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
- 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
哨兵的核心配置文件:sentinel.conf sentinel monitor mymaster 127.0.0.1 6379 1
数字1表示 :当一个哨兵主观认为主机断开,就可以客观认为主机故障,然后开始选举新的主机。
测试:redis-sentinel sentinel.conf
之后便可以开启哨兵模式,可以看到该端口是26379
哨兵模式优缺点:
-
优点: 哨兵集群,基于主从复制模式,拥有主从复制的优点
主从可以切换,故障可以转移,系统的可用性更好
哨兵模式是主从模式的升级,手动到自动,更加健壮 -
缺点: Redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
实现哨兵模式的配置其实是很麻烦的,里面有很多配置项
完整的sentinel.conf文件如下:
# Example sentinel.conf
# 哨兵sentinel实例运行的端口 默认26379
port 26379
# 哨兵sentinel的工作目录
dir /tmp
# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 1
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,
这个数字越小,完成failover所需的时间就越长,
但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
# SCRIPTS EXECUTION
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
#这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,
#一个是事件的类型,
#一个是事件的描述。
#如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
13.缓存穿透与雪崩
13.1缓存穿透(查不到)
概念
默认情况下,用户请求数据时,会先在缓存(Redis)中查找,若没找到即缓存未命中,再在数据库中进行查找,数量少可能问题不大,可是一旦大量的请求数据(例如秒杀场景)缓存都没有命中的话,就会全都到持久层数据库上查,造成数据库极大的压力,就有可能导致数据库崩溃。网络安全中也有人恶意使用这种手段进行攻击被称为洪水攻击。
解决方案
- 布隆过滤器:对所有可能查询的参数以Hash的形式存储,以便快速确定是否存在这个值,在控制层先进行拦截校验,校验不通过直接打回,减轻了存储系统的压力。
- 缓存空对象:一次请求若在缓存和数据库中都没找到,就在缓存中生成一个空对象用于处理后续这个请求。但是空对象也要空间,所以还需要设置空对象较短的过期时间。
13.2缓存击穿(量太大,缓存过期)
概念
一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。这就是缓存被击穿,只是针对其中某个key的缓存不可用而导致击穿,但是其他的key依然可以使用缓存响应。
比如热搜排行上,一个热点新闻被同时大量访问就可能导致缓存击穿
解决方法
-
设置热点数据永不过期
此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。
-
加互斥锁(分布式锁)
在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。
13.3缓存雪崩
概念
大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
解决方法
-
redis高可用
既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群
-
限流降级
在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
-
数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。