发表于: 2022-03-22 23:26:38

2 616


一,今天完成的事情

任务九

1,

根据拆分原则,我首先不考虑项目中一定要有entity,dao等针对数据库的操作。先把Spring Cloud能正常使用。


2,

我在看最近Spring Cloud官方demo注意到的一位作者,Olga Maciaszek-Sharma 小姐姐。她的相关的代码和说明我一定要参考。

她的履历比较清楚,所以她展示的Spring Cloud相关部分一般都必要而且重要的。


我查找了她维护的项目,发现没有Ribbon。然后查看我在项目中

<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-dependencies</artifactId>
 <version>2021.0.0</version>
 <type>pom</type>
 <scope>import</scope>
</dependency>

很多中文资料说包含的Ribbon,这个负载均衡的解决方案在2021版本包中找不到了。


继续查找现在的官方文档,我收回我之前日报中说明的:Spring Cloud Netflix是绕不过去的组件的话。Spring Cloud Netflix除了现在的Eureka,2020版本以及之后,就是不是只有伦敦地铁站的名字命名的版本,都不再优先考虑使用。


Hystrix功能应该考虑 Resilience4j。

Hystrix Dashboard / Turbine 应该考虑Micrometer + Monitoring System。

Ribbon 应该使用Spring Cloud Loadbalancer。

Zuul应该使用 Spring Cloud Gateway。

Archaius本来就不考虑在我的项目中,采用 Spring Boot外部化配置 + Spring Cloud配置。


所以我的项目不会包含Ribbon。


3,我上次代码中,下面的代码也能完成随机的操作

@RestController
public class HelloServerController {
@Autowired
   DiscoveryClient client;

   @RequestMapping("/")
public String hello() {
       List<ServiceInstance> instances = client.getInstances("HelloServer");
       ServiceInstance selectedInstance = instances
.get(new Random().nextInt(instances.size()));
       return "Hello World: " + selectedInstance.getServiceId() + ":" + selectedInstance
.getHost() + ":" + selectedInstance.getPort()+"  uri:   " +selectedInstance.getUri();
   }
}


DiscoveryClient

是Spring Cloud中重要的类,能通过在Eureka的注册名拿到实例。它带有的方法值得学习。


但是它的特性,应该不能和OpenFeign搭配使用。OpenFeign的设计方适合微服务调用,比较无感知的调用,应该使用,而不是换成带DiscoveryClient类的代码完成随机。而且我发现Olga Maciaszek-Sharma 小姐姐对LoadBalancer的介绍比较多,我感觉就是这个。


4,如果不设置,没有其它代码,2021年版本的Spring Cloud OpenFeign提供负载均衡,而且是自动提供,策略是轮询负载均衡。我只用Feign的时候点了30次以上,是不会改变顺序的轮询。

并且,我倒是在2021版本包中注意到了下面代码。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.cloud.loadbalancer.core;

import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import reactor.core.publisher.Mono;

public class RandomLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private static final Log log = LogFactory.getLog(RandomLoadBalancer.class);
   private final String serviceId;
   private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

   public RandomLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {
this.serviceId = serviceId;
       this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
   }

public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = (ServiceInstanceListSupplier)this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
       return supplier.get(request).next().map((serviceInstances) -> {
return this.processInstanceResponse(supplier, serviceInstances);
       });
   }

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

return serviceInstanceResponse;
   }

private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
if (instances.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No servers available for service: " + this.serviceId);
           }

return new EmptyResponse();
       } else {
int index = ThreadLocalRandom.current().nextInt(instances.size());
           ServiceInstance instance = (ServiceInstance)instances.get(index);
           return new DefaultResponse(instance);
       }
}
}

通过中文资料大家的观察,LoadBalancer只有轮询和随机两种算法。可以通过implements ReactorServiceInstanceLoadBalancer自定义方式进行负载均衡。虽然LoadBalancer直接提供的负载均衡方式只有两种,远远少于Ribbon自带的7种,还是LoadBalancer更优秀。


通过官方例子,看到

@SpringBootApplication
@RestController
public class UserApplication {

private final WebClient.Builder loadBalancedWebClientBuilder;
  private final ReactorLoadBalancerExchangeFilterFunction lbFunction;

