Redis
因为没有指定配置文件
需配置
redis-server redis.windows.conf
之后自动启动
测试性能
redis-benchmark -p 6379 -c 100 -n 10000
基础概念:
清空当前数据库:flushdb
清空全部数据库:flushall
redis是单线程的
redis的瓶颈是机器的内存和网络的带宽,用单线程既然可以实现,就用单线程了
为什么单线程还这么快呢
redis是将所有的数据全部放在内存中,所以说用单线程操作效率最高,多线程(cpu上下文会切换:耗时的操作),对于内存系统来说,如果没有上下文切换效率就是最高的!多次读写都是在一个cpu上,在内存情况下,这个就是最佳的选择。
底层数据结构(参考
简单动态字符串
一般来说,SDS 除了保存数据库中的字符串值以外,SDS 还可以作为缓冲区(buffer):包括 AOF 模块中的AOF缓冲区以及客户端状态中的输入缓冲区。
链表
Redis链表特性:
①、双端:链表具有前置节点和后置节点的引用,获取这两个节点时间复杂度都为O(1)。
②、无环:表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL,对链表的访问都是以 NULL 结束。
③、带链表长度计数器:通过 len 属性获取链表长度的时间复杂度为 O(1)。
④、多态:链表节点使用 void* 指针来保存节点值,可以保存各种不同类型的值
字典
Redis 的字典使用哈希表作为底层实现、
key 用来保存键,val 属性用来保存值,值可以是一个指针,也可以是uint64_t整数,也可以是int64_t整数。
注意这里还有一个指向下一个哈希表节点的指针,我们知道哈希表最大的问题是存在哈希冲突,如何解决哈希冲突,有开放地址法和链地址法。这里采用的便是链地址法,通过next这个指针可以将多个哈希值相同的键值对连接在一起,用来解决哈希冲突。
跳表
跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其它节点的指针,从而达到快速访问节点的目的。具有如下性质:
1、由很多层结构组成;
2、每一层都是一个有序的链表,排列顺序为由高层到底层,都至少包含两个链表节点,分别是前面的head节点和后面的nil节点;
3、最底层的链表包含了所有的元素;
4、如果一个元素出现在某一层的链表中,那么在该层之下的链表也全都会出现(上一层的元素是当前层的元素的子集);
5、链表中的每个节点都包含两个指针,一个指向同一层的下一个链表节点,另一个指向下一层的同一个链表节点;
①、搜索:从最高层的链表节点开始,如果比当前节点要大和比当前层的下一个节点要小,那么则往下找,也就是和当前层的下一层的节点的下一个节点进行比较,以此类推,一直找到最底层的最后一个节点,如果找到则返回,反之则返回空。
②、插入:首先确定插入的层数,有一种方法是假设抛一枚硬币,如果是正面就累加,直到遇见反面为止,最后记录正面的次数作为插入的层数。当确定插入的层数k后,则需要将新元素插入到从底层到k层。
③、删除:在各个层中找到包含指定值的节点,然后将节点从链表中删除即可,如果删除以后只剩下头尾两个节点,则删除这一层。
五大数据类型的应用场景
对于string 数据类型,因为string 类型是二进制安全的,可以用来存放图片,视频等内容,另外由于Redis的高性能读写功能,而string类型的value也可以是数字,可以用作计数器(INCR,DECR),比如分布式环境中统计系统的在线人数,秒杀等。
对于 hash 数据类型,value 存放的是键值对,比如可以做单点登录存放用户信息。
对于 list 数据类型,可以实现简单的消息队列,另外可以利用lrange命令,做基于redis的分页功能
对于 set 数据类型,由于底层是字典实现的,查找元素特别快,另外set 数据类型不允许重复,利用这两个特性我们可以进行全局去重,比如在用户注册模块,判断用户名是否注册;另外就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。
对于 zset 数据类型,有序的集合,可以做范围查找,排行榜应用,取 TOP N 操作等
五大数据类型
redis-key
#移除name key
move name 1
#设置过期时间
expire name 10
查看剩余过期时间:ttl name;
#查看键的类型
type name
String
如果append后的键不存在就创建一个,相当于set
获取一部分值
获取全部的值
替换值
setex 设置过期时间
setnx 如果没有这个键就设置值成功,如果已存在这个键就设置不成功(在分布式锁中常应用 )
批量设置键和值,批量获取值
msetnx 具有原子性
设置对象,以json字符串的形式
getset
List
在redis中,list可以被我们玩成栈,队列,阻塞队列
lpush 放进列表数据
lrange 取出指定位置的数据,可以看出下标是倒着来的。
说明rpush把值放在了队列的最后面
从列表中移除值,可以分为移除左边的和右边的
通过下标获取值
获取列表长度
llen
移除指定的值,可指定数量
通过下标截取指定的长度
移除列表的最后一个元素,将他移动到新列表中
更新指定位置的值
set
hash
Map集合,key-map,那时候这个值是一个map集合,本质和String类型没有太大区别,还是一个简单的key-vlaue
获取hash表的内容长度
获取所有的field和所有的值
hash更适合于对象的存储
Zset(有序集合)
排列
显示工资小于2000的工资排列
移除指定元素
获取集合中的个数
zcard salary
获取指定区间的成员数量
总结:
三种特殊数据类型
Geospatial(地理位置)
geoadd 添加地理位置
geoadd key longitude latitude member [longitude latitude member ...]
geopos获得地理位置详细信息
geopos key member [member ...]
geodist获得两个地点之间的距离,可在后面追加获得结果的单位 km m
geodist key member1 member2 [unit]
georadius获得以某一点经纬度为圆心,一定距离为半径之内的元素
georadius key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC
georadiusbymember获得某一成员为圆心,一定距离为半径之内的元素
geo底层的实现原理是zset,可以使用zset命令来操作geo
hyperloglog
统计基数
可以用作网站的UV(一个人访问网站多次,但是还是算作一个人)
传统的方式是用set,如果存储量太大的话就比较麻烦
测试
bitmaps
位存储
位图,是数据结构,都是操作二进制为来进行操作,只有0和1两个状态
统计一周的打卡情况
查看某一天的打卡情况
查看打卡了几天
127.0.0.1:6379> bitcount si
(integer) 2
事务
本质:一组命令的集合,所有命令都会被序列化,执行过程中,按照顺序执行
redis单挑命令保存原子性,但是事务不保证原子性,没有隔离级别的概念
所有命令在事务中,并不被直接执行,只有发起执行命令的时候才被执行
- 开启事务
- 命令入队
- 执行事务
正常执行事务
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec#执行事务
1) OK
2) OK
3) "v2"
4) OK
127.0.0.1:6379>
放弃事务
discard
编译型异常(代码有问题),事务中所有命令都不会被执行
127.0.0.1:6379> get k1 (nil) 127.0.0.1:6379> multi OK 127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> getset k3 (error) ERR wrong number of arguments for 'getset' command 127.0.0.1:6379> get k1 QUEUED 127.0.0.1:6379> exec (error) EXECABORT Transaction discarded because of previous errors.#所有事务不会被运行 127.0.0.1:6379>
运行时异常(1/0),其他命令正常执行,错误命令抛出异常
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1#字符串不会加1
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range#属于运行时异常
2) "v1" #正常执行
127.0.0.1:6379>
监控
乐观锁
获取version,比较version
监视:
watch key [key …]
执行之前,另外一个线程如果修改了我们的值,事务就会执行失败,就要放弃监视,然后重新监视
放弃监视
unwatch key
Jedis
配置依赖
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
</dependencies>
创建连接进行测试
Jedis jedis=new Jedis("127.0.0.1", 6379);
System.out.println(jedis.ping());
springboot整合
底层为lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全的情况,NIO模式
jeids采用直连,多线程不安全,如想避免不安全,就使用jedis pool连接池,BIO模式
操作各种基本类型
获得链接,操作数据库
序列化
放入对象需序列化
@Configuration
public class RedisConfig {
//编写自己的redisTemplate
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)throws UnknownHostException {
RedisTemplate<String,Object> redisTemplate=new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
//Json序列化配置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer=new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper=new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
//String的序列化
StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();
//key采用String的序列化方式
redisTemplate.setKeySerializer(stringRedisSerializer);
//Hash的序列化方式也采用String的方式
redisTemplate.setHashKeySerializer(stringRedisSerializer);
//value序列化方式采用jackson
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
———————–固定配置模板
Redis.conf详解
对大小写不敏感
包含
可包含多个配置文件
网络
bind 127.0.0.1
protected-mode yes #保护模式
port 6379 #默认端口
绑定的IP
通用
# NOT SUPPORTED ON WINDOWS daemonize no
#window端不支持此配置,配置此项须在linux端
daemonize yes #以守护进程的方式运行,默认是no,我们需要自己开启为yes
# NOT SUPPORTED ON WINDOWS pidfile /var/run/redis.pid
#如果指定为后台方式运行,则需指定pid文件
pidfile /var/run/redis.pid
#日志级别
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
loglevel notice
logfile "" #日志文件位置
databases 16 #默认数据库数量
快照
持久化:在规定时间内,执行了多少次操作则会持久化到文件.rdb .aof
reids是内存数据库,如果没有持久化,那么数据断电即失。
save 900 1 #如果900s内,有一个key进行了修改,我们将进行持久化操作。以下皆同
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes#持久化出错,redis是否还继续工作
rdbcompression yes#是否压缩rdb文件
rdbchecksum yes #保存rdb文件的时候,进行错误的检查校验
dir ./ #rdb文件的保存目录
安全
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> config set requirepass 1234 #设置密码
OK
127.0.0.1:6379> config get requirepass
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 1234 #登录
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "1234"
127.0.0.1:6379>
客户端连接相关
# maxclients 10000a 最大客户端数量
# maxmemory <bytes> 最大内存限制
# maxmemory-policy noeviction 内存到达极限值的处理策略
maxmemory-policy 六种方式
1、volatile-lru:只对设置了过期时间的key进行LRU(默认值)
2、allkeys-lru : 删除lru算法的key
3、volatile-random:随机删除即将过期key
4、allkeys-random:随机删除
5、volatile-ttl : 删除即将过期的
6、noeviction : 永不过期,返回错误
Redis持久化
RDB:Redis Databases
什么是RDB
在指定时间间隔后,将内存中的数据集快照写入数据库 (复制媒介);在恢复时候,直接读取快照文件,进行数据的恢复 ;
默认情况下, Redis 将数据库快照保存在名字为 dump.rdb的二进制文件中。文件名可以在配置文件中进行自定义。
三种触发方式
还有一种就是自动触发
工作原理
在进行 RDB
的时候,**redis
** 的主线程是不会做 io
操作的,主线程会 fork
一个子线程来完成该操作;
- Redis 调用forks。同时拥有父进程和子进程。
- 子进程将数据集写入到一个临时 RDB 文件中。
- 当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。
这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益(因为是使用子进程进行写操作,而父进程依然可以接收来自客户端的请求。)
触发机制
- save的规则满足的情况下,会自动触发rdb原则
- 执行flushall命令,也会触发我们的rdb原则
- 退出redis,也会自动产生rdb文件
优缺点
优点:
- 适合大规模的数据恢复
- 对数据的完整性要求不高
缺点:
耗时,耗性能
- 需要一定的时间间隔进行操作,如果redis意外宕机了,这个最后一次修改的数据就没有了,由此引入AOF。
- fork进程的时候,会占用一定的内容空间。
AOF
Append Only File
将我们所有的命令都记录下来,history,恢复的时候就把这个文件全部再执行一遍
以日志的形式来记录每个写的操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
三种策略
always:每条命令都刷盘
everysec:每一秒刷一次
no:操作系统决定
什么是AOF
快照功能(RDB)并不是非常耐久(durable): 如果 Redis 因为某些原因而造成故障停机, 那么服务器将丢失最近写入、以及未保存到快照中的那些数据。 从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方式: AOF 持久化。
如果要使用AOF,需要修改配置文件:
appendonly no yes
则表示启用AOF
默认是不开启的,我们需要手动配置,然后重启redis,就可以生效了!
如果这个aof文件有错位,这时候redis是启动不起来的,我需要修改这个aof文件
redis给我们提供了一个工具redis-check-aof --fix
优点和缺点
123456appendonly yes # 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分的情况下,rdb完全够用
appendfilename "appendonly.aof"
# appendfsync always # 每次修改都会sync 消耗性能
appendfsync everysec # 每秒执行一次 sync 可能会丢失这一秒的数据
# appendfsync no # 不执行 sync ,这时候操作系统自己同步数据,速度最快
优点
- 每一次修改都会同步,文件的完整性会更加好
- 每秒同步一次,可能会丢失一秒的数据
- 从不同步,效率最高
缺点
- 相对于数据文件来说,aof远远大于rdb,修复速度比rdb慢!
- Aof运行效率也要比rdb慢,所以我们redis默认的配置就是rdb持久化
AOF重写
fork出子进程,一个进行重写,一个把重写过程中产生的aof记录写到buffer中,最后添加到AOF新文件中。
RDB和AOP选择
RDB优势与劣势
优势
- 适合大规模的数据恢复
- 对数据完整性和一致性要求不高
劣势
-
在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改。
-
Fork的时候,内存中的数据被
克隆
了一份,大致2倍
的膨胀性能需要考虑
AOF优势/劣势
优势
- 每次修改同步:appendfsync always同步持久化,每次发生数据变更会被立即记录到磁盘,性能较差但数据完整性比较好
- 每秒同步:appendfsync everysec异步操作,每秒记录,如果一秒内宕机,仅一秒内的数据丢失
劣势
- 相同数据集的数据而言aof文件要远大于rdb文件,恢复速度慢于rdb
- Aof运行效率要慢于rdb,每秒同步策略效率较好,不同步效率和rdb相同
RDB消耗性能大,如果宕机可能丢失最后一次所做的修改,但是文件小,恢复快
为什么RDB恢复数据集较快
AOF,存放的指令日志,做数据恢复的时候,其实是要回放和执行所有的指令日志,来恢复出来内存中的所有数据的;
RDB,就是一份数据文件,恢复的时候,直接加载到内存中即可
如何选择使用哪种持久化方式?
一般来说, 如果想达到足以媲美 PostgreSQL 的数据安全性, 你应该同时使用两种持久化功能。
如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。
有很多用户都只使用 AOF 持久化, 但并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。
常见问题
fork
内存越大,fork时间越长,所以设置参数maxmemory参数
降低fork频率,避免不必要的全量复制
AOF追加阻塞
距离上次同步超过两秒,就阻塞主线程,直到同步完成
参考硬盘优化的相关策略
Redis发布订阅
订阅
subscribe hongdou
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "hongdou"
3) (integer) 1
发布
127.0.0.1:6379> publish hongdou hahaha (integer) 1
结果:
1) "message" 2) "hongdou" 3) "hahaha"
原理
每个 Redis 服务器进程都维持着一个表示服务器状态的 redis.h/redisServer 结构, 结构的 pubsub_channels 属性是一个字典, 这个字典就用于保存订阅频道的信息,其中,字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端。
客户端订阅,就被链接到对应频道的链表的尾部,退订则就是将客户端节点从链表中移除。
缺点
- 如果一个客户端订阅了频道,但自己读取消息的速度却不够快的话,那么不断积压的消息会使redis输出缓冲区的体积变得越来越大,这可能使得redis本身的速度变慢,甚至直接崩溃。
- 这和数据传输可靠性有关,如果在订阅方断线,那么他将会丢失所有在短线期间发布者发布的消息。
应用
- 消息订阅:公众号订阅,微博关注等等(起始更多是使用消息队列来进行实现)
- 多人在线聊天室。
稍微复杂的场景,我们就会使用消息中间件MQ处理。
Redis主从复制
概念
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。
默认情况下,每台Redis服务器都是主节点,一个主节点可以有0个或者多个从节点,但每个从节点只能由一个主节点。
作用
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
- 故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
- 负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。
- 高可用基石:主从复制还是哨兵和集群能够实施的基础。
为什么使用集群
- 单台服务器难以负载大量的请求
- 单台服务器故障率高,系统崩坏概率大
- 单台服务器内存容量有限。
环境配置
我们在讲解配置文件的时候,注意到有一个replication
模块 (见Redis.conf中第8条)
查看当前库的信息:info replication
123456789101112127.0.0.1:6379> info replication
# Replication
role:master # 角色
connected_slaves:0 # 从机数量
master_replid:3b54deef5b7b7b7f7dd8acefa23be48879b4fcff
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
既然需要启动多个服务,就需要多个配置文件。每个配置文件对应修改以下信息:
-
端口号
-
pid文件名
-
日志文件名
-
rdb文件名
注意:window配置方法:
开启多个端口服务器
1.配置文件
将redis.windows-service.conf复制一份,改名为相应文件,并更改配置文件中的端口为指定端口,以6380为例
port 6380
2.安装服务
redis-server –service-install –service-name redis_6380 redis.windows-service-6380.conf
3.启动服务
redis-server –service-start –service-name redis_6380
4.停止服务
redis-server –service-stop –service-name redis_6380
5.卸载服务
redis-server –service-uninstall –service-name redis_6380
指定端口启动客户端:
redis.cli -p 端口号
启动单机多服务集群:
一主二从配置
默认情况下,每台Redis服务器都是主节点;一般情况下只用配置从机就好了!
认老大!一主(79)二从(80,81)
使用SLAVEOF host port
就可以为从机配置主机了。
主机截图:
使用命令搭建是暂时的,真实开发中应该在从机的配置文件中进行配置,这样的话是永久的。
规则
从机只能读,不能写,主机可读可写但是多用于写。
当主机断电宕机后,默认情况下从机的角色不会发生变化 ,集群中只是失去了写操作,当主机恢复以后,又会连接上从机恢复原状。
当从机断电宕机后,若不是使用配置文件配置的从机,再次启动后作为主机是无法获取之前主机的数据的,若此时重新配置称为从机,又可以获取到主机的所有数据。这里就要提到一个同步原理
复制原理
从机成功连接到主机后会发送一个同步命令
主机启动bgsave,开始生成RDB文件,同时把期间的写命令缓存在内存中,RDB文件生成后发送给slave,slave会先写入磁盘,再从磁盘加载到内存,接着master将缓存中的写命令发送到slave,slave再进行同步
全量复制:主机生成RDB——-》同时缓存写命令———–》发送给从机,从机清空旧数据加载rdb到内存中———》同时基于旧数据版本提供服务
增量复制:每次更新数据同步到从机
过期key处理:主机删掉,模拟一条del指令发给slave
层层链路
主机从机成链路式连接
如果主机断开了连接,我们可以使用SLAVEOF no one
让自己变成主机!其他的节点就可以手动连接到最新的主节点
哨兵模式的全部配置
完整的哨兵模式配置文件 sentinel.conf
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970# 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
缓存穿透与雪崩
缓存穿透(查不到)
概念
在默认情况下,用户请求数据时,会先在缓存(Redis)中查找,若没找到即缓存未命中,再在数据库中进行查找,数量少可能问题不大,可是一旦大量的请求数据(例如秒杀场景)缓存都没有命中的话,就会全部转移到数据库上,造成数据库极大的压力,就有可能导致数据库崩溃。网络安全中也有人恶意使用这种手段进行攻击被称为洪水攻击。
解决方案
布隆过滤器
对所有可能查询的参数以Hash的形式存储,以便快速确定是否存在这个值,在控制层先进行拦截校验,校验不通过直接打回,减轻了存储系统的压力。
缓存空对象
一次请求若在缓存和数据库中都没找到,就在缓存中方一个空对象用于处理后续这个请求。
这样做有一个缺陷:存储空对象也需要空间,大量的空对象会耗费一定的空间,存储效率并不高。解决这个缺陷的方式就是设置较短过期时间
即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
缓存击穿(量太大,缓存过期)
概念
相较于缓存穿透,缓存击穿的目的性更强,一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。这就是缓存被击穿,只是针对其中某个key的缓存不可用而导致击穿,但是其他的key依然可以使用缓存响应。
比如热搜排行上,一个热点新闻被同时大量访问就可能导致缓存击穿。
解决方案
-
设置热点数据永不过期
这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。
-
加互斥锁(分布式锁)
在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。这样对锁的要求就十分高。
缓存雪崩
概念
大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
解决方案
-
redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群
-
限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
-
数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
缓存一致性
先更新数据库,在更新缓存,先更新缓存,再更新数据库都会有问题
一般使用先更新数据库再删除缓存可以解决,因为缓存的写入要快于数据库的写入,但是会影响缓存的命中率,
那么缓存删除失败怎么办?
-
先删除缓存,再更新数据库
-
此时就引入了延时双删:
删除缓存 更新数据库 睡眠 需要大于第二个线程读取数据并写入缓存的时间 删除缓存
睡眠时间难以估算,所以还是先更新数据库再删除缓存比较好
那么为什么不是更新缓存而是删除缓存呢,就是一个 lazy 计算的思想,不要每次都重新做复杂的计算,不管它会不会用到,而是让它到需要被使用的时候再重新计算
Redis 过期策略
定期删除+惰性删除
如果还有大量的过期数据既没有被用到过,又没有被定期删除扫描到那怎么办,就引入淘汰策略
淘汰策略
- noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。
- allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。
- allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。
- volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。
- volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。
- volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。
原创文章,作者:306829225,如若转载,请注明出处:https://blog.ytso.com/275206.html