SpringCloud-负载均衡-ribbon

本篇是从基础方便讲解一些springcloud-负载均衡-ribbon中的一些理论性的内容;还会讲一些源码内容;后面的文章会将源码进行整理,并且将源码的github地址上传。

1.什么是负载均衡

核心概念:用户来访请求应该相对平均的分摊到每个节点;

不要将所有工作量都给一个节点。

LB策略的目的(负载均衡):海量的用户请求均匀的分配到集群中的每一台机器上

设计系统时需要有一定的余量(应对系统不同情况)

2.客户端负载与服务端负载均衡

客户端的负载均衡:服务机器的列表(客户端保存copy,且动态变化)

+LB策略(负载均衡策略)

服务端的负载均衡:nignx/F5等

大型应用通常是客户端+服务端负载均衡搭配使用

客户端负载均衡(红色)与服务端负载均衡的比较:

客户端,对开发团队更加友好,代码中可以决定使用那种负载均衡策略(配置文件)。

运维成本低(不需要额外的组件,所有的负载均衡策略在服务端就制定好了)

强依赖于注册中心,从注册中心拉到可用服务的列表(可动态更新)

微服务框架搭配使用,gateway,zookeeper,ribbon偏多。

服务端,开发团队很难去主动的发起修改,负载均衡都在网关层,由专业的网络人员来

操作,修改。网关层语言跟日常的java有很大的区别。

运维成本高(需要额外的组件,使用F5还要搭配硬件设备)

通常不依赖(SpringCloud架构zookeeper,gateway时存在依赖)

Tomcat,JBoss部署传统应用:nginx,F5偏多

3.Ribbon体系架构解析

Ribbon体系架构:

丰富的组件库;适配性好;(牛的Ribbon可以脱离

SpringCloud可以应用在一般项目中)

组件图:

一个HttpRequest发过来,先被转发到Eureka上。此时Eureka仍然通过服务发现获取了所有服务节点的物理地址,但问题是他不知道该调用哪一个,只好把请求转到了Ribbon手里。

IPing IPing是Ribbon的一套healthcheck机制,故名思议,就是要Ping一下目标机器看是否还在线,一般情况下IPing并不会主动向服务节点发起healthcheck请求,Ribbon后台通过静默处理返回true默认表示所有服务节点都处于存活状态(和Eureka集成的时候会检查服节点UP状态)。
IRule 这就是Ribbon的组件库了,各种负载均衡策略都继承自IRule接口。所有经过Ribbon的请求都会先请示IRule一把,找到负载均衡策略选定的目标机器,然后再把请求转发过去。

实现的demo:

在eureka-consumer中,

java 复制代码
@SpringBootApplication
@EnableDiscoveryClient
public class EurekaConsumerApplication {
    @Bean
    public RestTemplate register(){
        return new RestTemplate();
    }
    public static void main(String[] args) {
        new SpringApplicationBuilder(EurekaConsumerApplication.class)
                .web(WebApplicationType.SERVLET)
                .run(args);
    }
}

在eureka-consumer的controller中,实现的方式是

java 复制代码
@RestController
public class Controller {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping
    public String sayHi() {
            return restTemplate.getForObject(
                    "http://eureka-client/sayHi",
                    String.class);
    }

}

在Ribbon-consumer的controller中如何实现(LB策略)下获取对应的节点:

这里直接将地址写上去(直接应用服务名)。返回值是String.class。

后台是如何找到对应的服务和对应的端口呢?

key就在@LoadBalanced

@Bean

@LoadBalanced

public RestTemplate template(){

return new RestTemplate();

}

4.懒加载与饥饿加载

java 复制代码
INFO [main] com.netflix.loadbalancer.DynamicServerListLoadBalancer -
DynamicServerListLoadBalancer for client eureka-consumer initialized

这行log意思是,Ribbon客户端已经完成了LoadBalancer的初始化。初始化是没问题的,但问题是初始化发生的时间,为什么偏偏发生在首次超时之前呢?

