1. 程式人生 > >淺談現公司的Spring Cloud微服務框架

淺談現公司的Spring Cloud微服務框架

分布式配置中心 thread 系列 ret 存儲 關閉 插件 ipaddr active

目錄

  • 說在前面
  • 服務註冊與發現
  • 服務網關及熔斷
  • 配置中心
  • 消息中心、服務鏈路追蹤
  • 小言

說在前面

本文偏小白,大佬慎入,若有錯誤或者質疑,歡迎留言提問,謝謝,祝大家新年快樂。

spring cloud
Spring Cloud 是將分布式系統中一系列基礎框架/工具進行整合的框架。其中包含: 服務註冊與發現、服務網關、熔斷器、配置中心、消息中心、服務鏈路追蹤等等 。這也是一個服務化架構的最小組成元素,有了這些基本的組成要素,就可以實現一個最簡單的服務架構。

Spring Cloud 並沒有重復造輪子,Spring Cloud只是依賴於Spring Boot屏蔽掉了各個框架復雜的配置。所有的組件就相當於Spring Cloud的插件,開發人員可以根據自己的需要自由結合使用。
有Spring Boot這個利器在,所有組件都可以輕松引入、便捷開發。也一定程度上降低了各組件的學習成本、調試成本、讓開發人員可以輕松上手。


服務註冊與發現

從最簡單、最核心的問題出發,假設服務 A 要調用服務 B,會有什麽問題?

服務在哪?(服務治理問題)怎麽調用?(服務調用問題)
這兩個是最核心的問題,也是任何微服務框架首要解決的兩個問題。

為了解決第一個問題 Spring Cloud 提供了 Eureka、Zookeeper、Cloud Foundry、Consul 等服務治理框架的集成。它們的工作模式是將所有的微服務註冊到一個 Server 上,然後通過心跳進行服務健康監測。這樣服務 A 調用 B 時可以從註冊中心拿到可用的服務 B 的地址、端口進行調用。

我公司就是使用的是Eureka

Eureka由兩個組件組成:Eureka服務器和Eureka客戶端。

先找到Eureka服務器,也就是我們項目的註冊中心
EurekaApplication.java

@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaserverApplication.class, args);
    }
}
  • 註解 @EnableEurekaServer 表示該 Spring Boot 應用是一個註冊中心。Eureka Server提供服務註冊服務,各個節點啟動後,會在Eureka Server中進行註冊,這樣EurekaServer中的服務註冊表中將會存儲所有可用服務節點的信息,服務節點的信息可以在界面中直觀的看到。

配置文件 bootstrap.yml

server:
  port: 8700
 
eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  server: 
    enableSelfPreservation: false
  • eureka.client.registerWithEureka: false 和fetchRegistry: false 來表明自己是一個 eureka server。
  • enableSelfPreservation: false表示在此eureka服務器中關閉自我保護模式,所謂自我保護模式是指,出現網絡分區、eureka在短時間內丟失過多客戶端時,會進入自我保護模式,即一個服務長時間沒有發送心跳,eureka也不會將其刪除。默認為true

以上是註冊中心EurekaApplication.java,而所有單獨開發的SB項目,就是client

而我在查看每個項目的配置文件時,發現並沒有找到eureka.client.serviceUrl.defaultZone:...這樣的配置

於是猜想可能在啟動腳本中添加此參數,果不其然,找到服務器上的啟動腳本:

-Deureka.client.serviceUrl.defaultZone=http://**.**.**.***:8700/eureka

這就是其作為eureka客戶端的證據(真的很想吐槽為什麽要寫在啟動腳本中)

第二個服務調用有人可能認為就是一個簡單的 HTTP 或者 RPC 調用,不是什麽問題。但是在分布式的場景下,服務調用需要考慮的因素會更多。比如一個服務有多個實例,此時請求進來了交給誰處理,請求的負載怎麽平衡到各個實例,都是比較棘手的問題。Spring Cloud 提供了兩種服務調用的方式:一種是 Ribbon + restTemplate,另一種是 Feign。

其中 Ribbon 是基於 HTTP 和 TCP 客戶端的負載均衡器,restTemplate 是 Spring 提供的 Restful 遠程調用的模板,兩者結合就可以達到遠程調用的負載均衡。

而 Feign 是一個更加聲明式的 HTTP 客戶端,開發者可以像調用本地方法一樣調用它,完全感覺不到是遠程調用,結合 Ribbon 也可以做負載均衡。


服務網關及熔斷

微服務中的服務很多,直接暴露給用戶一是不安全,二是對用戶不友好。因此在微服務和面向服務的架構中,通常會有一個路由網關的角色,來負責路由轉發和過濾。對應到 Spring Cloud 中有 Zuul 和 Gateway 兩個組件可用。

