Spring Cloud筆記(4)構(gòu)建Spring Cloud Demo

通過(guò)前幾篇文章的積累,我們現(xiàn)在可以來(lái)動(dòng)手搭建一個(gè)完整的Spring Cloud Demo項(xiàng)目了捎谨。為了更清楚的說(shuō)明Spring Cloud的結(jié)構(gòu)特點(diǎn)症革,我們的demo項(xiàng)目還是遵循由淺入深的原則,一開(kāi)始只加入一些基本的特性强戴,后面再來(lái)逐步完善悉患。

業(yè)務(wù)背景

本來(lái)演示技術(shù)點(diǎn)的demo残家,弄一些sayHello的方法出來(lái)也無(wú)可厚非。但Spring Cloud的很多特性都是與業(yè)務(wù)的實(shí)際需求緊密結(jié)合的售躁,脫離業(yè)務(wù)談技術(shù)難免顯得有些空洞跪削,所以我們也需要為demo弄一個(gè)簡(jiǎn)單的業(yè)務(wù)背景谴仙。就用常見(jiàn)的訂單管理和倉(cāng)儲(chǔ)管理來(lái)舉例子吧迂求,一個(gè)基本的業(yè)務(wù)流程就是客戶創(chuàng)建訂單 碾盐,如下圖:


客戶下單流程.png

為此我們需要?jiǎng)?chuàng)建兩個(gè)微服務(wù)模塊:訂單服務(wù)和倉(cāng)儲(chǔ)服務(wù),訂單服務(wù)提供創(chuàng)建訂單的接口揩局,創(chuàng)建訂單的同時(shí)需要調(diào)用倉(cāng)儲(chǔ)服務(wù)提供的接口來(lái)對(duì)庫(kù)存進(jìn)行更新毫玖。

組件選擇和環(huán)境搭建

之前我們介紹過(guò),每一種Spring Cloud的特性的實(shí)現(xiàn)都有好幾種不同的框架和工具進(jìn)行選擇凌盯,根據(jù)官方的推薦和技術(shù)的流行度付枫,demo主要采用了以下的框架和工具:

  • Consul:用于服務(wù)的注冊(cè)和發(fā)現(xiàn)
  • OpenFeign:聲明式HTTP客戶端,服務(wù)間調(diào)用
  • Spring Cloud Loadbalancer:客戶端負(fù)載均衡
  • Hystrix:斷路保護(hù)
  • Spring Cloud Gateway:智能路由驰怎,服務(wù)網(wǎng)關(guān)

這里面需要提前安裝配置的組件只有一個(gè)Consul阐滩,可以參考我之前的文章 Consul的架構(gòu)和配置

新建Maven項(xiàng)目

我們首先為所有的服務(wù)模塊創(chuàng)建一個(gè)Parent項(xiàng)目,用于管理各種公共的依賴包县忌,pom.xml如下掂榔,需要注意的地方請(qǐng)查看注釋:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.github.davidfantasy</groupId>
    <artifactId>spring-cloud-demo</artifactId>
    <version>1.0.0</version>
    <modules>
        <!--訂單管理服務(wù)-->
        <module>order-service</module>
        <!--倉(cāng)儲(chǔ)管理服務(wù)-->
        <module>storage-service</module>
    </modules>
    <name>spring-cloud-demo</name>
    <description>Demo project for Spring Cloud</description>
    <packaging>pom</packaging>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR3</spring-cloud.version>
        <spring-boot.version>2.2.6.RELEASE</spring-boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
            <!--如果不排除的話,默認(rèn)會(huì)使用ribbon作為負(fù)載均衡器症杏,會(huì)有警告日志-->
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <!--如果不排除的話装获,默認(rèn)會(huì)使用ribbon作為負(fù)載均衡器,會(huì)有警告日志-->
            <exclusions>
                <exclusion>
                    <artifactId>spring-cloud-netflix-ribbon</artifactId>
                    <groupId>org.springframework.cloud</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--為服務(wù)提供默認(rèn)的健康檢查實(shí)現(xiàn)厉颤,否則需要自定義Consul的健康檢查策略-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <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>

    <dependencyManagement>
        <dependencies>
            <!--管理spring-cloud的相關(guān)組件的版本號(hào)-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--管理spring-boot的相關(guān)組件的版本號(hào)-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

