文章目录
概念
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: true
client:
# 因为当前项目为服务,不需要向服务注册自己,默认为true
register-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: 8081
spring:
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提供的负载均衡器
*/
@Autowired
private 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 {
@Bean
public 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请求
不传参数
@Test
void 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());
}
传参数
@Test
void 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请求
@Test
void 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
@Test
void 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动态的发送请求
@Autowired
private LoadBalancerClient loadBalancerClient;
@RequestMapping("/fs")
public String show(){
ServiceInstance choose = loadBalancerClient.choose("e-server");
URI uri = choose.getUri();//http://localhost:8081 || http://localhost:8080
RestTemplate 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
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
@RestController
public class DemoController {
@Autowired
private 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 {
@Autowired
private AppClientOpenfeignClient appClientOpenfeignClient;
@RequestMapping("/fs")
public String show(){
String show = appClientOpenfeignClient.show();
System.out.println(show);
return show;
}
}
5.启动类上加@EnableFeignClients(basePackages = {“feign所在包”})
/**
* EnableFeignClients - 开启Openfeign技术。让spring cloud扫描Openfeign相关注解,
* 生成动态代理实现对象。
* 可选属性 basePackages = {"feign接口所在包1", "feign接口所在包2"}
* 默认扫描当前类型所在包,及所有子孙包。
*/
@SpringBootApplication
@EnableFeignClients(basePackages = {"com.feign"})
public class OpenFeignAppClientApp {
public static void main(String[] args) {
SpringApplication.run(OpenFeignAppClientApp.class, args);
}
}
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…