redis 官方网站 https://redis.io/
redis 官网文档 https://redis.io/docs/
redis 官网 RESP相关 https://redis.io/docs/reference/protocol-spec/
菜鸟教程 https://www.runoob.com/redis/redis-intro.html
redis 简介
redis数据库,REmote DIctionary Server(Redis) 是一个由 Salvatore Sanfilippo 写的 key-value 存储系统,是跨平台的非关系型数据库。Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库,并提供多种语言的 API。Redis 通常被称为数据结构服务器,因为值(value)可以是字符串(String)、哈希(Hash)、列表(list)、集合(sets)
有五种基本的数据结构
- string(字符串)
- hash(散列)
- list(链表)
- set(集合)
- sort set(有序集合)
几种基本数据类型存储方式以及应用场景
- 字符串 (String):
- 存储方式:Redis的字符串是动态字符串,类似于Java中的ArrayList。内部实现为一个结构体,其中包含长度(len)、空间(free)和字节数组(buf)。在字符串长度小于1M时,每次扩容都是加倍现有空间,超过1M后,每次扩容增加1M空间。
- 应用场景:除了存储普通字符串外,还经常用作各种类型的数值的存储,例如计数器、分布式锁的设置等。
- 哈希 (Hash):
- 存储方式:Hash类型在底层是通过哈希表实现的,当哈希集合中的元素个数和元素大小都比较小时,Redis会使用一种称作ziplist(压缩列表)的紧凑数据结构来存储哈希。当数据量增加之后,Redis会改用更标准的哈希表结构来存储键值对。
- 应用场景:适用于存储对象,可以用来表示用户的各种属性等。
- 链表 (List):
- 存储方式:List类型在底层主要是通过双向链表实现的,同样会在元素个数较少的情况下使用ziplist作为底层结构,节约内存空间。当列表项的数量或者大小超过预设的阈值时,会改用双向链表。
- 应用场景:适合用于实现队列和栈,以及在添加或删除元素时保持高性能。
- 集合 (Set):
- 存储方式:Set的底层同样是使用哈希表来实现的,这使得集合中的每个元素都是唯一的且没有顺序。在元素较少的情况下,会使用类似于哈希的ziplist存储结构,以优化内存使用。
- 应用场景:适合储存不重复的元素集合,例如标签、朋友关系等。
- 有序集合 (Sorted Set):
- 存储方式:有序集合是一种排序的集合,底层实际上是通过跳跃表(skip list)和哈希表共同实现的。跳跃表用于按分数排序和获取排名,而哈希表则用于存储成员到分数的映射。
- 应用场景:适用于需要按照一定顺序排列元素的场景,如排行榜。
redis 应用场景与底层实现,命令结构不再本文档重点关注范围内,因此不在这里赘述
前后端通讯协议
redis数据库前后端通讯使用RESP (Redis serialization protocol )协议,是专门为redis设计的一种文本传输协议,在redis 1.2中开始使用该协议,redis2中升级为 RESP2,redis6.0 引入了RESP3,RESP3 是RESP2的超集。
协议简介
RESP 大多数情况下是依托于tcp协议的,虽然从技术角度讲,该协议并不是面向tcp的协议,默认的监听端口是6379。
RESP本质上是一种串行协议,支持很多种消息类型,其每种消息的第一个字段,指示了这种消息的类型。由于他本身是一种文本通讯协议,跟传统的协议不同的是,每种消息的大小,长度,都是不固定的,包括其标识每条消息的长度信息,以及对应的载荷信息,长度都是不固定的,位置在报文中也不固定,唯一能确定消息结构的,只有每条消息的第一个字段。
虽然redis支持多种不同数据类型,但对于用户是完全透明的,因此在RESP协议的设计中,也考虑到了这一点,设计的相对较为简单,其主体思路就是将命令拆分整合,发送给服务端或者客户端,由服务端或者客户端去解析这些报文。
消息类型
数据类型 | 引入版本 | 种类 | 第一个字节 |
---|---|---|---|
simple strting | RESP2 | simple | + |
simple errors | RESP2 | simple | - |
integers | RESP2 | simple | : |
bulk string | RESP2 | aggregate | $ |
array | RESP2 | aggregate | * |
Nulls | RESP3 | simple | - |
booleans | RESP3 | simple | # |
Doubles | RESP3 | simple | , |
Big number | RESP3 | simple | ( |
Bulk errors | RESP3 | aggregate | ! |
verbatim string | RESP3 | aggregate | = |
Maps | RESP3 | aggregate | % |
sets | RESP3 | aggregate | ~ |
pushes | RESP3 | aggregate | > |
字段说明
- simple string
简单字符串,常用于服务端相应消息,消息结构为 “+” \r\n
示例:
+OK\r\n
- simple errors
大多数情况下是服务端发送给客户端的消息(目前没见过客户端发送的err消息不确定会不会有)消息结构为 “-”\r\n
和简单消息结构相同,只不过前缀字符从 “+” 变成了 “-”
示例:
-Error message\r\n
-ERR unknown command 'asdf'
-WRONGTYPE Operation against a key holding the wrong kind of value
- Intergers
十进制 64位整数,在协议中也是以字符串的形式存在的,其基本的数据格式为
:[<+|->]<value>\r\n
样例:
:0\r\n
:100\r\n
- Bulk String
Bulk String 表示一个二级制字符串,这个字符串长度并没有限制,但是redis规定字符串长度最长是512MB。
Bulk String 可以在array中出现,也可以单独出现,引导字符为 “$”。
基本格式为
$<length>\r\n<data>\r\n
样例
$5\r\nhello\r\n
# 空字符
$0\r\n\r\n
- Array
这个类型主要是将redis命令以及服务端返回的数据以array成员的形式进行传输,引导字符为 “*”,命令格式为
*<number of elements>\r\n<element1>\r\n<element2>\r\n……<element number>\r\n
样例:
*0\r\n
*2\r\n$5\r\nhello\r\n$5\r\nworld\r\n
- NULL
空数据 不存在任何数值
首字节用 “_” 引导
resp2中有 空bulk string, 还有空 array,因为是多元参数,导致在实现上有所冗余,在resp3中引入了这个NULL类型,用来解决这个问题
_\r\n
- Booleans, Doubles, Big Numbers
这三种类型同样是resp3引入的新类型,用于补充resp2中缺少的数据类型,用法类似于Intergers
// Booleans "#"引导
#<t|f>\r\n
// double ","引导
,[<+|->]<integral>[.<fractional>][<E|e>[sign]<exponent>]\r\n #E 科学计数法
// 正负无穷大,无数据
,inf\r\n
,-inf\r\n
,nan\r\n
// 超大整型
([+|-]<number>\r\n
- Bulk errors
这个字段也是对resp2的补充, 前导字符是 “!”, 消息格式
!<length>\r\n<error>\r\n
样例:
!21\r\nSYNTAX invalid syntax\r\n
- MAPS
RESP3引入的数据类型,跟array类似,使用"%" 引导,"%"字符后跟着一个数字,用以标识该maps中有多少个键值对,比如有一个键值对,则%后数字则为1,基本数据格式为:
%<number-of-entries>\r\n<key-1><value-1>...<key-n><value-n>
样例
%2\r\n
+first\r\n
:1\r\n
+second\r\n
:2\r\n
报文样例:
- Sets
这个主要是由服务端返回给客户端的,由 ‘~’ 引导,首先是返回的数值数量,后面跟着的就是内容
报文结构
~<number-of-elements>\r\n<element-1>...<element-n>
样例
- Pushes
RESP的推送包含带外数据。它们是协议请求-响应模型的例外,并为连接提供通用的推送模式。
报文结构与 sets 相同, 由 '>'引导
><number-of-elements>\r\n<element-1>...<element-n>
其他说明
- RESP协议中simple类型与aggregate类型,应该怎么理解
在Redis Serialization Protocol (RESP) 中,数据类型主要分为两类:简单数据类型(simple data types)和聚合数据类型(aggregate data types)。
- 简单数据类型(Simple Data Types)
- 聚合数据类型(Aggregate Data Types)
简单数据类型通常用于传输单个值,而聚合数据类型则用于传输包含多个值的复杂结构,例如列表、集合等。RESP协议通过这些数据类型的设计允许Redis传递丰富的数据结构,同时保持了协议的简洁性和效率。
- 对于超长的redis数据,resp是如何处理的
resp协议是基于文本的协议,当一个报文无法承载完整的数据时,剩下的报文就会直接放到下一个报文中,而下一条报文不会重新设置报文数据头,也不会有报文截断标识,每一条数据结束的标识只有\r\n。举例说明:
- redis 管道命令是如何解决的
每一条resp报文,可以有多条array,对于多条命令管道执行,第一个array为multi 最后一个array则为 exec,中间的array则为需要执行的各种命令,报文示例如下
、
- redis 对resp协议的支持
- redis数据库由1.2版本引入resp协议,2.0版本引入了resp2,5.0以上的版本则支持了resp3协议
- resp3 协议使用客户端需要先向服务端发送hello 3 消息,才能使用resp3协议