文章目录
Redis通信协议RESP
Redis Serialization Protocol,redis通信协议是基于TCP的,命令或数据一律以\r\n结尾。
请求格式
通过新建ServerSocket简体6379端口,模拟redis服务器,通过redis客户端向服务器发送命令
public static void main(String[] args) {
RedisURI uri = RedisURI.create("redis://127.0.0.1:6379?timeout=10s");
RedisClient redisClient = RedisClient.create(uri);
StatefulRedisConnection<String, String> connect = redisClient.connect();
// 创建同步命令
RedisCommands<String, String> commands = connect.sync();
String first_java = commands.set("client:test", "first java");
// 打印命令返回值
System.out.println(first_java);
// 先关闭连接后关闭客户端
connect.close();
redisClient.shutdown();
}
*3 *<参数数量> 总共三个参数 SET、client:test、first java
$3 $<参数的字节数量> 参数SET占3个字节
SET <参数的数据>
$11
client:test
$10
first java
响应格式
以不同的字符作为响应的第一个字节来表示不同的响应类型,通过模拟redis服务器返回响应+OK
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(6379);
while(true){
Socket accept = serverSocket.accept();
new Thread(()->{
try (
BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
BufferedOutputStream bos = new BufferedOutputStream(accept.getOutputStream())
) {
byte[] bytes = new byte[1024];
int len = bis.read(bytes);
System.out.println(new String(bytes, 0, len));
bos.write("+OK\r\n".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
+ 状态回复 +OK
- 错误回复 -ERR
: 整数回复 :100
$ 批量回复 $5 hello 当请求值不存在时返回$-1
多条批量回复与请求协议格式相同,如下
*3
:100
$5
hello
$5
world
数据结构
redisObject对象结构如下
struct redisObject{
unsigned type:4bit;// 对象类型 如:REDIS_STRING
unsigned encoding:4bit;// 编码方式,底层的编码实现方式 如:REDIS_ENCODING_RAW
unsigned lru:LRU_BITS; //记录对象最后一次被命令程序访问的时间
int refcount; // 引用计数
void *ptr; // 对象指针
}
String
redis默认使用SDS(simple dynamic string)类型来表示字符串,有三种编码方式,分别为int、embstr(对象长度小于39字节使用,3.2版本之后由于sds结构变化改为44)和raw。
特点
- 使用len保存字符串长度,O(1)时间复杂度获得长度,高于传统C语言遍历获得字符串长度的O(n)
- 避免缓冲区溢出,在修改时会自动扩容后才修改
- 通过预分配(min(len,1M))和惰性释放(在裁剪字符串后并不真正释放空间),减少内存分配次数
struct sdshdr8{
uint8_t len; // 保存的已使用的字符串长度
uint8_t alloc;// 除去头部和尾部\0的剩余长度
unsigned char flags;// 低三位来表示sds结构类型
char buf[];// 存放字符串
}
List
LinkList
双端链表,链表节点保存前后指针和当前值
特点
- 使用len保存链表长度,获得链表长度时间复杂度O(1)
- 获得表头和表尾时间复杂度O(1)
- 多态,链表节点可保存不同类型的值
// 节点结构
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value;
}
typedef struct list {
listNode *head; // 头结点
listNode *tail; // 尾节点
unsigned long len; // 链表长度
// 复制链表节点数据
void *(*dup)(void *ptr);
// 释放链表节点数据
void (*free)(void *ptr);
// 对比链表保存值与输入值是否相等
int (*match)(void *ptr, void *key);
}
ziplist
压缩列表,redis为节约空间使用,当列表键只包含少量列表项,且列表项为小整数或短的字符串则列表底层使用ziplist
特点
- 节约内存
- 添加新节点或删除节点可能引发连锁更新
ziplist结构如下
zlbytes:记录压缩列表占用字节数
zltail:记录尾节点距离起始地址的字节数
zllen:记录节点数量,当大于65535后需遍历得出数量
entryX:表示存放的节点
zlend:特殊值0xFF,表示压缩列表的末端
entry结构如下
previous_entry:记录上一个节点长度,1字节或5字节
encoding:记录节点content的类型及长度,1字节且以11开头表示整数,1,2,5字节以10,00,01开头表示字节数组
content:保存的节点值
Hash
zipmap
轻量级的字典,当键值对量不大且单个键和单个值长度小时使用
Dict
Redis中的字典使用哈希表作为底层实现
特点
- 使用链地址法解决索引冲突
- 持有两个hash表用来rehash,拓展大小为>=used*2的最小二次幂,收缩大小为>=used的最小二次幂
- 当没有执行数据库保存命令时负载因子>=1则执行拓展,否则>=5才执行拓展
- 渐进式rehash,通过rehashidx索引渐进的将ht[0]中的键值对转移到ht[1]中
// hash表结构
typedef struct dictht {
// hash表数组
dictEntry **table;
// hash表大小
unsigned long size;
// 掩码=size-1,用于和hash值计算索引位置
unsigned long sizemask;
// 已有节点数量
unsigned long used;
}
// hash表节点
typedef struct dictEntry {
// 当前key值
void *key;
// key对应的value值
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
// 索引冲突时,链表存储
struct dictEntry *next;
}
// 字典结构
typedef struct dict {
// 类型特定函数
dictType *type;
// 私有数据
void *privdata;
// hash表
dictht ht[2];
// rehash索引
long rehashidx;
// 迭代器数量
unsigned long iterators;
}
Set
dict
字典是实现集合的通用方式,集合中的元素作为字典中的可以存储,value值都为null。
intset
当集合键只包含整数时,且元素个数不多时,底层采用intset实现
特点
- 支持类型升级,当输入整数超出当前contents数组类型范围时,对整个数组类型进行升级,如int8 => int 16
- 只支持升级不支持降级
typedef struct intset{
uint32_t encoding; // 编码方式
uint32_t length; // 集合包含元素数量
int8_t contents[]; // 保存元素的数组,从小到大有序排列
}
Zset
ziplist
用于表示小的有序集合
skipList
跳表为redis有序集合的底层实现之一。
特点
- 节点层数确定:随机生成1~32之间的数,数越大生成概率越小
- 分数相同的节点按照成员对象的字典序大小排列
typedef struct zskiplistNode {
// 成员对象
robj *obj;
// 分值
double score;
// 后退指针
struct zskiplistNode *backward;
// 层
struct zskiplistLevel {
// 前进指针
struct zskiplistNode *forward;
// 跨度
unsigned int span;
} level[];
}
typedef struct zskiplist {
struct zskiplistNode *header, *tail;// 跳跃表头尾节点
unsigned long length;// 跳跃表长度,不包含表头节点
int level;// 最大层数节点的层数
}
// zset结构,通过维护dict来确保member唯一
typedef struct zset {
dict *dict;
zskiplist *zsl;
}