1、事务

Redis单条命令保存原子性,但事务不保证原子性 无隔离级别的概念

Redis事务的本质:一组命令的集合,所有命令都被序列化,按照顺序执行
一次性、顺序性、排他性、执行一系列命令
所有在事务中命令,并没有被直接执行!只有发起命令的时候才会被执行。
事务顺序:

  • 开启事务 :multi
  • 命令入队
  • 执行事务:exec ```shell 127.0.0.1:6379> multi #开启事务 OK 127.0.0.1:6379(TX)> set k1 v1 QUEUED 127.0.0.1:6379(TX)> set k2 v2 QUEUED 127.0.0.1:6379(TX)> get k2 QUEUED 127.0.0.1:6379(TX)> set k3 v3 QUEUED 127.0.0.1:6379(TX)> exec #执行事务 1) OK 2) OK 3) “v2” 4) OK 127.0.0.1:6379>
  1. - 放弃事务:discard
  2. ```shell
  3. # 事务中的命令都不会执行

异常处理: :::tips 编译型异常:事务中所有命令都不会执行 :::

  1. 127.0.0.1:6379> multi #开启事务
  2. OK
  3. 127.0.0.1:6379(TX)> set k1 v1
  4. QUEUED
  5. 127.0.0.1:6379(TX)> set k2 v2
  6. QUEUED
  7. 127.0.0.1:6379(TX)> getset k3 #错误
  8. (error) ERR wrong number of arguments for 'getset' command
  9. 127.0.0.1:6379(TX)> set k4 v4
  10. QUEUED
  11. 127.0.0.1:6379(TX)> exec #执行,出现问题
  12. (error) EXECABORT Transaction discarded because of previous errors.
  13. 127.0.0.1:6379> get k1 #事务失败
  14. (nil)

:::tips 运行时异常:只针对错误命令抛出异常,其余不做处理 :::

  1. 127.0.0.1:6379> multi
  2. OK
  3. 127.0.0.1:6379(TX)> incr k1
  4. QUEUED
  5. 127.0.0.1:6379(TX)> set k1 "v1"
  6. QUEUED
  7. 127.0.0.1:6379(TX)> incr k1
  8. QUEUED
  9. 127.0.0.1:6379(TX)> set k2 v2
  10. QUEUED
  11. 127.0.0.1:6379(TX)> get k2
  12. QUEUED
  13. 127.0.0.1:6379(TX)> exec
  14. 1) (integer) 1
  15. 2) OK
  16. 3) (error) ERR value is not an integer or out of range
  17. 4) OK
  18. 5) "v2"
  19. 127.0.0.1:6379> get k2
  20. "v2"
  21. 127.0.0.1:6379>

监控

  • 悲观锁:
    • 很悲观,认为什么时候都会出现问题,无论什么都会加锁
  • 悲观锁:
    • 很乐观,认为不会出现问题,所以不会上锁,更新数据的时候去判断,在此期间是否有人修改过这个数据
    • 获取version(MVCC)
  • 使用watch实现乐观锁
    • unwatch解锁

2、Jedis

官方推荐的java操作Jedis的工具

1、导入对应的依赖

  1. <dependencies>
  2. <dependency>
  3. <groupId>redis.clients</groupId>
  4. <artifactId>jedis</artifactId>
  5. <version>3.8.0</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>com.alibaba</groupId>
  9. <artifactId>fastjson</artifactId>
  10. <version>1.2.83</version>
  11. </dependency>
  12. </dependencies>

2、编码测试:

  • 打开连接限制
  • image.png
  • 连接数据库
  • 操作命令
  • 断开链接

    1. //1、new Jedis 对象、
    2. Jedis jedis = new Jedis("43.142.155.217",6379);
    3. //2、测试连接
    4. System.out.println(jedis.ping());
  • 输出:image.png

常用的API

具体和Redis的操作都是一致的,在此略过。

