话外题:
在面试中必问的就是你在开发中用到过redis吗?这五种基本数据类型都是怎么用的具体场景是啥?等等这样的问题,下面就记录下五种基本数据类型的应用场景。
一、String类型:轻量级数据缓存
典型场景:短信验证码缓存
业务需求:用户登录时需要短信验证码验证,要求验证码5分钟内有效且每个手机号60秒内不能重复发送
方案设计:
-
存储结构:key=code:手机号,value=验证码
-
设置双重过期时间:EX 300(5分钟),NX(不存在时设置)
-
防重复发送:使用SETNX实现原子操作
Java实现:
public class SmsService {
private JedisPool jedisPool = new JedisPool("redis-server", 6379);
public String sendCode(String phone) {
try (Jedis jedis = jedisPool.getResource()) {
// 防重复发送检查
String lockKey = "lock:" + phone;
if (jedis.set(lockKey, "1", "NX", "EX", 60) == null) {
return "请求过于频繁";
}
// 生成验证码
String code = String.valueOf((int)(Math.random() * 9000 + 1000));
// 存储验证码(包含过期时间)
String codeKey = "code:" + phone;
jedis.setex(codeKey, 300, code);
// 调用短信发送API(模拟)
sendSms(phone, code);
return "验证码已发送";
}
}
public boolean verifyCode(String phone, String inputCode) {
try (Jedis jedis = jedisPool.getResource()) {
String codeKey = "code:" + phone;
String realCode = jedis.get(codeKey);
return inputCode != null && inputCode.equals(realCode);
}
}
}
二、Hash类型:对象属性存储
典型场景:电商购物车设计
业务需求:
-
存储用户购物车中的商品信息
-
支持商品数量修改
-
需要完整获取购物车数据
方案优势:
-
天然适合存储对象属性
-
支持字段级操作(hincrby)
-
内存使用更高效(相比String序列化存储)
Java实现:
public class CartService {
private static final String CART_KEY_PREFIX = "cart:";
public void addItem(String userId, String itemId, int quantity) {
try (Jedis jedis = jedisPool.getResource()) {
String key = CART_KEY_PREFIX + userId;
jedis.hincrBy(key, itemId, quantity);
}
}
public Map<String, String> getCart(String userId) {
try (Jedis jedis = jedisPool.getResource()) {
String key = CART_KEY_PREFIX + userId;
return jedis.hgetAll(key);
}
}
public void removeItem(String userId, String... itemIds) {
try (Jedis jedis = jedisPool.getResource()) {
String key = CART_KEY_PREFIX + userId;
jedis.hdel(key, itemIds);
}
}
}
三、List类型:消息队列实现
典型场景:订单状态变更通知
业务需求:解耦订单系统与通知系统,实现异步处理
方案特点:
-
使用LPUSH/RPOP实现队列
-
BRPOP实现阻塞式消费
-
支持多消费者负载均衡
Java消费者示例:
public class OrderNotifyConsumer {
private volatile boolean running = true;
public void start() {
new Thread(() -> {
try (Jedis jedis = jedisPool.getResource()) {
while (running) {
List<String> messages = jedis.brpop(30, "queue:order-notify");
if (messages != null) {
String orderId = messages.get(1);
processOrder(orderId);
}
}
}
}).start();
}
private void processOrder(String orderId) {
// 实现通知逻辑(邮件、短信等)
}
}
四、Set类型:社交关系处理
典型场景:共同关注功能
业务需求:快速计算两个用户的共同关注列表
方案优势:
-
SINTER命令直接计算集合交集
-
O(N)时间复杂度优于数据库JOIN操作
Java实现:
public class RelationService {
public Set<String> getCommonFollowers(String user1, String user2) {
try (Jedis jedis = jedisPool.getResource()) {
String key1 = "followers:" + user1;
String key2 = "followers:" + user2;
return jedis.sinter(key1, key2);
}
}
public void followUser(String userId, String targetId) {
try (Jedis jedis = jedisPool.getResource()) {
jedis.sadd("following:" + userId, targetId);
jedis.sadd("followers:" + targetId, userId);
}
}
}
五、Sorted Set:实时排行榜
典型场景:直播间礼物周榜
业务需求:
-
实时更新用户送礼总值
-
按周维度滚动排行
-
支持TopN查询和用户排名
Java实现:
public class GiftRankService {
private static final String WEEKLY_RANK_KEY = "rank:gift:week";
public void addGift(String userId, double amount) {
try (Jedis jedis = jedisPool.getResource()) {
jedis.zincrby(WEEKLY_RANK_KEY, amount, userId);
}
}
public List<Map.Entry<String, Double>> getTop10() {
try (Jedis jedis = jedisPool.getResource()) {
Set<Tuple> tuples = jedis.zrevrangeWithScores(WEEKLY_RANK_KEY, 0, 9);
return tuples.stream()
.map(t -> new AbstractMap.SimpleEntry<>(t.getElement(), t.getScore()))
.collect(Collectors.toList());
}
}
public void resetWeeklyRank() {
try (Jedis jedis = jedisPool.getResource()) {
jedis.del(WEEKLY_RANK_KEY);
}
}
}
六、生产环境最佳实践
-
键名设计规范
-
使用冒号分隔的命名空间(如
user:1001:profile
) -
避免过长的键名(控制在100字节内)
-
-
连接池配置
JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(128); // 最大连接数 config.setMaxIdle(32); // 最大空闲连接 config.setMinIdle(8); // 最小空闲连接 config.setTestOnBorrow(true); // 取连接时校验 JedisPool pool = new JedisPool(config, "redis-host", 6379);
-
性能优化技巧
-
Pipeline批量操作提升吞吐量
-
合理设置过期时间避免内存泄漏
-
大Value拆分(Hash分桶存储)
-
七、常见问题解决方案
缓存穿透:
// 空值缓存
public String getData(String key) {
String value = jedis.get(key);
if ("NULL".equals(value)) {
return null; // 缓存空值
}
if (value == null) {
value = db.query(key);
jedis.setex(key, 300, value != null ? value : "NULL");
}
return value;
}
热Key处理:
-
本地缓存+Redis多级缓存
-
使用Hash分片存储
作者建议:实际使用中建议结合Spring Data Redis模板类,并通过@Resource注入RedisTemplate对象。注意合理设置序列化方式(推荐StringRedisSerializer),生产环境务必配置合理的连接超时和重试策略。
延伸阅读:
-
Redis持久化机制选择(RDB vs AOF)
-
集群模式下的数据分片策略
-
Redis事务与Lua脚本原子性操作
注意事项:
-
所有代码示例基于Jedis 3.7.0实现
-
生产环境建议配置SSL/TLS加密连接
-
高并发场景建议使用Lettuce客户端
-
注意Redis版本差异(部分命令需要Redis 5.0+)
建议将本文代码示例与实际Spring Boot项目结合,通过@Configuration配置RedisTemplate,可以更便捷地集成到企业级应用中。