Java進階專題(二十二) 從零開始搭建一個微服務架構系統 (上)

有夢想的老王發表於2021-02-01

前言

"微服務”一詞源於 Martin Fowler的名為 Microservices的,博文,可以在他的官方部落格上找到http:/ /martinfowler . com/articles/microservices.html簡單地說,微服務是系統架構上的一種設計風格,它的主旨是將一個原本獨立的系統拆分成多個小型服務,這些小型服務都在各自獨立的程式中執行,服務之間通過基於HTTP的 RESTfuL AP進行通訊協作。常見微服務框架:Spring的spring cloud、阿里dubbo、華為ServiceComb、騰訊Tars、Facebook thrift、新浪微博Motan。本章節我們先從瞭解組成完整系統的各個元件開始,下章節將利用這些元件,搭建出一個完善的分散式系統。

Spring Cloud

這就不用多說了,官網有詳細的介紹。

Spring Cloud Alibaba

Spring Cloud ɵɹibaba 致力於提供微服務開發的一站式解決方案。此專案包含開發分散式應用微服務的必需元件,方便開發者通過 Spring Cloud 程式設計模型輕鬆使用這些元件來開發分散式應用服務

主要元件
Sentinel:把流量作為切入點,從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性。
Nacos:一個更易於構建雲原生應用的動態服務發現、配置管理和服務管理平臺。
RocketMQ:一款開源的分散式訊息系統,基於高可用分散式叢集技術,提供低延時的、高可靠的訊息釋出與訂閱服務。
Dubbo:Apache Dubbo™ 是一款高效能 Java RPC 框架。
Seata:阿里巴巴開源產品,一個易於使用的高效能微服務分散式事務解決方案。

服務註冊與發現

Eureka:官方宣佈2.x不再開源(閉源),之前的版本已經停止更新;也就說Eureka將來更多的技術提升已經沒有了。所以,如果希望註冊中心有更多強大功能的話,還需要另闢蹊徑 。
Zookeeper:在企業級Zookeeper註冊中心與 Dubbo組合比較多一些,kafka使用的也是,隨著Eureka的停更,我們可以通過spring-cloud-starter-zookeeper-discovery這個啟動器,將Zookeeper做為springcloud的註冊中心。
Consul:go語言開發的,也是一個優秀的服務註冊框架,使用量也比較多。
Nacos:來自於SpringCloudɵɹibaba,在企業中經過了百萬級註冊考驗的,不但可以完美替換Eureka,還能做其他元件的替換,所以,Naocs也強烈建議使用。

介紹下Nacos用作註冊中心

Nacos簡介

Nacos(Dynamic Naming and Configur ation Service) 是阿里巴巴2018年7月開源的專案,致力於發現、配置和管理微服務。

Nacos安裝

單節點

--下載映象
docker pull nacos/nacos-server:1.3.1
--啟動容器
docker run  --name nacos --env MODE=standalone --privileged=true  -p 8848:8848 --restart=always   -d dc833dc45d8f

訪問:

http://127.0.0.1:8848/nacos 賬號密碼都是nacos

叢集

安裝前提

64 bit OS Linux/Unix/Mac,推薦使用Linux系統。
叢集需要依賴mysql,單機可不必
3個或3個以上Nacos節點才能構成叢集
搭建Nacos高可用叢集步驟:

1、需要去Nacos官網clone Nacos叢集專案nacos-docker
2、nacos-docker是使用的Docker Compose對容器進行編排,所以首先需要安裝Docker Compose詳細資訊可參照Nacos官網:https:/ /nacos.io/zh-cn/docs/quick-start-docker.html

1)安裝Docker Compose
什麼是Docker Compose
Compose專案是Docker官方的開源專案,負責實現對Docker容器叢集的快速編排。

#在Linux下下載(下載到/usr/local/bin)
curl -L https://github.com/docker/compose/releases/download/1.25.0/run.sh > /usr/local/bin/docker-compose
# 設定檔案可執行許可權
chmod +x /usr/local/bin/docker-compose
# 檢視版本資訊
docker-compose --version

2)克隆Nacos-docker專案

#切換到自定義目錄
cd /usr/local/nacos
#開始clone
git clone https://github.com/nacos-group/nacos-docker.git

3)執行nacos-docker指令碼

#執行編排命令
docker-compose -f /usr/local/nacos/nacos-docker/example/cluster-hostname.yaml up

上面的編排命令主要下載mysql映象和nacos映象,自動完成映象下載/容器啟動
Pulling from nacos/nacos-mysql,版本是5. 7(執行初始化指令碼)
Pulling nacos3 (nacos/nacos-server:latest)最新版本

