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 的配置与测试代码。
- 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();
}
}
- 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) {
}
}
- 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 包下的都是原生负载均衡的配置与测试代码:
- 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);
}
}
- 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);
}
}
- 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,根据响应信息中输出的端口号即可测试是轮询访问还是随机访问了。
总结需要注意的点:
- 需要手动关闭默认开启的 Ribbon
- 负载均衡配置类不需要添加
@Configuration
注解 @LoadBalancerClients.defaultConfiguration
不同于 ribbon 的该配置:不会覆盖对于不同服务的配置,反而会进行累加导致运行时异常:根据 service id 找到多个负载均衡策略的 bean(但表面抛出的异常不是这个异常)