Spring Cloud 系列之 Alibaba Nacos 註冊中心(一)

前言

從本章節開始,我們學習 Spring Cloud Alibaba 相關微服務組件。

Spring Cloud Alibaba 介紹

Spring Cloud Alibaba 致力於提供微服務開發的一站式解決方案。此項目包含開發分佈式應用微服務的必需組件,方便開發者通過 Spring Cloud 編程模型輕鬆使用這些組件來開發分佈式應用服務。

依託 Spring Cloud Alibaba,只需要添加一些註解和少量配置,就可以將 Spring Cloud 應用接入阿里微服務解決方案,通過阿里中間件來迅速搭建分佈式應用系統。

Spring Cloud Alibaba 功能

  1. 服務限流降級 Sentinel:支持 WebServlet,WebFlux,OpenFeign,RestTemplate,Dubbo,Gateway,Zuul 限流降級功能的接入。可以在運行時通過控制檯實時修改限流降級規則,並且還支持限流降級度量指標監控。
  2. 服務註冊與發現 Nacos:適配 Spring Cloud 服務註冊與發現標準,默認集成了 Ribbon 的支持。
  3. 分佈式配置管理 Nacos:支持分佈式系統中的外部化配置,配置更改時自動刷新。
  4. RPC 服務 Dubbo:擴展 Spring Cloud 客戶端 RestTemplate 和 OpenFeign 以支持調用 Dubbo RPC 服務。
  5. 消息驅動 RocketMQ:基於 Spring Cloud Stream 爲微服務應用構建消息驅動能力。
  6. 分佈式事務 Seata:支持高性能且易於使用的分佈式事務解決方案。
  7. 阿里雲對象存儲 OSS:大規模,安全,低成本,高度可靠的雲存儲服務。支持隨時隨地在任何應用程序中存儲和訪問任何類型的數據。
  8. 分佈式任務調度 SchedulerX:提供秒級、精準、高可靠、高可用的定時(基於 Cron 表達式)任務調度服務。同時提供分佈式的任務執行模型,如網格任務。網格任務支持海量子任務均勻分配到所有 Worker(schedulerx-client)上執行。
  9. 阿里雲短信服務 SMS:覆蓋全球的短信服務,友好、高效、智能的通訊能力,幫助企業迅速搭建客戶觸達通道。

Spring Cloud Alibaba 組件

  • Nacos:阿里巴巴開源產品,一個更易於構建雲原生應用的動態服務發現、配置管理和服務管理平臺。
  • Sentinel:面向分佈式服務架構的輕量級流量控制產品,把流量作爲切入點,從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性。
  • RocketMQ:一款開源的分佈式消息系統,基於高可用分佈式集羣技術,提供低延時的、高可靠的消息發佈與訂閱服務。
  • Dubbo:Apache Dubbo™ 是一款高性能 Java RPC 框架。
  • Seata:阿里巴巴開源產品,一個易於使用的高性能微服務分佈式事務解決方案。
  • Alibaba Cloud ACM:一款在分佈式架構環境中對應用配置進行集中管理和推送的應用配置中心產品。
  • Alibaba Cloud OSS:阿里雲對象存儲服務(Object Storage Service,簡稱 OSS),是阿里雲提供的海量、安全、低成本、高可靠的雲存儲服務。您可以在任何應用、任何時間、任何地點存儲和訪問任意類型的數據。
  • Alibaba Cloud SchedulerX:阿里中間件團隊開發的一款分佈式任務調度產品,提供秒級、精準、高可靠、高可用的定時(基於 Cron 表達式)任務調度服務。
  • Alibaba Cloud SMS:覆蓋全球的短信服務,友好、高效、智能的互聯化通訊能力,幫助企業迅速搭建客戶觸達通道。

什麼是註冊中心

服務註冊中心是服務實現服務化管理的核心組件,類似於目錄服務的作用,主要用來存儲服務信息,譬如提供者 url 串、路由信息等。服務註冊中心是微服務架構中最基礎的設施之一。

註冊中心可以說是微服務架構中的“通訊錄”,它記錄了服務和服務地址的映射關係。在分佈式架構中,服務會註冊到這裏,當服務需要調用其它服務時,就到這裏找到服務的地址,進行調用。

