设为首页 收藏本站
查看: 876|回复: 0

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

[复制链接]

尚未签到

发表于 2015-7-20 09:50:53 | 显示全部楼层 |阅读模式
  Redis从1.2版本开始采用新的统一协议,从2.0版本开始成为与Redis Server交互的标准方式.Redis协议是一个折中方案,它平衡了下面的需求:


  • 简单实现
  • 计算机快速解析
  • 足够简单人工能够正常解读
  概览
  客户端通过TCP6379端口连接Redis服务器.客户端服务器端之间传送的每一个Redis命令或者数据都是\r\n(CRLF)结束.Redis接受命令和参数,服务器接受命令之后处理后发回客户端.
  协议的完整内容请查看:http://redis.io/topics/protocol 下面是协议的概览图:


DSC0000.png

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

* CR LF            %参数个数
$ CR LF  %参数1的字节数
CR LF                   %参数1的数据
...
$ CR LF  %参数N的字节数
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之前添加一个*\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服务器和客户端一个快速的链接,客户端是否实现这一特性并不太重要,如果一个应用程序短时间内需要发送大量的的命令使用管道要快得多.
  
DSC0001.png
  理论与实践的分隔线

  

.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 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) ->
[, ?CRLF, mbulk0(Type)].
mbulk(Type, Arg) ->
[, ?CRLF, mbulk0(Type), mbulk0(Arg)].
mbulk(Type, Arg1, Arg2) ->
[, ?CRLF, mbulk0(Type), mbulk0(Arg1), mbulk0(Arg2)].
mbulk(Type, Arg1, Arg2, Arg3) ->
[, ?CRLF, mbulk0(Type), mbulk0(Arg1), mbulk0(Arg2), mbulk0(Arg3)].
mbulk(Type, Arg1, Arg2, Arg3, Arg4) ->
[, ?CRLF, mbulk0(Type), mbulk0(Arg1), mbulk0(Arg2), mbulk0(Arg3), mbulk0(Arg4)].
mbulk_list(L) ->
N = length(L),
Lines = [mbulk0(E) || E
parse_status_reply(Rest);
parse_reply() ->
parse_error_reply(Rest);
parse_reply() ->
b2n(Rest);
parse_reply() ->
null;
parse_reply() ->
{bulk_more, 0};
parse_reply() ->
N = b2n(Rest),
{bulk_more, N};
parse_reply() ->
null;
parse_reply() ->
null;
parse_reply() ->
N = b2n(Rest),
{mbulk_more, N}.
  
OK,今天就到这里,上面两个版本的实现代码都非常棒,大家可以下载看一下.更多Redis客户端的选择请参考这里:http://redis.io/clients
  

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-88557-1-1.html 上篇帖子: Redis安装及主从配置 下篇帖子: Redis学习笔记(3) List类型值存取
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表