Redis分布式缓存

单点Redis的问题

  1. 数据丢失问题:Redis是内存存储,服务器重启可能会丢失数据
    解决方案:实现Redis数据持久化
  2. **并发能力问题:**单节点Redis并发能力虽然不错,但是无法满足如618这样的高并发场景
    解决方案:搭建主从集群,实现读写分离
  3. 故障回复问题:如果Redis宕机,则服务不可用,需要一种自动的故障恢复手段
    解决方案:利用Redis哨兵,实现健康检测喝自动恢复
  4. 存储能力问题:Redis基于内存,单节点能存储的数据量难以满足海量数据需求
    解决方案:搭建分片集群,利用插槽机制实现动态扩容

Redis持久化

持久化存在两种方式:RDB 和 AOF

RDB持久化

RDB全称Redis DataBase Backup file(Redis数据备份文件),也被叫做Redis数据快照,简单的来说九十八内存中的所有数据都记录到磁盘中,党Redis实例故障重启后,从磁盘读取快照文件,恢复数据

快照文件称为RDB文件,默认保存在当前运行目录。

使用方法:

  1. 在客户端中使用save命令:由Redis主进程来执行RDB,会阻塞所有命令,如果数据量比较大,则消耗的时间比较长
  2. bgsave:开启子进程执行RDB,避免主进程受到影响
  3. Redis再停机的时候会自动执行一次RDB
RDB自动触发机制

RDB自动触发机制,需要在其配置文件中进行查看

image-20230213144304144

可以得知,如果再900秒内修改一次或者在300s内修改超过10次或者60秒内修改超过10000次,则自动进行一个保存,执行bgsave

RDB其他配置

image-20230213144748436

rdbcompression:保存的时候是否要进行一个压缩,默认为yes;如果进行了压缩就会消耗cup,建议关闭,存储空间占用大比较好解决

dbfilename:文件存储的名字

dir:标识存储的路径

rdbchecksum:默认开启,表示在存储快照后,redis使用CRC64算法来进行数据校验。这个操作会增加10%的性能消耗。如果希望获取最大的性能提升,改为no即关闭了此功能。

RDB的fork原理

bgsave开始时会fork主进程得到子进程,紫禁城共享主进程大的内存数据,完成fork后读取内存数据并写入RDB文件中

image-20230213150018692

fork采用的是copy-on-write技术:

  • 挡住进程执行读操作时,访问共享内存;
  • 当主进程执行写操作时,则会则会拷贝一份数据执行写操作。
RDB方式的流程
  • fork主进程得到一个子进程,共享内存空间
  • 紫禁城读取内存数据并写入新的RDB文件
  • 用心RDB文件替换旧的RDB文件
RDB的缺点

RDB执行间隔时间长,两次RDB直接写入数据有丢失的风险

fork紫禁城、压缩、写出RDB文件都比较耗时

AOF持久化

AOF全程Append Only File(追加文件)。Redis处理的每一个写命令都会记录在AOF,可以看作是命令日志文件。

AOF默认为关闭的,需要在配置文件中开启,此外,通过appendfilename可以指定文件的名称

image-20230213150654271

AOF的命令记录频率:

image-20230213150958968

由配置文件可知,存在三种模式:

no:写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓存写入到磁盘,速度最快

everysec:写命令执行完先放入AOF,然后标识每隔一秒将缓冲区的数据写入到AOF文件中默认方案

always: 标识每执行一次写命令,立即记录到AOF文件中,速度慢,但是最安全

配置项刷机时机优点缺点
always同步刷盘可靠性最高,几乎不会丢失数据性能影响比较大
everysec每秒刷机性能适中最多丢失1s内的数据
no操作系统控制性能最好可靠性较差,可能丢失大量数据
AOF重写机制:

因为AOF记录的是命令,导致AOF文件会比RDB文件大得多。而且AOP会记录对同一个key的多次写操作,但只有最后一次写操作才有意义。

可以通过bgrewriteaof命令,可以让AOF执行重写功能,用最傻送的命令达到相同的效果

Redis也会在自动触发阈值的时候自动重写AOF文件,阈值也可以在Redis.conf中配置

image-20230213152635227

auto-aof-rewrite-percentage:AOF文件比上次文件增长超过了多少百分比则触发重写

auto-aof-rewrite-min-size:AOF文件体积最小多大以上触发重写

AOF与RDB的比较

AOF与RDB各有各的优点缺点,如果对数据安全性要求比较高,在实际开发中往往会两种结合使用

