使用Redis协议构建网络服务

来源:互联网 发布:迪优美特网络机顶盒子 编辑:程序博客网 时间:2024/06/07 23:16

引言

关于服务,这是一个比较抽象的概念,意在为使用者做事,达到使用者的某些需求。当然我们在此讨论的是网络服务,通常我们可以将其定义为一个运行在操作系统上的一个程序,使用者通过网络与其进行交互并能得到想要的信息。

协议

在编写网络服务程序中,其中最重要的一个环节是约定好相互通信的内容格式,也就是我们常说的网络通讯协议。协议设计的好坏很大程度上会影响系统的灵活性、可拓展性、维护性等等。关于良好的协议设计,笔者认为应该具备如下几点:
1、易于实现。这里表示容易实现,但这个容易不能按照常规的意义去理解,此处容易笔者定义为简单不马虎而不失优雅。
2、能够被计算机高效的 Parse。这个不用解释。
3、能容易被人类读懂。这个比较关键,易于理解很重要。
4、稳定性。当某些地方需要扩展内容时不应该变动已有的格式,要做到稳定处该稳定,可变处要能适应这种可变性而不影响现有的通讯。

Redis协议

Redis客户端和服务端采用TCP连接来进行数据交互,采用请求-应答的通讯模式。客户端采用发送命令及命令参数的方式,服务端处理后再把结果返回给客户端。这种有点类似我们编程中的函数调用,我们可以把Redis服务端想成一个函数,客户端想成一个调用者,这样就好理解了。例如Redis客户端发起一个GET/SET命令调用:
127.0.0.1:6379> get name"root"
请求包格式:*2\r\n$3\r\nGET\r\n$4\r\nname\r\n
答复包格式:$4\r\nroot\r\n
127.0.0.1:6379> set name AdministratorOK
请求包格式:*3\r\n$3\r\nSET\r\n$4\r\nname\r\n$13\r\nAdministrator\r\n
答复包格式: +OK\r\n
从上可以看出Redis协议从设计上是相当简单的,而且非常易读,对于计算机来说也是很好解析,如果某个命令参数有增加,丝毫不影响整体的格式和解析。如果你对协议有兴趣,可以查找下相关资料进行研究。

编程实现

编写程序解析这种协议时,个人比较提倡的一种做法是解析成一个个Token。Token可以设计成如下结构(采用C/C++方式):
struct Token{    char* str;    int len;};
解析时Token中的指针是不分配内存的,只是指向一个RequestBuffer中的一个位置,len表示长度。
例如对于上面的命令,可以解析成如下数据结构:
get name --> token[0]=get token[1]=name
set name Administrator --> token[0]=set token[1]=name token[2]=Administrator
这样当命令参数有变化时,Token个数会随之变化,但不会引起解析错误。
程序内部应该建立一个命令映射表,这样便于维护和扩展。例如对于GET/SET命令,对应处理代码如下:
void onGetCommandHandler(...){    //keyname = token[1];    //dosomething}void onSetCommandHandler(...){    //....    //keyname = token[1]; keyvalue=token[2];    //dosomething}

应用场景

目前笔者在开发Oracle中间件,软件是一个代理程序。功能是Oracle客户端通过连接中间件来访问数据库,这对应用是完全透明的。中间件在此会进行协议解析和转发工作并完成一些功能,例如审计和拦截。由此可以看出软件的一些特点:
1、中间件需要占用一个端口接受客户端的连接请求,并完成相关协议解析和收发工作。
2、中间件和客户端通信使用的是Oracle 网络通讯协议(TNS),中间件需要按照约定格式解析内容。不符合协议格式的需要关闭连接。
3、中间件占用的端口除了处理Oracle客户端请求外,不能接收其他客户端的请求,也就是说这个端口只能做这件事情。

做到后面会发现存在如下需求:
1、需要外部去查询程序的状态。例如我要查询当前有多少客户端连入、执行了哪些SQL等
2、程序需要动态改变一些配置项。例如程序内部的一些参数,开关量
目前看来,这两点是没法做到的。只有程序内部知道自己的状态,外部是没法感知和操控的

思考方案1:用Oracle客户端发送特定SQL来操作程序。但存在需要解析SQL,区分SQL的过程,而且需要返回结果时,需要自己构造答复包,这个具备很大的复杂性。放弃。

思考方案2:用其他客户端协议完成,只要程序再开个端口并解析协议即可。经过思考,采用Redis协议是比较合理的解决方案。支持Redis协议的客户端的实现有很多,例如:C、C++、C#、php、java、python、go等语言。这样外部只需要用这些接口去访问程序就可以了。包括在WEB上展示一些信息也是相当的容易。例如可以解决如下问题:

1、WEB上要获取程序有哪些客户端接入方便展示
方案:实现一个clientstats命令,客户端调用即可
2、WEB上需要获取某个IP执行了哪些SQL方便查询
方案:实现一个sqlstats [IP] 命令,客户端调用传入IP即可
3、改变程序的行为,例如改变日志输出级别
方案:实现一个setloglvl [level] 命令,外部调用即可
4、关闭/重启程序
方案:实现shutdown/restart 命令即可
.....
.....
可以做的事情太多了


可以看出在此场景下,工作端口和Redis端口完全解耦,增加了Redis协议的支持将会给系统带来很大的灵活性和扩展性。总之一句话:Redis协议既简单又强大,只要合理运用可以做很多事情。

原创粉丝点击