这就要从Ribbon的懒加载说起了,原来Ribbon是在第一次方法调用的时候才去初始化LoadBalancer。这样看来,第一个方法请求不仅仅包含HTTP连接和方法的响应时间,还包括了LoadBalancer的创建耗时。假如你的方法本身就比较耗时的话,而且超时时间又设置的比较短,那么很大可能这第一次http调用就会失败。其实还有很多框架也实现了类似的懒加载功能,比如Hibernate的lazy-fetch,懒加载在大部分情况下可以节省系统资源开销,但某些情况下反而导致服务响应时间被延长。

之所以这类问题难以发现的原因,在于它无法稳定重现,比如只能通过重启来重现,对这种问题往往直接从生产环境的log入手去分析是比较有效的手段。

Ribbon--饥饿加载:

ribbon.eager-load.enabled=true

ribbon.eager-load.clients=ribbon-consumer

第一个参数开启了Ribbon的饥饿加载模式,第二个属性指定了需要应用饥饿加载的服务名称。完成上面配置并再次重启服务,就会发现LoadBalancer初始化日志在方法调用之前就打印出来了

5.负载均衡策略-七种策略
RandomRule :随性而为:随机挑选访问节点
RoundRobinRule :按部就班从一个节点一步一步向后选取节点,

不会跳过一个,不会原地踏步,每次只会向后移动一步;

假如在多线程环境下,两个请求同时访问这个Rule是否会读取到相同节点呢?不会,这靠的是RandomRobinRule底层的自旋锁+CAS的同步操作。CAS的全称是compare and swap,是一种借助操作系统函数来实现的同步操作。类似Eureka为了防止服务下线被重复调用,就使用AtomicBoolean的CAS方法做同步控制,CAS+自旋锁这套组合技是高并发下最廉价的线程安全手段,因为这套操作不需要锁定系统资源。有优点也有缺点,自旋锁如果迟迟不能释放,将会带来CPU资源的浪费,因为自旋本身并不会执行任何业务逻辑,而是单纯的使CPU"空转"。所以通常情况下会对自旋锁的旋转次数做一个限制,比如JDK中synchronize底层的锁升级策略,就对自旋次数做了动态调整。

java 复制代码
// CAS+自旋锁获取系统资源的打开方式,真实应用中还要注意防止无休止自旋:
// 或者for (;;) 做自旋
while (true) { 
	// cas操作
	if (cas(expected, update)) {
		// 业务逻辑代码
		// break或退出return
	}  }

Netflix是特别喜欢用自旋+CAS,作为中间件来说性能是非常重要的。
RetryRule:卷土重来

一个类似装饰器模式的Rule。装饰器相当于一层层的俄罗斯套娃,每一层都会加上一层独特的buff。

RetryRule也是同样的道理,他的BUFF就是给其他负载均衡策略加上"重试"功能。而在RetryRule里还藏着一个subRule,这才是隐藏在下面的真正被执行的负载均衡策略,RetryRule正是要为它添加重试功能(如果初始化时没指定subRule,将默认使用RoundRibinRule)。

WeightedResponseTimeRule :能者多劳

继承自RoundRibbonRule,他会根据服务节点的响应时间计算权重,

响应时间越长权重就越低,响应越快则权重越高。权重的高低决定了

机器被选中概览的高低。就是说,响应时间越小的机器,被选中的概览越大。

由于服务器刚启动的时候,对各个服务节点采样不足,因此采用轮询策略,当积累

到一定的样本时,会切换到WeightedResponseTimeRule模式。

BestAvailableRule :让最闲的人来

有点智能。过滤掉故障服务以后,它会基于30分钟的统计结果选取当前并发量最小的

服务结点,也就最"闲"的节点作为目标地址。

如果无统计结果,则采用轮询的方式选定节点。

关键字:过滤故障服务;选取并发量最小的节点。

