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