據查看我公司只使用zuul做路由轉發用
Zuul是Netflix開源的服務網關/API網關,提供動態路由、監控、彈性、安全性等功能。

ZuulApplication.java


@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
@RestController
public class ZuulApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class, args);
    }

    /**
     * 跨域許可設置
     */
    @Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource= new UrlBasedCorsConfigurationSource();
        final CorsConfiguration corsConfig = new CorsConfiguration();
        corsConfig.setAllowCredentials(true);
        corsConfig.addAllowedOrigin("*");
        corsConfig.addAllowedHeader("*");
        corsConfig.addAllowedMethod("OPTIONS");
        corsConfig.addAllowedMethod("HEAD");
        corsConfig.addAllowedMethod("GET");
        corsConfig.addAllowedMethod("PUT");
        corsConfig.addAllowedMethod("POST");
        corsConfig.addAllowedMethod("DELETE");
        corsConfig.addAllowedMethod("PATCH");
        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfig);
        return new CorsFilter(urlBasedCorsConfigurationSource);
    }

    @RequestMapping(value = "/")
    public String heartbeat() {
        return "";
    }
}
  • 在zuul服務下添加一個corsFilter實現跨域

bootstrap.yml

server:
  port: 8000

spring:
  application:
    name: zuul
  cloud:
    config:
      discovery:
        enabled: true
        serviceId: CONFIG

eureka:
  instance:
    preferIpAddress: true
    leaseRenewalIntervalInSeconds: 1
    leaseExpirationDurationInSeconds: 2
    nonSecurePort: ${server.port}
  client:
    serviceUrl:
      defaultZone: http://${eureka.host}:${eureka.port}/eureka/

application.yml


spring:
  profiles:
    active: eureka

logging:
  config: classpath:logback-error.xml

server:
  tomcat:
    max-threads: 100

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000

ribbon:
  ConnectTimeout: 3000
  ReadTimeout: 60000

hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: false
  threadpool:
    default:
      coreSize: 50
      maxQueueSize: 100
      queueSizeRejectionThreshold: 100

javamelody:
  login_name: viroyal
  login_pwd: viroyal2017

---
spring:
  profiles: eureka

