Redis基础知识
bash
#切换数据库
bd:0>select 2
"OK"
bd:2>dbsize
"0"
#清空数据库
bd:0>flushdb
"OK"
#设置值
bd:0>set name "lyt"
"OK"
#查看所有key
bd:0>keys *
1) "name"
#获取key
bd:0>get name
"lyt"
#查看key是否存在
bd:0>exists name
"1"
#设置过期事件
bd:0>expire name 10
"1"
#查看剩余过期时间
bd:0>ttl name
"2"
#查看key的类型
bd:0>type name
"string"
String类型
bash
#拼接字符串
bd:0>append name lyt
"6"
#获取字符串
bd:0>get name
"zjclyt"
#获取长度
bd:0>strlen name
"6"
#设置值
bd:0>set views 0
"OK"
#自增
bd:0>incr views
"1"
#指定自增
bd:0>incrby views 3
"4"
#自减
bd:0>decr views
"3"
#设置过期时间
bd:0>setnx lock haha
"1"
#设置后key值不可修改
bd:0>setnx lock xixi
"0"
bd:0>get lock
"haha"
bd:0>
#先get再set
bd:0>getset k1 v1
null
bd:0>getset k1 v2
"v1"
List类型
在redis中,我们可以把list玩成队列、栈或者阻塞队列!
bash
#从左插入
bd:0>lpush list 1
"1"
bd:0>lpush list 2
"2"
bd:0>lpush list 3
"3"
#从左查看所有元素
bd:0>lrange list 0 -1
1) "3"
2) "2"
3) "1"
#查看指定位置的元素,下标是从0开始的,[1,2]
bd:0>lrange list 1 2
1) "2"
2) "1"
#从左边弹出一个数据
bd:0>lpop list
"3"
#获取下标位置的元素
bd:0>lindex list 1
"1"
#查看元素长度
bd:0>llen list
"2"
#从左移除第一个为1的元素 lrem key num value
bd:0>lrem list 1 1
"1"
bd:0>lrange list 0 -1
1) "2"
Set类型
bash
#添加元素
bd:0>sadd set v1
"1"
bd:0>sadd set v2
"1"
bd:0>sadd set v3
"1"
#查看所有元素
bd:0>smembers set
1) "v2"
2) "v3"
3) "v1"
bd:0>smembers set
1) "v2"
2) "v3"
3) "v1"
#查看指定元素是否存在
bd:0>sismember set v1
"1"
bd:0>sismember set v4
"0"
#查看元素是否存在
bd:0>scard set
"3"
#移除指定元素
bd:0>srem set v1
"1"
bd:0>scard set
"2"
#随机获取一个元素
bd:0>srandmember set
"v3"
bd:0>srandmember set
"v2"
bd:0>srandmember set
"v2"
bd:0>spop set
"v2"
bash
bd:0>sadd k1 a
"1"
bd:0>sadd k1 b
"1"
bd:0>sadd k1 c
"1"
bd:0>sadd k2 c
"1"
bd:0>sadd k2 d
"1"
bd:0>sadd k2 e
"1"
#差集
bd:0>sdiff k1 k2
1) "a"
2) "b"
bd:0>sdiff k2 k1
1) "d"
2) "e"
#并集
bd:0>sunion k1 k2
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
#交集
bd:0>sinter k1 k2
1) "c"
#移动元素
bd:0>smove k1 k2 c
"1"
Hash类型
bash
#设置值
bd:0>hset user k1 v1
"1"
bd:0>hset user k12 v2
"1"
#取特定key的值
bd:0>hget user v1
null
bd:0>hget user k1
"v1"
#批量设置值
bd:0>hmset user k3 v3 k4 v4
"OK"
#获取所有key和value
bd:0>hgetall user
1) "k1"
2) "v1"
3) "k12"
4) "v2"
5) "k3"
6) "v3"
7) "k4"
8) "v4"
#删除值
bd:0>hdel user k3
"1"
#获取长度
bd:0>hlen user
"3"
#判断key是否存在
bd:0>hexists user k2
"0"
bd:0>hexists user k3
"0"
bd:0>hdel user k4
"1"
bd:0>hkeys user
1) "k1"
2) "k12"
bd:0>hvals user
1) "v1"
2) "v2"
Zset类型
案例思路:st排序存储班级成绩表,工资表排序!
普通消息,1,重要消息2,带权重进行判断!
排行榜应用实现,取Top N
bash
#添加元素(携带分数)
bd:0>zadd rank 1 lyt
"1"
bd:0>zadd rank 2 zjc
"1"
bd:0>zadd rank 3 hyw
"1"
#按照分数排名 从小到大
bd:0>zrangebyscore rank -inf +inf
1) "lyt"
2) "zjc"
3) "hyw"
#获取所有值携带分数
bd:0>zrangebyscore rank -inf +inf withscores
1) "lyt"
2) "1"
3) "zjc"
4) "2"
5) "hyw"
6) "3"
#获取 (-∞,2]的数据
bd:0>zrangebyscore rank -inf 2 withscores
1) "lyt"
2) "1"
3) "zjc"
4) "2"
#获取数据 从大到小
bd:0>zrevrange rank 0 -1
1) "hyw"
2) "zjc"
3) "lyt"
#移除指定元素
bd:0>zrem rank lyt
"1"
#元素大小
bd:0>zcard rank
"2"
#分数在[1,1]的元素
bd:0>zcount rank 1 1
"0"
#分数在[1,2]的元素
bd:0>zcount rank 1 2
"1"
使用场景
延迟队列
延迟队列中的消息在指定时间后才能被消费,常用于定时任务、订单超时等场景。
实现方案
有序集合(ZSET)
-
原理:将消息的到期时间戳作为分数(score),消息内容作为成员(value)。消费者定期轮询ZSET,获取当前时间之前到期的消息。
-
关键命令:
添加消息(延迟30秒)
ZADD delay_queue <current_timestamp+30> "message"
轮询到期消息
ZRANGEBYSCORE delay_queue 0 <current_timestamp> WITHSCORES ZREMRANGEBYSCORE delay_queue 0 <current_timestamp>
-
优点:简单高效,天然支持排序。
-
缺点:需主动轮询,间隔时间需权衡实时性和性能。
应用场景
-
订单超时未支付自动关闭。
-
定时提醒或通知。
-
异步任务调度。
优先级队列
优先级队列中,高优先级的消息先被消费,适用于VIP服务、紧急任务处理。
实现方案
有序集合(ZSET)
-
原理:将优先级作为分数(score),消息作为成员,按分数排序。
-
关键命令:
插入消息(优先级为3)
ZADD pri_queue 3 "message"
获取最高优先级消息(分数范围0-5)
ZRANGEBYSCORE pri_queue 5 0 WITHSCORES LIMIT 0 1
-
优点:动态调整优先级,支持范围查询。
-
缺点:需处理并发消费(需Lua脚本保证原子性)。
应用场景
-
客服系统优先处理VIP用户请求。
-
日志处理中优先处理错误日志。
-
任务调度中高优先级任务插队。
队列类型 | 数据结构 | 实时性 | 可靠性 | 适用场景 |
---|---|---|---|---|
延迟队列 | ZSET/Redisson | 中 | 高 | 定时任务、订单超时 |
优先级队列 | 多队列/ZSET | 高 | 中 | VIP服务、紧急任务 |
事务消息的核心概念
1. 什么是事务消息?
事务消息是一种 保证消息发送与本地事务原子性 的机制,解决以下问题:
场景:业务操作(如扣减库存)和消息发送(如订单创建通知)需要同时成功或失败。
挑战:直接发送消息可能导致:
消息发送成功,本地事务失败 → 消费者收到无效消息。
本地事务成功,消息发送失败 → 业务状态不一致。
2. 事务消息的实现原理
通过 两阶段提交(2PC) 实现:
-
Prepare 阶段:
-
将消息暂存到中间存储(如 Redis 延迟队列)。
-
执行本地事务。
-
-
Commit/Rollback 阶段:
-
本地事务成功 → 提交消息到 Kafka。
-
本地事务失败 → 丢弃消息。
-
3. 代码中的事务消息逻辑
阶段 | 代码行为 | 目的 |
---|---|---|
Prepare | 消息存入 Redis 延迟队列 | 暂存消息,等待事务结果 |
Commit | 发送消息到 Kafka,删除消息 | 保证消息与事务同时成功 |
Rollback | 直接删除消息 | 保证消息与事务同时失败 |
四、关键设计点
1. 延迟队列的作用
-
防丢失:本地事务执行期间消息暂存,避免因系统崩溃导致消息丢失。
-
超时处理:若事务未完成,延迟队列可触发重试或告警。
2. 同步发送的权衡
template.send(...).get(); // 同步等待发送结果
-
优点:确保消息发送成功后才删除延迟队列中的消息。
-
缺点:阻塞当前线程,影响性能(可优化为异步 + 回调)。
java
/**
* 发送事务消息
*
* @param topic 消息主题
* @param payload 消息内容
* @param ordering 顺序消息标识
* @param partition 指定分区
* @param callback 本地事务回调函数
* @param <K> 顺序标识类型
* @param <V> 消息载体类型
*/
public static <K, V> void send(@NonNull String topic, @NonNull V payload, K ordering, Integer partition,
@NonNull Predicate<String> callback) {
// 将事务消息放入延时队列
String id = StringUtils.uuid();
Session session = SessionContextHolder.getSession(false);
TransactionMessage transaction = TransactionMessage.builder().id(id).topic(topic).payload(payload)
.ordering(ordering).partition(partition).session(session).build();
String message = JacksonUtils.serialize(transaction);
DelayRedisQueue<String> queue = getTransactionQueue();
queue.offer(message, Duration.ofMillis(queue.getTimeout()));
// 处理本地事务
boolean committable;
try {
committable = callback.test(id);
} catch (Exception e) {
// 本地事务处理异常则删除事务延时消息
try {
queue.remove(message);
} catch (Throwable t) {
log.error("Kafka transaction message remove failed: {}", message, t);
}
throw e;
}
// 本地事务处理成功则发送Kafka消息
if (committable) {
KafkaTemplate<K, V> template = getKafkaTemplate();
try {
template.send(topic, partition, ordering, payload).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
// 处理完成后删除事务延时消息
try {
queue.remove(message);
} catch (Throwable t) {
log.error("Kafka transaction message remove failed: {}", message, t);
}
}