Jedis中的事务

  1. //1、new Jedis 对象、
  2. Jedis jedis = new Jedis("43.142.155.217",6379);
  3. //开启事务
  4. Transaction multi = jedis.multi();
  5. try {
  6. multi.set("k1","v1");
  7. multi.set("k2","v2");
  8. multi.exec(); //执行事务
  9. }catch (Exception e){
  10. multi.discard(); //放弃事务
  11. e.printStackTrace();
  12. }finally {
  13. System.out.println(jedis.get("k1"));
  14. System.out.println(jedis.get("k2"));
  15. jedis.close();//关闭事务
  16. }

3、SpringBoot整合项目

1、引入配置

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. <version>2.7.6</version>
  5. </dependency>

2、源码分析
在springboot2.x后,原来的jedis被替换成了lettuce

  • Jedis:采用的直连,多个线程的话不安全,如果避免不安全,需要使用jedispool连接池,更像BIO
  • lettuce:采用Netty,实例可以在多个线程中共享,不存在线程不安全的情况,可以减少线程数据,更像NIO模式

源码:

  1. @Bean
  2. @ConditionalOnMissingBean( name = {"redisTemplate"} ) //可以自己定义
  3. @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
  4. public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
  5. //默认的Redis么有过多设置,Redis对象都需要序列化
  6. //两个泛型都是Object类型,后续需要强制转换<Sring,Object>
  7. RedisTemplate<Object, Object> template = new RedisTemplate();
  8. template.setConnectionFactory(redisConnectionFactory);
  9. return template;
  10. }
  11. @Bean
  12. @ConditionalOnMissingBean
  13. @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
  14. public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
  15. return new StringRedisTemplate(redisConnectionFactory);
  16. }

结论:

  • 可以自己定义代替原本的实例
  • 默认的Redis么有过多设置,Redis对象都需要序列化
  • 两个泛型都是Object类型,后续需要强制转换
  • String可以使用直接使用stringRedisTemplate

整合测试
配置:

  1. #配置Redis
  2. spring.redis.host=43.142.155.217
  3. spring.redis.port=6379

测试命令
1、基本命令—opsFor···.xxx
image.png

2、常见命令
image.png

3、数据库命令

  1. RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
  2. connection.flushDb();
  1. @Autowired
  2. private RedisTemplate redisTemplate;
  3. @Test
  4. void contextLoads() {
  5. // RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
  6. // connection.flushDb();
  7. redisTemplate.opsForValue().set("k111","v1111");
  8. redisTemplate.opsForValue().set("k222","v12222");
  9. System.out.println(redisTemplate.opsForValue().get("k111"));
  10. }

序列化
image.png

image.png

在企业中pojo需要序列化。
image.png

编写自己的Template

  1. package com.tao.config;
  2. import com.fasterxml.jackson.annotation.JsonAutoDetect;
  3. import com.fasterxml.jackson.annotation.PropertyAccessor;
  4. import com.fasterxml.jackson.databind.ObjectMapper;
  5. import org.springframework.beans.factory.annotation.Configurable;
  6. import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
  7. import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
  8. import org.springframework.context.annotation.Bean;
  9. import org.springframework.context.annotation.Configuration;
  10. import org.springframework.data.redis.connection.RedisConnectionFactory;
  11. import org.springframework.data.redis.core.RedisTemplate;
  12. import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
  13. import org.springframework.data.redis.serializer.RedisSerializer;
  14. import org.springframework.data.redis.serializer.StringRedisSerializer;
  15. @Configuration
  16. public class RedisConfig {
  17. //编写自己的Template
  18. @Bean
  19. @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
  20. public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
  21. //为了连接方便
  22. RedisTemplate<String, Object> template = new RedisTemplate();
  23. Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
  24. ObjectMapper om = new ObjectMapper();
  25. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  26. om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  27. jackson2JsonRedisSerializer.setObjectMapper(om);
  28. //Stirng 的序列化
  29. StringRedisSerializer serializer = new StringRedisSerializer();
  30. //key采用String的序列化方式
  31. template.setKeySerializer(serializer);
  32. template.setHashKeySerializer(serializer);
  33. //采用jackson
  34. template.setValueSerializer(jackson2JsonRedisSerializer);
  35. //配置具体的序列化方式
  36. template.setHashValueSerializer(jackson2JsonRedisSerializer);
  37. template.afterPropertiesSet();
  38. template.setConnectionFactory(redisConnectionFactory);
  39. return template;
  40. }
  41. }

