通过ClientHttpRequestInterceptor实现简单的服务负载均衡调用

文章介绍了在不引入SpringCloud的情况下,基于Ribbon的工作原理手动实现负载均衡的过程。作者定义了注解、实现了ClientHttpRequestInterceptor来拦截请求并应用自定义的负载算法,并提供了相关的代码示例,以此来解决多服务器间的负载问题。
摘要由CSDN通过智能技术生成

  最近在进行功能开发的时候遇到了一个问题,提供服务的服务器为了避免单点故障所以部署了两台服务器,由于当前的服务架构比较简单,并没有引入分布式服务架构,所以如何对这两台服务器进行负载均衡调用以及重试和熔断就需要自己来实现。本来可以引入ribbonloadbalance,但是发现引入之后需要引入springcloud框架,所以就放弃了。本着学习ribbon的原理的态度,所以准备手写一个负载均衡,满足当前的需求。


一、Ribbon是什么?

 之前写过一篇关于Ribbon的工作原理的文章,本篇文章也是基于对Ribbon的源码的理解来进行的,不了解Ribbon的工作原理的可以看这篇文章Ribbon源码分析

二、实现步骤

1.定义注解

  使用过Ribbon的都知道,只要在Resttemplate上面加上@LoadBalance注解就可以实现调用的负载均衡。

 @LoadBalance是一个组合注解,它可以实现@Qualifier的作用,而@Qualifier的作用是当spring容器中有多个相同类型的类时,注入的时候@Qualifier可以通过类名进行注入,例如

@LoadBalanced
@Autowired(
        required = false
    )
private List<RestTemplate> restTemplates = Collections.emptyList();

可以将所有添加了@LoadBalance注解的RestTemplate全部注入到restTemplates集合中。如果想对@Qualifier做一个更深入的了解可以看这篇文章:@Qualifier的高级应用

通过这个注解就可以获取到需要负载均衡调用的Resttemplate了。

三、ClientHttpRequestInterceptor

  下一个关键的类就是ClientHttpRequestInterceptor,通过这个拦截器,可以拦截Resttemplate的请求,通过请求的服务名称来调用自己定义的负载均衡算法,最终设置调用的服务。

  有了拦截器之后就是给Resttemplate添加拦截器了,通过方法Resttemplate..getInterceptors().add()方法可以给RestTemplate添加拦截器。但是Ribbon的源码中使用了RestTemplateCustomizer进行定制化配置,所以这里准备模仿着写。

四、实现代码

定义一个带有@Qualifier的注解,添加该注解的Resttemplate会负载均衡调用

@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface AiChatLoadBalance {
}

定义ClientHttpRequestInterceptor的实现类,实现负载均衡调用的主要逻辑

@Slf4j
public class AiChatRetryLoadBalancerInterceptor implements ClientHttpRequestInterceptor {

    private AiChatLoadBalancer aiChatLoadBalancer;

    public AiChatRetryLoadBalancerInterceptor(AiChatLoadBalancer aiChatLoadBalancer) {
        this.aiChatLoadBalancer=aiChatLoadBalancer;
    }

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        URI originalUri = request.getURI();
        String serviceName = originalUri.getHost();
        Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
        List<AiChatOpenAiServer> serverList = aiChatLoadBalancer.getServer(serviceName);
        AiChatOpenAiServer aiChatOpenAiServer = aiChatLoadBalancer.chooseServer(serverList);
        log.info("当前调用的地址为:{}", aiChatOpenAiServer.getAddress());
        if (Objects.nonNull(aiChatOpenAiServer)) {
            AiChatRequestwraper aiChatRequestwraper = new AiChatRequestwraper(request, aiChatOpenAiServer.getAddress() + originalUri.getPath());
            ClientHttpResponse response = null;
            try {
                response = execution.execute(aiChatRequestwraper, body);
            }catch (Exception e) {
                log.info("调用ip={}的服务失败,失败的原因为:{}", aiChatOpenAiServer.getAddress(), e);
                // 更新当前ip的调用失败时间
                aiChatOpenAiServer.setUnvalibaleTime(System.currentTimeMillis());
                aiChatLoadBalancer.setServerList(serviceName, serverList);
                throw e;
            }
            if (Objects.nonNull(response)&&response.getStatusCode().isError()) {
                // 更新当前ip的调用失败时间
                aiChatOpenAiServer.setUnvalibaleTime(System.currentTimeMillis());
                aiChatLoadBalancer.setServerList(serviceName, serverList);
            }else if (response.getStatusCode().is2xxSuccessful()&&aiChatOpenAiServer.getUnvalibaleTime()!=0l) {
                // 更新当前ip的调用失败时间
                aiChatOpenAiServer.setUnvalibaleTime(0l);
                aiChatLoadBalancer.setServerList(serviceName, serverList);
            }
            return response;
        }
        return null;
    }


    static class AiChatRequestwraper extends HttpRequestWrapper {
        private String url;

        public AiChatRequestwraper(HttpRequest request,String url) {
            super(request);
            this.url = url;
        }
        @Override
        public URI getURI() {
            try {
                return new URI(url);
            } catch (URISyntaxException e) {
            }
            return null;
        }
    }
}