AvailabilityFilteringRule :我是有底线的(AFR)

底层依赖RandomRobinRule来选取节点,满足它的最小要求的节点才会被选中。

如果节点满足要求,无论响应时间或当前并发量是什么。都会被选中。

每次AvailabilityFilteringRule(简称AFR)都会请求RobinRule挑选一个节点,然后对这个节点做以下两步检查:

是否处于熔断状态(熔断是Hystrix中的知识点,后面章节会讲到,这里大家可以把熔断当做服务不可用)

节点当前的active请求连接数超过阈值,超过了则表示节点目前太忙,不适合接客

如果被选中的server不幸挂掉了检查,那么AFR会自动重试(次数最多10次),让RobinRule重新选择一个服务节点。

ZoneAvoidanceRule:我的地盘我做主

ZoneFilter:在Eureka注册中一个服务结点有Zone,Region和URL三个身份信息

其中Zone可以理解为机房大区(未指定则由Eureka给定默认值),

这里会对这个Zone的监控情况过滤器下面所有服务结点

可用性过滤:这里和AvailabilityFilteringRule的验证非常像,会过滤当前并发量

较大,或者处于熔断的服务节点。

5.配置负载均衡策略

Ribbon默认的策略是:RoundRobinRule。一个个轮询

配置负载均衡策略的方式:@Bean注入的方式。也可以放在启动类中

java 复制代码
@Configuration
public class RibbonConfiguration {

    @Bean
    public IRule delaultLBStrategy() {
        return new RandomRule();
    }

}

针对特定的服务的id的负载均衡策略:

配置在application.yml

java 复制代码
eureka-client:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

注解的方式:

(配置在public class RibbonConfiguration {)

//@RibbonClient(name = "eureka-client", configuration = MyRule.class)

6.部分源码品读:

概念:

**真假随机数:**真随机数是通过物理过程(如放射性衰变、大气噪声等)生成的,其每个数字都是完全随机的,无法预测。而假随机数则是通过算法生成的,其每个数字都是基于前一个数字计算得出的,存在一定的规律性,可以被预测。因此,在需要高度安全性的场景中,应该使用真随机数来保证随机性。

RandomRule核心:

java 复制代码
@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
public Server choose(ILoadBalancer lb, Object key) {
    if (lb == null) {
        return null;
    }
    Server server = null;
    while (server == null) {
        if (Thread.interrupted()) {
            return null;
        }
        List<Server> upList = lb.getReachableServers();
        List<Server> allList = lb.getAllServers();
        int serverCount = allList.size();
        if (serverCount == 0) {
            /*
             * No servers. End regardless of pass, because subsequent passes
             * only get more restrictive.
             */
            return null;
        }
        int index = chooseRandomInt(serverCount);
        server = upList.get(index);
        if (server == null) {
            /*
             * The only time this should happen is if the server list were
             * somehow trimmed. This is a transient condition. Retry after
             * yielding.
             */
            Thread.yield();
            continue;
        }
        if (server.isAlive()) {
            return (server);
        }
        // Shouldn't actually happen.. but must be transient or a bug.
        server = null;
        Thread.yield();
    }
    return server;
}

对于上面的分析:ILoadBalancer lb。

当server = = null. If(Thread.interrupted()),表示线程中断,则返回空。防御性编程

if (server == null) {

Thread.yield(); continue; }

Thread.yield()表示线程让渡,当前线程愿意让出CPU。

如果server.isAlive,则直接返回。

为空,则调用Thread.yield(),完成线程让步。

原因是没有其他退出线程的办法。

RoundRobinRule:在代码最后没有做线程让渡

因为有Count计数器,10次之后就自动退出了。

java 复制代码
   // Next.
    server = null;
}

if (count >= 10) {
    log.warn("No available alive servers after 10 tries from load balancer: "
            + lb);
}