4、RedisConfig

1、大小写不敏感

  1. #
  2. # 1k => 1000 bytes
  3. # 1kb => 1024 bytes
  4. # 1m => 1000000 bytes
  5. # 1mb => 1024*1024 bytes
  6. # 1g => 1000000000 bytes
  7. # 1gb => 1024*1024*1024 bytes
  8. #
  9. # units are case insensitive so 1GB 1Gb 1gB are all the same.

2、包含

  1. ################################## INCLUDES ###################################
  2. # Include one or more other config files here. This is useful if you
  3. # have a standard template that goes to all Redis servers but also need
  4. # to customize a few per-server settings. Include files can include
  5. # other files, so use this wisely.
  6. #
  7. # Notice option "include" won't be rewritten by command "CONFIG REWRITE"
  8. # from admin or Redis Sentinel. Since Redis always uses the last processed
  9. # line as value of a configuration directive, you'd better put includes
  10. # at the beginning of this file to avoid overwriting config change at runtime.
  11. #
  12. # If instead you are interested in using includes to override configuration
  13. # options, it is better to use include as the last line.
  14. #
  15. # include /path/to/local.conf
  16. # include /path/to/other.conf

3、网络

  • 绑定IP :::tips bind 127.0.0.1
    protected-mode no
    port 6379 :::

4、通用 :::tips daemonize yes # 以守护进程的方式运行、默认是no,需要手动开启 ::: :::tips pidfile /www/server/redis/redis.pid #如果以后台方式,需要指定pid ::: :::tips

Specify the server verbosity level.
# This can be one of:(四个级别)
# 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 “/www/server/redis/redis.log” # log文件位置

::: :::tips databases 16 #date默认16 ::: :::tips always-show-logo yes #是否显示logo :::

5、快照

持久化,在规定时间内,执行了多少次,则会持久化到文件 Redis是内存数据库,如果灭有持久化,则数据就会丢失

:::tips

如果9000秒内,如果至少1个key进行了修改,则进行修改
save 900 1
#如果300s内,对至少10个key进行了修改,我们进行持久化操作
save 300 10
save 60 10000

::: :::tips

持久化错误后,是否还进行继续工作
stop-writes-on-bgsave-error yes

::: :::tips

是否压缩rdb文件,需要消耗cpu
rdbcompression yes

::: :::tips rdbchecksum yes #保存rdb时,进行错误检查校验 ::: :::tips dir /www/server/redis/ #rdb文件保存的目录! :::

6、安全

  • 修改密码 :::tips requirepass foobared #配置文件中修改 ::: :::tips config set requirepass “1234” # 命令中设置密码 :::

7、限制

:::tips maxclients 10000 # 最大客户端数量 ::: :::tips maxmemory # redis最大内存容量 ::: :::tips maxmemory-policy noeviction #内存到达上限之后的处理策略
noeviction: 不删除策略, 达到最大内存限制时, 如果需要更多内存, 直接返回错误信息。(默认值)
allkeys-lru: 所有key通用; 优先删除最近最少使用(less recently used ,LRU) 的 key。
volatile-lru: 只限于设置了 expire 的部分; 优先删除最近最少使用(less recently used ,LRU) 的 key。
allkeys-random: 所有key通用; 随机删除一部分 key。
volatile-random: 只限于设置了 expire 的部分; 随机删除一部分 key。
volatile-ttl: 只限于设置了 expire 的部分; 优先删除剩余时间(time to live,TTL) 短的key。 :::

8、APPEND ONLY AOF配置

:::tips appendonly no #默认不开启AOF的默认使用rdb持久化
appendfilename “appendonly.aof” #持久化文件的名称

appendfsync always 每次修改通过
appendfsync everysec #每秒执行一次,可能会丢失这一秒的数据
# appendfsync no 不同步
具体在AOF中

