跑步机网站建设思维导图,重庆seo的薪酬水平,刚刚好痛,国际军事新闻最新消息深入理解Ribbon原理并手写一个微服务负载均衡器 负载均衡器理解Ribbon原理手写一个微服务负载均衡器总体设计LoadBalanceClientHttpRequestFactorySimpleLoadBalanceClientSimpleLoadBalancerLoadBalanceRulespring.factories与LoadBalanceConfig 负载均衡器
在微服务架构里面… 深入理解Ribbon原理并手写一个微服务负载均衡器 负载均衡器理解Ribbon原理手写一个微服务负载均衡器总体设计LoadBalanceClientHttpRequestFactorySimpleLoadBalanceClientSimpleLoadBalancerLoadBalanceRulespring.factories与LoadBalanceConfig 负载均衡器
在微服务架构里面我们的服务消费者请求服务提供者通常使用RestTemplate发起http请求。 我们可以写死服务提供者的ip地址和端口号然后通过RestTemplate发起http请求时指定该服务提供者的ip地址和端口号。我们可以写死服务提供者的ip地址端口号但是一个服务通常有好几个服务提供者节点组成一个集群这时候服务消费者就要记录所有服务提供者的ip地址端口号并且要自行决定请求哪一个节点这是非常不便于维护的。即使只有一个服务提供者它的ip地址和端口好也是有可能会变的。 在微服务的世界里负载均衡器是一个重要组成部分。而负载均衡器可以使得服务消费者可以按照某种负载均衡策略请求微服务集群中的不同服务提供者节点。 由于有了负载均衡器服务消费者请求服务提供者不再需要通过ip地址加端口号的方式而是可以以服务名作为域名负载均衡器会通过一定的负载均衡策略选择服务名对应的微服务集群中的其中一个服务提供者节点将请求地址中的服务名替换为该节点的ip地址端口号。 理解Ribbon原理
Ribbon是一个经典的微服务负载均衡器它是微服务客户端的负载均衡器。通过引入Ribbon我们的服务消费者可以通过Ribbon的负载均衡机制选择服务提供者集群中的某个节点发起请求。 Ribbon通过在RestTemplate中加入拦截器的方式扩展了RestTemplate的能力使得它具备客户端负载均衡的能力。Ribbon会在RestTemplate的拦截器链interceptors中加入一个自己的拦截器LoadBalancerInterceptor这个LoadBalancerInterceptor会为RestTemplate提供负载均衡的能力。 LoadBalancerInterceptor被添加到RestTemplate之后每个通过RestTemplate发起的http请求都会经过LoadBalancerInterceptor的处理。LoadBalancerInterceptor会调用LoadBalancerClient负载均衡客户端进行处理LoadBalancerClient会通过Ribbon的负载均衡器ILoadBalancer根据负载均衡策略从服务提供者列表中选出一个节点然后LoadBalancerClient根据选取到的负载均衡节点的ip地址和端口号重写请求的url。 这样RestTemplate拿到重写后的url就可以请求对应的服务提供者节点了。
那么还剩下一个问题LoadBalancerInterceptor是什么时候又是如何被添加到RestTemplate的拦截器链的呢
其实Ribbon利用了Spring的SmartInitializingSingleton这个扩展点Spring会在完成所有非懒加载单例bean的初始化后触发SmartInitializingSingleton的调用。Ribbon扩展了Spring的这个SmartInitializingSingleton接口并往Spring容器中注册。 Spring在完成所有非懒加载单例bean的初始化后触发该SmartInitializingSingleton的调用往RestTemplate的拦截器链中添加LoadBalancerInterceptor。 手写一个微服务负载均衡器
了解了微服务负载均衡器的作用又理解了Ribbon的原理之后我们就可以参照Ribbon动手写一个自己的微服务负载均衡器了。
我们大体上还是参照Ribbon增强RestTemplate的方式但是我们不像Ribbon那样往RestTemplate的拦截器链上加入自己的拦截器而是使用另外一个接口ClientHttpRequestFactory。
在RestTemplate发起http请求时会调用ClientHttpRequestFactory的createRequest(URI uri, HttpMethod httpMethod)方法构建一个ClientHttpRequest对象里面包含了请求的url地址。然后再调用这个request对象的execute()方法发起http请求返回一个response对象。这一切的逻辑就在RestTemplate的doExecute()方法中。
RestTemplate#doExecute protected T T doExecute(URI url, HttpMethod method, ...) throws RestClientException {...ClientHttpResponse response null;try {// 调用ClientHttpRequestFactory的createRequest()方法方法构造ClientHttpRequestClientHttpRequest request createRequest(url, method);...// 调用ClientHttpRequest的execute()方法发起http请求返回responseresponse request.execute();...}catch (...) {...}...}总体设计
于是我们的大体设计就是实现一个自己的ClientHttpRequestFactory在ClientHttpRequestFactory的createRequest方法里面进行负载均衡和重构url的操作。而我们的ClientHttpRequestFactory对象也是通过Spring的扩展点SmartInitializingSingleton接口放入到RestTemplate中。 我们的框架设计大概就是下面那样 除了ClientHttpRequestFactory以外我们还要实现LoadBalanceClient负载均衡客户端ClientHttpRequestFactory会调用LoadBalanceClient。然后LoadBalanceClient里面是一个loadBalancerMap负载均衡器mapkey是服务名value是对应的LoadBalancer负载均衡器。
那么整体流程如下
ClientHttpRequestFactory调用LoadBalanceClientLoadBalanceClient从url中取出serviceName以serviceName为key从loadBalancerMap中取出对应的LoadBalancerLoadBalancer进行负载均衡选取一个节点LoadBalanceClient获取LoadBalancer返回的节点根据节点的ip地址和port端口重写urlClientHttpRequestFactory利用重写的url构建ClientHttpRequest对象 上图除开灰色部分其余的部分都是我们要实现的逻辑。
其中LoadBalancer里面还有一个RegistryCenterClient对象和LoadBalanceRule对象。RegistryCenterClient是注册中心客户端用于从注册中心中根据服务名serviceName查询服务提供者列表的。而LoadBalanceRule则是负载均衡规则。 总体设计就讲述完毕下面我们就去看一下代码。
LoadBalanceClientHttpRequestFactory
我们实现的ClientHttpRequestFactory名字叫做LoadBalanceClientHttpRequestFactory它实现了ClientHttpRequestFactory接口。在LoadBalanceClientHttpRequestFactory中调用我们自己实现的负载均衡客户端LoadBalanceClient进行负载均衡和重构url的操作然后使用重构后的url构建一个Request对象返回。 public class LoadBalanceClientHttpRequestFactory implements ClientHttpRequestFactory {private ClientHttpRequestFactory parent;private LoadBalanceClient loadBalanceClient;...Overridepublic ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {uri URI.create(reconstructUri(uri.toString()));// 使用重构后的url构建request对象return parent.createRequest(uri, httpMethod);}private String reconstructUri(String uri) {String serviceName null;boolean startsWithHttps uri.startsWith(https);String temp uri.replace(startsWithHttps ? https:// : http://, );serviceName temp.contains(/) ? temp.substring(0, temp.indexOf(/)) : temp;// 调用我们自己实现的负载均衡客户端LoadBalanceClient进行负载均衡和重构url的操作uri loadBalanceClient.reconstructUrl(serviceName, uri);return uri;}可以看到构建Request对象时我们调用的是parent.createRequest(uri, httpMethod)这个parent也是一个ClientHttpRequestFactory它是Spring提供的SimpleClientHttpRequestFactory通过它就可以使用给定的url构建一个Request对象无需我们重复造轮子。 SimpleLoadBalanceClient
LoadBalanceClientHttpRequestFactory会调用LoadBalanceClient接口的reconstructUrl(serviceName, uri)方法LoadBalanceClient是我们定义的负载均衡客户端接口具体实现类是SimpleLoadBalanceClient。
public class SimpleLoadBalanceClient implements LoadBalanceClient {...private MapString, LoadBalancer loadBalancerMap;Autowiredprivate RegistryCenterClient registryCenterClient;Autowiredprivate LoadBalanceProperties loadBalanceProperties;...public SimpleLoadBalanceClient() {loadBalancerMap new ConcurrentHashMap();}Overridepublic String reconstructUrl(String serviceName, String url) {// 根据服务名serviceName获取负载均衡器LoadBalancerLoadBalancer loadBalancer loadBalancerMap.get(serviceName);// 如果loadBalancerMap中没有对应的LoadBalancer则创建LoadBalancerif (loadBalancer null) {// 利用Java的SPI机制加载所有的负载均衡策略类LoadBalanceRuleServiceLoaderLoadBalanceRule serviceLoader ServiceLoader.load(LoadBalanceRule.class);for (LoadBalanceRule loadBalanceRule: serviceLoader) {// 读取LoadBalanceRule实现类上的Rule注解Rule rule loadBalanceRule.getClass().getAnnotation(Rule.class);// 判断Rule注解是否与配置文件指定的负载均衡类型匹配if (StringUtils.equals(rule.value(), loadBalanceProperties.getType())) {// 创建LoadBalancer对象实现类是SimpleLoadBalancerloadBalancer new SimpleLoadBalancer(serviceName, registryCenterClient, loadBalanceRule);// LoadBalancer对象缓存到map中loadBalancerMap.put(serviceName, loadBalancer);break;}}}...// 根据负载均衡策略选取一个节点MicroService microService loadBalancer.chooseMicroService();if (microService ! null) {// 把url中的服务名替换成选取节点的ip地址和端口号url url.replace(serviceName, microService.getIp() : microService.getPort());}return url;}}SimpleLoadBalanceClient根据serviceName从loadBalancerMap取出LoadBalancer然后调用LoadBalancer的chooseMicroService()方法根据负载均衡策略选取一个服务提供者节点MicroService然后把url中的服务名替换成选取出的节点的ip地址和端口号。
如果SimpleLoadBalanceClient取不到LoadBalancer就会创建一个LoadBalancer。创建LoadBalancer前首先通过Java的SPI机制加载所有的负载均衡策略类LoadBalanceRule再通过Rule注解与配置文件的配置进行匹配匹配出一个LoadBalanceRule。再以匹配到的LoadBalanceRule对象以及注册中心客户端RegistryCenterClient 为构造方法参数创建SimpleLoadBalancer对象缓存到loadBalancerMap中。 SimpleLoadBalancer
再来看一下SimpleLoadBalancer的代码
public class SimpleLoadBalancer implements LoadBalancer {private String serviceName;private RegistryCenterClient registryCenterClient;private LoadBalanceRule loadBalanceRule;...Overridepublic MicroService chooseMicroService() {// 通过注册中心客户端根据服务名拉取服务实例列表ListMicroService microServiceList registryCenterClient.getMicroServiceList(serviceName);...// 根据负载均衡规则选出一个节点return loadBalanceRule.chooseMicroService(serviceName, microServiceList);}}SimpleLoadBalancer的逻辑很简单就是调用负载均衡客户端RegistryCenterClient的getMicroServiceList(serviceName)方法根据服务名serviceName从注册中心拉取服务实例列表。RegistryCenterClient里面是有本地缓存的如果本地已经缓存了服务名对应的服务实例列表就不会请求注册中心因此SimpleLoadBalancer里面我就没有再做一次缓存了。当SimpleLoadBalancer通过RegistryCenterClient获取到实例列表后调用负载均衡规则LoadBalanceRule的chooseMicroService(serviceName, microServiceList)方法根据负载均衡规则从列表中选取一个节点。
LoadBalanceRule
LoadBalanceRule是负载均衡规则的接口类似与Ribbon的IRule。我们看一个轮询策略的实现类RoundRobinLoadBalanceRule。
Rule(roundrobin)
public class RoundRobinLoadBalanceRule implements LoadBalanceRule {// key-服务名value-下标private MapString, AtomicLong indexMap new ConcurrentHashMap();Overridepublic MicroService chooseMicroService(String serviceName, ListMicroService microServices) {AtomicLong index indexMap.putIfAbsent(serviceName, new AtomicLong());long num index.getAndIncrement();return microServices.get((int) (num % microServices.size()));}}代码一看就懂indexMap是服务名serviceName与AtomicLong计数器的映射chooseMicroService方法通过通过serviceName拿到计算器然后调用AtomicLong的getAndIncrement()进行原子自增操作然后模上服务列表的size。
我们注意到RoundRobinLoadBalanceRule类上有一个Rule(“roundrobin”)我们规定每个LoadBalanceRule实现类都必须被Rule注解修饰然后Rule的属性是负载均衡规则名称用于与配置文件的“loadbalance.rule.type”配置进行匹配的。
spring.factories与LoadBalanceConfig
我们使用SpringBoot的自动装配机制在spring.factories文件中定义好我们的自动配置类LoadBalanceConfig spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration\com.huangjunyi1993.simple.microservice.loadbalance.config.LoadBalanceConfigLoadBalanceConfig就是我们的自动配置类会往Spring中注册LoadBalanceClientHttpRequestFactory、LoadBalanceClient等核心组件并通过SmartInitializingSingleton扩展点把LoadBalanceClientHttpRequestFactory设置到RestTemplate中。 Configuration
EnableConfigurationProperties({LoadBalanceProperties.class})
public class LoadBalanceConfig {Autowired(required false)private ListRestTemplate restTemplates Collections.emptyList();BeanConditionalOnMissingBean(LoadBalanceClient.class)public LoadBalanceClient loadBalanceClient() {return new SimpleLoadBalanceClient();}Beanpublic LoadBalanceClientHttpRequestFactory loadBalanceClientHttpRequestFactory(LoadBalanceClient loadBalanceClient) {return new LoadBalanceClientHttpRequestFactory(loadBalanceClient);}BeanConditionalOnMissingBeanpublic RestTemplateCustomizer restTemplateCustomizer(LoadBalanceClientHttpRequestFactory loadBalanceClientHttpRequestFactory) {return (restTemplate) - restTemplate.setRequestFactory(loadBalanceClientHttpRequestFactory);}Beanpublic SmartInitializingSingleton loadBalancedRestTemplateInitializer(ListRestTemplateCustomizer customizers) {return () - restTemplates.forEach(restTemplate - customizers.forEach(customizer - customizer.customize(restTemplate)));}}大源码图
代码仓库地址https://gitee.com/huang_junyi/simple-microservice/tree/master/simple-microservice-loadbalance