簡單理解就是:在沒有註冊中心時候,服務間調用需要知道被當服務調方的具體地址(寫死的 ip:port)。更換部署地址,就不得不修改調用當中指定的地址。而有了註冊中心之後,每個服務在調用別人的時候只需要知道服務名稱(軟編碼)就好,地址都會通過註冊中心根據服務名稱獲取到具體的服務地址進行調用。

舉個現實生活中的例子,比如說,我們手機中的通訊錄的兩個使用場景:

當我想給張三打電話時,那我需要在通訊錄中按照名字找到張三,然後就可以找到他的手機號撥打電話。—— 服務發現

李四辦了手機號並把手機號告訴了我,我把李四的號碼存進通訊錄,後續,我就可以從通訊錄找到他。—— 服務註冊

通訊錄 —— ?什麼角色(服務註冊中心)

總結:服務註冊中心的作用就是服務的註冊服務的發現

常見的註冊中心

  • Netflix Eureka
  • Alibaba Nacos
  • HashiCorp Consul
  • Apache ZooKeeper
  • CoreOS Etcd
  • CNCF CoreDNS

特性 Eureka Nacos Consul Zookeeper
CAP AP CP + AP CP CP
健康檢查 Client Beat TCP/HTTP/MYSQL/Client Beat TCP/HTTP/gRPC/Cmd Keep Alive
雪崩保護
自動註銷實例 支持 支持 不支持 支持
訪問協議 HTTP HTTP/DNS HTTP/DNS TCP
監聽支持 支持 支持 支持 支持
多數據中心 支持 支持 支持 不支持
跨註冊中心同步 不支持 支持 支持 不支持
SpringCloud集成 支持 支持 支持 支持

CAP 原則與 BASE 理論

CAP 原則

CAP 原則又稱 CAP 定理,指的是在一個分佈式系統中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分區容錯性),三者不可得兼。

CAP 由 Eric Brewer 在 2000 年 PODC 會議上提出。該猜想在提出兩年後被證明成立,成爲我們熟知的 CAP 定理。CAP 三者不可兼得。

特性 定理
Consistency 也叫做數據原子性,系統在執行某項操作後仍然處於一致的狀態。在分佈式系統中,更新操作執行成功後所有的用戶都應該讀到最新的值,這樣的系統被認爲是具有強一致性的。等同於所有節點訪問同一份最新的數據副本。
Availability 每一個操作總是能夠在一定的時間內返回結果,這裏需要注意的是"一定時間內"和"返回結果"。一定時間內指的是,在可以容忍的範圍內返回結果,結果可以是成功或者是失敗。
Partition tolerance 在網絡分區的情況下,被分隔的節點仍能正常對外提供服務(分佈式集羣,數據被分佈存儲在不同的服務器上,無論什麼情況,服務器都能正常被訪問)。

取捨策略

CAP 三個特性只能滿足其中兩個,那麼取捨的策略就共有三種:

  • CA without P:如果不要求P(不允許分區),則C(強一致性)和A(可用性)是可以保證的。但放棄 P 的同時也就意味着放棄了系統的擴展性,也就是分佈式節點受限,沒辦法部署子節點,這是違背分佈式系統設計的初衷的。
  • CP without A:如果不要求A(可用),相當於每個請求都需要在服務器之間保持強一致,而P(分區)會導致同步時間無限延長(也就是等待數據同步完才能正常訪問服務),一旦發生網絡故障或者消息丟失等情況,就要犧牲用戶的體驗,等待所有數據全部一致了之後再讓用戶訪問系統。設計成 CP 的系統其實不少,最典型的就是分佈式數據庫,如 Redis、HBase 等。對於這些分佈式數據庫來說,數據的一致性是最基本的要求,因爲如果連這個標準都達不到,那麼直接採用關係型數據庫就好,沒必要再浪費資源來部署分佈式數據庫。
  • AP without C:要高可用並允許分區,則需放棄一致性。一旦分區發生,節點之間可能會失去聯繫,爲了高可用,每個節點只能用本地數據提供服務,而這樣會導致全局數據的不一致性。典型的應用就如某米的搶購手機場景,可能前幾秒你瀏覽商品的時候頁面提示是有庫存的,當你選擇完商品準備下單的時候,系統提示你下單失敗,商品已售完。這其實就是先在 A(可用性)方面保證系統可以正常的服務,然後在數據的一致性方面做了些犧牲,雖然多少會影響一些用戶體驗,但也不至於造成用戶購物流程的嚴重阻塞。

