基于微服务架构的企业ERP设计与应用①

2021-09-10 07:31沈迎春
计算机系统应用 2021年8期
关键词:调用网关子系统

桂 俊,沈迎春

(武汉数字工程研究所,武汉 430205)

传统ERP 体系结构往往基于单体的三层架构,伴随着业务功能的扩展,组件之间依赖越来复杂,业务数据的大量积累等导致系统的运行速度和用户体验越来越差,如何解决上面困境,以满足高可用、高并发的要求,迫切需要一种新的软件架构模式来解决上述问题.文献[1]指出传统单体架构设计的系统内部紧密耦合,灵活性差,以及基于面向服务架构SOA的应用系统的安全性不足、负载均衡等局限性.文献[2]采用新型微服务架构的应用系统可以有效解决ERP 系统暴露出的软件复杂性问题,各个微服务能够单独部署,每个独立的微服务模块负责传统ERP 系统中一个或多个关联的业务系统.为了适应应用系统性能要求、需求快速变化,传统单体架构由于耦合度强、维护复杂,不利于新功能扩展、快速交付,随着开源技术的成熟,进化出满足开发敏捷性、持续交付、可伸缩、最终一致的新兴系统架构即微服务架构[3].本文首先研究了微服务架构的理论和技术基础,分析了基于微服务构造应用的优势,从基础服务、业务服务、公共服务、接入服务等几个维度来描叙基于微服务的应用架构,针对ERP的具体需求设计了基于微服务架构的企业ERP架构,详细介绍了微服务的实现技术Spring Boot、Spring Cloud、负载均衡、网关架构设计、微服务间调用Feign,以订单子系统为例,详细论述了微服务的开发过程.

1 微服务架构设计

微服务架构是一种软件架构模式,它将一个完整的应用从数据存储到业务逻辑的开发垂直切分多个不同的服务,服务运行于独立的进程之中,具有自己独立的生命周期,服务之间采用统一的轻量级通信协议相互通讯,具有独立开发、维护、部署和扩展的优势[4].一个基于微服务架构的应用平台通常由业务服务、接入服务、公共服务、基础服务等4 部分组成,微服务应用架构设计如图1所示.

图1 微服务应用架构设计

其中业务服务负责实现各个服务,同时提供运维监控手段对服务进行监控,业务服务层将各个业务系统提供的服务接口进行集成,形成统一的服务标准为接入层提供服务.接入服务层通过服务路由、负载均衡等手段负责服务的分发,基础服务包括使用标准的中间件进行服务的注册,公共服务包括基础数据存储服务MongoDB、关系型RDB、缓存服务Redis,服务接口之间采用轻量级RESTful 格式消息交互机制.统一门户负责管理监控业务服务.

2 微服务架构企业ERP 设计

2.1 ERP的总体需求

ERP 系统的功能之一是实现企业业务流程的自动化.当企业客户下达订单之后,销售部门结合企业的库存和产能审核客户提交的订单是否能被满足,如果库存物料和生产产能同时能够满足订单需求,对客户订单予以确认,然后根据订单上所列产品,向仓储部提出产品出库申请,若库存产品能够满足订单需求,直接出库由配送人员将产品交付给客户.如果库存不满足情况下,由生产部根据订单确认生产,生产出产品提交仓储部进行产品入库,当生产部申领的物料,当前的库存无法满足时,需要向采购部提出采购申请,采购申请经过审批确认之后,由采购人员选择合适的供应商进行物料的采购,并将采购回来的物料提交给仓储部进行物料入库,入库之后的物料由仓储部交付给申领物料的生产人员.同时采购人员将采购单提交给财务部,由财务部进行付款确认,进行财务结算,打款给供应商.客户签收之后,销售人员将订单提交给财务部由财务部进行收款确认,财务部再进行财务结算,最后整个业务流程结束.