RDBAOF
持久化方式定时对整个内存做快照记录每一次执行的命令
数据完整性不完整,两次备份之间会丢失相对完整,取决于刷盘策略
文件大小会有压缩,文件体积小记录命令,所以文件体积比较大
宕机恢复速度很快
数据恢复优先级高,因为数据完整性更高
系统资源占用高,大量CPU和内存消耗低,主要是磁盘IO资源,单AOF重写时会占用大量CPU和内存资源
使用场景可以容忍数分钟的数据丢失,追求更快的启动速度对数据安全性要求比较高的比较常见

Redis主从

单点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离

image-20230213153741543

数据同步原理

全量同步

主从第一次同步是全量同步:

image-20230213155435761

  • slave节点请求进行增量同步
  • master节点判断replication id是否一致,发现不一致,拒绝增量同步,进行全量同步
  • master将完整内存数据生成RDB文件(bgsave),发送RDB文件道slave
  • slave清空本地数据,加载master的RDB文件
  • master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave
  • slave执行接收到的命令,保持与master之间的同步
master如何判断slave是不是第一来同部数据集?
  • Replication ID:简称replid,是数据集的标记,id一只则说明是统一数据及,每个master都有唯一的replid,slave则会继承master界的点replid

  • offset:偏移量,随着记录的repl_baklog中的数据增多而捉逐渐增大slave完成同步时也会记录当前同步的offset,如果slave的offset小于master的offset,则说明slave数据落后于master,需要进行更新。

因此slave做数据同步,必须向master声明自己的replid和offset,master才可以判断到底需要同步哪些数据。

如何判断是否为第一次来?

根据replication id,如果发现ID不一致,则表明为第一次来,那么就会想slave返回主节点的replication id和offset,同时在进行一个全量同步。

增量同步

主从第一次同步时全量同步,但是如果slave重启后(无论是否正常重启),此外,slave 能够在repl_baklog中找到offset,则执行增量同步

image-20230213161712127

repl_baklog大小是有上限的(类似一个循环队列,相当于一个环),写满了就会覆盖最早的数据,如果slave断开时间过久,导致尚未备份的数据被覆盖,则无法基于log进行增量同步,只能再次进行全量同步

优化
  • 在master中配置repl-diskless-sync yes 启用无磁盘复制,避免全量同步时的磁盘ID。如果开启这个选项,建议网络性能比较高但是磁盘读写能力比较差

  • 控制Redis单节点的上线,单节点的上线不要太大,减少RDB导致的过多磁盘IO

  • 适当提高repl_baklog的大小,发现slave宕机后尽快实现故障恢复,尽可能避免全量同步

  • 限制一个master上的slave节点数量,如果实在是太多的slave,则可以使用主-从-从链式结构,减少master压力

image-20230213162553079

Redis哨兵

作用

哨兵(sentinel)机制用于自动故障恢复。

  • 监控:sentinel 会不断检查master和slave是否按照预期工作
  • 自动故障恢复:如果master故障,sentinel会将一个slave提升为master,当故障实例恢复后也以新的master为主
  • 通知:sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将新消息推送给Redis客户端

image-20230213163411644

服务状态监控机制

sentinel基于心跳机制检测服务状态,每隔一秒相机群的每个实例发送ping命令:

  • 主观下线:如果sentinel节点发现某个实力未在规定时间相应,则认为该实例主观下线
  • 客观下线:若超过指定数量(quorum)的sentinel都热瓦内该实例主观下线,那该实例客观下线,quorum值最好超过sentinel实例数量的一半

image-20230213163839455

选举新的master

一旦master发生故障,sentinel需要在slave中选择一个作为新的master,选择依据是:

  • 首先会判断slave节点与master节点断开时间的长短,如果超过指定值(down-after-milliseconds*10)则会排除该slave节点
  • 然后判断slave节点的slave-priority值,越小优先级越高,如果为0则表示用不参与选举,默认值为100
  • 如果slave-priority一样,则判断slave节点的offset值,越大说明越新,优先级越高
  • 最后是判断slave节点的运行id,学校优先级越高
如何实现故障转移

当选中了一个slave为新的master后,故障转移步骤:

  • sentinel给备选的slave节点发送slave no one命令,让该节点成为master
  • sentinel给所有其他slave发送slaveof 新masterIP 新master端口号 命令让slave称为新master的从节点,开始从新的master上同步数据
  • 最后sentinel将故障主节点标记为slave,党故障节点恢复后会自动成为新master的slave节点(修改了他的配置文件)

