关于ribbon:聊聊如何独立使用ribbon实现业务客户端负载均衡

前言ribbon是Netflix开源的客户端负载平衡工具,ribbon实现一系列的负载平衡算法,通过这些负载平衡算法去查找相应的服务。ribbon被大家所熟知,可能是来源于spring cloud,明天就来聊聊如何独自应用ribbon来实现业务客户端负载平衡 实现要害springcloud ribbon获取服务列表是通过注册核心,而独自应用ribbon,因为没有注册核心加持,就得独自配置服务列表 示例1、在业务我的项目中pom引入ribbon GAV<dependency> <groupId>com.netflix.ribbon</groupId> <artifactId>ribbon</artifactId> <version>2.2.2</version></dependency>不过引进去,发现如果引入netfiix的相干的类,比方IPing,会发现引不进去,起因是因为这个GAV外面依赖的jar的生命周期是runtime,即在运行期或者测试阶段才失效,在编译阶段是不失效的。如果咱们为了不便,能够间接独自引入spring cloud ribbon <dependency> <groupId>org.springframework.cloud</groupId>--> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>--> <version>2.2.2.RELEASE</version> </dependency>本文咱们是想脱离springcloud,间接应用ribbon,因而咱们能够间接 引入如下GAV <!-- 外围的通用性代码--> <dependency> <groupId>com.netflix.ribbon</groupId> <artifactId>ribbon-core</artifactId> <version>${ribbon.version}</version> </dependency> <!-- 基于apache httpClient封装的rest客户端,集成了负载平衡模块,内嵌http心跳检测--> <dependency> <groupId>com.netflix.ribbon</groupId> <artifactId>ribbon-httpclient</artifactId> <version>${ribbon.version}</version> </dependency> <!-- 负载平衡模块--> <dependency> <groupId>com.netflix.ribbon</groupId> <artifactId>ribbon-loadbalancer</artifactId> <version>${ribbon.version}</version> </dependency> <!-- IClientConfig配置相干--> <dependency> <groupId>com.netflix.archaius</groupId> <artifactId>archaius-core</artifactId> <version>0.7.6</version> </dependency> <!-- IClientConfig配置相干--> <dependency> <groupId>commons-configuration</groupId> <artifactId>commons-configuration</artifactId> <version>1.8</version> </dependency>2、创立ribbon元数据配置类@Data@AllArgsConstructor@NoArgsConstructor@Builderpublic class RuleDefinition { /** * 服务名称 */ private String serviceName; /** * 命名空间,当服务名雷同时,能够通过namesapce来进行隔离辨别 * 未指定默认为public */ @Builder.Default private String namespace = DEFAULT_NAMESPACE; /** * 自定义负载平衡策略,未指定默认为轮询 */ @Builder.Default private String loadBalancerRuleClassName = RoundRobin; /** * 自定义心跳检测,未指定不检测 */ @Builder.Default private String loadBalancerPingClassName = DummyPing; /** * 服务列表,多个用英文逗号隔开 */ private String listOfServers; /** * 该优先级大于loadBalancerPingClassName */ private IPing ping; /** * 心跳距离,不配置默认是10秒,单位秒 */ private int pingIntervalSeconds; /** * 该优先级大于loadBalancerRuleClassName */ private IRule rule;}@Data@AllArgsConstructor@NoArgsConstructor@ConfigurationProperties(prefix = PREFIX)public class LoadBalanceProperty { public static final String PREFIX = "lybgeek.loadbalance"; private List<RuleDefinition> rules; public Map<String,RuleDefinition> getRuleMap(){ if(CollectionUtils.isEmpty(rules)){ return Collections.emptyMap(); } Map<String,RuleDefinition> ruleDefinitionMap = new LinkedHashMap<>(); for (RuleDefinition rule : rules) { String key = rule.getServiceName() + RULE_JOIN + rule.getNamespace(); ruleDefinitionMap.put(key,rule); } return Collections.unmodifiableMap(ruleDefinitionMap); }}3、创立负载平衡工厂【外围实现】 private final LoadBalanceProperty loadBalanceProperty; // key:serviceName + nameSpace private static final Map<String, ILoadBalancer> loadBalancerMap = new ConcurrentHashMap<>(); public ILoadBalancer getLoadBalancer(String serviceName,String namespace){ String key = serviceName + RULE_JOIN + namespace; if(loadBalancerMap.containsKey(key)){ return loadBalancerMap.get(key); } RuleDefinition ruleDefinition = getAvailableRuleDefinition(serviceName,namespace); IPing ping = ruleDefinition.getPing(); if(ObjectUtils.isEmpty(ping)){ // 无奈通过ConfigurationManager.getConfigInstance().setProperty(serviceName + DOT + namespace + DOT + PING_CLASS_NAME, ruleDefinition.getLoadBalancerPingClassName()); //LoadBalancerBuilder没提供通过ClientConfig配置ping办法,只能通过withPing批改 ping = getPing(serviceName,namespace); } IRule rule = ruleDefinition.getRule(); if(ObjectUtils.isEmpty(rule)){ // 也能够通过ConfigurationManager.getConfigInstance().setProperty(serviceName + DOT + namespace + DOT + RULE_CLASS_NAME, ruleDefinition.getLoadBalancerRuleClassName()); rule = getRule(serviceName,namespace); } // 配置服务列表 ConfigurationManager.getConfigInstance().setProperty(serviceName + DOT + namespace + DOT + SERVER_LIST, ruleDefinition.getListOfServers()); // 因为服务列表目前是配置写死,因而敞开列表更新,否则当触发定时更新时,会从新将服务列表状态复原原样,这样会导致server的isLive状态不精确 // 不设置默认采纳com.netflix.loadbalancer.PollingServerListUpdater ConfigurationManager.getConfigInstance().setProperty(serviceName + DOT + namespace + DOT + SERVERLIST_UPDATER_CLASS_NAME, EmptyServerListUpdater.class.getName()); IClientConfig config = new DefaultClientConfigImpl(namespace); config.loadProperties(serviceName); ZoneAwareLoadBalancer<Server> loadBalancer = getLoadBalancer(config, ping, rule); loadBalancerMap.put(key,loadBalancer); if(ruleDefinition.getPingIntervalSeconds() > 0){ // 默认每隔10秒进行心跳检测 loadBalancer.setPingInterval(ruleDefinition.getPingIntervalSeconds()); } return loadBalancer; } public ZoneAwareLoadBalancer<Server> getLoadBalancer(IClientConfig config, IPing ping, IRule rule){ ZoneAwareLoadBalancer<Server> serverZoneAwareLoadBalancer = LoadBalancerBuilder.newBuilder() .withClientConfig(config) .withPing(ping) .withRule(rule) .buildDynamicServerListLoadBalancerWithUpdater(); return serverZoneAwareLoadBalancer; } /** * 获取 iping * @param serviceName * @param namespace * @return */ @SneakyThrows public IPing getPing(String serviceName, String namespace){ RuleDefinition ruleDefinition = getAvailableRuleDefinition(serviceName,namespace); Class<?> loadBalancerPingClass = ClassUtils.forName(ruleDefinition.getLoadBalancerPingClassName(), Thread.currentThread().getContextClassLoader()); Assert.isTrue(IPing.class.isAssignableFrom(loadBalancerPingClass),String.format("loadBalancerPingClassName : [%s] is not Iping class type",ruleDefinition.getLoadBalancerPingClassName())); return (IPing) BeanUtils.instantiateClass(loadBalancerPingClass); } /** * 获取 loadbalanceRule * @param serviceName * @param namespace * @return */ @SneakyThrows public IRule getRule(String serviceName, String namespace){ RuleDefinition ruleDefinition = getAvailableRuleDefinition(serviceName,namespace); Class<?> loadBalancerRuleClass = ClassUtils.forName(ruleDefinition.getLoadBalancerRuleClassName(), Thread.currentThread().getContextClassLoader()); Assert.isTrue(IRule.class.isAssignableFrom(loadBalancerRuleClass),String.format("loadBalancerRuleClassName : [%s] is not Irule class type",ruleDefinition.getLoadBalancerRuleClassName())); return (IRule) BeanUtils.instantiateClass(loadBalancerRuleClass); } private RuleDefinition getAvailableRuleDefinition(String serviceName,String namespace){ Map<String, RuleDefinition> ruleMap = loadBalanceProperty.getRuleMap(); Assert.notEmpty(ruleMap,"ruleDefinition is empty"); String key = serviceName + RULE_JOIN + namespace; RuleDefinition ruleDefinition = ruleMap.get(key); Assert.notNull(ruleDefinition,String.format("NOT FOUND AvailableRuleDefinition with serviceName : [{}] in namespace:[{}]",serviceName,namespace)); return ruleDefinition; }外围实现类:com.netflix.loadbalancer.LoadBalancerBuilder 利用该类创立相应的负载平衡 ...

