Redis学习笔记

安装

环境
CentOS 7

下载redis

https://redis.io/download
我下的是redis-7.2.4.tar.gz

解压

复制压缩包到/opt,解压 tar -zxvf redis-7.2.4.tar.gz,重命名解压文件夹为redis

执行编译环境命令

没安装过基本的编译工具和库的先安装,否则make执行不了
yum -y install gcc automake autoconf libtool make

编译安装

进入解压的redis目录,执行 make 编译命令

    LINK redis-server
    INSTALL redis-sentinel
    CC redis-cli.o
    CC cli_common.o
    LINK redis-cli
    CC redis-benchmark.o
    LINK redis-benchmark
    INSTALL redis-check-rdb
    INSTALL redis-check-aof

Hint: It's a good idea to run 'make test' ;)

出现这样后,执行make install 安装

备份 redis.conf

拷贝一份redis.conf到其他目录
cp /opt/redis/redis.conf /opt/myredis

配置修改

bind 0.0.0.0
protected-mode no
daemonize yes
  1. bind 0.0.0.0:这个配置项指示 Redis 服务器监听所有网络接口上的连接请求。通过设置为 0.0.0.0,Redis 将会在所有可用的网络接口上等待连接,而不是仅限于特定的 IP 地址或回环接口。
  2. protected-mode no:这个配置项关闭了 Redis 的保护模式。在保护模式下,Redis 只允许来自本地主机的连接请求,禁止外部连接。通过设置为 no,我们允许了外部连接到 Redis 服务器。
  3. daemonize yes:这个配置项告诉 Redis 是否以守护进程(daemon)模式运行。将其设置为 yes 会使 Redis 在后台默默地运行,而不是在终端中显示输出。

启动服务端

redis.conf在解压后的redis目录下,可以拷贝到其他地方,或者直接改也行

redis-server /opt/myredis/redis.conf

启动客户端

redis-cli
127.0.0.1:6379> 

指定端口启动

redis-cli -p 6379

关闭服务端

127.0.0.1:6379> SHUTDOWN
not connected>

在输入exit 退出redis命令界面

not connected>exit

重启

我是杀死进程,在启动的,其他方式还没试过

1.事务

1.1 定义

Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
Redis事务的主要作用就是串联多个命令防止别的命令插队。

1.2 命令 — Multi、Exec、discard

从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。
组队的过程中可以通过discard来放弃组队。
在这里插入图片描述
组队成功,提交成功
在这里插入图片描述
组队阶段报错,提交失败
语法不正确,放弃提交

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set b1 v1
QUEUED
127.0.0.1:6379> set b2 v2
QUEUED
127.0.0.1:6379> set b3
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.

组队成功,提交有成功有失败情况
语法正确,成功的继续提交,失败就报错,没有回滚

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set c1 v2
QUEUED
127.0.0.1:6379> incr c1
QUEUED
127.0.0.1:6379> set c2 v2
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK

1.3 事务的错误处理

组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消。
在这里插入图片描述
如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。

在这里插入图片描述

1.4 事务冲突的问题

1.4.1 悲观锁

在这里插入图片描述

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

1.4.2 乐观锁

在这里插入图片描述
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。

1.4.3 WATCH key [key …] — 乐观锁实现

Redis watch命令——监控事务,这篇可以看看

WATCH命令可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行。监控一直持续到EXEC命令(事务中的命令是在EXEC之后才执行的,所以在MULTI命令后可以修改WATCH监控的键值)

Redis 参考了多线程中使用的 CAS(比较与交换,Compare And Swap)去执行的。在数据高并发环境的操作中,我们把这样的一个机制称为乐观锁
在这里插入图片描述

例子
1.首先都开启监控,在开启事务
窗口一**

127.0.0.1:6379> get balance
"100"
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> multi
OK

窗口二

127.0.0.1:6379> get balance
"100"
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> multi
OK

2.在窗口一进行加操作,成功

127.0.0.1:6379> incrby balance 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 120

3.接着在窗口二也进行加操作,会发现失败,因为原本的balance已经改变了,则事务被打断,因为 balance 版本号不同

127.0.0.1:6379> incrby balance 20
QUEUED
127.0.0.1:6379> exec
(nil)

1.5 Redis事务三特性

单独的隔离操作

  • 事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

没有隔离级别的概念

  • 队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行

不保证原子性

  • 事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚

2.秒杀案例

2.1 代码

新建一个普通web项目
web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
		  http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <description></description>
        <display-name>doseckill</display-name>
        <servlet-name>doseckill</servlet-name>
        <servlet-class>com.atguigu.SecKillServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>doseckill</servlet-name>
        <url-pattern>/doseckill</url-pattern>
    </servlet-mapping>
</web-app>

index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
</head>
<body>
<h1>iPhone 13 Pro !!! 1元秒杀!!!
</h1>


<form id="msform" action="${pageContext.request.contextPath}/doseckill" enctype="application/x-www-form-urlencoded">
    <input type="hidden" id="prodid" name="prodid" value="0101">
    <input type="button" id="miaosha_btn" name="seckill_btn" value="秒杀点我"/>
</form>

</body>
<script type="text/javascript" src="${pageContext.request.contextPath}/script/jquery/jquery-3.1.0.js"></script>
<script type="text/javascript">
    $(function () {
        $("#miaosha_btn").click(function () {
            var url = $("#msform").attr("action");
            $.post(url, $("#msform").serialize(), function (data) {
                if (data == "false") {
                    alert("抢光了");
                    $("#miaosha_btn").attr("disabled", true);
                }
            });
        })
    })
</script>
</html>
public class SecKillServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    public SecKillServlet() {
        super();
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        String userid = new Random().nextInt(50000) + "";
        String prodid = request.getParameter("prodid");

        boolean isSuccess=SecKill_redis.doSecKill(userid,prodid);
        //boolean isSuccess = SecKill_redisByScript.doSecKill(userid, prodid);
        response.getWriter().print(isSuccess);
    }

}
public class JedisPoolUtil {
    private static volatile JedisPool jedisPool = null;

    private JedisPoolUtil() {
    }

    public static JedisPool getJedisPoolInstance() {
        if (null == jedisPool) {
            synchronized (JedisPoolUtil.class) {
                if (null == jedisPool) {
                    JedisPoolConfig poolConfig = new JedisPoolConfig();
                    poolConfig.setMaxTotal(200);
                    poolConfig.setMaxIdle(32);
                    poolConfig.setMaxWaitMillis(100 * 1000);
                    poolConfig.setBlockWhenExhausted(true);
                    poolConfig.setTestOnBorrow(true);  // ping  PONG

                    jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379, 60000);
                }
            }
        }
        return jedisPool;
    }

    public static void release(JedisPool jedisPool, Jedis jedis) {
        if (null != jedis) {
            jedis.close();
        }
    }

}
public class SecKill_redis {

    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.44.168", 6379);
        System.out.println(jedis.ping());
        jedis.close();
    }

    //秒杀过程
    public static boolean doSecKill(String uid, String prodid) {
        //1 uid和prodid非空判断
        if (uid == null || prodid == null) {
            return false;
        }

        //2 连接redis
        //Jedis jedis = new Jedis("192.168.44.168",6379);
        //通过连接池得到jedis对象
        JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
        Jedis jedis = jedisPoolInstance.getResource();

        //3 拼接key
        // 3.1 库存key
        String kcKey = "sk:" + prodid + ":qt";
        // 3.2 秒杀成功用户key
        String userKey = "sk:" + prodid + ":user";

        //监视库存
        jedis.watch(kcKey);

        //4 获取库存,如果库存null,秒杀还没有开始
        String kc = jedis.get(kcKey);
        if (kc == null) {
            System.out.println("please wait");
            jedis.close();
            return false;
        }

        // 5 判断用户是否重复秒杀操作
        if (jedis.sismember(userKey, uid)) {
            System.out.println("success,Can't repeat");
            jedis.close();
            return false;
        }

        //6 判断如果商品数量,库存数量小于1,秒杀结束
        if (Integer.parseInt(kc) <= 0) {
            System.out.println("over");
            jedis.close();
            return false;
        }

        //7 秒杀过程
        //使用事务
        Transaction multi = jedis.multi();

        //组队操作
        multi.decr(kcKey);
        multi.sadd(userKey, uid);

        //执行
        List<Object> results = multi.exec();

        if (results == null || results.size() == 0) {
            System.out.println("fail....");
            jedis.close();
            return false;
        }

        //7.1 库存-1
//        jedis.decr(kcKey);
//        //7.2 把秒杀成功用户添加清单里面
//        jedis.sadd(userKey,uid);

        System.out.println("success1..");
        jedis.close();
        return true;
    }
}

