> 文章列表 > 负载均衡:Ribbon - 远程调用:RestTemplate OpenFeign

负载均衡:Ribbon - 远程调用:RestTemplate OpenFeign

负载均衡:Ribbon - 远程调用:RestTemplate  OpenFeign

文章目录

  • 概念
  • 测试Ribbon负载均衡
    • 搭建Eureka Server
    • 搭建Eureka Client
  • Ribbon负载均衡算法及配置
  • 服务之间的远程调用
  • RestTemplate
    • RestTemplate和Ribbon实现Application Client调用Application Service集群
  • OpenFeign
    • hello world
  • 参数传递

概念

Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。
是进程内负载均衡,即使服务内部的负载均衡

Eureka Server允许不同项目的应用名称,即spring.application.name相同。当应用名称相同时会认定这些项目一个集群。所以部署集群时都是设置相应的应用程序相同的应用名称.

注意: ​ Application Client会从Eureka Server中根据spring.application.name加载Application Service的列表。根据设定的负载均衡算法,从列表中取出一个合适的URL,到此Ribbon的事情结束了。(Ribbon的功能只是负责挑选众多Service中的一个url,挑完就没它事了)

主流的负载均衡解决方案有:集中式负载均衡和进程内负载均衡。
1.集中式负载均衡: 在客户端和服务端之间使用独立的负载均衡设施,也叫服务器端负载均衡.
可以是硬件,如F5, 也可以是软件,如nginx(注意这里的客户端指的是用户端,服务端是服务器)
2.进程内负载均衡: 将负载均衡逻辑集成到客户端组件中(注意不是用户端,是相对于Eureka Server中调用其他服务的服务型客户端)
客户端组件从服务注册中心获知有哪些地址可用,再从这些地址中选择出一个合适的服务端发起请求。
Ribbon就是一个进程内的负载均衡实现。也叫做:客户端负载均衡。
(注意: 这里的客户端指的是一个服务调用另一个服务(不涉及用户端,是内部服务间的调用),调用方是客户端,被调用方是服务端)

测试Ribbon负载均衡

搭建Eureka Server

pom.xml

<parent><artifactId>spring-boot-starter-parent</artifactId><groupId>org.springframework.boot</groupId><version>2.3.12.RELEASE</version>
</parent><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-server</artifactId></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>

yml

server:port: 8888
eureka:instance:prefer-ip-address: trueclient:# 因为当前项目为服务,不需要向服务注册自己,默认为trueregister-with-eureka: false# 因为当前为非集群版eureka,所以不需要同步其他节点数据fetch-registry: false
# 当server.port配置不是8761时需要配置内容service-url:defaultZone: http://127.0.0.1:${server.port}/eureka/

搭建Eureka Client

1.搭建Application Server集群
pom.xml

<parent><artifactId>spring-boot-starter-parent</artifactId><groupId>org.springframework.boot</groupId><version>2.3.12.RELEASE</version>
</parent><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><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>Hoxton.SR12</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>

集群配置: 创建两个application Server 实例,及两个yml文件:
注意: application Server集群的spring.application.name要一致!!!
1.yml

# 此处应该定义名称,否则注册到Server后的名字为UNKNOWN
server:port: 8080
spring:application:name: application-server
# 注册中心地址,默认值是http://localhost:8761/eureka/,端口号不是8761,则要和Eureka Server一致
eureka:client:service-url:defaultZone: http://127.0.0.1:8888/eureka/

application-server2.yml

server:port: 8081spring:application:name: application-server
eureka:client:service-url:defaultZone: http://127.0.0.1:8888/eureka/

通过两个配置文件创建application-server实例,及搭建的集群.

2.创建application Client
pom.xml
注意spring-cloud-starter-netflix-eureka-client里面就包含Ribbon,所以不需要额外导jar包

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.12.RELEASE</version>
</parent><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></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>Hoxton.SR12</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>

yml配置

server:port: 9090
spring:application:name: application-client# 和Eureka Server一致
eureka:client:service-url:defaultZone: http://127.0.0.1:8888/eureka/

client创建controller

