天天看點

SpringCloud學習筆記(五、視圖微服務-RIBBON)

RIBBON是什麼?

前面已經做好了資料微服務,現在是時候整個視圖微服務通路前面注冊好的資料微服務了。 springcloud 提供了兩種方式,一種是 Ribbon,一種是 Feign。

Ribbon 是使用 restTemplate 進行調用,并進行用戶端負載均衡。 什麼是用戶端負載均衡呢? 在前面 注冊資料微服務 裡,注冊了8001和8002兩個微服務, Ribbon 會從注冊中心獲知這個資訊,然後由 Ribbon 這個用戶端自己決定是調用哪個,這個就叫做用戶端負載均衡。

Feign 是什麼呢? Feign 是對 Ribbon的封裝,調用起來更簡單。

建立子項目

建立子項目 product-view-service-ribbon:

SpringCloud學習筆記(五、視圖微服務-RIBBON)

pom.xml

spring-cloud-starter-netflix-eureka-client: eureka 用戶端

spring-boot-starter-web: springmvc

spring-boot-starter-thymeleaf: thymeleaf 做服務端渲染

<?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>springcloud</artifactId>
        <groupId>edu.hpu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>product-view-service-ribbon</artifactId>

    <name>product-view-service-ribbon</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>


    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>  <!--eureka 用戶端-->
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>  <!--SpringMvc-->
        </dependency>

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

    </dependencies>


</project>      

實體類

package edu.hpu.springcloud.pojo;

public class Product {
    private int id;
    private String name;
    private int price;

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

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

    public Product(int id, String name, int price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    public Product() {
    }
}      

Ribbon 用戶端

Ribbon 用戶端, 通過 restTemplate 通路

http://PRODUCT-DATA-SERVICE/products

, 而 product-data-service 既不是域名也不是ip位址,而是 資料服務在 eureka 注冊中心的名稱。

看一下注冊中心:

SpringCloud學習筆記(五、視圖微服務-RIBBON)

上面那個位址隻指定了服務,沒有指定端口号。

package edu.hpu.springcloud.client;

import edu.hpu.springcloud.pojo.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.util.List;

//Ribbon 用戶端
@Component
public class ProductClientRibbon {

    @Autowired
    RestTemplate restTemplate;
    public List<Product> listProcucts(){
        return restTemplate.getForObject("http://PRODUCT-DATA-SERVICE/products",List.class);
    }
}      

服務層

服務層資料從用戶端擷取

package edu.hpu.springcloud.service;

import edu.hpu.springcloud.client.ProductClientRibbon;
import edu.hpu.springcloud.pojo.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class ProductService {
    @Autowired
    ProductClientRibbon productClientRibbon;
    public List<Product> listProducts(){
        return productClientRibbon.listProcucts();   //從productClientRibbon中獲得資料服務
    }
}      
控制層
package edu.hpu.springcloud.controller;

import edu.hpu.springcloud.pojo.Product;
import edu.hpu.springcloud.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

@Controller
public class ProductController {
    @Autowired
    ProductService productService;
    @RequestMapping("/products")
    public Object products(Model m){
        List<Product> ps=productService.listProducts();
        m.addAttribute("ps",ps);
        return "products";
    }
}      

視圖

視圖采用thymeleaf:

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>products</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <style>

        table {
            border-collapse:collapse;
            width:400px;
            margin:20px auto;
        }
        td,th{
            border:1px solid gray;
        }