用 jmeter 测试 正常执行,不会超卖。

2.2 库存遗留问题

但是,已经秒光,可是还有库存。原因,就是乐观锁导致很多请求都失败。先点的没秒到,后点的可能秒到了。

将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数。提升性能。
LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。

但是注意redis的lua脚本功能,只有在Redis 2.6以上的版本才可以使用。
利用lua脚本淘汰用户,解决超卖问题。

redis 2.6版本以后,通过lua脚本解决争抢问题,实际上是redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题。

在这里插入图片描述

改用下面lua脚本解决问题,库存遗留问题正常清空;

public class SecKill_redisByScript {

    private static final org.slf4j.Logger logger = LoggerFactory.getLogger(SecKill_redisByScript.class);

    public static void main(String[] args) {
        JedisPool jedispool = JedisPoolUtil.getJedisPoolInstance();

        Jedis jedis = jedispool.getResource();
        System.out.println(jedis.ping());

        Set<HostAndPort> set = new HashSet<HostAndPort>();

        //	doSecKill("201","sk:0101");
    }

    static String secKillScript = "local userid=KEYS[1];\r\n" +
            "local prodid=KEYS[2];\r\n" +
            "local qtkey='sk:'..prodid..\":qt\";\r\n" +
            "local usersKey='sk:'..prodid..\":usr\";\r\n" +
            "local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" +
            "if tonumber(userExists)==1 then \r\n" +
            "   return 2;\r\n" +
            "end\r\n" +
            "local num= redis.call(\"get\" ,qtkey);\r\n" +
            "if tonumber(num)<=0 then \r\n" +
            "   return 0;\r\n" +
            "else \r\n" +
            "   redis.call(\"decr\",qtkey);\r\n" +
            "   redis.call(\"sadd\",usersKey,userid);\r\n" +
            "end\r\n" +
            "return 1";

    static String secKillScript2 =
            "local userExists=redis.call(\"sismember\",\"{sk}:0101:usr\",userid);\r\n" +
                    " return 1";

    public static boolean doSecKill(String uid, String prodid) throws IOException {

        JedisPool jedispool = JedisPoolUtil.getJedisPoolInstance();
        Jedis jedis = jedispool.getResource();

        //String sha1=  .secKillScript;
        String sha1 = jedis.scriptLoad(secKillScript);
        Object result = jedis.evalsha(sha1, 2, uid, prodid);

        String reString = String.valueOf(result);
        if ("0".equals(reString)) {
            System.err.println("已抢空!!");
        } else if ("1".equals(reString)) {
            System.out.println("抢购成功!!!!");
        } else if ("2".equals(reString)) {
            System.err.println("该用户已抢过!!");
        } else {
            System.err.println("抢购异常!!");
        }
        jedis.close();
        return true;
    }
}

3.持久化之RDB

3.1 是什么

在指定的时间间隔内将内存中的数据集快照写入磁盘, 也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里

3.2 备份是如何执行的

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。 整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能 如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。

3.3 Fork

  • Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等) 数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程
  • 在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,Linux中引入了“写时复制技术”
  • 一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。

3.4 RDB持久化流程

在这里插入图片描述

3.5 配置文件属性

3.5.1 dump.rdb文件

win中打开redis.windows-service.conf
在redis.conf中配置持久化的文件名称,默认为dump.rdb

# The filename where to dump the DB
dbfilename dump.rdb

3.5.2 存储位置

rdb文件的保存路径,也可以修改。默认为Redis启动时命令行所在的目录下

# The working directory.
#
# The DB will be written inside this directory, with the filename specified
# above using the 'dbfilename' configuration directive.
#
# The Append Only File will also be created inside this directory.
#
# Note that you must specify a directory here, not a file name.
dir ./

3.5.3 磁盘不够是否关掉写操作

当Redis无法写入磁盘的话,直接关掉Redis的写操作。推荐yes(默认).

# By default Redis will stop accepting writes if RDB snapshots are enabled
# (at least one save point) and the latest background save failed.
# This will make the user aware (in a hard way) that data is not persisting
# on disk properly, otherwise chances are that no one will notice and some
# disaster will happen.
#
# If the background saving process will start working again Redis will
# automatically allow writes again.
#
# However if you have setup your proper monitoring of the Redis server
# and persistence, you may want to disable this feature so that Redis will
# continue to work as usual even if there are problems with disk,
# permissions, and so forth.
stop-writes-on-bgsave-error yes

3.5.4 是否压缩文件

对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩。
如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能。推荐yes(默认).

# Compress string objects using LZF when dump .rdb databases?
# For default that's set to 'yes' as it's almost always a win.
# If you want to save some CPU in the saving child set it to 'no' but
# the dataset will likely be bigger if you have compressible values or keys.
rdbcompression yes

3.5.5 是否检查完整性

在存储快照后,还可以让redis使用CRC64算法来进行数据校验,
但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能
推荐yes(默认).

# Since version 5 of RDB a CRC64 checksum is placed at the end of the file.
# This makes the format more resistant to corruption but there is a performance
# hit to pay (around 10%) when saving and loading RDB files, so you can disable it
# for maximum performances.
#
# RDB files created with checksum disabled have a checksum of zero that will
# tell the loading code to skip the check.
rdbchecksum yes

3.5.6 持久化时间间隔设置

格式:save 秒钟 写操作次数
RDB是整个内存的压缩过的Snapshot,RDB的数据结构,可以配置复合的快照触发条件,
默认是1分钟内改了1万次,或5分钟内改了10次,或15分钟内改了1次。
禁用
不设置save指令,或者给save传入空字符串

#
# Save the DB on disk:
#
#   save <seconds> <changes>
#
#   Will save the DB if both the given number of seconds and the given
#   number of write operations against the DB occurred.
#
#   In the example below the behaviour will be to save:
#   after 900 sec (15 min) if at least 1 key changed
#   after 300 sec (5 min) if at least 10 keys changed
#   after 60 sec if at least 10000 keys changed
#
#   Note: you can disable saving completely by commenting out all "save" lines.
#
#   It is also possible to remove all the previously configured save
#   points by adding a save directive with a single empty string argument
#   like in the following example:
#
#   save ""

save 900 1:表示900 秒内如果至少有 1 个 key 的值变化,则保存
save 300 10:表示300 秒内如果至少有 10 个 key 的值变化,则保存
save 60 10000:表示60 秒内如果至少有 10000 个 key 的值变化,则保存

如到第二个900 秒时候,会重新计算变化个数,而不是累加之前的。

3.6 优势

  • 适合大规模的数据恢复
  • 对数据完整性和一致性要求不高更适合使用
  • 节省磁盘空间
  • 恢复速度快

3.7 劣势

  • Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
  • 虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。
  • 在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改。

3.8 恢复备份

先通过配置文件查询rdb文件的目录
将*.rdb的文件拷贝到别的地方+

rdb的恢复

  • 关闭Redis
  • 先把备份的文件拷贝到工作目录下 cp dump2.rdb dump.rdb
  • 启动Redis, 备份数据会直接加载

4.持久化之AOF(Append Only File)

4.1 是什么

以日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录), 只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作

4.2 配置文件属性

# By default Redis asynchronously dumps the dataset on disk. This mode is
# good enough in many applications, but an issue with the Redis process or
# a power outage may result into a few minutes of writes lost (depending on
# the configured save points).
#
# The Append Only File is an alternative persistence mode that provides
# much better durability. For instance using the default data fsync policy
# (see later in the config file) Redis can lose just one second of writes in a
# dramatic event like a server power outage, or a single write if something
# wrong with the Redis process itself happens, but the operating system is
# still running correctly.
#
# AOF and RDB persistence can be enabled at the same time without problems.
# If the AOF is enabled on startup Redis will load the AOF, that is the file
# with the better durability guarantees.

