[Erlang 0019]Redis协议解读与实现(.Net & Erlang)

本文涉及的产品
云数据库 Redis 版,标准版 2GB
推荐场景:
搭建游戏排行榜
云原生内存数据库 Tair,内存型 2GB
简介:

 Redis从1.2版本开始采用新的统一协议,从2.0版本开始成为与Redis Server交互的标准方式.Redis协议是一个折中方案,它平衡了下面的需求:

  • 简单实现
  • 计算机快速解析
  • 足够简单人工能够正常解读

 概览

客户端通过TCP6379端口连接Redis服务器.客户端服务器端之间传送的每一个Redis命令或者数据都是\r\n(CRLF)结束.Redis接受命令和参数,服务器接受命令之后处理后发回客户端.

协议的完整内容请查看:http://redis.io/topics/protocol 下面是协议的概览图:

 

请求Requests

新统一请求协议所有发送到Redis服务器的数据都是二进制安全的(binary safe).什么是二进制安全?参见维基百科 http://en.wikipedia.org/wiki/Binary-safe 简单讲二进制安全的函数把所有的输入当成原始的数据流没有特定格式,换句话说不会按照特定格式去解析数据,一个字节(8位)数据所有可能表达的256种取值都能够正常解读.

下面是Redis Request的格式说明:

*<number of arguments> CR LF            %参数个数
$<number of bytes of argument 1> CR LF  %参数1的字节数
<argument data> CR LF                   %参数1的数据
...
$<number of bytes of argument N> CR LF  %参数N的字节数
<argument data> CR LF                   %参数N的数据

下面是符合上述规范的一个例子:

"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n"

Redis的应答使用同样的结构. 像$6\r\nmydata\r\n这样一条应答被称为Bulk Reply.如果Redis返回是数据项列表,被称为Multi-bulk reply.这种情况下就会在一组Bulk Reply之前添加一个*<arg_count>\r\n数据头.

响应 Replies

响应消息的第一个字节表示了消息的类型:

  • 单行消息 "+"
  • 错误消息 "-"
  • 返回一个整型值 ":"
  • 返回bulk reply "$"
  • 返回 multi-bulk reply "*"

状态响应

状态响应(单行响应)是一个单行字符串以+开始\r\n结束,比如 +OK

客户端类库应该返回+字符后面的所有内容,上面例子中就是OK

The client library should return everything after the "+", that is, the string "OK" in this example.

错误响应

错误响应和状态响应类似,唯一的区别就是第一个字符是"-";只有异常出现的时候才会发送错误响应,比如你在错误的数据类型上进行一个操作,命令不存在等等.当接收到错误响应的时候客户端类库应该抛出异常.

整形响应

这种类型的响应的返回就是":"开头,数据体是一个整形值的字符串并以CRLF结尾,样例: ":0\r\n" ":1000\r\n"

像INCR,LASTSAVE这样的命令使用整型值响应,这种数值并没有特殊的含义,INCR仅仅是自增数值,LASTSAVE是UNIX时间.EXISTS命令返回值就是特殊含义的1代表true 0代表false.像SADD,SREM SETNX返回1代表操作成功返回,0代表其它情况.下面的命令会返回整形响应: SETNX, DEL, EXISTS, INCR, INCRBY, DECR, DECRBY, DBSIZE, LASTSAVE, RENAMENX, MOVE, LLEN, SADD, SREM, SISMEMBER, SCARD

块响应Bulk replies

Bulk replies 用来返回单条二进制安全的字符串,比如:

GET mykey %客户端请求

$6\r\nfoobar\r\n %服务器端响应

服务器的响应以$开头后面跟一个数字代表响应的字节数然后是CRLF,后面紧跟实际的数据,再往后就是CRLF两个字节表示结束.

如果没有请求的值并不存在就会bulk reply就会使用特殊值-1来表示数据长度,例如:

GET nonexistingkey %客户端请求一个不存在的key

$-1 %服务器返回一个数据长度为-1的结果

