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

Java面试题019:一文深入了解微服务之负载均衡Ribbon

        欢迎大家关注我的JAVA面试题专栏,该专栏会持续更新(第一目标100节),从原理角度覆盖Java知识体系的方方面面。

一文吃透JAVA知识体系(面试题)https://blog.csdn.net/wuxinyan123/category_7521898.html?fromshare=blogcolumn&sharetype=blogcolumn&sharerId=7521898&sharerefer=PC&sharesource=wuxinyan123&sharefrom=from_link

1、Ribbon简介 

        Ribbon是Netflix下的负载均衡项目,它在集群中为各个客户端的通信提供了支持,主要实现中间层应用层析的负载均衡。Ribbon提供以下特性:

  • 负载均衡器,可支持插拔式的负载均衡规则。
  • 对多种协议提供支持,例如HTTP、TCP、UDP。
  • 集成了负载均衡功能的客户端。

        Spring Cloud将Ribbon的API进行了封装,使用者可以使用封装后的API来实现负载均衡,也可以直接使用Ribbon的原生API。

Ribbon主要有以下三大子模块:

  • ribbon-core:项目核心,包括负载均衡器接口定义、客户端接口定义、内置的负载均衡实现等API。
  • ribbon-eureka:为Eureka客户端提供的负载均衡实现类。
  • ribbon-httpclient:对Apache的HttpClient进行封装,还提供负载均衡功能的REST客户端。

2、常见负载均衡算法

  • 随机法: 通过随机选择服务进行执行,一般这种方式使用较少。

  • 轮询法: 负载均衡默认实现方式,请求来之后排队处理。

        将请求按顺序轮流分配到后台服务器上,均衡的对待每一台服务器,而不关心服务器实际的连接数和当前的系统负载。

        对于当前轮询的位置变量pos,为了保证服务器选择的顺序性,需要对其在操作时加上synchronized锁,使得同一时刻只有一个线程能够修改pos的值,否则当pos变量被并发修改,将无法保证服务器选择的顺序性,甚至有可能导致keyList数组越界。

       使用轮询策略的目的是,希望做到请求转移的绝对均衡,但付出的代价性能也是相当大的。为了保证pos变量的并发互斥,引入了重量级悲观锁synchronized,将会导致该轮询代码的并发吞吐量明显下降。

  • 加权轮询法: 通过对服务器性能的分型,给高配置,低负载的服务器分配更高的权重,均衡各个服务器的压力。

public static String testWeightRandom() {// 重新创建一个map,避免出现由于服务器上线和下线导致的并发问题Map<String, Integer> serverMap = new HashMap<String, Integer>();serverMap.putAll(serviceWeightMap);//取得IP地址listSet<String> keySet = serverMap.keySet();List<String> serverList = new ArrayList<String>();Iterator<String> it = keySet.iterator();while (it.hasNext()) {String server = it.next();Integer weight = serverMap.get(server);for (int i=0; i<weight; i++) {serverList.add(server);}}Random random = new Random();int randomPos = random.nextInt(serverList.size());String server = serverList.get(randomPos);return server;
}
  • 源地址哈希法: 通过客户端请求的地址的HASH值取模映射进行服务器调度。 

        源地址哈希法的思想是根据服务消费者请求客户端的IP地址,通过哈希函数计算得到一个哈希值,将此哈希值和服务器列表的大小进行取模运算,得到的结果便是要访问的服务器地址的序号。采用源地址哈希法进行负载均衡,相同的IP客户端,如果服务器列表不变,将映射到同一个后台服务器进行访问。

