13 Redis 数据库与 Spring Boot

📅 2026/8/3 2:53:16 👁️ 阅读次数 📝 编程学习
13 Redis 数据库与 Spring Boot

第一章.NoSQL 数据库

1.1 什么是 NoSQL

1.NoSQL: Not Only SQL,泛指非关系型数据库。 2.关系型数据库: 使用表、行、列保存数据,依赖 SQL 操作。 例如 MySQL、Oracle、PostgreSQL。 3.NoSQL 数据库: 不强制使用固定表结构,通常更适合高并发、大数据量、灵活结构的数据场景。

1.2 NoSQL 适用场景

场景说明
高并发读写秒杀、热点数据、接口缓存
海量数据数据量大,关系模型不强
灵活数据结构字段变化频繁,不适合固定表结构
临时状态数据token、验证码、购物车、排行榜
Redis 常见用途: 1.缓存热点数据。 2.保存登录 token。 3.保存验证码。 4.实现分布式锁。 5.实现排行榜。 6.实现计数器。

1.3 NoSQL 不适用场景

1.需要复杂事务: 例如银行核心账务。 2.需要复杂关联查询: 多表 join、复杂报表统计。 3.需要强关系模型: 数据之间约束关系非常复杂。 4.要求 SQL 标准能力: 使用关系型数据库更合适。

1.4 常见 NoSQL 数据库

数据库类型特点
RedisKey-Value内存数据库,速度快,支持多种数据结构
MemcachedKey-Value简单缓存,数据结构较少
MongoDB文档型类 JSON 文档存储,结构灵活
Elasticsearch搜索引擎全文检索、日志分析

第二章.Redis 简介和安装

2.1 Redis 的介绍

1.Redis: Remote Dictionary Server,远程字典服务。 2.本质: 基于内存的 Key-Value 数据库。 3.特点: a.读写速度快 b.支持丰富数据类型 c.支持持久化 d.支持主从复制、哨兵、集群 e.常用于缓存和高并发场景

2.2 Redis 和 MySQL 的关系

客户端请求 ↓ 后端服务 ↓ 先查 Redis 缓存 ↓ 缓存没有再查 MySQL 数据库
1.MySQL: 负责长期、可靠地保存核心业务数据。 2.Redis: 负责保存热点数据,提高访问速度。 3.常见策略: 先查 Redis。 Redis 没有再查 MySQL。 查到后写入 Redis。

2.3 Linux 安装 Redis 常见步骤

# 解压 tar -zxvf redis-7.0.0.tar.gz # 进入目录 cd redis-7.0.0 # 编译 make # 安装 make install
1.Redis 通常通过源码编译安装。 2.安装后常见命令包括 redis-server 和 redis-cli。 3.真实版本号以本机下载的 Redis 版本为准。

2.4 Redis 启动和停止

前台启动

redis-server
前台启动: 1.当前命令窗口不能关闭。 2.适合临时测试。

后台启动

修改redis.conf:

daemonize yes

启动:

redis-server redis.conf

客户端连接

redis-cli

指定主机和端口:

redis-cli -h 127.0.0.1 -p 6379

停止服务

redis-cli shutdown

2.5 基础连接测试

127.0.0.1:6379> ping PONG
看到 PONG 表示 Redis 服务正常响应。

第三章.Redis key 操作

3.1 key 的特点

1.Redis 中所有数据都通过 key 访问。 2.key 是字符串。 3.key 应该有清晰命名规则。

命名建议:

业务名:对象名:id:字段

示例:

user:1001:name login:token:1001 product:hot:list

3.2 key 常用命令

命令说明
keys pattern按模式查询 key
exists key判断 key 是否存在
type key查看 key 对应 value 类型
del key删除 key
expire key seconds设置过期时间
ttl key查看剩余过期时间
rename old new重命名 key
dbsize查看当前库 key 数量
flushdb清空当前库
flushall清空所有库
set user:1:name tom exists user:1:name type user:1:name expire user:1:name 60 ttl user:1:name del user:1:name

