一.Ribbon简介
在之前我们已经学习过服务器的负载均衡,通过nginx实现的.既然有服务器的负载均衡,那也有客户端的负载均衡,就是Ribbon
客户端负载均衡执行流程
用户向nginx发起请求,nginx根据权重访问所代理中一个服务器,也就是application client,application client通过rabbon进行负载均衡算法计算出访问的application service 的uri,根据uri访问application service(访问的过程和ribbon没有关系的)
为什么Ribbon叫做客户端负载均衡
强调:客户端负载均衡:客户端已知服务端是集群,且必须进行负载均衡计算和处理,负载均衡工具对客户端是已知的,这种负载均衡是客户端负载均;服务端负载均衡:客户端不知爱到服务端是集群,也不知道是否需要做负载均衡计算和处理,负载均衡工具对客户端是透明未知的,这种负载均衡是服务端负载均衡
1.Ribbon介绍
Ribbo是由Netflix公司推出的开源软件,是基于HTTP和TCP协议的,其主要功能是实现客户端软件的负载均衡算法
Spring Cloud中Ribbon就是基于Netflix公司的Ribbon实现的.它不需要单独部署,但是却存在于整个微服务中.前面学习的Eureka里面有Ribbon,后面学习的OpenFeign也是基于Ribbon实现的
2.Ribbon原理
内部基于ILoadBalancer实现的(代码层面)
继承关系如下:
使用Ribbon工作原理:
所有的项目都会祖册到Eureka中,Eureka允许不同项目的spring.application.name是相同.当相同时会认为这些项目一个集群.所以同一个项目部署多次时都是设置应用程序名相同.
Application Client会从Eureka中根据spring.application.name加载Application Service的列表.根据设定的负载均衡算法,从列表中取出一个URL,到此Ribbon的事情结束了.剩下的事情由程序员自己进行技术选型,选择一个HTTP协议工具,通过这个URL调用Application Service.
注意:以下事情和Ribbon没有关系的
Application Service 注册到Eureka过程.这是Eureka的功能.
Application Client从Eureka取出注册列表.这是Eureka的功能.
Application Client通过URL访问Application Service.具体实现可以自己进行选择使用哪个HTTP工具
只有Application Client从Eureka中取出列表后进行负载均衡算法的过程和Ribbon有关系
二.负载均衡解决方案分类及特征
业界主流的负载均衡解决方案有:集中式负载均衡和进程内负载均衡
1.1.集中式负载均衡
即在客户端和服务端之间使用独立的负载均衡设施(可以是硬件,如F5,也可以是软件,如nginx),由该设施负责把访问请求通过某种策略转发值服务端
也叫做:服务器端负载均衡
服务器端负载均衡1.2进程内负载均衡
将负载均衡逻辑集成到客户端组件中,客户端组件从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务端发起请求.Ribbon就是一个进程内的负载均衡实现
也叫做:客户端负载均衡
客户端负载均衡三.搭建Application Service集群
前提:已经配置了单机版Eureka.端口为8761
新建项目ApplicationServiceDemo
1.添加依赖
新建项目后,在pom.xml中添加依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifatId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
</parent>
<dependencyManagement>
<deoendencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2.配置文件
在resource下先进application.yml
spring:
application:
name:application-service
server:
port:8081
3.编写控制器
@RestController
public class DemoController{
@ReqUESTmAPPING("/demo")
public String demo(){
System.out.println("执行了demo");
return "suiyi";
}
}
4.编写启动类
先进com.bjsxt.Demo1Application
@SpringBootApplication
public class Demo1Application{
public static void main(String[] args){
SpringApplication.run(Demo1Application.class,args);
}
}
5.启动项目(启动三个)
把Demo1Application类,复制两份,分别叫做Demo2Application和Demo3Application
先启动Demo1Application
修改application.yml中端口为8082,再启动Demo2Application
修改application.yml中端口为8083,再启动Demo3Application
启动三个Application为了验证Ribbon的负载均衡效果,在Application Client中通过Ribbon算法调用三个Application中一个
由于Ribbon是通过服务名称获取Application Service的,所以这三个Application Service的名称一定要相同
观察Eureka管理页面会发现已经注册了三个Provider
四.基于Ribbon测试负载均衡
1.添加依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.25.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
2.编写配置文件
spring:
application:
name:application-client
3.编写service
先进com.bjsxt.service.ClientService及实现类
public interface ClientService{
/**@return 返回值类型不是必须和调用的控制器方法返回值一样,需要看页面要什么
*/
String client();
}
@Service
public class ClientServiceImpl implements ClientService{
@Autowired
private LoadBalanceClient loadBalancerClient;
@Override
public String client(){
ServiceInstance si = loadBanlancerClient.choose("application-service");
//获取Application Service IP.192.168.232.132
System.out.println(si.getHost());
//获取IP及端口.192.168.232.132:8081
System.out.println(si.getInstanceId());
//获取额外声明信息.{management.port=8081}
System.out.println(si.getMetadata());
//端口8081
System.out.println(si.getPort());
//模式null
System.out.println(si.getScheme());
//应用程序名application-service
System.out.println(si.getServiceId());
//URI http://192.168.232.132:8081
System.out.println(si.getUri().toString());
return null;
}
}
4.编写控制器
新建控制器com.bjsxt.controller.ClientController
@RestController
publci class ClientController{
@Autowired
private ClientService clientService;
@RequestMapping("/client")
public String client(){
return clientService.client();
}
}
5.编写启动类
先进com.bjsxt.ClientApplication
@SpringBootApplication
public class ClientApplication{
public static void main(String[] args){
SpringApplication.run(ClientApplication.class,args);
}
}
6.运行
运行程序,通过浏览器多次访问项目ApplicationClientDemo中/client控制器,观察IDEA控制器中输出结果
会发现访问是轮训访问的
五.RestTemplate
RestTemplate是spring-web-xxx.jar包中提供的Http协议实现类,与之前学习的HttpClient功能类似.也就是说导入spring-boot-starter-web的项目可以直接使用RestTemplate
在该类中主要针对6类请求方式封装的方法
1.说明
get方式提供了来年各个方法;
两个方法都是发送get请求并处理响应.区别:
getForObject:把响应体直接转换为对象.该方法返回值为特定类类型.舍弃了Response Header的东西,但是用起来比getForEntity方便.如果只需要互殴去响应体中内容(调用控制器方法的返回值)使用此方法
getForEntity:返回值包含响应头和响应体.用起来比getForObject稍微麻烦一些
2.get方式
前提准备.先新建一个项目resttemplateserver,把项目导入spring-boot-starter-web依赖即可,端口给定8080(注意ApplicationClientDemo项目要关闭,否则端口冲突).
在控制器中给定一个控制器方法.其他代码省略
注意:
如果方法返回值是String或基本数据类型时,建议给定produces设置响应结果类型,否则使用浏览器测试和使用RestTemplate获取的ContentType类型可能不一致
@RestController
public class ServerController{
@RequestMapping(value="/demo1",produces="text/html;charset=utf-8")
public String demo1(){
return "demo1";
}
}
2.1getForObject无参数
使用Spring脚手架又创建了一个项目resttemplate
在测试类com.bjsxt.ResttemplateApplicationTests中添加方法
@Test
void testGetForObject(){
RestTemplate restTemplate = new RestTemplate();
//第二个参数是请求控制器响应内容类型.决定getForObject方法返回值类型.
String result = restTemplate.getForObject("http://localhost:8080/demo1",String.class);
System.out.println(result);
}
2.2getForEntity无参数测试
在测试类中直接天啊及方法进行测试
getForEntity和getForObject的区别就是返回值是ResponseEntity.里面不仅仅可以去除响应体内容,还可以取出响应头信息或请求状态码等
注意:
getForEntity和getForObject除了返回值不一样以外,其他用法完全相同.所以有参数的getForENtity就不在演示了
@Test
void testGetForEntity(){
RestTemplate restTemplate = new RestTempalte();
//getForEntity第二个参数类型决定了ResponseEntity泛型类型,表示响应体中内容类型.
ResponseEntity<String> result = restTemplate.getForEntity("http://localhost:8080/demo1",String.class);
//取出响应体内容
System.out.println(result.getStatusCode());
//通过getHeaders()取出响应头中想要的信息,以ContentType举例
System.out.println(result.getHeader().getContentType());
}
2.3getForObject使用不定项参数
前提:
在resttemplateserver项目中添加了一个控制器方法
@RequestMapping("/demo2")
public String demo2(String name,int age){
return "name:"+name+",age:"+age;
}
在resttemplate项目的测试类中添加测试方法
@RequestMapping("/demo2")
public String demo2(String name,int age){
return "name:"+name+",age:"+age;
}
在resttemplate项目中的测试类中添加测试方法
getForObject第一个参数URL中使用URL重写方式包含请求参数.参数后面使用{序号}格式进行占位,序号从1开始
注意:
getForObject第三个开始的参数顺序要和序号严格对应
@Test
void testGetForObjectWithParam(){
RestTempalte restTempalte = new RestTempalte();
String result = restTemplate.getForObject("http://localhost:8080/demo2?age={1}&name={2}",String.class,123,"张三");
System.out.println(result);
}
2.4getForObject使用Map传参
考虑使用不定项参数方式设置参数必须严格按照顺序进行设置,可能当参数过多时,比较难对应
所以还提供了一种使用Map进行设置参数.根据占位符{key}进行对应,占位符中key的内容就是Map中对应的Value
@Test
void testgetForObjectWithMapParam(){
RestTempalte restTempalte = new RestTempalte();
Map<String,Object>map = new HashMap<>();
map.put("age",15);
map.put("name","北京尚学堂");
String result = restTemplate.getForObject("http;//localhost:8080/demo2?age={age}&name={name}",String.class,map);
System.out.println(result);
}
2.5getForObject使用restful传值
前提控制必须支持restful方式.在resttemplateserver项目中添加控制器方法
@RequestMapping("/demo3/{name}/{age}")
public String demo3(@PathVariable String name,@PathVariable int age){
return "restful:name:"+name+",age:"+age;
}
在resttempalte项目的测试类中使用restful方式进行传参
@Test
void testGetForObjectRestful(){
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject("http://localhost:8080/demo3/张三/12");
System.out.println(result);
}
2.6使用固定值或字符串拼接方式传参
2.6.1固定方式值
很少在实际项目中URL参数是固定值.一般参数的值都是变量的值
@Test
void testGetForObjectWithParamFi(){
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject("http://localhost:8080/demo2?age=12&name=李四",Stirng.class);
System.out.println(result);
}
2.6.2字符串拼接
由于使用字符串拼接方式可能拼接错误,建议使用上面的几种方式
@Test
void testGetForObjectWithParamFi(){
RestTemplate restTemplate = new RestTemplate();
People peo = new People();
String result = restTemplate.getForObject("http://localhost:8080/demo2?age="+peo.getAge()+"&name="+peo.getname(),String.class);
System.out.println(result);
}
3.post方式
3.1传递表单数据
传参数语法与get相同(只是多了一个参数),虽然看起来是url重写(get方式)但实际上用post请求.也支持{序号}或{map的key}两种方式
post也支持postForObject和postForEntity两种方式,每种方式提供三种方法重载,与get相同
注意:
post相关方法比get相关方法多了一个参数,这个参数才参数列表中第二个.如果传递的是普通参数,第二个参数设置为null即可.如果希望向请求体中设置流数据,设置到第二个参数中,Application Service通过@RequestBody接收.第二个参数名多用在直接传递对象等数据的情况.
post相关方法比get相关方法多了一个参数,这个参数在参数列表中第二个.如果传递的是普通参数,第二个参数设置为null即可.如果希望向请求体中设置流数据,设置到第二个参数中,Application Service通过@RequestBody接收.第二个参数多用在直接传递对象等数据的情况
post里面多了postForLocation()返回值为URI获取到结果URI,使用较少
@Test
void testPost(){
RestTempalte restTemplate = new RestTemplate();
String result = restTempalte.postForObject("http://localhost:8080/demo2?age=15&name=北京尚学堂",null,String.class);
System.out.println(result);
}
3.2传递请求体数据
postForObject第二个参数为请求体数据.当设置第二个参数后,控制器方必须要使用@RequestBody接收此参数
在resttemplateserver方的控制器中添加方法
其中方法参数map接收的就是postForObject第二个参数数据
@RequestMapping("/demo4")
public String demo4(String name,int age,@RequestBody Map<String,Object>map){
return "name:"+anme+",age:"+age+",map:"+map.get("a")+","+map.get("b");
}
在resttemplate项目的测试类中添加
@Test
void testPostWithBody(){
Map<String,Object> map = new HashMap<>();
map.put("a","a1");
map.put("b","b2");
RestTempalte restTemplate = new RestTempalte();
String result = restTemplate.postForObject("http://localhost:8080/demo4?age=15&name=北京尚学堂",map,String.class);
System.out.println(result);
}
4.exchange
当请求的控制器返回值类型为List<xx>,Set<xxx>等带有泛型类型的类型时,就不能使用getForObject,getForEntity,postForObject等,因为它们都是只能设置返回值类型,二部能设置类型的泛型.这是就需要使用exchange方法.
除此之外,如果需要设置请求头参数情况也需要使用exchange方法
在resttemplateserver中添加实体类和控制器方法,要求控制器方法返回值带有泛型
@Data
@AllArgsController
@NoArgsController
public class People{
private int id;
rivate Stirng name;
}
@RequestMapping("/demo5")
public List<People>selectAll(){
List<People>list = new ArrayList<>();
list.add(new People(1,"张三"));
list.add(new People(2,"李四"));
return list;
}
在testtempalte项目的测试类中添加测试方法
@Test
void testList(){
//泛型为请求体类型
//构造方法参数是请求体数据
HttpEntity<String>httpEntity = new HttpEntity<>();
//泛型为调用控制器方法返回值类型,此处可以设置泛型
//最大的大括号是因为ParameterizedTypeReference是abstract,但没有抽象方法
ParameterizedTypeReference<List<People>>py = new PrameterizedTypeReference<List<People>>(){};
RestTemplate restTempalte = new RestTempalte();
//HttpMethod枚举,设置请求类型
ResponseEntity<List<People>>response = restTemplate.exchange("http://localhost:8080/demo5",HttpMethod.GET,httpEntity,py);
List<People>list = response.getBody();
for(People people:list){
System.out.println(people);
}
}
4.2请求时包含普通表单参数
用法和之前讲解的getForObject两种参数相同支持{序号}和{map和key}
以{序号}举例:
ResponseEntity<List<People>>response = restTempalte.exchange("http://localhost:8080/demo5?id={1}&name={2}",HttpMethod.GET,httpEntity,py,123,"root");
六.实现Application Client调用Application Service
上面的例子中是LoadBalance调用Provider的测试.现在实现调用Provider的控制器接口方法
在Java代码中调用另一个控制器接口,可以使用之前学习的HttpClient实现,在今天的课程中缓一种技术实现,基于RestTempalte完成的
1.新建配置类
新建com.bjsxt.config.RubbonConfig
注意方法上面要有@LoadBalanced注解.否则Ribbon不生效
@Configuration
public class RibbonConfig{
@Bean
@LoadBalanced
public RestTemplate getTemplate(){
return new RestTempalte();
}
}
2.修改service实现类
注意:无论使用RestTempalte的哪个方法,方法中URL参数必须使用spring.application.name应用程序名(ServerID)替换原来URL中host(主机)和port(端口).因为底层Ribbon是根据应用程序名获取注册列表的
@Service
public class ClientServiceImpl implements ClientService{
@Autowired
private RestTemplate restTemplate;
@Override
public String client(){
return restTemplate.getForObject("http://APPLICATION-SERVICE/demo",Stirng.class);
}
}
3.运行
运行项目,访问/client控制器,观察IDEA控制台打印的内容
七.Ribbon支持的负载均衡算法
ribbon的负载均衡策略是通过不同的类型来实现的,下表详细介绍一些常用负载均衡策略及对应的Ribbon策略类
八.指定负载均衡策略
1.添加bean
在application client的配置类中添加.配置类中指定那个负载均衡策略默认使用哪种策略.不允许配置多个负载均衡策略的实例
@Bean
public RandomRule getRandomRule(){
return new RandomRule();
}
2.在ApplicationService观察输出语句
测试是否随机
网友评论