翻译:
默认情况下,Redis将数据集异步转储到磁盘上。这种模式在许多应用程序中已经足够好了,但Redis进程出现问题或断电可能会导致几分钟的写丢失(取决于配置的保存点)。

仅追加文件是另一种持久性模式,提供了更好的持久性。例如,使用默认数据fsync策略(请参阅配置文件的后面部分),Redis可能会在服务器断电之类的重大事件中丢失一秒钟的写入,或者在Redis进程本身发生错误时丢失一次写入,但操作系统仍能正常运行。

AOF和RDB持久性可以同时启用而不会出现问题。如果在启动时启用AOF,Redis将加载AOF,即具有更好耐久性保证的文件。

4.2.1 AOF同步频率设置

appendfsync always
始终同步,每次Redis的写入都会立刻记入日志;性能较差但数据完整性比较好
appendfsync everysec
每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能丢失。
appendfsync no
redis不主动进行同步,把同步时机交给操作系统。

# The fsync() call tells the Operating System to actually write data on disk
# instead of waiting for more data in the output buffer. Some OS will really flush
# data on disk, some other OS will just try to do it ASAP.
#
# Redis supports three different modes:
#
# no: don't fsync, just let the OS flush the data when it wants. Faster.
# always: fsync after every write to the append only log. Slow, Safest.
# everysec: fsync only one time every second. Compromise.
#
# The default is "everysec", as that's usually the right compromise between
# speed and data safety. It's up to you to understand if you can relax this to
# "no" that will let the operating system flush the output buffer when
# it wants, for better performances (but if you can live with the idea of
# some data loss consider the default persistence mode that's snapshotting),
# or on the contrary, use "always" that's very slow but a bit safer than
# everysec.
#
# More details please check the following article:
# http://antirez.com/post/redis-persistence-demystified.html
#
# If unsure, use "everysec".

4.2.2 AOF默认不开启

可以在redis.conf中配置文件名称,默认为 appendonly.aof
AOF文件的保存路径,同RDB的路径一致。

4.2.3 AOF和RDB同时开启,redis听谁的?

AOF和RDB同时开启,系统默认取AOF的数据(数据不会存在丢失)

4.2.4 AOF启动/修复/恢复

AOF的备份机制和性能虽然和RDB不同, 但是备份和恢复的操作同RDB一样,都是拷贝备份文件,需要恢复时再拷贝到Redis工作目录下,启动系统即加载。

正常恢复

  • 修改默认的appendonly no,改为yes
  • 将有数据的aof文件复制一份保存到对应目录
  • 恢复:重启redis然后重新加载

异常恢复

  • 修改默认的appendonly no,改为yes
  • 如遇到AOF文件损坏,通过/usr/local/bin/redis-check-aof–fix appendonly.aof进行恢复
  • 备份被写坏的AOF文件
  • 恢复:重启redis,然后重新加载

异常恢复示例
由于用win的redis,异常恢复示例使用视频案例截图

1.正常的aof文件
在这里插入图片描述
2.加了个Hello
在这里插入图片描述
3.重启,读取aof文件内容进行加载,连接失败

在这里插入图片描述
4.修复
执行命令 redis-check-aof–fix appendonly.aof,输入y,成功修复
在这里插入图片描述
5.重启即可。

4.2.5 Rewrite压缩

就是把多条修改语句,合并成一条,如

set a a1
set b b1

转成类似这样

set a a1 b b1 
4.2.5.1 是什么

AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制, 当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩, 只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof

4.2.5.2 重写原理,如何实现重写

AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),redis4.0版本后的重写,是指上就是把rdb 的快照,以二级制的形式附在新的aof头部,作为已有的历史数据,替换掉原来的流水账操作。
no-appendfsync-on-rewrite:
如果 no-appendfsync-on-rewrite=yes ,不写入aof文件只写入缓存,用户请求不会阻塞,但是在这段时间如果宕机会丢失这段时间的缓存数据。(降低数据安全性,提高性能)
如果 no-appendfsync-on-rewrite=no, 还是会把数据往磁盘里刷,但是遇到重写操作,可能会发生阻塞。(数据安全,但是性能降低)
触发机制,何时重写
Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发
重写虽然可以节约大量磁盘空间,减少恢复时间。但是每次重写还是有一定的负担的,因此设定Redis要满足一定条件才会进行重写。
auto-aof-rewrite-percentage:设置重写的基准值,文件达到100%时开始重写(文件是原来重写后文件的2倍时触发)
auto-aof-rewrite-min-size:设置重写的基准值,最小文件64MB。达到这个值开始重写。
例如:文件达到70MB开始重写,降到50MB,下次什么时候开始重写?100MB
系统载入时或者上次重写完毕时,Redis会记录此时AOF大小,设为base_size,
如果Redis的AOF当前大小>= base_size +base_size*100% (默认)且当前大小>=64mb(默认)的情况下,Redis会对AOF进行重写。

4.2.5.3 重写流程

(1)bgrewriteaof触发重写,判断是否当前有bgsave或bgrewriteaof在运行,如果有,则等待该命令结束后再继续执行。
(2)主进程fork出子进程执行重写操作,保证主进程不会阻塞。
(3)子进程遍历redis内存中数据到临时文件,客户端的写请求同时写入aof_buf缓冲区和aof_rewrite_buf重写缓冲区保证原AOF文件完整以及新AOF文件生成期间的新的数据修改动作不会丢失。
(4)1).子进程写完新的AOF文件后,向主进程发信号,父进程更新统计信息。2).主进程把aof_rewrite_buf中的数据写入到新的AOF文件。
(5)使用新的AOF文件覆盖旧的AOF文件,完成AOF重写。

4.3 用哪个好

官方推荐两个都启用。
如果对数据不敏感,可以选单独用RDB。
不建议单独用 AOF,因为可能会出现Bug。
如果只是做纯内存缓存,可以都不用。

4.4 官网建议

RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储

AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.

Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大

只做缓存:如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.

同时开启两种持久化方式:
在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据, 因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.

RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件。那要不要只使用AOF呢?
建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份), 快速重启,而且不会有AOF可能潜在的bug,留着作为一个万一的手段。
性能建议

因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。

如果使用AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了。

代价,一是带来了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。

只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上。
默认超过原大小100%大小时重写可以改到适当的数值。

5.Redis_主从复制

主机数据更新后根据配置和策略, 自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主

5.1 能干嘛

  • 读写分离,性能扩展
  • 容灾快速恢复
    在这里插入图片描述

5.2 配置主从

百度吧,由于用的win redis搞不了,懒得用linux,就没搞了

注意点从挂了,在启动会变成主,得重新设置才会成从,数据也会恢复
而主挂了,默认从还是从,主在启动,就恢复正常

5.3 薪火相传

上一个Slave可以是下一个slave的Master,Slave同样可以接收其他 slaves的连接和同步请求,那么该slave作为了链条中下一个的master, 可以有效减轻master的写压力,去中心化降低风险。
用 slaveof
中途变更转向:会清除之前的数据,重新建立拷贝最新的
风险是一旦某个slave宕机,后面的slave都没法备份
主机挂了,从机还是从机,无法写数据了
主:6379
从:6380
从:6381
6381 设置主服务器时 设置为 slaveof 127.0.0.1 6380,即可,这样6380本身就是从,他也有从6381
在这里插入图片描述

5.4 反客为主

当一个master宕机后,后面的slave可以立刻升为master,其后面的slave不用做任何修改。
用 slaveof no one 将从机变为主机。
注意:主挂了,从并不会自动变成主,而需要手动设置,自动版为哨兵模式
在这里插入图片描述

5.5 复制原理

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

6.哨兵模式(sentinel)

6.1 是什么

反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库
在这里插入图片描述

6.2 配置哨兵

