我们通常说的负载均衡是指将一个请求均匀地分摊到不同的节点单元上执行,负载均和分为硬件负载均衡和软件负载均衡:
硬件负载均衡:比如F5、深信服、Array 等;
软件负载均衡:比如Nginx、LVS、HAProxy 等;
硬件负载均衡或是软件负载均衡,他们都会维护一个可用的服务端清单,通过心跳检测来剔除故障的服务端节点以保证清单中都是可以正常访问的服务端节点。当客户端发送请求到负载均衡设备的时候,该设备按某种算法(比如轮询、权重、最小连接数等)从维护的可用服务端清单中取出一台服务端的地址,然后进行转发。
Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,是一个基于HTTP和TCP的客户端负载均衡工具。Spring Cloud对Ribbon做了二次封装,可以让我们使用RestTemplate的服务请求,自动转换成客户端负载均衡的服务调用。Ribbon支持多种负载均衡算法,还支持自定义的负载均衡算法。Ribbon只是一个工具类框架,比较小巧,Spring Cloud对它封装后使用也非常方便,它不像服务注册中心、配置中心、API网关那样需要独立部署,Ribbon只需要在代码直接使用即可;
Ribbon与Nginx的区别:
Ribbon是客户端的负载均衡工具,而客户端负载均衡和服务端负载均衡最大的区别在于服务清单所存储的位置不同,在客户端负载均衡中,所有客户端节点下的服务端清单,需要自己从服务注册中心上获取,比如Eureka服务注册中心。同服务端负载均衡的架构类似,在客户端负载均衡中也需要心跳去维护服务端清单的健康性,只是这个步骤需要与服务注册中心配合完成。在Spring Cloud中,由于Spring Cloud对Ribbon做了二次封装,所以默认会创建针对Ribbon的自动化整合配置;
在SpringCloud中,Ribbon主要与RestTemplate对象配合起来使用,Ribbon会自动化配置RestTemplate对象,通过@LoadBalanced开启RestTemplate对象调用时的负载均衡
由于Spring Cloud Ribbon的封装,我们在微服务架构中使用客户端负载均衡调用非常简单,只需要如下两步:
1、启动多个服务提供者实例并注册到一个服务注册中心或是服务注册中心集群
就本例而言,将服务提供者provider代码复制一份,然后将模块导入到 IDEA 工作区中,启动服务注册中心集群,再启动两个服务提供者(注意在代码中将服务的返回结果用文字加以区分)
数字2表示同样的服务有两个服务提供者
2、服务消费者通过被@LoadBalanced注解修饰过的RestTemplate来调用服务提供者。这样,我们就可以实现服务提供者的高可用以及服务消费者的负载均衡调用
就本例而言,启动服务消费者,多次调用,会发现返回结果不一样,负载均衡已经实现
Ribbon的负载均衡策略是由IRule接口定义,该接口由如下实现:
下图是使用IDEA生成的Ribbon实现负载均衡的类图:
实现类 | 说明 |
---|---|
RandomRule | 随机 |
RoundRobinRule | 轮询 |
AvailabilityFilteringRule | 先过滤掉由于多次访问故障的服务,以及并发连接数超过阈值的服务,然后对剩下的服务按照轮询策略进行访问; |
WeightedResponseTimeRule | 根据平均响应时间计算所有服务的权重,响应时间越快服务权重就越大被选中的概率即越高,如果服务刚启动时统计信息不足,则使用RoundRobinRule策略,待统计信息足够会切换到该WeightedResponseTimeRule策略; |
RetryRule | 先按照RoundRobinRule策略分发,如果分发到的服务不能访问,则在指定时间内进行重试,分发其他可用的服务; |
BestAvailableRule | 先过滤掉由于多次访问故障的服务,然后选择一个并发量最小的服务; |
ZoneAvoidanceRule | 综合判断服务节点所在区域的性能和服务节点的可用性,来决定选择哪个服务; |
在服务消费者的配置类中加上负载均衡策略的配置
@Configuration
public class BeanConfig {
@LoadBalanced //使用Ribbon实现负载均衡的调用
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
/**
* 覆盖调Ribbon默认的轮询负载均衡策略
* @return
*/
@Bean
public IRule iRule(){
//采用随机的负载均衡策略
return new RandomRule();
}
}
@Configuration
public class BeanConfig {
@LoadBalanced //使用Ribbon实现负载均衡的调用
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
/**
* 覆盖调Ribbon默认的轮询负载均衡策略
* @return
*/
@Bean
public IRule iRule(){
//采用随机的负载均衡策略
return new RetryRule();
}
}
测试,因为只有轮询策略让消费者消费不了服务时,重试策略才会生效,让一台提供者挂掉,等到注册中心将关掉的服务从服务列表中踢出后,重试策略生效,只分发健康状态的服务
当我们从服务消费端去调用服务提供者的服务的时候,使用了一个极其方便的对象叫RestTemplate,当时我们只使用了RestTemplate中最简单的一个功能getForEntity发起了一个get请求去调用服务端的数据,同时,我们还通过配置@LoadBalanced注解开启客户端负载均衡,RestTemplate的功能非常强大.
在日常操作中,基于Rest的方式通常是四种情况,它们分表是:
GET请求 | 查询数据 |
POST请求 | 添加数据 |
PUT请求 | 修改数据 |
DELETE请求 | 删除数据 |
Get请求可以有两种方式:第一种:getForEntity该方法返回一个ResponseEntity对象,ResponseEntity是Spring对HTTP请求响应的封装,包括了几个重要的元素,比如响应码、contentType、contentLength、响应消息体等;
服务提供者代码:
@RestController
public class HelloController {
@RequestMapping("/service/hello")
public String hello(){
return "hello Springcloud!(服务提供者1)";
}
@RequestMapping("/service/user")
public User user(){
User user = new User();
user.setId(1);
user.setName("张三");
user.setPhone("123456");
return user;
}
@RequestMapping("/service/getUser")
public User getUser(@RequestParam("id") Integer id,
@RequestParam("name") String userName,
@RequestParam("phone") String phone) {
User user = new User();
user.setId(id);
user.setName(userName);
user.setPhone(phone);
return user;
}
}
服务消费者代码
@RestController
public class WebController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/web/hello")
public String hello() {
//调用远程的SpringCloud服务提供者提供的服务
// return restTemplate.getForEntity("http://localhost:8080/service/hello", String.class).getBody();
ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/hello", String.class);
String body = responseEntity.getBody();
HttpStatus statusCode = responseEntity.getStatusCode();
int statusCodeValue = responseEntity.getStatusCodeValue();
HttpHeaders headers = responseEntity.getHeaders();
System.out.println(body);
System.out.println(statusCode);
System.out.println(statusCodeValue);
System.out.println(headers);
return restTemplate.getForEntity("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/hello", String.class).getBody();
}
@RequestMapping("/web/user")
public User user() {
ResponseEntity<User> responseEntity = restTemplate.getForEntity("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/user", User.class);
User user = responseEntity.getBody();
return user;
}
/**
* 参数使用数组传递
* @return
*/
@RequestMapping("/web/getUser")
public User getUser() {
String[] strings = {"12","张三","88888888"};
ResponseEntity<User> responseEntity = restTemplate.getForEntity("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/getUser?id={1}&name={2}&phone={3}", User.class, strings);
User user = responseEntity.getBody();
return user;
}
/**
* 参数使用Map传递
* @return
*/
@RequestMapping("/web/getUser2")
public String getUser2() {
Map<String,String> paramMap = new HashMap<>();
paramMap.put("id","11");
paramMap.put("name","张三");
paramMap.put("phone","***");
ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/getUser?id={id}&name={name}&phone={phone}", String.class,paramMap);
String user = responseEntity.getBody();
return user;
}
}
hello Springcloud!(服务提供者2)
200 OK
200
[Content-Type:"text/plain;charset=UTF-8", Content-Length:"40", Date:"Wed, 04 Sep 2019 10:54:33 GMT"]
Post与Get请求非常类类似,主要有以下几个方法:
restTemplate.postForObject()
restTemplate.postForEntity()
restTemplate.postForLocation()
服务提供者:
@RequestMapping(value = "/service/addUser", method = RequestMethod.POST)
public User addUser(@RequestParam("id") Integer id,
@RequestParam("name") String userName,
@RequestParam("phone") String phone) {
User user = new User();
user.setId(id);
user.setName(userName);
user.setPhone(phone);
return user;
}
服务消费者:
@RequestMapping("/web/addUser")
public String addUser() {
MultiValueMap dataMap = new LinkedMultiValueMap();
dataMap.add("id",33);
dataMap.add("name","&&");
dataMap.add("phone","33333");
//注意使用普通的Map 接收不到值
ResponseEntity<String> responseEntity = restTemplate.postForEntity("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/addUser",dataMap, String.class);
String body = responseEntity.getBody();
return body;
}
@RequestMapping("/web/addUser2")
public User addUser2() {
MultiValueMap dataMap = new LinkedMultiValueMap();
dataMap.add("id",33);
dataMap.add("name","&&");
dataMap.add("phone","33333");
User user = restTemplate.postForObject("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/addUser",dataMap, User.class);
return user;
}
restTemplate.put();
服务提供者
@RequestMapping(value = "/service/updateUser", method = RequestMethod.PUT)
public User updateUser(@RequestParam("id") Integer id,
@RequestParam("name") String userName,
@RequestParam("phone") String phone) {
User user = new User();
user.setId(id);
user.setName(userName);
user.setPhone(phone);
return user;
}
服务消费者:
@RequestMapping("/web/updateUser")
public String updateUser() {
MultiValueMap dataMap = new LinkedMultiValueMap();
dataMap.add("id",33);
dataMap.add("name","&&");
dataMap.add("phone","33333");
restTemplate.put("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/updateUser",dataMap);
return "success";
}
restTemplate.delete();
服务提供者:
@RequestMapping(value = "/service/deleteUser", method = RequestMethod.DELETE)
public User deleteUser(@RequestParam("id") Integer id,
@RequestParam("name") String userName,
@RequestParam("phone") String phone) {
User user = new User();
user.setId(id);
user.setName(userName);
user.setPhone(phone);
return user;
}
服务消费者:
@RequestMapping("/web/deleteUser")
public String deleteUser() {
String[] strings = {"12","张三","88888888"};
restTemplate.delete("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/getUser?id={1}&name={2}&phone={3}",strings);
return "success";
}
@RequestMapping("/web/deleteUser2")
public String deleteUser2() {
Map<String,String> paramMap = new HashMap<>();
paramMap.put("id","11");
paramMap.put("name","张三");
paramMap.put("phone","***");
restTemplate.delete("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/getUser?id={id}&name={name}&phone={phone}",paramMap);
return "success";
}