June 20, 2023 · 3 min · jiezi

关于ribbon:Ribbon系列

Ribbon - 初始化Ribbon - 负载平衡流程Ribbon - 懒加载Ribbon - 几种自定义负载平衡策略

December 24, 2020 · 1 min · jiezi

关于ribbon:Ribbon-几种自定义负载均衡策略

批改某个服务配置文件形式Ribbon - 负载平衡流程提过了propertiesFactory.isSet,这个次要是用于批改某个服务的负载平衡。 @Bean@ConditionalOnMissingBeanpublic 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;}调用isSet的时候,会判断getClassName是否有找到对应的配置文件,如果有,则应用配置文件对应的规定。 public PropertiesFactory() { classToProperty.put(ILoadBalancer.class, "NFLoadBalancerClassName"); classToProperty.put(IPing.class, "NFLoadBalancerPingClassName"); classToProperty.put(IRule.class, "NFLoadBalancerRuleClassName"); classToProperty.put(ServerList.class, "NIWSServerListClassName"); classToProperty.put(ServerListFilter.class, "NIWSServerListFilterClassName");}public boolean isSet(Class clazz, String name) { return StringUtils.hasText(getClassName(clazz, name));}public String getClassName(Class clazz, String name) { if (this.classToProperty.containsKey(clazz)) { String classNameProperty = this.classToProperty.get(clazz); String className = environment .getProperty(name + "." + NAMESPACE + "." + classNameProperty); return className; } return null;}我本地的配置,这样就把轮询改为随机了。 ...