:::

5、Redis持久化

Redis是内存数据库,如果不进行保存,那么服务一旦推出,服务器中的数据库状态也会小时,所以Redis提供了持久化功能。

1、RDB(Redis DB)

什么是RDB

Reids - 图8

触发机制

  • 在save的规则满足时,会自动触发rdb规则
  • 执行flushall命令,也会触发rdb规则
  • 退出redis,也会产生rdb文件

    特点

    优点:
  1. 适合大规模的数据恢复
  2. 对数据完整性要求不高

缺点:

  1. 需要一定时间间隔进程操作,如果宕机,则会缺失最后一次修改该的数据
  2. fork进程,会占用一定的内存空间

2、AOF (Append Only File)

将所有命令都记录下来,history,恢复的时候就把这个文件全部在执行

image.png

只记录写操作,只追加文件但不改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启就根据日志的文件内容将写指令从前到后执行一次以完成数据的恢复工作。

如果aof文件有错误,这时候redis是启动不起来的,我们需要修复这个aof文件 :::tips redis-check-aof —fix appendonly.aof :::

重写规则 :::tips auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb #如果aof文件大于64m,太大了,fork一个新进程对我们的文件进行重写 ::: 特点
优点:

  1. 每一次修改都同步,文件的完整性会更好
  2. 每秒同步一次,可能会丢失数据
  3. 从不同步,效率高

缺点:

  1. 相对与数据文件来说,aof远远大于rdb,修复的速度比rdb慢
  2. AOF运行效率比RDB慢

总结

1、RDB 持久化方式能够在指定的时间间隔内对你的数据进行快照存储
2、AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始
的数据,AOF命令以Redis 协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重
写,使得AOF文件的体积不至于过大。
3、只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化
4、同时开启两种持久化方式

  • 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。
  • RDB 的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的Bug,留着作为一个万一的手段。

5、性能建议

  • 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留 save 900 1 这条规则。
  • 如果Enable AOF ,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了,代价一是带来了持续的IO,二是AOF rewrite 的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重写可以改到适当的数值。
  • 如果不Enable AOF ,仅靠 Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔IO,也减少了rewrite时带来的系统波动。代价是如果Master/Slave 同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个 Master/Slave 中的 RDB文件,载入较新的那个,微博就是这种架构。

6、Redis发布订阅

Redis发布订阅(pub/sub)是一种消息通信模式:发送者发送,订阅者消费。微博,微信,关注系统。
Redis客户端可以订阅任意数量的频道
消息图:
Reids - 图10
测试
订阅端: :::tips 127.0.0.1:6379> SUBSCRIBE Tao #先订阅频道 ::: 发布端: :::tips 127.0.0.1:6379> PUBLISH Tao 123 #发布消息
(integer) 1
127.0.0.1:6379> PUBLISH Tao nihao
(integer) 1
127.0.0.1:6379> PUBLISH Tao “helllo nihao “ ::: 消息展示: :::tips image.png :::

实现原理

Redis通过PUBLISHSUBSCRIBEPSUBSCRIBE等命令实现了发布和订阅功能。

  • 通过SUBSCRIBE命令订阅某个频道后,redis-server里面维护了一个字典,字典的键就是一个个channel,而字典的值则是一个链表,链表中保存了所有订阅这个channel的客户端。SUBSCRIBE命令的关键,就是将客户端添加到指定channel的订阅链表中。
  • 通过PUBLISH命令向订阅者发送信息,redis-server会使用给定的频道作为键,在他所维护的channel字典中查找记录订阅该频道的所有客户端的链表,通过遍历这个链表,来将信息发布给所有的订阅者

Pub/Sub 底层存储结构
订阅 Channel
在 Redis 的底层结构中,客户端和频道的订阅关系是通过一个字典加链表的结构保存的,形式如下:
Reids - 图12

在 Redis 的底层结构中,Redis 服务器结构体中定义了一个 pubsub_channels 字典 :::tips struct redisServer {
//存所有频道的订阅关系
dict *pubsub_channels
} :::

