SpringCloud系列教材 (七)- 视图微服务-Ribbon
步骤1:Ribbon 概念
步骤2:先运行,看到效果,再学习
步骤3:模仿和排错
步骤4:创建子项目
步骤5:pom.xml
步骤6:实体类
步骤7:Ribbon 客户端
步骤8:服务类
步骤9:控制器
步骤10:products.html
步骤11:启动类
步骤12:application.yml
步骤13:启动并访问
步骤14:调用图
步骤2:先运行,看到效果,再学习
步骤3:模仿和排错
步骤4:创建子项目
步骤5:pom.xml
步骤6:实体类
步骤7:Ribbon 客户端
步骤8:服务类
步骤9:控制器
步骤10:products.html
步骤11:启动类
步骤12:application.yml
步骤13:启动并访问
步骤14:调用图
步骤 1 : Ribbon 概念
接下来,我们就要访问前面注册好的数据微服务了。 springcloud 提供了两种方式,一种是 Ribbon,一种是 Feign。
Ribbon 是使用 restTemplate 进行调用,并进行客户端负载均衡。 什么是客户端负载均衡呢? 在前面 注册数据微服务 里,注册了8001和8002两个微服务, Ribbon 会从注册中心获知这个信息,然后由 Ribbon 这个客户端自己决定是调用哪个,这个就叫做客户端负载均衡。
Feign 是什么呢? Feign 是对 Ribbon的封装,调用起来更简单。。。
本知识点讲解如何实现 Ribbon 客户端。
Ribbon 是使用 restTemplate 进行调用,并进行客户端负载均衡。 什么是客户端负载均衡呢? 在前面 注册数据微服务 里,注册了8001和8002两个微服务, Ribbon 会从注册中心获知这个信息,然后由 Ribbon 这个客户端自己决定是调用哪个,这个就叫做客户端负载均衡。
Feign 是什么呢? Feign 是对 Ribbon的封装,调用起来更简单。。。
本知识点讲解如何实现 Ribbon 客户端。
步骤 2 : 先运行,看到效果,再学习
老规矩,先下载
下载区(点击进入)的可运行项目,配置运行起来,确认可用之后,再学习做了哪些步骤以达到这样的效果。
1. 先启动 EurekaServerApplication
2. 然后启动两次 ProductDataServiceApplication, 分别输入 8001和8002.
3. 然后运行 ProductViewServiceRibbonApplication 以启动 微服务,然后访问地址:
http://127.0.0.1:8010/products
多刷新几遍,会发现这个端口有时候是 8001,有时候是8002. 从而观察到访问 数据服务集群,客户端负载均衡的效果。
1. 先启动 EurekaServerApplication
2. 然后启动两次 ProductDataServiceApplication, 分别输入 8001和8002.
3. 然后运行 ProductViewServiceRibbonApplication 以启动 微服务,然后访问地址:
http://127.0.0.1:8010/products
多刷新几遍,会发现这个端口有时候是 8001,有时候是8002. 从而观察到访问 数据服务集群,客户端负载均衡的效果。
步骤 3 : 模仿和排错
在确保可运行项目能够正确无误地运行之后,再严格照着教程的步骤,对代码模仿一遍。
模仿过程难免代码有出入,导致无法得到期望的运行结果,此时此刻通过比较正确答案 ( 可运行项目 ) 和自己的代码,来定位问题所在。
采用这种方式,学习有效果,排错有效率,可以较为明显地提升学习速度,跨过学习路上的各个槛。
推荐使用diffmerge软件,进行文件夹比较。把你自己做的项目文件夹,和我的可运行项目文件夹进行比较。
这个软件很牛逼的,可以知道文件夹里哪两个文件不对,并且很明显地标记出来
这里提供了绿色安装和使用教程: diffmerge 下载和使用教程
模仿过程难免代码有出入,导致无法得到期望的运行结果,此时此刻通过比较正确答案 ( 可运行项目 ) 和自己的代码,来定位问题所在。
采用这种方式,学习有效果,排错有效率,可以较为明显地提升学习速度,跨过学习路上的各个槛。
推荐使用diffmerge软件,进行文件夹比较。把你自己做的项目文件夹,和我的可运行项目文件夹进行比较。
这个软件很牛逼的,可以知道文件夹里哪两个文件不对,并且很明显地标记出来
这里提供了绿色安装和使用教程: diffmerge 下载和使用教程
步骤 4 : 创建子项目
步骤 5 : pom.xml
包含以下jar:
spring-cloud-starter-netflix-eureka-client: eureka 客户端
spring-boot-starter-web: springmvc
spring-boot-starter-thymeleaf: thymeleaf 做服务端渲染
有同学就会问了,为什么不用前后端分离呢? 干嘛要用 thymeleaf 做服务端渲染呢?
原因如下:
1. 使用前后端分离,站长多半会用 vue.js + axios.js来做,就像 springboot 天猫教程那样。 如果学习者没有这个基础,就会加重学习的负担。
2. 使用前后端分离,是走的 http 协议, 那么就无法演示重要的 微服务端调用了,所以站长这里特意没有用前后端分离,以便于大家观察和掌握微服务的彼此调用
spring-cloud-starter-netflix-eureka-client: eureka 客户端
spring-boot-starter-web: springmvc
spring-boot-starter-thymeleaf: thymeleaf 做服务端渲染
有同学就会问了,为什么不用前后端分离呢? 干嘛要用 thymeleaf 做服务端渲染呢?
原因如下:
1. 使用前后端分离,站长多半会用 vue.js + axios.js来做,就像 springboot 天猫教程那样。 如果学习者没有这个基础,就会加重学习的负担。
2. 使用前后端分离,是走的 http 协议, 那么就无法演示重要的 微服务端调用了,所以站长这里特意没有用前后端分离,以便于大家观察和掌握微服务的彼此调用
<
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
>
<
parent
>
<
groupId
>cn.how2j.springcloud</
groupId
>
<
artifactId
>springcloud</
artifactId
>
<
version
>0.0.1-SNAPSHOT</
version
>
</
parent
>
<
artifactId
>product-view-service-ribbon</
artifactId
>
<
dependencies
>
<
dependency
>
<
groupId
>org.springframework.cloud</
groupId
>
<
artifactId
>spring-cloud-starter-netflix-eureka-client</
artifactId
>
</
dependency
>
<
dependency
>
<
groupId
>org.springframework.boot</
groupId
>
<
artifactId
>spring-boot-starter-web</
artifactId
>
</
dependency
>
<
dependency
>
<
groupId
>org.springframework.boot</
groupId
>
<
artifactId
>spring-boot-starter-thymeleaf</
artifactId
>
</
dependency
>
</
dependencies
>
</
project
>
|
步骤 6 : 实体类
package
cn.how2j.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() {
}
public
Product(
int
id, String name,
int
price) {
super
();
this
.id = id;
this
.name = name;
this
.price = price;
}
}
|
步骤 7 : Ribbon 客户端
Ribbon 客户端, 通过 restTemplate 访问
http://PRODUCT-DATA-SERVICE/products , 而 product-data-service 既不是域名也不是ip地址,而是 数据服务在 eureka 注册中心的名称。
注意看,这里只是指定了要访问的 微服务名称,但是并没有指定端口号到底是 8001, 还是 8002.
注意看,这里只是指定了要访问的 微服务名称,但是并没有指定端口号到底是 8001, 还是 8002.
package
cn.how2j.springcloud.client;
import
java.util.List;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.stereotype.Component;
import
org.springframework.web.client.RestTemplate;
import
cn.how2j.springcloud.pojo.Product;
@Component
public
class
ProductClientRibbon {
@Autowired
RestTemplate restTemplate;
public
List<Product> listProdcuts() {
return
restTemplate.getForObject(
"http://PRODUCT-DATA-SERVICE/products"
,List.
class
);
}
}
|
步骤 8 : 服务类
服务类,数据从 ProductClientRibbon 中获取
package
cn.how2j.springcloud.service;
import
java.util.List;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.stereotype.Service;
import
cn.how2j.springcloud.client.ProductClientRibbon;
import
cn.how2j.springcloud.pojo.Product;
@Service
public
class
ProductService {
@Autowired
ProductClientRibbon productClientRibbon;
public
List<Product> listProducts(){
return
productClientRibbon.listProdcuts();
}
}
|
步骤 9 : 控制器
控制器,把数据取出来放在 product.html 中
package
cn.how2j.springcloud.web;
import
java.util.List;
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
cn.how2j.springcloud.pojo.Product;
import
cn.how2j.springcloud.service.ProductService;
@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"
;
}
}
|
步骤 10 : products.html
遍历数据
<!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
>
|
步骤 11 : 启动类
启动类, 注解多了个 @EnableDiscoveryClient, 表示用于发现eureka 注册中心的微服务。
还多了个 RestTemplate,就表示用 restTemplate 这个工具来做负载均衡
Ribbon 客户端 里就用到了这个 restTemplate.
还多了个 RestTemplate,就表示用 restTemplate 这个工具来做负载均衡
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return
new
RestTemplate();
}
|
Ribbon 客户端 里就用到了这个 restTemplate.
package
cn.how2j.springcloud;
import
java.util.Scanner;
import
java.util.concurrent.ExecutionException;
import
java.util.concurrent.Future;
import
java.util.concurrent.TimeUnit;
import
java.util.concurrent.TimeoutException;
import
org.springframework.boot.autoconfigure.SpringBootApplication;
import
org.springframework.boot.builder.SpringApplicationBuilder;
import
org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import
org.springframework.cloud.client.loadbalancer.LoadBalanced;
import
org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import
org.springframework.context.annotation.Bean;
import
org.springframework.web.client.RestTemplate;
import
cn.hutool.core.convert.Convert;
import
cn.hutool.core.thread.ThreadUtil;
import
cn.hutool.core.util.NetUtil;
import
cn.hutool.core.util.NumberUtil;
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public
class
ProductViewServiceRibbonApplication {
public
static
void
main(String[] args) {
int
port =
0
;
int
defaultPort =
8010
;
Future<Integer> future = ThreadUtil.execAsync(() ->{
int
p =
0
;
System.out.println(
"请于5秒钟内输入端口号, 推荐 8010 超过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(ProductViewServiceRibbonApplication.
class
).properties(
"server.port="
+ port).run(args);
}
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return
new
RestTemplate();
}
}
|
步骤 12 : application.yml
配置类,指定了 eureka server 的地址,以及自己的名称。 另外是一些 thymeleaf 的默认配置。
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: product-view-service-ribbon
thymeleaf:
cache: false
prefix: classpath:/templates/
suffix: .html
encoding: UTF-8
content-type: text/html
mode: HTML5
|
步骤 13 : 启动并访问
运行 ProductViewServiceRibbonApplication 以启动 微服务,然后访问地址:
http://127.0.0.1:8010/products
多刷新几遍,会发现这个端口有时候是 8001,有时候是8002. 从而观察到访问 数据服务集群,客户端负载均衡的效果。
http://127.0.0.1:8010/products
多刷新几遍,会发现这个端口有时候是 8001,有时候是8002. 从而观察到访问 数据服务集群,客户端负载均衡的效果。
步骤 14 : 调用图