1. 引言
Redis作为当今最流行的键值对数据库,以其卓越的性能、丰富的数据结构和广泛的应用场景,成为了现代技术架构中不可或缺的组件。本文将从数据结构原理出发,通过完整的代码示例和实际应用案例,全面解析Redis的五大基本数据类型及其在企业级应用中的最佳实践。
1.1 Redis的核心特点
- 高性能:基于内存存储,支持每秒10万+的读写操作
- 数据结构丰富:支持String、Hash、List、Set、ZSet等多种结构
- 功能完善:提供事务、发布订阅、Lua脚本、集群等企业级特性
- 持久化可靠:支持RDB和AOF两种持久化方式
- 分布式友好:提供主从复制、哨兵、集群等分布式解决方案
1.2 主要应用场景
- 缓存系统:减轻数据库压力,提升响应速度
- 会话存储:分布式Session管理
- 计数器:实时统计、排行榜
- 消息队列:发布订阅、List队列
- 分布式锁:跨服务实例的并发控制
2. 五大基本数据类型详解
2.1 String类型
2.1.1 数据结构原理
String是Redis中最基础的数据类型,采用SDS(Simple Dynamic String) 结构实现。与C语言字符串相比,SDS具有以下优势:
struct sdshdr {
int len; // 已使用长度
int free; // 剩余可用空间
char buf[]; // 实际存储的字符串
}
2.1.2 常用命令演示
Redis命令行操作
# 设置键值
SET key value
# 获取键值
GET key
# 设置过期时间(秒)
SETEX key 30 value
# 批量设置
MSET key1 value1 key2 value2
# 数值增减
INCR counter
INCRBY counter 5
DECR counter
DECRBY counter 3
# 字符串追加
APPEND key "world"
Java实现示例
import redis.clients.jedis.Jedis;
public class StringOperations {
public static void main(String[] args) {
Jedis jedis = RedisConnection.getConnection();
try {
// 基本操作
jedis.set("username", "zhangsan");
String username = jedis.get("username");
System.out.println("Username: " + username);
// 设置过期时间
jedis.setex("session:123", 1800, "user_data");
// 数值操作
jedis.set("counter", "0");
jedis.incr("counter");
jedis.incrBy("counter", 10);
System.out.println("Counter: " + jedis.get("counter"));
// 批量操作
jedis.mset("key1", "value1", "key2", "value2");
System.out.println("MGET: " + jedis.mget("key1", "key2"));
} finally {
jedis.close();
}
}
}
2.1.3 典型应用场景
1. 缓存数据
public class CacheService {
private Jedis jedis = RedisConnection.getConnection();
// 设置缓存,30分钟过期
public void setCache(String key, String value) {
jedis.setex(key, 1800, value);
}
// 获取缓存
public String getCache(String key) {
String value = jedis.get(key);
if (value != null) {
// 延长过期时间
jedis.expire(key, 1800);
}
return value;
}
}
2. 分布式Session
public class SessionService {
private Jedis jedis = RedisConnection.getConnection();
// 创建Session
public String createSession(String userId) {
String sessionId = UUID.randomUUID().toString();
String sessionData = String.format("{\"userId\":\"%s\",\"loginTime\":\"%s\"}",
userId, System.currentTimeMillis());
jedis.setex("session:" + sessionId, 1800, sessionData);
return sessionId;
}
// 验证Session
public boolean validateSession(String sessionId) {
String sessionData = jedis.get("session:" + sessionId);
return sessionData != null;
}
}
2.1.4 性能优化建议
- 合理设置过期时间:避免内存泄漏,常用30分钟-2小时
- 使用批量命令:MSET/MGET减少网络往返
- 数值操作优于读写:INCR/DECR原子操作避免竞态
- 监控Key空间:使用SCAN替代KEYS避免阻塞
2.2 Hash类型
2.2.1 数据结构原理
Hash类型对应Java中的HashMap,适合存储对象信息。底层采用ziplist 或hashtable实现:
ziplist结构(小数据量):
[tail_offset][zllen][entry1][entry2][...][zlend]
hashtable结构(大数据量):
[dict_entry*] -> [key, value] -> [key, value]
2.2.2 常用命令演示
Redis命令行操作
# 设置Hash字段
HSET user:1 name "zhangsan"
HSET user:1 age 25
HSET user:1 email "zhangsan@example.com"
# 获取单个字段
HGET user:1 name
# 获取所有字段
HGETALL user:1
# 获取所有字段名
HKEYS user:1
# 获取所有值
HVALS user:1
# 判断字段是否存在
HEXISTS user:1 name
# 删除字段
HDEL user:1 email
# 字段数值增减
HINCRBY user:1 age 1
Java实现示例
import redis.clients.jedis.Jedis;
import java.util.HashMap;
import java.util.Map;
public class HashOperations {
public static void main(String[] args) {
Jedis jedis = RedisConnection.getConnection();
try {
// 单个字段操作
jedis.hset("user:1", "name", "zhangsan");
jedis.hset("user:1", "age", "25");
jedis.hset("user:1", "email", "zhangsan@example.com");
// 获取单个字段
System.out.println("Name: " + jedis.hget("user:1", "name"));
// 批量获取
System.out.println("All fields: " + jedis.hgetAll("user:1"));
// Map批量设置
Map<String, String> userMap = new HashMap<>();
userMap.put("name", "lisi");
userMap.put("age", "30");
userMap.put("city", "beijing");
jedis.hmset("user:2", userMap);
// 数值操作
jedis.hincrBy("user:1", "age", 1);
System.out.println("Age after increment: " + jedis.hget("user:1", "age"));
} finally {
jedis.close();
}
}
}
2.2.3 典型应用场景
1. 用户信息存储
public class UserService {
private Jedis jedis = RedisConnection.getConnection();
// 保存用户信息
public void saveUser(String userId, Map<String, String> userInfo) {
jedis.hmset("user:" + userId, userInfo);
}
// 获取用户信息
public Map<String, String> getUser(String userId) {
return jedis.hgetAll("user:" + userId);
}
// 更新单个字段
public void updateUserField(String userId, String field, String value) {
jedis.hset("user:" + userId, field, value);
}
// 用户登录次数统计
public long incrementLoginCount(String userId) {
return jedis.hincrBy("user:" + userId, "loginCount", 1);
}
}
2. 购物车实现
public class ShoppingCartService {
private Jedis jedis = RedisConnection.getConnection();
// 添加商品到购物车
public void addItem(String userId, String itemId, int quantity) {
jedis.hset("cart:" + userId, itemId, String.valueOf(quantity));
}
// 获取购物车内容
public Map<String, String> getCart(String userId) {
return jedis.hgetAll("cart:" + userId);
}
// 更新商品数量
public void updateQuantity(String userId, String itemId, int quantity) {
if (quantity <= 0) {
jedis.hdel("cart:" + userId, itemId);
} else {
jedis.hset("cart:" + userId, itemId, String.valueOf(quantity));
}
}
// 清空购物车
public void clearCart(String userId) {
jedis.del("cart:" + userId);
}
}
2.2.4 性能优化建议
- 控制Hash大小:单个Hash不要包含过多字段,建议<512个
- 字段名统一长度:便于ziplist存储优化
- 使用HMSET/HMGET:批量操作减少网络开销
- 合理使用HGETALL:大数据量时使用HSCAN替代
2.3 List类型
2.3.1 数据结构原理
List类型对应Java中的LinkedList,底层采用quicklist结构实现,支持双向链表操作:
quicklist结构:
[ziplist] <-> [ziplist] <-> [ziplist]
| | |
[elements] [elements] [elements]
2.3.2 常用命令演示
Redis命令行操作
# 从左侧插入
LPUSH list "item3" "item2" "item1"
# 从右侧插入
RPUSH list "item4" "item5"
# 获取指定范围元素
LRANGE list 0 -1
# 从左侧弹出
LPOP list
# 从右侧弹出
RPOP list
# 获取列表长度
LLEN list
# 根据索引获取元素
LINDEX list 0
# 设置索引元素
LSET list 0 "new_item1"
# 阻塞式弹出(消息队列)
BLPOP queue 0
BRPOP queue 0
Java实现示例
import redis.clients.jedis.Jedis;
import java.util.List;
public class ListOperations {
public static void main(String[] args) {
Jedis jedis = RedisConnection.getConnection();
try {
// 左侧插入
jedis.lpush("task:queue", "task3", "task2", "task1");
System.out.println("Queue: " + jedis.lrange("task:queue", 0, -1));
// 右侧插入
jedis.rpush("task:queue", "task4", "task5");
// 弹出元素
String task = jedis.lpop("task:queue");
System.out.println("Processed task: " + task);
// 获取长度
System.out.println("Queue length: " + jedis.llen("task:queue"));
// 获取指定范围
List<String> tasks = jedis.lrange("task:queue", 0, 2);
System.out.println("First 3 tasks: " + tasks);
} finally {
jedis.close();
}
}
}
2.3.3 典型应用场景
1. 消息队列
public class MessageQueue {
private Jedis jedis = RedisConnection.getConnection();
// 发送消息
public void sendMessage(String queueName, String message) {
jedis.rpush(queueName, message);
}
// 接收消息(阻塞式)
public String receiveMessage(String queueName, int timeout) {
List<String> result = jedis.blpop(timeout, queueName);
if (result != null && !result.isEmpty()) {
return result.get(1);
}
return null;
}
// 批量发送消息
public void batchSend(String queueName, List<String> messages) {
jedis.rpush(queueName, messages.toArray(new String[0]));
}
// 获取队列状态
public long getQueueLength(String queueName) {
return jedis.llen(queueName);
}
}
2. 最新列表
public class LatestItems {
private Jedis jedis = RedisConnection.getConnection();
private static final int MAX_SIZE = 100;
// 添加最新内容
public void addLatest(String type, String content) {
String key = "latest:" + type;
jedis.lpush(key, content);
// 保持列表长度
jedis.ltrim(key, 0, MAX_SIZE - 1);
}
// 获取最新内容列表
public List<String> getLatest(String type, int count) {
String key = "latest:" + type;
return jedis.lrange(key, 0, count - 1);
}
// 分页获取
public List<String> getPaginated(String type, int page, int pageSize) {
String key = "latest:" + type;
int start = (page - 1) * pageSize;
int end = start + pageSize - 1;
return jedis.lrange(key, start, end);
}
}
2.3.4 性能优化建议
- 控制列表长度:单个List避免过长,建议<5000元素
- 使用LRANGE分页:避免一次性获取全部元素
- 合理使用阻塞命令:BLPOP/BRPOP设置合理超时时间
- 定期清理:对过期数据使用LTRIM清理
2.4 Set类型
2.4.1 数据结构原理
Set类型对应Java中的HashSet,存储不重复字符串集合,底层采用intset 或hashtable实现:
intset结构(整数集合):
[encoding][length][element1][element2][...]
hashtable结构(字符串集合):
[hash_function(key)] -> [bucket] -> [value]
2.4.2 常用命令演示
Redis命令行操作
# 添加成员
SADD set1 "member1" "member2" "member3"
# 获取所有成员
SMEMBERS set1
# 判断成员是否存在
SISMEMBER set1 "member1"
# 获取成员数量
SCARD set1
# 删除成员
SREM set1 "member2"
# 随机获取成员
SRANDMEMBER set1 2
# 集合运算
SADD set2 "member2" "member3" "member4"
SINTER set1 set2 # 交集
SUNION set1 set2 # 并集
SDIFF set1 set2 # 差集
Java实现示例
import redis.clients.jedis.Jedis;
import java.util.Set;
public class SetOperations {
public static void main(String[] args) {
Jedis jedis = RedisConnection.getConnection();
try {
// 添加成员
jedis.sadd("tags:java", "spring", "redis", "mybatis");
jedis.sadd("tags:python", "django", "flask", "redis");
// 获取所有标签
System.out.println("Java tags: " + jedis.smembers("tags:java"));
// 判断标签是否存在
System.out.println("Has redis: " + jedis.sismember("tags:java", "redis"));
// 集合运算
Set<String> commonTags = jedis.sinter("tags:java", "tags:python");
System.out.println("Common tags: " + commonTags);
// 获取集合大小
System.out.println("Java tags count: " + jedis.scard("tags:java"));
// 随机获取标签
System.out.println("Random tags: " + jedis.srandmember("tags:java", 2));
} finally {
jedis.close();
}
}
}
2.4.3 典型应用场景
1. 标签系统
public class TagService {
private Jedis jedis = RedisConnection.getConnection();
// 添加标签
public void addTags(String objectId, Set<String> tags) {
String key = "tags:" + objectId;
jedis.sadd(key, tags.toArray(new String[0]));
}
// 获取标签
public Set<String> getTags(String objectId) {
return jedis.smembers("tags:" + objectId);
}
// 查找共同标签的对象
public Set<String> findObjectsWithCommonTags(String objectId1, String objectId2) {
return jedis.sinter("tags:" + objectId1, "tags:" + objectId2);
}
// 查找包含指定标签的对象
public Set<String> findObjectsWithTag(String tag) {
// 需要建立反向索引:tag:redis -> {object1, object2, ...}
return jedis.smembers("tag_index:" + tag);
}
}
2. 去重统计
public class UniqueCounter {
private Jedis jedis = RedisConnection.getConnection();
// 记录唯一访问
public boolean recordVisit(String date, String userId) {
String key = "visitors:" + date;
return jedis.sadd(key, userId) > 0;
}
// 获取日活用户数
public long getDailyUniqueVisitors(String date) {
return jedis.scard("visitors:" + date);
}
// 获取多天共同用户
public Set<String> getCommonVisitors(String... dates) {
String[] keys = new String[dates.length];
for (int i = 0; i < dates.length; i++) {
keys[i] = "visitors:" + dates[i];
}
return jedis.sinter(keys);
}
// 获取多天总用户数
public Set<String> getTotalVisitors(String... dates) {
String[] keys = new String[dates.length];
for (int i = 0; i < dates.length; i++) {
keys[i] = "visitors:" + dates[i];
}
return jedis.sunion(keys);
}
}
2.4.4 性能优化建议
- 控制Set大小:单个Set成员数量建议<10000
- 批量操作:使用SADD批量添加减少网络开销
- 合理使用集合运算:大数据量集合运算可能耗时
- 监控内存使用:Set占用内存相对较高,需合理规划
2.5 ZSet类型
2.5.1 数据结构原理
ZSet是有序集合,每个成员关联一个分数,按分数排序。底层采用skiplist 和hashtable组合实现:
zset结构:
{
dict: {member: score}, // 成员到分数的映射
zsl: skiplist // 跳表,按分数排序
}
skiplist结构(简化):
Level3: [header] -> [node1] -> [node3]
Level2: [header] -> [node1] -> [node2] -> [node3]
Level1: [header] -> [node1] -> [node2] -> [node3] -> [tail]
2.5.2 常用命令演示
Redis命令行操作
# 添加成员
ZADD leaderboard 100 "player1" 200 "player2" 150 "player3"
# 获取排名范围
ZRANGE leaderboard 0 9
ZRANGE leaderboard 0 -1 WITHSCORES
# 按分数范围获取
ZRANGEBYSCORE leaderboard 100 200
# 获取成员排名
ZRANK leaderboard "player2"
ZREVRANK leaderboard "player2"
# 获取成员分数
ZSCORE leaderboard "player2"
# 增加分数
ZINCRBY leaderboard 50 "player1"
# 获取指定排名成员
ZREVRANGE leaderboard 0 2
# 获取分数范围内的成员数
ZCOUNT leaderboard 100 200
# 删除成员
ZREM leaderboard "player3"
Java实现示例
import redis.clients.jedis.Jedis;
import java.util.Set;
import java.util.Map;
public class ZSetOperations {
public static void main(String[] args) {
Jedis jedis = RedisConnection.getConnection();
try {
// 添加成员
jedis.zadd("leaderboard", 100, "player1");
jedis.zadd("leaderboard", 200, "player2");
jedis.zadd("leaderboard", 150, "player3");
jedis.zadd("leaderboard", 180, "player4");
// 获取排行榜
Set<String> leaders = jedis.zrevrange("leaderboard", 0, 4);
System.out.println("Leaders: " + leaders);
// 获取带分数的排行榜
Set<redis.clients.jedis.Tuple> leadersWithScores =
jedis.zrevrangeWithScores("leaderboard", 0, 4);
for (redis.clients.jedis.Tuple tuple : leadersWithScores) {
System.out.println(tuple.getElement() + ": " + tuple.getScore());
}
// 获取玩家排名
long rank = jedis.zrevrank("leaderboard", "player2");
System.out.println("Player2 rank: " + (rank + 1));
// 增加分数
jedis.zincrby("leaderboard", 50, "player1");
System.out.println("Player1 new score: " + jedis.zscore("leaderboard", "player1"));
// 按分数范围查询
Set<String> highScorePlayers = jedis.zrangeByScore("leaderboard", 150, 250);
System.out.println("High score players: " + highScorePlayers);
} finally {
jedis.close();
}
}
}
2.5.3 典型应用场景
1. 排行榜系统
public class LeaderboardService {
private Jedis jedis = RedisConnection.getConnection();
// 更新玩家分数
public void updateScore(String leaderboardId, String playerId, double score) {
String key = "leaderboard:" + leaderboardId;
jedis.zadd(key, score, playerId);
}
// 增加玩家分数
public void incrementScore(String leaderboardId, String playerId, double delta) {
String key = "leaderboard:" + leaderboardId;
jedis.zincrby(key, delta, playerId);
}
// 获取Top N玩家
public Set<String> getTopPlayers(String leaderboardId, int topN) {
String key = "leaderboard:" + leaderboardId;
return jedis.zrevrange(key, 0, topN - 1);
}
// 获取玩家排名
public long getPlayerRank(String leaderboardId, String playerId) {
String key = "leaderboard:" + leaderboardId;
Long rank = jedis.zrevrank(key, playerId);
return rank != null ? rank + 1 : -1;
}
// 获取玩家周围排名
public Set<redis.clients.jedis.Tuple> getPlayerRankRange(
String leaderboardId, String playerId, int range) {
String key = "leaderboard:" + leaderboardId;
long playerRank = getPlayerRank(leaderboardId, playerId);
if (playerRank == -1) return null;
long start = Math.max(0, playerRank - range - 1);
long end = playerRank + range - 1;
return jedis.zrevrangeWithScores(key, start, end);
}
}
2. 延时队列
public class DelayedQueue {
private Jedis jedis = RedisConnection.getConnection();
// 添加延时任务
public void addDelayedTask(String task, long delaySeconds) {
String key = "delayed_queue";
long executeTime = System.currentTimeMillis() / 1000 + delaySeconds;
jedis.zadd(key, executeTime, task);
}
// 获取到期的任务
public java.util.List<String> getReadyTasks() {
String key = "delayed_queue";
long currentTime = System.currentTimeMillis() / 1000;
// 获取所有到期任务
Set<String> readyTasks = jedis.zrangeByScore(key, 0, currentTime);
// 删除已获取的任务
if (!readyTasks.isEmpty()) {
jedis.zremrangeByScore(key, 0, currentTime);
return new java.util.ArrayList<>(readyTasks);
}
return new java.util.ArrayList<>();
}
// 获取下一个任务的时间
public Long getNextTaskTime() {
String key = "delayed_queue";
Set<redis.clients.jedis.Tuple> tasks = jedis.zrangeWithScores(key, 0, 0);
if (tasks.isEmpty()) {
return null;
}
return (long) tasks.iterator().next().getScore();
}
}
2.5.4 性能优化建议
-
控制ZSet大小:单个ZSet成员数量建议<10000
-
合理使用ZRANGE:避免一次性获取大量成员
-
定期清理过期数据:使用ZREMRANGEBYSCORE清理
-
分数设计:使用时间戳或数值,避免浮点精度问题
public class RedisConnectionPool {
private static JedisPool jedisPool;static { JedisPoolConfig config = new JedisPoolConfig(); // 基础配置 config.setMaxTotal(100); // 最大连接数 config.setMaxIdle(50); // 最大空闲连接数 config.setMinIdle(10); // 最小空闲连接数 // 获取连接配置 config.setMaxWaitMillis(3000); // 获取连接最大等待时间 config.setTestOnBorrow(true); // 获取连接时测试 config.setTestOnReturn(false); // 归还连接时不测试 config.setTestWhileIdle(true); // 空闲时测试 // 空闲连接回收 config.setTimeBetweenEvictionRunsMillis(30000); // 检查空闲连接间隔 config.setMinEvictableIdleTimeMillis(60000); // 空闲连接最小生存时间 // 创建连接池 jedisPool = new JedisPool(config, "localhost", 6379, 3000, "password", 0); } public static Jedis getConnection() { return jedisPool.getResource(); } public static void closePool() { if (jedisPool != null) { jedisPool.close(); } }}