生产环境慎用 keys 和 flush

keys *会扫描所有 key,数据量大时可能阻塞 Redis;flushdbflushall会清空数据,生产环境必须谨慎。


第四章.String 字符串

4.1 String 的介绍

1.String: Redis 最基础的数据类型。 2.value 可以是: 字符串 数字 JSON 字符串 二进制数据 3.常见用途: 缓存对象 JSON 计数器 验证码 token

4.2 常用命令

命令说明
set key value设置字符串
get key获取字符串
setex key seconds value设置值并指定过期时间
setnx key valuekey 不存在时才设置
mset k1 v1 k2 v2批量设置
mget k1 k2批量获取
incr key数值加 1
decr key数值减 1
incrby key n数值增加 n
append key value追加字符串
strlen key获取字符串长度

4.3 案例:验证码

setex code:login:13800000000 300 9527 get code:login:13800000000 ttl code:login:13800000000
1.验证码通常有过期时间。 2.setex 可以一次完成写入和设置过期时间。 3.key 中加入手机号可以区分不同用户。

4.4 案例:计数器

set article:1001:read 0 incr article:1001:read incrby article:1001:read 10 get article:1001:read

第五章.List 列表

5.1 List 的介绍

1.List: Redis 列表,底层可以理解为双端队列。 2.特点: a.元素有序 b.元素可重复 c.可以从左侧或右侧插入、弹出 3.常见用途: 消息队列 最新列表 栈或队列

5.2 常用命令

命令说明
lpush key value...从左侧插入
rpush key value...从右侧插入
lpop key从左侧弹出
rpop key从右侧弹出
lrange key start stop按范围查看
llen key查看长度
lindex key index按索引取值
lrem key count value删除指定元素

5.3 队列案例

rpush task:queue task1 task2 task3 lpop task:queue lpop task:queue lrange task:queue 0 -1
rpush + lpop: 先进先出,可以模拟队列。

5.4 栈案例

lpush stack:a a lpush stack:a b lpush stack:a c lpop stack:a
lpush + lpop: 后进先出,可以模拟栈。

第六章.Set 集合

6.1 Set 的介绍

1.Set: Redis 无序集合。 2.特点: a.元素无序 b.元素不可重复 c.支持交集、并集、差集 3.常见用途: 去重 共同好友 用户标签 抽奖

6.2 常用命令

命令说明
sadd key member...添加元素
smembers key查看全部元素
sismember key member判断元素是否存在
srem key member...删除元素
scard key获取元素数量
srandmember key count随机获取元素
spop key count随机弹出元素
sinter key1 key2交集
sunion key1 key2并集
sdiff key1 key2差集

6.3 案例:共同好友

sadd user:1:follows 2 3 4 5 sadd user:2:follows 3 4 6 sinter user:1:follows user:2:follows
sinter 可以求两个集合的交集。 共同关注、共同好友都可以用类似思路。

6.4 案例:抽奖

sadd lottery:user 1001 1002 1003 1004 srandmember lottery:user 2 spop lottery:user 1
1.srandmember: 随机取,不删除。 2.spop: 随机取,并删除。

第七章.Hash 哈希

7.1 Hash 的介绍

1.Hash: key 对应一个 field-value 结构。 2.适合存储: 对象数据。 3.示例: user:1001 name=tom age=18 phone=13800000000

7.2 常用命令

命令说明
hset key field value设置字段
hget key field获取字段
hmset key f1 v1 f2 v2批量设置
hmget key f1 f2批量获取
hgetall key获取全部字段和值
hdel key field...删除字段
hexists key field判断字段是否存在
hkeys key获取全部 field
hvals key获取全部 value
hincrby key field n字段数值增加

7.3 案例:保存用户对象

hset user:1001 name tom hset user:1001 age 18 hset user:1001 phone 13800000000 hget user:1001 name hgetall user:1001
Hash 比 String 存 JSON 更方便局部修改字段。 如果业务经常整体读取对象,String 存 JSON 也很常见。

