了解Ribbon
什么是负载均衡?负载均衡(LoadBanlance)就是将用户请求平均分摊到多个服务上,从而达到系统的高可用。
服务端负载均衡:利用代理服务器(nginx),先发送请求,然后通过负载均衡算法,在多个服务器之间选择一个进行访问;即在服务器端进行负载均衡算法分配。
客户端负载均衡:客户端会有一个服务器地址列表,在发送请求前通过负载均衡算法选择一个服务器,然后进行访问,即在客户端就进行负载均衡算法分配。
SpringCloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。(Eureka中已经集成了Ribbon,所以不需要引入新包)
项目搭建
注:在springcloud-eureka-application基础上做修改,我们提供两个生产者provider01与provider02
1、修改配置
在之前项目基础上,provider再复制一份新的出来,做个集群,保证端口不冲突,配置注意如下;
集群中的provider要保证:spring.application.name=provider相同,eureka.instance.instance-id不同,否则集群不成功。
例如:
spring.application.name均为provider
spring:
application:
name: provider
instance-id分别为provider01与provider02
eureka:
instance:
instance-id: provider01
eureka:
instance:
instance-id: provider02
2、修改consumer中的RestTemplate,加上@LoadBalanced注解
3、修改consumer中的调用方式
修改完成之后启动Eureka Server、Provider01、Provider02与Consumer,查看Eureka界面,如下所示:
调用consumer中的接口:http://localhost:8083/list
查看两个provider中的调用情况,会发现两个服务被轮询访问。(Ribbon默认采用的就是轮询算法)
完整代码如下:
https://gitee.com/superbutton/spring-cloud-study/tree/develop/springcloud-ribbon-application
那么为什么根据服务名就可以选择定位到服务的相关信息,可以追踪到源码的LoadBalancerInterceptor,看到相关的处理思路
这里rule可以查看到具体的负载均衡策略
默认采用轮询算法
修改负载均衡策略的两种方式
1、修改配置文件application.yml
修改格式:服务名.Ribbon. NFLoadBalancerRuleClassName=****(策略的全类名)
provider: #配置负载均衡策略的服务名
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #随机
2、修改代码
创建配置类,注意,该类不能放在ComponentScan的同包或子包下,这里我创建com.zh.rule.myrule包,之前的代码都在com.zh.cloud包下:
package com.zh.rule.myrule;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyRule {
@Bean
public IRule createRule() {
return new RandomRule();
}
}
在主类中添加注解@RibbonClient(name = “provider”,configuration = MyRule.class)
解释:name是服务提供者的服务名,configuration 配置的负载均衡的配置
package com.zh.cloud;
import com.zh.rule.myrule.MyRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
@SpringBootApplication
@EnableDiscoveryClient // 开启EurekaClient功能
@RibbonClient(name = "provider",configuration = MyRule.class)
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
注:如果有多个需要配置,如下所示:
@RibbonClients(@RibbonClient(name = "provider",configuration = MyRule.class),@RibbonClient(name = "provider2",configuration = MyRule2.class))
这种也适合自定义负载均衡策略。
有兴趣的话可以看看这种配置方法,感觉太麻烦。
常见的负载均衡策略
策略名称 | 策略对应的类名 | 实现原理 |
轮询策略(默认) | RoundRobinRule | 每次都顺序取得下一个 provider |
权重轮询策略 | WeightedResponseTimeRule | 根据每个 provider 的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性越低。 |
随机策略 | RandomRule | 从 provider 列表中随机选择一个provider |
最少并发数策略 | BestAvailableRule | 选择正在请求中的并发数最小的 provider |
在“选定的负载均衡策略”基础上进行重试机制 | RetryRule | 1.“选定的负载均衡策略”这个策略是轮询策略RoundRobinRule 2.该重试策略先设定一个阈值时间段,如果在这个阈值时间段内当选择 provider 不成功,则一直尝试采用“选定的负载均衡策略:轮询策略”最后选择一个可用的provider |
可用性敏感策略 | AvailabilityFilteringRule | 过滤性能差的 provider,有 2种:第一种:过滤掉在 eureka 中处于一直连接失败 provider 第二种:过滤掉高并发的 provider |
区域敏感性策略 | ZoneAvoidanceRule | 1.以一个区域为单位考察可用性,对于不可用的区域整个丢弃,从剩下区域中选可用的provider2.如果这个 ip 区域内有一个或多个实例不可达或响应变慢,都会降低该 ip 区域内其他 ip 被选中的权重。 |
重试策略
思考一个问题,两个provider,突然一个挂了,但是之前说过的eureka的自我保护策略,服务并没有被及时剔除,那么consumer访问时势必会出现问题。所以这里有一个重试机制。SpringCloud整合了Spring Retry增强了restTemplate的重试能力,当第一次访问服务失败之后,会再次重试另一个服务。
实现方式如下(调用端修改):
添加Spring-retry依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
修改配置文件:
spring:
cloud:
loadbalancer:
retry:
enabled: true # 开启Spring Cloud的重试功能
provider: #调用服务的服务名
ribbon:
ConnectTimeout: 250 # Ribbon的连接超时时间.超时后开始重试
ReadTimeout: 1000 # Ribbon的数据读取超时时间
OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
MaxAutoRetriesNextServer: 1 # 切换实例的重试次数
MaxAutoRetries: 1 # 对当前实例的重试次数
测试中随意kill一台provider,查看是否会出现调用异常。可以看到内部已经有了重试机制,不会再出现异常了。
完整代码:
https://gitee.com/superbutton/spring-cloud-study/tree/develop/springcloud-ribbon-retry
了解Feign
一个可以简化ribbon调用,方便开发的组件:Feign。
Feign是一个声明式WebService客户端,它让微服务之间的调用变得更简单了。(Feign已经封装了Ribbon,让ribbon的调用方式更简单,若要配置负载均衡策略之类的,还是按照之前的配置就行。)
项目构建(consumer改造)
导入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
主类上加使能注解:@EnableFeignClients
新建UserClientInter
package com.zh.cloud.api;
import com.zh.cloud.model.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.List;
@FeignClient(value = "provider") //调用的服务名
public interface UserClientInter {
/**
* 暴露接口
* @return
*/
@GetMapping("/v1/user/list") //此处的调用路径必须与服务提供者的路径相同
public List<User> queryUser(); //接口声明与服务提供者保持一致
}
controller调用
package com.zh.cloud.controller;
import com.zh.cloud.api.UserClientInter;
import com.zh.cloud.model.User;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.context.annotation.Scope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.util.List;
@RestController
@Scope("prototype")
public class UserController {
@Resource
private UserClientInter userClientInter;
@GetMapping("/list")
public List<User> getUsers() {
return userClientInter.queryUser();
}
}
启动进行访问:http://localhost:8082/list
完整代码如下:
https://gitee.com/superbutton/spring-cloud-study/tree/develop/springcloud-feign-application