Redis 基础命令
Redis 是一个开源的、基于内存的高性能的键值存储系统,常用作数据库、缓存和消息代理。
基础命令包含:键,字符串,哈希,列表,集合,有序集合,发布订阅,事务,服务器等命令。
1. 键(Key)相关命令
1.1 DEL - 删除键
css
DEL key [key ...]
删除一个或多个键,返回被删除键的数量。
案例:
shell
> SET name "Redis"
OK
> DEL name
(integer) 1
1.2 EXISTS - 检查键是否存在
vbnet
EXISTS key [key ...]
检查一个或多个键是否存在,返回存在的键的数量。
案例:
sql
> SET age 10
OK
> EXISTS age
(integer) 1
> EXISTS non_existing_key
(integer) 0
1.3 EXPIRE - 设置过期时间
vbnet
EXPIRE key seconds
为键设置过期时间(秒)。
案例:
shell
> SET session:1234 "user_data"
OK
> EXPIRE session:1234 3600 # 1小时后过期
(integer) 1
> TTL session:1234 # 查看剩余时间
(integer) 3598
1.4 KEYS - 查找键
sql
KEYS pattern
查找所有符合给定模式的键。
案例:
sql
> MSET user:1 "Alice" user:2 "Bob" product:1 "Book"
OK
> KEYS user:*
1) "user:1"
2) "user:2"
2. 字符串(String)相关命令
场景:字符串是最基本的数据类型,可以用来存储任何形式的字符串,包括二进制数据。它可以用于缓存简单的数据,比如配置信息,字典,用户登录Token等。
2.1 SET/GET - 设置/获取值
vbnet
SET key value [EX seconds] [PX milliseconds] [NX|XX]
GET key
设置或获取字符串值。
案例:
shell
> SET website "redis.io"
OK
> GET website
"redis.io"
# 带过期时间的设置
> SET token "abc123" EX 3600
OK
# 仅当键不存在时设置
> SET username "admin" NX
OK
> SET username "newadmin" NX
(nil) # 设置失败
2.2 INCR/DECR - 自增/自减
vbnet
INCR key
DECR key
INCRBY key increment
DECRBY key decrement
对数字值进行原子性的增减操作。
案例:
markdown
> SET counter 100
OK
> INCR counter
(integer) 101
> INCRBY counter 10
(integer) 111
> DECR counter
(integer) 110
> DECRBY counter 5
(integer) 105
2.3 MSET/MGET - 批量设置/获取
vbnet
MSET key value [key value ...]
MGET key [key ...]
一次性设置或获取多个键值。
案例:
arduino
> MSET key1 "Hello" key2 "World"
OK
> MGET key1 key2
1) "Hello"
2) "World"
3. 哈希(Hash)相关命令
场景:哈希类型允许你存储键值对集合,其中每个键都是一个字符串,每个值可以是字符串或数字。它非常适合存储对象信息,如用户信息、商品信息等。
3.1 HSET/HGET - 设置/获取哈希字段
vbnet
HSET key field value [field value ...]
HGET key field
操作哈希表中的字段。
案例:
sql
> HSET user:1000 username "antirez" birthyear 1977
(integer) 2
> HGET user:1000 username
"antirez"
> HGET user:1000 birthyear
"1977"
3.2 HGETALL - 获取所有字段和值
vbnet
HGETALL key
获取哈希表中所有的字段和值。
案例:
sql
> HGETALL user:1000
1) "username"
2) "antirez"
3) "birthyear"
4) "1977"
3.3 HINCRBY - 哈希字段自增
vbnet
HINCRBY key field increment
增加哈希表中数字字段的值。
案例:
bash
> HSET product:1000 views 100
(integer) 1
> HINCRBY product:1000 views 1
(integer) 101
> HINCRBY product:1000 views 10
(integer) 111
4. 列表(List)相关命令
场景:列表类型可以用来存储一系列有序的数据项,如队列、消息列表等。它非常适合实现消息队列、任务队列等场景。
4.1 LPUSH/RPUSH - 从头部/尾部插入元素
css
LPUSH key element [element ...]
RPUSH key element [element ...]
从列表的头部或尾部插入一个或多个元素。
案例:
bash
> LPUSH mylist "world"
(integer) 1
> LPUSH mylist "hello"
(integer) 2
> RPUSH mylist "!"
(integer) 3
> LRANGE mylist 0 -1
1) "hello"
2) "world"
3) "!"
4.2 LPOP/RPOP - 从头部/尾部弹出元素
css
LPOP key [count]
RPOP key [count]
从列表的头部或尾部移除并返回元素。
案例:
bash
> RPUSH numbers 1 2 3 4 5
(integer) 5
> LPOP numbers
"1"
> RPOP numbers
"5"
> LRANGE numbers 0 -1
1) "2"
2) "3"
3) "4"
4.3 LLEN - 获取列表长度
vbnet
LLEN key
获取列表的长度。
案例:
bash
> LPUSH tasks "task1" "task2" "task3"
(integer) 3
> LLEN tasks
(integer) 3
5. 集合(Set)相关命令
场景:集合类型用来存储不重复的字符串元素,它非常适合需要快速查找、删除和添加元素的应用场景,如标签系统、社交网络中的共同好友、共同群组等
5.1 SADD - 添加元素
sql
SADD key member [member ...]
向集合添加一个或多个成员
案例:
bash
> SADD tags "redis" "database" "nosql"
(integer) 3
5.2 SMEMBERS - 获取所有成员
vbnet
SMEMBERS key
返回集合中的所有成员。
案例:
arduino
> SMEMBERS tags
1) "nosql"
2) "database"
3) "redis"
5.3 SISMEMBER - 检查成员是否存在
sql
SISMEMBER key member
判断成员是否在集合中。
案例:
bash
> SISMEMBER tags "redis"
(integer) 1
> SISMEMBER tags "sql"
(integer) 0
5.4 SINTER - 集合交集
vbnet
SINTER key [key ...]
返回多个集合的交集。
案例:
bash
> SADD set1 a b c
(integer) 3
> SADD set2 b c d
(integer) 3
> SINTER set1 set2
1) "b"
2) "c"
6. 有序集合(Sorted Set)相关命令
场景:有序集合和集合类似,但它为每个成员关联了一个分数(double类型),这使得它可以按照分数来排序成员。它非常适合实现排行榜、带权重的标签系统等
6.1 ZADD - 添加元素
css
ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
向有序集合添加一个或多个成员,或更新已存在成员的分数
案例:
bash
> ZADD leaderboard 100 "player1" 200 "player2" 300 "player3"
(integer) 3
6.2 ZRANGE - 获取范围内的元素
vbnet
ZRANGE key start stop [WITHSCORES]
通过索引区间返回有序集合指定区间内的成员。
案例:
bash
> ZRANGE leaderboard 0 -1 # 获取所有成员
1) "player1"
2) "player2"
3) "player3"
> ZRANGE leaderboard 0 -1 WITHSCORES # 带分数返回
1) "player1"
2) "100"
3) "player2"
4) "200"
5) "player3"
6) "300"
6.3 ZREVRANGE - 逆序获取范围内的元素
vbnet
ZREVRANGE key start stop [WITHSCORES]
返回有序集中指定区间内的成员,通过索引,分数从高到低。
案例:
arduino
> ZREVRANGE leaderboard 0 -1 WITHSCORES
1) "player3"
2) "300"
3) "player2"
4) "200"
5) "player1"
6) "100"
7. 发布/订阅相关命令
场景:实时消息通知、事件广播
7.1 PUBLISH - 发布消息
PUBLISH channel message
向指定的频道发布消息。
7.2 SUBSCRIBE - 订阅频道
css
SUBSCRIBE channel [channel ...]
订阅一个或多个频道。
案例: 在客户端1:
vbnet
> SUBSCRIBE news
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "news"
3) (integer) 1
在客户端2:
bash
> PUBLISH news "Hello Redis!"
(integer) 1
客户端1将收到:
arduino
1) "message"
2) "news"
3) "Hello Redis!"
8. 事务相关命令
8.1 MULTI/EXEC - 事务执行
sql
MULTI
命令1
命令2
...
EXEC
标记一个事务块的开始,然后执行所有在 MULTI 和 EXEC 之间的命令。
案例:
shell
> MULTI
OK
> SET name "Alice"
QUEUED
> SET age 30
QUEUED
> INCR counter
QUEUED
> EXEC
1) OK
2) OK
3) (integer) 1
8.2 WATCH - 监视键
vbnet
WATCH key [key ...]
监视一个或多个键,如果在事务执行之前这些键被其他命令改动,那么事务将被打断。
案例:
shell
> WATCH balance
OK
> MULTI
OK
> DECRBY balance 100
QUEUED
> EXEC # 如果在执行前balance被其他客户端修改,则返回(nil),unwach 取消监控
9. 服务器相关命令
9.1 FLUSHALL - 清空所有数据库
css
FLUSHALL [ASYNC]
删除所有数据库的所有键。
9.2 INFO - 获取服务器信息
css
INFO [section]
返回关于 Redis 服务器的各种信息和统计数值。
案例:
makefile
> INFO server
# Server
redis_version:6.2.4
redis_git_sha1:00000000
...
9.3 DBSIZE - 获取键数量
DBSIZE
返回当前数据库的 key 的数量。
案例:
bash
> DBSIZE
(integer) 42
Redis 管道
一、Redis 管道技术详解
1. 管道技术原理
Redis 管道技术(Pipeline)的核心原理是通过减少网络往返时间(RTT) 和提升服务器吞吐量来优化批量操作的性能。
本质是将多次网络往返合并为一次,通过协议层的批量数据传输和服务器端的顺序处理实现性能飞跃。
-
减少网络往返时间(RTT)
-
传统模式:客户端每发送一个命令都需要等待服务器返回结果后才能发送下一个命令,网络延迟(RTT)会成为性能瓶颈。
- 例如:执行100次
GET
命令需要100次RTT。
- 例如:执行100次
-
管道模式:客户端将多个命令一次性批量发送到服务器,服务器按顺序处理并批量返回结果。
- 例如:100次
GET
命令仅需1次RTT(或少量RTT,取决于数据包大小限制)
- 例如:100次
makefile传统模式: 客户端: 命令1 → 等待 → 响应1 → 命令2 → 等待 → 响应2 → ... 管道模式: 客户端: 命令1 → 命令2 → 命令3 → ... → 响应1, 响应2, 响应3, ...
-
-
服务端批量处理
- 缓冲队列:Redis服务器将管道中的命令缓存在内存队列中,按顺序依次执行。
-
适用场景
- 批量初始化数据
- 大批量数据导入
- 需要执行多个无依赖关系的命令
- 对原子性要求不高的批量操作
2. 管道 vs 普通模式
特性 | 普通模式 | 管道(Pipeline) |
---|---|---|
网络消耗 | 高(N次往返) | 低(1次往返) |
原子性 | 无 | 无 |
执行顺序 | 顺序执行 | 顺序执行 |
错误处理 | 立即报错 | 继续执行 |
适用场景 | 单命令操作 | 批量非关联命令 |
3. 性能对比
假设网络延迟为 1ms,Redis 服务器处理每个命令需要 0.1ms:
- 普通模式 执行 100 次 SET:
100 * (1 + 0.1) = 110ms
- 管道模式 执行 100 次 SET:
1 + (100 * 0.1) = 11ms
二、SpringBoot 中使用 Redis 管道
1. 基础配置
首先确保项目中已配置 Redis:
xml
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
yaml
# application.yml
spring:
redis:
host: localhost
port: 6379
password:
database: 0
2. 完整实战案例
批量写入数据
vbnet
@Service
public class RedisPipelineService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 普通方式批量插入(对比用)
*/
public long batchInsertNormal(Map<String, String> data) {
long start = System.currentTimeMillis();
data.forEach((key, value) -> {
stringRedisTemplate.opsForValue().set(key, value);
});
long end = System.currentTimeMillis();
return end - start;
}
/**
* 使用管道批量插入数据
* @param data 要插入的键值对
* @return 执行时间(毫秒)
*/
public long batchInsertWithPipeline(Map<String, String> data) {
long start = System.currentTimeMillis();
List<Object> results = stringRedisTemplate.executePipelined((RedisCallback<Object>) connection -> {
data.forEach((key, value) -> {
connection.set(key.getBytes(), value.getBytes());
});
return null;
});
long end = System.currentTimeMillis();
return end - start;
}
}
性能测试对比
typescript
@RestController
public class BenchmarkController {
@Autowired
private RedisPipelineService redisPipelineService;
@GetMapping("/benchmark")
public String benchmark() {
// 准备测试数据(1000个键值对)
Map<String, String> testData = new HashMap<>();
for (int i = 0; i < 1000; i++) {
testData.put("key:" + i, "value:" + i);
}
// 普通模式测试
long normalTime = redisPipelineService.batchInsertNormal(testData);
// 管道模式测试
long pipelineTime = redisPipelineService.batchInsertWithPipeline(testData);
// 删除测试数据
stringRedisTemplate.delete(testData.keySet());
return String.format("普通模式: %dms, 管道模式: %dms, 性能提升: %.2f%%",
normalTime, pipelineTime,
(normalTime - pipelineTime) * 100.0 / normalTime);
}
}
典型输出结果:
makefile
普通模式: 718ms, 管道模式: 82ms, 性能提升: 88.58%
通过合理使用 Redis 管道技术,可以显著提升 SpringBoot 应用与 Redis 交互的性能,特别是在批量操作场景下。但需要注意根据实际业务场景选择合适的技术方案,平衡性能与功能需求。
三、最佳实践与注意事项
-
批量大小控制:
- 建议每批命令控制在 1000-5000 个
- 过大的批量会导致内存消耗和 Redis 阻塞
-
错误处理:如果管道中某条命令出错,其他命令仍会继续执行
-
非原子性:管道中的命令不是原子性的,中间可能会被其他客户端的命令插入
-
连接管理:
- 管道会占用连接直到所有命令执行完成
- 避免在管道中执行耗时操作,防止连接池耗尽
-
监控与调优:
- 监控管道执行时间:
redisTemplate.executePipelined(...)
的耗时 - 根据实际网络延迟调整批量大小
- 监控管道执行时间:
Redis 事务
一、Redis 事务核心概念
1. Redis 事务特性
Redis 事务通过 MULTI/EXEC/DISCARD/WATCH 命令实现,具有以下特点:
- 原子性:事务中的命令要么全部执行,要么全部不执行
- 隔离性:事务执行过程中不会被其他客户端命令打断
- 无回滚机制:Redis 事务不支持回滚(与关系型数据库不同)
- 单线程保证:Redis 单线程特性自然保证了命令的顺序执行
2. Redis 事务核心命令
命令 | 作用 | 说明 |
---|---|---|
MULTI |
开启事务 | 标记事务开始,后续命令会加入队列 |
EXEC |
执行事务 | 执行队列中的所有命令 |
DISCARD |
取消事务 | 清空命令队列,放弃执行 |
WATCH |
监视键 | 乐观锁机制,如果被监视的键被修改,事务失败 |
UNWATCH |
取消监视 | 取消所有 WATCH 监视 |
3. 事务执行流程
-
MULTI
:开启事务。 -
命令入队:后续命令不会立即执行,而是加入队列。
-
EXEC
:执行队列中的所有命令,返回结果数组。 -
DISCARD
(可选):放弃事务,清空队列。scss+-------------------+ +-------------------+ | Client | | Redis Server | | | | | | 1. MULTI | | | | ───────────────┼───────► 2. 开启事务模式 | | | | (标记事务开始) | | | | | | 3. 命令入队 | | | | (CMD1,CMD2,...) | | | | ───────────────┼───────► 4. 返回QUEUED | | | | (命令存入队列) | | | | | | 5. EXEC | | | | ───────────────┼───────► 6. 原子性执行队列 | | | | (顺序执行所有命令)| | | | | | 7. 接收批量响应 ◄───┼─────── 8. 返回结果数组 | | | | | +-------------------+ +-------------------+
4. 基本事务案例
示例 1:简单事务(MULTI + EXEC)
ruby
127.0.0.1:6379> MULTI # 开启事务
OK
127.0.0.1:6379> SET name "Alice"
QUEUED # 命令进入队列
127.0.0.1:6379> INCR counter
QUEUED
127.0.0.1:6379> GET name
QUEUED
127.0.0.1:6379> EXEC # 执行事务
1) OK # SET name "Alice" 的结果
2) (integer) 1 # INCR counter 的结果
3) "Alice" # GET name 的结果
示例 2:事务失败(语法错误)
如果命令在入队时就有语法错误(如 SET
写错),整个事务不会执行:
ruby
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET name "Bob"
QUEUED
127.0.0.1:6379> INCRR counter # 错误命令(INCRR 不存在)
(error) ERR unknown command 'INCRR'
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
示例 3:运行时错误(不影响其他命令)
如果命令在运行时出错(如对字符串 INCR
),仅该命令失败,其他命令仍执行:
ruby
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET age "twenty"
QUEUED
127.0.0.1:6379> INCR age # 对字符串执行 INCR 会失败
QUEUED
127.0.0.1:6379> SET score 100
QUEUED
127.0.0.1:6379> EXEC
1) OK # SET age "twenty" 成功
2) (error) ERR value is not an integer or out of range # INCR age 失败
3) OK # SET score 100 仍执行
5. WATCH 乐观锁机制
WATCH
用于实现 CAS(Check-And-Set) 操作,监视一个或多个键,如果事务执行前这些键被修改,则事务失败。
Redis 的 CAS 机制通过以下流程工作:
- 客户端
WATCH
要监控的键 - 检查当前值
- 在
MULTI
/EXEC
事务中修改值 - 如果被监控的键在
WATCH
和EXEC
之间被修改过,则整个事务失败
示例 4:使用 WATCH 实现余额扣减
ruby
# 客户端1
127.0.0.1:6379> SET balance 100
OK
127.0.0.1:6379> WATCH balance # 监视 balance
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY balance 20
QUEUED
127.0.0.1:6379> EXEC # 如果 balance 未被其他客户端修改,事务执行成功
1) (integer) 80
# 客户端2(在客户端1 EXEC 前修改 balance)
127.0.0.1:6379> INCRBY balance 50
(integer) 150
# 此时客户端1的 EXEC 会返回 (nil),事务失败
127.0.0.1:6379> EXEC
(nil)
UNWATCH 取消监视
ruby
127.0.0.1:6379> WATCH balance
OK
127.0.0.1:6379> UNWATCH # 取消所有监视
OK
二、SpringBoot 实现 Redis 事务
1. 基础配置
确保已添加 Spring Data Redis 依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置 Redis 连接(application.yml):
yaml
spring:
redis:
host: localhost
port: 6379
password:
database: 0
2. 事务实现方式
方式1:使用 SessionCallback
csharp
@Service
public class RedisTransactionService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 基本事务操作
*/
public void basicTransaction() {
List<Object> results = stringRedisTemplate.execute(new SessionCallback<List<Object>>() {
@Override
public List<Object> execute(RedisOperations operations) throws DataAccessException {
operations.multi(); // 开启事务
operations.opsForValue().set("tx_key1", "value1");
operations.opsForValue().set("tx_key2", "value2");
operations.opsForValue().get("tx_key1");
return operations.exec(); // 执行事务
}
});
// 第三个命令(GET)的结果
System.out.println("事务中获取的值: " + results.get(2));
}
}
方式2:使用 Lambda 简化
csharp
public void lambdaTransaction() {
List<Object> results = stringRedisTemplate.execute(new SessionCallback<List<Object>>() {
@Override
public List<Object> execute(RedisOperations operations) throws DataAccessException {
operations.multi();
operations.opsForValue().set("product:stock:1001", "10");
operations.opsForValue().decrement("product:stock:1001"); // 减库存
operations.opsForList().leftPush("order:queue", "order1001");
return operations.exec();
}
});
System.out.println("减库存结果: " + results.get(1));
}
三、事务使用场景及案例
秒杀系统实现
typescript
@Service
public class SeckillService {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String SECKILL_STOCK_PREFIX = "seckill:stock:";
private static final String SECKILL_SUCCESS_USERS = "seckill:success:users:";
public boolean seckillProduct(String productId, String userId) {
// 1. 验证用户是否已经秒杀过
if (Boolean.TRUE.equals(
redisTemplate.opsForSet().isMember(SECKILL_SUCCESS_USERS + productId, userId))) {
return false;
}
// 2. 使用WATCH监控库存
redisTemplate.watch(SECKILL_STOCK_PREFIX + productId);
try {
// 3. 获取当前库存
String stockStr = redisTemplate.opsForValue()
.get(SECKILL_STOCK_PREFIX + productId);
if (stockStr == null || Integer.parseInt(stockStr) <= 0) {
redisTemplate.unwatch();
return false;
}
// 4. 开始事务
List<Object> results = redisTemplate.execute(new SessionCallback<List<Object>>() {
@Override
public List<Object> execute(RedisOperations operations) throws DataAccessException {
operations.multi();
operations.opsForValue().decrement(SECKILL_STOCK_PREFIX + productId);
operations.opsForSet().add(SECKILL_SUCCESS_USERS + productId, userId);
return operations.exec();
}
});
// 5. 判断事务是否执行成功
return results != null && !results.isEmpty();
} catch (Exception e) {
redisTemplate.unwatch();
throw e;
}
}
}
五、注意事项与最佳实践
-
Redis事务与关系型数据库事务的区别
- Redis事务没有隔离级别概念
- Redis事务执行期间不会被其他客户端中断
- Redis事务出错后不会回滚已执行的命令
-
性能考虑
- 事务会阻塞Redis服务器,避免在事务中包含大量命令
- 对于纯写入场景,考虑使用管道(pipeline)代替事务
-
WATCH使用建议
- 监视的key不宜过多
- 被WATCH的key如果频繁被修改,会导致事务频繁失败
- 考虑结合重试机制
-
事务超时问题
- Redis默认不限制事务执行时间
- 长时间运行的事务会导致连接占用
- 建议设置合理的超时时间
通过合理使用Redis事务,可以在SpringBoot应用中实现简单的原子性操作。对于复杂的业务场景,可以结合管道、Lua脚本等技术实现更高效可靠的Redis操作。
事务 vs 管道(Pipeline)
特性 | 事务(MULTI/EXEC) | 管道(Pipeline) |
---|---|---|
原子性 | ✅ 全部成功或失败 | ❌ 单条失败不影响其他 |
执行方式 | 命令在 EXEC 时执行 |
命令立即发送,批量返回 |
用途 | 需要原子性操作(如转账) | 批量操作优化(减少RTT) |
错误处理 | 运行时错误不影响后续命令 | 单条失败不影响其他 |