AiChatLoadBalancer提供了获取提供服务列表,以及按照自己的负载均衡策略选择服务的方法

@Slf4j
@Service
@EnableConfigurationProperties({AiChatLoadBalancerProperties.class})
public class AiChatLoadBalancer {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private AiChatLoadBalancerProperties aiChatLoadBalancerProperties;



    /**
     * @description: 获取所有可用的服务地址
     * @author: wangjinfeng
     * @date: 2023/4/28 15:04
     * @param: []
     * @return: java.util.List<com.zyj.aichat.common.loadbalance.AiChatOpenAiServer>
     **/
    public List<AiChatOpenAiServer> getServer(String serverName) {
        if (StringUtils.isBlank(serverName)) {
            return null;
        }
        String serverListStr = redisTemplate.opsForValue().get(serverName);
        if (StringUtils.isBlank(serverListStr)) {
            serverListStr = JSON.toJSONString(aiChatLoadBalancerProperties.getServer());
            redisTemplate.opsForValue().set(serverName, JSON.toJSONString(aiChatLoadBalancerProperties.getServer()), 24, TimeUnit.HOURS);
        }
        return JSON.parseArray(serverListStr, AiChatOpenAiServer.class);
    }

    /**
     * @description: 选择调用服务
     * @author: wangjinfeng
     * @date: 2023/4/28 14:38
     * @param: [serverList]
     * @return: com.zyj.aichat.common.loadbalance.AiChatOpenAiServer
     **/
    public AiChatOpenAiServer chooseServer(List<AiChatOpenAiServer> serverList) {
        if (CollectionUtils.isEmpty(serverList)) {
            // 没有可用的服务
            return null;
        }
        if (serverList.size()==1) {
            return serverList.get(0);
        }

        List<AiChatOpenAiServer> collect = serverList
                .stream()
                // 过滤掉一分钟之内调用失败的
                .filter(server -> {
                    long time = (System.currentTimeMillis() - server.getUnvalibaleTime()) / 1000;
                    log.info("当前服务失败窗口为:{}", time);
                    return time > 60;
                })
                .sorted(Comparator.comparing(AiChatOpenAiServer::getWeight).reversed())
                .collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(collect)&&collect.size()>1) {
            return collect.get(RandomUtil.randomInt(100)%serverList.size());
        } else if (collect.size()==1) {
            return collect.get(0);
        }
        return null;
                //.findFirst()
    }

    public void setServerList(String serverName, List<AiChatOpenAiServer> serverList) {
        redisTemplate.delete(serverName);
        redisTemplate.opsForValue().set(serverName, JSON.toJSONString(serverList), 24, TimeUnit.HOURS);
    }
}

最后进行配置

@Configuration
@ConditionalOnClass({RestTemplate.class})
public class AiChatLoadBalancerAutoConfiguration {

    @AiChatLoadBalance
    @Autowired(
            required = false
    )
    private List<RestTemplate> restTemplates = Collections.emptyList();

    @Bean
    public AiChatRetryLoadBalancerInterceptor loadBalancerInterceptor(AiChatLoadBalancer aiChatLoadBalancer) {
        return new AiChatRetryLoadBalancerInterceptor(aiChatLoadBalancer);
    }


    @Bean
    public SmartInitializingSingleton loadBalancedRestTemplateInitializer(final List<RestTemplateCustomizer> customizers) {
        return new SmartInitializingSingleton() {
            public void afterSingletonsInstantiated() {
                Iterator var1 = AiChatLoadBalancerAutoConfiguration.this.restTemplates.iterator();

                while (var1.hasNext()) {
                    RestTemplate restTemplate = (RestTemplate) var1.next();
                    Iterator var3 = customizers.iterator();

                    while (var3.hasNext()) {
                        RestTemplateCustomizer customizer = (RestTemplateCustomizer) var3.next();
                        customizer.customize(restTemplate);
                    }
                }

            }
        };
    }

    @Bean
    @ConditionalOnMissingBean
    public RestTemplateCustomizer restTemplateCustomizer(final AiChatRetryLoadBalancerInterceptor loadBalancerInterceptor) {
        return new RestTemplateCustomizer() {
            public void customize(RestTemplate restTemplate) {
                List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            }
        };
    }
}


总结

  本文参照了Ribbon源码来自己实现负载均衡和熔断,实现了简单的需求,同时可以更好的了解Ribbon的工作原理。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值