SpringCloud教程

本文轉載自https://blog.csdn.net/zpcandzhj/article/details/83692496
版權聲明:本文爲博主原創文章,遵循 CC 4.0 BY-SA 版權協議,本文鏈接:https://blog.csdn.net/zpcandzhj/article/details/84144453

如果此教程對您有幫助,就請有錢的捧個錢場,沒錢的捧個人場(轉載分享)哦~**

更多免費教程資源請訪問:https://blog.csdn.net/hellozpc
本教程代碼下載地址:       https://download.csdn.net/download/zpcandzhj/10762209

推薦Springboot2.0教程https://blog.csdn.net/hellozpc/article/details/82531834
Spring Cloud微服務教程(二): https://blog.csdn.net/hellozpc/article/details/84144453
Guava cache教程 https://blog.csdn.net/hellozpc/article/details/88613464

歡迎關注公衆號「程猿薇蔦」

Spring Cloud微服務教程(一)

文章目錄

**歡迎關注公衆號**

1.教程大綱

  • 統一開發環境
  • 瞭解微服務架構
  • 瞭解Spring Cloud
  • Spring Cloud快速入門
  • Eureka服務註冊中心
  • 使用Ribbon實現負載均衡
  • 使用Hystrix實現容錯

2.統一開發環境

  • JDK:  1.8
  • IDE:    IntelliJ IDEA
  • Maven:3.3.9
  • OS:        Windows 10 10.0
  • Springboot版本:2.0+

3.微服務架構

目前微服務是非常火的架構或者說概念,也是在構建大型互聯網項目時採用的架構方式。

3.1.單體架構

單體架構,是指將開發好的項目打成war包,然後發佈到tomcat等容器中的應用。

假設你正準備開發一款與Uber和滴滴競爭的出租車調度軟件,經過初步會議和需求分析,你可能會手動或者使用基於Spring Boot、Play或者Maven的生成器開始這個新項目,它的六邊形架構是模塊化的 ,架構圖如下:
在這裏插入圖片描述

  • 應用核心是業務邏輯,由定義服務、領域對象和事件的模塊完成。圍繞着核心的是與外界打交道的適配器。適配器包括數據庫訪問組件、生產和處理消息的消息組件,以及提供API或者UI訪問支持的web模塊等。

  • 儘管也是模塊化邏輯,但是最終它還是會打包並部署爲單體式應用。具體的格式依賴於應用語言和框架。例如,許多Java應用會被打包爲WAR格式,部署在Tomcat或者Jetty上,而另外一些Java應用會被打包成自包含的JAR格式,類似的,Rails和Node.js會被打包成層級目錄。

  • 這種應用開發風格很常見,因爲IDE和其它工具都擅長開發一個簡單應用,這類應用也很易於調試,只需要簡單運行此應用,用Selenium鏈接UI就可以完成端到端測試。單體式應用也易於部署,只需要把打包應用拷貝到服務器端,通過在負載均衡器後端運行多個拷貝就可以輕鬆實現應用擴展。在早期這類應用運行的很好。

3.2.單體架構存在的問題

在這裏插入圖片描述
在這裏插入圖片描述
如何解決以上問題呢? – 使用微服務架構。使得應用由重變輕。

3.3.什麼是微服務?

在這裏插入圖片描述

作者:Martin Fowler
在這裏插入圖片描述
在這裏插入圖片描述

3.4.微服務架構的特徵

在這裏插入圖片描述

3.5.微服務架構示例

在這裏插入圖片描述

每一個應用功能區都使用微服務完成。

4.Spring Cloud簡介

4.1.簡介

Spring Cloud項目的官方網址:
http://projects.spring.io/spring-cloud/
在這裏插入圖片描述
在這裏插入圖片描述

4.2.Spring Cloud子項目

在這裏插入圖片描述

4.3.版本說明

在這裏插入圖片描述

官方版本:
在這裏插入圖片描述

在這裏插入圖片描述

可見,目前Finchley.SR2版本是最新的穩定版,所以我們學習的過程中,就是使用的這個版本。
在這裏插入圖片描述

4.4.Spring Cloud框架特點

在這裏插入圖片描述

歡迎關注公衆號「程猿薇蔦」

5.使用 Spring Boot 實現微服務

在正式學習Spring Cloud之前我們先使用Spring Boot實現一個微服務。

業務非常簡單:
1、商品微服務:通過商品id查詢商品的服務;
2、訂單微服務:創建訂單時通時,通過調用商品的微服務進行查詢商品數據;

圖示:
在這裏插入圖片描述

說明:
1、對於商品微服務而言,商品微服務是服務的提供者,訂單微服務是服務的消費者;
2、對於訂單微服務而言,訂單微服務是服務的提供者,人是服務的消費者。

5.1.實現商品微服務

5.1.1.創建maven工程

在這裏插入圖片描述
在這裏插入圖片描述

5.1.2.導入依賴

重點是導入Spring Boot的依賴:

<?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>
&lt;groupId&gt;com.zpc.microservice&lt;/groupId&gt;
&lt;artifactId&gt;microservice-item&lt;/artifactId&gt;
&lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;

&lt;parent&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
    &lt;version&gt;2.0.6.RELEASE&lt;/version&gt;
&lt;/parent&gt;

&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;

&lt;build&gt;
    &lt;finalName&gt;${project.artifactId}&lt;/finalName&gt;
    &lt;plugins&gt;
        &lt;!-- 資源文件拷貝插件 --&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
            &lt;artifactId&gt;maven-resources-plugin&lt;/artifactId&gt;
            &lt;configuration&gt;
                &lt;encoding&gt;UTF-8&lt;/encoding&gt;
            &lt;/configuration&gt;
        &lt;/plugin&gt;
        &lt;!-- java編譯插件 --&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
            &lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt;
            &lt;configuration&gt;
                &lt;source&gt;1.8&lt;/source&gt;
                &lt;target&gt;1.8&lt;/target&gt;
                &lt;encoding&gt;UTF-8&lt;/encoding&gt;
            &lt;/configuration&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;
&lt;/build&gt;
&lt;/project&gt;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
5.1.3.創建實體Item
package com.zpc.item.entity;