總結

現如今,對於多數大型互聯網應用的場景,主機衆多、部署分散,而且現在的集羣規模越來越大,節點只會越來越多,所以節點故障、網絡故障是常態,因此分區容錯性也就成爲了一個分佈式系統必然要面對的問題。那麼就只能在 C 和 A 之間進行取捨。但對於傳統的項目就可能有所不同,拿銀行的轉賬系統來說,涉及到金錢的對於數據一致性不能做出一絲的讓步,C 必須保證,出現網絡故障的話,寧可停止服務,可以在 A 和 P 之間做取捨。

總而言之,沒有最好的策略,好的系統應該是根據業務場景來進行架構設計的,只有適合的纔是最好的。

BASE 理論

CAP 理論已經提出好多年了,難道真的沒有辦法解決這個問題嗎?也許可以做些改變。比如 C 不必使用那麼強的一致性,可以先將數據存起來,稍後再更新,實現所謂的 “最終一致性”。

這個思路又是一個龐大的問題,同時也引出了第二個理論 BASE 理論。

BASE:全稱 Basically Available(基本可用),Soft state(軟狀態),和 Eventually consistent(最終一致性)三個短語的縮寫,來自 ebay 的架構師提出。

BASE 理論是對 CAP 中一致性和可用性權衡的結果,其來源於對大型互聯網分佈式實踐的總結,是基於 CAP 定理逐步演化而來的。其核心思想是:

既然無法做到強一致性(Strong consistency),但每個應用都可以根據自身的業務特點,採用適當的方式來使系統達到最終一致性(Eventual consistency)。

Basically Available(基本可用)

基本可用是指分佈式系統在出現故障的時候,允許損失部分可用性(例如響應時間、功能上的可用性)。需要注意的是,基本可用絕不等價於系統不可用。

  • 響應時間上的損失:正常情況下搜索引擎需要在 0.5 秒之內返回給用戶相應的查詢結果,但由於出現故障(比如系統部分機房發生斷電或斷網故障),查詢結果的響應時間增加到了 1~2 秒。
  • 功能上的損失:購物網站在購物高峯(如雙十一)時,爲了保護系統的穩定性,部分消費者可能會被引導到一個降級頁面。

Soft state(軟狀態)

什麼是軟狀態呢?相對於原子性而言,要求多個節點的數據副本都是一致的,這是一種 “硬狀態”。

軟狀態是指允許系統存在中間狀態,而該中間狀態不會影響系統整體可用性。分佈式存儲中一般一份數據會有多個副本,允許不同副本數據同步的延時就是軟狀態的體現。

Eventually consistent(最終一致性)

系統不可能一直是軟狀態,必須有個時間期限。在期限過後,應當保證所有副本保持數據一致性。從而達到數據的最終一致性。這個時間期限取決於網絡延時,系統負載,數據複製方案設計等等因素。

實際上,不只是分佈式系統使用最終一致性,關係型數據庫在某個功能上,也是使用最終一致性的,比如備份,數據庫的複製都是需要時間的,這個複製過程中,業務讀取到的值就是舊值。當然,最終還是達成了數據一致性。這也算是一個最終一致性的經典案例。

總結

總的來說,BASE 理論面向的是大型高可用可擴展的分佈式系統,和傳統事務的 ACID 是相反的,它完全不同於 ACID 的強一致性模型,而是通過犧牲強一致性來獲得可用性,並允許數據在一段時間是不一致的。

爲什麼需要註冊中心

瞭解了什麼是註冊中心,那麼我們繼續談談,爲什麼需要註冊中心。在分佈式系統中,我們不僅僅是需要在註冊中心找到服務和服務地址的映射關係這麼簡單,我們還需要考慮更多更復雜的問題:

  • 服務註冊後,如何被及時發現
  • 服務宕機後,如何及時下線
  • 服務如何有效的水平擴展
  • 服務發現時,如何進行路由
  • 服務異常時,如何進行降級
  • 註冊中心如何實現自身的高可用

