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

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

测试是否随机

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351