public static String testConsumerHash(String remoteIp) {// 重新创建一个map,避免出现由于服务器上线和下线导致的并发问题Map<String, Integer> serverMap = new HashMap<String, Integer>();serverMap.putAll(serviceWeightMap);//取得IP地址listSet<String> keySet = serverMap.keySet();ArrayList<String> keyList = new ArrayList<String>();keyList.addAll(keySet);int hashCode = remoteIp.hashCode();int pos = hashCode % keyList.size();return keyList.get(pos);
}
  • 最小连接数: 即使请求均衡了,压力不一定会均衡,最小连接数法就是根据服务器的情况,比如请求积压数等参数,将请求分配到当前压力最小的服务器上。

        最小连接数法比较灵活和智能,由于后台服务器的配置不尽相同,对请求的处理有快有慢,它正是根据后端服务器当前的连接情况,动态的选取其中当前积压连接数最少的一台服务器来处理当前请求,尽可能的提高后台服务器利用率,将负载合理的分流到每一台服务器。

  • 加权随机法:加权随机法跟加权轮询法类似,根据后台服务器不同的配置和负载情况,配置不同的权重。不同的是,它是按照权重来随机选取服务器的,而非顺序。

  • 可用性过滤器:先过滤掉不可用的服务器,然后在剩下的服务器中选择一个。

  • 区域感知:结合了可用性过滤和区域感知,优先选择同一区域内的服务器。

3、Ribbon负载均衡原理

        Ribbon通常和Http请求结合,对Http请求进行负载均衡;通过注入RestTemplate,并且打上@LoadBlanced注解,即可得到一个带有负载均衡效果的RestTemplate。

@Configuration
public class HttpConfiguration {@Bean@LoadBalancedpublic RestTempttpTlate restTemplate() {return new RestTemplate();}
}
  • RestTemplate在发送请求过程中,会构造一条具有多个拦截器的执行链,Ribbon可以借助拦截器,在RestTemplate中加入一个LoadBalancerInterceptor拦截器;
  • 请求经过拦截器,Ribbon就可以根据请求的URL中的主机名(即服务名, 上面的mall-order),去注册中心拿到提供该服务的所有主机
  • 根据负载均衡策略,选择其中一个,然后把服务名替换为真正的IP,接着继续执行下一个拦截器,最终发送请求

负载均衡流程源码核心类

  • LoadBalanceClient:上面说到Http请求发送时,会经过Ribbon的LoadBalancerInterceptor拦截器进行负载均衡,该拦截器会把请求交给LoadBalancerClient进行负载均衡;该类是负载均衡客户端,也可以看成负载均衡的入口;采用的实现类是RibbonLoadBalancerClient。
//RibbonLoadBalancerClient#execute
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)throws IOException {//根据serviceId拿到ILoadBalancer,交由它进行负载均衡ILoadBalancer loadBalancer = getLoadBalancer(serviceId);//根据loadBalancer拿到真正的服务提供者Server server = getServer(loadBalancer, hint);if (server == null) {throw new IllegalStateException("No instances available for " + serviceId);}//包装ServerRibbonServer ribbonServer = new RibbonServer(serviceId, server,isSecure(server, serviceId),serverIntrospector(serviceId).getMetadata(server));//执行请求return execute(serviceId, ribbonServer, request);
}//RibbonLoadBalancerClient#getLoadBalancer
protected ILoadBalancer getLoadBalancer(String serviceId) {//从clientFactory中获得该服务对应的ILoadBalancer	return this.clientFactory.getLoadBalancer(serviceId);
}
public ILoadBalancer getLoadBalancer(String name) {return getInstance(name, ILoadBalancer.class);
}public <T> T getInstance(String name, Class<T> type) {//获取服务名对应的ApplicationContextAnnotationConfigApplicationContext context = getContext(name);if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,type).length > 0) {//从ApplicationContext中后去ILoadBalancer类型的beanreturn context.getBean(type);}return null;
}protected AnnotationConfigApplicationContext getContext(String name) {//大致逻辑就是直接容map中拿,没有则创建一个并缓存if (!this.contexts.containsKey(name)) {synchronized (this.contexts) {if (!this.contexts.containsKey(name)) {this.contexts.put(name, createContext(name));}}}return this.contexts.get(name);
}
  • ILoadBalancer:RibbonLoadBalancerClient内部的execute方法会以Http请求的服务名为key,找到ILoadBalancer对象,这个ILoadBalancer就是专门负责服务的负载均衡。
  • IRule:代表负载均衡策略,ILoadBalancer的负载均衡由它进行处理,Ribbon内置了多种负载均衡策略
//BaseLoadBalancer#chooseServer
public Server chooseServer(Object key) {if (counter == null) {counter = createCounter();}counter.increment();//如果没有配置rule则返回if (rule == null) {return null;} else {try {//由rule执行真正的负载均衡return rule.choose(key);} catch (Exception e) {logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);return null;}}
}

