HopeHomi脚手架(五)远程调用Feign与Sentinel

news2024/11/25 2:42:29

代码示例

cloudB

在这里插入图片描述

cloudA-api

在这里插入图片描述

cloudA

在这里插入图片描述

feign对象注册源码解析

FeignClientsRegistrar

FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar
当启动类自动扫描构造beanDefinition的时候,进行beanDefinition注册
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

ImportBeanDefinitionRegistrar回调registerBeanDefinitions

在这里插入图片描述

registerFeignClients

在这里插入图片描述
通过@EnableFeignClients下的包路径扫描,找到@FeignClient的接口,这里扫描到一个我们自定义feign接口
在这里插入图片描述
这里会对feign做校验(是否为接口)
在这里插入图片描述

registerClientConfiguration

注册bd,用FeignClientSpecification来做乘载。beanName为 demo-cloud-A.FeignClientSpecification

在这里插入图片描述
在这里插入图片描述

registerFeignClient

在这里创建FeignClientFactoryBean,他是FactoryBean。
再次注册一个bd,不过这里的构造方法是Supplier
在这里插入图片描述
在这里插入图片描述
将Supplier传递到bd中
在这里插入图片描述

创建bean实例时

判断bd中的instanceSupplier如果不为空,通过instanceSupplier来创建实例
在这里插入图片描述

FeignClientsRegistrar#lambda

在这里插入图片描述

factoryBean.getObject

在这里插入图片描述
Feign.Builder默认实现为openFeign的FeignCircuitBreaker.Builder,如果我们使用Sentinel,那么实现类为SentinelFeign.Builder
在这里插入图片描述

loadBalance

这里的第三个参数HardCodedTarget,通过new HardCodedTarget<>(type, name, url)构造
在这里插入图片描述
在这里插入图片描述
获取Feign的断路器Targeter,由FeignCircuitBreakerTargeter实现

在这里插入图片描述
调用targeter.target
在这里插入图片描述

loadBalance => targeter.target

判断是否为openFeign默认的Builder,这里我们使用的Sentinel
在这里插入图片描述
在这里插入图片描述
build方法由SentinelFeign实现
在这里插入图片描述
在这里插入图片描述

最终通过super.build()来构造Feign对象并返回
调用Feign.newInstance(target),属性如下图所示
在这里插入图片描述
基于JDK的动态代理对象,代理的类为HardCodedTarget,接口为Target,InvocationHandler为SentinelInvocationHandler
在这里插入图片描述

调用过程源码解析

userClient

userClient是一个jdk动态代理对象,创建过程在上面
在这里插入图片描述

SentinelInvocationHandler

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

methodHandler.invoke

在执行调用前,会初始化RequestTemplate和Options
在这里插入图片描述

executeAndDecode

开始执行http
在这里插入图片描述

targetRequest 拦截器执行,创建Request对象

在targetRequest中,会执行拦截器的apply方法
在这里我们创建了一个传递header的interceptor

在这里插入图片描述
在这里插入图片描述
自定义interceptor,向requestTemplate传递header数据
在这里插入图片描述
最终构造了一个Request对象
在这里插入图片描述

client.execute

在这里插入图片描述
通过FeignBlockingLoadBalancerClient调用
在这里插入图片描述

client.execute # loadBalancerClient.choose

loadBalancerClient.choose,这里的loadBalancerClient是BlockingLoadBalancerClient(org.springframework.cloud.loadbalancer.blocking.client包下)
在这里插入图片描述

负载均衡算法:RoundRobinLoadBalancer

这里的loadbalancer是使用的RoundRobinLoadBalancer

