桂 俊,沈迎春
(武漢數(shù)字工程研究所,武漢 430205)
傳統(tǒng)ERP 體系結(jié)構(gòu)往往基于單體的三層架構(gòu),伴隨著業(yè)務功能的擴展,組件之間依賴越來復雜,業(yè)務數(shù)據(jù)的大量積累等導致系統(tǒng)的運行速度和用戶體驗越來越差,如何解決上面困境,以滿足高可用、高并發(fā)的要求,迫切需要一種新的軟件架構(gòu)模式來解決上述問題.文獻[1]指出傳統(tǒng)單體架構(gòu)設計的系統(tǒng)內(nèi)部緊密耦合,靈活性差,以及基于面向服務架構(gòu)SOA的應用系統(tǒng)的安全性不足、負載均衡等局限性.文獻[2]采用新型微服務架構(gòu)的應用系統(tǒng)可以有效解決ERP 系統(tǒng)暴露出的軟件復雜性問題,各個微服務能夠單獨部署,每個獨立的微服務模塊負責傳統(tǒng)ERP 系統(tǒng)中一個或多個關(guān)聯(lián)的業(yè)務系統(tǒng).為了適應應用系統(tǒng)性能要求、需求快速變化,傳統(tǒng)單體架構(gòu)由于耦合度強、維護復雜,不利于新功能擴展、快速交付,隨著開源技術(shù)的成熟,進化出滿足開發(fā)敏捷性、持續(xù)交付、可伸縮、最終一致的新興系統(tǒng)架構(gòu)即微服務架構(gòu)[3].本文首先研究了微服務架構(gòu)的理論和技術(shù)基礎,分析了基于微服務構(gòu)造應用的優(yōu)勢,從基礎服務、業(yè)務服務、公共服務、接入服務等幾個維度來描敘基于微服務的應用架構(gòu),針對ERP的具體需求設計了基于微服務架構(gòu)的企業(yè)ERP架構(gòu),詳細介紹了微服務的實現(xiàn)技術(shù)Spring Boot、Spring Cloud、負載均衡、網(wǎng)關(guān)架構(gòu)設計、微服務間調(diào)用Feign,以訂單子系統(tǒng)為例,詳細論述了微服務的開發(fā)過程.
微服務架構(gòu)是一種軟件架構(gòu)模式,它將一個完整的應用從數(shù)據(jù)存儲到業(yè)務邏輯的開發(fā)垂直切分多個不同的服務,服務運行于獨立的進程之中,具有自己獨立的生命周期,服務之間采用統(tǒng)一的輕量級通信協(xié)議相互通訊,具有獨立開發(fā)、維護、部署和擴展的優(yōu)勢[4].一個基于微服務架構(gòu)的應用平臺通常由業(yè)務服務、接入服務、公共服務、基礎服務等4 部分組成,微服務應用架構(gòu)設計如圖1所示.
圖1 微服務應用架構(gòu)設計
其中業(yè)務服務負責實現(xiàn)各個服務,同時提供運維監(jiān)控手段對服務進行監(jiān)控,業(yè)務服務層將各個業(yè)務系統(tǒng)提供的服務接口進行集成,形成統(tǒng)一的服務標準為接入層提供服務.接入服務層通過服務路由、負載均衡等手段負責服務的分發(fā),基礎服務包括使用標準的中間件進行服務的注冊,公共服務包括基礎數(shù)據(jù)存儲服務MongoDB、關(guān)系型RDB、緩存服務Redis,服務接口之間采用輕量級RESTful 格式消息交互機制.統(tǒng)一門戶負責管理監(jiān)控業(yè)務服務.
ERP 系統(tǒng)的功能之一是實現(xiàn)企業(yè)業(yè)務流程的自動化.當企業(yè)客戶下達訂單之后,銷售部門結(jié)合企業(yè)的庫存和產(chǎn)能審核客戶提交的訂單是否能被滿足,如果庫存物料和生產(chǎn)產(chǎn)能同時能夠滿足訂單需求,對客戶訂單予以確認,然后根據(jù)訂單上所列產(chǎn)品,向倉儲部提出產(chǎn)品出庫申請,若庫存產(chǎn)品能夠滿足訂單需求,直接出庫由配送人員將產(chǎn)品交付給客戶.如果庫存不滿足情況下,由生產(chǎn)部根據(jù)訂單確認生產(chǎn),生產(chǎn)出產(chǎn)品提交倉儲部進行產(chǎn)品入庫,當生產(chǎn)部申領(lǐng)的物料,當前的庫存無法滿足時,需要向采購部提出采購申請,采購申請經(jīng)過審批確認之后,由采購人員選擇合適的供應商進行物料的采購,并將采購回來的物料提交給倉儲部進行物料入庫,入庫之后的物料由倉儲部交付給申領(lǐng)物料的生產(chǎn)人員.同時采購人員將采購單提交給財務部,由財務部進行付款確認,進行財務結(jié)算,打款給供應商.客戶簽收之后,銷售人員將訂單提交給財務部由財務部進行收款確認,財務部再進行財務結(jié)算,最后整個業(yè)務流程結(jié)束.
通過對業(yè)務流程的分析,接下來需要解決微服務中的領(lǐng)域劃分及微服務粒度劃分問題,遵循面向服務的領(lǐng)域驅(qū)動設計原則進行領(lǐng)域建模,通過業(yè)務領(lǐng)域拆分出一組領(lǐng)域模塊微服務,每個微服務必須是高度內(nèi)聚,符合開閉原則、自治等,僅聚集自己的業(yè)務、領(lǐng)域服務間通過接口交互達到松散耦合.通過領(lǐng)域分解識別出若干個業(yè)務功能子域,每個業(yè)務功能域?qū)粋€子系統(tǒng),通過子系統(tǒng)再次分解最終產(chǎn)生子流程再以迭代的方式逐步分解細化,最后達到與用戶交互的級別的子流程稱為原子流程.分析上述ERP 銷售訂單業(yè)務流程后,通過領(lǐng)域分解可以識別出銷售訂單、庫存、生產(chǎn)、采購、財務等幾個子域,每個子域?qū)⑵湓O計為一個領(lǐng)域服務,每個領(lǐng)域服務對應一個功能集合,比如訂單微服務包括訂單創(chuàng)建、訂單查詢、訂單審核等操作原子微服務,而每個原子微服務又可以被其他微服務共享,每個微服務可以看做是多個原子微服務的聚合服務.接下來需要定義微服務的接口,每個接口里封裝了若干操作,包含接口名稱、請求url、request 參數(shù)、封裝返回結(jié)果等.
上面詳細分析了整個ERP的業(yè)務流程,大致梳理出ERP的業(yè)務功能,從業(yè)務領(lǐng)域中識別出若干微服務模塊,包括庫存管理、生產(chǎn)管理、銷售管理及財務管理等,每個模塊繼續(xù)以迭代的方式分解為更細粒度的業(yè)務服務.基于微服務架構(gòu)的ERP 系統(tǒng)架構(gòu)如圖2所示,整個體系結(jié)構(gòu)自底向上分為4 層,基設施層提供以數(shù)據(jù)存儲為主的基礎服務,包括內(nèi)存數(shù)據(jù)庫Redis 提供緩存服務及關(guān)系型數(shù)據(jù)庫資源,微服務層包括所有業(yè)務微服務模塊的基礎業(yè)務服務及由基礎服務組合而成的聚合微服務,基礎微服務通過操作業(yè)務數(shù)據(jù)集來實現(xiàn)單一的業(yè)務規(guī)則,而聚合微服務往往實現(xiàn)跨業(yè)務模塊的復雜業(yè)務規(guī)則.各個微服務在注冊中心組件完成注冊部署,微服務之間通過Feign 方式交互,并向上層提供接口服務.服務網(wǎng)關(guān)提供外部訪問的統(tǒng)一入口,外部通過網(wǎng)關(guān)接入微服務,同時提供動態(tài)路由、授權(quán)安全、調(diào)度、監(jiān)控等的服務網(wǎng)關(guān)功能及Nginx 反向代理實現(xiàn)服務器的負載均衡.應用交互層包括Web 頁面、APP 頁面及調(diào)用的第三方系統(tǒng),往往采用前后端分離技術(shù),基于RESTful 風格交互,后端提供Rest 接口,前后端基于HTTP 協(xié)議通信、JSON 格式數(shù)據(jù)傳遞.接下來根據(jù)系統(tǒng)架構(gòu)來進行實現(xiàn)框架的選型,Spring Cloud是J2EE 環(huán)境下最流行、先進的微服務實現(xiàn)框架,它是一序列微服務開發(fā)工具集,包括微服務的分布式配置、服務發(fā)現(xiàn)、路由、負載均衡、斷路器、服務網(wǎng)關(guān)、消息傳遞等應用組件的提供.微服務架構(gòu)是一序列單體微服務應用的集合,Spring Boot 框架通過簡化配置快速簡單開發(fā)Spring 應用,可以利用Spring Boot 專注于單個應用的快速構(gòu)建.采購、生產(chǎn)、庫存各個子系統(tǒng)都是獨立的微服務,通過Eureka 進行服務治理,各個子系統(tǒng)將提供的服務注冊到Eureka Server中,作為服務提供端,同時各個子系統(tǒng)作為服務消費端使用Rest接口對服務提供端進行調(diào)用.API 網(wǎng)關(guān)為客戶端提供統(tǒng)一接口,服務網(wǎng)關(guān)Gateway是一種基于MVC 模式的響應式Web 框架,它簡化前端調(diào)用邏輯,根據(jù)請求代理到不同的服務,通過轉(zhuǎn)發(fā)將前端請求路由到后端服務中,調(diào)用單個服務或通過API 組合調(diào)用多個微服務返回結(jié)果[5].網(wǎng)關(guān)使用Nginx 做路由,能夠?qū)⑶岸说恼埱蠓至鞣至康陌l(fā)送給后端服務,實現(xiàn)負載均衡,減少后端服務壓力.
圖2 基于微服務的ERP 系統(tǒng)架構(gòu)圖
定義協(xié)作接口是規(guī)定團隊合作的契約,保證開發(fā)不同限界上下文的特性團隊能夠并行開發(fā).本文設計的ERP 系統(tǒng)涉及到訂單、客戶、員工、文件等實體,同時還要與第三方OA 系統(tǒng)的集成工作.訂單的上下文映射如圖3所示.
圖3 訂單的上下文映射
圖3中確定除與OA 集成上下文之間采用“發(fā)布者/訂閱者”模式,無需引入防腐層和開放主機服務,其余限界上下文之間的協(xié)作都是“客戶方/供應方”模式,要定義的協(xié)作接口其實就是各個限界上下文的應用服務接口.協(xié)作接口采用事件機制,定義的是限界上下文之間協(xié)作的接口,協(xié)作接口完全可以根據(jù)之前確定的上下文映射獲得,每個協(xié)作關(guān)系都意味著一個接口,不同的上下文映射模式可能會影響到對這些接口的設計.以生產(chǎn)訂單上下文為例,與前面上下文映射的不同之處是將訂單與OA 集成之間的協(xié)作改為了事件機制,記錄與訂單上下文相關(guān)的協(xié)作接口如圖4所示.
圖4 記錄與訂單上下文相關(guān)的協(xié)作接口
使用生產(chǎn)者(Producer)與消費者(Consumer)來抽象客戶方/供應方模式與發(fā)布者/訂閱者模式,多個模式的組合后面的服務定義就應該是遵循RESTful 服務定義的接口,開放主機服務,客戶方/供應方與開放主機服務之間的組合.回顧OA 集成上下文的上下文映射,是將事件持有的內(nèi)容轉(zhuǎn)換為要發(fā)送消息通知的內(nèi)容以及送達的地址,作為訂閱者的OA 集成上下文在接收到事件,然后發(fā)送消息通知.訂閱的事件應該是相同的,應該將Order Completed 修改為Notification Ready 事件,處理事件的邏輯完全相同.
開源環(huán)境下微服務的開發(fā)采用基于Spring 框架全棧技術(shù),包括Web 開發(fā)框架SpringMVC、服務開發(fā)框架Spring Boot、服務治理框架Spring Cloud 及ORM持久框架Mybatis,建立統(tǒng)分布式緩存Redis 集群.首先我們需要創(chuàng)建maven 項目,在pom.xml中增加項目中使用到的服務模塊module,比如配置、服務注冊及各種業(yè)務服務,并添加相關(guān)依賴,便可啟動服務注冊中心.由于各個微服務可以獨立的開發(fā)和部署,但每個服務的數(shù)據(jù)實體,控制層、實現(xiàn)層和數(shù)據(jù)持久層的風格都是一致的,Spring Boot 用來快速構(gòu)建單個微服務應用,首先,在application.properties 進行配置,包括數(shù)據(jù)源及Mybatis 配置.然后是業(yè)務系統(tǒng)的開發(fā),這里以訂單子系統(tǒng)為例,訂單管理包括產(chǎn)品出庫和產(chǎn)品入庫兩大類,每種都包括訂單的創(chuàng)建、查詢、審核及生成收揀貨任務,同時訂單子系統(tǒng)與客戶子系統(tǒng)及產(chǎn)品子系統(tǒng)產(chǎn)生關(guān)聯(lián),首先定義好訂單相關(guān)接口,并且接口請求URL 以RESTful 風格呈現(xiàn),整個訂單子系統(tǒng)包括訂單實體類、訂單控制類、業(yè)務服務接口類用以提供多樣的方法供控制層調(diào)用、數(shù)據(jù)庫映射關(guān)系類等4 種,用戶進入訂單查詢頁面,輸入訂單編號、倉庫信息等,SpringMVC 控制層接受到查詢參數(shù)后,調(diào)用訂單服務接口中的查詢方法,訂單服務實現(xiàn)類封裝了查詢客戶和產(chǎn)品信息的方法,通過調(diào)用DAO 組件的Mapper 接口類返回數(shù)據(jù),在服務實現(xiàn)類進行組裝再返回給前端用戶.最后啟動Spring 應用主程序,啟動嵌入式的Tomcat 并初始化Spring 組件.基于Spring Boot 構(gòu)建訂單服務模塊如圖5所示.搭建Redis 集群后,創(chuàng)建主從節(jié)點,Spring Boot 添加Redis 依賴后,在application.yml 配置nodes、poolConfig 等信息,在Redis 配置類中修改Redis 序列化方式,然后在服務類中注入Redis-Tempate 就可以完成數(shù)據(jù)對象的讀寫操作,比如可以將產(chǎn)品類以JSON 格式存儲到Redis中,或者將Redis中以JSON對象形式返回的產(chǎn)品list 轉(zhuǎn)換為JSON 字符串.
圖5 訂單服務時序圖
基于Spring Boot的服務開發(fā)完畢后,需要利用Eureka 搭建一個服務注冊發(fā)現(xiàn)架構(gòu),首先創(chuàng)建服務注冊中心,在Spring Boot 工程下添加Eureka-server 依賴及相關(guān)配置,創(chuàng)建產(chǎn)品服務product-service,通過在啟動類中使用@EnableEurekaServer 注解聲明該服務是Eureka的服務端,再添加端口,修改主機名,配置服務相關(guān)地址,接下來服務提供者需要將服務注冊到服務注冊中心供服務消費者訂閱,啟動服務端后就可以通過URL 訪問Eureka 查看注冊服務信息.客戶端將自身注冊到服務中心,同時從服務中心獲取服務,服務消費者在Pom 文件中,添加起步Eureka 依賴,在啟動類注解@EnableDiscoverClient,創(chuàng)建接口來獲取產(chǎn)品服務實例,然后啟動網(wǎng)關(guān)服務,客戶端統(tǒng)一通過Zuul 網(wǎng)關(guān)訪問內(nèi)部服務,網(wǎng)關(guān)接受發(fā)送請求,從Eureka 獲取可用服務,由Ribbon 進行負載均衡,分發(fā)到后端服務實例去調(diào)用[6,7].負載均衡技術(shù)將來自客戶端大量訪問流量捕獲到負載均衡服務器中,通過調(diào)用特定的調(diào)度算法向不同服務器分發(fā)訪問流量.在微服務架構(gòu)中,服務消費者EurekaClient 向Rabbion 發(fā)起RestTemplate 請求后會被LoadBalancer 攔截,根據(jù)URL 獲取服務名,根據(jù)EurekaClient中服務狀態(tài)返回到Rabbion的服務注冊表中的信息找到匹配的服務.具體配置如下,首先添加Ribbon的起步依賴,在application.yml中制定端口號及服務注冊地址URL,通過在RibbonConfig 類加上@LoadBalanced 注解來注入RestTemplate 開啟負載均衡功能.服務注冊發(fā)現(xiàn)與負載均衡架構(gòu)如圖6所示,具體代碼如下:
圖6 服務注冊發(fā)現(xiàn)與負載均衡架構(gòu)
@Configuraton
public class RibbonConfig{
@LoadBalanced
RestTemplate restTemplate ()
{return new RestTemplate();}
}
定義一個ProductService 類,注入restTemplate對象,在該類的QueryStock() 方法以Rest 方式調(diào)用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 方法的接口,調(diào)用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);}
}
網(wǎng)關(guān)服務作為最上層服務,提供統(tǒng)一入口,承擔權(quán)限身份認證、限流、監(jiān)控、接口轉(zhuǎn)發(fā)工作,調(diào)用下游的基礎接口服務,返回數(shù)據(jù)呈現(xiàn)給用戶.Spring Cloud中通過響應式的Spring Webflux 來實現(xiàn)API Gateway,執(zhí)行請求路由到后端服務,執(zhí)行API 組合、協(xié)議轉(zhuǎn)換等操作.Spring Cloud 服務網(wǎng)關(guān)架構(gòu)如圖7所示,它包括Main 包、API 包、代理包,Cofiguration 類定義了Spring beans,它負責路由與Order 相關(guān)的請求[8-10],OrderHandlers 類實現(xiàn)各種請求處理程序方法,使用API 組合獲取訂單的詳細信息.處理程序使用遠程代理調(diào)用后端服務.具體實現(xiàn)代碼如下所示:
圖7 所示 API Gateway 架構(gòu)
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()實現(xiàn)API 組合,以獲取訂單詳細信息,它并行調(diào)用3 個服務,并將結(jié)果組合在一起,創(chuàng)建一個OrderDetails對象轉(zhuǎn)換為ServerResponse.
OrderService 通過WebClient 調(diào)用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();}
}
微服務之間調(diào)用采用Feign 方式,這里以訂單服務與庫存服務交互為例,定義好Feign的起步依賴,在application.yml中配置eureka-feign-client、端口號、服務注冊地址等,再在啟動類中加上注解等.接下來創(chuàng)建接口使用@FeignClient 注解,其中value 表示要遠程調(diào)用的其他服務名稱,接口中方法QueryStock 通過Fegin 來調(diào)用eureka-client 服務中的接口[11-13],然后在訂單服務層OrderService中注入EurekaClientFeign 接口實例,再創(chuàng)建一個控制器注入服務接口實例,調(diào)用查詢庫存方法,就可以實現(xiàn)遠程調(diào)用Feign 客戶端的服務.服務間調(diào)用代碼如下:
@FeignClient (value="eureka-client",configuration=FeignConfig.class)
public interface EurekaClientFeign
{
@GetMapping(value="/ QueryStock")
String QueryStockFromClientEureka
(@RequestParam(value="name") String name) ;
}
控制層與服務層代碼類似已省略.
Swagger 規(guī)范用于生成描述文件和接口文檔,Spring Boot 使用swagger 構(gòu)建RESTful APIs,首先在業(yè)務服務的pom 文件中添加Springfox、Swagger UI 依賴后,再在控制層和啟動類添加注解、層中增加方法及參數(shù),最后在UI 配置類中配置接口,代碼如下:
@Controller
@RequestMapping("/product")
Public class OrderController
{
@Autowired
private OrderService orderService;
@ApiOperation(value="根據(jù)訂單ID 取產(chǎn)品信息")
@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);
}}
本文以銷售微服務為例,對其訂單管理部分功能進行測試,訂單創(chuàng)建界面如圖8所示,選擇客戶、訂單日期和商品后即可創(chuàng)建成功.
圖8 訂單創(chuàng)建
訂單查詢界面如圖9所示,選擇起止日期,按訂單號或客戶號輸入關(guān)鍵字即可查詢訂單.
圖9 訂單查詢
此次測試對基于微服務架構(gòu)的ERP 重構(gòu)前后的系統(tǒng)分別使用Jmeter 工具在相同的并發(fā)量下測試其性能.將模擬并發(fā)量提高到1000,持續(xù)時間30 s 條件下,分別對產(chǎn)品信息返回結(jié)果的響應時間如圖10所示.
圖10 系統(tǒng)響應時間百分比
圖10可以看出ERP 系統(tǒng)在微服務重構(gòu)前后,當請求響應量達到50%時,采用單體架構(gòu)和微服務架構(gòu)的響應時間分別為2.3 s和0.098 s,重構(gòu)后的系統(tǒng)在高并發(fā)情況下平均響應時間更短.
本文首先分析傳統(tǒng)單體架構(gòu)的缺陷,提出采用新型的微服務架構(gòu)的特點和優(yōu)勢.基于微服務的架構(gòu)設計因業(yè)務模塊具有各自的數(shù)據(jù)庫、實體、服務、API組件等,可獨立或者單獨部署多個微服務,具有靈活、可擴展、去中心化、敏捷性、自治等優(yōu)勢[14].本文提出基于微服務架構(gòu)來構(gòu)建企業(yè)ERP,分析微服務的分解模式,設計了基于微服務的分層架構(gòu),采用基于開源的微服務實現(xiàn)框架Spring Boot 開發(fā)服務、Spring Cloud來管理服務,使用Redis 做分布式數(shù)據(jù)緩存.雖然微服務相關(guān)技術(shù)不斷發(fā)展創(chuàng)新,微服務之間如何準確通信,以滿足用戶快速響應的需求,微服務的部署、測試、跨服務實現(xiàn)問題,如何在通信中保證數(shù)據(jù)的安全性,采用什么樣身份認證策略、數(shù)據(jù)加密方法都值得我們未來去探索[15].