December 24, 2020 · 1 min · jiezi

关于ribbon:Ribbon-懒加载

Ribbon - 负载平衡流程中提到,serviceId对应的AnnotationConfigApplicationContext在第一次调用的时候才创立(所以叫懒加载),那有可能造成调用的超时,那有没有方法提前加载呢?在RibbonAutoConfiguration类中,有个RibbonApplicationContextInitializer,如果不须要懒加载,咱们就须要ribbon.eager-load.enabled的值为true。 @Bean@ConditionalOnProperty("ribbon.eager-load.enabled")public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() { return new RibbonApplicationContextInitializer(springClientFactory(), ribbonEagerLoadProperties.getClients());}RibbonApplicationContextInitializer是实现ApplicationListener<ApplicationReadyEvent>,所以当上下文筹备好的时候,就会调用onApplicationEvent办法,而后持续调用initialize办法,所以咱们并不是说ribbon.eager-load.enabled的值设置true就能够的,还须要设置clientNames。 protected void initialize() { if (clientNames != null) { for (String clientName : clientNames) { this.springClientFactory.getContext(clientName); } }}为了去掉懒加载,我的配置如下,clients能够是多个: ribbon: eager-load: enabled: true clients: eureka-provider

December 24, 2020 · 1 min · jiezi

关于ribbon:Ribbon-负载均衡流程

Ribbon - 初始化中提到了,@LoadBalanced注解的RestTemplate会注入拦截器LoadBalancerInterceptor,咱们看看LoadBalancerInterceptor是怎么做的。 LoadBalancerInterceptor#intercept这里次要是通过URL把serviceId取出来,而后调用LoadBalancerClient 的execute办法。 @Overridepublic ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException { final URI originalUri = request.getURI(); // 这个就是咱们注册到注册核心的服务名称 String serviceName = originalUri.getHost(); Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri); return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));}RibbonLoadBalancerClient#execute次要是获取ILoadBalancer和Server,通过Server进行近程调用。这个server曾经是有对应的IP和端口了。 public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException { // 获取ILoadBalancer ILoadBalancer loadBalancer = getLoadBalancer(serviceId); // 通过ILoadBalancer获取Server Server server = getServer(loadBalancer, hint); if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } // 近程调用 RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server, serviceId), serverIntrospector(serviceId).getMetadata(server)); return execute(serviceId, ribbonServer, request);}RibbonLoadBalancerClient#getLoadBalancer会调用SpringClientFactory#getLoadBalancer,SpringClientFactory的注入能够看上一章。SpringClientFactory#getLoadBalancer调用父类的NamedContextFactory#getInstance办法,NamedContextFactory#getInstance会判断之前是否创立了serviceId对应的AnnotationConfigApplicationContext,如果没有则创立。 ...

December 24, 2020 · 2 min · jiezi

关于ribbon:SpringCloud二Ribbon

