Redis作为当下最流行的内存数据库之一,在互联网应用中扮演着至关重要的角色。这次我想分享的是在实际项目中整合Redis的完整经验,涵盖Spring Boot和Node.js两大技术栈的集成方案,以及Redisson分布式锁的实战应用,最后还会通过一个秒杀系统的案例来展示Redis在高并发场景下的威力。
我经历过多个电商和金融项目,发现Redis的合理使用往往能成为系统性能的关键转折点。特别是在分布式锁和秒杀场景中,Redis的表现直接决定了系统的稳定性和用户体验。下面就从基础整合开始,逐步深入到高级应用场景。
Spring Boot对Redis的支持非常友好,默认使用的是Lettuce客户端。Lettuce基于Netty实现,相比传统的Jedis有着更好的性能和资源管理能力。但在某些场景下,Jedis的简单直接可能更符合需求。
Maven依赖配置:
xml复制<!-- Lettuce (Spring Boot默认) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 如果需要使用Jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
配置示例(application.yml):
yaml复制spring:
redis:
host: 127.0.0.1
port: 6379
password: yourpassword
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: -1ms
# 如果使用Jedis
# jedis:
# pool:
# max-active: 8
# max-idle: 8
# min-idle: 0
代码示例:
java复制@RestController
public class RedisController {
@Autowired
private StringRedisTemplate redisTemplate;
@GetMapping("/set")
public String setValue(@RequestParam String key, @RequestParam String value) {
redisTemplate.opsForValue().set(key, value);
return "OK";
}
@GetMapping("/get")
public String getValue(@RequestParam String key) {
return redisTemplate.opsForValue().get(key);
}
}
注意:在生产环境中,一定要配置合理的连接池参数。过小的连接池会导致高并发时请求阻塞,过大的连接池则会浪费资源。
Node.js生态中最流行的Redis客户端是ioredis,它支持Cluster、Sentinel等高级特性,性能也非常出色。
安装:
bash复制npm install ioredis
基础使用示例:
javascript复制const Redis = require('ioredis');
const redis = new Redis({
port: 6379,
host: '127.0.0.1',
password: 'yourpassword',
db: 0,
});
// 设置值
await redis.set('key', 'value');
// 获取值
const value = await redis.get('key');
连接池配置:
javascript复制const Redis = require('ioredis');
const redis = new Redis({
port: 6379,
host: '127.0.0.1',
password: 'yourpassword',
db: 0,
maxRetriesPerRequest: 3, // 最大重试次数
enableOfflineQueue: false, // 是否启用离线队列
connectTimeout: 5000, // 连接超时时间(毫秒)
});
在分布式系统中,当多个服务实例需要访问共享资源时,传统的单机锁机制就失效了。Redis提供的SETNX命令虽然可以实现简单的分布式锁,但要实现一个健壮的分布式锁还需要考虑很多细节:
Redisson很好地解决了这些问题,提供了完善的分布式锁实现。
Maven依赖:
xml复制<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.16.8</version>
</dependency>
配置示例:
yaml复制spring:
redis:
redisson:
config: |
singleServerConfig:
address: "redis://127.0.0.1:6379"
password: "yourpassword"
database: 0
connectionPoolSize: 64
connectionMinimumIdleSize: 24
subscriptionConnectionPoolSize: 50
基本锁使用:
java复制@Autowired
private RedissonClient redisson;
public void doSomethingWithLock() {
RLock lock = redisson.getLock("myLock");
try {
// 尝试加锁,最多等待100秒,上锁后30秒自动解锁
boolean isLocked = lock.tryLock(100, 30, TimeUnit.SECONDS);
if (isLocked) {
// 执行业务逻辑
doBusiness();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
公平锁使用:
java复制RLock fairLock = redisson.getFairLock("myFairLock");
fairLock.lock(30, TimeUnit.SECONDS);
try {
// 业务逻辑
} finally {
if (fairLock.isHeldByCurrentThread()) {
fairLock.unlock();
}
}
实战经验:在使用分布式锁时,一定要确保锁最终会被释放。建议使用try-finally结构,并在finally块中检查当前线程是否持有锁后再释放。
锁的粒度:锁的粒度要尽可能小,只锁必要的资源。过大的锁范围会导致性能下降。
锁的命名:锁的名称要有业务含义,避免不同业务使用相同的锁名。
锁的过期时间:设置合理的过期时间,既不能太短导致业务未完成锁就释放,也不能太长导致死锁时恢复慢。
锁的续期:对于长时间操作,考虑使用看门狗机制自动续期。
异常处理:处理好中断异常,避免锁无法释放。
秒杀系统面临的主要挑战是高并发下的三个核心问题:
一个典型的秒杀系统架构如下:
初始化库存:
java复制@PostConstruct
public void initStock() {
String stockKey = "seckill:stock:" + productId;
redisTemplate.opsForValue().set(stockKey, initialStock);
}
Java版本:
java复制public boolean seckill(Long productId, Long userId) {
String stockKey = "seckill:stock:" + productId;
String orderKey = "seckill:order:" + productId;
// 使用Lua脚本保证原子性
String script =
"if redis.call('get', KEYS[1]) > '0' then " +
" redis.call('decr', KEYS[1]) " +
" redis.call('sadd', KEYS[2], ARGV[1]) " +
" return 1 " +
"else " +
" return 0 " +
"end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
redisScript.setResultType(Long.class);
Long result = redisTemplate.execute(redisScript,
Arrays.asList(stockKey, orderKey),
userId.toString());
return result != null && result == 1;
}
Node.js版本:
javascript复制async function seckill(productId, userId) {
const stockKey = `seckill:stock:${productId}`;
const orderKey = `seckill:order:${productId}`;
const script = `
if redis.call('get', KEYS[1]) > '0' then
redis.call('decr', KEYS[1])
redis.call('sadd', KEYS[2], ARGV[1])
return 1
else
return 0
end
`;
const result = await redis.eval(script, 2, stockKey, orderKey, userId);
return result === 1;
}
分层校验:
库存分段:将库存分成多个段,减少争抢同一个key的情况
热点数据隔离:将秒杀商品数据单独放在一个Redis实例
限流措施:
异步处理:秒杀成功后,订单创建可以异步处理
现象:出现"Could not get a resource from the pool"错误
解决方案:
常见错误:
正确做法:
java复制RLock lock = redisson.getLock("order:" + orderId);
try {
if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {
// 业务逻辑
}
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
原因:在高并发下,多个请求同时读到库存>0,都进行了扣减
解决方案:
优化方向:
添加依赖:
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
配置:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
metrics:
tags:
application: ${spring.application.name}
使用redis-stat或prometheus-redis-exporter等工具监控Redis实例。
示例:
javascript复制const client = require('prom-client');
const collectDefaultMetrics = client.collectDefaultMetrics;
collectDefaultMetrics({ timeout: 5000 });
// 自定义Redis相关指标
const redisCommandDuration = new client.Histogram({
name: 'redis_command_duration_seconds',
help: 'Duration of Redis commands in seconds',
labelNames: ['command'],
buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5]
});
// 包装Redis命令
const originalSendCommand = redis.sendCommand;
redis.sendCommand = function(command) {
const end = redisCommandDuration.startTimer({ command: command.name });
return originalSendCommand.apply(this, arguments)
.then(result => {
end();
return result;
})
.catch(err => {
end();
throw err;
});
};
对于大规模应用,单机Redis可能无法满足需求,需要考虑集群方案:
在实际项目中,我发现很多团队对Redis的使用还停留在基础的缓存层面,其实Redis在分布式系统中有更广泛的应用场景。特别是在高并发场景下,合理使用Redis的各种特性可以显著提升系统性能和稳定性。