    </style>
</head>
<body>

<div class="workingArea">
    <table>
        <thead>
        <tr>
            <th>id</th>
            <th>産品名稱</th>
            <th>價格</th>
        </tr>
        </thead>
        <tbody>
        <tr th:each="p: ${ps}">
            <td th:text="${p.id}"></td>
            <td th:text="${p.name}"></td>
            <td th:text="${p.price}"></td>
        </tr>
        </tbody>
    </table>
</div>

</body>

</html>      

配置

application.yml,eureka server 的位址,以及自己的名稱。 另外是一些 thymeleaf 的預設配置

eureka:
  clg: UTF-8
    content-type: text/html
    mode: HTML5ient:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
spring:
  application:
    name: product-view-service-ribbon
  thymeleaf:
    cache: false
    prefix: classpath:/templates/
    suffix: .html
    encodin      

啟動類

ProductDataServiceApplication,

啟動類, 注解多了個 @EnableDiscoveryClient, 表示用于發現eureka 注冊中心的微服務。

還多了個 RestTemplate,就表示用 restTemplate 這個工具來做負載均衡

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

在ribbon用戶端中使用了這個RestTemplate.

package edu.hpu.springcloud;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.NetUtil;
import cn.hutool.core.util.NumberUtil;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

import java.util.Scanner;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

@SpringBootApplication
@EnableEurekaClient
public class ProductDataServiceApplication {
    public static void main(String[] args) {
        int port = 0;
        int defaultPort = 8003;
        Future<Integer> future = ThreadUtil.execAsync(() ->{
            int p = 0;
            System.out.println("請于5秒鐘内輸入端口号, 推薦  8001 、 8002  或者  8003,超過5秒将預設使用 " + defaultPort);
            Scanner scanner = new Scanner(System.in);
            while(true) {
                String strPort = scanner.nextLine();
                if(!NumberUtil.isInteger(strPort)) {
                    System.err.println("隻能是數字");
                    continue;
                }
                else {
                    p = Convert.toInt(strPort);
                    scanner.close();
                    break;
                }
            }
            return p;
        });
        try{
            port=future.get(5, TimeUnit.SECONDS);
        }
        catch (InterruptedException | ExecutionException | TimeoutException e){
            port = defaultPort;
        }

        if(!NetUtil.isUsableLocalPort(port)) {
            System.err.printf("端口%d被占用了,無法啟動%n", port );
            System.exit(1);
        }

        new SpringApplicationBuilder(ProductDataServiceApplication.class).properties("server.port=" + port).run(args);
    }
}
      
運作通路
SpringCloud學習筆記(五、視圖微服務-RIBBON)

調用圖示

這裡面三個角色,注冊中心、服務提供者、服務消費者,資料微服務作為服務提供者,視圖微服務作為服務消費者。

1、 首先資料微服務和視圖微服務都被 eureka 管理起來了。

2、 資料服務是由三個執行個體的叢集組成的,端口分别是 8001 , 8002,8003。

3、視圖微服務通過 注冊中心調用微服務, 然後負載均衡到 8001 、8002或者8003端口的應用上。

SpringCloud學習筆記(五、視圖微服務-RIBBON)

錯誤

1、springCloud com.sun.jersey.api.client.ClientHandlerException: java.net.ConnectException: Connection refused: connect

在預設設定下,Eureka服務注冊中心也會将自己作為用戶端來嘗試注冊它自己,是以我們需要禁用它的用戶端注冊行為。

禁止方式如下:在application.properties配置檔案中增加以下内容

eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false      

.yml也差不多,寫的時候注意級别就行了。

2、低級錯誤:注冊中心和資料微服務沒啟動

中間關了IJ,直接啟動視圖微服務,爆了一堆錯,一想,是注冊中心和資料微服務沒啟動,它向誰請求、調用誰去。

3、java.lang.IllegalStateException: No instances available for PRODUCT-DATA-SERVICE

找不到這個執行個體,我們看一下注冊中心,看看資料微服務叫什麼名字。

SpringCloud學習筆記(五、視圖微服務-RIBBON)

似乎也沒什麼錯,不算了,直接給它粘貼到用戶端。OK。

4、java.net.ConnectException: Connection refused: connect

重新整理頁面的時候發現報了這麼個錯誤,負載均衡一到資料微服務8002這個端口的時候就報錯,似乎是被占用了吧,我又注冊個端口為8003的資料微服務,這個端口倒是沒有問題。

SpringCloud學習筆記(五、視圖微服務-RIBBON)

參考:

【1】、

http://how2j.cn/k/springcloud/springcloud-ribbon/2040.html#nowhere

【2】、

https://blog.csdn.net/qq_39930369/article/details/87616077

【3】、

https://www.cnblogs.com/zmblog/p/8777878.html

繼續閱讀