概述
Spring Cloud 是基于Netflix Ribbon应用系统定制开发实现的一套客户端 应用系统定制开发负载均衡的工具。
简单的说,Ribbon是Netflix应用系统定制开发发布的开源项目,应用系统定制开发主要功能是提供客户端应用系统定制开发的软件负载均衡算法和服务调用。Ribbon应用系统定制开发客户端组件提供一系列应用系统定制开发完善的配置项如连接超时,重试等。简单的说,应用系统定制开发就是在配置文件中列出Load Balancer(简称LB)应用系统定制开发后面所有的机器,Ribbon应用系统定制开发会自动的帮助你基于某种规则(如简单,应用系统定制开发随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。
官网
https://github.com/Netflix/ribbon/wiki/Getting-Started
LB负载均衡
LB负载均衡(Load Balance)是什么:
简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)。
常见的负载均衡有软件Nginx,LVS,硬件 F5等。
Ribbon本地负载均衡客户端 VS Nginx服务端负载均衡区别:
Nginx是服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求。即负载均衡是由服务端实现的。
Ribbon本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。
集中式LB
即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策略转发至服务的提供方;
进程内LB
将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。
Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
Ribbon负载均衡演示
Ribbon在工作时分成两步
第一步先选择 EurekaServer ,它优先选择在同一个区域内负载较少的server.
第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。
其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。
总结
Ribbon其实就是一个软负载均衡的客户端组件,
他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。
改pom
引入spring-cloud--ribbon依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId></dependency>
- 1
- 2
- 3
- 4
spring-cloud-starter-netflix-eureka-client自带了spring-cloud-starter-ribbon依赖,所以无需再单独引用ribbon,否则就可能会发生依赖版本冲突,也可通过排除掉eureka中的ribbon解决。
Ribbon核心组件IRule
IRule:根据特定算法中从服务列表中选取一个要访问的服务
com.netflix.loadbalancer.RoundRobinRule 轮询
com.netflix.loadbalancer.RandomRule 随机
com.netflix.loadbalancer.RetryRule 先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务
WeightedResponseTimeRule 对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
BestAvailableRule 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
AvailabilityFilteringRule 先过滤掉故障实例,再选择并发较小的实例
ZoneAvoidanceRule 默认规则,复合判断server所在区域的性能和server的可用性选择服务器
默认为轮询的方式:
@Configurationpublic class ApplicationContextConfig { /** * RestTemplate提供了多种便捷访问远程Http服务的方法, * 是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集 * @return */ @Bean @LoadBalanced //使用@LoadBalanced注解赋予RestTemplate负载均衡的能力 public RestTemplate restTemplate() { return new RestTemplate(); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
修改负载均衡规则:
官方文档明确给出了警告:
这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,
否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。
/** * 替换ribbon负载均衡规则(默认为轮询) * 这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下, * 否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。 * @author zhangYang * @date 2022/4/10/0:16 */@Configurationpublic class MySelfRule { /** * 替换为随机 * @return */ @Bean public IRule myRule() { return new RandomRule(); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
主启动类添加@RibbonClient
/**消费者 * @author yangzhang * @date 2022/1/21/16:00 */@SpringBootApplication@EnableEurekaClient//在启动该微服务的时候就能去加载我们的自定义Ribbon配置类,从而使配置生效(修改负载均衡规则为随机)@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)public class OrderMain8002 { public static void main(String[] args) { SpringApplication.run(OrderMain8002.class, args); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
手写负载均衡规则算法
负载均衡算法原理:
rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标 ,每次服务重启动后rest接口计数从1开始。
List instances = discoveryClient.getInstances(“CLOUD-PAYMENT-SERVICE”);
如: List [0] instances = 127.0.0.1:8002
List [1] instances = 127.0.0.1:8001
8001+ 8002 组合成为集群,它们共计2台机器,集群总数为2, 按照轮询算法原理:
当总请求数为1时: 1 % 2 =1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8001
当总请求数位2时: 2 % 2 =0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002
当总请求数位3时: 3 % 2 =1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8001
当总请求数位4时: 4 % 2 =0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002
如此类推…
微服务改造:
@RestController@Slf4jpublic class PaymentController { @Resource private PaymentService paymentService; @Value("${server.port}") private String serverPort; //服务发现 @Resource private DiscoveryClient discoveryClient; @PostMapping("/payment/create") public CommentResult create(@RequestBody Payment payment) { int result = paymentService.create(payment); if (result > 0) { return new CommentResult(200, "插入数据成功,serverPort:" + serverPort, result); } else { return new CommentResult(444, "插入数据失败", null); } } @GetMapping("/payment/get/{id}") public CommentResult getPaymentById(@PathVariable("id") Long id) { Payment payment = paymentService.getPaymentById(id); log.info("******查询结果:{}", payment); if (payment != null) { return new CommentResult(200, "查询成功,serverPort:" + serverPort, payment); }else { return new CommentResult(444, "没有对应记录,查询ID" + id, null); } } /** * 对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息 * @return */ @GetMapping("/payment/discover") public Object discover() { //获取微服务列表 List<String> services = discoveryClient.getServices(); for (String service : services) { log.info("*****service:" + service); } //根据微服务名称获取具体的微服务信息 List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE"); for (ServiceInstance instance : instances) { log.info(instance.getServiceId() + "\t" + instance.getHost() + "\t" + instance.getPort() + "\t" + instance.getUri()); } return this.discoveryClient; } /** * 自定义负载均衡算法规则 * @return */ @GetMapping(value = "/payment/lb") public String getPaymentLB() { return serverPort; }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
ApplicationContextBean去掉注解@LoadBalanced
@Configurationpublic class ApplicationContextConfig { /** * RestTemplate提供了多种便捷访问远程Http服务的方法, * 是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集 * @return */ @Bean //@LoadBalanced //使用@LoadBalanced注解赋予RestTemplate负载均衡的能力 public RestTemplate restTemplate() { return new RestTemplate(); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
新建接口
/** * 自定义负载均衡算法规则(必须把默认的@LoadBalanced注释) * @author zhangYang * @date 2022/4/12/22:49 */public interface LoadBalancer { /** * 获取每个微服务名称 * @param serviceInstances * @return */ ServiceInstance instances(List<ServiceInstance> serviceInstances);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
/** * 自定义负载均衡算法规则(必须把默认的@LoadBalanced注释) * @author zhangYang * @date 2022/4/12/22:52 */@Componentpublic class MyLb implements LoadBalancer { //原子操作类,设初值为0 private AtomicInteger atomicInteger = new AtomicInteger(0); public final int getAndIncrement() { int current; int next; do { //获取值 current = this.atomicInteger.get(); next = current >= 2147483647 ? 0 : current + 1; //利用CAS自旋锁的原理 若期望值current为初始值0的话那么就更新为next } while (!this.atomicInteger.compareAndSet(current, next)); System.out.println("******第几次访问,次数next:" + next); return next; } /** * 获取每个微服务名称 * @param serviceInstances * @return */ @Override public ServiceInstance instances(List<ServiceInstance> serviceInstances) { //负载均衡算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标 ,每次服务重启动后rest接口计数从1开始。 int index = getAndIncrement() % serviceInstances.size(); System.out.println(serviceInstances.get(index) + "***********************"); return serviceInstances.get(index); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
@RestControllerpublic class OrderController {// public static final String PaymentUrl = "http://localhost:8001"; //单机服务// 通过在eureka上注册过的微服务名称调用 public static final String PaymentUrl = "http://CLOUD-PAYMENT-SERVICE"; //集群部署 @Autowired private RestTemplate restTemplate; @Autowired private DiscoveryClient discoveryClient; @Autowired private LoadBalancer loadBalancer; @GetMapping("/consumer/payment/get/{id}") public CommentResult getPayment(@PathVariable("id") Long id) { return restTemplate.getForObject(PaymentUrl + "/payment/get/" + id, CommentResult.class, id); } /** * 客户端用浏览器是get请求,但是底层实质发送post调用服务端8001 * @param payment * @return */ @GetMapping("/consumer/payment/create") public CommentResult create(Payment payment) { //使用restTemplate访问restful接口非常的简单粗暴无脑。 //(url, requestMap, ResponseBean.class)这三个参数分别代表 //REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。 return restTemplate.postForObject(PaymentUrl + "/payment/create", payment, CommentResult.class); } @GetMapping("/consumer/payment/getForEntity/{id}") public CommentResult<Payment> getPayment2(@PathVariable("id") Long id) { ResponseEntity<CommentResult> forEntity = restTemplate.getForEntity(PaymentUrl + "/payment/get/" + id, CommentResult.class); if (forEntity.getStatusCode().is2xxSuccessful()) { return forEntity.getBody(); } else { return new CommentResult<>(444, "操作失败!"); } } /** * 自定义负载均衡算法规则(必须把默认的@LoadBalanced注释) * @return */ @GetMapping(value = "/consumer/payment/lb") public String getPaymentLB() { //根据微服务名称获取具体的微服务信息 List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE"); System.out.println(instances + "======================="); if (null == instances || instances.size() <= 0) { return null; } ServiceInstance instance = loadBalancer.instances(instances); URI uri = instance.getUri(); System.out.println(uri + "---------------------"); return restTemplate.getForObject(uri + "/payment/lb", String.class); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
测试:http://localhost/consumer/payment/lb