UDN-企业互联网技术人气社区

板块导航

浏览  : 1298
回复  : 0

[资源分享] SSH框架微服务改进实战

[复制链接]
呵呵燕的头像 楼主
发表于 2016-9-20 12:39:57 | 显示全部楼层 |阅读模式
  这篇文章从实操层面详细介绍如何对SSH框架的支付系统实施具体的技改。这里不涉及具体代码写法,重点在于说明方法论。虽然以SSH(Apache Struts + Springframework + Hibernate) 框架为例,也适合各种常用的web架构 (Apache Struts/Spring MVC / Apache Velocity + Springframework + Mybatis/Hibernate) 。

  选取入手模块

  如果遗留系统规模庞大,那应该如何挑选入手点?以支付系统为例,如前文所述,支付系统一般包括账户,交易,订单,优惠券,钱包,支付渠道,清结算,支付网关,运营系统等模块。模块很多,如何选择突破点? 我们采取的方法是寻找对外依赖最小的模块,由此开始调整。 首先模块依赖关系整理出来。

c.png


  从上图可以看出来,账户系统在依赖树中是处于树根的位置,对它的调整相对容易。只要保持对外接口不变即可。

  重构目标

  其次是确定本模块重构的目标,也就是通过重构,将达到什么样的结果。目标的制定可以从如下角度考虑:

  功能泛化:即让相同的接口支持更加通用的功能,比如将代扣和充值功能完善并重构为支付产品商务系统,建立支付商品,出入库,订单等子系统。

  功能完善:对现有功能进行完善, 将原来未实现的功能补充完成。 比如建立和完善账户系统,为清结算工作提供支持。

  性能提升:这是最容易说服人的一个理由了。 比如:在存储上,调整原依赖单一依赖mysql数据库的问题,接口可以根据需求选择合适的数据库,实现读写分离,比如对高性能需求的接口采用内存数据库。

  重构策略

  重构如同飞行中更换引擎,必须非常小心,我们采取的策略是:小步快跑,积小胜为大胜。

  每一个改进点,需在1~3天内完成,不能超过一个周。

  每次改进,均可直接上线运行,不需要长时间的AB测试。

  在功能也就是对外接口不变的前提下,开始进行拆分工作。 在结构上,原SSH系统是一个大项目,所有代码分层分模块堆在一起。微服务系统需要将它们拆分。直观的拆分方法是按层,按模块同构的拆分成各个独立运行和维护的系统。由此带来了一系列的调整。

  基于层的全面重构

  如果遗留接口不多,人力足够,老板又认可,那当然需要执行全面的系统重构了。 在SSH架构下,重构可以采用自上而下的方法进行,这样可以确保每一层的重构都有明确的输入输出,并且是可测试的。

  API网关

  在SSH架构下,对API网关需求不大,一般都不需要这个网关。如果有的话,也是通过NGINX的rewtite模块来实现,逻辑简单,人工维护即可。而一旦接口层按照业务来拆分后,网关路由逻辑复杂多了,通过人工维护配置文件难度激增,必须调整成自动注册更新路由的方式。也就是每个服务需要将自己提供的服务和API注册到API网关上, API网关需要自动识别并加载新的路由。

  针对这个需求,我们开发了一个connector, 其工作原理如下:

b.png


  服务在启动完成后,注册到zookeeper上。

  connector监听 zookeeper,一旦有变更,则获取服务列表,更新nginx.conf文件

  connector在更新完成nginx.conf文件后,执行nginx的reload命令,让配置生效。

  load balancer 将服务打到nginx上,nginx可以按照新配置来执行服务路由。

  在服务关闭时,执行类似的操作。第一步是在服务关闭前,将服务从zookeeper上删除。注意必须在服务关闭前删除,否则会发生服务不可用的错误。 服务注册项从zookeeper上删除,并且本地服务没有流量后,才能关闭服务。 connector可以和nginx部署在同一台机器上。

  服务接口调整

  使用spring MVC实现的Controller或者使用Apache Struts实现的Action,需要按照业务进行组合,拆分到具体项目中,原则上,一个项目不应该有超过5个接口,避免接口过于复杂。本次调整需要做的工作包括:

  增加服务注册机制,在服务启动时将服务注册到zookeeper上;

  增加服务退出机制,在服务关闭时将服务解除注册;

  将服务按照业务组合,建立对应的项目;

  将服务依赖的业务逻辑层打包到同一个jar中,作为后续改进的基础。

  服务上线,替换掉现有的服务。

  业务逻辑层调整

  在springframework框架下,业务逻辑层被实现为服务层和DAO层之间的桥梁。 对于绝大多数应用来说,业务逻辑层都是非常薄的一层封装,调整为微服务架构下,业务逻辑层有三种处理方式:

  抽象为独立的RPC服务,对于功能比较复杂业务逻辑,可以使用这种方式。

  下沉到DAO层,如果逻辑上涉及数据访问操作多,或者需要事务处理的,可以合并到DAO中,一同实现为RPC。

  上浮到接口层。如果业务逻辑比较简单,也可以上浮。

  DAO层的重构

  DAO层的重构的工作量比较大。 需要将原来访问数据库的逻辑,调整为远程RPC调用:

  针对DAO的接口,开发RPC服务,将数据访问逻辑通过RPC来隔离;

  提供DAO的RPC接口客户端,替换原DAO服务接口的实现。

  这样在业务逻辑层和服务层中调用的DAO,调整为RPC调用,将数据访问逻辑和业务逻辑分离。这样在DAO层就可以根据业务需要选用合适的存储数据库。

  性能优化

  完成上述调整,这才是万里长征走完的第一步。接下来就是码农们最心爱的性能优化了。 对于大部分线上应用来说,性能优化主要的工作是选择合适的存储介质来满足性能的需求。

  数据可以直接写入MySQL或者其他的持久化存储。这个库也会被称之为主库。但是如果写入性能要求高,可以调整为先写入内存数据库,再同步到持久化存储中。

  线上数据访问,指根据ID或者其他的某个属性值来读取1-2条数据。一般不要直接从MySQL等持久化存储中出,需要采用couchbase,redis等内存数据库。

  线上数据检索,检索和访问需要分开。按照关键字、时间等条件的检索,一般用Elastic来满足。

  线上数据列表,如最新、最热、推荐等,都需要将数据预先计算好,放在couchbase或者redis等内存数据库中,读取的时候直接从库中出数据,不能执行实时计算。

  通过这几个步骤,就可以优化线上服务的性能,最终呢,瓶颈应该不是在数据库或者CPU上,而是在带宽上。 那这就是砸钱的商务行为了。 这样处理,带来的最大的问题是空间浪费,是典型的以空间换时间的做法。 技术上的挑战,那就是数据一致性问题。 对于一致性要求不强的需求,这个做法是没问题的。 那如何在不同存储之间同步数据呢?主要的做法有:

  使用数据库本身自带的同步机制。好处是一般不需要开发,问题是数据库的同步机制,如MysQL、HBase的replication机制,仅支持少数类型的备库,对数据库本身也有压力。

  使用公共同步工具,如阿里的canal。

  使用消息中间件来实现数据同步。

  采用消息中间件目前主流的做法,适合于对数据实时性要求不高的场景。 如下图所示,在数据写入的服务中,完成写入后,抛出消息。其他数据库通过接受消息来更新数据。 优点是系统灵活,无论是同DC还是跨DC的情况都可以正常工作。 对数据同步的情况,可以通过MQ提供的监控系统也能够了解。 缺点是开发工作量大,数据同步实时性不高。

a.png


  如果时间不够

  上述重构是非常理想的场景了,那如果时间不足,只能部分重构,应该如何处理?一种方式是针对DAO层来改进。 一般来说,重构往往意味着数据结构的变更。另一方面,由于数据写入的服务相对数据读取服务要少得多,所以可以采用的策略是:

  调整DAO服务中数据写入操作,将数据写入到新的库表中;

  采用MQ来实现新库表和老库表的数据同步。 参见上图。

  老的DAO服务中数据读取的操作保持不变。

  毕竟大部分的服务并不需要太高的性能需求。 只需要将性能要求高的服务进行重构,实现读写分离即可。重构方式如上描述。

  总之,技改的难度在于如何梳理出头绪来。

原文作者:佚名  来源:开发者头条
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部