Redis事务+watch+RDB+AOF+发布订阅+主从复制+哨兵模式+缓存穿透和雪崩及解决方案+使用watch实现秒杀相关

5 篇文章 0 订阅

redis高级

redis事务

redis事务的本质:一组命令的集合,一个事务中所有命令都会被序列化,事务执行的过程中,会按照顺序执行!

一次性,顺序性,排他性,执行命令!

redis单条命令是保证原子性的,但是事务不保证原子性。

redis事务没有隔离性

redis事务中,所有命令不是直接执行,只有发起执行命令的时候才会执行。

事务执行三个阶段:

  • 开启事务(multi)
  • 命令入队(…)
  • 执行,取消事务(exec,discard)

一组事务执行或者取消后,就已经结束了,下次使用必须要从开启事务开始

例子

# 开启事务
multi

# 命令入列
set key1 k1
set key2 k2
get key1

# 事务执行/取消事务
exec/discard

监控 watch实现redis乐观锁

锁类型:

  • 悲观锁

效率低下,做什么都加锁

  • 乐观锁

效率高,视情况加锁

获取version,修改的时候比较version

模拟一个场景:a监听某个key并且开启了事务没有执行,b事务也在监听这个key,b中执行了事务,那么a未执行的事务将不会执行成功。

要注意:在事务中是不支持watch监听key的,所以要在事务的外面执行,乐观锁一定要在事务开启之前加上(watch key)

a中,监听key且事务中命令入列,先不执行

set key 1000
watch key
MULTI
DECRBY key 200
get key

新开一个redis连接,b中,对这个key进行修改

DECRBY key 200

a中,再执行

exec

结果为

(nil)

例子说明:A和B秒杀商品,现在只剩一件了,A在浏览这个商品准备下单,B直接下单了,这时候A再下单发现商品已经没了。

Java使用原生Jedis实现事务

是官方推荐的java连接redis的开发工具,导入依赖,jedis

依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.6.3</version>
</dependency>

简单实现

public static void main(String[] args) {
    Jedis j=new Jedis("127.0.0.1",6379);
    Transaction t = j.multi();
    try{
        t.set("user","1");
        t.exec();
    } catch(Exception e){
        t.discard();
    } finally {
        j.close();
    }
}

redis.conf配置文件详解

  • 对unit单位大小写,不敏感
  • 配置文件中可用include引入其他配置文件
  • bind 绑定ip,指定哪个ip可以访问redis,注释掉后表示所有ip可访问
  • protected-mode保护模式,值yes/no,是否开启保护模式(开启后不支持远程访问)
  • daemonize yes ,取值yes/no,是否以守护进程方式运行
  • pidfile /var/run/redis_6379.pid,如果以守护进程方式运行,则需要指定pidfile
  • loglevel notice日志级别,默认notice,生产相关,一般不用修改
  • logfile ,日志输出文件路径
  • 快照

900s内,执行1次修改操作,进行持久化,其他策略略同

save 900 1

save 300 10

save 60 10000

在规定时间内,执行多少次操作,会把数据持久化到.rdb,.aof文件中

  • rdbcompression yes rdb文件进行持久化(默认,会消耗一些cpu资源)
  • dbfilename dump.rdb rdp持久化文件名
  • requirepass 111111 设置redis密码
  • maxmemory-policy 内存到上限之后处理策略
  • appendonly no 持久化AOF模式,默认no,默认使用rdb持久化方式,足够
  • appendfilename 持久化文件名
  • appendfsync everysec 每秒执行一次持久化数据
  • no-appendfsync-on-rewrite no,重写机制

redis持久化之RDB(Redis DataBase)

执行原理

redis会单独创建(fork)一个子进程来进行数据的持久化,会先将数据写入到一个临时文件中,临时文件持久化结束后,再去替换掉上次持久化的文件。整个进程中,主进程不进行任何io操作,确保了极高的性能,缺点是最后一次持久化可能会造成数据丢失。

触发机制

触发redis持久化机制后,会产生rdb文件

  • 配置文件中,满足save规则时,触发
  • flushall命令,清除所有数据库数据时触发
  • 关闭redis服务时,触发rdb
  • 手动bgsave命令

恢复持久化数据

只需要把备份文件dump.rdb放入redis.conf配置文件同级目录下即可

redis启动时会去读取配置文件,由于配置文件指定了dump.rdb文件路径,redis会去自动扫描dump.rdb中的数据,并且进行恢复。