for(;😉:自旋锁的实现。

自旋锁+NSCC.CAS操作是一个非常高效的线程同步方式。

java 复制代码
private int incrementAndGetModulo(int modulo) {
    for (;;) {
        int current = nextServerCyclicCounter.get();
        int next = (current + 1) % modulo;
        if (nextServerCyclicCounter.compareAndSet(current, next))
            return next;
    }
}

全部代码:RoundRobinRule核心部分

java 复制代码
public Server choose(ILoadBalancer lb, Object key) {
    if (lb == null) {
        log.warn("no load balancer");
        return null;
    }

    Server server = null;
    int count = 0;
    while (server == null && count++ < 10) {
        List<Server> reachableServers = lb.getReachableServers();
        List<Server> allServers = lb.getAllServers();
        int upCount = reachableServers.size();
        int serverCount = allServers.size();

        if ((upCount == 0) || (serverCount == 0)) {
            log.warn("No up servers available from load balancer: " + lb);
            return null;
        }

        int nextServerIndex = incrementAndGetModulo(serverCount);
        server = allServers.get(nextServerIndex);

        if (server == null) {
            /* Transient. */
            Thread.yield();
            continue;
        }

        if (server.isAlive() && (server.isReadyToServe())) {
            return (server);
        }

        // Next.
        server = null;
    }

    if (count >= 10) {
        log.warn("No available alive servers after 10 tries from load balancer: "
                + lb);
    }
    return server;
}

BestAvailableRule:

分析先判断loadBalancerStats ==null为空时,

直接调用super.choose(key).

它调用的是上层(ClientConfigEnabledRoundRobinRule).

里面的代码是:

java 复制代码
@Override
public Server choose(Object key) {
    if (roundRobinRule != null) {
        return roundRobinRule.choose(key);
    } else {
        throw new IllegalArgumentException(
                "This class has not been initialized with the RoundRobinRule class");
    }
}

继续分析:这里代码是直接调用上层的roundRobinRule的choose方法。具体

RoundRobinRule核心choose方法上面已经进行了分析

获取所有的getAllServers.以及当前时间

遍历serverList,首先获取serverStats.

isCircuitBreakerTripped :一个布尔值,用于表示断路器是否触发。如果断路器已触发,则该值为 true;否则为 false。

对于isCircuitBreakerTripped

这段代码是一个名为 isCircuitBreakerTripped 的 Java 方法,它这段代码是一个名为 isCircuitBreakerTripped 的 Java 方法,它接受一个长整型参数 currentTime。该方法的作用是判断断路器是否触发。

首先,通过调用 getCircuitBreakerTimeout() 方法获取断路器超时时间,并将其赋值给变量 circuitBreakerTimeout。如果 circuitBreakerTimeout 小于等于 0,则表示断路器未设置超时时间,因此返回 false。

如果 circuitBreakerTimeout 大于 0,则比较当前时间 currentTime 和断路器超时时间 circuitBreakerTimeout。如果当前时间小于等于断路器超时时间,说明断路器还未触发,返回 false;否则,说明断路器已触发,返回 true。

BestAvailableRule的核心代码:

java 复制代码
@Override
public Server choose(Object key) {
    if (loadBalancerStats == null) {
        return super.choose(key);
    }
    List<Server> serverList = getLoadBalancer().getAllServers();
    int minimalConcurrentConnections = Integer.MAX_VALUE;
    long currentTime = System.currentTimeMillis();
    Server chosen = null;
    for (Server server: serverList) {
        ServerStats serverStats = loadBalancerStats.getSingleServerStat(server);
        if (!serverStats.isCircuitBreakerTripped(currentTime)) {
            int concurrentConnections = serverStats.getActiveRequestsCount(currentTime);
            if (concurrentConnections < minimalConcurrentConnections) {
                minimalConcurrentConnections = concurrentConnections;
                chosen = server;
            }
        }
    }
    if (chosen == null) {
        return super.choose(key);
    } else {
        return chosen;
    }
}

RetryRule:给与其他Rule进行重试策略

java 复制代码
public Server choose(ILoadBalancer lb, Object key) {
   long requestTime = System.currentTimeMillis();
   long deadline = requestTime + maxRetryMillis;

   Server answer = null;

   answer = subRule.choose(key);

   if (((answer == null) || (!answer.isAlive()))
         && (System.currentTimeMillis() < deadline)) {

      InterruptTask task = new InterruptTask(deadline
            - System.currentTimeMillis());

      while (!Thread.interrupted()) {
         answer = subRule.choose(key);

         if (((answer == null) || (!answer.isAlive()))
               && (System.currentTimeMillis() < deadline)) {
            /* pause and retry hoping it's transient */
            Thread.yield();
         } else {
            break;
         }
      }
      task.cancel();
   }
   if ((answer == null) || (!answer.isAlive())) {
      return null;
   } else {
      return answer;
   }
}

7.负载均衡器LoadBalancer原理解析

@LoadBalanced 这个注解一头挂在RestTemplate上,另一头挂在LoadBalancerAutoConfiguration这个类上。它就像连接两个世界的传送门,将所有顶着「LoadBalanced」注解的RestTemplate类,都传入到LoadBalancerAutoConfiguration中。如果要深挖底层的作用机制,大家可以发现这个注解的定义上还有一个@Qualifier注解,@Qualifier注解搭配@Autowired注解做自动装配,可以通过name属性,将指定的Bean装载到指定位置(即使有两个同样类型的Bean,也可以通过Qualifier定义时声明的name做区分)。这里「LoadBalanced」也是借助Qualifier实现了一个给RestTemplate打标签的功能,凡是被打标的RestTemplate都会被传送到AutoConfig中做进一步改造。

LBAutoConfig 从前一步中传送过来的RestTemplate,会经过LBAutoConfig的装配,将一系列的Interceptor(拦截器)添加到RestTemplate中。拦截器是类似职责链编程模型的结构,我们常见的ServletFilter,权限控制器等,都是类似的模式。Ribbon拦截器会拦截每个网络请求做一番处理,在这个过程中拦截器会找到对应的LoadBalancer对HTTP请求进行接管,接着LoadBalancer就会找到默认或指定的负载均衡策略来对HTTP请求进行转发。

总结Ribbon的作用机制就是,由LoadBalanced在RestTemplate上打标,Ribbon将带有负载均衡能力的拦截器注入标记好的RestTemplate中,以此实现了负载均衡。

IPing机制

DummyPing,默认返回true,即认为所有节点都可用,这也是单独使用Ribbon时的默认模式

NIWSDiscoveryPing,借助Eureka服务发现机制获取节点状态,假如节点状态是UP则认为是可用状态

PingUrl,它会主动向服务节点发起一次http调用,如果对方有响应则认为节点可用

大家可以看出第三种主动出击的模式比较生猛。大家可以想象,假如我们的服务节点搭载的是随便一个微服务都有大几千台服务器,在服务本身就被超高访问量调用的情况下,那这种主动出击的IPing策略必然会大大增加服务节点的访问压力。

既然Eureka已经有了服务发现机制,可以获取节点的当前状态,拿来就用岂不更好?因此,除非特殊指定,在和Eureka搭配使用的时候,采用的是第二种,也就是过滤非UP状态的节点(其实这个功能直接放Eureka里也能做)

8.IPing机制解析

public interface IPing接口只有一个方法public boolean isAlive(Server server);

5个实现类

对于DummyPing,isAlive方法,直接返回true

NoOpPing同上。

NIWSDiscoveryPing代码:

java 复制代码
DiscoveryEnabledServer dServer = (DiscoveryEnabledServer)server;	            
InstanceInfo instanceInfo = dServer.getInstanceInfo();

发现Eureka的服务列表,然后通过是否是up状态来判断对应的

Server是否处于alive状态

java 复制代码
public boolean isAlive(Server server) {
    boolean isAlive = true;
    if (server!=null && server instanceof DiscoveryEnabledServer){
           DiscoveryEnabledServer dServer = (DiscoveryEnabledServer)server;                
           InstanceInfo instanceInfo = dServer.getInstanceInfo();
           if (instanceInfo!=null){                    
               InstanceStatus status = instanceInfo.getStatus();
               if (status!=null){
                   isAlive = status.equals(InstanceStatus.UP);
               }
           }
       }
    return isAlive;
}

对于PingUrl类:拼接url,向服务节点发起调用

java 复制代码
public boolean isAlive(Server server) {
    String urlStr = "";
    if (this.isSecure) {
        urlStr = "https://";
    } else {
        urlStr = "http://";
    }

    urlStr = urlStr + server.getId();
    urlStr = urlStr + this.getPingAppendString();
    boolean isAlive = false;
    HttpClient httpClient = new DefaultHttpClient();
    HttpUriRequest getRequest = new HttpGet(urlStr);
    String content = null;

    try {
        HttpResponse response = httpClient.execute(getRequest);
        content = EntityUtils.toString(response.getEntity());
        isAlive = response.getStatusLine().getStatusCode() == 200;
        if (this.getExpectedContent() != null) {
            LOGGER.debug("content:" + content);
            if (content == null) {
                isAlive = false;
            } else if (content.equals(this.getExpectedContent())) {
                isAlive = true;
            } else {
                isAlive = false;
            }
        }
    } catch (IOException var11) {
        var11.printStackTrace();
    } finally {
        getRequest.abort();
    }

    return isAlive;
}

9.IRule机制解析

Rule就是负载均衡策略

IRule的对象结构

IRule调用的方式和改造点:

IRule在执行一次代码时,无需重复执行。(因为测试时debug时,第二次

执行接口测试的时候没有进入)

多次测试发现没经过RibbonClientConfiguration中如下的方法:

这里主要是加载environment中的环境资源:

拿到配置后会在SpringClientFactory中调用instantiateWithConfig

(下面代码就是简单的反射机制)

java 复制代码
try {
   Constructor<C> constructor = clazz.getConstructor(IClientConfig.class);
   result = constructor.newInstance(config);
}

ZoneAvoidanceRule 加载的config信息是:

java 复制代码
@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
   if (this.propertiesFactory.isSet(IRule.class, name)) {
      return this.propertiesFactory.get(IRule.class, config, name);
   }
   ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
   rule.initWithNiwsConfig(config);
   return rule;
}