以RandomRule为例看一下choose方法

//RandomRule#choose
public Server choose(ILoadBalancer lb, Object key) {if (lb == null) {return null;}Server server = null;while (server == null) {//... 从ILoadBalancer中拿到该服务对应的所有ServerList<Server> upList = lb.getReachableServers();List<Server> allList = lb.getAllServers();//... 产生随机数,并拿到对应Serverint index = chooseRandomInt(serverCount);server = upList.get(index);//...if (server.isAlive()) {return (server);}//...}return server;
}

负载均衡流程
(1)Http请求经过LoadBalancerInterceptor拦截器,它将调用LoadBalancerClient进行处理;
(2)LoadBalancerClient(实现类Ribbon``LoadBalancerClient)根据服务名拿到对应的ApplicationContext,并从容器中拿到ILoadBalancer(实际类ZoneAwareLoadBalancer)
(3)从ILoadBalancer中获取服务提供者Server,具体是调用chooseServer方法,内部会使用IRule进行负载均衡,并返回合适的Server
(4)IRule才是真正的负载均衡实现接口,Ribbon内置多种默认负载均衡策略

主机列表信息的更新维护

  • DynamicServerListLoadBalance: 负责服务对应的主机列表信息。具有ServerList功能的ILoadBalance
public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,ServerList<T> serverList, ServerListFilter<T> filter,ServerListUpdater serverListUpdater) {super(clientConfig, rule, ping);this.serverListImpl = serverList;this.filter = filter;this.serverListUpdater = serverListUpdater;if (filter instanceof AbstractServerListFilter) {((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());}//调用该方法进行主机列表初始化restOfInit(clientConfig);
}void restOfInit(IClientConfig clientConfig) {//...updateListOfServers();//...
}//这是另一个构造方法,可能是使用自定义配置时
public DynamicServerListLoadBalancer(IClientConfig clientConfig) {initWithNiwsConfig(clientConfig);
}//DynamicServerListLoadBalancer#initWithNiwsConfig
public void initWithNiwsConfig(IClientConfig clientConfig) {try {super.initWithNiwsConfig(clientConfig);//从配置文件中拿到NIWSServerListClassName对应的值,应该是拿到配置的ServerList实现类,负责维护服务信息//当注册中心不同时,应该可以动态替换String niwsServerListClassName = clientConfig.getPropertyAsString(CommonClientConfigKey.NIWSServerListClassName,DefaultClientConfigImpl.DEFAULT_SEVER_LIST_CLASS);//实例化ServerListServerList<T> niwsServerListImpl = (ServerList<T>) ClientFactory.instantiateInstanceWithClientConfig(niwsServerListClassName, clientConfig);//保存this.serverListImpl = niwsServerListImpl;//...//拿到ServerListUpdaterClassName配置的类String serverListUpdaterClassName = clientConfig.getPropertyAsString(CommonClientConfigKey.ServerListUpdaterClassName,DefaultClientConfigImpl.DEFAULT_SERVER_LIST_UPDATER_CLASS);//实例化this.serverListUpdater = (ServerListUpdater) ClientFactory.instantiateInstanceWithClientConfig(serverListUpdaterClassName, clientConfig);//在该函数中,会进行服务初始化restOfInit(clientConfig);} catch (Exception e) {throw new RuntimeException("Exception while initializing NIWSDiscoveryLoadBalancer:"+ clientConfig.getClientName()+ ", niwsClientConfig:" + clientConfig, e);}
}
  • ServerList接口:用于表示向注册中心拉取服务信息,维护和更新本服务对应的所有主机信息。
public interface ServerList<T extends Server> {public List<T> getInitialListOfServers();public List<T> getUpdatedListOfServers();   
}

Ribbon服务列表更新是通过定时任务来完成的。

4、修改Ribbon默认负载均衡策略

        Ribbon默认负载均衡策略是ZoneAvoidanceRule,复合判断server所在区域的性能和server的可用性选择服务器。

第一步:新建一个不会被@ComponentScan组件扫描到的包,如:com.teh
第二步:在该包下新建自己的负载均衡算法的规则类

package teh;import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RibbonRuleConfig {//方法名一定要为iRule@Beanpublic IRule iRule(){return new RandomRule();}
}

第三步:主启动类上添加注解:@RibbonClient

package teh;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
import ribbon.RandonRuleConfig;@SpringBootApplication
@RibbonClients(value = {@RibbonClient(name = "stock-service",configuration = RibbonRuleConfig.class)}) //配置负载均衡策略
public class OrderApplication {public static void main(String[] args) {SpringApplication.run(OrderApplication.class,args);}@Bean@LoadBalanced // 负载均衡器注解,nacos的服务调用依赖于负载均衡(nacos无法将服务名称转化为服务地址,需要使用负载均衡器,默认使用轮询的方式)public RestTemplate restTemplate(RestTemplateBuilder builder){RestTemplate RestTemplate = builder.build();return RestTemplate;}
}

第四步,配置文件修改负载均衡策略

stock-service: #在服务消费者配置服务提供者的服务名ribbon:NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

NFLoadBalancerRuleClassName后必须写全路径,上面代码修改策略为随机

5、@LoadBalanced注解

将该注解加在RestTemplate的Bean上,就可以实现负载均衡。

@Configuration
public class CustomConfiguration {@Bean@LoadBalanced // 开启负载均衡能力public RestTemplate restTemplate() {return new RestTemplate();}
}

源码分析:

/*** Annotation to mark a RestTemplate or WebClient bean to be configured to use a* LoadBalancerClient.* */
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {}

        接口LoadBalanced的定义上,添加了@Qualifier注解。

        当SpringIOC容器中有多个同类型的bean时,在使用@Autowired进行装配时,就无法完成自动装配,原因是@Autowired是按bean的类型来装配的,Spring也不知道我们到底要装配哪个bean,@Qualifier的出现就是为了解决这个问题。@Qualifier是根据bean的名称来进行装配。

        Qualifier是合格者的意思,表示为多个实现类选一个合格者注入。

@Autowired
@Qualifier("AUserServiceImpl")
private  UserService userService;

LoadBalancer的自动配置类LoadBalancerAutoConfiguration,

  @LoadBalanced出现在了SpringCloud的底层代码中,这里会筛选出添加了@LoadBalanced的RestTemplate,并装配到restTemplates中。

http://www.xdnf.cn/news/974611.html

相关文章:

  • Wireshark 筛选功能详解:语法与示例
  • 一些学习网站分享
  • OctoPrint公网部署如何实现?3D打印远程控制一键部署过程!
  • 《零基础读懂新能源汽车》——V2G/电池梯次利用/氢能源生态级技术拆解与商业预言
  • 智能体商业化:创建-接入-封装成小程序/网站/H5
  • PHP7+MySQL5.6 雪里开简易预约制访客管理系统V1.0
  • 深度解读云防火墙(WAF):守护网络安全的智能卫士
  • 在当系统未连接上wifi的时候,直接不显示wifi列表 ,这个判断导致?
  • UI 设计|审美积累|新拟态风格(Neumorphism)
  • 【华为Pura80系列】鸿蒙生态再升级:Pura 80 系列影像突破,WATCH 5 开启智能手表新纪元
  • 2025 年 MQTT 技术趋势:驱动 AI 与物联网未来发展的关键动力
  • 理解什么是并查集
  • 阿糖胞苷联合伊达比星为代表的强化治疗方案引领AML多阶段治疗新进展
  • 学习threejs,使用TSL计算粒子鼠标特效
  • Maven 构建性能优化深度剖析:原理、策略与实践
  • 目标检测yolo算法
  • AI赋能Automa二次开发
  • 超市售货管理平台小程序
  • 2025年渗透测试面试题总结-长亭科技[实习]安全服务工程师题目+回答)
  • 板凳-------Mysql cookbook学习 (十--5)
  • 仓库物资出入库管理系统源码+uniapp小程序
  • 云计算迁移策略:分步框架与优势
  • 实战案例-FPGA如何实现JESD204B最小确定性延迟
  • 【WSL2】Windows11开启WSL2
  • Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
  • Oracle安装报错:Error in invoking target ‘agent nmhs‘ of makefile
  • 饿一饿对肝脏好
  • 创建多个 OkHttpClient 实例 场景
  • aruco::detectMarkers中什么情况下marker会被判定为rejectedMarkers
  • 论文解析:一文弄懂Vision Transformer!