配置文件中填写 sentinel monitor mymaster 127.0.0.1 6379 1
其中mymaster为监控对象起的服务器名称, 1 为至少有多少个哨兵同意迁移的数量。

6.3 当主机挂掉,从机选举中产生新的主机

(大概10秒左右可以看到哨兵窗口日志,切换了新的主机)
哪个从机会被选举为主机呢?根据优先级别:slave-priority
原主机重启后会变为从机。
在这里插入图片描述

6.4 复制延时

由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。

6.5 过程

在这里插入图片描述
优先级在redis.conf中默认:slave-priority 100,值越小优先级越高
偏移量是指获得原主机数据最全的,最接近 主 数据的
每个redis实例启动后都会随机生成一个40位的runid

7.Redis集群

这里开始我在linux新搭建了个redis,不像上面直接在win操作了

7.1 问题

容量不够,redis如何进行扩容?
并发写操作, redis如何分摊?
另外,主从模式,薪火相传模式,主机宕机,导致ip地址发生变化,应用程序中配置需要修改对应的主机地址、端口等信息。
之前通过代理主机来解决,但是redis3.0中提供了解决方案。就是无中心化集群配置。

7.2 什么是集群

Redis 集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N。
Redis 集群通过分区(partition)来提供一定程度的可用性(availability): 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。
在这里插入图片描述
无中心化,可以互相调用,可以从任一节点进入;中心化要是代理挂了,其他就挂了。

7.3 无中心化集群,制作6个实例,7001-7006

新建一个/usr/local/redis-7.2.4/cluster/redis-template.conf,公用配置文件
cluster-announce-ip 这个不填,springboot redis配置集群连接,启动时会连接服务器内网地址导致连不上redis,启动失败
需要在cluster目录下提前创建好以下目录,直接复制输入命令:mkdir rdb pid nodes cluster-nodes logs

protected-mode no # 禁用保护模式,允许远程主机连接到 Redis 服务器,不进行保护模式下的限制。

daemonize yes # 将 Redis 服务器作为守护进程运行,即以后台方式运行 Redis 服务器。

dir /usr/local/redis-7.2.4/cluster/rdb # 指定 Redis 数据库持久化文件的存储路径为 /usr/local/redis-7.2.4/cluster/rdb。该目录将用于存储 Redis 数据库持久化文件(如 dump.rdb)以及 AOF 文件。

pidfile /usr/local/redis-7.2.4/cluster/pid # PID 文件路径

cluster-config-file /usr/local/redis-7.2.4/cluster/nodes # 集群配置文件路径

appendonly yes # 启用 AOF(Append Only File)持久化模式,即开启 AOF 日志持久化功能,用于记录所有写操作,以保证数据持久化。

cluster-enabled yes # 启用 Redis 集群模式,即将 Redis 服务器配置为集群模式,允许其成为 Redis 集群的一部分。

cluster-node-timeout 15000 # 设置 Redis 集群模式下节点之间通信的超时时间为 15000 毫秒(15 秒)。当节点在该时间内没有收到另一个节点的消息时,节点将被视为下线。

cluster-announce-ip 47.116.44.80 #填公网IP,作用告知其他节点用于与当前节点通信的 IP 地址

新建一个/usr/local/redis-7.2.4/cluster/redis-7001.confcluster-announce-portcluster-announce-bus-port配置的端口一定要在防火墙配置开放,否则外部连不进来,spring配置redis集群时候就会报错,设置key就会报错

include /usr/local/redis-7.2.4/cluster/redis-template.conf # 包含另一个配置文件,即 redis-template.conf,用于复用通用的配置项。

port 7001 # 指定 Redis 服务器监听的端口号为 7001,用于客户端与 Redis 服务器建立连接。

dbfilename dump-7001.rdb # 指定 Redis 持久化数据文件的名称为 dump-7001.rdb,默认情况下,用于持久化保存数据的文件名为 dump.rdb。该选项可用于为不同的 Redis 实例指定不同的持久化文件名。

appendfilename "appendonly-7001.aof" # 指定 Redis AOF(Append Only File)日志文件的名称为 appendonly-7001.aof。AOF 文件用于记录所有写操作的日志,用于数据恢复。

pidfile /usr/local/redis-7.2.4/cluster/pidfile/redis-7001.pid # 指定 Redis 服务器进程的 PID 文件的存储路径。PID 文件用于存储 Redis 服务器进程的进程 ID。

logfile "/usr/local/redis-7.2.4/cluster/logs/redis-7001.log" #设置了日志路径

cluster-config-file /usr/local/redis-7.2.4/cluster/cluster-nodes/nodes-7001.conf # 指定 Redis 集群模式下的集群配置文件的存储路径。集群配置文件用于存储集群的相关信息,包括节点信息、握手信息等。

cluster-announce-port 7001 #这是节点用于广播自己 IP 地址的端口,其他节点通过这个端口连接到该节点。

cluster-announce-bus-port 17001 # 这是节点用于集群内部通信的端口。在 Redis 集群中,各节点之间通过总线端口进行内部通信

接着新建第二个/usr/local/redis-7.2.4/cluster/redis-7002.conf,就是7001都改成7002

include /usr/local/redis-7.2.4/cluster/redis-template.conf
port 7002
dbfilename dump-7002.rdb
appendfilename "appendonly-7002.aof"
pidfile /usr/local/redis-7.2.4/cluster/pidfile/redis-7002.pid
logfile "/usr/local/redis-7.2.4/cluster/logs/redis-7002.log"
cluster-config-file /usr/local/redis-7.2.4/cluster/cluster-nodes/nodes-7002.conf
cluster-announce-port 7002
cluster-announce-bus-port 17002

依葫芦画瓢一直新建到7006

记得开放所有ip地址,否则集群的时候无法监听到,下面是bind配置翻译

默认情况下,如果未指定“bind”配置指令,Redis将侦听主机上所有可用网络接口的连接。可以使用“bind”配置指令(后跟一个或多个IP地址)只侦听一个或多个选定接口。每个地址的前缀都可以是“-”,这意味着如果地址不可用,redis不会启动失败。不可用仅指与任何网络接口不对应的地址。已在使用的地址将始终失败,不受支持的协议将始终以静默方式跳过。

示例:
绑定192.168。1.100 10.0.0.1侦听两个特定的IPv4地址
绑定127.0。0.1::1侦听环回IPv4和IPv6
绑定*-::*与默认值一样,所有可用接口

~~~警告~~如果运行Redis的计算机直接暴露于internet,绑定到所有接口是危险的,并且会向internet上的所有人公开该实例。因此,默认情况下,我们取消注释以下bind指令,这将强制Redis仅在IPv4和IPv6(如果可用)环回接口地址上侦听(这意味着Redis将只能接受来自运行它的同一主机的客户端连接)。

如果确定希望实例侦听所有接口
只需注释掉下面的一行。
#bind 127.0.0.1 -::1 

在这里插入图片描述

一次性杀死所有redis服务

ps -ef|grep redis|grep -v grep|awk  '{print "kill -9 " $2}' |sh

集群服务启动,一次性启动6个实例
用分号隔开,即可一次性启动

/usr/local/redis-7.2.4/src/redis-server /usr/local/redis-7.2.4/cluster/redis-7001.conf;/usr/local/redis-7.2.4/src/redis-server /usr/local/redis-7.2.4/cluster/redis-7002.conf;/usr/local/redis-7.2.4/src/redis-server /usr/local/redis-7.2.4/cluster/redis-7003.conf;/usr/local/redis-7.2.4/src/redis-server /usr/local/redis-7.2.4/cluster/redis-7004.conf;/usr/local/redis-7.2.4/src/redis-server /usr/local/redis-7.2.4/cluster/redis-7005.conf;/usr/local/redis-7.2.4/src/redis-server /usr/local/redis-7.2.4/cluster/redis-7006.conf;

我只安装了一个redis,启动时指定配置不一样,也可以复制多个redis应用去分别启动

7.4 将六个节点合成一个集群

组合之前,请确保所有redis实例启动后,nodes-xxxx.conf文件都生成正常。
前三个ip主,后三个ip从,但是并不是顺序对应,随机分配的。

