1. 程式人生 > 其它 >微服務架構實踐

微服務架構實踐

一、微服務架構圖:

二、技術介紹:(技術選型隨著程式碼的編寫會完成)

關於技術選型,我盜了一張我老大的微服務技術棧的圖,如下:原文: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

三、基本流程:

  1. 各個服務啟動的時候,都會將自己的資訊註冊到consulClient,consulClient將註冊資訊提交給consulServer,consulServer將資訊提交給consulLeader(也是consulServer),consulLeader將自身的資料複製給其他的consulServer,服務註冊完成!!!
  2. APP發出一個對gatewayX-server的request,該請求先到nginx,nginx選出一臺gatewayX-server的伺服器進行request的處理
  3. gatewayX-server通過myserviceA-client.jar來訪問myserviceA-server的具體邏輯
    1. 首先從consulServer上拉取可用的myserviceA-server的伺服器,服務發現完成!!!
    2. 根據負載均衡策略選出其中一個伺服器來進行訪問
    3. 訪問的過程中通過熔斷器來進行超時容錯處理
  4. 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流與核心架構。這些在下一章會以程式碼的形式做展示。