一、使用Ribbon自带的随机策略

  • MyRandomRule

 1 package com.sdkj.myrule.rule1;
 2 
 3 import com.netflix.loadbalancer.IRule;
 4 import com.netflix.loadbalancer.RandomRule;
 5 import org.springframework.context.annotation.Bean;
 6 import org.springframework.context.annotation.Configuration;
 7 
 8 /**
 9  * @Author wangshuo
10  * @Date 2022/5/22, 15:31
11  * 自定义随机策略
12  */
13 @Configuration
14 public class MyRandomRule {
15 
16     //随机策略
17     @Bean
18     public IRule myRule(){
19 
20         return new RandomRule();
21     }
22 }

 

   注意,官方文档明确给出警告:

    这个自定义配置类不能放在@Configuration所扫描的当前包及子包下,否则我们自定义的这个配置类就会被所有Ribbon客户端共享,达不到特殊化定制的目的了

    如果想要Ribbon客户端共享,那边就放在@Configuration所扫描的地方

    

  • App

 1 package com.sdkj;
 2 
 3 import com.sdkj.myrule.rule1.MyRandomRule;
 4 import com.sdkj.myrule.rule2.MyBalancedRule;
 5 import org.springframework.boot.SpringApplication;
 6 import org.springframework.boot.autoconfigure.SpringBootApplication;
 7 import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
 8 import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
 9 import org.springframework.cloud.netflix.ribbon.RibbonClient;
10 
11 /**
12  * @Author wangshuo
13  * @Date 2022/5/19, 19:46
14  * Please add a comment
15  */
16 @EnableEurekaClient/*注意这个是client和提供者server不一样*/
17 @SpringBootApplication(scanBasePackages = {"com.sdkj"})
18 //开启服务发现
19 @EnableDiscoveryClient
20 //使用自定义的Ribbon规则
21 @RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MyRandomRule.class)
22 public class App {
23 
24     public static void main(String[] args) {
25         SpringApplication.run(App.class);
26     }
27 }

 

二、自定义Ribbon均衡策略

  • MyBalancedRule

 1 package com.sdkj.myrule.rule2;
 2 
 3 import com.netflix.client.config.IClientConfig;
 4 import com.netflix.loadbalancer.AbstractLoadBalancerRule;
 5 import com.netflix.loadbalancer.ILoadBalancer;
 6 import com.netflix.loadbalancer.Server;
 7 
 8 import java.util.List;
 9 
10 /**
11  * @Author wangshuo
12  * @Date 2022/5/22, 17:06
13  * 自定义均衡策略
14  */
15 public class MyBalancedRule extends AbstractLoadBalancerRule {
16 
17     private int total = 0;             // 总共被调用的次数,目前要求每台被调用5次
18     private int currentIndex = 0;    // 当前提供服务的下标
19 
20     public Server choose(ILoadBalancer loadBalancer, Object key) {
21 
22         if (loadBalancer == null) {
23             return null;
24         }
25         Server server = null;
26 
27         while (server == null) {
28             if (Thread.interrupted()) {
29                 return null;
30             }
31             List<Server> upList = loadBalancer.getReachableServers(); //当前存活的服务
32             List<Server> allList = loadBalancer.getAllServers();  //获取全部的服务
33 
34             int serverCount = allList.size();
35             if (serverCount == 0) {
36                 return null;
37             }
38 
39             //int index = rand.nextInt(serverCount);
40             //server = upList.get(index);
41             if(total < 5)
42             {
43                 server = upList.get(currentIndex);
44                 total++;
45             }else {
46                 total = 0;
47                 currentIndex++;
48                 if(currentIndex >= upList.size())
49                 {
50                     currentIndex = 0;
51                 }
52             }
53 
54             if (server == null) {
55                 Thread.yield();
56                 continue;
57             }
58 
59             if (server.isAlive()) {
60                 return (server);
61             }
62 
63             // Shouldn't actually happen.. but must be transient or a bug.
64             server = null;
65             Thread.yield();
66         }
67         return server;
68     }
69 
70     public Server choose(Object key) {
71         return choose(getLoadBalancer(), key);
72     }
73     public void initWithNiwsConfig(IClientConfig clientConfig) {
74 
75     }
76 }
  • App

 1 package com.sdkj;
 2 
 3 import com.sdkj.myrule.rule1.MyRandomRule;
 4 import com.sdkj.myrule.rule2.MyBalancedRule;
 5 import org.springframework.boot.SpringApplication;
 6 import org.springframework.boot.autoconfigure.SpringBootApplication;
 7 import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
 8 import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
 9 import org.springframework.cloud.netflix.ribbon.RibbonClient;
10 
11 /**
12  * @Author wangshuo
13  * @Date 2022/5/19, 19:46
14  * Please add a comment
15  */
16 @EnableEurekaClient/*注意这个是client和提供者server不一样*/
17 @SpringBootApplication(scanBasePackages = {"com.sdkj"})
18 //开启服务发现
19 @EnableDiscoveryClient
20 //使用自定义的Ribbon规则
21 @RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MyBalancedRule.class)
22 public class App {
23 
24     public static void main(String[] args) {
25         SpringApplication.run(App.class);
26     }
27 }

 