然后新建兩個(gè)子模塊:order-service和storage-service穴豫,子模塊目前并不需要引入什么額外的依賴。接下來(lái)需要為每個(gè)模塊創(chuàng)建一個(gè)啟動(dòng)類和添加一個(gè)系統(tǒng)配置文件逼友,這部分兩個(gè)模塊其實(shí)都大同小異精肃,以order-service為例:

服務(wù)啟動(dòng)類
@SpringBootApplication
@EnableFeignClients
public class OrderServiceApplication {

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

}
配置文件:application.yml
server:
  port: 9001
spring:
  application:
    name: order-service
  cloud:
    consul:
      host: 192.168.1.220
      port: 8500
      discovery:
        #使用IP地址而不是HOSTNAME作為服務(wù)的訪問(wèn)地址
        prefer-ip-address: true
feign:
  hystrix:
    #啟用hystrix短路保護(hù)
    enabled: true
  okhttp:
    #feign使用okhttp來(lái)作為http客戶端
    enabled: true

其實(shí)到這里,一個(gè)服務(wù)就已經(jīng)基本搭建完成了帜乞,只需要填充具體的業(yè)務(wù)邏輯司抱。這也體現(xiàn)了Spring Boot機(jī)制的強(qiáng)大之處,通過(guò)簡(jiǎn)單的配置加上各種starter挖函,就能快速的將各種功能特性整合到項(xiàng)目中來(lái)状植。我們先來(lái)驗(yàn)證一下現(xiàn)在的配置,運(yùn)行兩個(gè)啟動(dòng)類怨喘,順利的話津畸,就能夠在Consul的控制臺(tái)http://localhost:8500/ui 看到服務(wù)成功注冊(cè)的信息:

服務(wù)注冊(cè)界面.png

服務(wù)注冊(cè)的過(guò)程

service啟動(dòng)時(shí),spring-cloud-starter-consul-discovery組件會(huì)根據(jù)配置信息生成一段服務(wù)注冊(cè)的json報(bào)文必怜,然后調(diào)用Consul Server的REST接口進(jìn)行服務(wù)注冊(cè)肉拓,比如order-service的注冊(cè)信息是這樣的(控制臺(tái)有相應(yīng)的輸出信息):

{id='order-service-9001', name='order-service', tags=[secure=false], address='192.168.1.252', meta=null, port=9001, enableTagOverride=null, check=Check{script='null', interval='10s', ttl='null', http='http://192.168.1.252:9001/actuator/health', method='null', header={}, tcp='null', timeout='null', deregisterCriticalServiceAfter='null', tlsSkipVerify=null, status='null'}, checks=null}

從輸出信息可以看出Consul對(duì)服務(wù)進(jìn)行健康檢查的回調(diào)地址是
http://192.168.1.252:9001/actuator/health,間隔時(shí)間是10s梳庆,如果需要修改默認(rèn)的健康檢查信息暖途,可以通過(guò)設(shè)置相應(yīng)的參數(shù)卑惜,請(qǐng)查看 這里。我們可以通過(guò)引入spring-boot-starter-actuator組件自動(dòng)實(shí)現(xiàn)健康檢查的回調(diào)驻售,否則就需要自行定義了露久。

添加業(yè)務(wù)邏輯

首先在storage-service中添加一個(gè)API,用于模擬庫(kù)存的變更欺栗,核心代碼如下:

@RestController
@RequestMapping("/api/storage")
public class Controller {
    @Autowired
    private StorageService storageService;

    @PostMapping("/change-inventory")
    public Integer changeInventory(@RequestBody InventoryChangeDTO req) {
        return storageService.changeInventory(req);
    }
}

storageService.changeInventory方法模擬對(duì)庫(kù)存的扣減操作毫痕,并返回一個(gè)Integer表示當(dāng)前剩余的庫(kù)存數(shù)(默認(rèn)返回98)。然后在order-service中添加一個(gè)Feign Client接口迟几,用于聲明需要遠(yuǎn)程調(diào)用的storage-service的相關(guān)接口:

@FeignClient(name = "storage-service", fallback = StorageServiceFallback.class)
public interface StorageService {

    @PostMapping("/api/storage/change-inventory")
    Integer updateInventoryOfGood(InventoryChangeDTO inventoryChangeDTO);

}

這個(gè)接口類相當(dāng)于是order-service訪問(wèn)storage-service中API的一個(gè)代理消请,@FeignClient的name字段中指定的名稱需要與storage-service注冊(cè)的服務(wù)名稱保持一致,這樣feign會(huì)通過(guò)服務(wù)名查詢Consul中已注冊(cè)的服務(wù)类腮,并自動(dòng)獲取order-service的訪問(wèn)地址臊泰。如果order-service部署了多個(gè)實(shí)例,F(xiàn)eign會(huì)使用Spring Cloud Loadbalancer進(jìn)行相應(yīng)的負(fù)載均衡(這個(gè)不需要額外的配置蚜枢,只需要在依賴包中引入相應(yīng)的starter即可)缸逃。fallback = StorageServiceFallback.class 聲明了如果storage-service中相應(yīng)的接口不可用的時(shí)候,需要進(jìn)行相應(yīng)的降級(jí)處理祟偷,這個(gè)是利用到了Hystrix的熔斷保護(hù)特性察滑,需要在配置文件中聲明:

feign.hystrix.enabled=true

然后我們來(lái)構(gòu)造order-service的createOrder接口,用于用戶創(chuàng)建訂單:

@RestController
@RequestMapping("/api/order")
@Slf4j
public class Controller {

    @Autowired
    private OrderService orderService;

    @Autowired
    private StorageService storageService;

    @PostMapping("/create-order")
    public String createOrder(@RequestBody OrderDTO order) {
        //創(chuàng)建新訂單
        orderService.createNewOrder(order);
        InventoryChangeDTO req = new InventoryChangeDTO();
        req.setGoodCode(order.getGoodCode());
        req.setQuantity(order.getQuantity());
        //調(diào)用倉(cāng)儲(chǔ)服務(wù)變更庫(kù)存
        Integer remainQuantity = storageService.updateInventoryOfGood(req);
        log.info("剩余數(shù)量:" + remainQuantity);
        return "ok";
    }

}

這樣基本的業(yè)務(wù)流程已經(jīng)完成了修肠,我們來(lái)編寫(xiě)一個(gè)單元測(cè)試來(lái)測(cè)試一下訂單創(chuàng)建的接口:

    @Test
    public void testCreateOrder() throws Exception {
        OrderDTO orderDTO = new OrderDTO();
        orderDTO.setCustomerCode("cus001");
        orderDTO.setGoodCode("tc-1");
        orderDTO.setQuantity(100);
        this.mvc.perform(post("/api/order/create-order").content(JsonUtil.obj2json(orderDTO))
                .contentType("application/json"))
                .andExpect(status().isOk())
                .andExpect(content().string("ok"));
    }

運(yùn)行之后贺辰,一切正常的話,就可以在控制臺(tái)看到輸出的剩余庫(kù)存日志信息:

2020-04-16 10:03:58.846  INFO 8276 --- [           main] c.g.d.s.o.controller.Controller          : 剩余數(shù)量:98

熔斷降級(jí)

如果當(dāng)遠(yuǎn)程服務(wù)不可用的時(shí)候嵌施,需要做一些額外的處理饲化,比如加入重試隊(duì)列后期進(jìn)行重試,記錄錯(cuò)誤日志等等吗伤,就需要用到Hystrix提供的熔斷保護(hù)特性了吃靠。之前說(shuō)到的StorageServiceFallback就是用于這個(gè)目的的,我們來(lái)看一下StorageServiceFallback的代碼:

@Component
@Slf4j
public class StorageServiceFallback implements StorageService {

    public Integer updateInventoryOfGood(InventoryChangeDTO inventoryChangeDTO) {
        log.error("StorageServiceFallback.updateInventoryOfGood暫不可用");
        return -1;
    }

}

這里的業(yè)務(wù)處理很簡(jiǎn)單足淆,只是返回一個(gè)默認(rèn)數(shù)字-1巢块,并打印了一行錯(cuò)誤日志。現(xiàn)在先把StorageServiceApplication停止巧号,然后再執(zhí)行testCreateOrder方法族奢,就會(huì)看到如下的輸出日志:

2020-04-16 10:55:09.245 ERROR 6432 --- [ HystrixTimer-1] c.g.d.s.o.remote.StorageServiceFallback  : StorageServiceFallback.updateInventoryOfGood暫不可用
2020-04-16 10:55:09.245  INFO 6432 --- [           main] c.g.d.s.o.controller.Controller          : 剩余數(shù)量:-1

從日志上就可以看出降級(jí)服務(wù)已經(jīng)生效了。熔斷降級(jí)在一些中小型的系統(tǒng)中可能意義不太大丹鸿,但還是可以利用這個(gè)機(jī)制來(lái)做一些其它的應(yīng)用越走,比如對(duì)單個(gè)服務(wù)進(jìn)行單元測(cè)試時(shí),其依賴的遠(yuǎn)程服務(wù)都需要打樁,通過(guò)降級(jí)機(jī)制我們就可以很容易的生成遠(yuǎn)程服務(wù)的MOCK接口廊敌,定制自己的測(cè)試邏輯了铜跑。

本文的相關(guān)代碼可以查看這里 spring-cloud-demo

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市骡澈,隨后出現(xiàn)的幾起案子锅纺,更是在濱河造成了極大的恐慌,老刑警劉巖秧廉,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伞广,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡疼电,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)减拭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蔽豺,“玉大人,你說(shuō)我怎么就攤上這事拧粪⌒薅福” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵可霎,是天一觀的道長(zhǎng)魄鸦。 經(jīng)常有香客問(wèn)我,道長(zhǎng)癣朗,這世上最難降的妖魔是什么拾因? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮旷余,結(jié)果婚禮上绢记,老公的妹妹穿的比我還像新娘。我一直安慰自己正卧,他們只是感情好蠢熄,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著炉旷,像睡著了一般签孔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上窘行,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天饥追,我揣著相機(jī)與錄音,去河邊找鬼抽高。 笑死判耕,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的翘骂。 我是一名探鬼主播壁熄,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼帚豪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了草丧?” 一聲冷哼從身側(cè)響起狸臣,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎昌执,沒(méi)想到半個(gè)月后烛亦,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡懂拾,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年煤禽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片岖赋。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡檬果,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出唐断,到底是詐尸還是另有隱情选脊,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布脸甘,位于F島的核電站恳啥,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏丹诀。R本人自食惡果不足惜钝的,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望忿墅。 院中可真熱鬧扁藕,春花似錦、人聲如沸疚脐。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)棍弄。三九已至望薄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間呼畸,已是汗流浹背痕支。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蛮原,地道東北人卧须。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親花嘶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子笋籽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 微服務(wù)架構(gòu)模式的核心在于如何識(shí)別服務(wù)的邊界,設(shè)計(jì)出合理的微服務(wù)椭员。但如果要將微服務(wù)架構(gòu)運(yùn)用到生產(chǎn)項(xiàng)目上车海,并且能夠發(fā)揮...
    java菜閱讀 2,939評(píng)論 0 6
  • 前言 Spring Cloud是一個(gè)基于Spring Boot實(shí)現(xiàn)的云應(yīng)用開(kāi)發(fā)工具,它為基于JVM的云應(yīng)用開(kāi)發(fā)中的...
    程序媛馬小兮閱讀 1,741評(píng)論 0 5
  • 最近一致在更新Spring Cloud Config的相關(guān)內(nèi)容隘击,主要也是為這篇埋個(gè)伏筆侍芝,相信不少調(diào)研過(guò)Spring...
    程序猿DD閱讀 1,914評(píng)論 0 32
  • 近日接到領(lǐng)導(dǎo)通知,要求年輕人務(wù)必參加今年總部組織的演講比賽埋同。題目是《我心中的五年》州叠。拿到這個(gè)題目我啞然良久。 五年...
    王歸源閱讀 179評(píng)論 0 1
  • 上聯(lián):思?jí)羧溯航顗?mèng)人留量,思念終夢(mèng)人 下聯(lián):
    方舟孤一葉閱讀 52評(píng)論 0 0