哨兵集群配置

这里我们在默认的sentinel.conf文件的基础上进行修改,该文件在解压redis的地方

对应的服务如下:

IP端口号角色
192.168.18.1326379master
192.168.18.1336379slave
192.168.18.1346379slave
192.168.18.13226379sentinel
192.168.18.13326379sentinel
192.168.18.13426379sentinel

配置文件修改:

# 最后面的2 标识数量超过2时认为主观下线,从而进行客观下线
sentinel monitor mymaster 192.168.18.132 6379 2
# 工作目录
dir "/opt/module/redis/bin/tmp"

随后直接启动sentinel启动指令./sentinel sentinel.conf,启动之后即可看到

image-20230213190308609

此时我们将主节点【192.168.18.132】进程直接结束来模拟宕机,结果如下

image-20230213192734760

从图中可知哨兵在检测道132宕机后先选出来了一个leader,随后在进行一个投票,最后三票通过选择了133这台机器,所有到最后master就转换到了133,我们在133上进行一个查看:

image-20230213193045751

我们重启一下132节点

image-20230213193204901

可见已经被修改成了133的从节点了

RedisTemplate的哨兵模式

pom文件

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.rover12421</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>
    </dependencies>

yml文件:

logging:
  level:
    io.lettuce.core: debug
spring:
  redis:
    sentinel:
      master: mymaster
      nodes:
        - 192.168.18.132:26379
        - 192.168.18.133:26379
        - 192.168.18.134:26379

Redis配置类:

package com.wxk.redistest;

import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import io.lettuce.core.ReadFrom;
import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @author wxk
 */
@Configuration
@EnableCaching
public class RedisConf {
    //用于实现哨兵模式
    @Bean
    public LettuceClientConfigurationBuilderCustomizer lettuceClientConfigurationBuilderCustomizer(){
        return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
    }
   
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);

        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
        FastJsonConfig fastJsonConfig = fastJsonRedisSerializer.getFastJsonConfig();
        SerializerFeature[] serializerFeatures = new SerializerFeature[] {SerializerFeature.WriteDateUseDateFormat, SerializerFeature.WriteMapNullValue};
        fastJsonConfig.setSerializerFeatures(serializerFeatures);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
        redisTemplate.setValueSerializer(fastJsonRedisSerializer);
        redisTemplate.setEnableTransactionSupport(true);

        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }
}

Controller类

@RestController
public class Controller {


    @Resource
    RedisTemplate redisTemplate;
    @GetMapping("/test")
    public String get(String key){
        return  redisTemplate.opsForValue().get(key)+"";
    }
    @GetMapping("/set")
    public String set(String key,String value){
        redisTemplate.opsForValue().set(key,value);
        return "ok";
    }
}

我们直接创建一个新的key

url如下:localhost:8080/set?key=time&value=time

获取key:localhost:8080/test?key=time

结果如下:

image-20230213203709869

我们查看控制台:

image-20230213203816334

可以看到从三个哨兵中选取了132的哨兵,随后获取相应的信息,由于第一步是写操作,写操作需要主节点来进行,所以最后选中了主节点:

image-20230213204513173

Redis分片集群

分片集群结构

主从和哨兵可以解决高可用、高并发读的问题,但是依然有两个问题没有解决:

  • 海量数据存储问题
  • 高并发写的问题

使用分片集群可以解决上述问题

分片集群的特征

  • 集群中可以有多个master,每个master保存不同的数据
  • 每个master都可以由多个slave节点
  • master之间通过ping监控不辞健康状态
  • 客户端请求可以访问集群任意节点,最终都会被转发到正确的节点

image-20230213205001362

散列插槽

Redis会把每一个master节点映射到0~16383共16384哥插槽(hash slot)上,查看集群信息是就能看到

数据key不适于节点绑定的,而是与插槽绑定的。redis会根据key的有效部分计算插槽值,分为两种情况:

  • key中包含"{}“,且”{}“中至少包含一个字符,”{}"中的部分是有效部分
  • key中不包含"{}",整个key都是有效部分

计算方式是利用CRC16算法

Redis如何判断某个key应该在哪个实例?
  • 将16384个插槽分配到不同的实例
  • 根据key的有效部分计算哈希值,对16384进行取余
  • 余数作为插槽,寻找插槽所在的实例即可
如何将同一类数据固定到同意Redis实例中?

将这一类数据使用相同的有效部分,例如key都可以以{wxk}作为前缀

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值