三、自定义轮询均衡器

  流程:使用DiscoveryClient发现服务—》通过自定义均衡器筛选服务—》使用RestTemplate调用

  1、对外提供均衡器接口

 1 package com.sdkj.myrule.rule3;
 2 
 3 import org.springframework.cloud.client.ServiceInstance;
 4 import org.springframework.stereotype.Component;
 5 
 6 import java.util.List;
 7 
 8 /**
 9  * @Author wangshuo
10  * @Date 2022/5/22, 21:41
11  * Please add a comment
12  */
13 public interface LoadBalancer {
14 
15     ServiceInstance getInstance(List<ServiceInstance>serviceInstances);
16 }

 

  2、编写接口实现类

 1 package com.sdkj.myrule.rule3;
 2 
 3 import org.springframework.cloud.client.ServiceInstance;
 4 import org.springframework.stereotype.Component;
 5 
 6 import java.util.List;
 7 import java.util.concurrent.atomic.AtomicInteger;
 8 
 9 /**
10  * @Author wangshuo
11  * @Date 2022/5/22, 21:43
12  * Please add a comment
13  */
14 @Component
15 public class MyLB implements LoadBalancer{
16 
17     //轮询核心实现
18     @Override
19     public ServiceInstance getInstance(List<ServiceInstance> serviceInstances) {
20         int index = getAtomicInteger() % serviceInstances.size();
21         return serviceInstances.get(index);
22     }
23 
24     private AtomicInteger atomicInteger = new AtomicInteger(0);
25 
26     //获取下一个值(线程安全并对最大值做了处理)
27     public final int getAtomicInteger() {
28         int current;
29         int next;
30         do {
31             current = atomicInteger.getAndIncrement();
32             next = current >= Integer.MAX_VALUE ? 0 : current + 1;
33         }while (atomicInteger.compareAndSet(current,next));
34         System.out.println("next = " + next);
35         return next;
36     }
37 }

 

  3、编写controller方法

 1 package com.sdkj.controller;
 2 
 3 import com.sdkj.myrule.rule3.LoadBalancer;
 4 import com.sdkj.response.CommonReturnType;
 5 import org.springframework.beans.factory.annotation.Autowired;
 6 import org.springframework.cloud.client.ServiceInstance;
 7 import org.springframework.cloud.client.discovery.DiscoveryClient;
 8 import org.springframework.stereotype.Controller;
 9 import org.springframework.web.bind.annotation.CrossOrigin;
10 import org.springframework.web.bind.annotation.RequestMapping;
11 import org.springframework.web.bind.annotation.ResponseBody;
12 import org.springframework.web.client.RestTemplate;
13 
14 import java.util.List;
15 
16 /**
17  * @Author wangshuo
18  * @Date 2022/5/19, 19:52
19  * Please add a comment
20  */
21 @Controller
22 @RequestMapping(value = "/order")
23 @CrossOrigin(origins = {"*"}, allowCredentials = "true")
24 public class OrderController {
25 
26     private static final String url = "http://CLOUD-PAYMENT-SERVICE";
27     @Autowired
28     private RestTemplate restTemplate;
29     @Autowired
30     private LoadBalancer loadBalancer;
31     @Autowired
32     private DiscoveryClient discoveryClient;
33 
34     @RequestMapping("/getPaymentById")
35     @ResponseBody
36     public CommonReturnType getById(Long id) {
37         //不要忘记把参数传过去
38         return restTemplate.getForObject(url + "/payment/findById/?id=" + id, CommonReturnType.class);
39     }
40 
41     @RequestMapping("/getPort")
42     @ResponseBody
43     public String getPort() {
44 
45         return restTemplate.getForObject(url + "/payment/getPort", String.class);
46     }
47 
48     @RequestMapping("/getLBPort")
49     @ResponseBody
50     public String getLbPort() {
51 
52         List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
53         if (instances == null || instances.size() == 0)
54             return null;
55         ServiceInstance instance = loadBalancer.getInstance(instances);
56         return restTemplate.getForObject(instance.getUri() + "/payment/getPort", String.class);
57     }
58 }

 

    注意:

    1)restTemplate对象,未被Ribbon包装:需要把AppConfig类中的@LoadBalanced注解去掉

    2)此处的instance.getUri()获取的url地址,可能是主机名称+端口,需要改成是IP+端口。而要获取到的是IP+端口,需要服务提供者,在Eureka注册的时候,使用ip注册

    服务端增加配置 最新配置如下(搭建提供者集群后,除去端口不同其余配置应一样):

server:
  # 端口
  port: 8001
spring:
  #要配置集群、名字必须相同
  application:
    name: cloud-payment-service
  #安全认证
  security:
    user:
      name: root
      password: root
  #数据源基本配置
  datasource:
    url: jdbc:mysql://localhost:3306/test_springcloud
    username: root
    password: 678678
    #druid
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
eureka:
  instance:
    #instance.getUri()获取到的就是这个值(默认为主机名称+端口 / 这里修改为IP地址+端口)
    instance-id: ${spring.cloud.client.ip-address}:${server.port}
  client:
    # 表示将自己注册进Eureka Server默认为true
    register-with-eureka: true
    # 是否从Eureka Server抓去已有的注册信息,默认是true
    fetch-registry: true
    # 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
    service-url:
      defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@eureka1.com:8887/eureka
mybatis:
  mapper-locations: classpath:mapping/*.xml
#开启优雅停服
#(必须通过POST请求向Eureka Client发起一个shutdown请求。请求路径为:http://ip:port/shutdown。可以通过任意技术实现,如:HTTPClient,AJAX等。)
management:
  endpoint:
    shutdown:
      enabled: true