客户端类库在遇到值不存在的情况时不要返回空字符串应该返回空对象(Nil object).例如Ruby类库返回nil,C类库返回NULL,等等

 

多块响应Multi-bulk replies

像LRANGE这样的命令会返回多个值(列表的每一个元素都一个值,LRANGE需要返回不止一个元素).返回值结构以*开头,然后是块数据的数量.

如果给定的key不存在就认为这个key对应一个空列表,块数据的数量值为0.例如:

LRANGE nokey 0 1 % 客户端请求

*0 %服务器端响应

BLPOP命令超时,就会返回一个空多块响应(nil multi bulk reply).这时使用的数量值是-1应该解析成空对象,例如:

BLPOP key 1

*-1

这种情况下客户端API应该返回一个空对象而不是空列表.这样就可以区分空列表和发生错误的状况.

多块响应的Nil elements in Multi-Bulk replies

多块响应中的元素可能会返回长度为-1的情况,这表示该元素没有找到以区别于空字符串.在使用GET配合SORT命令时会出现这种指定key值找不到的情况.例如:"*3\r\n$3\r\nfoo\r\n$-1\r\n$3\r\nbar\r\n"这里第二个元素就是空值,客户端应该返回类似这样的值:["foo",nil,"bar"]

多条命令和管道

一个客户端可以使用同一个连接发送多条命令.管道支持可以让客户端一次写操作就可以发送多条命令.没有必要等待服务响应之后再发送下一条命令.可以最后读取所有的响应结果.通常Redis服务器和客户端一个快速的链接,客户端是否实现这一特性并不太重要,如果一个应用程序短时间内需要发送大量的的命令使用管道要快得多.

 

 理论与实践的分隔线


 

.Net Client中的协议实现

Redis .net版本的开源客户端有很多,这里我们选取的项目是booksleeve,项目地址: http://code.google.com/p/booksleeve/ 
著名的技术类问答站Stack Exchange就是使用了这个项目,具体可以参考这里: http://www.biaodianfu.com/stack-exchanges-architecture.html,我们选取其中一段Redis协议的实现代码:

复制代码
//source: https://github.com/migueldeicaza/redis-sharp/blob/master/redis-sharp.cs

public void Set(IDictionary<string, byte[]> dict)
{
if (dict == null)
throw new ArgumentNullException("dict");

var nl = Encoding.UTF8.GetBytes("\r\n");

var ms = new MemoryStream();
foreach (var key in dict.Keys)
{
var val = dict[key];

var kLength = Encoding.UTF8.GetBytes("$" + key.Length + "\r\n");
var k = Encoding.UTF8.GetBytes(key + "\r\n");
var vLength = Encoding.UTF8.GetBytes("$" + val.Length + "\r\n");
ms.Write(kLength, 0, kLength.Length);
ms.Write(k, 0, k.Length);
ms.Write(vLength, 0, vLength.Length);
ms.Write(val, 0, val.Length);
ms.Write(nl, 0, nl.Length);
}

SendDataCommand(ms.ToArray(), "*" + (dict.Count * 2 + 1) + "\r\n$4\r\nMSET\r\n");
ExpectSuccess();
}

复制代码



Erlang Client中的协议实现

Redis Erlang版本的客户端我们可以看一下立涛写的erl-redis,项目地址:https://github.com/litaocheng/erl-redis

下面是一段代码是redis_proto.erl的摘取的片段,可以明显感受到Erlang对于二进制数据的表达能力更强一些,与上面.net的版本相比语言的语法噪音少了很多,Redis相关协议的实现更直观一些;

复制代码
%% @doc generate the mbulk command
mbulk(Type) ->
[<<"*1">>, ?CRLF, mbulk0(Type)].

mbulk(Type, Arg) ->
[<<"*2">>, ?CRLF, mbulk0(Type), mbulk0(Arg)].

mbulk(Type, Arg1, Arg2) ->
[<<"*3">>, ?CRLF, mbulk0(Type), mbulk0(Arg1), mbulk0(Arg2)].

