微服務架構實踐
一、微服務架構圖:
二、技術介紹:(技術選型隨著程式碼的編寫會完成)
關於技術選型,我盜了一張我老大的微服務技術棧的圖,如下:原文:http://www.jianshu.com/p/2da6becfb019
我將會用到上圖中的如下技術
- 服務註冊和服務發現:consul
- 服務健康檢查:consul
- 配置管理:consul、archaius
- 叢集容錯:hystrix
- 計數監控:codahale-metrics、java-statsd-client、hystrix-dashboard、turbine、statsd、graphite、grafana
- 服務路由:ribbon
- 服務通訊:retrofit、AsyncHttpClient(不選擇okhttp,是因為okhttp效能比較差)
- 文件輸出:swagger
- 日誌統計:logback+ELK
- 簡化程式碼:lombok
- 訊息佇列:rabbitmq
- 分散式鎖:redis實現和consul實現
- 本地快取:guava cache
- 鏈路跟蹤:zipkin、brave
- 基本技術:springboot
- 安全鑑權:auth2、openId connect
- 自動化構建與部署:gitlab + jenkins + docker + k8s
三、基本流程:
- 各個服務啟動的時候,都會將自己的資訊註冊到consulClient,consulClient將註冊資訊提交給consulServer,consulServer將資訊提交給consulLeader(也是consulServer),consulLeader將自身的資料複製給其他的consulServer,服務註冊完成!!!
- APP發出一個對gatewayX-server的request,該請求先到nginx,nginx選出一臺gatewayX-server的伺服器進行request的處理
- gatewayX-server通過myserviceA-client.jar來訪問myserviceA-server的具體邏輯
- 首先從consulServer上拉取可用的myserviceA-server的伺服器,服務發現完成!!!
- 根據負載均衡策略選出其中一個伺服器來進行訪問
- 訪問的過程中通過熔斷器來進行超時容錯處理
- gatewayX-server通過myserviceB-client.jar來訪問myserviceB-server的具體邏輯同3
說明:如果僅僅只是前邊這樣的流程或者以前邊這樣的流程為基礎並且myserviceB-server要呼叫myserviceA-server,那麼上圖中的myserviceB-server中的整個myserviceA-client.jar可以去掉,原因是gatewayX-server已經引入了myserviceA-client.jar。
如果不是上邊的流程,只是單純的myserviceB-server要訪問myserviceA-server,那麼需要引入myserviceA-client.jar。
注意:對於服務發現而言,consulServer會通過gossip協議將伺服器資料廣播給各個本地consul agent(通常是consulClient),所以我們不需要做本地快取,當被呼叫服務的伺服器列表發生改變時,會馬上廣播給consulClient。
第二章 微服務架構搭建 + 服務啟動註冊
一、首先編寫微服務基礎專案framework
1、pom.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4 5 <modelVersion>4.0.0</modelVersion> 6 7 <parent> 8 <groupId>org.springframework.boot</groupId> 9 <artifactId>spring-boot-starter-parent</artifactId> 10 <version>1.3.0.RELEASE</version> 11 </parent> 12 13 <groupId>com.microservice</groupId> 14 <artifactId>framework</artifactId> 15 <version>1.0-SNAPSHOT</version> 16 <packaging>jar</packaging> 17 18 <properties> 19 <java.version>1.8</java.version><!-- 官方推薦 --> 20 </properties> 21 22 <!-- 引入實際依賴 --> 23 <dependencies> 24 <dependency> 25 <groupId>org.springframework.boot</groupId> 26 <artifactId>spring-boot-starter-web</artifactId> 27 </dependency> 28 <!-- consul-client --> 29 <dependency> 30 <groupId>com.orbitz.consul</groupId> 31 <artifactId>consul-client</artifactId> 32 <version>0.10.0</version> 33 </dependency> 34 <!-- consul需要的包 --> 35 <dependency> 36 <groupId>org.glassfish.jersey.core</groupId> 37 <artifactId>jersey-client</artifactId> 38 <version>2.22.2</version> 39 </dependency> 40 <dependency> 41 <groupId>com.alibaba</groupId> 42 <artifactId>fastjson</artifactId> 43 <version>1.1.15</version> 44 </dependency> 45 <dependency> 46 <groupId>org.springframework.boot</groupId> 47 <artifactId>spring-boot-starter-actuator</artifactId> 48 </dependency> 49 <dependency> 50 <groupId>org.projectlombok</groupId> 51 <artifactId>lombok</artifactId> 52 <version>1.16.8</version> 53 <scope>provided</scope> 54 </dependency> 55 </dependencies> 56 57 <build> 58 <plugins> 59 <plugin> 60 <groupId>org.springframework.boot</groupId> 61 <artifactId>spring-boot-maven-plugin</artifactId> 62 </plugin> 63 </plugins> 64 </build> 65 </project>
說明:
- 上邊的<packaging>jar</packaging>可以去掉。因為spring-boot-maven-plugin會打jar包的
- 引入spring-boot-starter-actuator是為了註冊服務的時候可以直接使用"http://localhost:8080/health"進行健康檢查。見第二十章 springboot + consul
- 注意:health的port不是固定的8080,而是服務啟動的介面,如果服務是以8090啟動,使用"http://localhost:8090/health"來檢查
2、com.microservice.framework.MySpringAplication
1 package com.microservice.framework; 2 3 import org.springframework.boot.SpringApplication; 4 import org.springframework.boot.autoconfigure.SpringBootApplication; 5 6 import com.microservice.framework.consul.ConsulRegisterListener; 7 8 /** 9 * 注意:@SpringBootApplication該註解必須在SpringApplication.run()所在的類上 10 * 11 */ 12 @SpringBootApplication 13 public class MySpringAplication { 14 15 public void run(String[] args) { 16 SpringApplication sa = new SpringApplication(MySpringAplication.class); 17 sa.addListeners(new ConsulRegisterListener()); 18 sa.run(args); 19 } 20 21 public static void main(String[] args) { 22 } 23 }
注意:這裡的main方法宣告是要有的(否則無法install為jar)。
3、com.microservice.framework.consul.ConsulRegisterListener
1 package com.microservice.framework.consul; 2 3 import java.net.MalformedURLException; 4 import java.net.URI; 5 6 import org.springframework.context.ApplicationListener; 7 import org.springframework.context.event.ContextRefreshedEvent; 8 9 import com.orbitz.consul.AgentClient; 10 import com.orbitz.consul.Consul; 11 12 /** 13 * 監聽contextrefresh事件 14 */ 15 public class ConsulRegisterListener implements ApplicationListener<ContextRefreshedEvent> { 16 17 @Override 18 public void onApplicationEvent(ContextRefreshedEvent event) { 19 Consul consul = event.getApplicationContext().getBean(Consul.class); 20 ConsulProperties prop = event.getApplicationContext().getBean(ConsulProperties.class); 21 22 AgentClient agentClient = consul.agentClient(); 23 try { 24 agentClient.register(prop.getServicePort(), 25 URI.create(prop.getHealthUrl()).toURL(), 26 prop.getHealthInterval(), 27 prop.getServicename(), 28 prop.getServicename(), // serviceId: 29 prop.getServiceTag()); 30 } catch (MalformedURLException e) { 31 e.printStackTrace(); 32 } 33 } 34 35 }
注意:這個程式碼是關鍵,後邊會講改程式碼的作用。
其中,ConsulProperties和Consul我們需要在程式碼中構建成Bean(如下變4和5),之後才能從容器中取出來,否則為null。
4、com.microservice.framework.consul.ConsulProperties
1 package com.microservice.framework.consul; 2 3 import org.springframework.beans.factory.annotation.Value; 4 import org.springframework.stereotype.Component; 5 6 import lombok.Getter; 7 import lombok.Setter; 8 9 @Component 10 @Getter @Setter 11 public class ConsulProperties { 12 13 @Value("${service.name}") 14 private String servicename; 15 @Value("${service.port:8080}") 16 private int servicePort; 17 @Value("${service.tag:dev}") 18 private String serviceTag; 19 // @Value("${serviceIp:localhost}") 20 // private String serviceIp; 21 22 @Value("${health.url}") 23 private String healthUrl; 24 @Value("${health.interval:10}") 25 private int healthInterval; 26 27 }
注意:
- 這裡使用lombok簡化了pojo
- @value註解中可以指定預設值,檢視上邊":"後邊的值就是
5、com.microservice.framework.consul.ConsulConfig
1 package com.microservice.framework.consul; 2 3 import org.springframework.context.annotation.Bean; 4 import org.springframework.context.annotation.Configuration; 5 6 import com.orbitz.consul.Consul; 7 8 @Configuration 9 public class ConsulConfig { 10 11 @Bean 12 public Consul consul(){ 13 return Consul.builder().build(); 14 } 15 }
編寫完上述程式碼後,執行"mvn clean install",如果成功的話,此時"framework-1.0-SNAPSHOT.jar"這個jar就會裝載到本地的.m2/repository/com/microservice/framework/q.0-SNAPSHOT中了(mac中.m2預設在~下)
二、開發第一個微服務myserviceA
像上邊所示,我們建立了client和server。
- server:用於實現具體邏輯
- client:用於封裝server介面(通常就是server模組的controller中的各個url),提供給其他service或gateway甚至是app使用
1、myserviceA
pom.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4 5 <modelVersion>4.0.0</modelVersion> 6 7 <parent> 8 <groupId>org.springframework.boot</groupId> 9 <artifactId>spring-boot-starter-parent</artifactId> 10 <version>1.3.0.RELEASE</version> 11 </parent> 12 13 <groupId>com.microservice</groupId> 14 <artifactId>myserviceA</artifactId> 15 <version>1.0-SNAPSHOT</version> 16 <packaging>pom</packaging> 17 18 <properties> 19 <java.version>1.8</java.version><!-- 官方推薦 --> 20 </properties> 21 22 <modules> 23 <module>server</module> 24 <module>client</module> 25 </modules> 26 27 <!-- 引入實際依賴 --> 28 <dependencies> 29 <dependency> 30 <groupId>org.springframework.boot</groupId> 31 <artifactId>spring-boot-starter-web</artifactId> 32 </dependency> 33 </dependencies> 34 </project>
2、myserviceA-server
2.1、pom.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4 5 <modelVersion>4.0.0</modelVersion> 6 7 <parent> 8 <groupId>com.microservice</groupId> 9 <artifactId>myserviceA</artifactId> 10 <version>1.0-SNAPSHOT</version> 11 </parent> 12 13 <artifactId>myserviceA-server</artifactId> 14 15 <!-- 引入實際依賴 --> 16 <dependencies> 17 <dependency> 18 <groupId>com.microservice</groupId> 19 <artifactId>framework</artifactId> 20 <version>1.0-SNAPSHOT</version> 21 </dependency> 22 <dependency> 23 <groupId>com.alibaba</groupId> 24 <artifactId>fastjson</artifactId> 25 <version>1.1.15</version> 26 </dependency> 27 </dependencies> 28 29 <build> 30 <plugins> 31 <plugin> 32 <groupId>org.springframework.boot</groupId> 33 <artifactId>spring-boot-maven-plugin</artifactId> 34 </plugin> 35 </plugins> 36 </build> 37 </project>
2.2、application.properties
1 service.name=myserviceA 2 service.port=8080 3 service.tag=dev 4 health.url=http://localhost:8080/health 5 health.interval=10
說明:
- service.name(這是一個service在註冊中心的唯一標識)
- service.port
- service.tag(該值用於在註冊中心的配置管理,dev環境下使用dev的配置,prod下使用prod的配置,配置管理通常使用KV來實現的,tag用於構建Key)
- health.url(健康檢查的url)
- health.interval(每隔10s ping一次health.url,進行健康檢查)
2.3、com.microservice.myserviceA.MyServiceAApplication
1 package com.microservice.myserviceA; 2 3 import org.springframework.boot.autoconfigure.SpringBootApplication; 4 5 import com.microservice.framework.MySpringAplication; 6 7 @SpringBootApplication 8 public class MyServiceAApplication { 9 10 public static void main(String[] args) { 11 MySpringAplication mySpringAplication = new MySpringAplication(); 12 mySpringAplication.run(args); 13 } 14 }
說明:這裡呼叫了framework中的MySpringAplication的run(),該run()首先初始化了SpringApplication例項,之後為該例項新增ConsulRegisterListener例項,最後再執行SpringApplication的run()。
ConsulRegisterListener的執行時機見附4 springboot原始碼解析-run(),簡言之,就是
- run()方法會先構建容器ApplicationContext,之後將各個BeanDefinition裝入該容器,最後重新整理容器,這時候執行ConsulRegisterListener中的onApplication方法,用於註冊service到consul。
3、myserviceA-client
pom.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4 5 <modelVersion>4.0.0</modelVersion> 6 7 <parent> 8 <groupId>com.microservice</groupId> 9 <artifactId>myserviceA</artifactId> 10 <version>1.0-SNAPSHOT</version> 11 </parent> 12 13 <artifactId>myserviceA-client</artifactId> 14 15 <build> 16 <plugins> 17 <plugin> 18 <groupId>org.springframework.boot</groupId> 19 <artifactId>spring-boot-maven-plugin</artifactId> 20 </plugin> 21 </plugins> 22 </build> 23 </project>
該client以後在需要用到的時候完成。
測試:啟動consul,開發環境下,直接使用"consul agent -dev"快速啟動,檢視consul UI,如下:
啟動"myserviceA-server",啟動完成後,檢視consul UI,如下:
表示註冊成功,我們還可以檢視myserviceA的健康檢查URL,如下:
以上就完成了基本微服務架構的搭建與服務啟動時自動註冊!
一、首先編寫微服務基礎專案framework
1、pom.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4 5 <modelVersion>4.0.0</modelVersion> 6 7 <parent> 8 <groupId>org.springframework.boot</groupId> 9 <artifactId>spring-boot-starter-parent</artifactId> 10 <version>1.3.0.RELEASE</version> 11 </parent> 12 13 <groupId>com.microservice</groupId> 14 <artifactId>framework</artifactId> 15 <version>1.0-SNAPSHOT</version> 16 <packaging>jar</packaging> 17 18 <properties> 19 <java.version>1.8</java.version><!-- 官方推薦 --> 20 </properties> 21 22 <!-- 引入實際依賴 --> 23 <dependencies> 24 <dependency> 25 <groupId>org.springframework.boot</groupId> 26 <artifactId>spring-boot-starter-web</artifactId> 27 </dependency> 28 <!-- consul-client --> 29 <dependency> 30 <groupId>com.orbitz.consul</groupId> 31 <artifactId>consul-client</artifactId> 32 <version>0.10.0</version> 33 </dependency> 34 <!-- consul需要的包 --> 35 <dependency> 36 <groupId>org.glassfish.jersey.core</groupId> 37 <artifactId>jersey-client</artifactId> 38 <version>2.22.2</version> 39 </dependency> 40 <dependency> 41 <groupId>com.alibaba</groupId> 42 <artifactId>fastjson</artifactId> 43 <version>1.1.15</version> 44 </dependency> 45 <dependency> 46 <groupId>org.springframework.boot</groupId> 47 <artifactId>spring-boot-starter-actuator</artifactId> 48 </dependency> 49 <dependency> 50 <groupId>org.projectlombok</groupId> 51 <artifactId>lombok</artifactId> 52 <version>1.16.8</version> 53 <scope>provided</scope> 54 </dependency> 55 </dependencies> 56 57 <build> 58 <plugins> 59 <plugin> 60 <groupId>org.springframework.boot</groupId> 61 <artifactId>spring-boot-maven-plugin</artifactId> 62 </plugin> 63 </plugins> 64 </build> 65 </project>
說明:
- 上邊的<packaging>jar</packaging>可以去掉。因為spring-boot-maven-plugin會打jar包的
- 引入spring-boot-starter-actuator是為了註冊服務的時候可以直接使用"http://localhost:8080/health"進行健康檢查。見第二十章 springboot + consul
- 注意:health的port不是固定的8080,而是服務啟動的介面,如果服務是以8090啟動,使用"http://localhost:8090/health"來檢查
2、com.microservice.framework.MySpringAplication
1 package com.microservice.framework; 2 3 import org.springframework.boot.SpringApplication; 4 import org.springframework.boot.autoconfigure.SpringBootApplication; 5 6 import com.microservice.framework.consul.ConsulRegisterListener; 7 8 /** 9 * 注意:@SpringBootApplication該註解必須在SpringApplication.run()所在的類上 10 * 11 */ 12 @SpringBootApplication 13 public class MySpringAplication { 14 15 public void run(String[] args) { 16 SpringApplication sa = new SpringApplication(MySpringAplication.class); 17 sa.addListeners(new ConsulRegisterListener()); 18 sa.run(args); 19 } 20 21 public static void main(String[] args) { 22 } 23 }
注意:這裡的main方法宣告是要有的(否則無法install為jar)。
3、com.microservice.framework.consul.ConsulRegisterListener
1 package com.microservice.framework.consul; 2 3 import java.net.MalformedURLException; 4 import java.net.URI; 5 6 import org.springframework.context.ApplicationListener; 7 import org.springframework.context.event.ContextRefreshedEvent; 8 9 import com.orbitz.consul.AgentClient; 10 import com.orbitz.consul.Consul; 11 12 /** 13 * 監聽contextrefresh事件 14 */ 15 public class ConsulRegisterListener implements ApplicationListener<ContextRefreshedEvent> { 16 17 @Override 18 public void onApplicationEvent(ContextRefreshedEvent event) { 19 Consul consul = event.getApplicationContext().getBean(Consul.class); 20 ConsulProperties prop = event.getApplicationContext().getBean(ConsulProperties.class); 21 22 AgentClient agentClient = consul.agentClient(); 23 try { 24 agentClient.register(prop.getServicePort(), 25 URI.create(prop.getHealthUrl()).toURL(), 26 prop.getHealthInterval(), 27 prop.getServicename(), 28 prop.getServicename(), // serviceId: 29 prop.getServiceTag()); 30 } catch (MalformedURLException e) { 31 e.printStackTrace(); 32 } 33 } 34 35 }
注意:這個程式碼是關鍵,後邊會講改程式碼的作用。
其中,ConsulProperties和Consul我們需要在程式碼中構建成Bean(如下變4和5),之後才能從容器中取出來,否則為null。
4、com.microservice.framework.consul.ConsulProperties
1 package com.microservice.framework.consul; 2 3 import org.springframework.beans.factory.annotation.Value; 4 import org.springframework.stereotype.Component; 5 6 import lombok.Getter; 7 import lombok.Setter; 8 9 @Component 10 @Getter @Setter 11 public class ConsulProperties { 12 13 @Value("${service.name}") 14 private String servicename; 15 @Value("${service.port:8080}") 16 private int servicePort; 17 @Value("${service.tag:dev}") 18 private String serviceTag; 19 // @Value("${serviceIp:localhost}") 20 // private String serviceIp; 21 22 @Value("${health.url}") 23 private String healthUrl; 24 @Value("${health.interval:10}") 25 private int healthInterval; 26 27 }
注意:
- 這裡使用lombok簡化了pojo
- @value註解中可以指定預設值,檢視上邊":"後邊的值就是
5、com.microservice.framework.consul.ConsulConfig
1 package com.microservice.framework.consul; 2 3 import org.springframework.context.annotation.Bean; 4 import org.springframework.context.annotation.Configuration; 5 6 import com.orbitz.consul.Consul; 7 8 @Configuration 9 public class ConsulConfig { 10 11 @Bean 12 public Consul consul(){ 13 return Consul.builder().build(); 14 } 15 }
編寫完上述程式碼後,執行"mvn clean install",如果成功的話,此時"framework-1.0-SNAPSHOT.jar"這個jar就會裝載到本地的.m2/repository/com/microservice/framework/q.0-SNAPSHOT中了(mac中.m2預設在~下)
二、開發第一個微服務myserviceA
像上邊所示,我們建立了client和server。
- server:用於實現具體邏輯
- client:用於封裝server介面(通常就是server模組的controller中的各個url),提供給其他service或gateway甚至是app使用
1、myserviceA
pom.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4 5 <modelVersion>4.0.0</modelVersion> 6 7 <parent> 8 <groupId>org.springframework.boot</groupId> 9 <artifactId>spring-boot-starter-parent</artifactId> 10 <version>1.3.0.RELEASE</version> 11 </parent> 12 13 <groupId>com.microservice</groupId> 14 <artifactId>myserviceA</artifactId> 15 <version>1.0-SNAPSHOT</version> 16 <packaging>pom</packaging> 17 18 <properties> 19 <java.version>1.8</java.version><!-- 官方推薦 --> 20 </properties> 21 22 <modules> 23 <module>server</module> 24 <module>client</module> 25 </modules> 26 27 <!-- 引入實際依賴 --> 28 <dependencies> 29 <dependency> 30 <groupId>org.springframework.boot</groupId> 31 <artifactId>spring-boot-starter-web</artifactId> 32 </dependency> 33 </dependencies> 34 </project>
2、myserviceA-server
2.1、pom.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4 5 <modelVersion>4.0.0</modelVersion> 6 7 <parent> 8 <groupId>com.microservice</groupId> 9 <artifactId>myserviceA</artifactId> 10 <version>1.0-SNAPSHOT</version> 11 </parent> 12 13 <artifactId>myserviceA-server</artifactId> 14 15 <!-- 引入實際依賴 --> 16 <dependencies> 17 <dependency> 18 <groupId>com.microservice</groupId> 19 <artifactId>framework</artifactId> 20 <version>1.0-SNAPSHOT</version> 21 </dependency> 22 <dependency> 23 <groupId>com.alibaba</groupId> 24 <artifactId>fastjson</artifactId> 25 <version>1.1.15</version> 26 </dependency> 27 </dependencies> 28 29 <build> 30 <plugins> 31 <plugin> 32 <groupId>org.springframework.boot</groupId> 33 <artifactId>spring-boot-maven-plugin</artifactId> 34 </plugin> 35 </plugins> 36 </build> 37 </project>
2.2、application.properties
1 service.name=myserviceA 2 service.port=8080 3 service.tag=dev 4 health.url=http://localhost:8080/health 5 health.interval=10
說明:
- service.name(這是一個service在註冊中心的唯一標識)
- service.port
- service.tag(該值用於在註冊中心的配置管理,dev環境下使用dev的配置,prod下使用prod的配置,配置管理通常使用KV來實現的,tag用於構建Key)
- health.url(健康檢查的url)
- health.interval(每隔10s ping一次health.url,進行健康檢查)
2.3、com.microservice.myserviceA.MyServiceAApplication
1 package com.microservice.myserviceA; 2 3 import org.springframework.boot.autoconfigure.SpringBootApplication; 4 5 import com.microservice.framework.MySpringAplication; 6 7 @SpringBootApplication 8 public class MyServiceAApplication { 9 10 public static void main(String[] args) { 11 MySpringAplication mySpringAplication = new MySpringAplication(); 12 mySpringAplication.run(args); 13 } 14 }
說明:這裡呼叫了framework中的MySpringAplication的run(),該run()首先初始化了SpringApplication例項,之後為該例項新增ConsulRegisterListener例項,最後再執行SpringApplication的run()。
ConsulRegisterListener的執行時機見附4 springboot原始碼解析-run(),簡言之,就是
- run()方法會先構建容器ApplicationContext,之後將各個BeanDefinition裝入該容器,最後重新整理容器,這時候執行ConsulRegisterListener中的onApplication方法,用於註冊service到consul。
3、myserviceA-client
pom.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4 5 <modelVersion>4.0.0</modelVersion> 6 7 <parent> 8 <groupId>com.microservice</groupId> 9 <artifactId>myserviceA</artifactId> 10 <version>1.0-SNAPSHOT</version> 11 </parent> 12 13 <artifactId>myserviceA-client</artifactId> 14 15 <build> 16 <plugins> 17 <plugin> 18 <groupId>org.springframework.boot</groupId> 19 <artifactId>spring-boot-maven-plugin</artifactId> 20 </plugin> 21 </plugins> 22 </build> 23 </project>
該client以後在需要用到的時候完成。
測試:啟動consul,開發環境下,直接使用"consul agent -dev"快速啟動,檢視consul UI,如下:
啟動"myserviceA-server",啟動完成後,檢視consul UI,如下:
表示註冊成功,我們還可以檢視myserviceA的健康檢查URL,如下:
以上就完成了基本微服務架構的搭建與服務啟動時自動註冊!
第一章 微服務閘道器 - 入門
一、什麼是服務閘道器
服務閘道器 = 路由轉發 + 過濾器
1、路由轉發:接收一切外界請求,轉發到後端的微服務上去;
2、過濾器:在服務閘道器中可以完成一系列的橫切功能,例如許可權校驗、限流以及監控等,這些都可以通過過濾器完成(其實路由轉發也是通過過濾器實現的)。
二、為什麼需要服務閘道器
上述所說的橫切功能(以許可權校驗為例)可以寫在三個位置:
- 每個服務自己實現一遍
- 寫到一個公共的服務中,然後其他所有服務都依賴這個服務
- 寫到服務閘道器的前置過濾器中,所有請求過來進行許可權校驗
第一種,缺點太明顯,基本不用;
第二種,相較於第一點好很多,程式碼開發不會冗餘,但是有兩個缺點:
- 由於每個服務引入了這個公共服務,那麼相當於在每個服務中都引入了相同的許可權校驗的程式碼,使得每個服務的jar包大小無故增加了一些,尤其是對於使用docker映象進行部署的場景,jar越小越好;
- 由於每個服務都引入了這個公共服務,那麼我們後續升級這個服務可能就比較困難,而且公共服務的功能越多,升級就越難,而且假設我們改變了公共服務中的許可權校驗的方式,想讓所有的服務都去使用新的許可權校驗方式,我們就需要將之前所有的服務都重新引包,編譯部署。
而服務閘道器恰好可以解決這樣的問題:
- 將許可權校驗的邏輯寫在閘道器的過濾器中,後端服務不需要關注許可權校驗的程式碼,所以服務的jar包中也不會引入許可權校驗的邏輯,不會增加jar包大小;
- 如果想修改許可權校驗的邏輯,只需要修改閘道器中的許可權校驗過濾器即可,而不需要升級所有已存在的微服務。
所以,需要服務閘道器!!!
三、服務閘道器技術選型
引入服務閘道器後的微服務架構如上,總體包含三部分:服務閘道器、open-service和service。
1、總體流程:
- 服務閘道器、open-service和service啟動時註冊到註冊中心上去;
- 使用者請求時直接請求閘道器,閘道器做智慧路由轉發(包括服務發現,負載均衡)到open-service,這其中包含許可權校驗、監控、限流等操作
- open-service聚合內部service響應,返回給閘道器,閘道器再返回給使用者
2、引入閘道器的注意點
- 增加了閘道器,多了一層轉發(原本使用者請求直接訪問open-service即可),效能會下降一些(但是下降不大,通常,閘道器機器效能會很好,而且閘道器與open-service的訪問通常是內網訪問,速度很快);
- 閘道器的單點問題:在整個網路呼叫過程中,一定會有一個單點,可能是閘道器、nginx、dns伺服器等。防止閘道器單點,可以在閘道器層前邊再掛一臺nginx,nginx的效能極高,基本不會掛,這樣之後,閘道器服務就可以不斷的新增機器。但是這樣一個請求就轉發了兩次,所以最好的方式是閘道器單點服務部署在一臺牛逼的機器上(通過壓測來估算機器的配置),而且nginx與zuul的效能比較,根據國外的一個哥們兒做的實驗來看,其實相差不大,zuul是netflix開源的一個用來做閘道器的開源框架;
- 閘道器要儘量輕。
3、服務閘道器基本功能
- 智慧路由:接收外部一切請求,並轉發到後端的對外服務open-service上去;
- 注意:我們只轉發外部請求,服務之間的請求不走閘道器,這就表示全鏈路追蹤、內部服務API監控、內部服務之間呼叫的容錯、智慧路由不能在閘道器完成;當然,也可以將所有的服務呼叫都走閘道器,那麼幾乎所有的功能都可以整合到閘道器中,但是這樣的話,閘道器的壓力會很大,不堪重負。
- 許可權校驗:只校驗使用者向open-service服務的請求,不校驗服務內部的請求。服務內部的請求有必要校驗嗎?
- API監控:只監控經過閘道器的請求,以及閘道器本身的一些效能指標(例如,gc等);
- 限流:與監控配合,進行限流操作;
- API日誌統一收集:類似於一個aspect切面,記錄介面的進入和出去時的相關日誌
- 。。。後續補充
上述功能是閘道器的基本功能,閘道器還可以實現以下功能:
- A|B測試:A|B測試時一塊比較大的東西,包含後臺實驗配置、資料埋點(看轉化率)以及分流引擎,在服務閘道器中,可以實現分流引擎,但是實際上分流引擎會呼叫內部服務,所以如果是按照上圖的架構,分流引擎最好做在open-service中,不要做在服務閘道器中。
- 。。。後續補充
4、技術選型
筆者準備自建一個輕量級的服務閘道器,技術選型如下:
- 開發語言:java + groovy,groovy的好處是閘道器服務不需要重啟就可以動態的新增filter來實現一些功能;
- 微服務基礎框架:springboot;
- 閘道器基礎元件:netflix zuul;
- 服務註冊中心:consul;
- 許可權校驗:jwt;
- API監控:prometheus + grafana;
- API統一日誌收集:logback + ELK;
- 壓力測試:Jmeter;
- 。。。後續補充
第二章 微服務閘道器基礎元件 - zuul入門
1、作用
zuul使用一系列的filter實現以下功能
- 認證和安全 - 對每一個resource進行身份認證
- 追蹤和監控 - 實時觀察後端微服務的TPS、響應時間,失敗數量等準確的資訊
- 日誌 - 記錄所有請求的訪問日誌資料,可以為日誌分析和查詢提供統一支援
- 動態路由 - 動態的將request路由到後端的服務上去
- 壓力測試 - 逐漸的增加訪問叢集的壓力,來測試叢集的效能
- 限流 - allocating capacity for each type of request and dropping requests that go over the limit
- 靜態響應 - 直接在閘道器返回一些響應,而不是通過內部的服務返回響應
2、元件:
- zuul-core:library which contains the core functionality of compiling and executing Filters
- zuul-netflix:library which adds other NetflixOSS components to Zuul - using Ribbon for routing requests, for example.
3、例子:
- zuul-simple-webapp:webapp which shows a simple example of how to build an application with zuul-core
- zuul-netflix-webapp:webapp which packages zuul-core and zuul-netflix together into an easy to use package
github地址:https://github.com/Netflix/zuul/
二、zuul filter
1、關鍵元素
- Type:most often defines the stage during the routing flow when the Filter will be applied (although it can be any custom string)
- 值可以是:pre、route、post、error、custom
- Execution Order: filter執行的順序(applied within the Type, defines the order of execution across multiple Filters)
- Criteria:filter執行的條件(the conditions required in order for the Filter to be executed)
- Action: filter執行的動作(the action to be executed if the Criteria is met)
注意點:
- filters之間不會直接進行通訊交流,他們通過一個RequestContext共享一個state
- 該RequestContext對於每一個request都是唯一的
- filter當前使用groovy來寫的,也可以使用java
- The source code for each Filter is written to a specified set of directories on the Zuul server that are periodically polled for changes
- zuul可以動態的read, compile, and run these Filters
- 被更新後的filter會被從disk讀取到記憶體,並動態編譯到正在執行的server中,之後可以用於其後的每一個請求(Updated filters are read from disk, dynamically compiled into the running server, and are invoked by Zuul for each subsequent request)
2、filter type(與一個典型的request的生命週期相關的filter type)
-
PRE Filters
- 執行時機: before routing to the origin.
- 這類filter可能做的事
- request authentication
- choosing origin servers(選機器)
- logging debug info.
- 限流
- ROUTING Filters
- 這類filter可能做的事:真正的向service的一臺server(這臺server是pre filter選出來的)發請求,handle routing the request to an origin,This is where the origin HTTP request is built and sent using Apache HttpClient or Netflix Ribbon.
-
POST Filters
- 執行時機:after the request has been routed to the origin
- 這類filter可能做的事
- adding standard HTTP headers to the response
- gathering statistics and metrics
- streaming the response from the origin to the client
-
ERROR Filters
- 執行時機:其他三個階段任一階段發生錯誤時執行(when an error occurs during one of the other phases)
-
CUSTOM Filters
- 沿著預設的filter流,zuul允許我們建立一些自定義的Filter type,並且準確的執行他們。
- 例如:我們自定義一個STATIC type的filter,用於從zuul直接產生響應,而不是從後邊的services(we have a custom STATIC type that generates a response within Zuul instead of forwarding the request to an origin)
三、zuul request lifecycle(filter流)
說明:對應(二)的filter type來看
四、zuul核心架構
zuul的核心就是:filter、filter流與核心架構。這些在下一章會以程式碼的形式做展示。