通过对业务流程的分析,接下来需要解决微服务中的领域划分及微服务粒度划分问题,遵循面向服务的领域驱动设计原则进行领域建模,通过业务领域拆分出一组领域模块微服务,每个微服务必须是高度内聚,符合开闭原则、自治等,仅聚集自己的业务、领域服务间通过接口交互达到松散耦合.通过领域分解识别出若干个业务功能子域,每个业务功能域对应一个子系统,通过子系统再次分解最终产生子流程再以迭代的方式逐步分解细化,最后达到与用户交互的级别的子流程称为原子流程.分析上述ERP 销售订单业务流程后,通过领域分解可以识别出销售订单、库存、生产、采购、财务等几个子域,每个子域将其设计为一个领域服务,每个领域服务对应一个功能集合,比如订单微服务包括订单创建、订单查询、订单审核等操作原子微服务,而每个原子微服务又可以被其他微服务共享,每个微服务可以看做是多个原子微服务的聚合服务.接下来需要定义微服务的接口,每个接口里封装了若干操作,包含接口名称、请求url、request 参数、封装返回结果等.

2.2 基于微服务的ERP 系统架构设计

上面详细分析了整个ERP的业务流程,大致梳理出ERP的业务功能,从业务领域中识别出若干微服务模块,包括库存管理、生产管理、销售管理及财务管理等,每个模块继续以迭代的方式分解为更细粒度的业务服务.基于微服务架构的ERP 系统架构如图2所示,整个体系结构自底向上分为4 层,基设施层提供以数据存储为主的基础服务,包括内存数据库Redis 提供缓存服务及关系型数据库资源,微服务层包括所有业务微服务模块的基础业务服务及由基础服务组合而成的聚合微服务,基础微服务通过操作业务数据集来实现单一的业务规则,而聚合微服务往往实现跨业务模块的复杂业务规则.各个微服务在注册中心组件完成注册部署,微服务之间通过Feign 方式交互,并向上层提供接口服务.服务网关提供外部访问的统一入口,外部通过网关接入微服务,同时提供动态路由、授权安全、调度、监控等的服务网关功能及Nginx 反向代理实现服务器的负载均衡.应用交互层包括Web 页面、APP 页面及调用的第三方系统,往往采用前后端分离技术,基于RESTful 风格交互,后端提供Rest 接口,前后端基于HTTP 协议通信、JSON 格式数据传递.接下来根据系统架构来进行实现框架的选型,Spring Cloud是J2EE 环境下最流行、先进的微服务实现框架,它是一序列微服务开发工具集,包括微服务的分布式配置、服务发现、路由、负载均衡、断路器、服务网关、消息传递等应用组件的提供.微服务架构是一序列单体微服务应用的集合,Spring Boot 框架通过简化配置快速简单开发Spring 应用,可以利用Spring Boot 专注于单个应用的快速构建.采购、生产、库存各个子系统都是独立的微服务,通过Eureka 进行服务治理,各个子系统将提供的服务注册到Eureka Server中,作为服务提供端,同时各个子系统作为服务消费端使用Rest接口对服务提供端进行调用.API 网关为客户端提供统一接口,服务网关Gateway是一种基于MVC 模式的响应式Web 框架,它简化前端调用逻辑,根据请求代理到不同的服务,通过转发将前端请求路由到后端服务中,调用单个服务或通过API 组合调用多个微服务返回结果[5].网关使用Nginx 做路由,能够将前端的请求分流分量的发送给后端服务,实现负载均衡,减少后端服务压力.

图2 基于微服务的ERP 系统架构图

2.3 协作接口设计

定义协作接口是规定团队合作的契约,保证开发不同限界上下文的特性团队能够并行开发.本文设计的ERP 系统涉及到订单、客户、员工、文件等实体,同时还要与第三方OA 系统的集成工作.订单的上下文映射如图3所示.

图3 订单的上下文映射

图3中确定除与OA 集成上下文之间采用“发布者/订阅者”模式,无需引入防腐层和开放主机服务,其余限界上下文之间的协作都是“客户方/供应方”模式,要定义的协作接口其实就是各个限界上下文的应用服务接口.协作接口采用事件机制,定义的是限界上下文之间协作的接口,协作接口完全可以根据之前确定的上下文映射获得,每个协作关系都意味着一个接口,不同的上下文映射模式可能会影响到对这些接口的设计.以生产订单上下文为例,与前面上下文映射的不同之处是将订单与OA 集成之间的协作改为了事件机制,记录与订单上下文相关的协作接口如图4所示.