RDB优缺点

优:

  • 适用于大量数据的恢复
  • 对数据完整性要求不高

缺:

  • redis宕机,可能导致最后一次修改的数据丢失
  • fork进程执行的时候,可能占用一定的空间

持久化之AOF(Append Only File)

redis默认使用rdb的持久化方式,但是如果aof模式开启了,那么会去同时使用rdb和aof作为持久化,但是数据恢复时只会执行aof来恢复数据(aof模式的数据完整性更高)

执行原理

实现方式,fork一个子进程,去记录每次执行的命令追加到aof文件中,恢复数据时,直接执行aof文件来恢复数据

appendonly yes 即可开启aof模式

appendfilename appendonly.aof 指定文件名

当aof文件损坏时

假如aof文件损坏了(模拟:在aof文件中随便加了一些字符),那么启动redis时就会失败,由于aof文件损坏,redis启动时读取aof文件时出现问题,造成无法启动redis服务

解决方案:

使用redis目录下redis-check-aof工具修复aof文件

redis-check-aof --fix appendonly.aof

AOF优缺点

优:

  • 每一次修改都会同步(命令记录入aof文件),数据完整性更高
  • 每秒同步一次,只丢失一秒的数据
  • 不开启时,从不同步,效率最高

缺:

  • aof文件远大于rdb文件,修复速度也比rdb文件慢很多
  • aof运行效率比rdb慢

redis发布订阅

redis发布订阅(pub/sub),是一种消息通信模式,发送者发送(pub)消息,订阅者订阅(sub)消息

redis客户端可以订阅任意数量的频道

三个角色

  • 消息生产者
  • 频道
  • 消息订阅者

订阅者订阅频道,订阅后会自动监听

# 订阅一个频道wlhme
subscribe wlhme

发布者发布消息到频道

publish wlhme helloworld

Redis主从复制

指将一台服务器的数据复制给其他服务器,前者是主机,后者从机,数据复制是单向的,只能是从主机到从机。master以写为主,slave以读为主。

主从复制,读写分离。减缓服务器压力,一主二从。

作用:

  • 数据冗余,主从复制实现了数据备份
  • 故障恢复,主节点出问题,从节点可以提供服务,实现快速故障修复
  • 负载均衡,主从复制基础上,配合读写分离,分担服务器负载,提高redis并发量
  • 高可用基石:主从复制是哨兵模式和集群的基础

单台redis服务器,最大使用内存不应超过20G,超了就整从机。

默认情况下,每台redis服务器都是主节点,可以修改配置文件变成从节点。一个主节点可以有多个从节点,但是一个从节点只能有一个主节点。

Redis集群

环境配置:只需要配置从节点,主节点不用配置。

# 查看集群配置信息
info replication

从节点如何配置?

  • 方案一:使用命令方式配置(不是永久的)

    # 成为本机6379端口服务的从机(认主)
    slaveof 127.0.0.1 6379
    

    要点:

    如果主节点存在密码,那么需要在从机配置文件中中加上masterauth 主密码,配置主节点的密码才可以。

  • 方案二:直接在从节点配置文件中加上主节点的配置

    replicaof 127.0.0.1 6379
    masterauth 111111
    

    细节

    主写从读。主节点存入数据后,复制数据到从节点,从节点可以读到数据。

    从机只能读数据,不能写入数据。

    主节点宕机后,从节点仍然可以提供服务,过一段时间主节点上线,从节点仍然可以和主节点保持连接(获取到主机写的数据)。

    如果是使用命令行配置的从机,那么从机挂掉后再启动就会变成主机(最好配置在文件中,可以永久保存),当从机再次连接到主机时,主机的数据仍会同步到从机中,仍然可以读取到主机中的数据。

主从复制的实现原理

slave启动服务,连接到master后会发送一个sync同步命令。master接到命令,启动后台存盘进程,同时收集所有用于修改数据的命令,后台进程执行完毕以后,master将整个数据文件发送到slave,完成同步。

  • 全量复制

slave接收到master的数据文件后,直接同步数据加载到内存中。

  • 增量复制

master继续将修改数据命令依次发送给slave,实际上就是,master和slave保持连接时,master修改一条数据,slave同步一次数据。

要点:

只要是重新连接master,那么全量复制将自动执行。

哨兵模式

可参考文章

Redis的哨兵模式

手动谋权篡位过于费时费力,采用自动选举老大的哨兵模式。

