📚 前言
Redis作为高性能的内存数据库,在互联网应用中扮演着至关重要的角色。本文将深入探讨Redis的核心应用场景、性能优化技巧以及高可用架构设计。
🎯 一、Redis核心数据结构与应用场景
1.1 String:缓存与计数器
java
@Service
public class CacheService {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 缓存用户信息
*/
public void cacheUser(User user) {
String key = "user:" + user.getId();
String value = JSON.toJSONString(user);
// 设置过期时间30分钟
redisTemplate.opsForValue().set(key, value, 30, TimeUnit.MINUTES);
}
/**
* 获取缓存
*/
public User getUser(Long userId) {
String key = "user:" + userId;
String value = redisTemplate.opsForValue().get(key);
return value != null ? JSON.parseObject(value, User.class) : null;
}
/**
* 页面访问计数
*/
public Long incrementPageView(String pageId) {
String key = "page:view:" + pageId;
return redisTemplate.opsForValue().increment(key);
}
/**
* 分布式ID生成
*/
public Long generateId(String bizType) {
String key = "id:generator:" + bizType;
return redisTemplate.opsForValue().increment(key);
}
}
1.2 Hash:对象存储
java
@Service
public class UserCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 存储用户信息(使用Hash)
*/
public void saveUser(User user) {
String key = "user:hash:" + user.getId();
Map<String, Object> userMap = new HashMap<>();
userMap.put("name", user.getName());
userMap.put("age", user.getAge());
userMap.put("email", user.getEmail());
redisTemplate.opsForHash().putAll(key, userMap);
redisTemplate.expire(key, 30, TimeUnit.MINUTES);
}
/**
* 获取用户单个字段
*/
public String getUserName(Long userId) {
String key = "user:hash:" + userId;
return (String) redisTemplate.opsForHash().get(key, "name");
}
/**
* 更新用户单个字段
*/
public void updateUserAge(Long userId, Integer age) {
String key = "user:hash:" + userId;
redisTemplate.opsForHash().put(key, "age", age);
}
/**
* 获取用户所有信息
*/
public Map<Object, Object> getUser(Long userId) {
String key = "user:hash:" + userId;
return redisTemplate.opsForHash().entries(key);
}
}
1.3 List:消息队列与排行榜
java
@Service
public class MessageQueueService {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 生产者:发送消息
*/
public void sendMessage(String queue, String message) {
redisTemplate.opsForList().rightPush(queue, message);
}
/**
* 消费者:接收消息(阻塞)
*/
public String receiveMessage(String queue, long timeout) {
return redisTemplate.opsForList().leftPop(queue, timeout, TimeUnit.SECONDS);
}
/**
* 获取队列长度
*/
public Long getQueueSize(String queue) {
return redisTemplate.opsForList().size(queue);
}
/**
* 最新文章列表(保留最新100条)
*/
public void addArticle(String articleId) {
String key = "articles:latest";
redisTemplate.opsForList().leftPush(key, articleId);
// 保留最新100条
redisTemplate.opsForList().trim(key, 0, 99);
}
}
1.4 Set:标签与好友关系
java
@Service
public class SocialService {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 添加用户标签
*/
public void addUserTags(Long userId, String... tags) {
String key = "user:tags:" + userId;
redisTemplate.opsForSet().add(key, tags);
}
/**
* 获取用户标签
*/
public Set<String> getUserTags(Long userId) {
String key = "user:tags:" + userId;
return redisTemplate.opsForSet().members(key);
}
/**
* 共同好友
*/
public Set<String> getCommonFriends(Long userId1, Long userId2) {
String key1 = "user:friends:" + userId1;
String key2 = "user:friends:" + userId2;
return redisTemplate.opsForSet().intersect(key1, key2);
}
/**
* 可能认识的人(好友的好友,但不是自己的好友)
*/
public Set<String> getSuggestedFriends(Long userId) {
String myKey = "user:friends:" + userId;
Set<String> myFriends = redisTemplate.opsForSet().members(myKey);
Set<String> suggested = new HashSet<>();
for (String friendId : myFriends) {
String friendKey = "user:friends:" + friendId;
// 好友的好友
Set<String> friendsOfFriend = redisTemplate.opsForSet().members(friendKey);
suggested.addAll(friendsOfFriend);
}
// 排除自己和已经是好友的人
suggested.remove(userId.toString());
suggested.removeAll(myFriends);
return suggested;
}
}
1.5 ZSet:排行榜
java
@Service
public class RankingService {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 更新用户积分
*/
public void updateScore(Long userId, double score) {
String key = "ranking:score";
redisTemplate.opsForZSet().add(key, userId.toString(), score);
}
/**
* 增加用户积分
*/
public void incrementScore(Long userId, double delta) {
String key = "ranking:score";
redisTemplate.opsForZSet().incrementScore(key, userId.toString(), delta);
}
/**
* 获取排行榜(前N名)
*/
public Set<ZSetOperations.TypedTuple<String>> getTopN(int n) {
String key = "ranking:score";
// 降序获取前N名
return redisTemplate.opsForZSet().reverseRangeWithScores(key, 0, n - 1);
}
/**
* 获取用户排名
*/
public Long getUserRank(Long userId) {
String key = "ranking:score";
// 降序排名
return redisTemplate.opsForZSet().reverseRank(key, userId.toString());
}
/**
* 获取用户积分
*/
public Double getUserScore(Long userId) {
String key = "ranking:score";
return redisTemplate.opsForZSet().score(key, userId.toString());
}
/**
* 获取指定分数范围的用户
*/
public Set<String> getUsersByScoreRange(double min, double max) {
String key = "ranking:score";
return redisTemplate.opsForZSet().rangeByScore(key, min, max);
}
}
🔐 二、分布式锁实现
2.1 基于SETNX的简单实现
java
@Component
public class RedisLock {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String LOCK_PREFIX = "lock:";
/**
* 获取锁
*/
public boolean tryLock(String key, String value, long expireTime) {
Boolean result = redisTemplate.opsForValue()
.setIfAbsent(LOCK_PREFIX + key, value, expireTime, TimeUnit.MILLISECONDS);
return Boolean.TRUE.equals(result);
}
/**
* 释放锁(使用Lua脚本保证原子性)
*/
public boolean releaseLock(String key, String value) {
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(LOCK_PREFIX + key),
value
);
return result != null && result > 0;
}
}
2.2 Redisson分布式锁(推荐)
xml
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.5</version>
</dependency>
java
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://localhost:6379")
.setPassword("your_password")
.setDatabase(0)
.setConnectionPoolSize(50)
.setConnectionMinimumIdleSize(10);
return Redisson.create(config);
}
}
@Service
public class OrderService {
@Autowired
private RedissonClient redissonClient;
/**
* 使用分布式锁扣减库存
*/
public boolean deductStock(Long productId, Integer count) {
String lockKey = "product:stock:" + productId;
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试获取锁,最多等待10秒,锁自动释放时间30秒
boolean locked = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (!locked) {
return false;
}
// 业务逻辑:扣减库存
Integer stock = getStock(productId);
if (stock < count) {
return false;
}
updateStock(productId, stock - count);
return true;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
} finally {
// 释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
private Integer getStock(Long productId) {
// 从数据库或缓存获取库存
return 100;
}
private void updateStock(Long productId, Integer stock) {
// 更新库存到数据库
}
}
2.3 RedLock算法(多Redis实例)
java
@Service
public class RedLockService {
@Autowired
private RedissonClient redissonClient1;
@Autowired
private RedissonClient redissonClient2;
@Autowired
private RedissonClient redissonClient3;
/**
* 使用RedLock算法
*/
public void executeWithRedLock(String lockKey, Runnable task) {
RLock lock1 = redissonClient1.getLock(lockKey);
RLock lock2 = redissonClient2.getLock(lockKey);
RLock lock3 = redissonClient3.getLock(lockKey);
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
try {
// 尝试获取锁
boolean locked = redLock.tryLock(10, 30, TimeUnit.SECONDS);
if (locked) {
task.run();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
redLock.unlock();
}
}
}
💾 三、缓存设计模式
3.1 Cache Aside(旁路缓存)
java
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 查询用户(先查缓存,再查数据库)
*/
public User getUserById(Long userId) {
String key = "user:" + userId;
// 1. 查询缓存
String cached = redisTemplate.opsForValue().get(key);
if (cached != null) {
return JSON.parseObject(cached, User.class);
}
// 2. 查询数据库
User user = userMapper.selectById(userId);
if (user != null) {
// 3. 写入缓存
redisTemplate.opsForValue().set(
key,
JSON.toJSONString(user),
30,
TimeUnit.MINUTES
);
}
return user;
}
/**
* 更新用户(先更新数据库,再删除缓存)
*/
public void updateUser(User user) {
// 1. 更新数据库
userMapper.updateById(user);
// 2. 删除缓存
String key = "user:" + user.getId();
redisTemplate.delete(key);
}
}
3.2 Read Through / Write Through
java
@Service
public class CacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private UserMapper userMapper;
/**
* Read Through:缓存层自动加载数据
*/
public User getUserById(Long userId) {
String key = "user:" + userId;
return (User) redisTemplate.opsForValue().get(key);
}
/**
* Write Through:同时更新缓存和数据库
*/
@Transactional
public void updateUser(User user) {
String key = "user:" + user.getId();
// 1. 更新数据库
userMapper.updateById(user);
// 2. 更新缓存
redisTemplate.opsForValue().set(
key,
user,
30,
TimeUnit.MINUTES
);
}
}
3.3 Write Behind(异步写入)
java
@Service
public class AsyncCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private UserMapper userMapper;
private final ExecutorService executor = Executors.newFixedThreadPool(10);
/**
* 异步写入数据库
*/
public void updateUser(User user) {
String key = "user:" + user.getId();
// 1. 立即更新缓存
redisTemplate.opsForValue().set(key, user, 30, TimeUnit.MINUTES);
// 2. 异步更新数据库
executor.submit(() -> {
try {
userMapper.updateById(user);
} catch (Exception e) {
// 记录日志,后续补偿
log.error("异步更新数据库失败", e);
}
});
}
}
🚨 四、缓存常见问题与解决方案
4.1 缓存穿透
问题: 查询不存在的数据,导致每次都查询数据库
解决方案1:缓存空值
java
@Service
public class CachePenetrationService {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private UserMapper userMapper;
public User getUserById(Long userId) {
String key = "user:" + userId;
// 1. 查询缓存
String cached = redisTemplate.opsForValue().get(key);
// 缓存中存在
if (cached != null) {
// 空值标记
if ("NULL".equals(cached)) {
return null;
}
return JSON.parseObject(cached, User.class);
}
// 2. 查询数据库
User user = userMapper.selectById(userId);
if (user != null) {
// 3. 缓存数据
redisTemplate.opsForValue().set(
key,
JSON.toJSONString(user),
30,
TimeUnit.MINUTES
);
} else {
// 4. 缓存空值(设置较短过期时间)
redisTemplate.opsForValue().set(key, "NULL", 5, TimeUnit.MINUTES);
}
return user;
}
}
解决方案2:布隆过滤器
java
@Configuration
public class BloomFilterConfig {
@Bean
public BloomFilter<Long> userBloomFilter() {
// 预期元素数量100万,误判率0.01
BloomFilter<Long> bloomFilter = BloomFilter.create(
Funnels.longFunnel(),
1000000,
0.01
);
// 初始化:将所有用户ID加入布隆过滤器
// userMapper.selectAllIds().forEach(bloomFilter::put);
return bloomFilter;
}
}
@Service
public class BloomFilterService {
@Autowired
private BloomFilter<Long> userBloomFilter;
@Autowired
private UserMapper userMapper;
public User getUserById(Long userId) {
// 1. 布隆过滤器判断
if (!userBloomFilter.mightContain(userId)) {
// 一定不存在
return null;
}
// 2. 可能存在,查询数据库
return userMapper.selectById(userId);
}
/**
* 新增用户时,加入布隆过滤器
*/
public void createUser(User user) {
userMapper.insert(user);
userBloomFilter.put(user.getId());
}
}
4.2 缓存击穿
问题: 热点数据过期,大量请求同时查询数据库
解决方案:互斥锁
java
@Service
public class CacheBreakdownService {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private UserMapper userMapper;
@Autowired
private RedissonClient redissonClient;
public User getUserById(Long userId) {
String key = "user:" + userId;
// 1. 查询缓存
String cached = redisTemplate.opsForValue().get(key);
if (cached != null) {
return JSON.parseObject(cached, User.class);
}
// 2. 缓存未命中,使用分布式锁
String lockKey = "lock:user:" + userId;
RLock lock = redissonClient.getLock(lockKey);
try {
// 获取锁
boolean locked = lock.tryLock(10, TimeUnit.SECONDS);
if (locked) {
// 双重检查
cached = redisTemplate.opsForValue().get(key);
if (cached != null) {
return JSON.parseObject(cached, User.class);
}
// 查询数据库
User user = userMapper.selectById(userId);
if (user != null) {
// 写入缓存
redisTemplate.opsForValue().set(
key,
JSON.toJSONString(user),
30,
TimeUnit.MINUTES
);
}
return user;
} else {
// 获取锁失败,等待后重试
Thread.sleep(100);
return getUserById(userId);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
4.3 缓存雪崩
问题: 大量缓存同时过期,导致数据库压力骤增
解决方案:
java
@Service
public class CacheAvalancheService {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private UserMapper userMapper;
private static final Random RANDOM = new Random();
/**
* 方案1:过期时间加随机值
*/
public void cacheUser(User user) {
String key = "user:" + user.getId();
// 基础过期时间30分钟 + 随机0-5分钟
long expireTime = 30 + RANDOM.nextInt(5);
redisTemplate.opsForValue().set(
key,
JSON.toJSONString(user),
expireTime,
TimeUnit.MINUTES
);
}
/**
* 方案2:热点数据永不过期(后台定时刷新)
*/
@Scheduled(fixedRate = 600000) // 每10分钟执行
public void refreshHotData() {
List<Long> hotUserIds = getHotUserIds();
for (Long userId : hotUserIds) {
User user = userMapper.selectById(userId);
if (user != null) {
String key = "user:hot:" + userId;
redisTemplate.opsForValue().set(
key,
JSON.toJSONString(user)
// 不设置过期时间
);
}
}
}
private List<Long> getHotUserIds() {
// 获取热点用户ID列表
return Arrays.asList(1L, 2L, 3L);
}
}
🔥 五、Redis性能优化
5.1 Pipeline批量操作
java
@Service
public class PipelineService {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* ❌ 逐个操作(性能差)
*/
public void badBatchSet(Map<String, String> data) {
for (Map.Entry<String, String> entry : data.entrySet()) {
redisTemplate.opsForValue().set(entry.getKey(), entry.getValue());
}
}
/**
* ✅ 使用Pipeline批量操作
*/
public void goodBatchSet(Map<String, String> data) {
redisTemplate.executePipelined(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
for (Map.Entry<String, String> entry : data.entrySet()) {
operations.opsForValue().set(entry.getKey(), entry.getValue());
}
return null;
}
});
}
/**
* Pipeline批量查询
*/
public List<String> batchGet(List<String> keys) {
List<Object> results = redisTemplate.executePipelined(
new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
for (String key : keys) {
operations.opsForValue().get(key);
}
return null;
}
}
);
return results.stream()
.map(obj -> obj != null ? obj.toString() : null)
.collect(Collectors.toList());
}
}
5.2 Lua脚本原子操作
java
@Service
public class LuaScriptService {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 限流器(令牌桶算法)
*/
public boolean tryAcquire(String key, int maxCount, int windowSeconds) {
String script =
"local key = KEYS[1] " +
"local maxCount = tonumber(ARGV[1]) " +
"local windowSeconds = tonumber(ARGV[2]) " +
"local current = tonumber(redis.call('get', key) or '0') " +
"if current < maxCount then " +
" redis.call('incr', key) " +
" if current == 0 then " +
" redis.call('expire', key, windowSeconds) " +
" end " +
" return 1 " +
"else " +
" return 0 " +
"end";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(key),
String.valueOf(maxCount),
String.valueOf(windowSeconds)
);
return result != null && result > 0;
}
/**
* 扣减库存(防止超卖)
*/
public boolean deductStock(String productId, int count) {
String script =
"local key = KEYS[1] " +
"local count = tonumber(ARGV[1]) " +
"local stock = tonumber(redis.call('get', key) or '0') " +
"if stock >= count then " +
" redis.call('decrby', key, count) " +
" return 1 " +
"else " +
" return 0 " +
"end";
String key = "product:stock:" + productId;
Long result = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(key),
String.valueOf(count)
);
return result != null && result > 0;
}
}
5.3 大Key优化
java
@Service
public class BigKeyOptimization {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* ❌ 大Key:一次性存储大量数据
*/
public void badSaveUserList(List<User> users) {
String key = "users:all";
String value = JSON.toJSONString(users);
redisTemplate.opsForValue().set(key, value);
}
/**
* ✅ 拆分大Key:使用Hash存储
*/
public void goodSaveUserList(List<User> users) {
String key = "users:hash";
Map<String, String> userMap = users.stream()
.collect(Collectors.toMap(
user -> user.getId().toString(),
user -> JSON.toJSONString(user)
));
redisTemplate.opsForHash().putAll(key, userMap);
}
/**
* 分页获取数据
*/
public List<User> getUsersByPage(int page, int size) {
String key = "users:hash";
// 使用HSCAN分页获取
ScanOptions options = ScanOptions.scanOptions()
.count(size)
.build();
Cursor<Map.Entry<Object, Object>> cursor =
redisTemplate.opsForHash().scan(key, options);
List<User> users = new ArrayList<>();
while (cursor.hasNext() && users.size() < size) {
Map.Entry<Object, Object> entry = cursor.next();
User user = JSON.parseObject(entry.getValue().toString(), User.class);
users.add(user);
}
return users;
}
}
🏗️ 六、Redis高可用架构
6.1 主从复制配置
yaml
# application.yml
spring:
redis:
# 主节点
host: 192.168.1.100
port: 6379
password: your_password
# 从节点(读写分离)
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
read-from: REPLICA_PREFERRED # 优先从从节点读取
6.2 哨兵模式配置
yaml
spring:
redis:
sentinel:
master: mymaster
nodes:
- 192.168.1.100:26379
- 192.168.1.101:26379
- 192.168.1.102:26379
password: your_password
lettuce:
pool:
max-active: 8
6.3 集群模式配置
yaml
spring:
redis:
cluster:
nodes:
- 192.168.1.100:7000
- 192.168.1.100:7001
- 192.168.1.101:7000
- 192.168.1.101:7001
- 192.168.1.102:7000
- 192.168.1.102:7001
max-redirects: 3
password: your_password
lettuce:
pool:
max-active: 16
max-idle: 8
min-idle: 4
集群模式下的批量操作:
java
@Service
public class ClusterBatchService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 集群模式下的批量操作(按slot分组)
*/
public void clusterBatchSet(Map<String, String> data) {
// 按slot分组
Map<Integer, Map<String, String>> slotMap = new HashMap<>();
for (Map.Entry<String, String> entry : data.entrySet()) {
int slot = calculateSlot(entry.getKey());
slotMap.computeIfAbsent(slot, k -> new HashMap<>())
.put(entry.getKey(), entry.getValue());
}
// 每个slot使用Pipeline批量操作
for (Map<String, String> slotData : slotMap.values()) {
redisTemplate.executePipelined(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
for (Map.Entry<String, String> entry : slotData.entrySet()) {
operations.opsForValue().set(entry.getKey(), entry.getValue());
}
return null;
}
});
}
}
/**
* 计算key的slot
*/
private int calculateSlot(String key) {
return CRC16.crc16(key.getBytes()) % 16384;
}
}
📊 七、Redis监控与运维
7.1 性能监控
java
@Component
@Slf4j
public class RedisMonitor {
@Autowired
private StringRedisTemplate redisTemplate;
@Scheduled(fixedRate = 60000) // 每分钟执行
public void monitorRedis() {
Properties info = redisTemplate.execute((RedisCallback<Properties>) connection -> {
return connection.info();
});
if (info != null) {
log.info("=== Redis监控信息 ===");
log.info("已使用内存: {}", info.getProperty("used_memory_human"));
log.info("内存峰值: {}", info.getProperty("used_memory_peak_human"));
log.info("连接的客户端数: {}", info.getProperty("connected_clients"));
log.info("阻塞的客户端数: {}", info.getProperty("blocked_clients"));
log.info("键总数: {}", info.getProperty("db0"));
log.info("命中率: {}%", calculateHitRate(info));
}
}
private double calculateHitRate(Properties info) {
long hits = Long.parseLong(info.getProperty("keyspace_hits", "0"));
long misses = Long.parseLong(info.getProperty("keyspace_misses", "0"));
if (hits + misses == 0) {
return 0;
}
return (double) hits / (hits + misses) * 100;
}
/**
* 慢查询监控
*/
@Scheduled(fixedRate = 300000) // 每5分钟执行
public void monitorSlowLog() {
List<Object> slowLogs = redisTemplate.execute((RedisCallback<List<Object>>) connection -> {
return connection.slowLogGet(10); // 获取最近10条慢查询
});
if (slowLogs != null && !slowLogs.isEmpty()) {
log.warn("=== Redis慢查询 ===");
slowLogs.forEach(log::warn);
}
}
}
7.2 内存优化
java
@Service
public class MemoryOptimization {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 清理过期键
*/
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行
public void cleanExpiredKeys() {
// Redis会自动清理过期键,这里可以手动触发
redisTemplate.execute((RedisCallback<Object>) connection -> {
connection.bgRewriteAof(); // 触发AOF重写
return null;
});
}
/**
* 分析大Key
*/
public void analyzeBigKeys() {
Set<String> keys = redisTemplate.keys("*");
if (keys != null) {
Map<String, Long> bigKeys = new HashMap<>();
for (String key : keys) {
Long size = redisTemplate.execute((RedisCallback<Long>) connection -> {
DataType type = connection.type(key.getBytes());
switch (type) {
case STRING:
return connection.strLen(key.getBytes());
case LIST:
return connection.lLen(key.getBytes());
case SET:
return connection.sCard(key.getBytes());
case ZSET:
return connection.zCard(key.getBytes());
case HASH:
return connection.hLen(key.getBytes());
default:
return 0L;
}
});
if (size != null && size > 10000) { // 大于10000认为是大Key
bigKeys.put(key, size);
}
}
// 输出大Key
bigKeys.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.limit(10)
.forEach(entry ->
log.warn("大Key: {}, 大小: {}", entry.getKey(), entry.getValue())
);
}
}
}
🎯 八、实战案例
8.1 秒杀系统
java
@Service
@Slf4j
public class SeckillService {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private RedissonClient redissonClient;
@Autowired
private OrderService orderService;
/**
* 初始化秒杀库存
*/
public void initSeckillStock(Long productId, Integer stock) {
String key = "seckill:stock:" + productId;
redisTemplate.opsForValue().set(key, stock.toString());
}
/**
* 秒杀下单
*/
public boolean seckill(Long productId, Long userId) {
String stockKey = "seckill:stock:" + productId;
String userKey = "seckill:user:" + productId + ":" + userId;
// 1. 检查用户是否已经购买
Boolean exists = redisTemplate.hasKey(userKey);
if (Boolean.TRUE.equals(exists)) {
log.warn("用户{}已经购买过商品{}", userId, productId);
return false;
}
// 2. 使用Lua脚本扣减库存
String script =
"local stock = tonumber(redis.call('get', KEYS[1]) or '0') " +
"if stock > 0 then " +
" redis.call('decr', KEYS[1]) " +
" redis.call('setex', KEYS[2], 86400, '1') " +
" return 1 " +
"else " +
" return 0 " +
"end";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Arrays.asList(stockKey, userKey)
);
if (result != null && result > 0) {
// 3. 异步创建订单
CompletableFuture.runAsync(() -> {
try {
orderService.createOrder(productId, userId);
} catch (Exception e) {
log.error("创建订单失败", e);
// 回滚库存
redisTemplate.opsForValue().increment(stockKey);
redisTemplate.delete(userKey);
}
});
return true;
}
return false;
}
/**
* 查询剩余库存
*/
public Integer getRemainStock(Long productId) {
String key = "seckill:stock:" + productId;
String stock = redisTemplate.opsForValue().get(key);
return stock != null ? Integer.parseInt(stock) : 0;
}
}
8.2 分布式Session
java
@Configuration
public class SessionConfig {
@Bean
public RedisTemplate<String, Object> sessionRedisTemplate(
RedisConnectionFactory connectionFactory
) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 使用Jackson序列化
Jackson2JsonRedisSerializer<Object> serializer =
new Jackson2JsonRedisSerializer<>(Object.class);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
return template;
}
}
@Component
public class SessionManager {
@Autowired
private RedisTemplate<String, Object> sessionRedisTemplate;
private static final String SESSION_PREFIX = "session:";
private static final long SESSION_TIMEOUT = 1800; // 30分钟
/**
* 创建Session
*/
public String createSession(Long userId, Map<String, Object> attributes) {
String sessionId = UUID.randomUUID().toString();
String key = SESSION_PREFIX + sessionId;
Map<String, Object> sessionData = new HashMap<>(attributes);
sessionData.put("userId", userId);
sessionData.put("createTime", System.currentTimeMillis());
sessionRedisTemplate.opsForHash().putAll(key, sessionData);
sessionRedisTemplate.expire(key, SESSION_TIMEOUT, TimeUnit.SECONDS);
return sessionId;
}
/**
* 获取Session
*/
public Map<Object, Object> getSession(String sessionId) {
String key = SESSION_PREFIX + sessionId;
Map<Object, Object> session = sessionRedisTemplate.opsForHash().entries(key);
if (!session.isEmpty()) {
// 刷新过期时间
sessionRedisTemplate.expire(key, SESSION_TIMEOUT, TimeUnit.SECONDS);
}
return session;
}
/**
* 更新Session
*/
public void updateSession(String sessionId, String field, Object value) {
String key = SESSION_PREFIX + sessionId;
sessionRedisTemplate.opsForHash().put(key, field, value);
sessionRedisTemplate.expire(key, SESSION_TIMEOUT, TimeUnit.SECONDS);
}
/**
* 删除Session
*/
public void removeSession(String sessionId) {
String key = SESSION_PREFIX + sessionId;
sessionRedisTemplate.delete(key);
}
}
8.3 限流器
java
@Component
public class RateLimiter {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 固定窗口限流
*/
public boolean tryAcquireFixedWindow(String key, int maxCount, int windowSeconds) {
String redisKey = "rate_limit:fixed:" + key;
String script =
"local current = tonumber(redis.call('get', KEYS[1]) or '0') " +
"if current < tonumber(ARGV[1]) then " +
" redis.call('incr', KEYS[1]) " +
" if current == 0 then " +
" redis.call('expire', KEYS[1], ARGV[2]) " +
" end " +
" return 1 " +
"else " +
" return 0 " +
"end";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(redisKey),
String.valueOf(maxCount),
String.valueOf(windowSeconds)
);
return result != null && result > 0;
}
/**
* 滑动窗口限流
*/
public boolean tryAcquireSlidingWindow(String key, int maxCount, int windowSeconds) {
String redisKey = "rate_limit:sliding:" + key;
long now = System.currentTimeMillis();
long windowStart = now - windowSeconds * 1000L;
String script =
"redis.call('zremrangebyscore', KEYS[1], 0, ARGV[1]) " +
"local count = redis.call('zcard', KEYS[1]) " +
"if count < tonumber(ARGV[3]) then " +
" redis.call('zadd', KEYS[1], ARGV[2], ARGV[2]) " +
" redis.call('expire', KEYS[1], ARGV[4]) " +
" return 1 " +
"else " +
" return 0 " +
"end";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(redisKey),
String.valueOf(windowStart),
String.valueOf(now),
String.valueOf(maxCount),
String.valueOf(windowSeconds)
);
return result != null && result > 0;
}
/**
* 令牌桶限流(使用Redisson)
*/
@Autowired
private RedissonClient redissonClient;
public boolean tryAcquireTokenBucket(String key, int rate, int capacity) {
RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
// 初始化限流器
rateLimiter.trySetRate(RateType.OVERALL, rate, 1, RateIntervalUnit.SECONDS);
// 尝试获取令牌
return rateLimiter.tryAcquire();
}
}
// 使用示例
@RestController
@RequestMapping("/api")
public class ApiController {
@Autowired
private RateLimiter rateLimiter;
@GetMapping("/data")
public Result<String> getData(HttpServletRequest request) {
String clientIp = request.getRemoteAddr();
// 限流:每个IP每秒最多10次请求
if (!rateLimiter.tryAcquireSlidingWindow(clientIp, 10, 1)) {
return Result.fail("请求过于频繁,请稍后重试");
}
return Result.success("数据内容");
}
}
🎓 总结
本文全面介绍了Redis的核心应用技术:
- 数据结构应用:String、Hash、List、Set、ZSet的实战场景
- 分布式锁:基于SETNX、Redisson、RedLock的实现
- 缓存设计:Cache Aside、Read/Write Through、Write Behind
- 常见问题:缓存穿透、击穿、雪崩的解决方案
- 性能优化:Pipeline、Lua脚本、大Key优化
- 高可用架构:主从复制、哨兵模式、集群模式
- 监控运维:性能监控、内存优化、慢查询分析
- 实战案例:秒杀系统、分布式Session、限流器
掌握这些技术,你将能够构建高性能、高可用的Redis应用!
💬 欢迎讨论:你在Redis实践中遇到过哪些问题?欢迎在评论区分享!
🌟 觉得有帮助? 点赞、收藏、关注三连支持一下吧!