基于Java实现的Redis服务器,完全兼容Redis协议(RESP),支持Redis的核心功能。
MiniRedis是一个用Java实现的Redis服务器,旨在提供一个学习和理解Redis内部工作原理的平台。项目采用模块化设计,实现了Redis的核心功能,包括:
- 完整的数据结构支持:String、List、Set、Hash、ZSet(有序集合)
- 持久化机制:支持RDB快照和AOF日志两种持久化方式
- 主从复制:支持Redis主从复制功能
- 高级特性:事务、Lua脚本执行、发布订阅、键过期等
- 协议兼容:完全兼容Redis协议(RESP),可以使用标准Redis客户端连接
- 版本:1.0-SNAPSHOT
- Java版本:JDK 8+
- 状态:开发中,核心功能已实现
- Java 8+:核心开发语言
- Maven:项目构建和依赖管理
- Netty 4.1.53:高性能网络通信框架
- LuaJ:Lua脚本执行引擎
- Logback:日志框架
- Guava:Google核心库,提供工具类
- Apache Commons:通用工具库
+---------------+
| 客户端 |
+-------+-------+
|
+----------+----------+
| Netty网络层 |
+----------+----------+
|
+-----------------+------------+
| java-redis 服务器 |
+-----------------+------------+
| | |
+-------v-----+ +-------v-----+ +-----v-----+
|命令解析模块 | |数据处理模块 | |持久化模块 |
+---------------+ +---------------+ +---------+
| | |
+-------+-------+ +-------+-------+ +-------+-------+
|协议编解码 | |存储引擎 | |RDB/AOF |
+---------------+ +---------------+ +---------------+
项目采用多模块Maven架构,各模块职责如下:
| 模块 | 职责说明 |
|---|---|
| java-redis30 | 主服务器实现模块,包含命令处理、服务器上下文、客户端会话管理等核心功能 |
| redis-protocol | Redis协议(RESP)编解码实现,处理客户端请求和服务器响应 |
| redis-storage | 数据存储层,提供Dict接口和DictValue数据结构实现 |
| redis-persistence | 持久化实现,支持RDB快照和AOF日志的读写 |
| redis-client | Java客户端实现,提供与服务器交互的客户端API |
| remoting-netty4 | 基于Netty的网络通信层,处理TCP连接和消息传输 |
| redis-common | 通用工具类和数据结构,如ZSet、BytesKey等 |
| redis-context | 服务器上下文配置,包含DBConfig等配置类 |
| persistence-collections | 持久化集合实现,提供Copy-on-Write数据结构 |
| redis-jmh | 性能基准测试模块 |
| redis-original | 原始实现(可能为历史版本) |
-
命令处理流程
- 客户端通过Netty发送Redis协议命令
RedisServerInitializer初始化Channel PipelineRedisCommandHandler处理解码后的命令CommandSuite根据命令名称查找对应的命令实现- 命令执行后返回响应给客户端
-
数据存储
- 使用
Dict接口作为数据存储抽象 - 每个数据库(RedisDb)包含一个主字典和一个过期字典
- 支持多种数据类型:String、List、Set、Hash、ZSet
- 使用
-
持久化机制
- RDB:定期快照,将内存数据序列化到磁盘
- AOF:追加日志,记录所有写操作
- 支持启动时自动加载持久化文件
- JDK 8 或更高版本
- Maven 3.6 或更高版本
- 操作系统:Windows、Linux、macOS
# 克隆项目(如果从Git仓库)
git clone <repository-url>
cd MiniRedis
# 编译项目
mvn clean compile
# 打包项目
mvn clean package# 方式1:直接运行主类
cd java-redis30
mvn exec:java -Dexec.mainClass="com.daicy.redis.RedisServer"
# 方式2:运行打包后的jar
java -cp target/java-redis30-1.0-SNAPSHOT.jar:../target/classes com.daicy.redis.RedisServer服务器默认监听端口:6380
启动成功后,你会看到:
redis server is up! localhost:6380
使用标准Redis客户端连接测试:
# 使用redis-cli(需要指定端口)
redis-cli -p 6380
# 测试命令
127.0.0.1:6380> PING
PONG
127.0.0.1:6380> SET key value
OK
127.0.0.1:6380> GET key
"value"- String(字符串):基本的键值对存储
- List(列表):有序的字符串列表,支持双向操作
- Set(集合):无序的字符串集合,支持集合运算
- Hash(哈希表):字段-值映射表
- ZSet(有序集合):带分数的有序集合
-
RDB持久化:
- 支持
SAVE和BGSAVE命令 - 定期自动保存(可配置)
- 启动时自动加载RDB文件
- 支持
-
AOF持久化:
- 记录所有写操作
- 启动时重放AOF日志恢复数据
- 支持AOF文件压缩
- 支持
SLAVEOF命令配置主从关系 - 支持
SYNC命令进行全量同步 - 支持增量复制(通过AOF传播)
- 支持
MULTI、EXEC、DISCARD命令 - 事务中的命令会被队列化,直到
EXEC执行 - 支持事务回滚
- 支持
EVAL和EVALSHA命令 - 使用LuaJ引擎执行Lua脚本
- 支持脚本缓存和SHA1校验
- 支持
SUBSCRIBE、UNSUBSCRIBE、PUBLISH命令 - 支持模式订阅:
PSUBSCRIBE、PUNSUBSCRIBE - 支持频道和模式两种订阅方式
- 支持键过期:
EXPIRE、PEXPIRE、TTL、PTTL - 支持键删除:
DEL - 支持键查询:
EXISTS、KEYS、TYPE - 支持键重命名:
RENAME
SETBIT:设置指定位的值GETBIT:获取指定位的值BITCOUNT:统计指定位范围内1的个数
| 命令 | 说明 | 示例 |
|---|---|---|
| GET | 获取键的值 | GET key |
| SET | 设置键值对 | SET key value |
| 命令 | 说明 | 示例 |
|---|---|---|
| LPUSH | 从左边推入元素 | LPUSH list value |
| RPUSH | 从右边推入元素 | RPUSH list value |
| LPOP | 从左边弹出元素 | LPOP list |
| RPOP | 从右边弹出元素 | RPOP list |
| LLEN | 获取列表长度 | LLEN list |
| LRANGE | 获取列表范围 | LRANGE list 0 -1 |
| LINDEX | 获取指定索引的元素 | LINDEX list 0 |
| LSET | 设置指定索引的元素 | LSET list 0 value |
| 命令 | 说明 | 示例 |
|---|---|---|
| SADD | 添加元素到集合 | SADD set member |
| SMEMBERS | 获取集合所有成员 | SMEMBERS set |
| SREM | 从集合移除元素 | SREM set member |
| SISMEMBER | 判断元素是否在集合中 | SISMEMBER set member |
| SCARD | 获取集合元素数量 | SCARD set |
| SPOP | 随机弹出元素 | SPOP set |
| SRANDMEMBER | 随机获取元素 | SRANDMEMBER set |
| SINTER | 求交集 | SINTER set1 set2 |
| SUNION | 求并集 | SUNION set1 set2 |
| SDIFF | 求差集 | SDIFF set1 set2 |
| 命令 | 说明 | 示例 |
|---|---|---|
| HSET | 设置哈希字段值 | HSET hash field value |
| HGET | 获取哈希字段值 | HGET hash field |
| HGETALL | 获取所有字段和值 | HGETALL hash |
| HDEL | 删除哈希字段 | HDEL hash field |
| HEXISTS | 判断字段是否存在 | HEXISTS hash field |
| HKEYS | 获取所有字段名 | HKEYS hash |
| HVALS | 获取所有值 | HVALS hash |
| HLEN | 获取字段数量 | HLEN hash |
| HMSET | 批量设置字段 | HMSET hash f1 v1 f2 v2 |
| HMGET | 批量获取字段 | HMGET hash f1 f2 |
| 命令 | 说明 | 示例 |
|---|---|---|
| ZADD | 添加成员及分数 | ZADD zset 1.0 member |
| ZRANGE | 按索引范围获取成员 | ZRANGE zset 0 -1 |
| ZREVRANGE | 反向按索引范围获取 | ZREVRANGE zset 0 -1 |
| ZRANGEBYSCORE | 按分数范围获取 | ZRANGEBYSCORE zset 0 100 |
| ZREM | 删除成员 | ZREM zset member |
| ZCARD | 获取成员数量 | ZCARD zset |
| ZINCRBY | 增加成员分数 | ZINCRBY zset 1.0 member |
| 命令 | 说明 | 示例 |
|---|---|---|
| DEL | 删除键 | DEL key |
| EXISTS | 判断键是否存在 | EXISTS key |
| EXPIRE | 设置过期时间(秒) | EXPIRE key 60 |
| PEXPIRE | 设置过期时间(毫秒) | PEXPIRE key 60000 |
| TTL | 获取剩余时间(秒) | TTL key |
| PTTL | 获取剩余时间(毫秒) | PTTL key |
| PERSIST | 移除过期时间 | PERSIST key |
| KEYS | 查找匹配的键 | KEYS * |
| RANDOMKEY | 随机获取一个键 | RANDOMKEY |
| RENAME | 重命名键 | RENAME old new |
| TYPE | 获取键的类型 | TYPE key |
| 命令 | 说明 | 示例 |
|---|---|---|
| PING | 测试连接 | PING |
| ECHO | 回显消息 | ECHO message |
| QUIT | 关闭连接 | QUIT |
| SELECT | 选择数据库 | SELECT 0 |
| INFO | 获取服务器信息 | INFO |
| DBSIZE | 获取键数量 | DBSIZE |
| FLUSHDB | 清空当前数据库 | FLUSHDB |
| SAVE | 同步保存RDB | SAVE |
| BGSAVE | 异步保存RDB | BGSAVE |
| SLAVEOF | 配置主从关系 | SLAVEOF host port |
| SYNC | 同步数据 | SYNC |
| 命令 | 说明 | 示例 |
|---|---|---|
| MULTI | 开始事务 | MULTI |
| EXEC | 执行事务 | EXEC |
| DISCARD | 取消事务 | DISCARD |
| 命令 | 说明 | 示例 |
|---|---|---|
| EVAL | 执行Lua脚本 | EVAL "return redis.call('get', 'key')" 0 |
| EVALSHA | 执行缓存的脚本 | EVALSHA sha1 0 |
| SCRIPT | 脚本管理 | SCRIPT LOAD "..." |
| 命令 | 说明 | 示例 |
|---|---|---|
| SUBSCRIBE | 订阅频道 | SUBSCRIBE channel |
| UNSUBSCRIBE | 取消订阅 | UNSUBSCRIBE channel |
| PUBLISH | 发布消息 | PUBLISH channel message |
| PSUBSCRIBE | 模式订阅 | PSUBSCRIBE pattern |
| PUNSUBSCRIBE | 取消模式订阅 | PUNSUBSCRIBE pattern |
| 命令 | 说明 | 示例 |
|---|---|---|
| SETBIT | 设置指定位 | SETBIT key 0 1 |
| GETBIT | 获取指定位 | GETBIT key 0 |
| BITCOUNT | 统计位为1的数量 | BITCOUNT key |
服务器通过DBConfig类进行配置,支持链式构建:
DBConfig config = DBConfig.builder()
.withPersistence() // 启用持久化
.withNotifications() // 启用键空间通知
.withOffHeapCache() // 启用堆外缓存
.withRdbCcompression() // 启用RDB压缩
.build();| 配置项 | 说明 | 默认值 |
|---|---|---|
numDatabases |
数据库数量 | 16 |
persistenceActive |
是否启用持久化 | true |
rdbFile |
RDB文件路径 | dump.rdb |
aofFile |
AOF文件路径 | appendonly.aof |
syncPeriod |
同步周期(秒) | 60 |
cleanPeriod |
清理周期(秒) | 30 |
notificationsActive |
是否启用通知 | false |
offHeapActive |
是否启用堆外缓存 | false |
// 禁用持久化
DBConfig config = DBConfig.builder()
.withoutPersistence()
.build();
// 自定义持久化文件路径
DBConfig config = DBConfig.builder()
.withPersistence()
.setRdbFile("/path/to/custom.rdb")
.setAofFile("/path/to/custom.aof")
.setSyncPeriod(30) // 30秒同步一次
.build();# 连接服务器
redis-cli -p 6380
# String操作
127.0.0.1:6380> SET name "MiniRedis"
OK
127.0.0.1:6380> GET name
"MiniRedis"
# List操作
127.0.0.1:6380> LPUSH list "item1" "item2" "item3"
(integer) 3
127.0.0.1:6380> LRANGE list 0 -1
1) "item3"
2) "item2"
3) "item1"
# Hash操作
127.0.0.1:6380> HSET user name "John" age "30"
(integer) 2
127.0.0.1:6380> HGETALL user
1) "name"
2) "John"
3) "age"
4) "30"
# Set操作
127.0.0.1:6380> SADD tags "java" "redis" "netty"
(integer) 3
127.0.0.1:6380> SMEMBERS tags
1) "java"
2) "redis"
3) "netty"
# ZSet操作
127.0.0.1:6380> ZADD scores 100 "Alice" 90 "Bob" 95 "Charlie"
(integer) 3
127.0.0.1:6380> ZRANGE scores 0 -1 WITHSCORES
1) "Bob"
2) "90.0"
3) "Charlie"
4) "95.0"
5) "Alice"
6) "100.0"import com.daicy.redis.client.RedisClient;
import com.daicy.redis.client.RedisCommand;
import com.daicy.redis.protocal.BulkRedisMessage;
// 创建客户端
RedisClient client = new RedisClient();
// 执行命令
RedisCommand setCmd = new RedisCommand("SET", "key", "value");
client.send(setCmd);
RedisCommand getCmd = new RedisCommand("GET", "key");
BulkRedisMessage response = (BulkRedisMessage) client.send(getCmd);
System.out.println(response.getString()); // 输出: value
// 关闭客户端
client.shutdown();127.0.0.1:6380> MULTI
OK
127.0.0.1:6380> SET key1 "value1"
QUEUED
127.0.0.1:6380> SET key2 "value2"
QUEUED
127.0.0.1:6380> EXEC
1) OK
2) OK# 终端1:订阅频道
127.0.0.1:6380> SUBSCRIBE news
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "news"
3) (integer) 1
# 终端2:发布消息
127.0.0.1:6380> PUBLISH news "Hello World"
(integer) 1
# 终端1会收到消息
1) "message"
2) "news"
3) "Hello World"127.0.0.1:6380> EVAL "return redis.call('get', 'key')" 0
"value"
127.0.0.1:6380> EVAL "local val = redis.call('get', KEYS[1]); return val" 1 key
"value"- 创建命令类
在java-redis30/src/main/java/com/daicy/redis/command/下创建命令类:
package com.daicy.redis.command.string;
import com.daicy.redis.Request;
import com.daicy.redis.annotation.Command;
import com.daicy.redis.annotation.ParamLength;
import com.daicy.redis.annotation.ReadOnly;
import com.daicy.redis.command.DBCommand;
import com.daicy.redis.protocal.RedisMessage;
import com.daicy.redis.storage.RedisDb;
@Command("mycommand") // 命令名称
@ParamLength(1) // 参数数量
@ReadOnly // 如果是只读命令
public class MyCommand implements DBCommand {
@Override
public RedisMessage execute(RedisDb db, Request request) {
String param = request.getParamStr(0);
// 实现命令逻辑
return new BulkRedisMessage("result");
}
}- 注册命令
在META-INF/services/com.daicy.redis.command.RedisCommand文件中添加命令类:
com.daicy.redis.command.string.MyCommand
- 重新编译
mvn clean compile| 注解 | 说明 | 示例 |
|---|---|---|
@Command |
指定命令名称 | @Command("get") |
@ParamLength |
指定参数数量 | @ParamLength(1) |
@ParamType |
指定参数类型 | @ParamType(DataType.STRING) |
@ReadOnly |
标记为只读命令 | @ReadOnly |
@TxIgnore |
事务中忽略此命令 | @TxIgnore |
@PubSubAllowed |
允许在发布订阅模式下使用 | @PubSubAllowed |
java-redis30
├── redis-protocol (协议编解码)
├── redis-storage (数据存储)
├── redis-persistence (持久化)
├── redis-client (客户端)
├── remoting-netty4 (网络通信)
├── redis-context (配置)
└── redis-common (通用工具)
redis-persistence
├── redis-storage
└── redis-protocol
redis-client
├── redis-protocol
└── remoting-netty4
MiniRedis/
├── java-redis30/ # 主服务器模块
│ ├── src/main/java/
│ │ └── com/daicy/redis/
│ │ ├── command/ # 命令实现
│ │ │ ├── string/ # 字符串命令
│ │ │ ├── list/ # 列表命令
│ │ │ ├── set/ # 集合命令
│ │ │ ├── hash/ # 哈希命令
│ │ │ ├── zset/ # 有序集合命令
│ │ │ ├── key/ # 键命令
│ │ │ ├── db/ # 服务器命令
│ │ │ ├── transaction/ # 事务命令
│ │ │ ├── lua/ # Lua脚本命令
│ │ │ └── pubsub/ # 发布订阅命令
│ │ ├── handler/ # 请求处理器
│ │ ├── event/ # 事件通知
│ │ └── RedisServer.java # 服务器入口
│ └── pom.xml
├── redis-protocol/ # 协议模块
├── redis-storage/ # 存储模块
├── redis-persistence/ # 持久化模块
├── redis-client/ # 客户端模块
├── remoting-netty4/ # 网络通信模块
├── redis-common/ # 通用模块
├── redis-context/ # 上下文模块
├── persistence-collections/ # 持久化集合模块
├── redis-jmh/ # 性能测试模块
├── pom.xml # 父POM
└── README.md # 项目文档
- RedisServer:服务器启动入口
- DefaultRedisServerContext:服务器上下文,管理数据库、客户端、命令等
- RedisDb:数据库实例,包含数据字典和过期字典
- CommandSuite:命令注册表,管理所有命令
- PersistenceManager:持久化管理器,处理RDB和AOF
- ReplicationManager:复制管理器,处理主从复制
- RedisCommandHandler:命令处理器,处理客户端请求
欢迎提交Issue和Pull Request!
本项目采用MIT许可证。
注意:本项目主要用于学习和研究目的,不建议在生产环境使用。