/usr/local/redis-7.2.4/src/redis-cli --cluster create --cluster-replicas 1 公网IP:7001 公网IP:7002 公网IP:7003 公网IP:7004 公网IP:7005 公网IP:7006;

执行完,会叫你输入请不要输入y,而是yes,不然会不成功
在这里插入图片描述
贴个完整记录

[root@aerozb src]# /usr/local/redis-7.2.4/src/redis-server /usr/local/redis-7.2.4/cluster/redis-7001.conf;/usr/local/redis-7.2.4/src/redis-server /usr/local/redis-7.2.4/cluster/redis-7002.conf;/usr/local/redis-7.2.4/src/redis-server /usr/local/redis-7.2.4/cluster/redis-7003.conf;/usr/local/redis-7.2.4/src/redis-server /usr/local/redis-7.2.4/cluster/redis-7004.conf;/usr/local/redis-7.2.4/src/redis-server /usr/local/redis-7.2.4/cluster/redis-7005.conf;/usr/local/redis-7.2.4/src/redis-server /usr/local/redis-7.2.4/cluster/redis-7006.conf;/usr/local/redis-7.2.4/src/redis-cli --cluster create --cluster-replicas 1 172.29.163.48:7001 172.29.163.48:7002 172.29.163.48:7003 172.29.163.48:7004 172.29.163.48:7005 172.29.163.48:7006;
478803:C 28 Apr 2024 21:14:05.390 # WARNING: Changing databases number from 16 to 1 since we are in cluster mode
478805:C 28 Apr 2024 21:14:05.394 # WARNING: Changing databases number from 16 to 1 since we are in cluster mode
478807:C 28 Apr 2024 21:14:05.397 # WARNING: Changing databases number from 16 to 1 since we are in cluster mode
478813:C 28 Apr 2024 21:14:05.401 # WARNING: Changing databases number from 16 to 1 since we are in cluster mode
478819:C 28 Apr 2024 21:14:05.404 # WARNING: Changing databases number from 16 to 1 since we are in cluster mode
478825:C 28 Apr 2024 21:14:05.407 # WARNING: Changing databases number from 16 to 1 since we are in cluster mode
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 172.29.163.48:7005 to 172.29.163.48:7001
Adding replica 172.29.163.48:7006 to 172.29.163.48:7002
Adding replica 172.29.163.48:7004 to 172.29.163.48:7003
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: 05353d1ec81c3e721f65a47731f6f61fd35e311c 172.29.163.48:7001
   slots:[0-5460] (5461 slots) master
M: 5bec4878e206801e0d067d05a02845eb05cc1458 172.29.163.48:7002
   slots:[5461-10922] (5462 slots) master
M: c5708d5cf25dc2243ed7497956c27f92edda959e 172.29.163.48:7003
   slots:[10923-16383] (5461 slots) master
S: 9e36f61e7b0014f890d0fc86c0fc7ae6832148d5 172.29.163.48:7004
   replicates 5bec4878e206801e0d067d05a02845eb05cc1458
S: 6690c04b34a532aac63844210a4aaa0596de6d7d 172.29.163.48:7005
   replicates c5708d5cf25dc2243ed7497956c27f92edda959e
S: ea9e5196b5681cd8ae609ae725a496035cca2ab4 172.29.163.48:7006
   replicates 05353d1ec81c3e721f65a47731f6f61fd35e311c
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
.
>>> Performing Cluster Check (using node 172.29.163.48:7001)
M: 05353d1ec81c3e721f65a47731f6f61fd35e311c 172.29.163.48:7001
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: 9e36f61e7b0014f890d0fc86c0fc7ae6832148d5 172.29.163.48:7004
   slots: (0 slots) slave
   replicates 5bec4878e206801e0d067d05a02845eb05cc1458
S: ea9e5196b5681cd8ae609ae725a496035cca2ab4 172.29.163.48:7006
   slots: (0 slots) slave
   replicates 05353d1ec81c3e721f65a47731f6f61fd35e311c
M: 5bec4878e206801e0d067d05a02845eb05cc1458 172.29.163.48:7002
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: 6690c04b34a532aac63844210a4aaa0596de6d7d 172.29.163.48:7005
   slots: (0 slots) slave
   replicates c5708d5cf25dc2243ed7497956c27f92edda959e
M: c5708d5cf25dc2243ed7497956c27f92edda959e 172.29.163.48:7003
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

7.5 -c 采用集群策略连接,设置数据会自动切换到相应的写主机

[root@localhost myredis]# redis-cli -c -p 6379
127.0.0.1:6379> set k1 v1
-> Redirected to slot [12706] located at 192.168.245.129:6381
OK
192.168.245.129:6381>

可以看出设置完,根据key值 切换到对应写主机了

7.6 通过 cluster nodes 命令查看集群信息

127.0.0.1:7001> cluster nodes
9e36f61e7b0014f890d0fc86c0fc7ae6832148d5 172.29.163.48:7004@17004 slave 5bec4878e206801e0d067d05a02845eb05cc1458 0 1714311238569 2 connected
ea9e5196b5681cd8ae609ae725a496035cca2ab4 172.29.163.48:7006@17006 slave 05353d1ec81c3e721f65a47731f6f61fd35e311c 0 1714311239571 1 connected
5bec4878e206801e0d067d05a02845eb05cc1458 172.29.163.48:7002@17002 master - 0 1714311239000 2 connected 5461-10922
6690c04b34a532aac63844210a4aaa0596de6d7d 172.29.163.48:7005@17005 slave c5708d5cf25dc2243ed7497956c27f92edda959e 0 1714311237566 3 connected
c5708d5cf25dc2243ed7497956c27f92edda959e 172.29.163.48:7003@17003 master - 0 1714311240574 3 connected 10923-16383
05353d1ec81c3e721f65a47731f6f61fd35e311c 172.29.163.48:7001@17001 myself,master - 0 1714311238000 1 connected 0-5460

7.7 什么是slots(插槽)

在上面进群节点创建完毕后最后会有以下这几行显示

[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

一个 Redis 集群包含 16384 个插槽(hash slot), 数据库中的每个键都属于这 16384 个插槽的其中一个,
集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。
集群中的每个节点负责处理一部分插槽。 举个例子, 如果一个集群可以有主节点, 其中:
节点 A 负责处理 0 号至 5460 号插槽。
节点 B 负责处理 5461 号至 10922 号插槽。
节点 C 负责处理 10923 号至 16383 号插槽。
在这里插入图片描述

7.8 在集群中录入值

在redis-cli每次录入、查询键值,redis都会计算出该key应该送往的插槽,如果不是该客户端对应服务器的插槽,redis会报错,并告知应前往的redis实例地址和端口。
redis-cli客户端提供了 –c 参数实现自动重定向。
如 redis-cli -c –p 6379 登入后,再录入、查询键值对可以自动重定向。
不在一个slot下的键值,是不能使用mget,mset等多键操作。

127.0.0.1:6379> mset k1 v1 k2 v2
(error) CROSSSLOT Keys in request don't hash to the same slot

可以通过{}来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中去。
如下表示k1,k2因为都是clu组,所以通过算法算出来会被分配到同一个槽中

127.0.0.1:6379> mset k1{clu} v1 k2{clu} v2
-> Redirected to slot [8782] located at 192.168.245.129:6380
OK
192.168.245.129:6380> 

7.9 查询集群中的值

7.9.1 CLUSTER KEYSLOT key

返回一个整数,用于标识指定键所散列到的哈希槽。该命令主要用来调试和测试,因为它通过一个API来暴露Redis底层哈希算法的实现。该命令的使用示例:

客户端库可能会使用Redis来测试他们自己的哈希算法,生成随机的键并且使用他们自己实现的算法和Redis的CLUSTER KEYSLOT命令来散列这些键,然后检查结果是否相同。

人们会使用这个命令去检查哈希槽是哪个,然后关联Redis Cluster的节点,并且负责一个给定的键。

例如

> CLUSTER KEYSLOT somekey
11058
> CLUSTER KEYSLOT foo{hash_tag}
(integer) 2515
> CLUSTER KEYSLOT bar{hash_tag}
(integer) 2515
192.168.245.129:6380> CLUSTER KEYSLOT clu
(integer) 8782

7.9.2 CLUSTER COUNTKEYSINSLOT slot

返回连接节点负责的指定hash slot的key的数量。该命令只查询连接节点的数据集,所以如果连接节点指派到该hash slot会返回0

192.168.245.129:6380> CLUSTER COUNTKEYSINSLOT 8782
(integer) 2
192.168.245.129:6380> CLUSTER COUNTKEYSINSLOT 8781
(integer) 0
192.168.245.129:6380> CLUSTER COUNTKEYSINSLOT 88899
(error) ERR Invalid slot

7.9.3 CLUSTER GETKEYSINSLOT slot count

本命令返回存储在连接节点的指定hash slot里面的key的列表。key的最大数量通过count参数指定,所以这个API可以用作keys的批处理。

192.168.245.129:6380> CLUSTER GETKEYSINSLOT 8782 10
1) "k1{clu}"
2) "k2{clu}"