RoundRobinLoadBalancer是一种常见的负载均衡算法,其主要思想是按照轮询的方式将请求分配给后端的多个服务器,在每一轮中均匀地选择其中一个服务器处理请求。
具体来说,RoundRobinLoadBalancer维护一个列表,其中包含了所有可用的后端服务器。在每次请求到来时,该算法会依次选择列表中的下一个服务器,并将该请求发送给它。当到达列表的末尾时,算法会重新从列表的开头开始选择服务器。这种方式可以确保每个服务器在处理请求时都能够平均分配负载,从而避免了任何一个服务器被过度利用而导致性能问题。
需要注意的是,RoundRobinLoadBalancer在实际使用中也有一些问题。例如,如果其中一个服务器处理请求的时间比其他服务器长,那么该服务器上的积压请求会增加,而其他服务器可能会一直处于空闲状态。此外,如果列表中的某一个服务器已经失效,算法可能会一直选择该服务器,从而导致请求失败。为了解决这些问题,通常需要采用一些额外的技术手段来优化负载均衡算法的性能。

在这里插入图片描述

会通过nacos去找serviceId的客户端服务信息
在这里插入图片描述nacos服务发现寻址
在这里插入图片描述
在这里插入图片描述
先从缓存获取ServiceInfo
在这里插入图片描述
如果获取不到,通过grpc或者http去请求服务端,拉取最新的可用列表
在这里插入图片描述
最后解析完毕后,通过HttpURLConnection调用(需要更改成okhttp,在优化点中)
在这里插入图片描述

openFeign优化点

Feign拦截器,传递请求头,将MDC放入请求头

public class FeignRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        Enumeration<String> headerNames = request.getHeaderNames();
        if (headerNames != null) {
            while (headerNames.hasMoreElements()) {
                String name = headerNames.nextElement();
                String values = request.getHeader(name);
                requestTemplate.header(name, values);
            }
        }
        requestTemplate.header(LogConstant.TRACE_ID, MDC.get(LogConstant.TRACE_ID));
    }
}
 @Bean
    public FeignRequestInterceptor feignRequestInterceptor() {
        return new FeignRequestInterceptor();
    }

整合okhttp

默认的情况下,openFeign使用的上是HttpURLConnection发起请求,openFeign每次需要创建一个新的请求,而不是使用的链接池,所以我们的需要替换掉这个默认的实现,改用一个有链接池的实现。

<!-- 替换默认的HttpURLConnection,改为okhttp,并添加链接池-->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
    <version>11.9.1</version>
</dependency>

okhttp配置

@Data
@ConfigurationProperties("hope.feign.okhttp")
public class OkHttpProperties {

    /**
     * 是否支持重定向,默认:true
     */
    boolean followRedirects = true;

    /**
     * 链接超时时间,单位毫秒
     */
    int connectTimeout = 5000;

    /**
     * 禁用ssl验证
     */
    boolean disableSslValidation = true;

    /**
     * 读超时,单位毫秒
     */
    int readTimeout = 15000;

    /**
     * 写超时,单位毫秒
     */
    int writeTimeout = 15000;

    /**
     * 是否自动重连
     */
    boolean retryOnConnectionFailure = true;

    /**
     * 最大空闲链接
     */
    int maxIdleConnections = 10;

    /**
     * 默认保持5分钟
     */
    long keepAliveDuration = 1000 * 60 * 5L;
}

/**
     * okhttp feign配置
     */
    @AutoConfiguration
    @ConditionalOnClass({OkHttpClient.class})
    @ConditionalOnMissingBean({okhttp3.OkHttpClient.class})
    @ConditionalOnProperty(value = "feign.okhttp.enabled", havingValue = "true")
    @EnableConfigurationProperties(OkHttpProperties.class)
    class OkHttpFeignConfiguration {

        private okhttp3.OkHttpClient okHttpClient;

        @Bean
        public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory, OkHttpProperties properties, OkHttpClientConnectionPoolFactory connectionPoolFactory) {
            this.okHttpClient = httpClientFactory.createBuilder(properties.disableSslValidation)
                    // 链接超时时间
                    .connectTimeout(properties.getConnectTimeout(), TimeUnit.MILLISECONDS)
                    // 是否禁用重定向
                    .followRedirects(properties.isFollowRedirects())
                    //设置读超时
                    .readTimeout(properties.getReadTimeout(), TimeUnit.MILLISECONDS)
                    //设置写超时
                    .writeTimeout(properties.getWriteTimeout(), TimeUnit.MILLISECONDS)
                    // 链接失败是否重试
                    .retryOnConnectionFailure(properties.isRetryOnConnectionFailure())
                    //链接池
                    .connectionPool(connectionPoolFactory.create(properties.getMaxIdleConnections(), properties.getKeepAliveDuration(), TimeUnit.MILLISECONDS))
                    .addInterceptor(new OkHttpFeignLoggingInterceptor())
                    .build();
            return this.okHttpClient;
        }

        @PreDestroy
        public void destroy() {
            if (this.okHttpClient != null) {
                this.okHttpClient.dispatcher().executorService().shutdown();
                this.okHttpClient.connectionPool().evictAll();
            }
        }
    }