zuul:
  routes:
    account:
      path: /account/**
      serviceId: USERMANAGER
      stripPrefix: false
      
    about:
      path: /about/**
      serviceId: ABOUT
      stripPrefix: false
            
    app:
      path: /app/**
      serviceId: CAMPUSCMS
      stripPrefix: false

    res:
      path: /res/**
      serviceId: CMS
      stripPrefix: false

    device:
      path: /device/**
      serviceId: CAMPUS-DEVICE
      stripPrefix: false

hystrix就是熔斷器

熔斷器的原理很簡單,如同電力過載保護器。它可以實現快速失敗,如果它在一段時間內偵測到許多類似的錯誤,會強迫其以後的多個調用快速失敗,不再訪問遠程服務器,從而防止應用程序不斷地嘗試執行可能會失敗的操作,使得應用程序繼續執行而不用等待修正錯誤,或者浪費CPU時間去等到長時間的超時產生。熔斷器也可以使應用程序能夠診斷錯誤是否已經修正,如果已經修正,應用程序會再次嘗試調用操作。

hystrix默認是打開的,所以此配置中未進行過多設置,只設置了hystrix.threadpool.default.threadPool的相關屬性,這裏說一下queueSizeRejectionThreshold:100,是為隊列設置拒絕閾值,當線程池隊列到達100時則開啟熔斷器。

  • 具體的hystrix所欲配置講解,請參考:https://www.cnblogs.com/li3807/p/7501427.html

記住:熔斷器就是保護服務高可用的最後一道防線。

關於超時,由於zuul本身就集合了hystrix和ribbon,zuul 中配置超時時間,據官方的介紹,分兩種情況:

  • 用 serviceId 進行路由時,使用 ribbon.ReadTimeout 和 ribbon.SocketTimeout 設置

  • 用指定 url 進行路由時,使用 zuul.host.connect-timeout-millis 和 zuul.host.socket-timeout-millis 設置
    此配置文件是用 serviceId 進行路由的(根據請求path,分發到對應的serviceId上),所以未設置zuul超時,且hystrix.XX.timeout.enabled設置成了false,關掉了hystrix的超時設置(實際上,如果同時配置了 Ribbon 和 Hystrix 的超時時間,則以最小的為準)。所以,此項目只開啟了ribbon的超時設置。像此例中的ribbon的超時是作用全局的。

直接使用 serviceId作為前綴,可以區分不同客戶端下的配置,類似如下:

USERMANAGER:
  ribbon:
    ConnectTimeout: 5000
    ReadTimeout: 5000
  • spring Cloud超時總結參考:http://www.itmuch.com/spring-cloud-sum/spring-cloud-timeout/

關於zuul.routes.XXX.stripPrefix的設置,當zuul.routes.XXX.path=/api/**時,作用如下例子:

  • 當stripPrefix=true的時候 (http://127.0.0.1:8181/api/user/list -> http://192.168.1.100:8080/user/list)
  • 當stripPrefix=false的時候(http://127.0.0.1:8181/api/user/list -> http://192.168.1.100:8080/api/user/list)

路由網關接收了所有的用戶請求,有著很高的負載,因此它通常是一個集群。用戶的請求會先經過一層負載均衡被發到路由網關。


配置中心

在微服務應用中,服務數量巨多,而每個服務不同環境都有著不同的配置,為了方便服務配置文件統一管理,實時更新,所以需要分布式配置中心組件。需要註意的是此處的配置與註冊中心註冊的配置信息是兩個概念,此處的配置是服務本身的一些配置信息。

ConfigApplication.java

@SpringBootApplication
@EnableConfigServer
@EnableEurekaClient
public class ConfigApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConfigApplication.class, args);
    }
}
  • 本身也是EurekaClient,使用@EnableConfigServer成為配置中心,提供配置服務

bootstraop.yml

#comment can‘t have chinese
#using outer net interface is convenient for local webapp start and debug
server:
  port: 8888

spring:
  application:
    name: config
  profiles:
    active: native
  cloud:
    config:
      server:
        native:
          searchLocations:
            file:/var/configs
    inetutils:
      ignoredInterfaces:
        - eth0

eureka:
  instance:
    preferIpAddress: true
    leaseRenewalIntervalInSeconds: 1
    leaseExpirationDurationInSeconds: 2
    nonSecurePort: ${server.port}
  client:
    serviceUrl:
      defaultZone: http://${eureka.host}:${eureka.port}/eureka/

這裏 spring.appliction.name為config

我看了一下其他服務的bootstrap.yml中有兩項配置

sprong.cloud.config.discovery.enabled: true    
sprong.cloud.config.discovery.serviceId: CONFIG      

第一個作用是:使用註冊中心找尋config-server的地址
第二個作用是:配置中心在註冊中心的applicationName

通過找到配置中心 發現 配置文件存儲在/var/configs路徑下,且讀取文件名和本服務serviceId相同的文件中的配置


消息中心、服務鏈路追蹤

據我查看,我公司並未實現消息中心和服務鏈路追蹤,這裏我就總結一下他們的作用吧

消息中心
在微服務架構的系統中,我們通常會使用輕量級的消息代理來構建一個共用的消息主題讓系統中所有微服務實例都能連接上來,由於該主題中產生的消息會被所有實例監聽和消費,所以我們稱它為消息總線。在總線上的各個實例都可以方便地廣播一些需要讓其他連接在該主題上的實例都知道的消息,例如配置信息的變更或者其他一些管理操作等。
由於消息總線在微服務架構系統的廣泛使用,所以它同配置中心一樣,幾乎是微服務架構中的必備組件。spring cloud作為微服務架構綜合性的解決方案,對此自然也有自己的實現,這就是spring cloud bus。通過spring cloud bus,可以非常容易的搭建起消息總線,同時實現了一些消息總線中的常用功能,比如配合spring cloud config實現微服務應用配置信息的動態更新等。

服務鏈路追蹤
微服務架構是一個分布式架構,它按業務劃分服務單元,一個分布式系統往往有很多個服務單元。由於服務單元數量眾多,業務的復雜性,如果出現了錯誤和異常,很難去定位。主要體現在,一個請求可能需要調用很多個服務,而內部服務的調用復雜性,決定了問題難以定位。所以微服務架構中,必須實現分布式鏈路追蹤,去跟進一個請求到底有哪些服務參與,參與的順序又是怎樣的,從而達到每個請求的步驟清晰可見,出了問題,很快定位。
在微服務系統中,一個來自用戶的請求,請求先達到前端A(如前端界面),然後通過遠程調用,達到系統的中間件B、C(如負載均衡、網關等),最後達到後端服務D、E,後端經過一系列的業務邏輯計算最後將數據返回給用戶。對於這樣一個請求,經歷了這麽多個服務,怎麽樣將它的請求過程的數據記錄下來呢?這就需要用到服務鏈路追蹤。
我公司業務體系不大,所以就為完成此項集成吧。


小言

以上來看,可以說這是最精簡的spring cloud分布式系統的構成了,不談具體技術實現如何,大體的架構還是有的,也讓我對spring cloud有了廣義上實質的了解。

淺談現公司的Spring Cloud微服務框架