7.10 故障恢复

7.10.1 如果主节点下线?从节点能否自动升为主节点?注意:15秒超时

答:会

正常状况
主从都正常

[root@localhost ~]# redis-cli -c -p 6379
127.0.0.1:6379> CLUSTER NODES
1c5a2f7fc0305cb1c3bf296a15de838b2275a5b3 192.168.245.129:6381@16381 master - 0 1640868210916 3 connected 10923-16383
419d68bccee5e71554f891363f272ea79ff5a2ec 192.168.245.129:6389@16389 slave 17eef04f0da512f5bd9da55c3416e45d0d42c9b5 0 1640868209841 2 connected
5a3e1bd106e64109e5c3b586ac02cfb6b75a2062 192.168.245.129:6391@16391 slave f39b7f99e926026d4f148c4dc2b005f9078dcbcd 0 1640868207000 1 connected
00fe79a0e23db26426ecdce9db376749510ad523 192.168.245.129:6390@16390 slave 1c5a2f7fc0305cb1c3bf296a15de838b2275a5b3 0 1640868209000 3 connected
17eef04f0da512f5bd9da55c3416e45d0d42c9b5 192.168.245.129:6380@16380 master - 0 1640868208770 2 connected 5461-10922
f39b7f99e926026d4f148c4dc2b005f9078dcbcd 192.168.245.129:6379@16379 myself,master - 0 1640868207000 1 connected 0-5460

6379主节点挂掉

1.模拟挂掉

127.0.0.1:6379> SHUTDOWN

2.再次连接进不去

[root@localhost ~]# redis-cli -c -p 6379
Could not connect to Redis at 127.0.0.1:6379: Connection refused
not connected> exit

3.从其他正常节点进入观察,发现6379 master,fail,而通过主节点进行选举,通过算法选出一个从节点(6391)当主节点,撤销故障主节点负责的槽(0-5460),并执行clusterAddSlot把这些槽委派给(6391)
参考:Redis cluster集群 故障转移

[root@localhost ~]# redis-cli -c -p 6380
127.0.0.1:6380> CLUSTER NODES
00fe79a0e23db26426ecdce9db376749510ad523 192.168.245.129:6390@16390 slave 1c5a2f7fc0305cb1c3bf296a15de838b2275a5b3 0 1640868361169 3 connected
5a3e1bd106e64109e5c3b586ac02cfb6b75a2062 192.168.245.129:6391@16391 master - 0 1640868363209 7 connected 0-5460
1c5a2f7fc0305cb1c3bf296a15de838b2275a5b3 192.168.245.129:6381@16381 master - 0 1640868363000 3 connected 10923-16383
17eef04f0da512f5bd9da55c3416e45d0d42c9b5 192.168.245.129:6380@16380 myself,master - 0 1640868362000 2 connected 5461-10922
f39b7f99e926026d4f148c4dc2b005f9078dcbcd 192.168.245.129:6379@16379 master,fail - 1640868345101 1640868341000 1 disconnected
419d68bccee5e71554f891363f272ea79ff5a2ec 192.168.245.129:6389@16389 slave 17eef04f0da512f5bd9da55c3416e45d0d42c9b5 0 1640868364237 2 connected

7.10.2 主节点恢复后,主从关系会如何?主节点回来变成从节点

可以看出6379变成6391的从节点了

[root@localhost ~]# redis-server /opt/myredis/redis6379.conf;
[root@localhost ~]# redis-cli -c -p 6380
127.0.0.1:6380> CLUSTER NODES
00fe79a0e23db26426ecdce9db376749510ad523 192.168.245.129:6390@16390 slave 1c5a2f7fc0305cb1c3bf296a15de838b2275a5b3 0 1640869523245 3 connected
5a3e1bd106e64109e5c3b586ac02cfb6b75a2062 192.168.245.129:6391@16391 master - 0 1640869521000 7 connected 0-5460
1c5a2f7fc0305cb1c3bf296a15de838b2275a5b3 192.168.245.129:6381@16381 master - 0 1640869522233 3 connected 10923-16383
17eef04f0da512f5bd9da55c3416e45d0d42c9b5 192.168.245.129:6380@16380 myself,master - 0 1640869521000 2 connected 5461-10922
f39b7f99e926026d4f148c4dc2b005f9078dcbcd 192.168.245.129:6379@16379 slave 5a3e1bd106e64109e5c3b586ac02cfb6b75a2062 0 1640869521217 7 connected
419d68bccee5e71554f891363f272ea79ff5a2ec 192.168.245.129:6389@16389 slave 17eef04f0da512f5bd9da55c3416e45d0d42c9b5 0 1640869522000 2 connected
127.0.0.1:6380> 

7.10.3 如果所有某一段插槽的主从节点都宕掉,redis服务是否还能继续?

如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为yes ,那么 ,整个集群都挂掉
如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为no ,那么,该插槽数据全都不能使用,也无法存储。
redis.conf中的参数 cluster-require-full-coverage 是注释掉的,默认应该是no,从实验中可以看出

1.重启了集群,可以看出 6379的从是6391

M: d80191f55361180bad25d5082166b3d34d7c086c 192.168.245.129:6379
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
M: 8ced2c1f69666ea7e6478e3f3558df17e1bbb334 192.168.245.129:6380
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: aa2bbba8b3791381223f5b17498f25edbfe217b0 192.168.245.129:6391
   slots: (0 slots) slave
   replicates d80191f55361180bad25d5082166b3d34d7c086c
S: c42e56e2f87566a619867d7b256f91e8f9d24b07 192.168.245.129:6389
   slots: (0 slots) slave
   replicates 8ced2c1f69666ea7e6478e3f3558df17e1bbb334
M: 1130149459b43bb02b8d977ef27fd318f454e4c9 192.168.245.129:6381
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: e79cc2830a430591772da576b24cf7ff3d6d2e76 192.168.245.129:6390
   slots: (0 slots) slave
   replicates 1130149459b43bb02b8d977ef27fd318f454e4c9

2.杀死这俩进程

[root@localhost ~]# ps -ef|grep redis
root       19269       1  0 21:51 ?        00:00:00 redis-server *:6379 [cluster]
root       19271       1  0 21:51 ?        00:00:00 redis-server *:6380 [cluster]
root       19277       1  0 21:51 ?        00:00:00 redis-server *:6381 [cluster]
root       19283       1  0 21:51 ?        00:00:00 redis-server *:6389 [cluster]
root       19285       1  0 21:51 ?        00:00:00 redis-server *:6390 [cluster]
root       19291       1  0 21:51 ?        00:00:00 redis-server *:6391 [cluster]
root       19359   14823  0 21:55 pts/0    00:00:00 grep --color=auto redis
[root@localhost ~]# kill 19269
[root@localhost ~]# kill 19291

3.再看看集群是否存活?发现正常存活,不过0-5460插槽,就无节点管理了。

