SpringCloud - RPC (2) 集成 Ribbon 与 SpringCloudLoadBalancer

Ribbon

我们继续在 my-mall 项目中新增一个服务,端口为 6400

引入依赖:

<dependencies>
    <!-- spring cloud -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    <!-- 实际上 nacos-discovery 已经引入 ribbon 了 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency>
    <!-- 官方原生的负载均衡组件 & 可以用于增强 RestTemplate,使其带有负载均衡调用功能 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>

    <!-- springboot starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

ribbon 包下都是关于 Ribbon 的配置与测试代码。

load-balance-service-ribbon-pkg

  1. config:调用不同服务时采用的负载均衡策略配置,以及对于所有服务调用使用的默认策略
// 默认 Ribbon 配置
@Configuration
public class DefaultRibbonConfig {

    @Bean
    public IRule iRule() {
//        return new NacosRule();
        return new MyRibbonRule(1);
    }
}

// 商品服务 Ribbon 配置
@Configuration
public class GoodsServiceRibbonConfig {

    // 随机
    @Bean
    public IRule iRule() {
        return new RandomRule();
    }
}

// 订单服务 Ribbon 配置
@Configuration
public class OrderServiceRibbonConfig {

    // 轮询
    @Bean
    public IRule iRule() {
        return new RoundRobinRule();
    }
}
  1. rule:自定义负载均衡策略
public class MyRibbonRule extends AbstractLoadBalancerRule {

    private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    private final AtomicInteger nextServerIndex = new AtomicInteger(0);

    private int maxRetry;

    public MyRibbonRule() {}

    public MyRibbonRule(int maxRetry) {
        this.maxRetry = maxRetry;
    }

    @Override
    public Server choose(Object key) {
        ILoadBalancer loadBalancer = getLoadBalancer();
        List<Server> allServer = loadBalancer.getAllServers();
        if (allServer == null || allServer.isEmpty()) {
            return null;
        }
        Server server;
        int retry = 0;
        int maxRetry = this.maxRetry;
        int allServerCount = allServer.size();
        do {
            int nextServerIndex = getNextServerIndex(allServerCount);
            server = allServer.get(nextServerIndex);
            if (server.isAlive() && server.isReadyToServe()) {
                return server;
            }
        }
        while (++retry < maxRetry);
        log.warn("No available alive servers after {} tries from load balancer: {}", retry, loadBalancer);
        return null;
    }

    private int getNextServerIndex(int allServerCount) {
        while (true) {
            int current = nextServerIndex.get();
            int next = (current + 1) % allServerCount;
            if (nextServerIndex.compareAndSet(current, next)) {
                return next;
            }
        }
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {

    }
}
  1. RibbonLoadBalanceServiceApplication:测试启动类
// 对不同服务设置不同的负载均衡策略
@RibbonClients(
        value = {
                @RibbonClient(name = "nacos-goods-service", configuration = GoodsServiceRibbonConfig.class),
                @RibbonClient(name = "nacos-order-service", configuration = OrderServiceRibbonConfig.class)
        }
        // 也可以写一个通用配置使得生效与所有节点 (注意此配置会覆盖对于不同服务的配置)
        // , defaultConfiguration = DefaultRibbonConfig.class
)
@SpringBootApplication
public class RibbonLoadBalanceServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(RibbonLoadBalanceServiceApplication.class, args);
    }

    @RestController
    public class RibbonController {

        @Bean
        @LoadBalanced // 赋予负载均衡的能力
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }

        @Resource
        private RestTemplate restTemplate;

        @GetMapping(value = "/ribbon/load-balance/{service-name}/{string}")
        public String echo(@PathVariable("service-name") String serviceName, @PathVariable String string) {
            return restTemplate.getForObject(String.format("http://%s/echo/%s", serviceName, string), String.class);
        }
    }
}

order-service、goods-service 接口:

@RestController
public class OrderController {

    @Value("${server.port}")
    private String port;

    @GetMapping(value = "/echo/{string}")
    public String echo(@PathVariable String string) {
        return String.format("Order service %s %s", port, string);
    }
}

@RestController
public class GoodsController {

    @Value("${server.port}")
    private String port;

    @GetMapping(value = "/echo/{string}")
    public String echo(@PathVariable String string) {
        return String.format("Goods service %s %s", port, string);
    }
}

启动服务后,访问 localhost:/ribbon/load-balance/{service-name}/{string},传递不同 service-name,根据响应信息中输出的端口号即可测试是轮询访问还是随机访问了。

Load balancer

引入依赖(在测试 ribbon 时已经引入了):

