OpenFeign(9.5.0)性能优化尝试

一、为什么需要优化?

    微服务之间的RPC调用使用的OpenFeign组件,并且完全使用的默认设置,**默认的设置**包括:

1.1 HTTP客户端

默认使用的HttpURLConnection,这是java自带的发送http请求的API,优点就是java自带的,调用的时候方便,缺点就是性能和安全方面

缺点

  • HttpURLConnection每次请求都会打开一个新的TCP连接,不复用TCP连接,这会导致在高并发场景下HTTP请求和响应的速度变慢

  • 比较繁琐,需要手动进行请求的创建、连接、读取和关闭等操作;

  • 线程安全性不如其他并发包,需要在多线程环境中进行适当的同步;

  • 对响应数据的读取需要手动进行,需要调用IO流API进行读取。

现状

目前使用的默认的HttpURLConnection

1.2 重试策略

默认的重试策略

@Configuration
public class FeignClientsConfiguration {
@Bean
@ConditionalOnMissingBean
    public Retryer feignRetryer() {
        return Retryer.NEVER_RETRY;
    }
}

默认是永不重试,请求五秒超时后就抛异常结束

现状

目前重试策略由第三方组件Guava手动实现重试,需要重试的接口自己实现重试机制。

1.3 日志打印策略

 public enum Level {
    /**
     * No logging.
     */
    NONE,
    /**
     * Log only the request method and URL and the response status code and execution time.
     */
    BASIC,
    /**
     * Log the basic information along with request and response headers.
     */
    HEADERS,
    /**
     * Log the headers, body, and metadata for both requests and responses.
     */
    FULL
  }

默认是NONE,不打印feign的请求响应日志

现状

目前是默认的不打印feign的请求响应日志

1.4 编码解码策略

@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
   return new ResponseEntityDecoder(new SpringDecoder(this.messageConverters));
}

@Bean
@ConditionalOnMissingBean
public Encoder feignEncoder() {
   return new SpringEncoder(this.messageConverters);
}

默认使用是是SpringEncoder和SpringDecoder(它们共同作用于HTTP消息转换,其中SpringEncoder用于将Java对象转换为请求的HTTP消息体,SpringDecoder将响应的HTTP消息体转换为相应的Java对象)

以下是一个HTTP消息体的例子:

POST /api/books HTTP/1.1
Host: example.com
Content-Type: application/json

{
    "title": "The Hitchhiker's Guide to the Galaxy",
    "author": "Douglas Adams",
    "year": 1979,
    "publisher": "Pan Books",
    "isbn": "978-0330508537",
    "language": "English",
    "format": "paperback",
    "pages": 224
}

现状

目前是默认配置

二、目前可以优化那些方面

目前我们主要想提升的是feign接口的性能,因为平时总会有一些feign接口超时,但是具体去排查,并不是数据库响应的问题,所以我们需要对feign本身的请求性能进行优化。

重试策略我们可以不做改动,需要重试的地方就自己实现。

feign请求响应日志打印这一块,我们也为了性能使用默认不打印(好像也没有这种需要)。

编码解码策略也可以使用默认,性能提升不在这。

所以优化重点放在了Feign的HTTP客户端

三、Feign-HTTP客户端替换

3.1 我们该如何配置

不做特殊配置的话,在pom文件里面加上http客户端,SpringBoot应用就可以识别并使用到。但是需要做特殊配置的话就不行。

<dependency>
   <groupId>io.github.openfeign</groupId>
   <artifactId>feign-httpclient</artifactId>
</dependency>

9.5.0版本的OpenFeign是不支持在yaml文件或者properties文件中配置的。在高版本的OpenFeign可以直接在ymal或者properties配置

### Feign 配置
feign:
  httpclient:
    # 开启 Http Client
    enabled: true
    # 最大连接数,默认:200
    max-connections: 200
    # 最大路由,默认:50
    max-connections-per-route: 50
    # 连接超时,默认:2000/毫秒
    connection-timeout: 2000
    # 生存时间,默认:900L
    time-to-live: 900
    # 响应超时的时间单位,默认:TimeUnit.SECONDS
#    timeToLiveUnit: SECONDS

### Feign 配置
feign:
  httpclient:
    # 是否开启 Http Client
    enabled: false
#    # 最大连接数,默认:200
#    max-connections: 200
#    # 最大路由,默认:50
#    max-connections-per-route: 50
#    # 连接超时,默认:2000/毫秒
#    connection-timeout: 2000
#    # 生存时间,默认:900L
#    time-to-live: 900
#    # 响应超时的时间单位,默认:TimeUnit.SECONDS
##    timeToLiveUnit: SECONDS
  okhttp:
    enabled: true


那么在9.5.0该如何配置我们的最大连接数、连接超时时间等参数呢?

肯定只能自己写配置文件来处理了

@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignConfig {
  @Bean
  public OkHttpClient okHttpClient() {
    OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
    clientBuilder.readTimeout(10, TimeUnit.SECONDS)
        .writeTimeout(10, TimeUnit.SECONDS)
        .connectTimeout(5, TimeUnit.SECONDS)
        .retryOnConnectionFailure(true)
        .connectionPool(new ConnectionPool(255, 5, TimeUnit.MINUTES));
        //.addInterceptor(okHttpInterceptor);
    return clientBuilder.build();
  }
}