okhttp拦截器,用来打印feign请求日志

@Slf4j
public class OkHttpFeignLoggingInterceptor implements Interceptor {
    private static final Charset UTF8 = StandardCharsets.UTF_8;

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response;
        StringBuilder builder = StrUtil.builder();
        builder.append("================  Feign Start  ================").append("\n");
        try {
            RequestBody requestBody = request.body();
            boolean hasRequestBody = requestBody != null;
            Connection connection = chain.connection();
            String requestStartMessage = "==> "
                    + request.method()
                    + ' ' + request.url()
                    + (connection != null ? " " + connection.protocol() : "");
            builder.append(requestStartMessage).append("\n");

            if (hasRequestBody) {
                // Request body headers are only present when installed as a network interceptor. Force
                // them to be included (when available) so there values are known.
                if (requestBody.contentType() != null) {
                    builder.append("Content-Type: ").append(requestBody.contentType()).append("\n");
                }
                if (requestBody.contentLength() != -1) {
                    builder.append("Content-Length: ").append(requestBody.contentLength()).append("\n");
                }
            }

            Headers headers = request.headers();
            for (int i = 0, count = headers.size(); i < count; i++) {
                String name = headers.name(i);
                // Skip headers from the request body as they are explicitly logged above.
                if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
                    builder.append(name).append(": ").append(headers.value(i)).append("\n");
                }
            }

            if (!hasRequestBody) {
                builder.append("==> END ").append(request.method()).append("\n");
            } else if (bodyHasUnknownEncoding(request.headers())) {
                builder.append("==> END ").append(request.method()).append(" (encoded body omitted)").append("\n");
            } else {
                Buffer buffer = new Buffer();
                requestBody.writeTo(buffer);

                Charset charset = UTF8;
                MediaType contentType = requestBody.contentType();
                if (contentType != null) {
                    charset = contentType.charset(UTF8);
                }

                if (isPlaintext(buffer)) {
                    builder.append(buffer.readString(charset)).append("\n");
                    builder.append("==> END ").append(request.method()).append(" (").append(requestBody.contentLength()).append("-byte body)").append("\n");
                } else {
                    builder.append("==> END ").append(request.method()).append(" (binary ").append(requestBody.contentLength()).append("-byte body omitted)").append("\n");
                }
            }