哨兵模式是一种特殊模式,redis提供了哨兵模式的命令,哨兵作为独立的进程执行,原理:哨兵通过发送命令到redis服务器,等待redis服务器响应,从而监控运行的多个redis实例。

当主节点挂掉后,哨兵1先发现主服务不可用(主观下线),后面的哨兵也发现了,并且达到一定数量后,哨兵们会进行投票选举。选举完成后,通过发布订阅模式,让各个哨兵通知自己监控的从节点切换主节点(换老大),这个过程是客观下线。

实现

在redis目录下,创建sentinel.conf配置文件

内容

# 监控本机6379的redis,投票机制是1
sentinel monitor red1 127.0.0.1 6379 1

启动哨兵

redis-sentinel /sentinel.conf

当我们的redis主节点挂了之后,哨兵会选举老大,使之成为主节点。当原主节点恢复了,他也只能当从机!!!

优点

  • 基于主从复制模式,所有主从配置优点它都有
  • 主从可以切换,故障可以转移,可用性好
  • 主从模式的升级,手动改为自动选举老大

缺点

  • redis不好在线扩容,集群容量一旦达到上限,在线扩容非常麻烦
  • 实现哨兵模式配置很麻烦,里面有很多选择

缓存穿透和雪崩

缓存穿透(数据找不到)

用户发起请求查询一个数据,发现redis中没有,也就是缓存没有命中,于是去mysql中查询,但是持久化数据库也没有数据,于是本次查询失败。当用户量很多,都去发起请求,都没有缓存命中,都去请求持久化数据库,给持久层数据库造成很大的压力,就是缓存穿透。

解决方案

  • 布隆过滤器
  • 缓存空对象

当缓存未命中,且持久层数据库也无数据,就在缓存中加一个空对象返回

缓存击穿(热点key过期)

指一个key非常热点,扛着大量的请求压力,并发集中对一个点进行访问。放这个key在失效的瞬间,持续的大并发击穿缓存,直接访问持久层数据库,给数据库造成很大压力。

解决方案

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

分布式锁,保证每个key每次只有一个线程去进行访问

缓存雪崩(大量key同时过期)

指在某一时间段,缓存key集中失效,或者redis突然宕机。

所有的访问压力全部落在了持久层数据库的身上,造成很大的访问压力。

解决方案

  • redis集群
  • 限流降级

方案思想:redis的大部分key失效后,进行加锁或者队列,限制持久层数据库的访问线程数量

  • 数据预热

正式部署之前,先访问一遍,把可能大量访问的数据加载到缓存中,在大量访问前,手动加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

使用watch实现秒杀相关业务代码

参考自redis的watch机制及应用

String redisKey = "miaosha";
ExecutorService executorService = Executors.newFixedThreadPool(20);//20个线程
try {//初始化
    Jedis jedis = new Jedis("127.0.0.1", 6379);
    // 初始值
    jedis.set(redisKey, "0");
    jedis.close();
} catch (Exception e) {
    e.printStackTrace();
}

for (int i = 0; i < 1000; i++) {//尝试1000次
    executorService.execute(() -> {
        try (Jedis jedis1 = new Jedis("127.0.0.1", 6379)) {
            jedis1.watch(redisKey);
            String redisValue = jedis1.get(redisKey);
            int valInteger = Integer.valueOf(redisValue);
            String userInfo = UUID.randomUUID().toString();
            // 没有秒完
            if (valInteger < 20) {//redisKey
                Transaction tx = jedis1.multi();//开启事务
                tx.incr(redisKey);//自增
                List list = tx.exec();//提交事务,如果返回nil则说明执行失败,因为我watch了的,只要执行失败,则
                // 进来发现东西还有,秒杀成功
                if (list != null && list.size() > 0) {
                    System.out.println("用户:" + userInfo + ",秒杀成功!当前成功人数:" + (valInteger + 1));
                } else {//执行结果不是OK,说明被修改了,被别人抢了
                    System.out.println("用户:" + userInfo + ",秒杀失败");
                }
            } else {//东西秒完了
                System.out.println("已经有20人秒杀成功,秒杀结束");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }//关闭redis
    });
}
executorService.shutdown();//关闭线程池

参考

哨兵模式可参考Redis的哨兵模式
布隆过滤器参考SpringBoot+Redis布隆过滤器防恶意流量击穿缓存的正确姿势
redis分布式锁redis分布式锁的实现(setNx命令和Lua脚本)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值