import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
@RestController
public class ApplicationClient{/* Ribbon提供的负载均衡器*/@Autowiredprivate LoadBalancerClient loadBalancerClient;@RequestMapping("/fs")public String client() {ServiceInstance si = loadBalancerClient.choose("application-server");//这里是Application Server集群的统一名称// 获取Application Service IP。String port = String.valueOf(si.getPort());return port;}
}

可以看到浏览器轮询打印8080,8081

Ribbon负载均衡算法及配置

常用:
轮询策略(默认)
权重轮询策略(常用,中小型项目使用) :根据每个application service的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性越低。

将轮询改为权重轮询策略

@Configuration
public class RibbonConfig {@Beanpublic WeightedResponseTimeRule getWeightedResponseTimeRule(){return new WeightedResponseTimeRule();}
}

服务之间的远程调用

两种方式:
RestTemplate: spring web 提供: 和eureka无关,就是服务之间发送到对应控制单元类似ajax的请求.
OpenFeign: spring cloud 提供 (常用)
发送的请求相当于ajax

RestTemplate

不需要额外导包,包含在spring-boot-starter-web中
和eureka无关,就是服务之间发送到对应控制单元类似ajax的请求.

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>

1.一个服务向另一个服务发送get请求

不传参数@Testvoid contextLoads() {RestTemplate   restTemplate=new RestTemplate();//发送请求  get post  put delete ..请求方式都可发送//  xxForObject() 和xxxForEntity() 区别//xxForObject() 返回的结果只有相应值//xxxForEntity() 返回结果 中不仅包含了相应的值 还有响应头 和响应体等信息String s = restTemplate.getForObject("http://192.168.163.1:8081/edit", String.class);System.out.println(s);ResponseEntity<String> entity = restTemplate.getForEntity("http://192.168.163.1:8081/edit", String.class);System.out.println(entity.getStatusCode());System.out.println(entity.getBody());System.out.println(entity.getHeaders());}
传参数@Testvoid contextLoads2() {RestTemplate   restTemplate=new RestTemplate();//====>请求方式のGET  GET不可以发送请求体数据!!!!//restTemplate.getForObject("http://192.168.163.1:8081/edit2?name=zs&age=19",String.class);// 在后面书写参数 在 前面进行参数的赋值 {1}...//restTemplate.getForObject("http://192.168.163.1:8081/edit2?name={1}&age={2}",String.class,"zs",19);//发送REST风格数据//restTemplate.getForObject("http://192.168.163.1:8081/edit3/{1}/{2}",String.class,"zs",19);//restTemplate.getForObject("http://192.168.163.1:8081/edit4?name={1}&age={2}",String.class,"zs",19);//发送数据 封装到map集合中 在取值的时候需要使用map的key 进行接收Map<String,Object>  map =new HashMap<>();map.put("a","zs");map.put("b",80);restTemplate.getForObject("http://192.168.163.1:8081/edit4?name={a}&age={b}",String.class,map);}

2.一个服务向另一个服务发送post请求

    @Testvoid contextLoads3() {RestTemplate   restTemplate=new RestTemplate();//====>请求方式のPOST//restTemplate.postForEntity("http://192.168.163.1:8081/edit2?name=zs&age=123",null,String.class);//restTemplate.postForEntity("http://192.168.163.1:8081/edit2?name={1}&age={2}",null,String.class,"zs",123);//restTemplate.postForEntity("http://192.168.163.1:8081/edit3/{1}/{2}",null,String.class,"zs",123);//restTemplate.postForEntity("http://192.168.163.1:8081/edit4?name={1}&age={2}",null,String.class,"zs",123);//POST请求可以传递请求体数据  例如一个对象 可以直接传递  一次请求中最多有一个请求体//Student  stu=new Student(111,"sxt");//restTemplate.postForEntity("http://192.168.163.1:8081/edit5",stu,String.class);//可以传递map集合Map<String,Object>  map =new HashMap<>();map.put("a","zs");map.put("b",80);restTemplate.postForEntity("http://192.168.163.1:8081/edit3/{a}/{b}",null,String.class,map);}

3.响应结果中有集合或泛型解决方法exchange

    @Testvoid contextLoads4() {RestTemplate   restTemplate=new RestTemplate();//exchange 方法可以发送任何请求  并且如何响应结果中含有泛型  使用ParameterizedTypeReference 直接指定即可ParameterizedTypeReference<List<Student>> pt=new ParameterizedTypeReference<List<Student>>() {};ResponseEntity<List<Student>> responseEntity = restTemplate.exchange("http://192.168.163.1:8081/list", HttpMethod.GET, null, pt);List<Student> list = responseEntity.getBody();System.out.println(list);System.out.println(list.get(0).getName());}

发送数据设置请求头和响应体HttpEntity

 		//HttpEntity: 进行请求体数据封装 里面不仅包含了请求体数据 还包含了 请求头 都可以进行设置//传递的请求体数据Student stu=new Student(19,"zs");//请求头MultiValueMap<String,String> map =new LinkedMultiValueMap<>();map.add("abc","xxx");HttpEntity httpEntity=new HttpEntity(stu,map);restTemplate.exchange("http://192.168.163.1:8081/edit5",HttpMethod.POST,httpEntity,String.class);

RestTemplate和Ribbon实现Application Client调用Application Service集群

方法1:实际上就是利用Ribbon的负载均衡器,获取不同的 uri 进行拼接成url,然后用RestTemplate动态的发送请求

    @Autowiredprivate LoadBalancerClient loadBalancerClient;@RequestMapping("/fs")public String show(){ServiceInstance choose = loadBalancerClient.choose("e-server");URI uri = choose.getUri();//http://localhost:8081 || http://localhost:8080RestTemplate restTemplate = new RestTemplate();String result = restTemplate.getForObject(uri + "/save", String.class);return result;}

方法2.利用Eureka
创建RestTemplate对象的方法,增加注解
LoadBalanced - 把Spring Cloud封装的LoadBalancerClient于RestTemplate整合。
让RestTemplate自带负载均衡能力。仅在当前的Ribbon环境中生效。

@Configuration
public class AppClientConfiguration {@Bean@LoadBalancedpublic RestTemplate restTemplate(){return new RestTemplate();}
}@RestController
public class DemoController {@Autowiredprivate RestTemplate restTemplate;@RequestMapping("/fs")public String show(){String result = restTemplate.getForObject("http://e-server(application server集群名称)/save", String.class(返回值类型));return result;}
}

OpenFeign

声明式调用是指,就像调用本地方法一样调用远程方法.
1.Application Service向Eureka Server 注册服务, Application Client从Eureka Server中发现服务。
2.在Application Client中调用OpenFeign接口中方法,Application Client中OpenFeign通过应用程序名访问Application Service。
3.OpenFeign访问远程服务时,基于Ribbon实现负载均衡。

hello world

一.创建Eureka Server,详细参考Eureka
二.创建Application Server,详细参考Eureka
三.创建Application Client
1…导入依赖

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.12.RELEASE</version>
</parent>
<dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>Hoxton.SR12</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><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
</dependencies>

2.yml配置文件

server:port: 9999
spring:application:name: eureka-client-open-feign

假设远程调用的Application Server的spring.profiles.name为e-server
远程调用其的控制单元为:

    @RequestMapping("/save")public String show(){return "hello world";}

3.接下来在Application Client中的service包下创建AppClientOpenfeignClient接口

@FeignClient("e-server")//这里是Eureka Server中的application Server的spring.profiles.name名称
public interface AppClientOpenfeignClient{@RequestMapping("/save")//方法名可以不一致外,其余要一样(直接把除方法体外的复制过来就行)public String show();
}

4.直接注入后调用

@RestController
public class DemoController {@Autowiredprivate AppClientOpenfeignClient appClientOpenfeignClient;@RequestMapping("/fs")public String show(){String show = appClientOpenfeignClient.show();System.out.println(show);return show;}
}

5.直接访问http://localhost:9999/fs
浏览器输出hello world.

参数传递

调用方法类型,返回值,参数顺序及个数要一致

//定义注解 书写应用名称 底层采用就是动态代理
@FeignClient("APPSERVICE")
public interface StudentFeign {//4、建议:本地feign中方法名称建议和远程中一致//   建议:本地feign中方法形参名称建议和远程中方法形参保持一致@RequestMapping("/edit")public    String  edit();//参数列表默认传递参数都是请求体数据 对每一个参数前加@RequestBody//如果我们传递是普通表单数据 必须增加@RequestParam@RequestMapping("/edit2")String  edit2(@RequestParam("name") String name,@RequestParam("age") int age);//REST风格参数 要求 参数列表必须增加@PathVariable 尽管 形参名称和路径中名称一致//也建议增加name属性@RequestMapping("/edit3/{name}/{age}")String  edit3(@PathVariable("name") String name,@PathVariable("age") int age);@RequestMapping("/edit4")String  edit4(@RequestParam("name") String name,@RequestParam("age") int age);//默认就是请求体数据 在远程端必须使用@RequestBoby@RequestMapping("/edit5")String  edit5(@RequestBody Student student);@RequestMapping("/one")Student  findOne();@RequestMapping("/list")List<Student>  findAll();}

gzip…