当前位置: 首页 > 科技观察

创业初期的技术问题:如何搭建一个更通用的业务技术架构_0

时间:2023-03-14 19:30:35 科技观察

1.通用架构概述在创业之初,我们往往会选择最简单的技术架构,以便快速迭代产品,比如LAMP架构、SSH三层架构。这些架构能够适应初期业务的快速发展。但是,随着业务越来越复杂,我们会发现这些架构越来越难以支撑业务的发展。看起来一个类写了几千行代码,一个方法到处都是ifelse语句。如果主程序员中途辞职,后面介入的程序员很难看懂这些代码。最终产品越来越难以迭代,只能推倒重来。如果我们在业务一开始就写出适应性更强的架构的代码,那么后面就会少走很多弯路。下面这篇文章是我自己总结的一套架构。经过实践,适应能力还不错。2.通用架构的实现总的来说,我的通用架构是在三层架构的基础上演化而来的。在经典的三层架构中,顶层是controller,中间是service,底层是dao。在我的架构中,顶层是gateway层,controller只是一种gateway,中间是业务层,service只是业务层的入口,底层是baselayer,dao是只是基础层中的数据存储组件。2.1.网关层网关层本质上是处理来自不同网络协议的请求,比如HTTP协议和TCP协议。当然,它也可以处理来自其他协议的请求。详见下图:2.1.1.HTTP请求一般PC和APP的请求都是基于HTTP协议的。业界已经成熟了处理HTTP请求的方案。首先,tomcat容器本身封装了HTTP请求处理的复杂性。其次,springmvc为请求处理提供了RESTful风格的编码方式,大大降低了开发的复杂度。我们要做的就是按照业务领域划分controller,比如按照订单、成员划分大的领域,里面的各种方法都是对这个领域的操作。这里的控制器就是统一网关处理层。对于每个控制器方法,只完成三件事。首先解析请求参数,组装成内部参数。其次,调用底层服务执行业务逻辑。三、组装返回结果,对于异常情况,需要记录异常堆栈日志并转换错误码,堆栈信息不能暴露给调用者。2.1.2.TCP请求业界已经有成熟的处理TCP请求的方案,比如Netty。但是TCP请求毕竟太底层了,我们很多时候都是基于TCP协议来开发自己的协议。另外,很多分布式框架都是基于TCP协议的,比如RPC框架Dubbo、消息框架RocketMQ等。从单机系统到分布式系统,网关层有更多处理TCP请求的逻辑。理论上,底层业务不需要感知是单机环境还是分布式环境。网关层的作用就是屏蔽这种差异。外部呼叫来源的详细信息。在Dubbo服务器中,我们需要实现远程接口,在内部转发远程服务调用。转发逻辑也很简单。首先解析参数,组装内部参数,然后调用业务层的接口执行业务逻辑。***组装返回结果,这里也需要做异常处理,防止异常暴露给外部应用程序。2.1.3小结网关层的本质是对协议进行处理,同时将业务逻辑汇聚到网关层,而不是对外暴露。当内部业务逻辑重构时,外部调用者不需要感知这些变化。当外部调用源增加时,内部业务逻辑不需要感知这种变化,从而实现了外部调用者与内部业务逻辑的解耦。2.2.业务层业务层是一个系统。无论是单机系统还是分布式系统群中的业务系统,业务层都是承载业务流程和规则的地方。业务层由外到内分为三层:第一层是业务服务,第二层是业务流程,第三层是业务组件。具体如下:2.2.1.业务服务业务服务是业务层统一的外在门面,由业务接口、入参、出参三方面组成。a)业务接口业务接口表示域中的业务服务。例如,订单域的业务服务由接口OrderService表示,会员域的业务服务由接口MemberService表示。接口按照执行的性质可以分为读接口和写接口,比如OrderReadService和OrderWriteService。读写分离的好处是可以对集群进行读写分组管理流量。当然,单机系统的读写分离意义不大。字段中的操作在业务接口中以方法的形式体现出来,比如order字段有createOrder下单,cancelOrder取消订单等操作。对于这些操作,尽量设计出具有业务意义的方法,而不是增删改查。当然,对于一些简单的业务,只能增删改查。b)输入参数接下来是输入参数的设计。入口方式对于阅读方式来说比较简单,不做赘述。对于write方法,我们将输入参数设计为分层数据模型。首先需要设计一个公共数据模型,比如订单数据模型、商户数据模型、产品数据模型等,然后将这些数据模型与一些具体业务下的个体数据结合起来,形成一个Request对象,根据不同的业务操作不同而不同,对应的返回结果是response,同样根据不同的业务返回不同的参数。比如拿一个餐饮订单,首先我们要识别这些业务流程中的一些比较基础的数据模型,比如餐饮领域的菜品、餐桌等。之所以称这些款为基础款,是因为,无论下什么餐饮单,盘子和桌子都绝对逃不掉,可以重复使用!因此,我们为这些基本模型设计了相关的DO(DomianObjects):DishDO(菜品),BoardDO(桌子)等。接下来,我们设计一个请求对象DishOrderCreateRequest,用于下餐饮订单。DishOrderCreateRequest里面包含了DishDO和BoardDO,它还会包含一些具体的属性,比如人数,折扣等等,这样可以做到万能灵活。DishOrderCreateRequest代表个性化和灵活的业务入口,而DishDO和BoardDO等代表一个不可更改的基础模型。c)输出参数***是输出参数的设计。对于写法,一般参考比较简单。对于read方法,输出参数往往是一个复合对象,具有复杂的结构和层次。比如查询一个订单,这个订单有订单的基本信息,还有商品信息,收货人地址信息等。在设计参数的时候,应该把结构设计成一个组合对象,但是实际查询的时候,使用查询选择器查询不同的组合对象。例如查询选择器将商品查询设为true,将地址查询设为false,那么本次查询的订单只包含商品,不包含地址。2.2.2.业务流程业务流程其实就是对业务规则的解释,只不过这个解释是用代码来实现的。我们需要做的是准确翻译这些业务规则,维护这些业务规则。业务流程大致可以分为三类动作节点,1.组装参数节点2.规则判断节点3.执行动作节点,其中每个动作节点都是一些业务代码的片段。比如下一个餐饮订单,我们第一步就是把上层传入的参数组装成一个基本的DishOrderDO(组装参数节点),然后根据具体的规则填充这个DishOrderDO(规则判断节点),然后调用DAO创建DishOrderDO(执行动作节点)。业务流程是最容易改变的地方。维护业务流程并不容易。大致思路是将大的业务流程拆分成小的业务流程,提取每个业务流程中的公共代码片段,将它们变成可执行维护的业务组件。2.2.2.业务组件a)基础组件业务组件实际上封装了一些内聚的、可重用的代码片段。对应业务流程中的三个业务节点,业务组件也分为三种类型:组装参数组件、规则判断组件、动作执行业务组件。业务组件的抽象往往是在深入了解业务之后进行的。一味地抽象业务组件,到头来往往是徒劳的。b)Capabilities进一步抽象业务组件,获得能力。业务能力是具有一定复用性的组件的组合,比如发送短信的能力=组装短信参数组件+发送短信。发送文本消息的能力可以被不同的业务流程重用。比如下单和短信发送成功,逻辑类似,只是内容不同。能力是一个粒度比较大的组件。粒度越大,复用性越低。能力的提取也是基于对具体服务的深刻理解,没有一劳永逸的银弹。c)高纬度抽象经过我自己的实践,对于互联网等需求快速变化的场景,高纬度的组件抽象往往是非常划算的,不推荐。2.3、基础层基础层由两部分组成,第一是接口定义,第二是技术组件。2.3.1.接口定义接口定义就是根据不同的技术框架,结合业务需求,设计合理的接口。对于业务组件,只感知技术接口,不感知技术实现。我们不应该把具体的技术细节向上暴露,这就是所谓的面向接口编程。技术接口往往是业务和技术之间的桥梁。接口本身包含业务含义。最常见的是DAO接口。我们在设计DAO接口的时候,不会把它设计成与insert、update、query等业务无关的接口。设计为insertUser、updateUserById等业务相关的接口。同理,在设计缓存接口时,不要设计成put、get等接口,而应该设计成cacheUser、deprecateUser等接口。2.3.2.技术组件单机系统的技术组件一般分为两类。一种是通用技术组件,例如:数据存储、缓存、消息和调度任务、事务、锁。一是基础设施,比如spring容器、tomcat容器。让我们谈谈一般的技术组件。数据存储:数据存储包括关系数据库、非关系数据库和文件存储系统。关系型数据库,如MySQL,适合存储大部分业务数据。非关系型数据库,如hbase,可以存储历史日志,归档历史MySQL数据。文件存储系统一般都是基于Linux的文件系统,比如图片、html文件等,也有基于HDFS做大数据分析的。缓存:缓存按响应时间可分为纳秒级缓存、毫秒级缓存和百毫秒级缓存。纳秒级缓存是基于本地内存的通用缓存,如encache,毫秒级缓存一般是集中式内存缓存,如memcache。访问时由于远程调用,响应时间会延长到几毫秒,几百毫秒级别的缓存一般是集中持久化的缓存,比如redis。由于缓存击穿导致的远程访问和读取持久化记录的存在,其响应时间会更长,达到几十甚至上百毫秒。单机系统一般使用本地内存缓存。当缓存被打破时,它直接访问数据库。消息和调度任务:消息和调度任务本质上是异步方法。不同的是,消息不能控制异步时间,而调度任务可以。一般情况下,消息发出后,监听消息的系统会立即收到消息,从而立即触发业务逻辑的执行,调度任务会根据调度规则执行一次或多次业务逻辑。单机系统中很少使用消息和调度任务。消息可用于日志监控,调度任务可用于数据上报统计。事务:事务的本质是基于数据库实现的。单机系统的事务是依赖于数据库的事务。我们可以使用spring-tx的事务模板进行事务操作。在业务逻辑的开发中,一定要把握事务的大小。推荐使用一堆和业务比较贴近的数据库操作放在一个事务里面,不要随便给每个方法开一个事务。锁:单机系统中主要使用两种锁:乐观锁和悲观锁。乐观锁是通过在数据库的业务表中增加一个版本字段来实现的。每次更新都会判断版本是否发生变化。如果发生变化,则需要重试。这种锁的粒度比较小。悲观锁是基于JDK的Lock接口,对一个业务流程进行加锁和释放,锁的粒度比较粗。3.总结以上就是我经过长期实践摸索出来的业务技术架构。我觉得还是比较通用的,一定程度上可以支持可变业务。当然,这个架构绝对不是灵丹妙药,也不可能解决所有的业务场景,所以最终还是要针对具体的场景进行借鉴。作者简介吴继新,目前在杭州旅居星球担任架构师,专注于技术架构治理和产品架构。