            long startNs = System.nanoTime();
            try {
                response = chain.proceed(request);
            } catch (Exception e) {
                builder.append("<-- HTTP FAILED: ").append(e).append("\n");
                throw e;
            }
            long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);

            ResponseBody responseBody = response.body();
            long contentLength = responseBody.contentLength();
            builder.append("<-- ").append(response.code()).append(response.message().isEmpty() ? "" : ' ' + response.message()).append(' ').append(response.request().url()).append(" (").append(tookMs).append("ms").append(')').append("\n");

            Headers responseHeader = response.headers();
            int count = responseHeader.size();
            for (int i = 0; i < count; i++) {
                builder.append(responseHeader.name(i)).append(": ").append(responseHeader.value(i)).append("\n");
            }

            if (!HttpHeaders.hasBody(response)) {
                builder.append("<-- END HTTP").append("\n");
            } else if (bodyHasUnknownEncoding(response.headers())) {
                builder.append("<-- END HTTP (encoded body omitted)").append("\n");
            } else {
                BufferedSource source = responseBody.source();
                // Buffer the entire body.
                source.request(Long.MAX_VALUE);
                Buffer buffer = source.getBuffer();

                Long gzippedLength = null;
                if ("gzip".equalsIgnoreCase(responseHeader.get("Content-Encoding"))) {
                    gzippedLength = buffer.size();
                    GzipSource gzippedResponseBody = null;
                    try {
                        gzippedResponseBody = new GzipSource(buffer.clone());
                        buffer = new Buffer();
                        buffer.writeAll(gzippedResponseBody);
                    } finally {
                        if (gzippedResponseBody != null) {
                            gzippedResponseBody.close();
                        }
                    }
                }

                Charset charset = UTF8;
                MediaType contentType = responseBody.contentType();
                if (contentType != null) {
                    charset = contentType.charset(UTF8);
                }

                if (!isPlaintext(buffer)) {
                    builder.append("<-- END HTTP (binary ").append(buffer.size()).append("-byte body omitted)").append("\n");
                    return response;
                }

                if (contentLength != 0) {
                    builder.append(buffer.clone().readString(charset)).append("\n");
                }

                if (gzippedLength != null) {
                    builder.append("<-- END HTTP (").append(buffer.size()).append("-byte, ").append(gzippedLength).append("-gzipped-byte body)").append("\n");
                } else {
                    builder.append("<-- END HTTP (").append(buffer.size()).append("-byte body)").append("\n");
                }
            }
        } finally {
            builder.append("================  Feign End  ================").append("\n");
            log.info("\n\n" + builder.toString());
        }
        return response;


    }

    /**
     * Returns true if the body in question probably contains human readable text. Uses a small sample
     * of code points to detect unicode control characters commonly used in binary file signatures.
     */
    private static boolean isPlaintext(Buffer buffer) {
        try {
            Buffer prefix = new Buffer();
            long byteCount = buffer.size() < 64 ? buffer.size() : 64;
            buffer.copyTo(prefix, 0, byteCount);
            for (int i = 0; i < 16; i++) {
                if (prefix.exhausted()) {
                    break;
                }
                int codePoint = prefix.readUtf8CodePoint();
                if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
                    return false;
                }
            }
            return true;
        } catch (EOFException e) {
            // Truncated UTF-8 sequence.
            return false;
        }
    }

    private boolean bodyHasUnknownEncoding(Headers headers) {
        String contentEncoding = headers.get("Content-Encoding");
        return contentEncoding != null
                && !"identity".equalsIgnoreCase(contentEncoding)
                && !"gzip".equalsIgnoreCase(contentEncoding);
    }
}

开启请求压缩功能

feign:
  # 开启压缩功能
  compression:
    request:
      enabled: true
      mime-types: text/xml,application/xml,application/json
      min-request-size: 2048
    response:
      enabled: true 

添加LoadBalancerCacheManager

项目启动时出现

iguration$LoadBalancerCaffeineWarnLogger : Spring Cloud LoadBalancer is currently working with the default cache. While this cache implementation is useful for development and tests, it’s recommended to use Caffeine cache in production.You can switch to using Caffeine cache, by adding it and org.springframework.cache.caffeine.CaffeineCacheManager to the classpath.

在这里插入图片描述

配置超时时间

feign:
  client:
    config:
      # 设置超时,囊括了okhttp的超时,okhttp属于真正执行的超时,openFeign属于服务间的超时
      # 设置全局超时时间
      default:
        connectTimeout: 2000
        readTimeout: 5000
      # 针对特定contextId设置超时时间
      xxx-server:
        connectTimeout: 1000
        readTimeout: 2000

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/456808.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

TryHackMe-Second(boot2root)

Second 排名第二并不是一件坏事&#xff0c;但在这种情况下并非如此。 端口扫描 循例nmap Web枚举 进到8000 注册个账号进去&#xff0c;没啥用 二次注入 虽然登录框那里没有sql注入&#xff0c;但是可以尝试注册个非法账户名尝试二次注入 登录进去之后使用单词计数器 说明…