然后就到ribbonLoadBalancer来生成ILoadBalancer

(RibbonClientConfiguration类中的方法,同上类)

ZoneAwareLoadBalancer 来加载配置

java 复制代码
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
      ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
      IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
   if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
      return this.propertiesFactory.get(ILoadBalancer.class, config, name);
   }
   return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
         serverListFilter, serverListUpdater);
}

10.自定义IRule

自定义基于一致性哈希负载均衡策略;

制定服务应用自定义策略:

来访的请求怎么找到对应的服务器呢?将来访的请求的某一个

特征量(可以是请求中的RequestParam也可以是你整个的URL)找出来,

通过一定的算法做成一个摘要。再通过HashCode算法过滤一遍摘要,

变成一个Int值,映射到环上。接下来就寻找离它最近的下一个节点的位置

(只能是顺时针或者是逆时针)

分析:同一个request的特征量每次都是相同的,摘要相同;只要服务器不宕机,

永远只能找到同一台机器。因为每次的位置都在环上同一个位置。服务器宕机后,

会继续往前寻找下一个节点。

//无构造器的构造方法

java 复制代码
@NoArgsConstructor
public class MyRule extends AbstractLoadBalancerRule implements IRule {
    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {

    }

    //key无法做摘要,就是default
    @Override
    public Server choose(Object key) {

        HttpServletRequest request = ((ServletRequestAttributes)
                RequestContextHolder.getRequestAttributes())
                .getRequest();
        // 特征量 uri 具体需要根据情况决定。
        // 比如当关联USERID时,需要从request将用户的id提取出来
        String uri = request.getServletPath() + "?" + request.getQueryString();
        //处理特征量 这里使用的是所有的服务列表,区别于getReachableServers
        return route(uri.hashCode(), getLoadBalancer().getAllServers());
    }