第八章.ZSet 有序集合

8.1 ZSet 的介绍

1.ZSet: Sorted Set,有序集合。 2.特点: a.元素不可重复 b.每个元素有一个 score 分数 c.按照 score 排序 3.常见用途: 排行榜 热搜榜 延迟队列

8.2 常用命令

命令说明
zadd key score member添加元素和分数
zrange key start stop从小到大按排名查看
zrevrange key start stop从大到小按排名查看
zrangebyscore key min max按分数范围查询
zrank key member获取从小到大排名
zrevrank key member获取从大到小排名
zscore key member获取分数
zincrby key increment member增加分数
zrem key member删除元素

8.3 案例:文章排行榜

zadd article:rank 100 article:1 zadd article:rank 80 article:2 zadd article:rank 120 article:3 zrevrange article:rank 0 2 withscores zincrby article:rank 30 article:2 zrevrank article:rank article:2
zrevrange: 按分数从高到低查询,很适合排行榜。

第九章.Redis 综合练习

9.1 用户登录 token

setex login:token:abc123 1800 user:1001 get login:token:abc123 ttl login:token:abc123
1.登录成功后生成 token。 2.token 作为 key,用户 id 作为 value。 3.设置过期时间,到期自动失效。

9.2 商品详情缓存

setex product:1001 3600 '{"id":1001,"name":"Java课程","price":99}' get product:1001
缓存流程: 1.先查 Redis。 2.Redis 有数据直接返回。 3.Redis 没有查 MySQL。 4.查到后写入 Redis 并设置过期时间。

9.3 秒杀库存扣减

set seckill:stock:1001 100 decr seckill:stock:1001 get seckill:stock:1001
decr 是 Redis 原子命令。 但完整秒杀还需要处理库存不能小于 0、重复下单、订单异步创建等问题。

第十章.Jedis 客户端

10.1 Jedis 的介绍

1.Jedis: Java 操作 Redis 的客户端工具。 2.特点: API 和 Redis 命令名称接近。 3.使用场景: 学习 Redis Java API。 简单项目直接访问 Redis。

10.2 Maven 依赖

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

10.3 基本使用

import redis.clients.jedis.Jedis; public class JedisDemo { public static void main(String[] args) { try (Jedis jedis = new Jedis("127.0.0.1", 6379)) { String pong = jedis.ping(); System.out.println(pong); jedis.set("name", "tom"); String name = jedis.get("name"); System.out.println(name); } } }

10.4 操作常见数据类型

try (Jedis jedis = new Jedis("127.0.0.1", 6379)) { jedis.set("user:1:name", "tom"); jedis.lpush("list:demo", "a", "b", "c"); jedis.sadd("set:demo", "java", "redis", "java"); jedis.hset("user:1", "age", "18"); jedis.zadd("rank", 100, "user:1"); }

第十一章.Spring Boot 整合 Redis

11.1 Spring Data Redis

1.Spring Data Redis: Spring 提供的 Redis 操作封装。 2.常用核心对象: RedisTemplate StringRedisTemplate 3.好处: 和 Spring Boot 自动配置、连接池、序列化机制整合更方便。

11.2 添加依赖

<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>

11.3 application.yml 配置

spring: data: redis: host: 127.0.0.1 port: 6379 database: 0 timeout: 3000ms lettuce: pool: enabled: true max-active: 8 max-idle: 8 min-idle: 0 max-wait: 1000ms
1.host: Redis 服务器地址。 2.port: Redis 端口,默认 6379。 3.database: 使用第几个库,默认 0。 4.lettuce.pool: Lettuce 连接池配置。

11.4 RedisTemplate 基本使用

import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.RedisTemplate; @SpringBootTest public class RedisTemplateTest { @Autowired private RedisTemplate<String, Object> redisTemplate; @Test public void testString() { redisTemplate.opsForValue().set("name", "tom"); Object name = redisTemplate.opsForValue().get("name"); System.out.println(name); } }