【产品设计】RBAC权限设计

权限管理是B端中常见的话题&#xff0c;它规定了用户各自的角色和可使用的职能&#xff0c;也对数据的安全提供了保障。 权限管理是B端产品绕不开的话题&#xff0c;本文总结了我对权限管理的设计经验与设计方法&#xff0c;共分为4个部分&#xff1a; 权限管理的概念梳理RBAC…

部署LVS-DR群集

LVS-DR数据包流向分析 为了方便进行原理分析&#xff0c;将Client与群集机器放在同一网络中&#xff0c;数据包流经的路线为1-2-3-4 &#xff08;Client向目标VIP发出请求&#xff0c;调度器接收&#xff1b;director根据负载均衡算法选择realserver不修改也不封装IP&#xf…

【深度学习入门系列】径向基函数(RBF)神经网络原理介绍及pytorch实现(内含分类、回归任务实例)

文章目录 1 RBF神经网络1.1 简介1.2 步骤输入rbf层核函数中心点求解方法 输出 1.3 几个问题 2 分类2.0 数据集2.1 网络架构2.2 代码2.3 结果 3 回归3.0 数据集3.1 网络架构3.2 代码3.3 结果 4 代码&#xff08;可直接食用&#xff09; 众所周知&#xff0c;MATLAB工具箱里提供了…

MySQL基础练习——创建数据库、数据表,并进行修改

目录 题目&#xff1a; 创建库和表&#xff1a; 创建库&#xff1a; 创建表&#xff1a; 将 c_contact 字段插入到 c_birth 字段后面&#xff1a; 将 c_name 字段数据类型改为VARCHAR(70)&#xff1a; 将 c_contact 字段改名为 c_phone&#xff1a; 将表名修改为 customer…

魔兽世界az端和TC端有什么区别 Mangos,TC,SW,AZ,AC的关系

魔兽世界az端和TC端有什么区别 Mangos,TC,SW,AZ,AC的关系 大家好我是艾西&#xff0c;魔兽世界现在很多小伙伴对AZ端和TC端不是很能理解什么意思有什么区别&#xff0c;小编查询了大量的资料简单跟大家说一下&#xff0c;今天是艾西故事会大家全当听故事了&#xff01; &#…

learn C++ NO.2 ——认识引用、auto关键字

1.引用 1.1 引用的概念 引用并不是定义一个新的变量&#xff0c;而是给已经存在的变量起的一个别名。从语言的层面上&#xff0c;编译器并不会为了引用而去开辟新的内存空间。引用和被它引用的变量是共用一块内存空间的。举个生活中引用的例子&#xff0c;西游记中&#xff0…

C++入门(上)

C入门 c是对于c语言的补充而发展的一种面向对象的语言&#xff0c;也能兼容c语言的内容&#xff0c;所以c语言的东西可以在cpp文件中写c语言的内容&#xff0c;也是可以运行的&#xff08;可以混写&#xff09; 文章目录 C入门命名空间命名空间的定义命名空间的使用 C的输入…

22、Tweak原理及部分逆向防护

一、Tweak原理 1.1 Tweak产物.dylib 执行make命令时,在 .theos的隐藏目录中,编译出obj/debug目录,包含 arm64、arm64e两种架构,同时生成readbadges.dylib动态库 在arm64、arm64e目录下,有各自架构的readbadges.dylib,而debug目录下的readbadges.dylib,是一个胖二进制文件 fi…

ShareSDK QQ平台注册

注册开发者账号 1.在QQ互联开放平台首页 QQ互联官网首页 &#xff0c;点击右上角的“登录”按钮&#xff0c;使用QQ帐号登录&#xff0c;如下图所示&#xff1a; 重要提示&#xff1a; 开发者QQ号码一旦注册不能变更&#xff0c;建议使用公司公共QQ号码而不是员工私人号码注册…

软件测试好学习吗?