[root@localhost ~]# redis-cli -c -p 6381
127.0.0.1:6381> CLUSTER NODES
d80191f55361180bad25d5082166b3d34d7c086c 192.168.245.129:6379@16379 master,fail - 1640872535451 1640872531000 1 disconnected
aa2bbba8b3791381223f5b17498f25edbfe217b0 192.168.245.129:6391@16391 master,fail - 1640872561027 1640872557000 7 disconnected 0-5460
1130149459b43bb02b8d977ef27fd318f454e4c9 192.168.245.129:6381@16381 myself,master - 0 1640872578000 3 connected 10923-16383
c42e56e2f87566a619867d7b256f91e8f9d24b07 192.168.245.129:6389@16389 slave 8ced2c1f69666ea7e6478e3f3558df17e1bbb334 0 1640872579000 2 connected
e79cc2830a430591772da576b24cf7ff3d6d2e76 192.168.245.129:6390@16390 slave 1130149459b43bb02b8d977ef27fd318f454e4c9 0 1640872580601 3 connected
8ced2c1f69666ea7e6478e3f3558df17e1bbb334 192.168.245.129:6380@16380 master - 0 1640872579581 2 connected 5461-10922

7.10.4 当 Redis 集群中的一个节点挂掉后,你可以按照以下步骤重新启动该节点并让其重新加入集群:

启动节点

redis-server redis.conf

进入另一台正常运行redis节点的服务器

redis-cli -p 正常运行的redis端口 -a 密码 cluster meet 恢复运行节点的IP 端口

回到故障的节点,查看集群状态

redis-cli -c -p 端口 -a 密码
cluster nodes

-a 密码 如果有密码则加这行命令,否则去掉
有密码不加的话执行cluster nodes会提示 NOAUTH Authentication required.

7.10.5 当服务重启后,恢复集群方法

正常来说,依次请启动主节点再从节点,就会自己恢复

如果不正常只能清除持久化的数据,再重新集群

  1. rm -rf appendonly.aof dump.rdb
  2. 进入redis客户端redis-cli -c -p 端口 -a 密码,每个节点都依次执行 CLUSTER RESET SOFT FLUSHALL
  3. 退出来,redis5.0以下用 redis-trib.rb 执行集群命令,5.0以上用redis-cli,命令看上面的 7.4章节有写

FLUSHALL :是清空 Redis 实例中所有数据的命令
CLUSTER RESET SOFT: 则是对 Redis 集群进行软重置的命令,它会将集群状态中的所有节点移除,但保留数据不变

7.11 集群的Jedis开发

public class JedisClusterTest {
  public static void main(String[] args) { 
     Set<HostAndPort>set =new HashSet<HostAndPort>();
     set.add(new HostAndPort("192.168.31.211",6379));
     JedisCluster jedisCluster=new JedisCluster(set);
     jedisCluster.set("k1", "v1");
     System.out.println(jedisCluster.get("k1"));
  }
}

7.12 Redis 集群提供了以下好处

  • 实现扩容
  • 分摊压力
  • 无中心配置相对简单

7.13 Redis 集群的不足

  • 多键操作是不被支持的
  • 多键的Redis事务是不被支持的。lua脚本不被支持
  • 由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至redis cluster,需要整体迁移而不是逐步过渡,复杂度较大。

8. Redis应用问题解决

参考:缓存穿透、缓存击穿、缓存雪崩区别和解决方案

7.14 学完集群疑惑解答

7.14.1 节点 哈希槽 数据库 关系

在 Redis 集群中,每个节点负责一部分哈希槽,每个哈希槽都会被分配到一个节点上。每个节点上都会运行一个或多个 Redis 数据库实例,每个实例都有自己的数据库编号。在 Redis 集群中,每个节点上的数据库实例数量是相同的,这样可以保证每个节点上的数据结构是相同的。因此,可以说 Redis 节点、哈希槽和数据库之间存在着密切的关系。

7.14.2 槽 里面存储数据吗

在 Redis 集群中,哈希槽是一个整数,范围是 0 到 16383,对应了 Redis 中的 16384 个槽。每个槽都有一个唯一的编号,用于标识该槽在集群中的位置。每个节点都会维护一个槽映射表,用于记录每个槽对应的节点。槽本身并不存储数据,而是用于将数据分配到不同的节点上进行存储。具体来说,当一个键值对被存储到 Redis 集群中时,集群会根据键的哈希值将其分配到一个槽上,然后将该槽对应的数据存储到对应的节点上。因此,槽是用于分配数据存储位置的一种机制,而不是用于存储数据的。

7.14.3 三主三从,每个主节点间数据是一样的吗

在 Redis 三主三从的架构中,每个主节点负责管理不同的数据集,因此每个主节点之间的数据是不同的。主节点之间进行数据同步的时候,只会同步自己负责的数据,不会同步其他主节点的数据。因此,在 Redis 三主三从的架构中,每个主节点之间的数据是不同的。但是,每个从节点都会从对应的主节点上复制完整的数据集,因此每个从节点的数据是和其对应的主节点的数据是一样的。

前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果。
在这里插入图片描述

8.1 缓存穿透

8.1.1 问题描述

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
在这里插入图片描述

8.1.2 解决方案

  1. 校验: 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
  2. 对空值缓存从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
  3. 设置可访问的名单(白名单): 使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。
  4. 采用布隆过滤器:(布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。
    布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
    将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被 这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。
  5. **进行实时监控:**当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务

8.2 缓存击穿

8.2.1 问题描述

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
在这里插入图片描述

8.2.2 解决方案

  1. 设置热点数据永远不过期;在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长
  2. 实时调整: 现场监控哪些数据热门,实时调整key的过期时长
  3. 加互斥锁,互斥锁参考代码如下:

在这里插入图片描述
说明:
4. 缓存中有数据,直接走上述代码13行后就返回结果了
5. 缓存中没有数据,第1个进入的线程,获取锁并从数据库去取数据,没释放锁之前,其他并行进入的线程会等待100ms,再重新去缓存取数据。这样就防止都去数据库重复取数据,重复往缓存中更新数据情况出现。
6. 当然这是简化处理,理论上如果能根据key值加锁就更好了,就是线程A从数据库取key1的数据并不妨碍线程B取key2的数据,上面代码明显做不到这点。

8.3 缓存雪崩

8.3.1 问题描述

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

正常
在这里插入图片描述
缓存失效瞬间
在这里插入图片描述

8.3.2 解决方案

  1. 将缓存失效时间分散开: 比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

  2. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。

  3. 设置热点数据永远不过期。

8.4 分布式锁

8.4.1 问题描述

随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!
分布式锁主流的实现方案:

  1. 基于数据库实现分布式锁
  2. 基于缓存(Redis等)
  3. 基于Zookeeper
    每一种分布式锁解决方案都有各自的优缺点:
  4. 性能:redis最高
  5. 可靠性:zookeeper最高
    这里,我们就基于redis实现分布式锁。

8.4.2 解决方案:使用redis实现分布式锁

SETNX key value

将key设置值为value,如果key不存在,这种情况下等同SET命令。 当key存在时,什么也不做。SETNX是”SET if Not eXists”的简写。

返回值:

  • 1 如果key被设置了
  • 0 如果key没有被设置
EXPIRE key seconds

设置key的过期时间,超过时间后,将会自动删除该key。

案例: 设置一个key,再去设置它就会发现因为存在而不成功,在设置过期时间,不然其他锁竞争者万一等好几天时间太久,也不符合设计。

127.0.0.1:6379> setnx u 1
(integer) 1
127.0.0.1:6379> setnx u 1
(integer) 0
127.0.0.1:6379> del u
(integer) 1
127.0.0.1:6379> setnx u 2
(integer) 1
127.0.0.1:6379> get u
"2"
127.0.0.1:6379> EXPIRE u 20
(integer) 1
127.0.0.1:6379> ttl u
(integer) 15

在这里插入图片描述
上面案例问题:

上锁之后突然出现异常,无法设置过期时间了

解决:

上锁时候同时设置过期时间就可以

命令

set key value [expiration EX seconds|PX milliseconds] [NX|XX]

例子
set sku:1:info “OK” NX PX 10000

参数解释:

  • EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
  • PX millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。
  • NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
  • XX :只在键已经存在时,才对键进行设置操作。

在这里插入图片描述

  1. 多个客户端同时获取锁(setnx)
  2. 获取成功,执行业务逻辑{从db获取数据,放入缓存},执行完成释放锁(del)
  3. 其他客户端等待重试

8.4.3 根据以上命令编写代码

1.Redis: set num 0

2.java 代码
config

@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport
{
    @Bean
    public RedisTemplate < String, Object > redisTemplate(RedisConnectionFactory factory)
    {
        RedisTemplate < String, Object > template = new RedisTemplate < > ();
        RedisSerializer < String > redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setConnectionFactory(factory);
        //key序列化方式
        template.setKeySerializer(redisSerializer);
        //value序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //value hashmap序列化
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        return template;
    }
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory)
    {
        RedisSerializer < String > redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        //解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // 配置序列化(解决乱码的问题),过期时间600秒
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofSeconds(600))
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
            .disableCachingNullValues();
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
            .cacheDefaults(config)
            .build();
        return cacheManager;
    }
}