在这个字典中,key 代表的是频道名称,value 是一个链表,这个链表里面存放的是所有订阅这个频道的客户端
所以当有客户端执行订阅频道的动作的时候,服务器就会将客户端与被订阅的频道在 pubsub_channels 字典中进行关联。
这个时候有两种情况:

  • 该渠道是首次被订阅:首次被订阅说明在字典中并不存在该频道的信息,那么程序首先要创建一个对应的 key,并且要赋值一个空链表,然后将对应的客户端加入到链表中。此时链表只有一个元素。
  • 该频道已经被其他客户端订阅过:这个时候就直接将对应的客户端信息添加到链表的末尾就好了。

比如,如果有一个新的客户端 Client 08 要订阅 run 频道,那么上图就会变成
Reids - 图13

如果 Client 08 要订阅一个新的渠道 new_sport ,那么就会变成
Reids - 图14

取消订阅
上面介绍的是单个 Channel 的订阅,相反的如果一个客户端要取消订阅相关 Channel,则无非是找到对应的 Channel 的链表,从中删除对应的客户端,如果该客户端已经是最后一个了,则将对应 Channel 也删除。

模式订阅结构
模式频道的订阅与单个频道的订阅类似,不过服务器是将所有模式的订阅关系都保存在服务状太pubsub_patterns 属性里面。

  1. struct redisServer{
  2. //保存所有模式订阅关系
  3. list *pubsub_patterns;
  4. }

与订阅单个 Channel 不同的是,pubsub_patterns 属性是一个链表,不是字典。节点的结构如下:

  1. struct pubsubPattern{
  2. //订阅模式的客户端
  3. redisClient *client;
  4. //被订阅的模式
  5. robj *pattern;
  6. } pubsubPattern;

其实 client 属性是用来存放对应客户端信息,pattern 是用来存放客户端对应的匹配模式。

所以对应上面的 Client-06 模式匹配的结构存储如下
Reids - 图15

在pubsub_patterns链表中有一个节点,对应的客户端是 Client-06,对应的匹配模式是run*。

订阅模式
当某个客户端通过命令psubscribe 订阅对应模式的 Channel 时候,服务器会创建一个节点,并将 Client 属性设置为对应的客户端,pattern 属性设置成对应的模式规则,然后添加到链表尾部。

  1. 创建新节点;
  2. 给节点的属性赋值;
  3. 将节点添加到链表的尾部;

退订模式
退订模式的命令是punsubscribe,客户端使用这个命令来退订一个或者多个模式 Channel。服务器接收到该命令后,会遍历pubsub_patterns链表,将匹配到的 client 和 pattern 属性的节点给删掉。这里需要判断 client 属性和 pattern 属性都合法的时候再进行删除。

遍历所有的节点,当匹配到相同 client 属性和 pattern 属性的时候就进行节点删除。

发布消息
当一个客户端执行了publish channelName message 命令的时候,服务器会从pubsub_channels和pubsub_patterns 两个结构中找到符合channelName 的所有 Channel,进行消息的发送。在 pubsub_channels 中只要找到对应的 Channel 的 key 然后向对应的 value 链表中的客户端发送消息。

使用场景

1、实时消息系统!
2、实时聊天
3、关注系统
稍微复杂的场景我们会使用消息中间件。

7、Redis主从复制

概念

主从复制,将一台服务器的数据复制到其他服务器。前者称为主节点(master/leader),后者成为从节点(slave/follower);数据的复制是单向的。Master以写为主,Slave以读为主。
作用:

  1. 数据冗余
  2. 故障uifu
  3. 负载均衡
  4. 高可用基石:是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。

一般来说,一台Redis是万万不能的,原因如下:
1、从结构上,单个服务器会发生单点故障,并需要处理所有请求,压力较大。
2、从容量上,单个服务器容量有i吸纳,一般来说单个Redis最大使用内存不应该超过20G。

环境配置