11.5 方法分组

方法操作类型
opsForValue()String
opsForList()List
opsForSet()Set
opsForHash()Hash
opsForZSet()ZSet
expire()设置过期时间
delete()删除 key

11.6 StringRedisTemplate

import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import java.time.Duration; @Service public class CodeService { private final StringRedisTemplate stringRedisTemplate; public CodeService(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } public void saveCode(String phone, String code) { String key = "code:login:" + phone; stringRedisTemplate.opsForValue().set(key, code, Duration.ofMinutes(5)); } public String getCode(String phone) { return stringRedisTemplate.opsForValue().get("code:login:" + phone); } }
StringRedisTemplate: key 和 value 都按字符串处理。 学习和简单字符串场景更直观。

11.7 RedisTemplate 序列化配置

import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; 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.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); StringRedisSerializer stringSerializer = new StringRedisSerializer(); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); Jackson2JsonRedisSerializer<Object> jsonSerializer = new Jackson2JsonRedisSerializer<>(objectMapper, Object.class); template.setKeySerializer(stringSerializer); template.setHashKeySerializer(stringSerializer); template.setValueSerializer(jsonSerializer); template.setHashValueSerializer(jsonSerializer); template.afterPropertiesSet(); return template; } }
为什么要配置序列化: 1.默认 JDK 序列化结果不方便人工查看。 2.String key 更清晰。 3.JSON value 更适合对象缓存。

第十二章.Redis 配置文件

12.1 网络配置

配置说明
bind绑定允许访问的 IP
portRedis 服务端口,默认 6379
tcp-backlogTCP 连接队列
timeout客户端空闲超时时间
tcp-keepaliveTCP 心跳检测
bind 127.0.0.1 port 6379 timeout 0 tcp-keepalive 300

12.2 通用配置

配置说明
daemonize是否后台运行
pidfile进程 id 文件
databases数据库数量,默认 16
logfile日志文件
dir工作目录
daemonize yes pidfile /var/run/redis_6379.pid databases 16 dir ./

12.3 安全配置

requirepass 123456

客户端认证:

redis-cli auth 123456
如果配置了 requirepass, 客户端必须认证后才能执行命令。

12.4 内存限制配置

配置说明
maxclients最大客户端连接数
maxmemory最大可用内存
maxmemory-policy内存淘汰策略
maxmemory-samples淘汰采样数量
maxclients 10000 maxmemory 512mb maxmemory-policy allkeys-lru

12.5 常见淘汰策略

策略说明
noeviction不淘汰,写入时报错
allkeys-lru所有 key 中淘汰最近最少使用
volatile-lru设置过期时间的 key 中淘汰最近最少使用
allkeys-random所有 key 中随机淘汰
volatile-random设置过期时间的 key 中随机淘汰
volatile-ttl设置过期时间的 key 中优先淘汰快过期的

第十三章.Redis 事务和锁机制

13.1 Redis 事务

1.Redis 事务: 一次性、顺序性、排他性地执行一组命令。 2.核心命令: MULTI EXEC DISCARD WATCH UNWATCH

13.2 事务基本使用

multi set user:1:name tom set user:1:age 18 exec

放弃事务:

multi set a 1 discard

13.3 watch 乐观锁

watch balance:1001 multi decrby balance:1001 100 incrby balance:1002 100 exec
1.watch 会监控 key。 2.如果事务执行前 key 被其他客户端修改,exec 会失败。 3.这是一种乐观锁思想。

13.4 Redis 事务特点

1.单独的 Redis 命令天然原子。 2.Redis 事务能保证命令按顺序执行。 3.Redis 事务不等同于 MySQL 事务。 4.Redis 事务中某条命令运行时报错,其他命令可能仍会执行。

第十四章.Redis Lua 脚本

14.1 Lua 的作用

