實戰順序
- 1. 搭建 spring cloud 微服務腳手架
- 2. 基本概念
- 3. 進入實戰
- 3.1 創建項目
- 3.2 搭建 eureka 註冊中心
- 3.3 搭建生產者服務
- 3.4 搭建消費者服務
- 3.4.1 設置依賴
- 3.4.2 創建啓動類
- 3.4.3 創建 `application.yml` 配置文件
- 3.4.4 創建 Feign 客戶端調用 Provider 服務
- 3.4.5 創建 ConsumerController 查看調用的內容
- 3.4.6 啓動應用
- 3.5 消費者配置 Hystrix 熔斷
- 3.5.1 修改 `msk-consumer` 中的配置
- 3.5.2 增加熔斷處理類`ConsumerServiceFallback`
- 3.5.3 在 FeignClient 接口中增加 Fallback 配置
- 3.5.4 重啓服務
- 3.6 消費者配置熔斷監控頁面
- 3.7 搭建 API 網關-Zuul
- 3.8 搭建Config配置中心
- 3.9 搭建 SpringBootAdmin 服務監控中心
- 3.10 搭建 Zipkin 鏈路追蹤
- 4. 聯繫
1. 搭建 spring cloud 微服務腳手架
最近從事了一個 spring cloud 的微服務項目,整個微服務平臺的構建都是自己調研的。。。現在重構了一個基本的微服務腳手架,一方面是想鞏固一下知識,另一方面也是爲了在後面的項目中直接使用。
這裏面不僅有基本的理念,還加入了很多實踐經驗(避免很多人都會踩的坑),比理論上的更好理解,非常適合新手學習。
項目 Github 地址:https://github.com/fishdemon/micro-service-kit
在項目的 oauth 分支有關於 Oauth2+SSO 實現微服務統一鑑權中心的例子,是經過項目實踐驗證的,之後我會專門寫一篇文章來介紹,如果有興趣可以先行閱讀源碼
歡迎下載下來一起學習交流,如果覺得還可以,還請加 Star 哦!!!
2. 基本概念
先上一張微服務的架構圖,之後在補充詳細內容
3. 進入實戰
3.1 創建項目
- 用 IDEA + Gradle 創建一個項目,並在項目下創建 8 個 Module , 如下所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-RsBraP4b-1590933150543)(assets/1585374948461.png)]
msk-auth
msk-boot-admin
msk-config
msk-consumer
msk-eureka
msk-gateway
msk-provider
msk-zipkin
- 配置項目根目錄下的 build.gradle,先設定一些公共的依賴及屬性。
之後所有子module 的依賴也在這個根 build.gradle 進行配置,而 module 中自帶的 build.gradle 暫時不用。這種集中配置有一個很大的好處,就是修改起來特別方便,對於目前的項目來說很好用。。。當然也有缺點,如果子模塊由不同的開發者同時維護,那就不適合這種模式。
build.gradle
plugins {
id 'java'
id 'idea'
id 'org.springframework.boot' version '2.2.5.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
}
// set dependencies of all projects
allprojects {
apply plugin: 'idea'
apply plugin: 'java'
group 'com.fishdemon.msk'
version '1.0'
sourceCompatibility = 1.8
repositories {
maven{ url 'https://maven.aliyun.com/repository/jcenter'}
maven{ url 'https://maven.aliyun.com/repository/gradle-plugin'}
maven{ url 'https://maven.aliyun.com/repository/public'}
mavenCentral()
}
}
// set dependencies of sub projects
subprojects {
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
ext {
set('springBootAdminVersion', "2.2.1")
set('springCloudVersion', "Hoxton.SR3")
}
dependencies {
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
runtimeOnly 'org.springframework.boot:spring-boot-devtools'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
dependencyManagement {
imports {
mavenBom "de.codecentric:spring-boot-admin-dependencies:${springBootAdminVersion}"
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
test {
useJUnitPlatform()
}
}
3.2 搭建 eureka 註冊中心
3.2.1 設置依賴
根 build.gradle
project(':msk-eureka') {
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'
}
}
3.2.2 創建啓動類
進入到 msk-eureka
模塊中,Eureka 的啓動類很簡單,只需要加一個 @EnableEurekaServer 註解可以了
@SpringBootApplication
@EnableEurekaServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3.2.3 創建 application.yml
配置文件
爲了簡單起見,這裏先設置一個單節點的 Eureka 服務,若後面想實現高可用,可參考文章
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
client:
# 單機模式下由於自己就是服務器,不需要註冊到自己
register-with-eureka: false
# 單機模式下由於自己就是服務器,不需要從服務器獲取註冊信息
fetch-registry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
instance:
# 強烈建議不要用 localhost,否則在分佈式部署的時候一大堆坑
hostname: msk-eureka
# 租期更新時間間隔(默認30秒)
lease-renewal-interval-in-seconds: 10
# 租期到期時間(默認90秒)
lease-expiration-duration-in-seconds: 30
server:
# 關閉自我保護(AP),默認是 true
enable-self-preservation: false
# 清理間隔,默認是 60 * 1000 ms
eviction-interval-timer-in-ms: 4000
Eureka 節點註冊有兩種方案:域名 或 IP地址,如果沒有指定域名,默認用 localhost
。
在這裏我們用自定義域名來演示,這樣系統的移植性比較好,只需要在 hosts 或者 域名服務器 中配置域名映射就可以了。修改系統的 hosts 文件:
127.0.0.1 msk-eureka
3.3 搭建生產者服務
3.3.1 設置依賴
根 build.gradle
project(':msk-provider') {
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
implementation 'org.springframework.boot:spring-boot-starter-web'
}
}
3.3.2 創建啓動類
進入到子模塊msk-provider
中,只需要在啓動類上添加註解 @EnableEurekaClient
就可以,表明該服務是Eureka 的一個服務提供方。
package com.fishdemon.msk.provider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3.3.3 創建 application.yml
配置文件
如果不使用 docker 部署的話,服務一般使用 ip 地址註冊進行比較好,否則分佈式部署就要配置很多域名了。
在很多博客文章中,爲了簡單的演示都是用localhost
來註冊服務,並沒有說明限制,但對於新手來說,這是很坑的。。。博主當時也是在分佈式部署時踩了很多 localhsot
的坑,別提多難受了。
server:
port: 8781
servlet:
context-path: /provider
eureka:
instance:
# 強制使用IP地址進行註冊
prefer-ip-address: true
# 設置客戶端實例id, 若使用IP地址註冊,這個配置必須有,否則會顯示成 localhost
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
service-url:
# 註冊服務到eureka上
defaultZone: http://msk-eureka:8761/eureka
spring:
application:
name: msk-provider
3.3.4 提供 Controller 服務
package com.fishdemon.msk.provider.controller;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("hello")
public class ProviderController {
@GetMapping
public String hello() {
return "hello, i'm provider";
}
// 測試 feign 中 path variable 的用法
@GetMapping("/{id}")
public String getById(@PathVariable("id") String id) {
return "hello, you get the provider " + id;
}
// 測試 feign 中 request param 的用法
@GetMapping("/user")
public String getByName(@RequestParam("name") String name) {
return "hello, you get the user " + name;
}
}
3.3.5 啓動生產者服務
- 查看 eureka 頁面 http://localhost:8761
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-YGmNbqFW-1590933150550)(assets/1585378370025.png)]
- 調用 API,訪問 http://localhost:8781/provider/hello
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-nlqj9dxI-1590933150558)(assets/1585378394270.png)]
3.4 搭建消費者服務
3.4.1 設置依賴
根 build.gradle
project(':msk-consumer') {
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
implementation 'org.springframework.boot:spring-boot-starter-web'
}
}
3.4.2 創建啓動類
進入到子模塊 msk-consumer
中 , 加入 @EnableEurekaClient
註解表明這是一個 eureka 客戶端,同時加入 @EnableFeignClients
註解表明使用 Feign 客戶端來調用其他服務的API。
package com.fishdemon.msk.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3.4.3 創建 application.yml
配置文件
server:
port: 8782
servlet:
context-path: /consumer
eureka:
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
service-url:
defaultZone: http://msk-eureka:8761/eureka
spring:
application:
name: msk-consumer
3.4.4 創建 Feign 客戶端調用 Provider 服務
Feign 是一種聲明式的服務調用,使用方式非常簡單。只需創建一個接口,並通過註解註明 服務名稱 , 調用的URL,調用參數 即可,使用的都是 WebMVC 已有的註解。它底層仍是使用 RestTemplate
和 Ribbon
來實現的。
package com.fishdemon.msk.consumer.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
// 註明遠程服務的名稱
@FeignClient("msk-provider")
public interface ConsumerService {
// 註明遠程調用的 url 及 參數
@GetMapping("/provider/hello")
String hello();
@GetMapping("/provider/hello/{id}")
String getById(@PathVariable("id") String id);
@GetMapping("/provider/hello/user")
String getByName(@RequestParam("name") String name);
}
3.4.5 創建 ConsumerController 查看調用的內容
package com.fishdemon.msk.consumer.controller;
import com.fishdemon.msk.consumer.service.ConsumerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("hello")
public class ConsumerController {
@Autowired
private ConsumerService consumerService;
@GetMapping
public String hello() {
return consumerService.hello();
}
@GetMapping("/{id}")
public String getById(@PathVariable("id") String id) {
return consumerService.getById(id);
}
@GetMapping("/user")
public String getByName(@RequestParam("name") String name) {
return consumerService.getByName(name);
}
}
3.4.6 啓動應用
- 查看 eureka 頁面 http://localhost:8761,現在有兩個服務了
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-VS1XJBrY-1590933150563)(assets/1585380577918.png)]
- 調用消費者的API , http://localhost:8782/consumer/hello
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-IKEZv6OY-1590933150568)(assets/1585380543797.png)]
3.5 消費者配置 Hystrix 熔斷
由於新冠疫情影響,最近一段時間股市熔斷了3次,扯得有點遠,哈哈。。。那這個熔斷到底是什麼意思呢?股市的熔斷和微服務中的熔斷一樣麼?我們來做個實驗。
假設生產者服務宕機了,會造成影響什麼呢?我們主動將 msk-provider
服務停掉。然後在訪問消費者API: http://localhost:8782/consumer/hello ,頁面等待了一段時間,最後返回下面的錯誤:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Sat Mar 28 15:32:54 CST 2020
There was an unexpected error (type=Internal Server Error, status=500).
connect timed out executing GET http://msk-provider/provider/hello feign.RetryableException:
connect timed out executing GET http://msk-provider/provider/hello
大意就是說調用超時,服務器錯誤,頁面等待的時候就是超時的時間。
可以這樣擴展一下,假如超時時間爲 2 秒,消費者服務平均1 秒鐘有 10000 請求,生產者宕機後,消費者服務中會有每一秒就會有 20000 個服務線程停留在內存中。如果請求更多呢,就可能讓消費者服務內存崩潰進而宕掉;如果這時還有其他服務調用這個消費者服務呢,想象一下,其他服務也會宕掉。。。這就是雪崩現象。。
爲了不出現上面的情況,微服務中引入了熔斷機制,簡單來說,就是當生產者服務宕掉後,消費者服務採用默認的 reponse 返回,而不去調用生產者了,保證服務的高可用。
Hystrix 主要配置在服務間通信的地方,而我們使用的Feign正是用來實現服務間通信的。因爲Feign已經內置了Hystrix,我們只需要在配置啓用 hystrix 即可。
3.5.1 修改 msk-consumer
中的配置
application.yml
feign:
hystrix:
# 開啓Feign的Hystrix熔斷器支持
enabled: true
3.5.2 增加熔斷處理類ConsumerServiceFallback
熔斷處理類直接實現 FeignClient 的接口即可,正常時使用 FeignClient 接口來處理,熔斷後使用該接口的實現類來處理。
package com.fishdemon.msk.consumer.service.fallback;
import com.fishdemon.msk.consumer.service.ConsumerService;
import org.springframework.stereotype.Service;
@Component
public class ConsumerServiceFallback implements ConsumerService {
@Override
public String hello() {
return "error request: provider service is down";
}
@Override
public String getById(String id) {
return "error request: provider service is down";
}
@Override
public String getByName(String name) {
return "error request: provider service is down";
}
}
3.5.3 在 FeignClient 接口中增加 Fallback 配置
這個坑我踩了 1 個小時,注意這裏一定要在原 FeignClient 註解中增加這個配置,否則不生效。
@FeignClient(value="msk-provider", fallback = ConsumerServiceFallback.class)
public interface ConsumerService {
// 省略......
}
3.5.4 重啓服務
- 停掉生產者服務,訪問 http://localhost:8782/consumer/hello
error request: provider service is down
- 再啓動生產者服務,再訪問 http://localhost:8782/consumer/hello ,恢復正常了
hello, i'm provider
3.6 消費者配置熔斷監控頁面
3.6.1 增加依賴
根 build.gradle
project(':msk-consumer') {
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
implementation 'org.springframework.boot:spring-boot-starter-web'
// 新增依賴
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-hystrix-dashboard'
}
}
3.6.2 增加註解
增加 @EnableHystrixDashboard 開啓頁面服務
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableHystrixDashboard
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3.6.3 增加配置類
package com.fishdemon.msk.consumer.config;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* SpringBoot2.x版本中需要手動指定dashboard的servlet請求地址
*/
@Configuration
public class HystrixDashboardConfig {
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
3.6.4 重啓服務
3.7 搭建 API 網關-Zuul
3.7.1 設置依賴
根 build.gradle
project(':msk-gateway') {
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-zuul'
}
}
3.7.2 創建啓動類
進入到模塊 msk-gateway
中,@EnableEurekaClient
開啓 Eureka 客戶端;@EnableZuulProxy
開啓Zuul網關的支持。
package com.fishdemon.msk.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3.7.3 創建 application.yml
配置文件
server:
port: 8765
eureka:
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
service-url:
defaultZone: http://msk-eureka:8761/eureka
spring:
application:
name: msk-gateway
zuul:
routes:
# 路由名稱,可以隨意寫,不過一般用服務名來區別好一些
msk-consumer:
# zuul默認會將heaher移除再轉發,這裏設置成傳遞所有 header
sensitiveHeaders: "*"
# 路由地址
path: /consumer/**
# 路由地址對應的服務名稱
service-id: msk-consumer
# 是否去掉路由前綴再轉發
stripPrefix: false
msk-provider:
sensitiveHeaders: "*"
path: /provider/**
service-id: msk-provider
stripPrefix: false
3.7.4 啓動服務
- 通過網關來訪問消費者的API http://localhost:8765/consumer/hello
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-vlHelMdb-1590933150571)(assets/1585386620277.png)]
訪問成功,說明通過一個網關就可以訪問其他所有的服務。
3.7.5 爲網關設置熔斷處理
網關是整個微服務集羣的出口,熔斷是非常必要的,所以 Zuul 網關中內置了熔斷。我們可以試試訪問一下不存在的 API,或者將消費者服務停掉在調用一次API,會出現下面的頁面。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-2o21Jbu6-1590933150575)(assets/1585402117093.png)]
或者是下面的頁面
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-nNnSJBD7-1590933150576)(assets/1585402319657.png)]
這就是因爲服務不可用,導致熔斷而返回的信息。在實際中,肯定不能直接返回 spring 拋出的信息,非常不友好。
我們可以配置一個 Fallback 類來定製服務不可用後返回的信息:ConsumerFallbackProvider
package com.fishdemon.msk.gateway.fallback;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
@Component
public class ConsumerFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
// 配置生效的服務id, 如果返回 * 或 null , 代表全支持
return "msk-consumer";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.OK.value();
}
@Override
public String getStatusText() throws IOException {
return HttpStatus.OK.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map = new HashMap<>();
map.put("code", -1);
map.put("message", ",該服務不可用,服務器異常");
return new ByteArrayInputStream(mapper.writeValueAsString(map).getBytes("UTF-8"));
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
return httpHeaders;
}
};
}
}
停止消費者服務,訪問 http://localhost:8765/consumer/hello ,結果是
{"code":-1,"message":",該服務不可用,服務器異常"}
3.7.6 爲網關設置過濾器
使用 Zuul 做網關,最方便的地方就在於可以通過定義過濾器攔截請求來實現各種各樣的功能,比如 權限驗證,IP地址過濾,訪問日誌記錄,限流等,下面來示範一個簡單權限驗證的例子:
package com.fishdemon.msk.gateway.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@Component
public class AccessFilter extends ZuulFilter {
@Override
public String filterType() {
// 設置過濾器類型 pre/routing/post/error
return "pre";
}
@Override
public int filterOrder() {
// 設置過濾器優先級, 值越小, 優先級越高
return 0;
}
@Override
public boolean shouldFilter() {
// 過濾器是否生效, true 爲生效
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
HttpServletResponse response = context.getResponse();
// 查看 header 中是否有token
String token = request.getHeader("x-auth-token");
if (token == null) {
// 不在向後臺服務轉發
context.setSendZuulResponse(false);
// 設置 response status 爲 401
context.setResponseStatusCode(401);
try {
response.getWriter().write("no authentication");
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
重啓網關,訪問 http://localhost:8765/consumer/hello ,結果爲
no authentication
3.8 搭建Config配置中心
3.8.1 Server 端
1. 設置依賴
根 build.gradle
project(':msk-config') {
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
implementation 'org.springframework.cloud:spring-cloud-config-server'
}
}
2. 創建啓動類
package com.fishdemon.msk.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
@EnableConfigServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3. 創建 application.yml
配置文件
server:
port: 8888
eureka:
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
service-url:
defaultZone: http://msk-eureka:8761/eureka
spring:
application:
name: msk-config
# config 使用本地存儲配置時必須使用 native 環境
profiles:
active: native
cloud:
config:
server:
native:
# 配置文件的存儲庫配置
search-locations: classpath:config/
這裏使用本地作爲配置的存儲庫,當然更好的方案是使用 github/gitlab 來存儲,配置如下所示
spring:
# 不要用 native 環境
# profiles:
# active: native
cloud:
config:
server:
# 配置倉庫的分支
label: master
git:
uri: http://******.git # git 克隆地址
searchPaths: config # 存儲的目錄
username: ******* # 用戶名
password: ******* # 密碼
4. 存儲配置文件
在 src/main/resources
中創建文件夾 config
, 並將上一節中 zuul 網關服務的配置複製過來放入其下,重命名爲application-gateway-pro.yml
,同時只保留一些主要的端口和路由配置
application-gateway-pro.yml
server:
port: 8765
zuul:
routes:
msk-consumer:
sensitiveHeaders: "*"
path: /consumer/**
service-id: msk-consumer
stripPrefix: false
msk-provider:
sensitiveHeaders: "*"
path: /provider/**
service-id: msk-provider
stripPrefix: false
5. 啓動服務
- 訪問 http://localhost:8888/gateway/pro
{
"name":"gateway",
"profiles":["pro"],
"label":null,
"version":null,
"state":null,
"propertySources":[]
}
說明 Config 服務配置成功,客戶端可以獲取到相關的配置。
3.8.2 Client 端
由於 Config Server 中放置的是 gateway 配置,那我們就用之前的 Zuul Gateway 服務來演示。
1. 增加 Config Client依賴
根 build.gradle
project(':msk-gateway') {
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-zuul'
// 新增的 config client 依賴
implementation 'org.springframework.cloud:spring-cloud-starter-config'
}
}
2. 使用 bootstrap.yml 代替 application.yml 來配置【必須】
爲了驗證遠程配置起作用,將原有的 application.yml
刪掉,這樣路由信息只存在於遠程配置中。同時新建一個 bootstrap.yml
配置:
bootstrap.yml
server:
port: 8765
eureka:
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
service-url:
defaultZone: http://msk-eureka:8761/eureka
spring:
application:
name: msk-gateway
profiles:
active: native
cloud:
config:
# 配置文件的前綴名
name: application-gateway
# 配置文件的環境標識, dev/test/prod
profile: prod
# label: # 配置倉庫的分支, 這裏是本地讀取,可不配置
discovery:
# 啓動配置發現
enabled: true
# config server 的服務id
serviceId: msk-config
一般 bootstrap.yml
中只放置 端口,eureka 地址,及 config client 的配置即可,作用只是爲了與 Config Server 進行通信,因爲 bootstrap.yml
的加載先於 application.yml
的加載。其他的配置仍可以放在 application.yml
中,他們可以並存。
網上有些文章說如果 Config server 使用默認的 8888 端口, 則 Config Client 可以使用 application.yml 。我驗證了一下,這種說法是謬論。儘管他們的 Client 端正確的獲得了配置,但這是一種巧合的環境導致的,Config 的 server端 與 Client端在同一臺機器上,且 Config server 使用 8888 端口,這樣 Client 在啓動的時候默認從 http://localhost:8888
獲取配置是成功的。。。但是如果 Config 的 server端 與 Client端不在同一臺機器上呢?那就獲取不到了把,因此必須用 bootstrap.yml
。
3. 重啓網關服務
- 訪問 http://localhost:8765/consumer/hello
hello, i'm provider
訪問成功,證明遠程配置中的路由起作用了。
3.9 搭建 SpringBootAdmin 服務監控中心
3.9.1 Server 端
1. 設置依賴
根 build.gradle
project(':msk-boot-admin') {
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
implementation 'de.codecentric:spring-boot-admin-starter-server'
implementation 'de.codecentric:spring-boot-admin-server-ui'
}
}
2. 創建啓動類
進入子模塊 msk-boot-admin
,@EnableEurekaClient 開啓 Eureka 客戶端, @EnableAdminServer 開啓 boot admin 服務支持。
package com.fishdemon.msk.bootadmin;
import de.codecentric.boot.admin.server.config.EnableAdminServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
@EnableAdminServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3. 創建 application.yml
配置文件
server:
port: 8766
eureka:
client:
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
service-url:
defaultZone: http://msk-eureka:8761/eureka
spring:
application:
name: msk-boot-admin
boot:
admin:
# boot admin 頁面的路徑
context-path: /admin
3.9.2 Client 端
1. 增加依賴
客戶端有兩種配置方式,區別很大哦,詳情請參考文章
- 客戶端直連到 Admin Server,需要每一個客戶端都配置 Admin Server 的地址
- 客戶端加入Eureka集羣,Admin Server 自動從 Eureka Server 中拉取服務註冊信息,來獲取服務的狀態
這裏採用第二種方式,客戶端無需做其他修改,只需要加入監控相關的依賴即可
implementation 'org.springframework.boot:spring-boot-starter-actuator'
由於 spring-boot-admin
底層是依賴 actuator 神器實現的,所以需要在之前創建的每一個服務中都加入這個依賴(msk-boot-admin
不需要加,因爲它是 Server 端),然後重啓所有服務。
2. 啓動 Admin Server, 訪問 Admin 頁面
http://localhost:8766/admin/wallboard
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-tPn5tcCo-1590933150579)(assets/1585393309743.png)]
這個監控頁面帥不帥!是不是很有感覺!其實它的功能還很強大,點擊任何圖中的一個服務進去,還可以看到服務的具體信息(內存/日誌/CPU使用率/線程等),它是基於 actuator ,通過配置可以開放更多的信息。
management:
endpoint:
health:
enabled: true
show-details: always
endpoints:
web:
base-path: /actuator
cors:
allowed-origins: true
exposure:
include:
- '*'
3.10 搭建 Zipkin 鏈路追蹤
3.10.1 Server 端
1. 設置依賴
根 build.gradle
2. 創建啓動類
3. 創建 application.yml
配置文件
3.10.2 Client 端
如果需要 ZipKin 監控所有服務的信息,需要讓每個服務都成爲 ZipKin 的客戶端。
除了msk-zipkin
服務本身,其他的服務全都要成爲 ZipKin 的客戶端,在每個服務中都添加下面的配置。
1. 增加依賴
根 build.gradle
// zipkin 客戶端
implementation 'org.springframework.cloud:spring-cloud-starter-zipkin'
2. 增加配置
spring:
zipkin:
base-url: http://localhost:9411
3. 重啓所有服務
重啓之前所有的服務:msk-eureka, msk-config, msk-gateway, msk-provider, msk-consumer
在頁面調用一下之前寫的消費者接口 http://localhost:8765/consumer/hello
然後打開鏈路追蹤的頁面 http://localhost:9411/zipkin ,然後點擊查找
,就會出現下面的信息
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-1NFmpxke-1590933150580)(assets/1585400645401.png)]
點擊下面的第一條鏈路信息,可以看到這條API經過了哪些服務,以及經過時間等信息
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-SFkX9uM8-1590933150582)(assets/1585400993922.png)]
搭建過程非常簡單,但是非常強大。
參數說明:
-
span
: 基本工作單元 -
Trace
: 一系列Spans組成的樹狀結構 -
Annotation
: 用來即時記錄一個時間的存在,比如請求的開始於結束 -
cs
: Client Server,客戶端發起一個請求,這個Annotation描述了這個Span的開始sr
: Server Received,服務端獲取請求並開始處理它。sr - cs
得到網絡延遲時間ss
: Server Sent 請求處理完成,請求返回客戶端。ss - sr
得到服務端處理請求的時間cr
: Client Received 表明Span的結束,客戶端成功接收到服務端的回覆。cr - cs
得到客戶端從服務端獲取回覆花費的總時間。