TOP100 案例标题 徐华海 架构师 当当
摘要 1.案例目标: 对无线部门原来的遗留PHP接口进行SOA化,允许使用其他语言来实现新的服务。 2.成功(或教训)要点: 成功之处: 2) 服务自动注册和发现,提供热部署 3) 没有设计单点,避免了单点故障 4) 协议简单,方便多语言扩展 不足之处: 1) 部署稍显复杂,需要简化 2) 缺少一套类似Thrift的自动代码生成工具 3.案例ROI分析: 有效整合原来的PHP服务,引入Java等其他语言来实现服务,服务化的梳理有效提高了复用度,降低了开发成本。 4.案例启示: 1) 企业的服务化改造是一个长期的工作,需要严格遵守原则 2) 服务化需要尽量考虑继承原来代码的问题 3) 服务化改造成功的企业才能快速响应需求的变更
案例标题 a)整合原来遗留的PHP接口,进行服务化改造 b)启动这个改造的动机很简单,就是为了整合旧接口
谁,哪来的 a)案例背景 公司原来有大量的PHP业务接口,“坨”在一起,接口的不同版本越来越多,接口的不同版本的实现可能都会互相影响,研发在开发的时候难免会在编写新接口的时候影响了老接口,这也给测试人员的接口测试带来了挑战,最直接的影响就是测试周期变长,而且也无法完全确保测试的覆盖率。 在这种背景下,启动了改造计划,启动前确定几个基本原则: 1,降低接口耦合度,进行服务化改造 2,接口支持多版本 3,服务支持自动注册和发现 4,服务的实现支持多种技术语言
怎么做到的 1,确定服务接口的报文协议和调用堆栈 2,确定服务的自动注册与自动发现的方案 3,确定依赖的技术选型
实践 1.1 - 报文协议 * datagram packet protocol: * crc : 32bit crc32 value of all fields excluding itself, * including 'identifier', 'length', 'type', 'reversed', 'body' * identifier : 32bit unsigned int, * the request is same as the response * length : 32bit unsigned int, * just means the size of 'body' field * type : 8bit char, * request type is [0,128], [0,0x0F] is for system need, * response type is (128,256], * [response type] = [response type] | (0x01 << 7) * defined types as following: * 0x01 - heartbeat request * 0x10 - business request * reversed : 24bit char[3], padding with '\0' * body : bytes' count indicated by the 'length' field, * for the better performance, * if this is request, max value is 8192 (8k), * if this is response, max value is 81920 (80k)
实践 1.2 - 调用堆栈
实践 1.3 - PHP服务节点 1.运行框架使用Swoole作为服务的运行时容器(网络IO底层是libevent) 2.按照技术堆栈实现协议的编解码、crc校验、服务的路由 3.编写PHP扩展用于实现与Zookeeper的交互,主要是注册服务的逻辑 4.由于Swoole反调函数本身的限制,编写了一个专门用于摘除服务节点的命令行工具 5.通过使用composer解决类加载的问题 6.模块单独编写打包,部署到运行框架中,自动加载和注册模块中的服务 7.编写了启动和停止的shell脚本
实践 1.4 - Java服务节点 1.运行框架使用MINA作为服务的网络IO底层 2.按照技术堆栈实现协议的编解码、crc校验、服务的路由 3.使用curator开源库来实现注册服务到Zookeeper的逻辑 4.模块单独编写打包,部署到运行框架中,自动加载和注册模块中的服务 5.编写了一个简单的任务处理队列,前端灌入解封的消息,后端是一个处理消息的线程池 6.服务调用支持本地调用,也支持远程调用 7.编写了启动和停止的shell脚本
实践 1.5 - 服务注册与发现
实践 1.6 - 注册服务 1.使用Zookeeper作为服务的注册中心 2.服务器端在启动后,将自己所能提供的服务以及版本信息保存到znode节点上 3.一个服务器端对应一个znode节点,节点的内容信息就是其可以提供的服务的列表 4.确保如果服务器不可用的时候,znode节点也要被删除 5.确保如果服务器恢复的时候,znode节点也要被恢复 6.一个服务端可以注册一种服务的多个版本
实践 1.7 - 发现并调用服务 1.消费端在初始化的时候,会根据其声明的服务及版本,到注册中心查询可以提供这些服务的服务端列表,同时初始化到服务器端的连接池 2.实时观察服务端节点的状态变化,并调整连接池资源 3.根据服务名找到匹配的连接池,从池中取出一个可用的连接,使用该连接发送请求并接收应答,使用完毕,将连接释放回连接池 4.如果没有找到匹配的连接池或者连接池没有空闲连接,则报调用错误 5.连接池负责管理长连接
实践 1.8 - PHP客户端调用stub 1.PHP调用端一般是CGI环境,属于脚本解释执行,执行完所有的上下文即可销毁,这与我们需要的长连接管理要求相悖,为了解决这个问题,就在客户端机器上部署一个轻量级的stub程序,通过unix域套接字通讯,该stub就完成服务的代理调用的功能 2.stub使用原生zookeeper C库来构建,监控服务节点的可用性,内部动态管理连接池资源 3.该程序本身使用一个监控脚本来保持永远可用
And 1.曾经采用MsgPack作为协议报文的body部分的编码方式,但是后来发现MsgPack的Java版实现与PHP版实现并不兼容,只好作罢,还是暂且采用标准Json作为编码方式 2.不采用HTTP协议而是采用自定义的私有协议,主要是为了提高运行效率 3.假设Zookeeper节点数为n,则不能允许超过n/2 – 1 节点数不可用,否则会导致整个系统不可用(包括客户端的session漂移不可用,客户端连接也会失败,……),这是我在实际测试中验证的 4.部署过于麻烦,未来计划推动自动化部署,简化部署工作 5.无论是PHP还是Java服务模块,未来计划实现类似Thrift的代码生成工具,只需要选择语言,就可以生成服务模块的代码框架 6.在服务节点中都提供了调用日志关联的记录,未来计划增加服务跟踪运维管理的监控端
案例ROI分析 我并没有专门进行投入产出的分析,只从以下几点分析以下带来的改变: 1.已经上线的服务接口一般并不需要变,开发和测试都可以只关注新版本的服务,减少了回归测试的工作 2.可以实现灰度发布了,在线上可以同时存在不同版本的服务接口,便于比较不同版本的优劣 3.服务实现都包含在服务模块中,代码文件的管理更加清晰顺畅了
案例启示 1.对于遗留的旧系统的改造,尽量避免大刀阔斧的改变,这样才能把对当前系统的冲击降到最低 2.设计这个系统并不是创新,只是参考了Dubbo的实现原理,进行了在其他技术环境下的“画瓢”实现,实际上来看也是可行的,还是那句话,借鉴这种SOA设计思路很重要 3.SOA框架本身并不能解决所有的问题,最重要的还是让产品和开发都明白面向服务化的意义和重要性,才能更好开展工作