1.Lua: 一种轻量脚本语言。 2.Redis 支持执行 Lua 脚本。 3.核心价值: 把多条 Redis 命令作为一个脚本整体执行。 脚本执行具有原子性。

14.2 Redis 执行 Lua

eval "return redis.call('get', KEYS[1])" 1 name

参数说明:

1.eval: 执行 Lua 脚本。 2.KEYS: 传入 key 数组。 3.ARGV: 传入普通参数数组。

14.3 库存扣减 Lua 示例

local stock = tonumber(redis.call('get', KEYS[1])) local count = tonumber(ARGV[1]) if stock == nil then return -1 end if stock < count then return 0 end redis.call('decrby', KEYS[1], count) return 1
返回值含义: -1: 库存 key 不存在。 0: 库存不足。 1: 扣减成功。

14.4 Spring Boot 执行 Lua

import org.springframework.core.io.ClassPathResource; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.stereotype.Service; import java.util.Collections; @Service public class StockService { private final StringRedisTemplate stringRedisTemplate; public StockService(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } public Long deduct(String productId, String count) { DefaultRedisScript<Long> script = new DefaultRedisScript<>(); script.setLocation(new ClassPathResource("lua/deduct.lua")); script.setResultType(Long.class); String key = "stock:" + productId; return stringRedisTemplate.execute(script, Collections.singletonList(key), count); } }

第十五章.Redis 持久化

15.1 持久化的介绍

1.Redis 主要基于内存保存数据。 2.服务重启后,内存数据会丢失。 3.持久化用于把内存数据保存到磁盘。 Redis 持久化方式: 1.RDB 2.AOF

15.2 RDB 持久化

1.RDB: Redis Database Backup。 2.方式: 在指定时间点生成内存数据快照。 3.结果文件: dump.rdb。

RDB 相关命令

save bgsave
1.save: 同步保存,会阻塞 Redis。 2.bgsave: 后台保存,由子进程生成 RDB 文件。

RDB 配置

save 900 1 save 300 10 save 60 10000 dbfilename dump.rdb dir ./
save 900 1: 900 秒内至少 1 个 key 发生变化,触发 RDB。

RDB 优缺点

类型内容
优点文件紧凑,适合备份,恢复速度快
缺点两次快照之间的数据可能丢失

15.3 AOF 持久化

1.AOF: Append Only File。 2.方式: 记录每一条写命令。 3.恢复: Redis 重启时重新执行 AOF 文件中的命令。

AOF 配置

appendonly yes appendfilename "appendonly.aof" appendfsync everysec

appendfsync 策略

策略说明
always每次写命令都刷盘,安全但慢
everysec每秒刷盘,常用折中方案
no交给操作系统决定,性能好但风险高

AOF 优缺点

类型内容
优点数据丢失更少,安全性更高
缺点文件通常更大,恢复速度可能慢于 RDB

15.4 持久化选择

1.只做缓存: 可以不开持久化,或只用 RDB。 2.数据比较重要: 开启 AOF everysec。 3.兼顾备份和安全: RDB + AOF 同时开启。

第十六章.Redis 主从复制

16.1 主从复制的介绍

1.主从复制: 一台 Redis 作为主节点 master。 多台 Redis 作为从节点 replica。 2.数据流向: 主节点数据同步到从节点。 3.常见用途: a.读写分离 b.数据备份 c.故障恢复基础

16.2 主从复制作用

作用说明
读写分离主节点写,从节点读
容灾备份主节点异常时从节点有数据副本
扩展读能力多个从节点分担读请求

16.3 配置从节点

replicaof 127.0.0.1 6379

或者客户端执行:

redis-cli -p 6380 replicaof 127.0.0.1 6379

查看复制信息:

info replication

16.4 主从复制注意点

1.默认情况下从节点主要负责读。 2.写操作通常在主节点执行。 3.主从复制存在延迟。 4.主节点宕机后,普通主从不会自动选主。

第十七章.Redis 哨兵模式

17.1 哨兵模式的介绍