4)停止、啟動

#啟動
docker-compose -f  /usr/local/nacos/nacos-docker/example/cluster-hostname.yaml start
#停止
docker-compose -f  /usr/local/nacos/nacos-docker/example/cluster-hostname.yaml stop

5)訪問Nacos

http://192.168.1.1:8848/nacos
http://192.168.1.1:8849/nacos
http://192.168.1.1:8850/nacos

Nacos快速入門

配置服務提供者

服務提供者可以通過 Nacos 的服務註冊發現功能將其服務註冊到 Nacos server 上。

新增nacos依賴

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <version>${latest.version}</version>
</dependency>

新增配置

server.port=8070
spring.application.name=nacos-demo

spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

啟動類

@SpringBootApplication
@EnableDiscoveryClient
public class NacosProviderApplication {

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

	@RestController
	class EchoController {
		@RequestMapping(value = "/echo/{string}", method = RequestMethod.GET)
		public String echo(@PathVariable String string) {
			return "Hello Nacos Discovery " + string;
		}
	}
}

啟動後,控制檯:

說明註冊成功,後臺檢視該服務:

下面我們用spring cloud 整合naocs實現服務呼叫

配置服務消費者

新增配置

server.port=8080
spring.application.name=service-consumer

spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

新增啟動類:服務消費者使用 @LoadBalanced RestTemplate 實現服務呼叫

@SpringBootApplication
@EnableDiscoveryClient
public class NacosConsumerApplication {

    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

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

    @RestController
    public class TestController {

        private final RestTemplate restTemplate;

        @Autowired
        public TestController(RestTemplate restTemplate) {this.restTemplate = restTemplate;}

        @RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)
        public String echo(@PathVariable String str) {
            return restTemplate.getForObject("http://service-provider/echo/" + str, String.class);
        }
    }
}

測試

啟動 ProviderApplicationConsumerApplication ,呼叫 http://localhost:8080/echo/2018,返回內容為 Hello Nacos Discovery 2018

分散式配置中心解決方案與應用

目前市面上用的比較多的配置中心有(時間順序)
Disconf:2014年7月百度開源的配置管理中心,同樣具備配置的管理能力,不過目前已經不維護了,最近的一次提交是4-5年前了。
Spring Cloud Config:2014年9月開源,Spring Cloud 生態元件,可以和Spring Cloud體系無縫整合。
Apollo:2016年5月,攜程開源的配置管理中心,具備規範的許可權、流程治理等特性。
Nacos:2018年6月,阿里開源的配置中心,也可以做DNS和RPC的服務發現

介紹下Nacos用作分散式配置中心

啟動了 Nacos server 後,您就可以參考以下示例程式碼,為您的 Spring Cloud 應用啟動 Nacos 配置管理服務了。完整示例程式碼請參考:nacos-spring-cloud-config-example

  1. 新增依賴:
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    <version>${latest.version}</version>
</dependency>

注意:版本 2.1.x.RELEASE 對應的是 Spring Boot 2.1.x 版本。版本 2.0.x.RELEASE 對應的是 Spring Boot 2.0.x 版本,版本 1.5.x.RELEASE 對應的是 Spring Boot 1.5.x 版本。

更多版本對應關係參考:版本說明 Wiki

  1. bootstrap.properties 中配置 Nacos server 的地址和應用名
spring.cloud.nacos.config.server-addr=127.0.0.1:8848

spring.application.name=example

說明:之所以需要配置 spring.application.name ,是因為它是構成 Nacos 配置管理 dataId欄位的一部分。

在 Nacos Spring Cloud 中,dataId 的完整格式如下:

${prefix}-${spring.profiles.active}.${file-extension}
  • prefix 預設為 spring.application.name 的值,也可以通過配置項 spring.cloud.nacos.config.prefix來配置。
  • spring.profiles.active 即為當前環境對應的 profile,詳情可以參考 Spring Boot文件注意:當 spring.profiles.active 為空時,對應的連線符 - 也將不存在,dataId 的拼接格式變成 ${prefix}.${file-extension}
  • file-exetension 為配置內容的資料格式,可以通過配置項 spring.cloud.nacos.config.file-extension 來配置。目前只支援 propertiesyaml 型別。
  1. 通過 Spring Cloud 原生註解 @RefreshScope 實現配置自動更新:
@RestController
@RequestMapping("/config")
@RefreshScope
public class ConfigController {

    @Value("${useLocalCache:false}")
    private boolean useLocalCache;