软件测试好不好学习其实各自的认知都不同&#xff0c;想要知道自己能不能学会&#xff0c;对于自己怎么样&#xff0c;最简单的方法就是找个基础教程先去学习一下了~ 其实软件测试这个行业与其他岗位相比&#xff0c;对零基础的学习者更加友好。即使你不懂互联网&#xff0c;不…

小程序过审失败,怎么解决?

小程序过审失败&#xff0c;怎么解决&#xff1f; 如果你的小程序未能通过审核&#xff0c;可以参考以下步骤解决问题&#xff1a; 1、审核不通过原因&#xff1a;在审核失败的通知中会注明不通过的具体原因和相关文件路径。请先认真阅读并理解不通过的原因&#xff0c;找到问…

存储电路:计算机存储芯片的电路结构是怎样的?

我们把用于存储数据的电路叫做存储器&#xff0c;按照到 CPU 距离的远近&#xff0c;存储器主要分为寄存器、缓存和主存。我们就来重点分析这三种存储器的特点、原理&#xff0c;以及应用场景。 存储器是由基本的存储单元组成的&#xff0c;要想搞清楚存储器原理&#xff0c;我…

【C++关联容器】set的成员函数

目录 set 1. 构造、析构和赋值运算符重载 1.1 构造函数 1.2 析构函数 1.3 赋值运算符重载 2. 迭代器 3. 容量 4. 修改器 5. 观察者 6. 操作 7. 分配器 set set是按照特定顺序存储唯一元素的容器。 在一个set中&#xff0c;一个元素的值也是它的标识&#xff08;值…

插装式两位两通电磁阀DSV-080-2NCP、DDSV-080-2NCP

特性 压力4000 PSI(276 Bar) 持续的电磁。 硬化处理的提升阀和柱塞可获得更长的寿命和低泄漏量。 有效的混式电磁铁结构。 插装阀允许交流电压。可选的线圈电压和端子。 标准的滤网低泄漏量选择 手动关闭选择。 工业化通用阀腔。 紧凑的尺寸。 两位两通常闭式双向电磁…

热门好用的企业网盘工具大盘点

企业网盘作为热门的企业文件管理工具相比于个人网盘&#xff0c;更注重安全性&#xff0c;并增加了协同功能。当下市面上的企业网盘工具可谓是百花齐放&#xff0c;今天就盘点几款热门好用的网盘工具&#xff0c;希望能帮助您挑选到心仪的网盘工具~ 1. Zoho Workdrive Zoho Wo…

#PythonPytorch 2.如何对CTG特征数据建模

系列文章目录 #Python&Pytorch 1.如何入门深度学习模型 #Python&Pytorch 2.如何对CTG特征数据建模 我之前也写过一篇使用GBDT对UCI-CTG特征数据进行建模的博客&#xff0c;不过那是挺早的时候写的&#xff0c;只是简单贴了代码&#xff0c;方便了解流程而已&#xff0…

原神3.2剧情服搭建教程

同步官服所有剧情和交互 优化后电脑16G运行内存也可以完美运行 数据库再次启动报错的,把将redis.service中的Type=forking配置删除或者注释掉即可。 位于:/usrb/systemd/system/redis.service 然后重启服务就不会爆错了。 下面是具体步骤 su root (此处会提示输入密…

相机雷达联合标定cam_lidar_calibration

文章目录 运行环境&#xff1a;1.1 ROS环境配置1&#xff09;工作空间创建和编译2&#xff09;官方数据集测试环境 2.1 在线标定1&#xff09;数据类型2&#xff09;标定板制作3&#xff09;配置文件4&#xff09;开始标定5&#xff09;完整实现步骤 3.1 python版本选择3.2 rvi…

医疗保障信息平台HASF应用系统技术架构名词解释技术选型架构图

下载地址&#xff1a; 医疗保障信息平台HASF应用系统技术架构规范.pdf下载—无极低码 HSAF 医疗保障应用框架&#xff08;Healthcare Security Application Framework&#xff09; IaaS 基础设施即服务&#xff08;Infrastructure-as-a-Service&#xff09; PaaS 平台即服务…