只配置从库,不用配置主库

  1. 127.0.0.1:6379> info replication #查看当前库的信息
  2. # Replication
  3. role:master #角色 master
  4. connected_slaves:0 # 没有主机
  5. master_failover_state:no-failover
  6. master_replid:c24250bf031c14d0bdea2710cf01b302458ef6c0
  7. master_replid2:0000000000000000000000000000000000000000
  8. master_repl_offset:0
  9. second_repl_offset:-1
  10. repl_backlog_active:0
  11. repl_backlog_size:1048576
  12. repl_backlog_first_byte_offset:0
  13. repl_backlog_histlen:0

复制3个配置文件,然后修改对应信息

  1. 端口
  2. pid名字
  3. log文件名字
  4. dump.rdb 文件名字 :::tips image.png :::

一主二从

默认情况下,每台Redis服务器都是主节点,一般只需要配置从机就可以。

  1. 127.0.0.1:6380> SLAVEOF 127.0.0.1 6379
  2. OK
  3. 127.0.0.1:6380> info replication
  4. # Replication
  5. role:slave #当前角色
  6. master_host:127.0.0.1 #主机信息
  7. master_port:6379 #主机端口
  8. master_link_status:up
  1. 127.0.0.1:6379> info replication
  2. # Replication
  3. role:master
  4. connected_slaves:1
  5. slave0:ip=127.0.0.1,port=6380,state=online,offset=14,lag=0
  6. master_failover_state:no-failover

实际使用中,主从配置应在配置文件中修改,才是永久的,否则只适用于本次。

  1. replicaof <masterip> <masterport> #主机的基本信息
  2. masterauth <master-password> #主机密码

细节

  • 主机可以写,从只读不可以写
  • 如果主机断开,从机依旧连接到主机,但是没有写操作,这时候如果主机回来了,从机依旧可以获取到主机。
  • 如果使用命令行,则需要重新设置主从
  • 只要变为从机,立马就会从主机中获取值!

复制原理

  • Slave启动成功连接到master后 会发送一个sync同步命令
  • Master接到命令,启动后台的存盘进程,同时收集所有接受到的用于数据修改的命令,在后台进程执行完毕后,master将传送整个数据文件到slave,并完成一次完全同步。
  • 全量复制:slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
  • 增量复制:Master继续将新的所有收到的修改命令以此传给slave,完成同步
  • 但是如果重新连接master,一次完全同步(全量复制)将自动执行。

自动配置: :::tips SLAVEOF no one #自己成为主机点 :::

8、哨兵模式

自动选举老大的模式

概述

主从切换的方式是,当主服务宕机后,需要手动把一台服务器切换为主服务器。这就需要人工干预,还会造成一段时间内服务不可用,更多时候我们可以考虑哨兵模式。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,它作为进程会独立运行,其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
Reids - 图17
主要两个作用:

  • 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
  • 当哨兵检测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他服务器,修改配置文件,让他们切换主机。

然而一个哨兵进程可能会出现问题,为此可以使用多个哨兵,各个哨兵还会进行监控,这样就形成了多哨兵模式。

测试

1、配置哨兵配置文件 sentinel.conf :::tips sentinel monitor mymaster 127.0.0.1 6379 1 ::: 后面的数字1,代表主机挂了,slave投票看让谁接替成为主机,票数最多的成为主机。
2、启动哨兵 :::tips redis-sentinel ../sentinel.conf ::: 3、选举算法、随机选择主节点。 :::tips 选择成功后,前一个主机上线后,会总动成为从机 :::

特点

优点

1、哨兵集群,基于主从复制模式,所有主从设置优点,它全有
2、主从可以切换,故障可以转移,系统的可用性会更好
3、哨兵模式就是主从模式的升级,手动到自动,更加健壮!!!

缺点

1、Redis不好在线扩容,集群容量一旦到达上限,在线扩容就十分麻烦
2、实现哨兵模式的配置其实很麻烦,有很多的选择

哨兵模式的全部配置