    @RequestMapping("/get")
    public boolean get() {
        return useLocalCache;
    }
}
  1. 首先通過呼叫 Nacos Open API 向 Nacos Server 釋出配置:dataId 為example.properties,內容為useLocalCache=true
curl -X POST "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=example.properties&group=DEFAULT_GROUP&content=useLocalCache=true"
  1. 執行 NacosConfigApplication,呼叫 curl http://localhost:8080/config/get,返回內容是 true
  2. 再次呼叫 Nacos Open API 向 Nacos server 釋出配置:dataId 為example.properties,內容為useLocalCache=false
curl -X POST "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=example.properties&group=DEFAULT_GROUP&content=useLocalCache=false"
  1. 再次訪問 http://localhost:8080/config/get,此時返回內容為false,說明程式中的useLocalCache值已經被動態更新了。

分散式服務呼叫

RPC概述

RPC 的主要功能目標是讓構建分散式計算(應用)更容易,在提供強大的遠端呼叫能力時不損失本地呼叫的語義簡潔性。為實現該目標,RPC 框架需提供一種透明呼叫機制,讓使用者不必顯式的區分本地呼叫和遠端呼叫。

RPC的優點:分散式設計、部署靈活、解耦服務、擴充套件性強。

RPC框架

Dubbo:國內最早開源的 RPC 框架,由阿里巴巴公司開發並於 2011 年末對外開源,僅支援 Java 語言。
Motan:微博內部使用的 RPC 框架,於 2016 年對外開源,僅支援 Java 語言。
Tars:騰訊內部使用的 RPC 框架,於 2017 年對外開源,僅支援 C++ 語言。
Spring Cloud:國外 Pivotal 公司 2014 年對外開源的 RPC 框架,提供了豐富的生態元件。
gRPC:Google 於 2015 年對外開源的跨語言 RPC 框架,支援多種語言。
Thrift:最初是由 Facebook 開發的內部系統跨語言的 RPC 框架,2007 年貢獻給了 Apache 基金,成為
Apache:開源專案之一,支援多種語言。

RPC框架優點

RPC框架一般使用長連結,不必每次通訊都要3次握手,減少網路開銷。
RPC框架一般都有註冊中心,有豐富的監控管理髮布、下線介面、動態擴充套件等,對呼叫方來說是無感知、統一化的操作協議私密,安全性較高
RPC 協議更簡單內容更小,效率更高,服務化架構、服務化治理,RPC框架是一個強力的支撐。

RPC框架應用:使用Spring Cloud Alibaba 整合Dubbo實現

由於 Dubbo Spring Cloud 構建在原生的 Spring Cloud 之上, 其服務治理方面的能力可認為是 Spring Cloud Plus,不僅完全覆蓋 Spring Cloud 原生特性,而且提供更為穩定和成熟的實現,特性比對如下表所示:

Dubbo 作為 Spring Cloud 服務呼叫

