系统重构的事儿
2015 年 12 月 14 日
architecture

    相信每个人都做过系统重构相关的事儿,简单说就是对系统不合理的地方进行修复, 比如系统架构,数据库设计,代码规范,或某一行代码等,重构需要更多的思考, 而不是一畏地写代码,但又不能考虑太多,做到有的放矢。最近对老系统进行重构, 与其说重构,不如说重写,因为涉及到的有 系统架构数据库设计代码规范等, 由于旧代码已经烂不忍睹,因此有了这么一次系统地重构, 正常的重构应该是每天的事儿,而不是某天的事儿。 本文将尽量详细讲述重构中自己主要涉及的部分,但愿对你有所帮助。

  • 旧系统

  • 系统基本架构

  • SLB

  • 最外层使用的是阿里云提供的SLB服务,主要对多台代理云服务器进行 流量分发(负载均衡),并保障系统的高可用性

  • Web Proxy(Nginx)

  • WEB代理服务器使用的是nginx,将请求分发到后端 WEB服务器,对Web服务进行分流,同时保障服务的高可用性。

  • Web App(Web服务)

  • 系统中的 WEBADMINWAPAPP 等均为Java为主的WEB服务,主要就是业务处理。

  • Pay

  • Pay作为整个系统的支付网关,提供Http服务, 其中主要就是用户 支付充值账户等相关业务,基本设计大概如图所示(以支付宝支付为例):

  • Push

  • Push负责消息推送, 即将一些业务数据或消息推送到客户端APP,如今,已经有很多第三方推送系统可供使用, 如腾讯信鸽百度云推送,这里也是自有的一个不够完善的推送系统,基本设计大概如图所示:

  • 建立连接的基本步骤

  • 1. ClientBroker进行注册。
    2. Broker返回一个 clientId和其中一个 Server的TCP连接地址
    3. Client向Server发起连接请求。
    4. Server响应Client,建立连接。

  • 消息推送的基本步骤

  • 1. 业务系统需要记录Client的clientId
    2. 业务系统向某clientId推送消息至Broker
    3. Broker将消息转发至对应Server
    4. Server将详细推送至Client。

  • 消息安全

  • Push系统中使用 非对称加密对称加密来保障消息的安全性。 非对称加密用于传输对称加密时使用的密钥, 对称加密则用于对业务数据进行加解密。

  • 消息存储

  • 为了尽可能保证消息可靠,可以通过MQ来对消息进行生产消费。

  • Server和消息路由

  • Push系统中使用 一致性哈希进行Server和消息路由。

  • Broker高可用性

  • Push系统中Broker的高可用性, 可用通过NginxHA-Proxy来保证。

  • Server集群管理

  • Push系统使用 Zookeeper来对Server进行集群管理

  • Location

  • Location负责用户位置相关的业务,提供Http服务, 需要实时记录和查询用户位置,并提供轨迹,附近等基本计算功能, 内部使用MongoDB作为存储, 如今有很多开源产品也支持位置计算,如 RedisElasticSearch等。

  • Coupon

  • Coupon负责用户优惠券相关的业务,提供Http服务, 主要需要提供 优惠券活动优惠券发放优惠券消费等基础功能。

  • 存储

  • RDBMS

  • 系统主要使用Postgresql, 对就是世界上最先进的开源关系型数据库。然而,系统中出现问题最多就是数据库, 当然根本问题不在数据库,而是使用数据库的人,各种杀手级SQL已经被作为炫耀的噱头,该系统使用 Hibernate作为数据库操作层,然而由于人为因素,对Hibernate没有认真深刻地思考,滥用导致数据库操作及容易出问题。

  • NoSQL

  • 系统中使用到一些常见的NoSQL,如Redis(缓存)MongoDB(位置计算)等。

  • Search

  • 系统中的搜索使用的是ElasticSearch, 其中用于订单查询分析,或者结合 LogstashKibana 进行系统日志监控分析等。

  • MQ

  • 系统中使用消息队列来做一些 数据状态流转业务解耦, 如比较轻量的NSQ

  • Zookeeper

  • 系统中使用Zookeeper做一些 分布式锁,或者集群管理(如Push中的Server)

  • 重构的诱因

  • 按照上面的架构,以当前的业务量是完全没有问题的,那为什么还需要这样系统地重构?
  • 1. 旧系统代码基本 没有规范代码没有质量,以至他人难以维护。
    2. 数据库设计混乱。 对于数据库设计没有一些基本规范,只是想当然得加减字段表,没有一个整体规划。 各种杀手级SQL居然沦为炫耀的噱头(你看,我多么牛逼,能写这么复杂的SQL)。
    3. 各子系统均以web应用独立开来,可能修改某个地方,就需要重新部署多个应用,这对系统无疑是一种隐形伤害, 所以需要将系统进一步拆分,即高类聚,低耦合
    4. 考虑到未来的业务量增加,需要更稳定可靠的服务存储

  • 新系统

  • 新系统的基本架构

  • Wep App(Web应用)

  • Web应用将变得很轻量,就是所说的Controller层, 主要负责参数接收参数校验调用服务接口,完成各业务逻辑的组装。

  • Rpc服务化

  • 系统中的各子模块均以Rpc接口的形式提供服务。 相比之前简单直观的Http服务, Rpc服务从规范和性能方面要更出色。 由于主要以Java作为服务端语言,最终选择Dubbo作为服务化框架, 其他可选方案也有如Thrift等。

  • 服务化规范

  • 1. 服务接口均需返回统一的响应对象(无论失败或成功,切忌抛出异常)。
    2. 必要接口应保证幂等性
    3. 服务接口应该尽量保持单一职责原则
    4. 接口升级应保证向后兼容
    5. ...

  • 基础服务

  • 基础服务作为系统最核心的组件,其并不由业务决定,基本属于各系统通用的,比如用户服务支付服务等,这类服务应该具有最稳定的特性,不需要时常更新扩展。

  • 业务服务

  • 业务服务则依赖基础服务,作为业务发展中不断更新增加的一部分服务,这样也是为了不影响基础服务的单一稳定性,而独立出来的服务。

  • 服务依赖

  • 当系统中的服务越来越多时,服务之间的依赖关系也会变得更复杂,应尽量保证基础服务的依赖关系简单稳定,尽量避免出现相互依赖,必要时可以通过Web层去除服务之间的依赖,也可以通过MQ去除依赖。

  • 服务异步化

  • 系统中除了一些主业务逻辑(必须同步执行完成)外, 有很多操作都是可以异步调用,或者通过MQ消息消费, 还有一些系统调用可能需要调用许多内部服务,也可适当将一些服务通过类似 FutureFork/Join等模式来异步处理。

  • 服务部署

  • 可采用单机单服务的部署方式, 便于运维管理调优监控等, 如果有条件则可以尝试更轻量的docker实例部署。

  • 数据存储

  • 关系型数据库(RDBMS)

  • 对于大多数RDBMS,到后期都会涉及到分库分表, 面对的主要问题就是数据路由数据聚合, 通常的模式就是客户端或者代理(Proxy), 但基本做法会差不多,根据SQL中的路由键, 根据配置的路由规则将数据插入到目标数据库或表中, 在查询时,可以根据路由键到对应数据库或表中查询数据, 若未指定路由键,则需要做数据聚合操作, 比较麻烦得就是针对一些复杂SQL,如Join等的支持, 但对于一个大型互联网应用而言,Join操作是不明智的。 对于客户端代理(Proxy)模式, 客户端更简单方便,并且少了Proxy中间层的性能损耗,但维护要麻烦些,而代理则对客户端变得透明,无须维护客户端,增加了中间层性能损耗,且需要单独维护。

    新系统中采用了Mycat作为DB Proxy。 对一些数据量比较大的进行分库操作, 采用了range-modepartition-pattern分库规则, 而后端DB服务器采用两套独立的Master-Slave部署:

  • NoSQL

  • 想较于RDBMS, 一些NoSQL则天生具有 集群分区高可用等特性, 如Cassandra, 可能在数据查询事务处理等能力不如RDBMS, 通过合理的设计,或加上一些索引工具,是可以满足复杂查询的, 而事务处理, 根据不同NoSQL产品对事务的支持程度, 可能需要做一些额外的程序处理,因此使用NoSQL作为存储,未尝不是一种选择。

  • 运维部署

  • 新系统中使用服务化,新增了很多服务实例, 为了能快速方便进行分发部署,势必需要一些自动化工具, 如FabricSaltStackAnsible等,最终使用更轻量的Ansible, 介绍可见这里

好人,一生平安。