這些問題的解決都依賴於註冊中心。簡單看,註冊中心的功能有點類似於 DNS 服務器或者負載均衡器,而實際上,註冊中心作爲微服務的基礎組件,可能要更加複雜,也需要更多的靈活性和時效性。所以我們還需要學習更多 Spring Cloud 微服務組件協同完成應用開發。

註冊中心解決了以下問題:

  • 服務管理
  • 服務之間的自動發現
  • 服務的依賴關係管理

Nacos 介紹

Nacos 是 Alibaba 公司推出的開源工具,用於實現分佈式系統的服務發現與配置管理。英文全稱 Dynamic Naming and Configuration Service,Na 爲 Naming/NameServer 即註冊中心,co 爲 Configuration 即配置中心,Service 是指該註冊/配置中心都是以服務爲核心。服務(Service)是 Nacos 世界的一等公民。

官網是這樣說的:一個更易於構建雲原生應用的動態服務發現、配置管理和服務管理平臺。

Nacos 致力於發現、配置和管理微服務。Nacos 提供了一組簡單易用的特性集,可以快速實現動態服務發現、服務配置、服務元數據及流量管理。

Nacos 可以更敏捷和容易地構建、交付和管理微服務平臺。 Nacos 是構建以“服務”爲中心的現代應用架構的服務基礎設施。

使用 Nacos 簡化服務發現、配置管理、服務治理及管理的解決方案,讓微服務的發現、管理、共享、組合更加容易。

Nacos 官網:https://nacos.io/zh-cn/

Github:https://github.com/alibaba/nacos

Nacos 安裝

環境準備

Nacos 依賴 Java 環境來運行。如果您是從代碼開始構建並運行 Nacos,還需要爲此配置 Maven 環境,請確保是在以下版本環境中安裝使用:

  • JDK 1.8+;
  • Maven 3.2.x+。

下載源碼或者安裝包

可以通過源碼和發行包兩種方式來獲取 Nacos。

源碼方式

從 Github 上下載源碼方式。

git clone https://github.com/alibaba/nacos.git
cd nacos/
mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U  
ls -al distribution/target/

// change the $version to your actual path
cd distribution/target/nacos-server-$version/nacos/bin

發行包方式

您可以從 https://github.com/alibaba/nacos/releases 下載最新穩定版本的 nacos-server 包。

啓動服務器

Linux/Unix/Mac

在 Nacos 的解壓目錄 nacos/bin 目錄下啓動。

啓動命令(standalone 代表着單機模式運行,非集羣模式):

sh startup.sh -m standalone

如果您使用的是 ubuntu 系統,或者運行腳本報錯提示符號找不到,可嘗試如下運行:

bash startup.sh -m standalone

Windows

啓動命令:

cmd startup.cmd

或者雙擊 startup.cmd 運行文件。

訪問

訪問:http://localhost:8848/nacos/ ,默認用戶名/密碼是 nacos/nacos。

關閉服務器

Linux/Unix/Mac

sh shutdown.sh

Windows

cmd shutdown.cmd

或者雙擊 shutdown.cmd 運行文件。

Nacos 入門案例

創建項目

我們創建聚合項目來講解 Nacos,首先創建一個 pom 父工程。

添加依賴

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- 項目座標地址 -->
    <groupId>com.example</groupId>
    <!-- 項目模塊名稱 -->
    <artifactId>nacos-demo</artifactId>
    <!-- 項目版本名稱 快照版本SNAPSHOT、正式版本RELEASE -->
    <version>1.0-SNAPSHOT</version>

    <!-- 繼承 spring-boot-starter-parent 依賴 -->
    <!-- 使用繼承方式,實現複用,符合繼承的都可以被使用 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.RELEASE</version>
    </parent>

    <!--
        集中定義依賴組件版本號,但不引入,
        在子工程中用到聲明的依賴時,可以不加依賴的版本號,
        這樣可以統一管理工程中用到的依賴版本
     -->
    <properties>
        <!-- Spring Cloud Hoxton.SR4 依賴 -->
        <spring-cloud.version>Hoxton.SR4</spring-cloud.version>
        <!-- spring cloud alibaba 依賴 -->
        <spring-cloud-alibaba.version>2.1.0.RELEASE</spring-cloud-alibaba.version>
    </properties>

    <!-- 項目依賴管理 父項目只是聲明依賴,子項目需要寫明需要的依賴(可以省略版本信息) -->
    <dependencyManagement>
        <dependencies>
            <!-- spring cloud 依賴 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- spring cloud alibaba 依賴 -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

