当前位置: 首页 > news >正文

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中,

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

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

@RestController
public class Controller {@Autowiredprivate RestTemplate restTemplate;@GetMappingpublic 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.懒加载与饥饿加载

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底层的锁升级策略,就对自旋次数做了动态调整。

// 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注入的方式。也可以放在启动类中

@Configuration
public class RibbonConfiguration {@Beanpublic IRule delaultLBStrategy() {return new RandomRule();}

}

针对特定的服务的id的负载均衡策略:
配置在application.yml

eureka-client:ribbon:NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

注解的方式:
(配置在public class RibbonConfiguration {)
//@RibbonClient(name = “eureka-client”, configuration = MyRule.class)

6.部分源码品读:

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

RandomRule核心:

@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次之后就自动退出了。

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

在这里插入图片描述

for(;😉:自旋锁的实现。
自旋锁+NSCC.CAS操作是一个非常高效的线程同步方式。

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

全部代码:RoundRobinRule核心部分

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).
里面的代码是:

@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的核心代码:

@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进行重试策略

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代码:

DiscoveryEnabledServer dServer = (DiscoveryEnabledServer)server;	            
InstanceInfo instanceInfo = dServer.getInstanceInfo();

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

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,向服务节点发起调用

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
(下面代码就是简单的反射机制)

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

ZoneAvoidanceRule 加载的config信息是:
在这里插入图片描述

@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 来加载配置

@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的特征量每次都是相同的,摘要相同;只要服务器不宕机,
永远只能找到同一台机器。因为每次的位置都在环上同一个位置。服务器宕机后,
会继续往前寻找下一个节点。

//无构造器的构造方法

@NoArgsConstructor
public class MyRule extends AbstractLoadBalancerRule implements IRule {@Overridepublic void initWithNiwsConfig(IClientConfig clientConfig) {}//key无法做摘要,就是default@Overridepublic Server choose(Object key) {HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();// 特征量 uri 具体需要根据情况决定。// 比如当关联USERID时,需要从request将用户的id提取出来String uri = request.getServletPath() + "?" + request.getQueryString();//处理特征量 这里使用的是所有的服务列表,区别于getReachableServersreturn 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;}
}

http://www.mrgr.cn/news/56205.html

相关文章:

  • 【Unity】Unity Shader学习笔记(八)基础纹理2:高度纹理、法线纹理、模型空间下的法线纹理、切线空间下的法线纹理光照计算
  • 深入理解 RPC:概念、作用与在.NET 中的应用
  • 【Spring MVC】创建项目和建立请求连接
  • Python+Flask接口判断身份证省份、生日、性别、有效性验证+docker部署+Nginx代理运行
  • Linux下进程通信原理图(详细)总结附实例代码快速掌握
  • uniapp,获取头部高度
  • 智能优化算法-禁忌搜索算法(TS)(附源码)
  • 服务控制管理器
  • 应用假死?
  • 35岁的打工人,生了二胎然后被炒(职场吐槽漫画)
  • 有趣的css - 跷跷板加载动画
  • Mac电脑:资源库Library里找不到WebServer问题的解决
  • 小白对时序数据库的理解
  • 汽车电子行业的LIMS:提升质量与效率的关键助力
  • position: sticky 粘性定位
  • 【最新华为OD机试E卷-支持在线评测】寻找符合要求的最长子串(200分)多语言题解-(Python/C/JavaScript/Java/Cpp)
  • 效果图渲染为什么需要用渲染100云渲染?
  • Web Service
  • 【贪心 临项交换 博弈论】1686. 石子游戏 VI|2000
  • MSE Loss、BCE Loss
  • 跨越数字鸿沟,FileLink文件摆渡系统——您的数据安全高效传输新选择
  • AI江湖 | 开发者招募计划征集令活动参与流程
  • SpringBoot集成Spring security 2024.10(Spring Security 6.3.3)
  • 2024 四川省大学生信息安全技术大赛 安恒杯 部分 WP
  • 【网络原理】HTTP协议
  • 【智能制造-34】机器人算法工程师为什么一定要懂电机?