微服務-從零開始用eureka+zuul+cloud config+boot admin+feign+ribbon+zipkin構建微服務腳手架

實戰順序

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 已有的註解。它底層仍是使用 RestTemplateRibbon 來實現的。

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得到客戶端從服務端獲取回覆花費的總時間。

4. 聯繫

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章