mbulk(Type, Arg1, Arg2, Arg3) ->
[<<"*4">>, ?CRLF, mbulk0(Type), mbulk0(Arg1), mbulk0(Arg2), mbulk0(Arg3)].

mbulk(Type, Arg1, Arg2, Arg3, Arg4) ->
[<<"*5">>, ?CRLF, mbulk0(Type), mbulk0(Arg1), mbulk0(Arg2), mbulk0(Arg3), mbulk0(Arg4)].

mbulk_list(L) ->
N = length(L),
Lines = [mbulk0(E) || E <- L],
[<<"*">>, ?N2S(N), ?CRLF, Lines].

%% @doc parse the reply
parse_reply(<<"+", Rest/binary>>) ->
parse_status_reply(Rest);
parse_reply(<<"-", Rest/binary>>) ->
parse_error_reply(Rest);
parse_reply(<<":", Rest/binary>>) ->
b2n(Rest);

parse_reply(<<"$-1\r\n">>) ->
null;
parse_reply(<<"$0\r\n">>) ->
{bulk_more, 0};
parse_reply(<<"$", Rest/binary>>) ->
N = b2n(Rest),
{bulk_more, N};
parse_reply(<<"*-1\r\n">>) ->
null;
parse_reply(<<"*0\r\n">>) ->
null;
parse_reply(<<"*", Rest/binary>>) ->
N = b2n(Rest),
{mbulk_more, N}.
复制代码


OK,今天就到这里,上面两个版本的实现代码都非常棒,大家可以下载看一下.更多Redis客户端的选择请参考这里:http://redis.io/clients

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
21天前
|
缓存 NoSQL Java
【Azure Redis 缓存 Azure Cache For Redis】Redis出现 java.net.SocketTimeoutException: Read timed out 异常
【Azure Redis 缓存 Azure Cache For Redis】Redis出现 java.net.SocketTimeoutException: Read timed out 异常
|
18天前
|
缓存 NoSQL 网络协议
【Azure Redis 缓存】Redisson 连接 Azure Redis出现间歇性 java.net.UnknownHostException 异常
【Azure Redis 缓存】Redisson 连接 Azure Redis出现间歇性 java.net.UnknownHostException 异常
|
18天前
|
存储 NoSQL Redis
【Azure Developer】一个复制Redis Key到另一个Redis服务的工具(redis_copy_net8)
【Azure Developer】一个复制Redis Key到另一个Redis服务的工具(redis_copy_net8)
【Azure Developer】一个复制Redis Key到另一个Redis服务的工具(redis_copy_net8)
|
2月前
|
存储 开发框架 前端开发
基于Lumisoft.NET组件,使用IMAP协议收取邮件
基于Lumisoft.NET组件,使用IMAP协议收取邮件
|
2月前
|
存储 NoSQL Redis
【Azure Developer】一个复制Redis Key到另一个Redis服务的工具(redis_copy_net8)
介绍一个简单的工具,用于将Redis数据从一个redis端点复制到另一个redis端点,基于原始存储库转换为.NET 8:https://github.com/LuBu0505/redis-copy-net8
|
3月前
|
NoSQL 大数据 Redis
分享5款.NET开源免费的Redis客户端组件库
分享5款.NET开源免费的Redis客户端组件库
|
4月前
|
开发框架 前端开发 JavaScript
JavaScript云LIS系统源码ASP.NET CORE 3.1 MVC + SQLserver + Redis医院实验室信息系统源码 医院云LIS系统源码
实验室信息系统(Laboratory Information System,缩写LIS)是一类用来处理实验室过程信息的软件,云LIS系统围绕临床,云LIS系统将与云HIS系统建立起高度的业务整合,以体现“以病人为中心”的设计理念,优化就诊流程,方便患者就医。
63 0
|
5天前
|
开发框架 前端开发 JavaScript
ASP.NET MVC 教程
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
16 7
|
3天前
|
存储 开发框架 前端开发
ASP.NET MVC 迅速集成 SignalR
ASP.NET MVC 迅速集成 SignalR
13 0
|
27天前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
29 0