public class Item {

private Long id;

private String title;

private String pic;

private String desc;

private Long price;

public Item(){}

public Item(long id, String title, String pic, String desc, Long price) {
    this.id=id;
    this.title=title;
    this.pic=pic;
    this.desc=desc;
    this.price=price;
}

public Long getId() {
    return id;
}

public void setId(Long id) {
    this.id = id;
}

public String getTitle() {
    return title;
}

public void setTitle(String title) {
    this.title = title;
}

public String getPic() {
    return pic;
}

public void setPic(String pic) {
    this.pic = pic;
}

public String getDesc() {
    return desc;
}

public void setDesc(String desc) {
    this.desc = desc;
}

public Long getPrice() {
    return price;
}

public void setPrice(Long price) {
    this.price = price;
}

@Override
public String toString() {
    return "Item [id=" + id + ", title=" + title + ", pic=" + pic + ", desc=" + desc + ", price=" + price + "]";
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
5.1.4.編寫ItemService

編寫ItemService用於實現具體的商品查詢邏輯,爲了演示方便,我們並不真正的連接數據庫,而是做模擬實現。

package com.zpc.item.service;
import com.zpc.item.entity.Item;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;

@Service
public class ItemService {

private static final Map&lt;Long, Item&gt; ITEM_MAP = new HashMap&lt;Long, Item&gt;();

static {// 準備一些靜態數據,模擬數據庫
    ITEM_MAP.put(1L, new Item(1L, "商品1", "http://圖片1", "商品描述1", 1000L));
    ITEM_MAP.put(2L, new Item(2L, "商品2", "http://圖片2", "商品描述2", 2000L));
    ITEM_MAP.put(3L, new Item(3L, "商品3", "http://圖片3", "商品描述3", 3000L));
    ITEM_MAP.put(4L, new Item(4L, "商品4", "http://圖片4", "商品描述4", 4000L));
    ITEM_MAP.put(5L, new Item(5L, "商品5", "http://圖片5", "商品描述5", 5000L));
    ITEM_MAP.put(6L, new Item(6L, "商品6", "http://圖片6", "商品描述6", 6000L));
    ITEM_MAP.put(7L, new Item(7L, "商品7", "http://圖片7", "商品描述7", 7000L));
    ITEM_MAP.put(8L, new Item(8L, "商品8", "http://圖片8", "商品描述8", 8000L));
    ITEM_MAP.put(8L, new Item(9L, "商品9", "http://圖片9", "商品描述9", 9000L));
    ITEM_MAP.put(8L, new Item(10L, "商品10", "http://圖片10", "商品描述10", 10000L));
}

/**
 * 模擬實現商品查詢
 *
 * @param id
 * @return
 */
public Item queryItemById(Long id) {
    return ITEM_MAP.get(id);
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
5.1.5.編寫ItemController
package com.zpc.item.controller;

import com.zpc.item.entity.Item;
import com.zpc.item.service.ItemService;
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.RestController;

@RestController
public class ItemController {

@Autowired
private ItemService itemService;

/**
 * 對外提供接口服務,查詢商品信息
 *
 * @param id
 * @return
 */
@GetMapping(value = "item/{id}")
public Item queryItemById(@PathVariable("id") Long id) {
    return this.itemService.queryItemById(id);
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

@RestController註解的說明:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {

/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
* @since 4.0.1
*/
@AliasFor(annotation = Controller.class)
String value() default “”;

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

從源碼可以看出,這是一個組合註解,組合了@Controller@Response註解。相當於我們同時寫了這2個註解。

@GetMapping註解的說明:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {...}
  • 1
  • 2
  • 3
  • 4
  • 5

@GetMapping註解是@RequestMapping(method = RequestMethod.GET)簡寫方式。其功能都是一樣的。

同理還有其它註解:
在這裏插入圖片描述

5.1.6.編寫程序入口
package com.zpc.item.runner;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

/**

  • @author Evan
    */

@SpringBootApplication//申明這是一個Spring Boot項目
@ComponentScan(basePackages = {“com.zpc.item.controller”,“com.zpc.item.service”})//手動指定bean組件掃描範圍
public class ItemApp {
public static void main(String[] args) {
SpringApplication.run(ItemApp.class, args);
}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
5.1.7.創建application.yml配置文件

Spring Boot以及Spring Cloud項目支持yml和properties格式的配置文件。

yml格式是YAML(Yet Another Markup Language)編寫的格式,YAML和properties格式的文件是可以相互轉化的。如:

server:
  port: 8081 #服務端口
  • 1
  • 2

等價於properties文件的配置:
server.port=8081

配置文件的示例:

server:
  port: 8081 #服務端口
  • 1
  • 2
5.1.8.啓動程序測試

在這裏插入圖片描述

可以看到已經通過微服務查詢到數據。

5.2.實現訂單微服務

5.2.1.創建工程

在這裏插入圖片描述
在這裏插入圖片描述

5.2.2.導入依賴
<?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>
&lt;groupId&gt;com.zpc.microservice&lt;/groupId&gt;
&lt;artifactId&gt;microservice-item&lt;/artifactId&gt;
&lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;

&lt;parent&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
    &lt;version&gt;2.0.6.RELEASE&lt;/version&gt;
&lt;/parent&gt;

&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;

&lt;build&gt;
    &lt;finalName&gt;${project.artifactId}&lt;/finalName&gt;
    &lt;plugins&gt;
        &lt;!-- 資源文件拷貝插件 --&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
            &lt;artifactId&gt;maven-resources-plugin&lt;/artifactId&gt;
            &lt;configuration&gt;
                &lt;encoding&gt;UTF-8&lt;/encoding&gt;
            &lt;/configuration&gt;
        &lt;/plugin&gt;
        &lt;!-- java編譯插件 --&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
            &lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt;
            &lt;configuration&gt;
                &lt;source&gt;1.8&lt;/source&gt;
                &lt;target&gt;1.8&lt;/target&gt;
                &lt;encoding&gt;UTF-8&lt;/encoding&gt;
            &lt;/configuration&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;
&lt;/build&gt;
&lt;/project&gt;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
5.2.3.創建訂單Order實體
package com.zpc.order.entity;

import java.util.Date;
import java.util.List;

public class Order {

private String orderId;

private Long userId;

private Date createDate;

private Date updateDate;

private List&lt;OrderDetail&gt; orderDetails;

public Order() {

}

public Order(String orderId, Long userId, Date createDate, Date updateDate) {
    this.orderId = orderId;
    this.userId = userId;
    this.createDate = createDate;
    this.updateDate = updateDate;
}

public String getOrderId() {
    return orderId;
}

public void setOrderId(String orderId) {
    this.orderId = orderId;
}

public Long getUserId() {
    return userId;
}

public void setUserId(Long userId) {
    this.userId = userId;
}

public Date getCreateDate() {
    return createDate;
}

public void setCreateDate(Date createDate) {
    this.createDate = createDate;
}

public Date getUpdateDate() {
    return updateDate;
}

public void setUpdateDate(Date updateDate) {
    this.updateDate = updateDate;
}


public List&lt;OrderDetail&gt; getOrderDetails() {
    return orderDetails;
}

public void setOrderDetails(List&lt;OrderDetail&gt; orderDetails) {
    this.orderDetails = orderDetails;
}

@Override
public String toString() {
    return "Order [orderId=" + orderId + ", userId=" + userId
            + ", createDate=" + createDate + ", updateDate=" + updateDate
            + "]";
}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
5.2.4.創建訂單詳情OrderDetail實體

訂單與訂單詳情是一對多的關係。

package com.zpc.order.entity;

public class OrderDetail {

private String orderId;

private Item item = new Item();

public OrderDetail() {

}

public OrderDetail(String orderId, Item item) {
    this.orderId = orderId;
    this.item = item;
}

public String getOrderId() {
    return orderId;
}

public void setOrderId(String orderId) {
    this.orderId = orderId;
}

public Item getItem() {
    return item;
}

public void setItem(Item item) {
    this.item = item;
}

@Override
public String toString() {
    return "OrderDetail [orderId=" + orderId + ", item=" + item + "]";
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
5.2.5.將商品微服務項目中的Item類拷貝到當前工程

在這裏插入圖片描述

5.2.6.編寫OrderService

該Service實現的根據訂單Id查詢訂單的服務,爲了方便測試,我們將構造數據實現,不採用查詢數據庫的方式。

package com.zpc.order.service;

import com.zpc.order.entity.Item;
import com.zpc.order.entity.Order;
import com.zpc.order.entity.OrderDetail;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
public class OrderService {

private static final Map&lt;String, Order&gt; ORDER_DATA = new HashMap&lt;String, Order&gt;();

static {
    // 模擬數據庫,構造測試數據
    Order order = new Order();
    order.setOrderId("201810300001");
    order.setCreateDate(new Date());
    order.setUpdateDate(order.getCreateDate());
    order.setUserId(1L);
    List&lt;OrderDetail&gt; orderDetails = new ArrayList&lt;OrderDetail&gt;();

    Item item = new Item();// 此處並沒有商品的數據,只是保存了商品ID,需要調用商品微服務獲取
    item.setId(1L);
    orderDetails.add(new OrderDetail(order.getOrderId(), item));

    item = new Item(); // 構造第二個商品數據
    item.setId(2L);
    orderDetails.add(new OrderDetail(order.getOrderId(), item));

    order.setOrderDetails(orderDetails);

    ORDER_DATA.put(order.getOrderId(), order);
}

@Autowired
private ItemService itemService;

/**
 * 根據訂單id查詢訂單數據
 *
 * @param orderId
 * @return
 */
public Order queryOrderById(String orderId) {
    Order order = ORDER_DATA.get(orderId);
    if (null == order) {
        return null;
    }
    List&lt;OrderDetail&gt; orderDetails = order.getOrderDetails();
    for (OrderDetail orderDetail : orderDetails) {
        // 通過商品微服務查詢商品詳細數據
        Item item = this.itemService.queryItemById(orderDetail.getItem()
                .getId());
        if (null == item) {
            continue;
        }
        orderDetail.setItem(item);
    }

    return order;
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
5.2.7.實現ItemService
package com.zpc.order.service;

import com.zpc.order.entity.Item;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class ItemService {

// Spring框架對RESTful方式的http請求做了封裝,來簡化操作
@Autowired
private RestTemplate restTemplate;

public Item queryItemById(Long id) {
    return this.restTemplate.getForObject("http://127.0.0.1:8081/item/"
            + id, Item.class);
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
5.2.8.編寫OrderController
package com.zpc.order.controller;

import com.zpc.order.entity.Order;
import com.zpc.order.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.RestController;

@RestController
public class OrderController {
@Autowired
private OrderService orderService;

@GetMapping(value = "order/{orderId}")
public Order queryOrderById(@PathVariable("orderId") String orderId) {
    return this.orderService.queryOrderById(orderId);
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
5.2.9.編寫程序入口
package com.zpc.order.runner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.client.RestTemplate;

/**

  • @author Evan
    */
    @SpringBootApplication//申明這是一個Spring Boot項目
    @ComponentScan(basePackages = {“com.zpc.order.controller”, “com.zpc.order.service”})//手動指定bean掃描範圍
    public class OrderApp {
    public static void main(String[] args) {
    SpringApplication.run(OrderApp.class, args);
    }

    /**

    • 向Spring容器中定義RestTemplate對象
    • @return
      */
      @Bean
      public RestTemplate restTemplate() {
      return new RestTemplate();
      }
      }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
5.2.10.編寫application.yml配置文件
server:
  port: 8082 #服務端口
  • 1
  • 2
5.2.11.啓動測試

在這裏插入圖片描述

測試結果可見,查詢訂單時,同時也將商品數據查詢到。

5.3.添加okHttp的支持

okhttp是一個封裝URL,比HttpClient更友好易用的工具。目前似乎okhttp更流行一些。

官網:http://square.github.io/okhttp/

在這裏插入圖片描述

RestTemplate底層默認使用的jdk的標準實現,如果我們想讓RestTemplate的底層使用okhttp,非常簡單:
1、添加okhttp依賴

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>3.9.0</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

2、設置requestFactory
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());

@Bean
public RestTemplate restTemplate() {
    return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
  • 1
  • 2
  • 3
  • 4

測試:
在這裏插入圖片描述

結果(沒有變化):
在這裏插入圖片描述
測試結果是一樣的。

5.4.解決訂單系統中的url硬編碼問題

  • 通過以上的測試我們發現,在訂單系統中要調用商品微服務中的查詢接口來獲取數據,在訂單微服務中將url硬編碼到代碼中,這樣顯然不好,因爲,運行環境一旦發生變化這個url地址將不可用。

  • 如何解決呢?

解決方案:將url地址寫入到application.yml配置文件中。

實現:
修改application.yml文件:

server:
  port: 8082 #服務端口
myspcloud:
  item:
    url: http://127.0.0.1:8081/item/
  • 1
  • 2
  • 3
  • 4
  • 5

修改ItemService中的實現:

@Service
public class ItemService {
// Spring框架對RESTful方式的http請求做了封裝,來簡化操作
@Autowired
private RestTemplate restTemplate;

@Value("${myspcloud.item.url}")
private String itemUrl;

public Item queryItemById(Long id) {
    return this.restTemplate.getForObject(itemUrl
            + id, Item.class);
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

測試(debug可以看到效果):
在這裏插入圖片描述

5.5.繼續優化解決硬編碼的問題

在SpringBoot中使用@ConfigurationProperties註解可以非常簡單的將配置文件中的值映射成對象。
第一步,創建ItemProperties類:

package com.zpc.order.properties;

public class ItemProperties {
private String url;

public String getUrl() {
    return url;
}

public void setUrl(String url) {
    this.url = url;
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

第二步,創建OrderProperties類:

package com.zpc.order.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**

  • 以myspcloud開頭的配置被匹配到

  • @author Evan
    */
    @Component
    @ConfigurationProperties(prefix=“myspcloud”)
    public class OrderProperties {

    private ItemProperties item = new ItemProperties();

    public ItemProperties getItem() {
    return item;
    }

    public void setItem(ItemProperties item) {
    this.item = item;
    }
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

第三步,在Itemservice中注入該對象:

@Service
public class ItemService {
// Spring框架對RESTful方式的http請求做了封裝,來簡化操作
@Autowired
private RestTemplate restTemplate;

//@Value("${myspcloud.item.url}")
//private String itemUrl;

@Autowired
OrderProperties orderProperties;

public Item queryItemById(Long id) {
    return this.restTemplate.getForObject(orderProperties.getItem().getUrl()
            + id, Item.class);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

第四步,啓動類中增加bean掃描路徑

@ComponentScan(basePackages = {"com.zpc.order.controller","com.zpc.order.service","com.zpc.order.properties"})//
手動指定bean掃描範圍
  • 1
  • 2

第五步,debug運行
在這裏插入圖片描述
可以看出,這種解決方案比第一種好很多,更加的方便的。

思考:

  • 這樣是否還存在問題?如果商品的微服務有多個怎麼辦?
  • 我們在訂單微服務中使用了Item實體,直接採取從商品微服務拷貝代碼的方式是否太生硬?

6.Spring Cloud快速入門

6.1.分析硬編碼的問題

通過前面5.4、5.5的實現,我們視乎已經解決了url硬編碼的問題,但是我們想想:
1、如果商品微服務的ip地址發生了變更,訂單微服務中的配置文件也需要跟着修改
2、如果商品微服務有多個,那麼在訂單微服務中又該如何寫地址?

那應該怎麼解決呢? – 通過服務註冊、發現的機制來完成。

6.2.微服務註冊與發現

原理示意圖:
在這裏插入圖片描述

由上圖可以看出:
1、服務提供者將服務註冊到註冊中心
2、服務消費者通過註冊中心查找服務
3、查找到服務後進行調用(這裏就是無需硬編碼url的解決方案)
4、服務的消費者與服務註冊中心保持心跳連接,一旦服務提供者的地址發生變更時,註冊中心會通知服務消費者

6.3.註冊中心:Eureka

Spring Cloud提供了多種註冊中心的支持,如:Eureka、consul、ZooKeeper等。Eureka已經閉源了。本教程第二篇也會介紹使用其它兩種方式作爲註冊中心。
在這裏插入圖片描述

6.3.1.原理

在這裏插入圖片描述

Eureka包含兩個組件:Eureka ServerEureka Client

  • Eureka Server提供服務註冊服務,各個節點啓動後,會在Eureka Server中進行註冊,這樣EurekaServer中的服務註冊表中將會存儲所有可用服務節點的信息,服務節點的信息可以在界面中直觀的看到。

  • Eureka Client是一個java客戶端,用於簡化與Eureka Server的交互,客戶端同時也就別一個內置的、使用輪詢(round-robin)負載算法的負載均衡器。

  • 在應用啓動後,將會向Eureka Server發送心跳,默認週期爲30秒,如果Eureka Server在多個心跳週期內沒有接收到某個節點的心跳,Eureka Server將會從服務註冊表中把這個服務節點移除(默認90秒)。

  • Eureka Server之間通過複製的方式完成數據的同步,Eureka還提供了客戶端緩存機制,即使所有的Eureka Server都掛掉,客戶端依然可以利用緩存中的信息消費其他服務的API。綜上,Eureka通過心跳檢查、客戶端緩存等機制,確保了系統的高可用性、靈活性和可伸縮性。

6.3.2.編寫Eureka Server

第一步:創建Maven工程:

在這裏插入圖片描述
在這裏插入圖片描述
第二步,導入依賴:
這裏需要導入Spring Cloud的管理依賴。

<?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>
&lt;groupId&gt;com.zpc.springcloud.eureka&lt;/groupId&gt;
&lt;artifactId&gt;demo&lt;/artifactId&gt;
&lt;version&gt;0.0.1-SNAPSHOT&lt;/version&gt;
&lt;packaging&gt;jar&lt;/packaging&gt;

&lt;name&gt;demo&lt;/name&gt;
&lt;description&gt;Demo project for Spring Boot&lt;/description&gt;

&lt;parent&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
    &lt;version&gt;2.0.6.RELEASE&lt;/version&gt;
&lt;/parent&gt;
&lt;dependencyManagement&gt;
    &lt;dependencies&gt;
        &lt;!-- 導入Spring Cloud的依賴管理 --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
            &lt;artifactId&gt;spring-cloud-dependencies&lt;/artifactId&gt;
            &lt;version&gt;Finchley.SR1&lt;/version&gt;
            &lt;type&gt;pom&lt;/type&gt;
            &lt;scope&gt;import&lt;/scope&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;
&lt;/dependencyManagement&gt;
&lt;dependencies&gt;
	&lt;!--springboot 整合eureka服務端--&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
        &lt;artifactId&gt;spring-cloud-starter-netflix-eureka-server&lt;/artifactId&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;

&lt;build&gt;
    &lt;plugins&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;
&lt;/build&gt;

</project>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

第三步,編寫程序啓動類:

package com.zpc.springcloud.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

/**

  • Eureka註冊中心
    */
    @SpringBootApplication
    @EnableEurekaServer //申明這是一個Eureka服務
    public class AppEureka {

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

第四步,編寫application.yml配置文件:

###服務端口號
server:
  port: 8100

###服務名稱
spring:
application:
name: app-eureka-center

eureka:
instance:
#註冊中心地址
hostname: 127.0.0.1
###客戶端調用地址
client:
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:8100/eureka/
###是否將自己註冊到Eureka服務中,因爲該應用本身就是註冊中心,不需要再註冊自己(集羣的時候爲true)
register-with-eureka: false
###是否從Eureka中獲取註冊信息,因爲自己爲註冊中心,不會在該應用中的檢索服務信息
fetch-registry: false

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

第五步,啓動程序做測試:
在這裏插入圖片描述

6.4.將商品微服務註冊到Eureka

接下來,我們需要將商品的微服務註冊到Eureka服務中。

第一步:修改pom文件,引入Spring Cloud的管理依賴以及eureka服務依賴。

<?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>
&lt;groupId&gt;com.zpc.microservice&lt;/groupId&gt;
&lt;artifactId&gt;microservice-item&lt;/artifactId&gt;
&lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;

&lt;parent&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
    &lt;version&gt;2.0.6.RELEASE&lt;/version&gt;
&lt;/parent&gt;

&lt;dependencyManagement&gt;
    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
            &lt;artifactId&gt;spring-cloud-dependencies&lt;/artifactId&gt;
            &lt;version&gt;Finchley.SR1&lt;/version&gt;
            &lt;type&gt;pom&lt;/type&gt;
            &lt;scope&gt;import&lt;/scope&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;
&lt;/dependencyManagement&gt;

&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;!--springboot 整合eureka客戶端--&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
        &lt;artifactId&gt;spring-cloud-starter-netflix-eureka-client&lt;/artifactId&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;

&lt;build&gt;
    &lt;finalName&gt;${project.artifactId}&lt;/finalName&gt;
    &lt;plugins&gt;
        &lt;!-- 資源文件拷貝插件 --&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
            &lt;artifactId&gt;maven-resources-plugin&lt;/artifactId&gt;
            &lt;configuration&gt;
                &lt;encoding&gt;UTF-8&lt;/encoding&gt;
            &lt;/configuration&gt;
        &lt;/plugin&gt;
        &lt;!-- java編譯插件 --&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
            &lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt;
            &lt;configuration&gt;
                &lt;source&gt;1.8&lt;/source&gt;
                &lt;target&gt;1.8&lt;/target&gt;
                &lt;encoding&gt;UTF-8&lt;/encoding&gt;
            &lt;/configuration&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;
&lt;/build&gt;

</project>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

第二步,修改application.yml配置文件:

###服務端口號(本身是一個web項目)
server:
  port: 8081
###起個名字作爲服務名稱(該服務註冊到eureka註冊中心的名稱,比如商品服務)
spring:
    application:
        name: app-item
###服務註冊到eureka註冊中心的地址
eureka:
  client:
    service-url:
           defaultZone: http://127.0.0.1:8100/eureka
###因爲該應用爲服務提供者,是eureka的一個客戶端,需要註冊到註冊中心
    register-with-eureka: true
###是否需要從eureka上檢索服務
    fetch-registry: true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

第三步,修改啓動類,增加@EnableEurekaClient 註解:

package com.zpc.item.runner;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.ComponentScan;

/**

  • @author Evan
    */
    //申明這是一個Spring Boot項目
    @SpringBootApplication
    @EnableEurekaClient
    @ComponentScan(basePackages = {“com.zpc.item.controller”,“com.zpc.item.service”})
    public class ItemApp {
    public static void main(String[] args) {
    SpringApplication.run(ItemApp.class, args);
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

第四步,啓動測試:

在這裏插入圖片描述

至此,我們已經將自己的微服務註冊到Eureka server中了。

6.5.訂單系統從Eureka中發現商品服務

之前我們在訂單系統中是將商品微服務的地址進行了硬編碼,現在,由於已經將商品服務註冊到Eureka中,所以,只需要從Eureka中發現服務即可。

第一步,在訂單系統中添加依賴:

<?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>
&lt;groupId&gt;com.zpc.microservice&lt;/groupId&gt;
&lt;artifactId&gt;microservice-order&lt;/artifactId&gt;
&lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;

&lt;parent&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
    &lt;version&gt;2.0.6.RELEASE&lt;/version&gt;
&lt;/parent&gt;

&lt;dependencyManagement&gt;
    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
            &lt;artifactId&gt;spring-cloud-dependencies&lt;/artifactId&gt;
            &lt;version&gt;Finchley.SR1&lt;/version&gt;
            &lt;type&gt;pom&lt;/type&gt;
            &lt;scope&gt;import&lt;/scope&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;
&lt;/dependencyManagement&gt;

&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;!--springboot 整合eureka客戶端--&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
        &lt;artifactId&gt;spring-cloud-starter-netflix-eureka-client&lt;/artifactId&gt;
    &lt;/dependency&gt;

    &lt;dependency&gt;
        &lt;groupId&gt;com.squareup.okhttp3&lt;/groupId&gt;
        &lt;artifactId&gt;okhttp&lt;/artifactId&gt;
        &lt;version&gt;3.9.0&lt;/version&gt;
    &lt;/dependency&gt;

&lt;/dependencies&gt;

&lt;build&gt;
    &lt;finalName&gt;${project.artifactId}&lt;/finalName&gt;
    &lt;plugins&gt;
        &lt;!-- 資源文件拷貝插件 --&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
            &lt;artifactId&gt;maven-resources-plugin&lt;/artifactId&gt;
            &lt;configuration&gt;
                &lt;encoding&gt;UTF-8&lt;/encoding&gt;
            &lt;/configuration&gt;
        &lt;/plugin&gt;
        &lt;!-- java編譯插件 --&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
            &lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt;
            &lt;configuration&gt;
                &lt;source&gt;1.8&lt;/source&gt;
                &lt;target&gt;1.8&lt;/target&gt;
                &lt;encoding&gt;UTF-8&lt;/encoding&gt;
            &lt;/configuration&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;
&lt;/build&gt;

</project>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72

第二步,修改application.yml配置文件:

server:
  port: 8082 #服務端口
myspcloud:
  item:
    url: http://127.0.0.1:8081/item/
###起個名字作爲服務名稱(該服務註冊到eureka註冊中心的名稱,比如訂單服務)
spring:
    application:
        name: app-order
###服務註冊到eureka註冊中心的地址
eureka:
  client:
    service-url:
           defaultZone: http://127.0.0.1:8100/eureka
###因爲該應用爲服務提供者,是eureka的一個客戶端,需要註冊到註冊中心
    register-with-eureka: true
###是否需要從eureka上檢索服務
    fetch-registry: true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

第三步,修改ItemService的實現邏輯:

package com.zpc.order.service;
import com.zpc.order.entity.Item;
import com.zpc.order.properties.OrderProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class ItemService {

// Spring框架對RESTful方式的http請求做了封裝,來簡化操作
@Autowired
private RestTemplate restTemplate;

@Autowired
OrderProperties orderProperties;

public Item queryItemById(Long id) {

    // 該方法走eureka註冊中心調用(去註冊中心根據app-item查找服務,這種方式必須先開啓負載均衡@LoadBalanced)
    String itemUrl = "http://app-item/item/{id}";
    Item result = restTemplate.getForObject(itemUrl, Item.class, id);
    System.out.println("訂單系統調用商品服務,result:" + result);
    return result;
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

第四步,在啓動類中添加@EnableEurekaClient註解 ,獲取RestTemplate的方法上加 @LoadBalanced註解

package com.zpc.order.runner;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

/**

  • @author Evan
    */

@SpringBootApplication//申明這是一個Spring Boot項目
@EnableEurekaClient
@ComponentScan(basePackages = {“com.zpc.order.controller”, “com.zpc.order.service”,“com.zpc.order.properties”})//手動指定bean掃描範圍
public class OrderApp {
public static void main(String[] args) {
SpringApplication.run(OrderApp.class, args);
}

/**
 * 向Spring容器中定義RestTemplate對象
 * @return
 */
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

第五步,啓動測試(此時有3個應用:Eureka註冊中心、Item服務、order服務)
在這裏插入圖片描述

在註冊中心http://localhost:8100看到有2個客戶端:
在這裏插入圖片描述

7.深入理解Eureka

7.1.爲Eureka添加用戶認證

在前面的示例中,我們可以看到我們需要登錄即可訪問到Eureka服務,這樣其實是不安全的。

接下來,我們爲Eureka添加用戶認證。

第一步,爲Eureka服務端(eureka-server)添加安全認證依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4

第二步,增加application.yml配置文件:

###服務端口號
server:
  port: 8100

###服務名稱
spring:
application:
name: app-eureka-center
security:
basic:
enable: true #開啓基於HTTP basic的認證
user: #配置用戶的賬號信息
name: zpc
password: 123456

eureka:
instance:
#註冊中心地址
hostname: 127.0.0.1

###客戶端調用地址
client:
serviceUrl:
defaultZone: http://spring.security.user.name:{spring.security.user.name}:{spring.security.user.password}@${eureka.instance.hostname}:8100/eureka/
###是否將自己註冊到Eureka服務中,因爲該應用本身就是註冊中心,不需要再註冊自己(集羣的時候爲true)
register-with-eureka: false
###是否從Eureka中獲取註冊信息,因爲自己爲註冊中心,不會在該應用中的檢索服務信息
fetch-registry: true

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

第三步,在eurka服務端添加一個安全認證類:

package com.zpc.springcloud.eureka;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

/**
 * 高版本springcloud的丟棄了配置:
 *
 * security:
 *   basic:
 *    enabled: true
 *
 * 所以應該使用以下方式開啓
 *
 * @param http
 * @throws Exception
 */
@Override
protected void configure(HttpSecurity http) throws Exception {
    // Configure HttpSecurity as needed (e.g. enable http basic).
    http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
    http.csrf().disable();
    //注意:爲了可以使用 http://${user}:${password}@${host}:${port}/eureka/ 這種方式登錄,所以必須是httpBasic,
    // 如果是form方式,不能使用url格式登錄
    http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

第四步,重新啓動Eureka服務進行測試:
在這裏插入圖片描述

輸入正確的用戶名密碼即可登錄。

這時,服務提供者註冊到Eureka時會報錯:

2018-10-31 23:01:13.419  WARN 143840 --- [nfoReplicator-0] c.n.d.s.t.d.RetryableEurekaHttpClient    : Request execution failure with status code 401; retrying on another server if available
2018-10-31 23:01:13.420  WARN 143840 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient    : DiscoveryClient_APP-ORDER/Evan-Zhou:app-order:8082 - registration failed Cannot execute request on any known server

com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server

  • 1
  • 2
  • 3
  • 4

所以,需要在服務註冊時也需要設置用戶名和密碼。

7.1.1.服務註冊時(client端)設置賬戶信息

服務註冊到有認證需求的註冊中心時,需要設置如下地址:

http://USER:[email protected]:端口號/eureka/

配置如下(至此,Eureka註冊中心、Item服務、order服務3個配置文件全部改成了帶賬號密碼的請求地址):

###服務註冊到eureka註冊中心的地址
eureka:
  client:
    service-url:
           defaultZone: http://zpc:[email protected]:8100/eureka
  • 1
  • 2
  • 3
  • 4
  • 5

7.2.Eureka的自我保護模式

在這裏插入圖片描述

如圖,當前Eureka進入了自我保護模式。(先開啓Eureka server端和client端,然後再斷開client端,此時刷新Eureka界面,就會看到紅色字樣)
在這裏插入圖片描述

在短時間內丟失了服務實例的心跳,不會剔除該服務,這是eurekaserver的自我保護機制的宗旨。主要是爲了防止由於短暫的網絡故障誤刪除可用的服務。

所以,一般進入自我保護模式,無需處理。如果,需要禁用自我保護模式,只需要在配置文件中添加配置即可:
(測試環境、開發環境可以關閉自我保護機制,保證服務不可用時及時剔除)
配置方式:在server端 配置文件中添加server: 配置

eureka:
  instance:
    #註冊中心地址
    hostname: 127.0.0.1

###客戶端調用地址
client:
serviceUrl:
defaultZone: http://spring.security.user.name:{spring.security.user.name}:{spring.security.user.password}@${eureka.instance.hostname}:8100/eureka/
register-with-eureka: false
fetch-registry: true

server:
enable-self-preservation: false #禁用自我保護模式

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

重新啓動服務查看效果:
在這裏插入圖片描述
提示,如果禁用自我保護模式,在網絡通信故障下可能會出現問題,因爲服務可能只是短暫的不可用。
上述界面出現後,斷開client端,此時刷新Eureka界面,就會看到紅色字樣,同時會把服務實例從註冊中心剔除:No instances available
在這裏插入圖片描述

7.3.Eureka的高可用(Eureka集羣)

前面的測試,我們會發現,Eureka服務是一個單點服務,在生產環境就會出現單點故障,爲了確保Eureka服務的高可用,我需要搭建Eureka服務的集羣。

搭建Eureka集羣非常簡單,只要啓動多個Eureka Server服務並且讓這些Server端之間彼此進行註冊即可實現。

第一步,修改eureka server端的application.yml文件:

  • 端口爲8100的機器註冊到端口爲9100的註冊中心
###服務端口號
server:
  port: 8100

###服務名稱
spring:
application:
name: app-eureka-center
security:
basic:
enable: true #開啓基於HTTP basic的認證
user: #配置用戶的賬號信息
name: zpc
password: 123456

eureka:
instance:
#註冊中心地址
hostname: 127.0.0.1

###客戶端調用地址
client:
serviceUrl:
defaultZone: http://spring.security.user.name:{spring.security.user.name}:{spring.security.user.password}@${eureka.instance.hostname}:9100/eureka/
###是否將自己註冊到Eureka服務中,集羣的時候爲true
register-with-eureka: true
fetch-registry: true

server:
enable-self-preservation: false

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

第二步,修改配置文件,再建一個Eureka server工程,啓動兩個工程(兩個工程的name屬性一樣)測試:

  • 端口爲9100的機器註冊到端口爲8100的註冊中心
###服務端口號
server:
 port: 9100

###服務名稱
spring:
application:
name: app-eureka-center
security:
basic:
enable: true #開啓基於HTTP basic的認證
user: #配置用戶的賬號信息
name: zpc
password: 123456

eureka:
instance:
#註冊中心地址
hostname: 127.0.0.1
###客戶端調用地址
client:
serviceUrl:
defaultZone: http://spring.security.user.name:{spring.security.user.name}:{spring.security.user.password}@${eureka.instance.hostname}:8100/eureka/
###是否將自己註冊到Eureka服務中,集羣的時候爲true
register-with-eureka: true
fetch-registry: true
###測試環境、開發環境可以關閉自我保護機制,保證服務不可用時及時剔除
server:
enable-self-preservation: false
eviction-interval-timer-in-ms: 2000

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

測試結果:
在這裏插入圖片描述
在這裏插入圖片描述

可以看到,2個Eureka服務進行了彼此註冊。

7.4.將client端服務註冊到Eureka高可用集羣

服務註冊到Eureka集羣時,可以指定多個,也可以指定一個Eureka服務(因爲Eureka服務集羣間彼此互聯)。

修改商品微服務項目microservice-item的application.yml配置文件,defaultZone 寫上所有註冊中心的地址:

###服務端口號(本身是一個web項目)
server:
  port: 8081
###起個名字作爲服務名稱(該服務註冊到eureka註冊中心的名稱,比如商品服務)
spring:
    application:
        name: app-item
###服務註冊到eureka註冊中心的地址
eureka:
  client:
    service-url:
           defaultZone: http://zpc:[email protected]:8100/eureka/,http://zpc:[email protected]:9100/eureka/
###因爲該應用爲服務提供者,是eureka的一個客戶端,需要註冊到註冊中心
    register-with-eureka: true
###是否需要從eureka上檢索服務
    fetch-registry: true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

重啓啓動,測試:可以通過停止其中一個Eureka server服務進行測試,結果會發現集羣是高可用。
即:啓動2個Eureka服務端(註冊中心),1個商品服務(item),1個訂單服務(order)。停掉其中一個Eureka服務端後,訪問另外一個Eureka服務端頁面可以看到item服務立馬切換到此註冊中心,訂單服務照樣可以訪問商品服務,瀏覽器輸入http://localhost:8082/order/201810300001還能獲取到商品數據(因爲商品服務同時註冊到了2個註冊中心)。

爲了防止是緩存的效果,OrderService 再加一條數據201810300002 ,用http://localhost:8082/order/201810300002測試高可用。

package com.zpc.order.service;

import com.zpc.order.entity.Item;
import com.zpc.order.entity.Order;
import com.zpc.order.entity.OrderDetail;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
public class OrderService {

private static final Map&lt;String, Order&gt; ORDER_DATA = new HashMap&lt;String, Order&gt;();

static {
    // 模擬數據庫,構造測試數據
    Order order1 = new Order();
    order1.setOrderId("201810300001");
    order1.setCreateDate(new Date());
    order1.setUpdateDate(order1.getCreateDate());
    order1.setUserId(1L);
    List&lt;OrderDetail&gt; orderDetails1 = new ArrayList&lt;OrderDetail&gt;();

    Item item = new Item();// 此處並沒有商品的數據,只是保存了商品ID,需要調用商品微服務獲取
    item.setId(1L);
    orderDetails1.add(new OrderDetail(order1.getOrderId(), item));

    item = new Item(); // 構造第二個商品數據
    item.setId(6L);
    orderDetails1.add(new OrderDetail(order1.getOrderId(), item));
    order1.setOrderDetails(orderDetails1);
    ORDER_DATA.put(order1.getOrderId(), order1);

    Order order2 = new Order();
    order2.setOrderId("201810300002");
    order2.setCreateDate(new Date());
    order2.setUpdateDate(order2.getCreateDate());
    order2.setUserId(3L);
    List&lt;OrderDetail&gt; orderDetails2 = new ArrayList&lt;OrderDetail&gt;();

    Item item2 = new Item();// 此處並沒有商品的數據,只是保存了商品ID,需要調用商品微服務獲取
    item2.setId(3L);
    orderDetails2.add(new OrderDetail(order2.getOrderId(), item2));

    item2 = new Item(); // 構造第二個商品數據
    item2.setId(5L);
    orderDetails2.add(new OrderDetail(order2.getOrderId(), item2));
    order2.setOrderDetails(orderDetails2);
    ORDER_DATA.put(order2.getOrderId(), order2);
}

@Autowired
private ItemService itemService;

/**
 * 根據訂單id查詢訂單數據
 *
 * @param orderId
 * @return
 */
public Order queryOrderById(String orderId) {
    Order order = ORDER_DATA.get(orderId);
    if (null == order) {
        return null;
    }
    List&lt;OrderDetail&gt; orderDetails = order.getOrderDetails();
    for (OrderDetail orderDetail : orderDetails) {
        // 通過商品微服務查詢商品詳細數據
        Item item = this.itemService.queryItemById(orderDetail.getItem()
                .getId());
        if (null == item) {
            continue;
        }
        orderDetail.setItem(item);
    }

    return order;
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81

7.5.指定服務的IP地址

在服務的提供者配置文件中可以指定ip地址,如下:

###服務註冊到eureka註冊中心的地址
eureka:
  client:
    service-url:
           defaultZone: http://zpc:[email protected]:8100/eureka/,http://zpc:[email protected]:9100/eureka/
###因爲該應用爲服務提供者,是eureka的一個客戶端,需要註冊到註冊中心
    register-with-eureka: true
###是否需要從eureka上檢索服務
    fetch-registry: true
  instance:
      prefer-ip-address: true #將自己的ip地址註冊到Eureka服務中
      ip-address: 127.0.0.1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在這裏插入圖片描述

7.6.指定實例id

通過instance-id 參數指定服務註冊到Eureka中的服務實例id:

eureka:
  client:
    service-url:
           defaultZone: http://zpc:[email protected]:8100/eureka/,http://zpc:[email protected]:9100/eureka/
###因爲該應用爲服務提供者,是eureka的一個客戶端,需要註冊到註冊中心
    register-with-eureka: true
###是否需要從eureka上檢索服務
    fetch-registry: true
  instance:
      prefer-ip-address: true #將自己的ip地址註冊到Eureka服務中
      ip-address: 127.0.0.1
      instance-id: ${spring.application.name}###${server.port} #指定實例id
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在這裏插入圖片描述

8.負載均衡:Ribbon

首先,我們思考一個問題,如果爲同一個的提供者在Eureka中註冊了多個服務,那麼客戶端該如何選擇服務呢?

這時,就需要在客戶端實現服務的負載均衡。

在Spring Cloud中推薦使用Ribbon來實現負載均衡。

8.1.Ribbon簡介

在這裏插入圖片描述

8.2.架構

在這裏插入圖片描述

8.3.開始使用Ribbon

8.3.1.爲microservice order增加ribbon依賴
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4

其實,該依賴是可以省略的,因爲spring-cloud-starter-netflix-eureka-client中已經包含了spring-cloud-starter-netflix-ribbon:
在這裏插入圖片描述

8.3.2.爲RestTemplate設置@LoadBalanced註解
package com.zpc.order.runner;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

/**

  • @author Evan
    */
    @SpringBootApplication//申明這是一個Spring Boot項目
    @EnableEurekaClient
    @ComponentScan(basePackages = {“com.zpc.order.controller”, “com.zpc.order.service”,“com.zpc.order.properties”})//手動指定bean掃描範圍
    public class OrderApp {
    public static void main(String[] args) {
    SpringApplication.run(OrderApp.class, args);
    }

    /**

    • 向Spring容器中定義RestTemplate對象
    • @return
      */
      @Bean
      @LoadBalanced
      public RestTemplate restTemplate() {
      return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
      }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

這樣,RestTemplate就具備了負載均衡的功能。

8.3.3.改造ItemService的實現

之前的實現(其實本教程前面沒有用這種方式):

public Item queryItemById(Long id) {
    String serviceId = "app-item";
    List<ServiceInstance> instances = this.discoveryClient.getInstances(serviceId);
    if(instances.isEmpty()){
        return null;
    }
    // 爲了演示,在這裏只獲取第一個實例
    ServiceInstance serviceInstance = instances.get(0);
    String url = serviceInstance.getHost() + ":" + serviceInstance.getPort();
    return this.restTemplate.getForObject("http://" + url + "/item/" + id, Item.class);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

改造後的實現(其實本教程一直採用的是這種方式):

public Item queryItemById(Long id) {
    // 該方法走eureka註冊中心調用(這種方式必須先開啓負載均衡@LoadBalanced)
    String itemUrl = "http://app-item/item/{id}";
    Item result = restTemplate.getForObject(itemUrl, Item.class, id);
    System.out.println("訂單系統調用商品服務,result:" + result);
    return result;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

可以發現,實現更加簡化了。
這種方式關鍵在於獲取RestTemplat對象時要加上@LoadBalanced註解 ,否則restTemplate.getForObject方法會報java.net.UnknownHostException: app-item,而前面一種方式是手動指定了獲取的服務實例,不需要加此註解。

8.3.4.重啓訂單服務進行測試

測試結果:
在這裏插入圖片描述

結果顯示,可以正常獲取到商品數據。

內部原理(可以啓動多個item實例看效果,比如端口分別設爲8081,8091,在下面2個類裏打斷點):
org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor
在這裏插入圖片描述
org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient
在這裏插入圖片描述

在執行請求前會經過org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor這個攔截器,並且在org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient中,通過serverId查找服務地址,通過一定的負載均衡策略去做真正的請求。

8.3.5.測試負載均衡

測試方法:
第一步,啓動2個microservice-item服務(多個也可以)
在這裏插入圖片描述
第二步,導入測試依賴,編寫單元測試用例:

<!-- 引入SpringBoot的單元測試 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

第三步,編寫測試用例:

package com.zpc.test;

import com.zpc.order.runner.OrderApp;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = ItemServiceTest.class)
@Import(OrderApp.class)
public class ItemServiceTest {

@Autowired
private LoadBalancerClient loadBalancerClient;//自動注入

@Test
public void test() {
    String serviceId = "app-item";
    for (int i = 0; i &lt; 100; i++) {
        ServiceInstance serviceInstance = this.loadBalancerClient.choose(serviceId);
        System.out.println("第" + (i + 1) + "次:" + serviceInstance.getHost() + ": " + serviceInstance.getPort());
    }
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

測試結果:
第1次:127.0.0.1: 8091
第2次:127.0.0.1: 8081
第3次:127.0.0.1: 8091
第4次:127.0.0.1: 8081
第5次:127.0.0.1: 8091
第6次:127.0.0.1: 8081
第7次:127.0.0.1: 8091
第8次:127.0.0.1: 8081
第9次:127.0.0.1: 8091
第10次:127.0.0.1: 8081
第11次:127.0.0.1: 8091
第12次:127.0.0.1: 8081
第13次:127.0.0.1: 8091
第14次:127.0.0.1: 8081
第15次:127.0.0.1: 8091
第16次:127.0.0.1: 8081
第17次:127.0.0.1: 8091
第18次:127.0.0.1: 8081
第19次:127.0.0.1: 8091
第20次:127.0.0.1: 8081
第21次:127.0.0.1: 8091
第22次:127.0.0.1: 8081
第23次:127.0.0.1: 8091
第24次:127.0.0.1: 8081
第25次:127.0.0.1: 8091
第26次:127.0.0.1: 8081
第27次:127.0.0.1: 8091
第28次:127.0.0.1: 8081
第29次:127.0.0.1: 8091
第30次:127.0.0.1: 8081
第31次:127.0.0.1: 8091
第32次:127.0.0.1: 8081
第33次:127.0.0.1: 8091
第34次:127.0.0.1: 8081
第35次:127.0.0.1: 8091
第36次:127.0.0.1: 8081
第37次:127.0.0.1: 8091
第38次:127.0.0.1: 8081
第39次:127.0.0.1: 8091
第40次:127.0.0.1: 8081

或者在應用程序裏測試,比如在item微服務裏打印端口信息,這樣多啓動幾個item服務,多次請求可以看到效果:

package com.zpc.item.controller;

import com.zpc.item.entity.Item;
import com.zpc.item.service.ItemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**

  • @author Evan
    */
    @RestController
    public class ItemController {

    @Value("${server.port}")
    private String port;

    @Autowired
    private ItemService itemService;

    /**

    • 對外提供接口服務,查詢商品信息
    • @param id
    • @return
      */
      @GetMapping(value = “item/{id}”)
      public Item queryItemById(@PathVariable(“id”) Long id) {
      System.out.println(“service port:”+port);
      return this.itemService.queryItemById(id);
      }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

8.4.設置負載均衡策略

只需要在配置文件中添加配置
serviceId.ribbon.NFLoadBalancerRuleClassName=自定義的負載均衡策略類

例如在order微服務的yml配置文件中添加:

app-item:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
  • 1
  • 2
  • 3

其中app-item是要訪問的服務id

測試:
第1次:127.0.0.1: 8081
第2次:127.0.0.1: 9081
第3次:127.0.0.1: 9081
第4次:127.0.0.1: 8081
第5次:127.0.0.1: 9081
第6次:127.0.0.1: 8081
第7次:127.0.0.1: 9081
第8次:127.0.0.1: 9081
第9次:127.0.0.1: 8081
第10次:127.0.0.1: 8081
第11次:127.0.0.1: 8081
第12次:127.0.0.1: 8081
第13次:127.0.0.1: 8081
第14次:127.0.0.1: 9081
第15次:127.0.0.1: 9081
第16次:127.0.0.1: 9081
第17次:127.0.0.1: 8081
第18次:127.0.0.1: 9081
第19次:127.0.0.1: 8081
第20次:127.0.0.1: 8081

8.5.其它策略

接口:com.netflix.loadbalancer.IRule,其實現類:
在這裏插入圖片描述
策略描述:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

9.容錯保護:Hystrix

9.1.分析

在這裏插入圖片描述
在這裏插入圖片描述

9.2.雪崩效應

  • 在微服務架構中通常會有多個服務層調用,基礎服務的故障可能會導致級聯故障,進而造成整個系統不可用的情況,這種現象被稱爲服務雪崩效應。服務雪崩效應是一種因“服務提供者”的不可用導致“服務消費者”的不可用,並將不可用逐漸放大的過程。

  • 如果下圖所示:A作爲服務提供者,B爲A的服務消費者,C和D是B的服務消費者。A不可用引起了B的不可用,並將不可用像滾雪球一樣放大到C和D時,雪崩效應就形成了。

在這裏插入圖片描述

9.3.Hystrix簡介

主頁:https://github.com/Netflix/Hystrix/

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述

9.4.原理說明

正常情況:
在這裏插入圖片描述
在這裏插入圖片描述
當對特定服務的呼叫達到一定閾值時(Hystrix中的默認值爲5秒內的20次故障),電路打開,不進行通訊。並且是一個隔離的線程中進行的。

9.5.快速入門

在 microservice-order系統中增加Hystrix容錯(本教程中order微服務工程主要扮演消費者的角色)。

9.5.1.導入依賴
<!--整合hystrix-->
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
9.5.2.修改ItemService的queryItemById方法

在要做容錯處理的方法上加@HystrixCommand註解,並指定一個容錯方法,即fallbackMethod 。

/**
 * 進行容錯處理
 * fallbackMethod的方法參數個數類型要和原方法一致
 *
 * @param id
 * @return
 */
@HystrixCommand(fallbackMethod = "queryItemByIdFallbackMethod")
public Item queryItemById3(Long id) {
    String itemUrl = "http://app-item/item/{id}";
    Item result = restTemplate.getForObject(itemUrl, Item.class, id);
    System.out.println("===========HystrixCommand queryItemById-線程池名稱:" + Thread.currentThread().getName() + "訂單系統調用商品服務,result:" + result);
    return result;
}

/**

  • 請求失敗執行的方法
  • fallbackMethod的方法參數個數類型要和原方法一致
  • @param id
  • @return
    */
    public Item queryItemByIdFallbackMethod(Long id) {
    return new Item(id, “查詢商品信息出錯!”, null, null, null);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
9.5.3.在啓動類OrderApplication添加@EnableHystrix註解
/**
 * @author Evan
 */
@SpringBootApplication
@EnableEurekaClient
@EnableHystrix
@ComponentScan(basePackages = {"com.zpc.order.controller", "com.zpc.order.service","com.zpc.order.properties"})//手動指定bean掃描範圍
public class OrderApp {
    public static void main(String[] args) {
        SpringApplication.run(OrderApp.class, args);
    }
/**
 * 向Spring容器中定義RestTemplate對象
 * @return
 */
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
9.5.4.在Controller增加一個入口
package com.zpc.order.controller;

import com.zpc.order.entity.Order;
import com.zpc.order.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.RestController;

@RestController
public class OrderController {
@Autowired
private OrderService orderService;

@GetMapping(value = "order/{orderId}")
public Order queryOrderById(@PathVariable("orderId") String orderId) {
    return this.orderService.queryOrderById(orderId);
}

@GetMapping(value = "order2/{orderId}")
public Order queryOrderById2(@PathVariable("orderId") String orderId) {
    return this.orderService.queryOrderByIdx(orderId);
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

OrderService中增加:

public Order queryOrderByIdx(String orderId) {
    Order order = ORDER_DATA.get(orderId);
    if (null == order) {
        return null;
    }
    List<OrderDetail> orderDetails = order.getOrderDetails();
    for (OrderDetail orderDetail : orderDetails) {
        // 通過商品微服務查詢商品詳細數據
        Item item = this.itemService.queryItemById3(orderDetail.getItem()
                .getId());
        if (null == item) {
            continue;
        }
        orderDetail.setItem(item);
    }
return order;

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
9.5.5.重新啓動進行測試

http://localhost:8082/order/201810300001
http://localhost:8082/order2/201810300001
在這裏插入圖片描述

測試一切正常。可以發現加了@HystrixCommand註解的方法和普通方法不是共用的線程池,是隔離的:
在這裏插入圖片描述

接下來,我們把商品服務停止進行測試:

在這裏插入圖片描述

  • 可以看到,使用了hystrix容錯機制時訂單服務(app-order)正常,儘管查詢商品服務(app-item)已停止服務,查詢到的是錯誤信息。

  • 由此可見,商品服務的宕機並沒有影響訂單服務的正常工作,起到的容錯效果。

如果調用沒有做Hytrix容錯的方法,則直接返回異常信息:
在這裏插入圖片描述

9.6.定義統一的fallback接口

        如果在每個方法裏都定義對應的fallback方法,顯得太臃腫,實際項目中可以單獨使用一個類定義各種fallback,在使用feign客戶端時使用統一fallback接口。
詳情請見本教程的姊妹篇《Spring cloud微服務教程2》:介紹了其它SpringCloud組件。

歡迎關注公衆號「程猿薇蔦」
微信掃一掃
                                </div>
            <link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-b6c3c6d139.css" rel="stylesheet">
                                            <div class="more-toolbox">
            <div class="left-toolbox">
                <ul class="toolbox-list">
                    
                    <li class="tool-item tool-active is-like "><a href="javascript:;"><svg class="icon" aria-hidden="true">
                        <use xlink:href="#csdnc-thumbsup"></use>
                    </svg><span class="name">點贊</span>
                    <span class="count">106</span>
                    </a></li>
                    <li class="tool-item tool-active is-collection "><a href="javascript:;" data-report-click="{&quot;mod&quot;:&quot;popu_824&quot;}"><svg class="icon" aria-hidden="true">
                        <use xlink:href="#icon-csdnc-Collection-G"></use>
                    </svg><span class="name">收藏</span></a></li>
                    <li class="tool-item tool-active is-share"><a href="javascript:;" data-report-click="{&quot;mod&quot;:&quot;1582594662_002&quot;}"><svg class="icon" aria-hidden="true">
                        <use xlink:href="#icon-csdnc-fenxiang"></use>
                    </svg>分享</a></li>
                    <!--打賞開始-->
                                            <!--打賞結束-->
                                            <li class="tool-item tool-more">
                        <a>
                        <svg t="1575545411852" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5717" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M179.176 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5718"></path><path d="M509.684 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5719"></path><path d="M846.175 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5720"></path></svg>
                        </a>
                        <ul class="more-box">
                            <li class="item"><a class="article-report">文章舉報</a></li>
                        </ul>
                    </li>
                                        </ul>
            </div>
                        </div>
        <div class="person-messagebox">
            <div class="left-message"><a href="https://blog.csdn.net/zpcandzhj">
                <img src="https://profile.csdnimg.cn/5/C/E/3_zpcandzhj" class="avatar_pic" username="zpcandzhj">
                                        <img src="https://g.csdnimg.cn/static/user-reg-year/1x/9.png" class="user-years">
                                </a></div>
            <div class="middle-message">
                                    <div class="title"><span class="tit"><a href="https://blog.csdn.net/zpcandzhj" data-report-click="{&quot;mod&quot;:&quot;popu_379&quot;}" target="_blank">niaobirdfly</a></span>
                                        </div>
                <div class="text"><span>發佈了138 篇原創文章</span> · <span>獲贊 1054</span> · <span>訪問量 86萬+</span></div>
            </div>
                            <div class="right-message">
                                        <a href="https://bbs.csdn.net/topics/395527229" target="_blank" class="btn btn-sm btn-red-hollow bt-button personal-messageboard">他的留言板
                    </a>
                                                        <a class="btn btn-sm  bt-button personal-watch" data-report-click="{&quot;mod&quot;:&quot;popu_379&quot;}">關注</a>
                                </div>
                        </div>
                </div>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章