<!-- 官方原生的负载均衡组件 & 可以用于增强 RestTemplate,使其带有负载均衡调用功能 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

bootstrap.yaml 中关闭默认启用的 Ribbon,继而才能使用 SpringCloud 原生的负载均衡组件:

spring:
  cloud:
    loadbalancer:
      ribbon:
        enabled: false

springcloud 包下的都是原生负载均衡的配置与测试代码:

load-balance-service-springcloudloadbalance-pkg

  1. config:调用不同服务时采用的负载均衡策略配置,以及对于所有服务调用使用的默认策略
// @Configuration 
// 注意,此处不能增加 @Configuration 注解,这样反而会使得下方的 @Bean 方法形参中注入不了 LoadBalancerClientFactory,说明该参数是负载均衡配置类的处理类传入的。
public class DefaultServiceLoadBalancerConfig {

    @Bean
    public ReactorLoadBalancer<ServiceInstance> myLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        ObjectProvider<ServiceInstanceListSupplier> provider = loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class);
        return new MyLoadBalancer(provider, name);
    }
}


public class GoodsServiceLoadBalancerConfig {

    // 随机
    @Bean
    public ReactorLoadBalancer<ServiceInstance> goodsServiceRandomLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        ObjectProvider<ServiceInstanceListSupplier> provider = loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class);
        return new RandomLoadBalancer(provider, name);
    }
}


public class OrderServiceLoadBalancerConfig {

    // 轮询
    @Bean
    public ReactorLoadBalancer<ServiceInstance> orderServiceRoundRobinLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        ObjectProvider<ServiceInstanceListSupplier> provider = loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class);
        return new RoundRobinLoadBalancer(provider, name);
    }
}
  1. loadbalancer:自定义负载均衡策略
public class MyLoadBalancer implements ReactorServiceInstanceLoadBalancer {

    private static final Log log = LogFactory.getLog(MyLoadBalancer.class);

    private final AtomicInteger position;

    private final String serviceId;

    private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;


    public MyLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {
        this.serviceId = serviceId;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        this.position = new AtomicInteger(0);
    }

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        // TODO: move supplier to Request?
        // Temporary conditional logic till deprecated members are removed.
        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier.get()
                .next()
                .map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
    }

    private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {
        Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);
        if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
            ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
        }
        return serviceInstanceResponse;
    }

    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
        if (instances.isEmpty()) {
            log.warn("No servers available for service: " + this.serviceId);
            return new EmptyResponse();
        }
        // TODO: enforce order?
        int pos = Math.abs(this.position.incrementAndGet());

        ServiceInstance instance = instances.get(pos % instances.size());

        return new DefaultResponse(instance);
    }
}
  1. SpringCloudLoadBalancerServiceApplication:测试启动类

@LoadBalancerClients(
        value = {
                @LoadBalancerClient(name = "nacos-goods-service", configuration = GoodsServiceLoadBalancerConfig.class),
                @LoadBalancerClient(name = "nacos-order-service", configuration = OrderServiceLoadBalancerConfig.class)
        }
        // 也可以写一个通用配置使得生效与所有节点 (注意此配置不同于 ribbon 不会覆盖对于不同服务的配置,反而会进行累加导致运行时异常:根据 service id 找到多个负载均衡策略的 bean,但表面抛出的异常不是这个异常)
//        , defaultConfiguration = DefaultServiceLoadBalancerConfig.class
)
@SpringBootApplication
public class SpringCloudLoadBalancerServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringCloudLoadBalancerServiceApplication.class, args);
    }

    @RestController
    public class SpringCloudLoadBalancerController {

        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }

        @Resource
        private RestTemplate restTemplate;

        @GetMapping(value = "/spring-cloud/load-balance/{service-name}/{string}")
        public String echo(@PathVariable("service-name") String serviceName, @PathVariable String string) {
            return restTemplate.getForObject(String.format("http://%s/echo/%s", serviceName, string), String.class);
        }
    }
}

启动服务后,访问 localhost:/ribbon/load-balance/{service-name}/{string},传递不同 service-name,根据响应信息中输出的端口号即可测试是轮询访问还是随机访问了。

总结需要注意的点:

  1. 需要手动关闭默认开启的 Ribbon
  2. 负载均衡配置类不需要添加 @Configuration 注解
  3. @LoadBalancerClients.defaultConfiguration 不同于 ribbon 的该配置:不会覆盖对于不同服务的配置,反而会进行累加导致运行时异常:根据 service id 找到多个负载均衡策略的 bean(但表面抛出的异常不是这个异常)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值