  public UserApplication(WebClient.Builder webClientBuilder,
        ReactorLoadBalancerExchangeFilterFunction lbFunction) {
this.loadBalancedWebClientBuilder = webClientBuilder;
     this.lbFunction = lbFunction;
  }

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

@RequestMapping("/hi")
public Mono<String> hi(@RequestParam(value = "name", defaultValue = "Mary") String name) {
return loadBalancedWebClientBuilder.build().get().uri("http://say-hello/greeting")
.retrieve().bodyToMono(String.class)
.map(greeting -> String.format("%s, %s!", greeting, name));
  }

@RequestMapping("/hello")
public Mono<String> hello(@RequestParam(value = "name", defaultValue = "John") String name) {
return WebClient.builder()
.filter(lbFunction)
.build().get().uri("http://say-hello/greeting")
.retrieve().bodyToMono(String.class)
.map(greeting -> String.format("%s, %s!", greeting, name));
  }
}


提供了

WebClient.Builder

ReactorLoadBalancerExchangeFilterFunction

能看懂代码,才更知道怎么做。


5,Spring Cloud的负载均衡设置在客户端。

我配合LoadBalancer使用的最终还是

RestTemplate

,它的应用范围广,需要了解基本使用。

服务器提供的服务代码就是controller能够回复自己的接口号,说明自己是同

spring:
application:
name: HelloClient

的服务器,但是端口不同。


可以复制多台服务器,只改端口号,写出能证明自己端口号的controller即可,区分不同服务器。也可以用Intellij

设置端口等属性。

最后要运行至少2台服务器,所以复制代码没错。


通过OpenFeign调用。但是修改为随机的负载均衡方式。

最终我的客户端的模块结构如下:


需要特别贴出的是loadbalance包中的内容

package demo.loadbalance;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;

//这里 不需要 @configuration注解  不需要 不需要
public class CustomLoadBalancerConfiguration {

@Bean
   ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
                                                           LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);

       return new RandomLoadBalancer(loadBalancerClientFactory
.getLazyProvider(name, ServiceInstanceListSupplier.class),
               name);
   }
}


package demo.loadbalance;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
//在这里配置我们自定义的LoadBalancer策略 如果有大佬想自己扩展算法 需要实现ReactorServiceInstanceLoadBalancer接口
//@LoadBalancerClients(defaultConfiguration = {name = "CLOUD-PAYMENT-SERVICE", configuration = CustomLoadBalancerConfiguration.class})
//注意这里的name属性 需要和eureka页面中的服务提供者名字一直 此时页面中是大写
@LoadBalancerClient(name = "HELLOSERVER",configuration = CustomLoadBalancerConfiguration.class)
public class ApplicationContextConfig {

//将这个对象放入ioc容器
   @Bean
   @LoadBalanced  //使用这个注解给restTemplate赋予了负载均衡的能力
   public RestTemplate getRestTemplate(){
return new RestTemplate();
   }
}


因为configuration = CustomLoadBalancerConfiguration.class,所以根据

return new RandomLoadBalancer(

,如果能理解这个随机负载均衡类怎么使用,就能知道能使用RestTemplate达到使用随机策略的目的。


最终controller

package demo.controller;

import demo.service.HelloClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;
import java.util.Random;

@RestController
public class HelloClientController {

private static final Logger LOGGER = LoggerFactory.getLogger(HelloClientController.class);

   @Autowired
   HelloClient client;

   @Autowired
   RestTemplate template;

   @Autowired
   Environment environment;

   @RequestMapping("/")
public String hello() {
return client.hello();

   }

@RequestMapping("/random")
public String slow() {
String url = "http://HelloServer";
       String callmeResponse = template.getForObject(url, String.class);
       LOGGER.info("Response: {}", callmeResponse);
       return "I'm Caller running on port " + environment.getProperty("local.server.port")
+ " calling-> " + callmeResponse;
   }
}


启动类就只有SpringBoot启动类的代码


6,

@Override
public Flux<List<ServiceInstance>> get() {
return Flux.just(Arrays
.asList(new DefaultServiceInstance(serviceId + "1", serviceId, "localhost", 8090, false),
              new DefaultServiceInstance(serviceId + "2", serviceId, "localhost", 9092, false),
              new DefaultServiceInstance(serviceId + "3", serviceId, "localhost", 9999, false)));
}

官方使用的Flux也可以注意


7,最后测试,发现确实是随机访问,不是轮询。成功。

3个服务,保证服务访问到可能性基本相同,从312的顺序变成了321




二,今天问题

继续理解Spring Cloud 2020及之后版本负载均衡使用过程中出现过的各类知识点

包括类的使用等知识,越容易理解


三,今天的收获

Spring Cloud 2020及之后版本负载均衡。抛开数据库先使用。

做事情我一直认为方向重要。

Spring Cloud的负载均衡设置在客户端。

不能因为一个产品中文参考资料多,使用时间长,优先考虑那个产品。

地球上有苦难。保守看顾搜救队员的工作,安慰遇难者亲人朋友的心


四,明天的计划

任务九



返回列表 返回列表
评论

    分享到