Redis手记(LTS)

Redis简介

全程 Remote Dictionary Server 远程词典服务器

Redis是一种基于内存的 key-value 结构数据库 ,将数据存储与内存,读写性能更好

MySQL的数据存储方式是二维表

Redis 的特征

  • 键值(key-value)型,value支持多种不同的数据结构,功能丰富
  • 单线程,每个命令具备原子性
  • 低延迟,速度快(基于内存、IO多路复用、良好的编码)
  • 支持数据持久化(Redis会定期将数据从内存持久化到磁盘
  • 支持主从集群(从节点备份主节点数据)、分片集群
  • 支持多语言客户端

Redis安装

linux下安装redis(超详细,每一步命令都有命令截图及运行截图)_linux、 安装redis-CSDN博客

更推荐 yum 安装

sudo yum install -y redis
  • 启动 redis
systemctl start redis

配置文件一般在 /etc/redisredis.conf 中,修改 redis.conf 文件后直接重启 redis 即可

systemctl restart redis
  • 停止 redis 服务
systemctl stop redis

redis-server 是redis服务器
redis-cli 是redis客户端

Redis启动命令

redis/bin 目录下

启动Redis服务

./redis-server redis.conf

停止Redis服务

ctrl + c

redis.conf 是Redis的配置文件,端口号等修改可以在redis.conf中修改

启动Redis客户端

先启动服务,再启动客户端

./redis-cli

远程连接 redis

关闭 redis 的主机绑定与保护模式

  • 修改 redis.conf
# 注释绑定主机
#bind 127.0.0.1 -::1
# 关闭保护模式
protected-mode no

在这里插入图片描述

  • 重启 redis 服务
./redis-server redis.conf

使用 Redis Insight 连接
下载地址:https://redis.io/insight/
在这里插入图片描述

Redis 常用数据类型

在这里插入图片描述

基本类型

  • 字符串(String) :普通字符串
    • 可以包含任何数据,比如 jpg 图片或者序列化的对象。
    • 一个键最大能存储 512MB。
  • 哈希(Hash) : 键值对集合
    • 一个 key 类似于 MySQL 中的一张表
    • 也称为散列
    • 哈希是一个字符串字段和字符串值之间的映射表,用来表示对象。
  • 列表(List) : 字符串列表
    • 可以看做是一个双向链表结构
    • 按照插入顺序排序。
    • 添加一个元素到列表的头部(左边)或者尾部(右边)。
    • 常用来实现队列,因为它支持两端的推入和弹出操作。
  • 集合(Set) : 字符串的无序集合
    • 元素不可重复,无序,类似 Java 中的 HashSet
    • 通过哈希表实现,所以添加、删除、查找的复杂度都是 O(1)。
    • 支持交集、并集、差集,非常适合用来做去重。
  • 有序集合(Sorted Set) : 字符串的有序集合
    • 元素不可重复
    • 每个元素都会关联一个浮点数分数(double类型),用于排序,从小到大的排序
    • 查询速度快

特殊类型

本质还是字符串

  • 位图(Bitmap) : 字符串
    • 可以视为由比特构成的数组,通过位操作命令来处理。
    • 适用于实现如在线用户统计等功能。
  • HyperLogLog : 用作基数统计的算法
    • 占用空间小,无论统计的元素数量是多少,所需的内存大小固定为 12KB。
  • 地理空间(Geo) : 计算两地点之间的距离

Redis中key的层级格式

因为Redis中没有 表 的概念,为区分不同业务模块的key,推荐按照格式存储

项目名:业务名:类型:id

Redis命令

Redis常用命令(超详细整理)_redis命令-CSDN博客

不区分大小写

字符串(String操作命令

key–value

  • string:普通字符串
  • int:整数类型,可以做自增、自减操作
  • float:浮点类型,可以做自增、自减操作

底层都是字节数组形式存储,但是编码方式不同,比如可以将一张图片编译为字节形式上传,最大不超过 512 M

添加或修改指定key的值

set key value

批量添加多个 string 类型的键值对

mset key1 value1 key2 value2 ...

获取指定key的值

get key

获取多个 string 类型的值

mget key1 key2 key3 ...

整形的 key 自增1

incr key 1
#如果 key 对应的 value 的值原本为1,则在执行完这条命令后值就变为2
#值可以为负数

整形的key自增并指定步长

incrby key 3
#如果 key 对应的 value 的值原本为1,则在执行完这条命令后值就变为4
#值可以为负数

浮点型数字自增并指定步长

incrbyfloat key 0.5
#如果 key 对应的 value 的值原本为0.1,则在执行完这条命令后值就变为0.6

设置指定key的值与过期时间(秒数)

如手机验证码,设置过期时间

TTL 有效时间

setex key seconds value
#或者
set key value ex seconds

设置指定key的值,但是只有在key不存在时设置

如分布式锁

setnx key value

key不存在时返回null

哈希(Hash)操作命令

key–field–value

添加或修改名为 key 的哈希表中的字段field的值

hset key field value
#例如
hset dir1:dir2:dir3 name fishpie
hset dir1:dir2:dir3 age 20

在这里插入图片描述

添加多组数据(注意添加与修改的区别)

hmset dir1:dir2:dir3 name tom age 20 sex man

获取存储在哈希表中指定字段的值

hget key field
#例如
hget dir1:dir2:dir3 name

获取多个字段

hmget dir1:dir2:dir3 name age sex

在这里插入图片描述

获取一个hash类型中key中所有的 field 和 value

hgetall dir1:dir2:dir3

获取一个hash类型的key中所有field

hkeys dir1:dir2:dir3

获取一个hash类型的key中所有的value

hvals dir1:dir2:dir3

设置指定字段field自增长

hincrby dir1:dir2:dir3 age 2
# age += 2

添加一个hash类型的key的field值,前提是这个field不存在,否则不执行

hsetnx dir1:dir2:dir3:1 name shark	#执行
hsetnx dir1:dir2:dir3 name fishpie	#不执行
hsetnx dir1:dir2:dir3 name shark	#执行
#注意区分是field存不存在,而不是值的问题

删除存储在哈希表中的指定字段

hdel key field

列表(List)操作命令

key–value1 value2 value3

将一个值或多个值插入到列表头(左侧第一个元素)

lpush key value1 ...

移出并返回列表左侧的第一个元素,没有则返回null

lpop key

#阻塞,等待100秒来执行这个语句
blpop key 100

获取列表指定范围内的元素

类似与SQL中的分页查询

lrange key start stop

#查询列表中的所有元素
lrange key 0 -1	  #此处-1相当于最后一个元素

向列表右侧插入一个或多个元素

rpush key value1 ...

移出并获取列表最后一个元素(右侧第一个元素)

注意,此处的数据结构为队列先进先出

栈是先进后出

rpop key

#阻塞,等待100秒来执行这个语句
brpop key 100

获取列表 key 的长度

llen key

集合(Set)操作命令

一个集合对应一个或多个成员

一个集合内的元素不能重复,但是不同集合的元素可以重复(共有元素)

key— value1

​ –value2

向集合添加一个或多个成员

sadd key member1 [member2]

判断一个元素是否存在于set中

sismember key member
#元素存在返回1,否则返回0

返回集合中所有成员

smembers key

获取集合成员数

scard key

返回给定所有集合的交集

sinter key1 [key2]

返回集合间的差集

sdiff s1 s2
#返回集合 s1 中存在,而 s2 中不存在的元素

返回所有给定集合的并集

sunion key1 [key2]

删除集合中一个或多个成员

srem key member1 [member2]
#删除成功返回1,否则返回0

有序集合(Sorted Set)操作命令

key – 0.1 value1

​ – 1.0 value2

向有序集合添加一个或多个成员,如果已存在则更新其score值

zadd key score1 member1 [score2 member2]

通过索引区间返回有序集合中指定区间内的成员

zrange key start stop
# start 开始索引
# stop 结束索引

按照score排序后,获取指定 score 范围内的元素

zrangebyscore key min max
# score 数值小的在上面

统计 score 值在指定范围内的所有元素的个数

zcount key start stop
# start 开始索引
# stop 结束索引

有序集合中对指定成员的分数加上增量 increment

zincrby key increment member

获取 sorted set 中指定元素的 score 值

zscore key member

获取 sorted set 中的指定元素的排名

zrank key member

获取 sorted set 中元素个数

zcard key

移出有序集合中的一个或多个成员

zrem key member [member ...]

求差集、交集、并集

zdiff
zinter
zunion

通用命令

切换数据源

#index为数据源索引 0--15
select index

查找所有符合给定模式(pattern)的key

keys pattern

#查找Redis中所有的 key
#不建议使用,因为Redis是单线程,可能会因为这个查询阻塞请求
keys *
#查找以 set 开头的key有哪些
keys set*

检查给定的 key 是否存在

exists key

返回 key 所存储的值的类型

type key

删除 key ,前提是 key 存在

del key

设置 / 查看key的有效期

  • 数据存入 Redis 时推荐全部设置有效期
#为名为 key 的键设置有效期为20秒
expire key 20

#查看名为 key 的键的剩余时间
ttl key age

#如果一个键 key 未设置有效期,则使用 ttl 命令后显示 -1
#表示永久有效

在Java中使用Redis

使用 Spring Date Redis ,兼容了Jedis 与 lettuce

API返回值类型说明
redisTemplate.opsForValue()ValueOperations操作 String 类型数据
redisTemplate.opsForHash()HashOperations操作 Hash 类型数据
redisTemplate.opsForList()ListOperations操作 List 类型数据
redisTemplate.opsForSet()SetOperations操作 Set 类型数据
redisTemplate.opsForZSet()ZSetOperations操作 SortedSet 类型数据
redisTemplate通用命令

操作步骤

  1. 导入Spring Date Redis 的 maven 坐标
<!--Redis依赖-->
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--链接池依赖-->
<dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-pool2</artifactId>
</dependency>
  1. 配置Redis数据源
spring:  
  redis:
    database: ${example.redis.database}
    host: ${example.redis.host}
    port: ${example.redis.port}
    password: *****
    lettuce:
    	pool:
    		max-active: 8 #最大连接
    		max-idle: 8   #最大空闲连接
    		min-idle: 0   #最小空闲连接
    		max-wait: 100 #连接等待时间
  1. RedisTemplate序列化

    方案一:

    • 自定义 RedisTemplate
    • 修改RedisTemplate序列化器为 GenericJackson2JsonRedisSerializer
@Configuration
@Slf4j
public class RedisConfiguration {

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
        log.info("开始创建redis模版对象...");
        //创建 RedisTemplate 对象
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate();
        //设置 Redis 连接工厂对象
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //创建 JSON 序列化工具
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        //设置redis key的序列化器,目的是所见即所得
        redisTemplate.setKeySerializer(RedisSerializer.string());
        redisTemplate.setHashKeySerializer(RedisSerializer.string());
        //设置redis value的序列化
        redisTemplate.setValueSerializer(jsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jsonRedisSerializer);
        
        return redisTemplate;
    }

}
  • 方案二:(节省内存空间)
    • 使用 StringRedisTemplate
    • 写入Redis时,手动把对象序列化为JSON
    • 读取Redis时,手动把读取到的JSON反序列化为对象
	@Autowired
    private StringRedisTemplate stringRedisTemplate;
    private static final ObjectMapper mapper = new ObjectMapper();

	@Test
    void testSaveUser() throws JsonProcessingException{
        //创建对象(假设存在这样一个User实体类)
        User user = new User("fishpie",21);
		//手动序列化,将 user 对象转为json字符串
        String json = mapper.writeValueAsString(user);
        //写入数据
        stringRedisTemplate.opsForValue().set("user:200",json);
        
        //获取数据
        String jsonUser = stringRedisTemplate.opsForValue().get("user:200");
        //手动反序列化
        User user1 = mapper.readValue(jsonUser,User.class);
        System.out.println("user1 = " + user1);
    }

StringRedisTemplateRedisTemplate的一个子类,它在RedisTemplate的基础上做了部分优化,默认采用String序列化策略,只能用于操作Redis中的String类型数据

由于StringRedisTemplate默认采用String序列化策略,所以使用时不需要再指定序列化/反序列化方式,可以直接传入及接收String数据。

两者的 JavaAPI 一致

  1. 通过RedisTemplateStringRedisTemplate对象操作Redis

    //一下的操作以 StringRedisTemplate 为例

对于String操作命令

@Autowired
    //使用 stringRedisTemplate 时,键值对只能以 String 字符串的形式存储
    private StringRedisTemplate stringRedisTemplate;

    /**
     * stringRedisTemplate对String类型数据的常用操作
     */
    @Test
    public void testString(){
        //使用 stringRedisTemplate 时,键值对只能以 String 字符串的形式存储
        ValueOperations<String, String> forValue = stringRedisTemplate.opsForValue();
        //添加或修改指定 key 的值
        forValue.set("name","fish pie");
        //获取指定 key 的值
        forValue.get("name");
        //如果 key 不存在则设置字符串值
        forValue.setIfAbsent("age","20");
        //先获取 key 的值,再设置新的字符串值
        forValue.getAndSet("age","21");
        //将key对应的值加步长(如果key不存在,则先设为0再执行加1操作)
        forValue.increment("age",-1);
        //将值value追加到指定key字符串值的末尾
        forValue.append("name","pie");
        //获取key对应字符串的长度
        forValue.size("name");
    }

对于Hash操作命令

@Autowired
    //使用 stringRedisTemplate 时,键值对只能以 String 字符串的形式存储
    private StringRedisTemplate stringRedisTemplate;

	/**
     * stringRedisTemplate对Hash类型数据的常用操作
     */
    @Test
    public void testHash(){
        HashOperations<String, Object, Object> forHash = stringRedisTemplate.opsForHash();
        //向指定Hash黄总添加或更新一个字段值对
        forHash.put("userGroup1","name","fish pie");
        forHash.put("userGroup1","age","20");
        //批量多个键值对
        HashMap<Object, Object> map = new HashMap<>();
        map.put("name","fish pie brave");
        map.put("age","20");
        forHash.putAll("userGroup2",map);
        //获取一个hash中一个字段的value
        forHash.get("userGroup1","name");
        //获取一个hash中所有的键值对
        forHash.entries("userGroup1");
        //判断hash中是否存在某个字段
        Boolean hasKey = forHash.hasKey("userGroup2", "age");
        //指定hash中某个字段值进行增加或减少
        forHash.increment("userGroup2","age",-1);   //是了,越活越年轻
        //获取指定hash中字段的数量
        forHash.size("userGroup2");
    }

对于List操作命令

@Autowired
    //使用 stringRedisTemplate 时,键值对只能以 String 字符串的形式存储
    private StringRedisTemplate stringRedisTemplate;

	/**
     * stringRedisTemplate对List类型数据的常用操作
     */
    @Test
    public void testList(){
        ListOperations<String, String> forList = stringRedisTemplate.opsForList();
        //将一个或多个值推入列表左侧
        forList.leftPush("list1","value1");
        forList.leftPushAll("list1","value2","value3");
        //将一个或多个值推入列表右侧
        forList.rightPush("list2","value1");
        forList.rightPushAll("list2","value1","value2");
        //移出列表左侧第一个元素并返回
        forList.leftPop("list1");
        //移出列表右侧第一个元素并返回
        forList.rightPop("list2");
        //获取一个列表中指定索引位置的元素
        forList.index("list1",0);
        //获取一个列表中指定范围内的元素
        forList.range("list1",0,2);
        forList.range("list1",0,-1);    //直接是获取列表list1的所有元素
        //修剪某个列表,只保留指定范围内的元素
        forList.trim("list1",0,2);
        //设置列表中指定索引位置的元素值
        forList.set("list1",2,"valueBeenReplace_2");
        //获取一个列表的长度
        forList.size("list1");
        //从列表中移除指定数量的值
        //从左侧移出2个list1中值为value1的元素,并返回移出元素的个数
        Long l = forList.remove("list1", 2, "value1");
        //从右侧移出2个list1中值为value1的元素,并返回移出元素的个数
        Long r = forList.remove("list1", -2, "value1");
    }

对于Set操作命令

@Autowired
    //使用 stringRedisTemplate 时,键值对只能以 String 字符串的形式存储
    private StringRedisTemplate stringRedisTemplate;

	/**
     * stringRedisTemplate对Set类型数据的常用操作
     */
    @Test
    public void testSet(){
        SetOperations<String, String> forSet = stringRedisTemplate.opsForSet();
        //向某个集合中添加一个或多个成员
        forSet.add("set1","value1","value2","value3","value4");
        forSet.add("set2","value1","value2","value3","value4");
        //向某个集合中移出一个或多个成员
        forSet.remove("set1","value1","value2");
        //获取集合中所有的成员
        Set<String> set1 = forSet.members("set1");
        //判断给定的值是否是集合的成员
        Boolean isMember = forSet.isMember("set1", "value3");
        //从某个集合中移出并返回一个随机元素
        forSet.pop("set1");
        //获取某个集合中元素的个数
        forSet.size("set1");
        //获取两个集合的并集
        forSet.union("set1","set2");
        //获取两个集合的交集
        forSet.intersect("set1","set2");
        //获取给定集合set1与set2的差集
        forSet.difference("set1","set2");
    }

对于有序集合

@Autowired
    //使用 stringRedisTemplate 时,键值对只能以 String 字符串的形式存储
    private StringRedisTemplate stringRedisTemplate;

	/**
     * stringRedisTemplate对SortedSet类型数据的常用操作
     */
    public void testZSet(){
        ZSetOperations<String, String> forZSet = stringRedisTemplate.opsForZSet();
        //向某个有序集合中添加一个成员及其分数
        forZSet.add("zset1","value1",1.0);
        forZSet.add("zset1","value2",2.0);
        forZSet.add("zset1","value3",99.0);
        forZSet.add("zset1","value4",98.0);
        forZSet.add("zset1","value5",80.0);
        //从某个有序集合中移出一个或多个成员
        forZSet.remove("zset1","value1","value2");
        //增加某个有序集合中指定成员的分数
        forZSet.incrementScore("zset1","value3",10.0);

        //获取某个有序集合中指定范围的成员
        //这里是获取所有zset1中的所有成员(正序),分数从小到大
        forZSet.range("zset1",0,-1);
        //按照分数从小到大排序返回排名从1到3的所有元素
        forZSet.range("zset1",0,2);

        //获取某个有序集合中指定范围内的成员,并以逆序返回结果
        //这里是获取所有zset1中的所有成员(逆序),
        forZSet.reverseRange("zset1",0,-1);
        //返回某个有序集合指定分数范围内的元素
        forZSet.rangeByScore("zset1",80,100);
        //返回某个有序集合中指定元素的排名(正序)
        //分数从小到大第几个
        forZSet.rank("zset1","value5");
        //返回某个有序集合中指定元素的排名(逆序)
        //分数从大到小第几个
        forZSet.reverseRank("zset1","value5");
        //获取有序集合中指定成员的分数
        forZSet.score("zset1","value5");
    }

通用命令操作

@Autowired
    //使用 stringRedisTemplate 时,键值对只能以 String 字符串的形式存储
    private StringRedisTemplate stringRedisTemplate;

    @Test
    public void testCommon(){

        //查询所有的key
        redisTemplate.keys("*");

        //判断key是否存在
        redisTemplate.hasKey("name");

        //获取key的种类
        for(Object key : keys){
            redisTemplate.type(key);
            sout
        }

        //删除指定key
        redisTemplate.delete("mylist");

    }

Redis适用的业务类型

以下仅提出理论,并无实际代码示范

短信登录

缓存数据

缓存的概念

缓存就是数据交换的缓冲区(Cache),是临时存储数据的地方,一般读写性能很高

缓存大小也是CPU的性能指标之一

可以将需要查询的数据存入Redis中达到缓存的目的

缓存的应用场景

在这里插入图片描述

  • 缓存的作用
    1. 降低后端负载
    2. 提高读写效率,降低响应时间
  • 缓存的成本
    1. 数据一致性成本,缓存存储与内存,磁盘中的数据与内存中的数据一致性问题需要解决
    2. 代码维护成本
    3. 运维成本

缓存的更新策略

  1. 内存淘汰(Redis自身机制)
  2. 超时剔除
  3. 主动更新

在主动更新缓存时,推荐先操作数据库,再删除缓存

缓存穿透

客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库

大量的请求查询不存在的数据,会对数据库造成很大的压力,可能导致数据库崩溃

缓存穿透解决方案:

  1. 缓存空对象

在这里插入图片描述

缺点:

为内存带来额外压力

可能会造成短暂的数据不一致性

  1. 布隆过滤

在缓存之前,先使用布隆过滤器判断数据是否存在。布隆过滤器是一种高效的数据结构,可以快速判断一个元素是否在集合中。如果布隆过滤器判断数据不存在,就直接返回

有点类似于双重校验机制

  1. 增加 id 复杂度

从根源解决用户请求

缓存雪崩

同一时间段大量的缓存key同时失效或者Redis服务器宕机,导致大量请求到达数据库,带来巨大压力

数据库无法同时承受如此大的查询压力,从而导致数据库崩溃,进而导致整个系统崩溃

缓存雪崩的解决方案:

  • 缓存高可用

    实现Redis的高可用

    部署多个服务器,避免单节点故障

  • 缓存预热

    提前将热点数据加入到缓存,避免大量请求直接到达数据库

    定时刷新缓存(定时预热)

  • 避免同时过期

    设置不同的缓存过期时间,避免同一时间大量缓存失效

    (可以在原有的过期时间基础增加一个随机值,使得每个缓存的过期时间分散开来)

  • 限流降级

    缓存失效后,通过加锁或者队列的方式控制对数据库的访问量,避免数据库承受过大压力

    设置合理的阈值,当请求达到阈值时进行降级处理,如返回默认数据,提示错误等

  • 数据库优化

    提高查询性能(索引,SQL语句,分库分表)

  • 多级缓存

    在Redis之前再加一层缓存(如Nginx),减小Redis的压力

  • 监控报警

    对缓存的访问量、命中率、错误率进行实时监控,异常及时报警,对问题采取特化方案

缓存击穿

小型但是致命的雪崩

一个热点的Key在缓存中失效时,大量的请求同时访问该Key,由于缓存中没有数据,所有的请求都会落到数据库上,对数据库造成巨大的压力,导致数据库宕机

热点key的缓存建立较慢

缓存击穿的解决方案:

  • 互斥锁

    热点缓存失效时,先使用互斥锁对 key 加锁,只让一个请求去读取数据库数据并再次建立缓存,其他的请求等待缓存生效时再读取缓存,可以有效防止数据库宕机,但是会很大程度影响用户体验

  • 永不过期

    将热点数据的缓存设置为永不过期,但是要保证数据一致性的问题

  • 数据预热

    热点缓存快要失效前,主动对热点数据进行数据库查询并更新缓存

  • 双缓存

    对同一个热点数据建立一个短期缓存和一个长期缓存,当短期缓存失效时,从长期缓存中读取数据并重建短期缓存,当长期缓存也失效时再从数据库中查询并建立缓存,(减少数据库的查询频率)

  • 限流

    就是限流

分布式锁

在集群条件下,每个服务器都会有一台JVM运行Tomcat,Synchronized 只能锁住当前服务器的此台JVM的Tomcat下的一个线程,所以要引入分布式锁

满足分布式系统或集群模式下多进程可见并且互斥的锁

通过Redis实现分布式锁的方法:

接口 ILock tryLock() / unlock

  1. 获取锁

    • 互斥:确保只能有一个线程获取锁

    • 非阻塞:尝试一次,成功返回true,失败返回 false

      尝试获取锁 tryLock()

    #添加锁,NX是互斥,EX是设置超时时间
    set lock thread1 nx ex 10
    
    不拆分为
    set lock thread
    expire lock 10
    的目的是为了保证起原子性,防止Redis还未执行给 key 设置 TTL 而宕机!
    
  2. 释放锁

    • 手动释放

      unlock()

    • 超时释放

    #释放锁
    del lock
    

锁的线程问题

在这里插入图片描述

解决方案:在一个线程释放锁时判断锁的标识是否一致(看看现在的锁还是不是自己一开始拿的锁)

解决锁的线程问题:

  1. 获取锁时存入线程标识(使用UUID)
  2. 在释放锁时先获取锁中的线程标识,判断是否与当前线程标识一致
    • 如果一致则释放锁
    • 如果不一致则不释放锁

uuid是区分不同服务器的,线程id是区分相同服务器的

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值