一般是由运维人员配置

  1. # Example sentinel.conf
  2. # 哨兵sentinel实例运行的端口 默认26379
  3. port 26379
  4. # 哨兵sentinel的工作目录
  5. dir /tmp
  6. # 哨兵sentinel监控的redis主节点的 ip port
  7. # master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
  8. # quorum 配置多少个sentinel哨兵统一认为master主节点失联 那么这时客观上认为主节点失联了
  9. # sentinel monitor <master-name> <ip> <redis-port> <quorum>
  10. sentinel monitor mymaster 127.0.0.1 6379 2
  11. # 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都
  12. 要提供密码
  13. # 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
  14. # sentinel auth-pass <master-name> <password>
  15. sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
  16. # 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
  17. # sentinel down-after-milliseconds <master-name> <milliseconds>
  18. sentinel down-after-milliseconds mymaster 30000
  19. # 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同
  20. 步,
  21. 这个数字越小,完成failover所需的时间就越长,
  22. 但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
  23. 可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
  24. # sentinel parallel-syncs <master-name> <numslaves>
  25. sentinel parallel-syncs mymaster 1
  26. # 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
  27. #1. 同一个sentinel对同一个master两次failover之间的间隔时间。
  28. #2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的
  29. master那里同步数据时。
  30. #3.当想要取消一个正在进行的failover所需要的时间。
  31. #4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超
  32. 时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
  33. # 默认三分钟
  34. # sentinel failover-timeout <master-name> <milliseconds>
  35. sentinel failover-timeout mymaster 180000
  36. # SCRIPTS EXECUTION
  37. #配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮
  38. 件通知相关人员。
  39. #对于脚本的运行结果有以下规则:
  40. #若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
  41. #若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
  42. #如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
  43. #一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执
  44. 行。
  45. #通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等
  46. 等),将会去调用这个脚本,这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常
  47. 运行的信息。调用该脚本时,将传给脚本两个参数,一个是事件的类型,一个是事件的描述。如果
  48. sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执
  49. 行的,否则sentinel无法正常启动成功。
  50. #通知脚本
  51. # sentinel notification-script <master-name> <script-path>
  52. sentinel notification-script mymaster /var/redis/notify.sh
  53. # 客户端重新配置主节点参数脚本
  54. # 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master
  55. 地址已经发生改变的信息。
  56. # 以下参数将会在调用脚本时传给脚本:
  57. # <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
  58. # 目前<state>总是“failover”,
  59. # <role>是“leader”或者“observer”中的一个。
  60. # 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的
  61. slave)通信的
  62. # 这个脚本应该是通用的,能被多次调用,不是针对性的。
  63. # sentinel client-reconfig-script <master-name> <script-path>
  64. sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

9、Redis缓存穿透和雪崩

缓存穿透

概念

缓存穿透的概念很简单,用户想要查询一个数据,发现Redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询,发现也没有,当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库,这会给持久层数据库造成很大的压力,这就是相当于出现了缓存穿透。

解决方案

布隆过滤器
这是一种数据结构,对所有可能查询的参数都以hash存储,在控制层先进行校验,不符合则丢弃,从而避免了对存储系统的查询压力

缓存空对象
当存储层不命中后,即使返回的空对象也将其存储起来,同时设置一个过期时间,之后再访问这个数据会从缓存中获取,保护了后端数据源。
两个问题

  • 如果控制能够被缓存起来,这就意味着缓存许哟啊更多的空间存储更多的键,因为这当中可能会有更多的空值的键;
  • 即使对控制设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响

缓存击穿

概述

指一个高频key,在不停的扛着大并发。当这个key失效的瞬间,持续的大并发就会击破缓存,直接请求数据库,就像在缓存屏障上凿开了一个洞。

解决方案

设置热点数据不过期
加互斥锁

缓存雪崩

概述

大量的key在一段时间内集中失效,或者服务宕机等原因缓存失效,造成存储层压力过大,造成整个服务的不可用。

解决方案

Redis高可以用
集群搭建(异地多活)
服务限流降级(SpringCloud)
当缓存失效后,通过加锁或者队列的方式控制读数据库的线程数量
服务预热
在正式部署前,先把可能的数据先访问一遍,这样数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。