RibbonRestTemplate近程调用springboot 提供的近程调用工具相似于 HttpClient,能够发送 http 申请,并解决响应。RestTemplate简化了Rest API调用,只须要应用它的一个办法,就能够实现申请、响应、Json转换 办法: getForObject(url, 转换的类型.class, 提交的参数)postForObject(url, 协定体数据, 转换的类型.class)RestTemplate 和 Dubbo 近程调用的区别: RestTemplate: http调用效率低Dubbo: RPC调用,Java的序列化效率高之前的系统结构是浏览器间接拜访后盾服务前面咱们通过一个Demo我的项目演示 Spring Cloud 近程调用 上面咱们先不应用ribbon, 独自应用RestTemplate来执行近程调用 新建 ribbon 我的项目pom.xmlapplication.yml主程序controller启动,并拜访测试新建 sp06-ribbon 我的项目 pom.xmleureka-client 中曾经蕴含 ribbon 依赖须要增加 sp01-commons 依赖application.ymlspring: application: name: ribbon server: port: 3001 eureka: client: service-url: defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka主程序创立 RestTemplate 实例RestTemplate 是用来调用其余微服务的工具类,封装了近程调用代码,提供了一组用于近程调用的模板办法,例如:getForObject()、postForObject() 等package cn.tedu.sp06;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.context.annotation.Bean;import org.springframework.web.client.RestTemplate;@EnableDiscoveryClient@SpringBootApplicationpublic class Sp06RibbonApplication { //创立 RestTemplate 实例,并存入 spring 容器 @Bean public RestTemplate getRestTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(Sp06RibbonApplication.class, args); }}RibbonControllerpackage cn.tedu.sp06.controller;import java.util.List;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.client.RestTemplate;import cn.tedu.sp01.pojo.Item;import cn.tedu.sp01.pojo.Order;import cn.tedu.sp01.pojo.User;import cn.tedu.web.util.JsonResult;@RestControllerpublic class RibbonController { @Autowired private RestTemplate rt; @GetMapping("/item-service/{orderId}") public JsonResult<List<Item>> getItems(@PathVariable String orderId) { //向指定微服务地址发送 get 申请,并取得该服务的返回后果 //{1} 占位符,用 orderId 填充 return rt.getForObject("http://localhost:8001/{1}", JsonResult.class, orderId); } @PostMapping("/item-service/decreaseNumber") public JsonResult decreaseNumber(@RequestBody List<Item> items) { //发送 post 申请 return rt.postForObject("http://localhost:8001/decreaseNumber", items, JsonResult.class); }RibbonSpringcloud集成的工具,作用是负载平衡,和重试 ...

December 1, 2020 · 3 min · jiezi

关于ribbon:第五阶段112

eureka连贯地址defaultZone 默认地点其余地点配置,须要由云服务提供eureka: client: service-url: defaultZone: http://eureka1:2001/eurekaGetting all instance registry info from the eureka serverThe response status is 200 Registering application ORDER-SERVICE with eureka with status UP registration status: 204 激活以后profile:--spring.profiles.active=eureka2 --server.port=2002重试一种容错形式,调用近程服务失败(异样,超时)时,能够主动进行重试调用1.增加spring-retry依赖2.配置重试参数MaxAutoRtries 单台服务器的重试次数 1次MaxAutoRtriesNextAerver 更换服务器的次数 2次OkToRetryOnAllOperations- 零碎容错工具降级和熔断 降级调用近程服务失败(异样,超时,服务不存在),能够通过执行以后服务中的一段代码来向客户端发回响应降级响应: 谬误提醒和返回缓存数据疾速失败:即便后盾服务故障,也要让客户端尽快失去谬误提醒,而不能让客户端期待增加降级1.增加Hystrix依赖2.启动类增加@EnableCircuitBreaker3.增加降级代码 在近程调用办法上增加 @HystrixCommand(fallbackMethod="降级办法") 实现降级办法,返回降级响应 ribbon重试 Hystrix断路器当申请量增大,呈现过多谬误,hystrix能够和后盾服务断开连接能够防止雪崩效应,故障流传限流措施流量过大时造成服务故障,能够断开服务,升高它的流量在特定条件下会主动触发熔断1.10秒内20次申请(必须首先满足)2.50%出错,执行了降级代码3.断路器关上几秒后进入半开状态,尝试发送申请如果申请胜利主动敞开断路器恢复正常 如果申请失败,再放弃关上状态几秒钟 Hystrix DashboardHystrix监控仪表盘,监控Hystrix降级和熔断的错误信息 actuatorspringboot提供的我的项目监控工具,提供了多种我的项目的监控数据衰弱状态 零碎环境 beans-spring容器中所有的对象mappings- spring mvc 所有映射的门路....hystrix在actuator中,增加了本人的监控数据 增加actuator1.增加actuator依赖2.yml配置裸露监控信息 m.e.w.e.i="*" 裸露所有监控 m.e.w.e.i=["health","beans","mappings"] m.e.w.e.i=beans Hystrix 配置:1.增加Hystrix依赖2.@EnableCircuitBreaker 装置rabbitmq rpm包rpm -ivh *.rpm 启动rabbitmq服务器 rabbitmq治理界面启用治理界面 重启rabbitmq服务 ...

November 2, 2020 · 1 min · jiezi