商品服務 product-service

創建項目

在剛纔的父工程下創建一個 product-service 項目。

添加依賴

主要添加 spring-cloud-starter-alibaba-nacos-discovery 依賴。

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

完整依賴如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
    <!-- 繼承父依賴 -->
    <parent>
        <artifactId>nacos-demo</artifactId>
        <groupId>com.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>product-service</artifactId>

    <!-- 項目依賴 -->
    <dependencies>
        <!-- spring cloud alibaba nacos discovery 依賴 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- spring boot web 依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- lombok 依賴 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

        <!-- spring boot test 依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

</project>

配置文件

application.yml

server:
  port: 7070 # 端口

spring:
  application:
    name: product-service # 應用名稱
  # 配置 Nacos 註冊中心
  cloud:
    nacos:
      discovery:
        enabled: true # 如果不想使用 Nacos 進行服務註冊和發現,設置爲 false 即可
        server-addr: 127.0.0.1:8848 # Nacos 服務器地址

實體類

Product.java

package com.example.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product implements Serializable {

    private Integer id;
    private String productName;
    private Integer productNum;
    private Double productPrice;

}

編寫服務

ProductService.java

package com.example.service;

import com.example.pojo.Product;

import java.util.List;

/**
 * 商品服務
 */
public interface ProductService {

    /**
     * 查詢商品列表
     *
     * @return
     */
    List<Product> selectProductList();

}

ProductServiceImpl.java

package com.example.service.impl;

import com.example.pojo.Product;
import com.example.service.ProductService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;

/**
 * 商品服務
 */
@Slf4j
@Service
public class ProductServiceImpl implements ProductService {

    /**
     * 查詢商品列表
     *
     * @return
     */
    @Override
    public List<Product> selectProductList() {
        log.info("商品服務查詢商品信息...");
        return Arrays.asList(
                new Product(1, "華爲手機", 1, 5800D),
                new Product(2, "聯想筆記本", 1, 6888D),
                new Product(3, "小米平板", 5, 2020D)
        );
    }

}

控制層

ProductController.java

package com.example.controller;

import com.example.pojo.Product;
import com.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/product")
public class ProductController {

    @Autowired
    private ProductService productService;

    /**
     * 查詢商品列表
     *
     * @return
     */
    @GetMapping("/list")
    public List<Product> selectProductList() {
        return productService.selectProductList();
    }

}

該項目我們可以通過單元測試進行測試,也可以直接通過 url 使用 postman 或者瀏覽器來進行測試。

啓動類

通過 Spring Cloud 原生註解 @EnableDiscoveryClient 開啓服務註冊發現功能。

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// 開啓 @EnableDiscoveryClient 註解,當前版本默認會開啓該註解
//@EnableDiscoveryClient
@SpringBootApplication
public class ProductServiceApplication {

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

}

註冊中心

刷新 Nacos 服務器,可以看到服務已註冊至 Nacos。

訂單服務 order-service

創建項目

在剛纔的父工程下創建一個 order-service 項目。

添加依賴

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <!-- 繼承父依賴 -->
    <parent>
        <artifactId>nacos-demo</artifactId>
        <groupId>com.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>order-service</artifactId>

    <!-- 項目依賴 -->
    <dependencies>
        <!-- spring cloud alibaba nacos discovery 依賴 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- spring boot web 依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- lombok 依賴 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

        <!-- spring boot test 依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    
</project>

配置文件

application.yml

server:
  port: 9090 # 端口

spring:
  application:
    name: order-service # 應用名稱
  # 配置 Nacos 註冊中心
  cloud:
    nacos:
      discovery:
        enabled: true # 如果不想使用 Nacos 進行服務註冊和發現,設置爲 false 即可
        server-addr: 127.0.0.1:8848 # Nacos 服務器地址

實體類

Product.java

