RIBBON是什麼?
前面已經做好了資料微服務,現在是時候整個視圖微服務通路前面注冊好的資料微服務了。 springcloud 提供了兩種方式,一種是 Ribbon,一種是 Feign。
Ribbon 是使用 restTemplate 進行調用,并進行用戶端負載均衡。 什麼是用戶端負載均衡呢? 在前面 注冊資料微服務 裡,注冊了8001和8002兩個微服務, Ribbon 會從注冊中心獲知這個資訊,然後由 Ribbon 這個用戶端自己決定是調用哪個,這個就叫做用戶端負載均衡。
Feign 是什麼呢? Feign 是對 Ribbon的封裝,調用起來更簡單。
建立子項目
建立子項目 product-view-service-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 注冊中心的名稱。
看一下注冊中心:
上面那個位址隻指定了服務,沒有指定端口号。
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);
}
}
運作通路
調用圖示
這裡面三個角色,注冊中心、服務提供者、服務消費者,資料微服務作為服務提供者,視圖微服務作為服務消費者。
1、 首先資料微服務和視圖微服務都被 eureka 管理起來了。
2、 資料服務是由三個執行個體的叢集組成的,端口分别是 8001 , 8002,8003。
3、視圖微服務通過 注冊中心調用微服務, 然後負載均衡到 8001 、8002或者8003端口的應用上。
錯誤
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
找不到這個執行個體,我們看一下注冊中心,看看資料微服務叫什麼名字。
似乎也沒什麼錯,不算了,直接給它粘貼到用戶端。OK。
4、java.net.ConnectException: Connection refused: connect
重新整理頁面的時候發現報了這麼個錯誤,負載均衡一到資料微服務8002這個端口的時候就報錯,似乎是被占用了吧,我又注冊個端口為8003的資料微服務,這個端口倒是沒有問題。
參考:
【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