图4 记录与订单上下文相关的协作接口

使用生产者(Producer)与消费者(Consumer)来抽象客户方/供应方模式与发布者/订阅者模式,多个模式的组合后面的服务定义就应该是遵循RESTful 服务定义的接口,开放主机服务,客户方/供应方与开放主机服务之间的组合.回顾OA 集成上下文的上下文映射,是将事件持有的内容转换为要发送消息通知的内容以及送达的地址,作为订阅者的OA 集成上下文在接收到事件,然后发送消息通知.订阅的事件应该是相同的,应该将Order Completed 修改为Notification Ready 事件,处理事件的逻辑完全相同.

3 基于微服务的ERP 系统实现

3.1 Spring Boot 子系统构建

开源环境下微服务的开发采用基于Spring 框架全栈技术,包括Web 开发框架SpringMVC、服务开发框架Spring Boot、服务治理框架Spring Cloud 及ORM持久框架Mybatis,建立统分布式缓存Redis 集群.首先我们需要创建maven 项目,在pom.xml中增加项目中使用到的服务模块module,比如配置、服务注册及各种业务服务,并添加相关依赖,便可启动服务注册中心.由于各个微服务可以独立的开发和部署,但每个服务的数据实体,控制层、实现层和数据持久层的风格都是一致的,Spring Boot 用来快速构建单个微服务应用,首先,在application.properties 进行配置,包括数据源及Mybatis 配置.然后是业务系统的开发,这里以订单子系统为例,订单管理包括产品出库和产品入库两大类,每种都包括订单的创建、查询、审核及生成收拣货任务,同时订单子系统与客户子系统及产品子系统产生关联,首先定义好订单相关接口,并且接口请求URL 以RESTful 风格呈现,整个订单子系统包括订单实体类、订单控制类、业务服务接口类用以提供多样的方法供控制层调用、数据库映射关系类等4 种,用户进入订单查询页面,输入订单编号、仓库信息等,SpringMVC 控制层接受到查询参数后,调用订单服务接口中的查询方法,订单服务实现类封装了查询客户和产品信息的方法,通过调用DAO 组件的Mapper 接口类返回数据,在服务实现类进行组装再返回给前端用户.最后启动Spring 应用主程序,启动嵌入式的Tomcat 并初始化Spring 组件.基于Spring Boot 构建订单服务模块如图5所示.搭建Redis 集群后,创建主从节点,Spring Boot 添加Redis 依赖后,在application.yml 配置nodes、poolConfig 等信息,在Redis 配置类中修改Redis 序列化方式,然后在服务类中注入Redis-Tempate 就可以完成数据对象的读写操作,比如可以将产品类以JSON 格式存储到Redis中,或者将Redis中以JSON对象形式返回的产品list 转换为JSON 字符串.

图5 订单服务时序图

3.2 服务注册发现与负载均衡架构

基于Spring Boot的服务开发完毕后,需要利用Eureka 搭建一个服务注册发现架构,首先创建服务注册中心,在Spring Boot 工程下添加Eureka-server 依赖及相关配置,创建产品服务product-service,通过在启动类中使用@EnableEurekaServer 注解声明该服务是Eureka的服务端,再添加端口,修改主机名,配置服务相关地址,接下来服务提供者需要将服务注册到服务注册中心供服务消费者订阅,启动服务端后就可以通过URL 访问Eureka 查看注册服务信息.客户端将自身注册到服务中心,同时从服务中心获取服务,服务消费者在Pom 文件中,添加起步Eureka 依赖,在启动类注解@EnableDiscoverClient,创建接口来获取产品服务实例,然后启动网关服务,客户端统一通过Zuul 网关访问内部服务,网关接受发送请求,从Eureka 获取可用服务,由Ribbon 进行负载均衡,分发到后端服务实例去调用[6,7].负载均衡技术将来自客户端大量访问流量捕获到负载均衡服务器中,通过调用特定的调度算法向不同服务器分发访问流量.在微服务架构中,服务消费者EurekaClient 向Rabbion 发起RestTemplate 请求后会被LoadBalancer 拦截,根据URL 获取服务名,根据EurekaClient中服务状态返回到Rabbion的服务注册表中的信息找到匹配的服务.具体配置如下,首先添加Ribbon的起步依赖,在application.yml中制定端口号及服务注册地址URL,通过在RibbonConfig 类加上@LoadBalanced 注解来注入RestTemplate 开启负载均衡功能.服务注册发现与负载均衡架构如图6所示,具体代码如下:

图6 服务注册发现与负载均衡架构

@Configuraton

public class RibbonConfig{

@LoadBalanced

RestTemplate restTemplate ()

{return new RestTemplate();}

}

定义一个ProductService 类,注入restTemplate对象,在该类的QueryStock() 方法以Rest 方式调用Eureka client API 接口,服务层代码如下:

@Service

public class ProductService {

@Autowired

RestTemplate restTemplate ;

public String QueryStock (String cPdCode) {

Return restTemplate.getForObject(

“http://eureka-client/QueryStock?cPdCode=”+cPdCode,String.class);

}}

在OrderController 类加上@RestController 注解,开启 RestController的功能,添加Get 方法的接口,调用productService 类的QueryStock()方法,代码如下:

@RestController

public class OrderController {

@Autowired

private ProductService productService;

@GetMapping("/QueryStock")

public String QueryStock(@RequestParam (required=false,defaultValue="83010042") String cPdCode) {

return ribbonService.QueryStock(cPdCode);}

}

3.3 网关设计

网关服务作为最上层服务,提供统一入口,承担权限身份认证、限流、监控、接口转发工作,调用下游的基础接口服务,返回数据呈现给用户.Spring Cloud中通过响应式的Spring Webflux 来实现API Gateway,执行请求路由到后端服务,执行API 组合、协议转换等操作.Spring Cloud 服务网关架构如图7所示,它包括Main 包、API 包、代理包,Cofiguration 类定义了Spring beans,它负责路由与Order 相关的请求[8-10],OrderHandlers 类实现各种请求处理程序方法,使用API 组合获取订单的详细信息.处理程序使用远程代理调用后端服务.具体实现代码如下所示:

图7 所示 API Gateway 架构

public class OrderHandlers {

private OrderService orderService;

private ProductService productService;

private CustomerService customerService;

public OrderHandlers(OrderService orderService,

ProductService productService,

CustomerService customerService) {

this.orderService=orderService;

this.productService=productService;

this.customerService=customerService;}

Public Moo<ServerResponse>

getOrderDetails(ServerRequest serverRequest) {

String orderid=serverRequest.pathVarable("orderId");

Mono<Orderinfo>orderinfo=orderService.findOrder Byid(orderid);

Mono<Optional<Productinfo>>productinfo=productService.findProdyctByOrderid(orderid);

Mono<Tuple4<0rderinfo,productinfo,custoinfo>>combined=Mono.when(orderinfo,productinfo,cusinfo);

Mono<OrderDetails> orderDetails=

combined.map(OrderDetails:makeOrderDetails);

…}

getOrderDetails()实现API 组合,以获取订单详细信息,它并行调用3 个服务,并将结果组合在一起,创建一个OrderDetails对象转换为ServerResponse.

OrderService 通过WebClient 调用OrderService 远程代理,将JSON 响应反序列为OrderInfo对象.

@Service

public class OrderService {

private OrderIntent orderIntent;

private WebClient client;

Public OrderService(OrderIntent orderIntent,WebClient client)

{this.orderIntent=orderIntent;

this.client=client;}

public Mono<OrderInfo>findOrderById(String orderId){

Mono<ClientResponse>result=client.get()

.uri(orderIntent.orderServiceUrl+"/orders/{order}",orderId).exchange();

Return result.flatMap(res->res.bodyToMono(OrderInfo class)).block();}

}

3.4 微服务调用

微服务之间调用采用Feign 方式,这里以订单服务与库存服务交互为例,定义好Feign的起步依赖,在application.yml中配置eureka-feign-client、端口号、服务注册地址等,再在启动类中加上注解等.接下来创建接口使用@FeignClient 注解,其中value 表示要远程调用的其他服务名称,接口中方法QueryStock 通过Fegin 来调用eureka-client 服务中的接口[11-13],然后在订单服务层OrderService中注入EurekaClientFeign 接口实例,再创建一个控制器注入服务接口实例,调用查询库存方法,就可以实现远程调用Feign 客户端的服务.服务间调用代码如下:

@FeignClient (value="eureka-client",configuration=FeignConfig.class)

public interface EurekaClientFeign

{

@GetMapping(value="/ QueryStock")

String QueryStockFromClientEureka

(@RequestParam(value="name") String name) ;

}

控制层与服务层代码类似已省略.

4 系统测试与运行

4.1 接口测试

Swagger 规范用于生成描述文件和接口文档,Spring Boot 使用swagger 构建RESTful APIs,首先在业务服务的pom 文件中添加Springfox、Swagger UI 依赖后,再在控制层和启动类添加注解、层中增加方法及参数,最后在UI 配置类中配置接口,代码如下:

@Controller

@RequestMapping("/product")

Public class OrderController

{

@Autowired

private OrderService orderService;

@ApiOperation(value="根据订单ID 取产品信息")

@RequestMapping(value="/info/{orderId}",method=RequestMethod.GET,charset="utf-8")

@ResponseBody

Public Product getproductInfo(@ApiParam(name=

"orderId",value="订单ID") @PathVariable

Long orderId) throws Exception {

Return orderService.getProductInfo(orderId);

}}

4.2 功能测试

本文以销售微服务为例,对其订单管理部分功能进行测试,订单创建界面如图8所示,选择客户、订单日期和商品后即可创建成功.

图8 订单创建

订单查询界面如图9所示,选择起止日期,按订单号或客户号输入关键字即可查询订单.

图9 订单查询

4.3 性能测试

此次测试对基于微服务架构的ERP 重构前后的系统分别使用Jmeter 工具在相同的并发量下测试其性能.将模拟并发量提高到1000,持续时间30 s 条件下,分别对产品信息返回结果的响应时间如图10所示.

图10 系统响应时间百分比

图10可以看出ERP 系统在微服务重构前后,当请求响应量达到50%时,采用单体架构和微服务架构的响应时间分别为2.3 s和0.098 s,重构后的系统在高并发情况下平均响应时间更短.

5 结语

本文首先分析传统单体架构的缺陷,提出采用新型的微服务架构的特点和优势.基于微服务的架构设计因业务模块具有各自的数据库、实体、服务、API组件等,可独立或者单独部署多个微服务,具有灵活、可扩展、去中心化、敏捷性、自治等优势[14].本文提出基于微服务架构来构建企业ERP,分析微服务的分解模式,设计了基于微服务的分层架构,采用基于开源的微服务实现框架Spring Boot 开发服务、Spring Cloud来管理服务,使用Redis 做分布式数据缓存.虽然微服务相关技术不断发展创新,微服务之间如何准确通信,以满足用户快速响应的需求,微服务的部署、测试、跨服务实现问题,如何在通信中保证数据的安全性,采用什么样身份认证策略、数据加密方法都值得我们未来去探索[15].

猜你喜欢
调用网关子系统
智能燃气表物联网运行体系网关技术研究
基于FPGA的工业TSN融合网关设计
大规模低轨卫星网络移动性管理方案
一种主从冗余网关的故障模式分析与处理
网络空间供应链中入侵检测及防御子系统的投资机制研究
网络空间供应链中入侵检测及防御子系统的投资机制研究
基于Android Broadcast的短信安全监听系统的设计和实现
团队与知识管理的关系研究
浅谈中职学校新型模块化机房建设
利用RFC技术实现SAP系统接口通信