测试用 Controller

@RestController
public class RedisController {

    @Autowired
    private RedisTemplate redisTemplate;

    @GetMapping("testLock")
    public void testLock() {
        //1获取锁,setne
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111");
        //2获取锁成功、查询num的值
        if (lock) {
            System.out.println(1111);
            Object value = redisTemplate.opsForValue().get("num");
            //2.1判断num为空return
            if (ObjectUtils.isEmpty(value)) {
                return;
            }
            //2.2有值就转成成int
            int num = Integer.parseInt(value + "");
            //2.3把redis的num加1
            redisTemplate.opsForValue().set("num", ++num);
            //2.4释放锁,del
            redisTemplate.delete("lock");

        } else {
            //3获取锁失败、每隔0.1秒再获取
            try {
                Thread.sleep(100);
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

8.4.4 上面代码误删key问题

  1. 如果请求A在执行过程中,锁到期了,
  2. 这时候请求B来执行,获取到锁,也在执行,
  3. 请求A因为业务结束而要删除锁,这样就把B的锁给删了,不符合设计

8.4.5 解决误删:使用UUID key

代码改造如下:setnx获取锁时,设置一个指定的唯一值(例如:uuid);释放前获取这个值,判断是否自己的锁
在这里插入图片描述

8.4.6 判断删除时原子问题

场景:

  1. index1执行删除时,查询到的lock值确实和uuid相等
    uuid=v1
    set(lock,uuid);
    在这里插入图片描述
  2. index1执行删除前,lock刚好过期时间已到,被redis自动释放
    在redis中没有了lock,没有了锁。
    在这里插入图片描述
  3. index2获取了lock
    index2线程获取到了cpu的资源,开始执行方法
    uuid=v2
    set(lock,uuid);
  4. index1执行删除,此时会把index2的lock删除
    index1 因为已经在方法中了,所以不需要重新上锁。index1有执行的权限。index1已经比较完成了,这个时候,开始执行
    在这里插入图片描述
    删除的index2的锁!

8.4.7 解决原子问题:lua脚本

问题是在判断然后删除的时候出问题,这时候只需要使用lua脚本同时执行这两句命令,保证是在同一个事物内,具有原子性,即可。

@GetMapping("testLockLua")
    public void testLockLua() {
        //1 声明一个uuid ,将做为一个value 放入我们的key所对应的值中
        String uuid = UUID.randomUUID().toString();
        //2 定义一个锁:lua 脚本可以使用同一把锁,来实现删除!
        String skuId = "25"; // 访问skuId 为25号的商品 100008348542
        String locKey = "lock:" + skuId; // 锁住的是每个商品的数据

        // 3 获取锁
        Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, uuid, 3, TimeUnit.SECONDS);

        // 第一种: lock 与过期时间中间不写任何的代码。
        // redisTemplate.expire("lock",10, TimeUnit.SECONDS);//设置过期时间
        // 如果true
        if (lock) {
            // 执行的业务逻辑开始
            // 获取缓存中的num 数据
            Object value = redisTemplate.opsForValue().get("num");
            // 如果是空直接返回
            if (StringUtils.isEmpty(value)) {
                return;
            }
            // 不是空 如果说在这出现了异常! 那么delete 就删除失败! 也就是说锁永远存在!
            int num = Integer.parseInt(value + "");
            // 使num 每次+1 放入缓存
            redisTemplate.opsForValue().set("num", String.valueOf(++num));
            /*使用lua脚本来锁*/
            // 定义lua 脚本
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            // 使用redis执行lua执行
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptText(script);
            // 设置一下返回值类型 为Long
            // 因为删除判断的时候,返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型,
            // 那么返回字符串与0 会有发生错误。
            redisScript.setResultType(Long.class);
            // 第一个要是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值。
            redisTemplate.execute(redisScript, Arrays.asList(locKey), uuid);
        } else {
            // 其他线程等待
            try {
                // 睡眠
                Thread.sleep(1000);
                // 睡醒了之后,调用方法。
                testLockLua();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

8.4.8 分布式锁使用总结

1.加锁
从redis中获取锁,set k1 v1 px 20000 nx

String uuid = UUID.randomUUID().toString();
Boolean lock = this.redisTemplate.opsForValue()
      .setIfAbsent("lock", uuid, 2, TimeUnit.SECONDS);

2、使用lua释放锁

//  释放锁 del
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
// 设置lua脚本返回的数据类型
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
// 设置lua脚本返回类型为Long
redisScript.setResultType(Long.class);
redisScript.setScriptText(script);
redisTemplate.execute(redisScript, Arrays.asList("lock"),uuid);

3、重试

Thread.sleep(500);
testLock();

9. 分布式锁框架RedisLockRegistry 和 Redisson

RedisLockRegistry 是springboot 实现的分布式锁
Redisson是redis提供的分布式锁实现

Redisson使用

初始化一个springboot项目,引入Redisson依赖,我选的当下最新的,我spring-boot版本用的2.6.13,貌似也没见启动不了,冲突啥的

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.29.0</version>
        </dependency>

配置redis集群节点

spring:
  redis:
    cluster:
      nodes:
        - 公网IP:7001
        - 公网IP:7002
        - 公网IP:7003
        - 公网IP:7004
        - 公网IP:7005
        - 公网IP:7006

写个控制器测试

@RestController
public class RedissonController {

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @GetMapping(value = "/redisson/{key}")
    public String redissonTest(@PathVariable("key") String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            lock.lock();
            Thread.sleep(10000);
        } catch (Exception e) {

        } finally {
            lock.unlock();
        }
        return "已解锁";
    }

    @GetMapping(value = "/redisTemplate")
    public void redisTemplate() {
        redisTemplate.opsForList().leftPush("listTest","1");
        redisTemplate.opsForList().leftPush("listTest","2");
        redisTemplate.opsForList().leftPush("listTest","3");
    }
}

GET http://localhost:8080/redisson/asdasd
结果如下图,雀氏锁定住了
在这里插入图片描述
redisTemplate 测试用了下,也是可以用的

尚硅谷是一个教育机构,他们提供了一份关于Redis学习笔记。根据提供的引用内容,我们可以了解到他们提到了一些关于Redis配置和使用的内容。 首先,在引用中提到了通过执行命令"vi /redis-6.2.6/redis.conf"来编辑Redis配置文件。这个命令可以让你进入只读模式来查询"daemonize"配置项的位置。 在引用中提到了Redis会根据键值计算出应该送往的插槽,并且如果不是该客户端对应服务器的插槽,Redis会报错并告知应该前往的Redis实例的地址和端口。 在引用中提到了通过修改Redis的配置文件来指定Redis的日志文件位置。可以使用命令"sudo vim /etc/redis.conf"来编辑Redis的配置文件,并且在文件中指定日志文件的位置。 通过这些引用内容,我们可以得出结论,尚硅谷的Redis学习笔记涵盖了关于Redis的配置和使用的内容,并提供了一些相关的命令和操作示例。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Redis学习笔记--尚硅谷](https://blog.csdn.net/HHCS231/article/details/123637379)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Redis学习笔记——尚硅谷](https://blog.csdn.net/qq_48092631/article/details/129662119)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值