預設情況,Spring Cloud Open Feign 以及@LoadBalanced`RestTemplate 作為 Spring Cloud 的兩種服務呼叫方式。 Dubbo Spring Cloud 為其提供了第三種選擇,即 Dubbo 服務將作為 Spring Cloud 服務呼叫的同等公民出現,應用可通過 Apache Dubbo 註解@Service 和@Reference 暴露和引用 Dubbo 服務,實現服務間多種協議的通訊。同時,也可以利用 Dubbo 泛化介面輕鬆實現服務閘道器。

快速上手

按照傳統的 Dubbo 開發模式,在構建服務提供者之前,第一個步驟是為服務提供者和服務消費者定義 Dubbo 服務介面。
為了確保契約的一致性,推薦的做法是將 Dubbo 服務介面打包在第二方或者第三方的 artifact(jar)中,該 artifact 甚至無需新增任何依賴。
對於服務提供方而言,不僅通過依賴 artifact 的形式引入 Dubbo 服務介面,而且需要將其實現。對應的服務消費端,同樣地需要依賴該 artifact,並以介面呼叫的方式執行遠端方法。接下來的步驟則是建立 artifact。

建立服務API

建立一個api模組,專門寫各種介面的:

/**
 * @author 原
 * @date 2020/12/8
 * @since 1.0
 **/
public interface TestService {

    String getMsg();
}

建立服務提供者

匯入依賴

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-dubbo</artifactId>
        </dependency>
        <!-- api介面的依賴包-->
        <dependency>
            <groupId>com.dubbo.demo</groupId>
            <artifactId>dubbo-demo-api</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

編寫配置

dubbo:
	scan:
	# dubbo 服務掃描基準包
		base-packages: org.springframework.cloud.alibaba.dubbo.bootstrap
	protocol:
	# dubbo 協議
		name: dubbo
		# dubbo 協議埠( -1 表示自增埠,從 20880 開始)
		port: -1
	spring:
		cloud:
			nacos:
			# Nacos 服務發現與註冊配置
			discovery:
				server-addr: 127.0.0.1:8848實現
/**
 * @author 原
 * @date 2021/1/28
 * @since 1.0
 **/
@Service//dubbo的service註解
public class TestServiceImpl implements TestService {
    @Override
    public String getMsg() {
        return "123123";
    }
}

啟動類

/**
 * @author 原
 * @date 2021/1/28
 * @since 1.0
 **/
@SpringBootApplication
@EnableDiscoveryClient
public class DubboProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(DubboProviderApplication.class,args);
    }
}

建立服務消費者

除了api的實現類 其他複用提供者的程式碼

編寫測試類

/**
 * @author 原
 * @date 2021/1/28
 * @since 1.0
 **/
@RestController
public class TestController {

    @Reference
    TestService testService;

    @GetMapping("/dubbo/test")
    public String getMsg(){
        return testService.getMsg();
    }
}

訪問:

返回111

服務流量管理

為什麼要流控降級

流量是非常隨機性的、不可預測的。前一秒可能還風平浪靜,後一秒可能就出現流量洪峰了(例如雙十一零點的場景)。然而我們系統的容量總是有限的,如果突然而來的流量超過了系統的承受能力,就可能會導致請求處理不過來,堆積的請求處理緩慢,CPU/Load飆高,最後導致系統崩潰。因此,我們需要針對這種突發的流量來進行限制,在儘可能處理請求的同時來保障服務不被打垮,這就是流量控制

一個服務常常會呼叫別的模組,可能是另外的一個遠端服務、資料庫,或者第三方API 等。例如,支付的時候,可能需要遠端呼叫銀聯提供的 API;查詢某個商品的價格,可能需要進行資料庫查詢。然而,這個被依賴服務的穩定性是不能保證的。如果依賴的服務出現了不穩定的情況,請求的響應時間變長,那麼呼叫服務的方法的響應時間也會變長,執行緒會產生堆積,最終可能耗盡業務自身的執行緒池,服務本身也變得不可用。

現代微服務架構都是分散式的,由非常多的服務組成。不同服務之間相互呼叫,組成複雜的呼叫鏈路。以上的問題在鏈路呼叫中會產生放大的效果。複雜鏈路上的某一環不穩定,就可能會層層級聯, 最終導致整個鏈路都不可用。 因此我們需要對不穩定的弱依賴服務進行熔斷降級,暫時切斷不穩定呼叫,避免區域性不穩定因素導致整體的雪崩。

關於容錯元件的停更/升級/替換

服務降級:
Hystrix:官網不極力推薦,但是中國企業中還在大規模使用,對於限流和熔斷降級雖然在1 .5版本官方還支援(版本穩定),
但現在官方已經開始推薦大家使用Resilience4j
Resilience4J:官網推薦使用,但是國內很少用這個。
Sentienl:來自於Spring Cloud Alibaba,在中國企業替換Hystrix的元件,國內強烈建議使用

這裡就主要介紹下Sentinel。

Sentinel介紹

Sentinel是阿里開源的專案,提供了流量控制、熔斷降級、系統負載保護等多個維度來保障服務之間的穩定性。
Sentinel的流控操作起來非常簡單,在控制檯進行配置即可看見效,所見即所得

Sentinel 具有以下特徵:

  • 豐富的應用場景:Sentinel 承接了阿里巴巴近 10 年的雙十一大促流量的核心場景,例如秒殺(即突發流量控制在系統容量可以承受的範圍)、訊息削峰填谷、叢集流量控制、實時熔斷下游不可用應用等。
  • 完備的實時監控:Sentinel 同時提供實時的監控功能。您可以在控制檯中看到接入應用的單臺機器秒級資料,甚至 500 臺以下規模的叢集的彙總執行情況。
  • 廣泛的開源生態:Sentinel 提供開箱即用的與其它開源框架/庫的整合模組,例如與 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相應的依賴並進行簡單的配置即可快速地接入 Sentinel。
  • 完善的 SPI 擴充套件點:Sentinel 提供簡單易用、完善的 SPI 擴充套件介面。您可以通過實現擴充套件介面來快速地定製邏輯。例如定製規則管理、適配動態資料來源等。

官網

https://github.com/alibaba/Sentinel
中文
https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
https://sentinelguard.io/zh-cn/docs/introduction.html

Sentinel 的使用可以分為兩個部分:
核心庫(Java 客戶端):不依賴任何框架/庫,能夠執行於 Java 7 及以上的版本的執行時環境,同時對Dubbo / Spring Cloud 等框架也有較好的支援。
控制檯(Dashboard):控制檯主要負責管理推送規則、監控、叢集限流分配管理、機器發現等

使用場景

 在服務提供方(Service Provider)的場景下,我們需要保護服務提供方自身不被流量洪峰打垮。 這時候通常根據服務提供方的服務能力進行流量控制, 或針對特定的服務呼叫方進行限制。我們可以結合前期壓測評估核心口的承受能力,配置 QPS 模式的限流,當每秒的請求量超過設定的閾值時,會自動拒絕多餘的請求。

 為了避免呼叫其他服務時被不穩定的服務拖垮自身,我們需要在服務呼叫端(Service Consumer)對不穩定服務依賴進行隔離和熔斷。手段包括訊號量隔離、異常比例降級、RT 降級等多種手段。

 當系統長期處於低水位的情況下, 流量突然增加時, 直接把系統拉昇到高水位可能瞬間把系統壓垮。這時候我們可以藉助 Sentinel 的 WarmUp 流控模式控制通過的流量緩慢增加,在一定時間內逐漸增加到閾值上限,而不是在一瞬間全部放行。這樣可以給冷系統一個預熱的時間,避免冷系統被壓垮。

 利用 Sentinel 的勻速排隊模式進行“削峰填谷”, 把請求突刺均攤到一段時間內, 讓系統負載保持在請求處理水位之內,同時儘可能地處理更多請求。

 利用 Sentinel 的閘道器流控特性,在閘道器入口處進行流量防護,或限制 API 的呼叫頻率。

Sentinel安裝

1、下載jar包https://github.com/alibaba/Sentinel/releases

2、啟動

java -Dserver.port=8787 -Dcsp.sentinel.dashboard.server=127.0.0.1:8787 -Dproject.name=sentinel-dashboard -jar /home/sentinel/sentinel-dashboard-1.8.0.jar

3、訪問

http://127.0.0.1:8787/#/login

初始賬號密碼sentinel/sentinel

可以看到sentinel是自己本身的監控

sentinel快速入門

1、匯入依賴

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.8.0</version>
</dependency>

2、測試類

public class TestService {

    public static void main(String[] args) {
        initFlowRules();
        while (true) {
            Entry entry = null;
            try {
                entry = SphU.entry("HelloWorld");
                /*您的業務邏輯 - 開始*/
                System.out.println("hello world");
                /*您的業務邏輯 - 結束*/
            } catch (BlockException e1) {
                /*流控邏輯處理 - 開始*/
                System.out.println("block!");
                /*流控邏輯處理 - 結束*/
            } finally {
                if (entry != null) {
                    entry.exit();
                }
            }
        }
    }

    //設定流量控制規則 設定當QPS達到20時 會限制流量(丟擲異常,可以執行處理)
    private static void initFlowRules(){
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setResource("HelloWorld");
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // Set limit QPS to 20.
        rule.setCount(20);
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }
}

執行結果:

可以看到,這個程式每秒穩定輸出 "hello world" 20 次,和規則中預先設定的閾值是一樣的。 block表示被阻止的請求。

官方使用文件:https://github.com/alibaba/Sentinel/wiki/如何使用

Sentinel整合SpringCloud實現服務限流/熔斷

匯入依賴

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
    </dependencies>

配置

server.port=8082
spring.application.name=sentinel-demo
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.sentinel.transport.dashboard=127.0.0.1:8787
//在需要流控的方法加上@SentinelResource
@RestController
public class TestController {

    @GetMapping("/sentinel")
    @SentinelResource
    public String getMsg(){
        return "11";
    }
}

啟動應用,訪問http://127.0.0.1:8082/sentinel後去sentinel後臺

下面我們來配一條最簡單的流控規則。針對 sentinel_spring_web_context /sentinel 這個服務呼叫配置限流規則(需要有過訪問量才能看到)。我們配一條 QPS 為 1的流控規則,這代表針對該服務方法的呼叫每秒鐘不能超過 1 次,超出會直接拒絕。

現在快速訪問:http://localhost:8082/sentinel

檢視實時監控頁面:

其他功能的使用,大家可以參考官方文件自行摸索。

如何選擇流控降級元件

以下是 Sent inel 與其它fault-tolerance 元件的對比:

分散式事務

待續...

相關文章