    /**
     * 主逻辑:用来寻找特定的Server,最终需要返回的对象
     * @param hashId
     * @param addressList
     * @return
     */
    public Server route(int hashId, List<Server> addressList){
        //防御性编程
        if (CollectionUtils.isEmpty(addressList)){
            return null;
        }

        // 排序相关
        TreeMap<Long, Server> address = new TreeMap<>();
        // 循环List stream方式
        addressList.stream().forEach(e ->{
            //假设圆环有几个节点,对应i值
            // 这里是每个server的hash值,然后将其放进来
            // 这8个hash值分布不够均匀
            // TODO 可以针对这个算法,修改成均匀分布的算法
            // 虚化若干个服务节点,到环上
            for (int i = 0; i < 8; i++){
                // 计算hash值,e是addressList中的server element。
                long hash = hash(e.getId() + i);
                //将address放进来
                address.put(hash,e);
            } });

        Long hash = hash(String.valueOf(hashId));

        // 获取大于hash的值,比如传进去的值是3,则获取的值是4等(比三大的整数值)
        SortedMap<Long, Server> last = address.tailMap(hash);

        // 当request URL 的hash值大于任意一个服务器对应的hashKey
        // 取address中的第一个节点
        if (last.isEmpty()){
            address.firstEntry().getValue();
        }

        return last.get(last.firstKey());
    }

//    用来计算hash值的
    public Long hash(String key) {
        // md5有必须检查的异常
        MessageDigest md5;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            // RuntimeException封装一下
            throw new RuntimeException(e);
        }