1.哨兵 Sentinel: Redis 高可用方案。 2.作用: a.监控主从节点状态 b.主节点宕机后自动选举新主节点 c.通知客户端新的主节点地址

17.2 哨兵配置

示例sentinel.conf:

port 26379 sentinel monitor mymaster 127.0.0.1 6379 2 sentinel down-after-milliseconds mymaster 30000 sentinel parallel-syncs mymaster 1 sentinel failover-timeout mymaster 180000

启动哨兵:

redis-sentinel sentinel.conf

17.3 哨兵工作流程

哨兵监控 master ↓ 发现 master 无响应 ↓ 多个哨兵确认主观下线和客观下线 ↓ 从 slave 中选出新的 master ↓ 其他 slave 改为复制新 master ↓ 客户端获取新的 master 地址

17.4 Spring Boot 连接哨兵

spring: data: redis: sentinel: master: mymaster nodes: - 127.0.0.1:26379 - 127.0.0.1:26380 - 127.0.0.1:26381

第十八章.Redis 集群

18.1 为什么需要集群

单机 Redis 的问题: 1.内存容量有限。 2.并发能力有限。 3.单点故障风险。 Redis Cluster: 1.通过分片保存数据。 2.多个 master 分担数据。 3.每个 master 可以配置 replica。

18.2 Redis Cluster 的 slots

1.Redis Cluster 有 16384 个槽位。 2.key 会通过算法映射到某个槽位。 3.槽位分配给不同 master。 4.访问 key 时,根据槽位找到对应节点。
0 - 5460 -> master1 5461 - 10922 -> master2 10923 - 16383 -> master3

18.3 搭建集群基本思路

1.准备多个 Redis 实例。 2.每个实例使用不同端口和配置文件。 3.启动所有 Redis 实例。 4.使用 redis-cli --cluster create 创建集群。 5.指定副本数量。

示例命令:

redis-cli --cluster create \ 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 \ 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 \ --cluster-replicas 1

18.4 集群登录

redis-cli -c -p 6379
-c: 表示以集群模式连接,支持自动重定向到正确节点。

18.5 查看集群信息

cluster info cluster nodes

18.6 集群优缺点

类型内容
优点扩展容量、分散压力、提高可用性
缺点运维复杂、跨槽多 key 操作受限、事务支持受限

18.7 Spring Boot 连接集群

spring: data: redis: cluster: nodes: - 127.0.0.1:6379 - 127.0.0.1:6380 - 127.0.0.1:6381 - 127.0.0.1:6382 - 127.0.0.1:6383 - 127.0.0.1:6384

第十九章.Redis 常见业务案例

19.1 缓存穿透

1.缓存穿透: 查询一个 Redis 和数据库都不存在的数据。 请求每次都会打到数据库。 2.解决思路: a.缓存空值 b.布隆过滤器 c.接口参数校验

缓存空值示例:

String key = "product:" + id; String json = redisTemplate.opsForValue().get(key); if (json != null) { return json; } Product product = productMapper.selectById(id); if (product == null) { redisTemplate.opsForValue().set(key, "", Duration.ofMinutes(5)); return null; } redisTemplate.opsForValue().set(key, toJson(product), Duration.ofMinutes(30)); return product;

19.2 缓存击穿

1.缓存击穿: 热点 key 过期瞬间,大量请求同时访问数据库。 2.解决思路: a.热点 key 不过期 b.互斥锁重建缓存 c.逻辑过期

19.3 缓存雪崩

1.缓存雪崩: 大量 key 同时失效,导致请求集中打到数据库。 2.解决思路: a.过期时间加随机值 b.热点数据分批预热 c.Redis 高可用 d.服务限流降级

19.4 分布式锁

加锁:

set lock:order:1001 uuid nx ex 30

释放锁 Lua 思路:

if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end
分布式锁注意: 1.加锁要设置过期时间。 2.value 要使用唯一标识。 3.释放锁要先判断 value,再删除。 4.判断和删除要用 Lua 保证原子性。