package com.example.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product implements Serializable {

    private Integer id;
    private String productName;
    private Integer productNum;
    private Double productPrice;

}

Order.java

package com.example.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order implements Serializable {

    private Integer id;
    private String orderNo;
    private String orderAddress;
    private Double totalPrice;
    private List<Product> productList;

}

消費服務

OrderService.java

package com.example.service;

import com.example.pojo.Order;

public interface OrderService {

    /**
     * 根據主鍵查詢訂單
     *
     * @param id
     * @return
     */
    Order selectOrderById(Integer id);

}

對於服務的消費我們這裏講三種實現方式:

  • DiscoveryClient:通過元數據獲取服務信息
  • LoadBalancerClient:Ribbon 的負載均衡器
  • @LoadBalanced:通過註解開啓 Ribbon 的負載均衡器

DiscoveryClient

Spring Boot 不提供任何自動配置的RestTemplate bean,所以需要在啓動類中注入 RestTemplate

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

// 開啓 @EnableDiscoveryClient 註解,當前版本默認會開啓該註解
//@EnableDiscoveryClient
@SpringBootApplication
public class OrderServiceApplication {

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

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

}

OrderServiceImpl.java

package com.example.service.impl;

import com.alibaba.fastjson.JSON;
import com.example.pojo.Order;
import com.example.pojo.Product;
import com.example.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    /**
     * 根據主鍵查詢訂單
     *
     * @param id
     * @return
     */
    @Override
    public Order selectOrderById(Integer id) {
        log.info("訂單服務查詢訂單信息...");
        return new Order(id, "order-001", "中國", 22788D,
                selectProductListByDiscoveryClient());
    }

    private List<Product> selectProductListByDiscoveryClient() {
        StringBuffer sb = null;

        // 獲取服務列表
        List<String> serviceIds = discoveryClient.getServices();
        if (CollectionUtils.isEmpty(serviceIds))
            return null;

        // 根據服務名稱獲取服務
        List<ServiceInstance> serviceInstances = discoveryClient.getInstances("product-service");
        if (CollectionUtils.isEmpty(serviceInstances))
            return null;

        // 構建遠程服務調用地址
        ServiceInstance si = serviceInstances.get(0);
        sb = new StringBuffer();
        sb.append("http://" + si.getHost() + ":" + si.getPort() + "/product/list");
        log.info("訂單服務調用商品服務...");
        log.info("從註冊中心獲取到的商品服務地址爲:{}", sb.toString());

        // 遠程調用服務
        // ResponseEntity: 封裝了返回數據
        ResponseEntity<List<Product>> response = restTemplate.exchange(
                sb.toString(),
                HttpMethod.GET,
                null,
                new ParameterizedTypeReference<List<Product>>() {});
        log.info("商品信息查詢結果爲:{}", JSON.toJSONString(response.getBody()));
        return response.getBody();
    }

}

LoadBalancerClient

OrderServiceImpl.java

package com.example.service.impl;

import com.alibaba.fastjson.JSON;
import com.example.pojo.Order;
import com.example.pojo.Product;
import com.example.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private LoadBalancerClient loadBalancerClient; // Ribbon 負載均衡器

    /**
     * 根據主鍵查詢訂單
     *
     * @param id
     * @return
     */
    @Override
    public Order selectOrderById(Integer id) {
        log.info("訂單服務查詢訂單信息...");
        return new Order(id, "order-001", "中國", 22788D,
                selectProductListByLoadBalancerClient());
    }

    private List<Product> selectProductListByLoadBalancerClient() {
        StringBuffer sb = null;

        // 根據服務名稱獲取服務
        ServiceInstance si = loadBalancerClient.choose("product-service");
        if (null == si)
            return null;

        sb = new StringBuffer();
        sb.append("http://" + si.getHost() + ":" + si.getPort() + "/product/list");
        log.info("訂單服務調用商品服務...");
        log.info("從註冊中心獲取到的商品服務地址爲:{}", sb.toString());

        // ResponseEntity: 封裝了返回數據
        ResponseEntity<List<Product>> response = restTemplate.exchange(
                sb.toString(),
                HttpMethod.GET,
                null,
                new ParameterizedTypeReference<List<Product>>() {});
        log.info("商品信息查詢結果爲:{}", JSON.toJSONString(response.getBody()));
        return response.getBody();
    }

}