        byte[] keyByte = null;
        try {
            keyByte = key.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }

        //将md5更新到key里,把key塞到md5这个算法里
        md5.update(keyByte);
        // 做出摘要,digest利用md5输出byte数组,即为摘要(16位的)
        byte[] digest = md5.digest();

        //根据只要生成hashCode
        //从第三位开始,下标为2((long) (digest[2] & 0xFF << 16))
        // 2 "& 0xFF" 转成long型 , << 16表示往前移16位
        long hashCode = ((long) (digest[2] & 0xFF << 16))
                | ((long) (digest[2] & 0xFF << 8))
                | ((long) (digest[2] & 0xFF ));

        //将一个整数(hashCode)与0xffffffffL进行按位与操作,
        // 以保留其低32位。这样可以确保返回的哈希值是一个无符号整数。
        return hashCode & 0xffffffffL;
    }
}
相关推荐
猫猫不是喵喵.4 小时前
【微服务】Feign 远程调用
spring cloud·微服务
猫猫不是喵喵.6 小时前
【微服务】Nacos 注册中心
spring cloud·微服务·eureka
打败4046 小时前
lvs知识点归纳
负载均衡·lvs
bestcxx1 天前
(二十四)、在 k8s 中部署自己的 jar 镜像(以 springcloud web 项目为例)
spring cloud·kubernetes·jar
淘源码d1 天前
Spring Cloud +UniApp智慧工地源码,智慧工地综合解决方案,建筑工程云平台源码
大数据·人工智能·spring cloud·信息可视化·数据分析·源码·智慧工地
爱技术的小伙子2 天前
【Linux 从基础到进阶】高可用性与负载均衡
linux·运维·负载均衡
管理大亨2 天前
大数据之VIP(Virtual IP,虚拟IP)负载均衡
大数据·tcp/ip·负载均衡
尚学教辅学习资料2 天前
基于SpringCloud的WMS管理系统源码
后端·spring·spring cloud·wms
p-knowledge2 天前
高并发-负载均衡
java·负载均衡