美文网首页
高级框架第九天Ribbon:负载均衡工具

高级框架第九天Ribbon:负载均衡工具

作者: w漫漫 | 来源:发表于2020-09-02 15:00 被阅读0次

一.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观察输出语句

测试是否随机

相关文章

网友评论

      本文标题:高级框架第九天Ribbon:负载均衡工具

      本文链接:https://www.haomeiwen.com/subject/ykgvsktx.html