@LoadBalanced

啓動類注入 RestTemplate 時添加 @LoadBalanced 負載均衡註解,表示這個 RestTemplate 在請求時擁有客戶端負載均衡的能力。

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@EnableDiscoveryClient
@SpringBootApplication
public class OrderServiceApplication {

    @Bean
    @LoadBalanced // 負載均衡註解
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

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

}

OrderServiceImpl.java

package com.example.service.impl;

import com.alibaba.fastjson.JSON;
import com.example.pojo.Order;
import com.example.pojo.Product;
import com.example.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 根據主鍵查詢訂單
     *
     * @param id
     * @return
     */
    @Override
    public Order selectOrderById(Integer id) {
        log.info("訂單服務查詢訂單信息...");
        return new Order(id, "order-001", "中國", 22788D,
                selectProductListByLoadBalancerAnnotation());
    }

    private List<Product> selectProductListByLoadBalancerAnnotation() {
        String url = "http://product-service/product/list";
        log.info("訂單服務調用商品服務...");
        log.info("從註冊中心獲取到的商品服務地址爲:{}", url);
        // ResponseEntity: 封裝了返回數據
        ResponseEntity<List<Product>> response = restTemplate.exchange(
                url,
                HttpMethod.GET,
                null,
                new ParameterizedTypeReference<List<Product>>() {});
        log.info("商品信息查詢結果爲:{}", JSON.toJSONString(response.getBody()));
        return response.getBody();
    }

}

控制層

OrderController.java

package com.example.controller;

import com.example.pojo.Order;
import com.example.service.OrderService;
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.RestController;

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private OrderService orderService;

    /**
     * 根據主鍵查詢訂單
     *
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public Order selectOrderById(@PathVariable("id") Integer id) {
        return orderService.selectOrderById(id);
    }

}

訪問

刷新 Nacos 服務器,可以看到服務已註冊至 Nacos。

訪問:http://localhost:9090/order/1 結果如下:

配置 MySQL 數據庫

Nacos 在 0.7 版本之前,默認使用的是嵌入式數據庫 Apache Derby 來存儲數據(內嵌的數據庫會隨着 Nacos 一起啓動,無需額外安裝);0.7 版本及以後,增加了對 MySQL 數據源的支持。

MySQL數據源

環境要求:MySQL 5.6.5+(生產使用建議至少主備模式,或者採用高可用數據庫);

初始化 MySQL 數據庫

創建數據庫 nacos_config

SQL源文件地址:https://github.com/alibaba/nacos/blob/master/distribution/conf/nacos-mysql.sql ,或者在 nacos-server 解壓目錄 conf 下,找到 nacos-mysql.sql 文件,運行該文件,結果如下:

application.properties 配置

修改 nacos/conf/application.properties 文件的以下內容。

最終修改結果如下:

#*************** Config Module Related Configurations ***************#
### If user MySQL as datasource:
# 指定數據源爲 MySQL
spring.datasource.platform=mysql

### Count of DB:
# 數據庫實例數量
db.num=1

# 數據庫連接信息,如果是 MySQL 8.0+ 版本需要添加 serverTimezone=Asia/Shanghai
### Connect URL of DB:
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&serverTimezone=Asia/Shanghai
db.user=root
db.password=1234

如果你和我一樣使用的是 MySQL 8.0+ 版本,那麼啓動 Nacos 時肯定會報錯。莫慌,在 Nacos 安裝目錄下新建 plugins/mysql 文件夾,並放入 8.0+ 版本的 mysql-connector-java-8.0.xx.jar,重啓 Nacos 即可,啓動時會提示更換了 MySQL 的 driver-class 類。

下一篇我們講解 Nacos 註冊中心集羣環境搭建,記得關注噢~

本文采用 知識共享「署名-非商業性使用-禁止演繹 4.0 國際」許可協議

大家可以通過 分類 查看更多關於 Spring Cloud 的文章。


🤗 您的點贊轉發是對我最大的支持。

📢 掃碼關注 哈嘍沃德先生「文檔 + 視頻」每篇文章都配有專門視頻講解,學習更輕鬆噢 ~

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