发起一个Http请求,可以看到我们的配置是生效的

image.png

3.2 Feign(9.5.0)自动配置源码

@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignAutoConfiguration {
    @Configuration
    @ConditionalOnClass(OkHttpClient.class)
    @ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
    @ConditionalOnProperty(value = "feign.okhttp.enabled", matchIfMissing = true)
    protected static class OkHttpFeignConfiguration {

        @Autowired(required = false)
        private okhttp3.OkHttpClient okHttpClient;

        @Bean
        @ConditionalOnMissingBean(Client.class)
        public Client feignClient() {
            if (this.okHttpClient != null) {
                return new OkHttpClient(this.okHttpClient);
            }
            return new OkHttpClient();
        }
    }
}
//OkHttpClient是Client的实现类
public final class OkHttpClient implements Client {
  
}

当Springboot应用启动的时候,检测到类路径中Feign类,并且没有com.netflix.loadbalancer.ILoadBalancer类时,才会加载这个配置,通俗讲,就是如果用到了Spring-Cloud的Ribbon负载均衡组件,FeignAutoConfiguration就不会加载OkHttpFeignConfiguration。

那什么时候初始化Feign的Client呢?

@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@Configuration
@AutoConfigureBefore(FeignAutoConfiguration.class)
//Order is important here, last should be the default, first should be optional
// see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
      OkHttpFeignLoadBalancedConfiguration.class,
      DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
  
}
@Configuration
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty(value = "feign.okhttp.enabled", matchIfMissing = true)
class OkHttpFeignLoadBalancedConfiguration {

    @Autowired(required = false)
    private okhttp3.OkHttpClient okHttpClient;

    @Bean
    @ConditionalOnMissingBean(Client.class)
    public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
                              SpringClientFactory clientFactory) {
        OkHttpClient delegate;
        if (this.okHttpClient != null) {
            delegate = new OkHttpClient(this.okHttpClient);
        } else {
            delegate = new OkHttpClient();
        }
        return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
    }
}

OK,我们看到在FeignRibbonClientAutoConfiguration类里面,主动使用@Import导入了OkHttpFeignLoadBalancedConfiguration配置类,配置类里面加载了OkHttpClient并委托给LoadBalancerFeignClient,供之后Feign的http调用时使用。

四、OkHttpClient参数的制定

@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignConfig {
  @Bean
  public OkHttpClient okHttpClient() {
    OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
    clientBuilder.readTimeout(10, TimeUnit.SECONDS)
        .writeTimeout(10, TimeUnit.SECONDS)
        .connectTimeout(5, TimeUnit.SECONDS)
        .retryOnConnectionFailure(true)
        .connectionPool(new ConnectionPool(255, 5, TimeUnit.MINUTES));
        //.addInterceptor(okHttpInterceptor);
    return clientBuilder.build();
  }
}

4.1 maxIdleConnections

maxIdleConnections 连接池大小,指单个okhttpclient实例所有连接的连接池。

如果设置的过大?

maxIdleConnections 设置得过大可能会占用过多的系统资源,导致系统性能下降。因为连接池中的每个空闲连接都需要占据一定的内存,如果连接池中的连接数量过多,就会占用过多的内存资源。此外,连接池中的连接也会占用操作系统资源,例如文件句柄、线程等。

另外,将 maxIdleConnections 设置得过大也可能会影响网络请求的响应速度。因为连接池中的连接数量越多,每个连接就会得到更少的使用机会,导致连接空闲时间变长,从而增加网络请求的响应时间。

因此,应该根据应用的实际情况合理设置 maxIdleConnections 的值,以平衡资源利用和网络请求响应速度。

如果设置的过小?

maxIdleConnections 设置得过小可能会导致连接池中的连接不足,从而影响网络请求的响应速度。因为当连接数不足时,新的请求需要等待现有的连接释放,才能够得到响应。如果请求量很大,等待连接释放的时间就会变长,从而导致网络请求的响应时间变长。

此外,将 maxIdleConnections 设置得过小也可能会导致连接频繁地被创建和关闭,从而降低连接的重用率,从而增加了系统负担和网络请求的响应时间。

因此,应该根据应用的实际情况合理设置 maxIdleConnections 的值,以确保连接池中的连接数量能够满足并发请求的需求,同时避免连接数量过多导致资源浪费。

到底设置多少合适?

确定最优值需要考虑以下几个因素:

  1. 并发请求的数量。如果同时有很多请求,那么连接池中就需要有足够多的空闲连接,以便快速响应请求。
  2. 服务器的响应速度。如果服务器响应速度很快,那么连接池中的连接就会很快被释放,可以减少连接池中的空闲连接数。
  3. 应用的网络环境。如果网络延迟较高,那么连接池中的连接可能需要等待很长时间才能收到响应,因此需要更多的空闲连接。

一般来说,可以根据应用的实际情况进行调整。可以尝试不同的值,观察连接池中的空闲连接数和请求的响应时间,选择一个能够平衡资源利用和响应速度的值作为最终的配置。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352

推荐阅读更多精彩内容