Java生鮮電商平臺-微服務入門與服務的拆分架構實戰
剛開始進入軟件行業時還是單體應用的時代,前后端分離的概念都還沒普及,開發的時候需要花大量的時間在“強大”的JSP上面,那時候SOA已經算是新技術了。現在,微服務已經大行其道,有哪個互聯網產品不說自己是微服務架構呢?
但是,對于微服務的理解每個人都不太一樣,這篇文章主要是聊一聊我對微服務的理解以及如何搭建經典的微服務架構,目的是梳理一下自己的一些想法,如果存在不同看法的歡迎指正!
什么是微服務
首先,什么是微服務呢?
單體應用
相對的,要理解什么是微服務,那么可以先理解什么是單體應用,在沒有提出微服務的概念的“遠古”年代,一個軟件應用,往往會將應用所有功能都開發和打包在一起,那時候的一個B/S應用架構往往是這樣的:

但是,當用戶訪問量變大導致一臺服務器無法支撐時怎么辦呢?加服務器加負載均衡,架構就變成這樣了:

后面發現把靜態文件獨立出來,通過CDN等手段進行加速,可以提升應用的整體相應,單體應用的架構就變成:

上面3中架構都還是單體應用,只是在部署方面進行了優化,所以避免不了單體應用的根本的缺點:
- 代碼臃腫,應用啟動時間長;(代碼超過1G的項目都有!)
- 回歸測試周期長,修復一個小小bug可能都需要對所有關鍵業務進行回歸測試。
- 應用容錯性差,某個小小功能的程序錯誤可能導致整個系統宕機;
- 伸縮困難,單體應用擴展性能時只能整個應用進行擴展,造成計算資源浪費。
- 開發協作困難,一個大型應用系統,可能幾十個甚至上百個開發人員,大家都在維護一套代碼的話,代碼merge復雜度急劇增加。
微服務
我認為任何技術的演進都是有跡可循的,任何新技術的出現都是為了解決原有技術無法解決的需求,所以,微服務的出現就是因為原來單體應用架構已經無法滿足當前互聯網產品的技術需求。
在微服務架構之前還有一個概念:SOA(Service-Oriented Architecture)-面向服務的體系架構。我認為的SOA只是一個架構模型的方法論,并不是一個明確而嚴謹的架構標準,只是后面很多人將SOA與The Open Group的SOA參考模型等同了,認為嚴格按照TOG-SOA標準的才算真正的SOA架構。SOA就已經提出的面向服務的架構思想,所以微服務應該算是SOA的一種演進吧。
撇開架構先不說,什么樣的服務才算微服務呢?
- 單一職責的。一個微服務應該都是單一職責的,這才是“微”的體現,一個微服務解決一個業務問題(注意是一個業務問題而不是一個接口)。
- 面向服務的。將自己的業務能力封裝并對外提供服務,這是繼承SOA的核心思想,一個微服務本身也可能使用到其它微服務的能力。
我覺得滿足以上兩點就可以認為典型的微服務。
微服務典型架構
微服務架構,核心是為了解決應用微服務化之后的服務治理問題。
應用微服務化之后,首先遇到的第一個問題就是服務發現問題,一個微服務如何發現其他微服務呢?最簡單的方式就是每個微服務里面配置其他微服務的地址,但是當微服務數量眾多的時候,這樣做明顯不現實。所以需要使用到微服務架構中的一個最重要的組件:服務注冊中心,所有服務都注冊到服務注冊中心,同時也可以從服務注冊中心獲取當前可用的服務清單:

?
解決服務發現問題后,接著需要解決微服務分布式部署帶來的第二個問題:服務配置管理的問題。當服務數量超過一定程度之后,如果需要在每個服務里面分別維護每一個服務的配置文件,運維人員估計要哭了。那么,就需要用到微服務架構里面第二個重要的組件:配置中心,微服務架構就變成下面這樣了:

以上應用內部的服務治理,當客戶端或外部應用調用服務的時候怎么處理呢?服務A可能有多個節點,服務A、服務B和服務C的服務地址都不同,服務授權驗證在哪里做?這時,就需要使用到服務網關提供統一的服務入口,最終形成典型微服務架構:

上面是一個典型的微服務架構,當然微服務的服務治理還涉及很多內容,比如:
- 通過熔斷、限流等機制保證高可用;
- 微服務之間調用的負載均衡;
- 分布式事務(2PC、3PC、TCC、LCN等);
- 服務調用鏈跟蹤等等。
微服務框架
目前國內企業使用的微服務框架主要是Spring Cloud和Dubbo(或者DubboX),但是Dubbo那兩年的停更嚴重打擊了開發人員對它的信心,Spring Cloud已經逐漸成為主流,比較兩個框架的優劣勢的文章在網上有很多,這里就不重復了,選擇什么框架還是按業務需求來吧,業務框架決定技術框架。
Spring Cloud全家桶提供了各種各樣的組件,基本可以覆蓋微服務的服務治理的方方面面,以下列出了Spring Cloud一些常用組件:

搭建典型微服務架構
本章節主要介紹如何基于Spring Cloud相關組件搭建一個典型的微服務架構。
首先,創建一個Maven父項目spring-cloud-examples
,用于管理項目依賴包版本。由于Spring Cloud組件很多,為保證不同組件之間的兼容性,一般通過spring-cloud-dependencies
統一管理Spring Cloud組件版本,而非每個組件單獨引入。
pom.xml配置如下:
<!-- 繼承SpringBoot父項目,注意與SpringCloud版本的匹配 --><parent><groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> </parent> <properties> <spring.boot.version>2.1.4.RELEASE</spring.boot.version> <spring.cloud.version>Greenwich.SR1</spring.cloud.version> <lombok.version>1.18.8</lombok.version> <maven.compiler.plugin.version>3.8.1</maven.compiler.plugin.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring.cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
搭建服務配置中心
- 在
spring-cloud-examples
項目下創建一個子項目spring-cloud-example-config
,添加Spring Cloud Config Server端的相關依賴包:
<dependencies><dependency><groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> </dependencies>
- 添加Spring Boot配置文件
application.yml
,配置如下:
spring:application:name: spring-cloud-example-configprofiles:active: native #啟用本地配置文件cloud:config:server:native:search-locations: classpath:/configs/ #配置文件掃描目錄server:port: 8000 #服務端口
- 啟動類添加注解
@EnableConfigServer
通過啟用Config Server服務。
@SpringBootApplication
@EnableConfigServer
public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
搭建服務注冊中心
- 在
spring-cloud-examples
項目下創建一個子項目spring-cloud-example-registry
,在pom.xml
中添加Eureka Server相關依賴包:
<dependencies><dependency><groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> </dependencies>
- 在
spring-cloud-example-config
配置中心項目的src/main/resource/configs
目錄下添加一個服務配置文件spring-cloud-example-registry.yml
,配置如下:
spring:application:name: spring-cloud-example-registry# Eureka相關配置
eureka:client:register-with-eureka: false #不注冊服務fetch-registry: false #不拉去服務清單 serviceUrl: defaultZone: http://localhost:${server.port}/eureka/ #多個通過英文逗號分隔 server: port: 8001
- 在
spring-cloud-example-registry
項目的src/main/resource/
目錄添加bootstrap.yml
配置文件,配置如下:
spring:cloud:config:name: spring-cloud-example-registry #配置文件名稱,多個通過逗號分隔uri: http://localhost:8000 #Config Server服務地址
- 啟動類添加注解
@EnableEurekaServer
通過啟用Eureka Server服務。
@SpringBootApplication
@EnableEurekaServer
public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
搭建業務服務A
- 在
spring-cloud-examples
項目下創建一個業務服務A的子項目spring-cloud-example-biz-a
,在pom.xml
中添加以下依賴包:
<dependencies><!-- Spring Boot Web Starter --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- feign --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!-- Eureka Client Starter --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- Config Client Starter --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> </dependencies>
- 在
spring-cloud-example-config
配置中心項目的src/main/resource/configs
目錄下添加一個服務配置文件spring-cloud-example-biz-a.yml
,配置如下:
spring:application:name: spring-cloud-example-biz-aserver:port: 8010# Eureka相關配置
eureka:client:serviceUrl:defaultZone: http://localhost:8001/eureka/instance:lease-renewal-interval-in-seconds: 10 # 心跳時間,即服務續約間隔時間(缺省為30s) lease-expiration-duration-in-seconds: 60 # 發呆時間,即服務續約到期時間(缺省為90s) prefer-ip-address: true instance-id: ${spring.application.name}:${spring.application.instance_id:${server.port}}
- 在
spring-cloud-example-biz-a
項目的src/main/resource/
目錄添加bootstrap.yml
配置文件,配置如下:
spring:cloud:config:name: spring-cloud-example-biz-a #配置文件名稱,多個通過逗號分隔uri: http://localhost:8000 #Config Server服務地址
- 添加一個示例接口,代碼參考:
@RestController
@RequestMapping("/hello")
public class HelloController { /** * 示例方法 * * @return */ @GetMapping public String sayHello() { return "Hello,This is Biz-A Service."; } }
搭建業務服務B
參考上面業務服務A搭建另外一個業務服務B。
搭建服務網關
- 在
spring-cloud-examples
項目下創建一個業務服務A的子項目spring-cloud-example-gateway
,在pom.xml
中添加以下依賴包:
<dependencies><!-- zuul --><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <!-- Eureka Client Starter --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- Config Client Starter --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> </dependencies>
- 在
spring-cloud-example-config
配置中心項目的src/main/resource/configs
目錄下添加一個服務配置文件spring-cloud-example-gateway.yml
,配置如下:
spring:application:name: spring-cloud-example-gatewayserver:port: 8002# Eureka相關配置
eureka:client:serviceUrl:defaultZone: http://localhost:8001/eureka/instance:lease-renewal-interval-in-seconds: 10 # 心跳時間,即服務續約間隔時間(缺省為30s) lease-expiration-duration-in-seconds: 60 # 發呆時間,即服務續約到期時間(缺省為90s) prefer-ip-address: true instance-id: ${spring.application.name}:${spring.application.instance_id:${server.port}}
- 在
spring-cloud-example-gateway
項目的src/main/resource/
目錄添加bootstrap.yml
配置文件,配置如下:
spring:cloud:config:name: spring-cloud-example-gateway #配置文件名稱,多個通過逗號分隔uri: http://localhost:8000 #Config Server服務地址
- 啟動類添加注解
@EnableZuulProxy
通過啟用網關代理服務。
@SpringBootApplication
@EnableZuulProxy
public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
啟動示例
-
啟動順序
spring-cloud-example-config
>>spring-cloud-example-eureka
>>spring-cloud-example-biz-a
/spring-cloud-example-biz-b
/spring-cloud-example-gateway
-
通過網關訪問服務A接口
服務A調用 -
通過網關訪問服務B接口
服務B調用
服務之間調用
- 在業務服務A中添加一個Feign Client Bean,參考代碼如下:
@FeignClient(name = "spring-cloud-example-biz-b") # 指定服務名稱
public interface RemoteService { /** * 調用服務B的hello方法 * * @return */ @GetMapping("/hello") #指定請求地址 String sayHello(); }
- 業務服務A示例接口類增加
call2b
接口,代碼如下:
@RestController
@RequestMapping("/hello")
public class HelloController { @Autowired private RemoteService remoteService; /** * 示例方法 * * @return */ @GetMapping public String sayHello() { return "Hello,This is Biz-A Service."; } /** * 示例方法:調用服務B * * @return */ @GetMapping(path = "/call2b") public String sayHello2B() { return remoteService.sayHello(); } }
- 重啟業務服務A,通過調用
/hello/call2b
接口:服務之間調用
下一代微服務
目前網上很多說是下一代微服務架構就是Service Mesh,Service Mesh主流框架有Linkerd和Istio,其中Istio有大廠加持所以呼聲更高。Service Mesh我接觸還不多,但是個人感覺并不一定能稱為下一代微服務架構,可能認為是服務治理的另外一種解決方案更合適,是否能夠取代當前的微服務架構還需要持續觀察。