Redis协议与数据结构

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    $<参数的字节数量> 参数SET3个字节
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。
特点

  1. 使用len保存字符串长度,O(1)时间复杂度获得长度,高于传统C语言遍历获得字符串长度的O(n)
  2. 避免缓冲区溢出,在修改时会自动扩容后才修改
  3. 通过预分配(min(len,1M))和惰性释放(在裁剪字符串后并不真正释放空间),减少内存分配次数
struct sdshdr8{
	uint8_t len; // 保存的已使用的字符串长度
	uint8_t alloc;// 除去头部和尾部\0的剩余长度
	unsigned char flags;// 低三位来表示sds结构类型
	char buf[];// 存放字符串
}

List

LinkList

双端链表,链表节点保存前后指针和当前值
特点

  1. 使用len保存链表长度,获得链表长度时间复杂度O(1)
  2. 获得表头和表尾时间复杂度O(1)
  3. 多态,链表节点可保存不同类型的值
// 节点结构
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
特点

  1. 节约内存
  2. 添加新节点或删除节点可能引发连锁更新

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中的字典使用哈希表作为底层实现
特点

  1. 使用链地址法解决索引冲突
  2. 持有两个hash表用来rehash,拓展大小为>=used*2的最小二次幂,收缩大小为>=used的最小二次幂
  3. 当没有执行数据库保存命令时负载因子>=1则执行拓展,否则>=5才执行拓展
  4. 渐进式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实现
特点

  1. 支持类型升级,当输入整数超出当前contents数组类型范围时,对整个数组类型进行升级,如int8 => int 16
  2. 只支持升级不支持降级
typedef struct intset{
    uint32_t encoding; // 编码方式
    uint32_t length;  // 集合包含元素数量
    int8_t contents[]; // 保存元素的数组,从小到大有序排列
}

Zset

ziplist

用于表示小的有序集合

skipList

跳表为redis有序集合的底层实现之一。
特点

  1. 节点层数确定:随机生成1~32之间的数,数越大生成概率越小
  2. 分数相同的节点按照成员对象的字典序大小排列
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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值