自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Feign如何設(shè)置超時(shí)時(shí)間,不同情況下還真不一樣

開(kāi)發(fā) 前端
重試次數(shù)我們前面也提到了,雖然一般我們不設(shè)置,但是為了嚴(yán)謹(jǐn)還是得加上,因?yàn)橐淮蜨ttp接口的執(zhí)行時(shí)間肯定跟重試次數(shù)有關(guān),重試次數(shù)越多,時(shí)間就越長(zhǎng)。

大家好,我是三友~~

今天來(lái)聊一聊前段時(shí)間看到的一個(gè)面試題,也是在實(shí)際項(xiàng)目中需要考慮的一個(gè)問(wèn)題,F(xiàn)eign的超時(shí)時(shí)間如何設(shè)置?

Feign的超時(shí)時(shí)間設(shè)置方式并不固定,它取決于Feign在項(xiàng)目中是如何使用的,不同的使用方式,超時(shí)時(shí)間設(shè)置方式也不大相同,甚至還可能有坑。

前置知識(shí)

由于文章會(huì)涉及到Feign的底層知識(shí),如果不懂點(diǎn)Feign的基本概念的話,后面就看不下去了

所以為了方便不了解Feign的小伙伴也能夠讀得懂文章,這里我就簡(jiǎn)單地說(shuō)說(shuō)Feign的原理,點(diǎn)到為止,雖然不深入,但足夠應(yīng)付這篇文章了

Feign的作用

在項(xiàng)目中,我們經(jīng)常需要調(diào)用第三方提供的Http接口,此時(shí)我們就可以使用一些Http框架來(lái)實(shí)現(xiàn),比如HttpClient

public class HttpClientDemo {

    public static void main(String[] args) throws Exception {
        //創(chuàng)建一個(gè)HttpClient
        HttpClient httpClient = HttpClientBuilder.create().build();

        //構(gòu)建一個(gè)get請(qǐng)求
        HttpGet httpGet = new HttpGet("http://192.168.100.1:8080/order/1");

        //發(fā)送請(qǐng)求,獲取響應(yīng)
        HttpResponse httpResponse = httpClient.execute(httpGet);
        HttpEntity httpEntity = httpResponse.getEntity();

        //讀出響應(yīng)值
        String response = EntityUtils.toString(httpEntity);

        System.out.println("Response: " + response);
    }

}

如果項(xiàng)目中只有一兩個(gè)這種第三方接口這樣寫(xiě)還行,但是一旦這種三方接口過(guò)多的話,每次都得這樣組裝參數(shù),發(fā)送請(qǐng)求,寫(xiě)一堆同樣的代碼,就顯然很麻煩了。

所以為了簡(jiǎn)化發(fā)送Http請(qǐng)求的開(kāi)發(fā),減少重復(fù)代碼,F(xiàn)eign就出現(xiàn)了。

Feign是一個(gè)聲明式的Http框架

當(dāng)你需要調(diào)用Http接口時(shí),你需要聲明一個(gè)接口,加一些注解就可以了

而像組裝參數(shù)、發(fā)送Http請(qǐng)求等重復(fù)性的工作都交給Feign來(lái)完成。

Feign的原理

雖然有了接口,但是僅僅有接口是不夠的,因?yàn)榻涌谟植荒軇?chuàng)建對(duì)象,我們得需要對(duì)象。

Feign為了方便我們?yōu)榻涌趧?chuàng)建對(duì)象,提供的Feign.Builder這個(gè)內(nèi)部類

圖片圖片

這個(gè)類的作用就是解析接口的上的注解,為接口生成一個(gè)動(dòng)態(tài)代理對(duì)象,后面通過(guò)這個(gè)代理對(duì)象就可以發(fā)送請(qǐng)求了。

這個(gè)內(nèi)部類有很多屬性,這些屬性都是Feign的核心組件。

在這些核心的組件中有一個(gè)叫Client的,上圖中我圈出來(lái)了。

圖片圖片

這個(gè)Client類劃個(gè)重點(diǎn),非常非常重要,本文討論的東西跟他有密切關(guān)系。

它只有一個(gè)方法Response execute(Request request, Options options)

方法的第一個(gè)參數(shù)Request就是封裝了http請(qǐng)求的url、請(qǐng)求方法,請(qǐng)求頭、請(qǐng)求體之類的參數(shù)

圖片圖片

第二個(gè)參數(shù)Options就是本文的主題,封裝了超時(shí)時(shí)間。

圖片圖片

返回值Response就是封裝了一些響應(yīng)碼status、響應(yīng)頭之類的

圖片圖片

所以通過(guò)方法的參數(shù)和返回值也可以猜出來(lái),這個(gè)Client作用是用來(lái)組裝Http請(qǐng)求參數(shù),發(fā)送Http請(qǐng)求的

并且http請(qǐng)求超時(shí)時(shí)間是根據(jù)傳給Client的Options參數(shù)來(lái)決定的

圖片圖片

如果想更深一步了解Feign原理,可在公眾號(hào)菜單欄springcloud分類中查看

Feign單獨(dú)使用時(shí)超時(shí)時(shí)間設(shè)置

Feign本身就是一個(gè)http客戶端,可獨(dú)立使用,F(xiàn)eign提供了兩種超時(shí)時(shí)間設(shè)置方式

1、通過(guò)Feign.Builder設(shè)置

前面提到,F(xiàn)eign.Builder的作用是為接口的動(dòng)態(tài)代理對(duì)象的

Feign.Builder里面有很多屬性,其中就有關(guān)于超時(shí)時(shí)間的屬性O(shè)ptions

圖片圖片

如果你不設(shè)置,那么超時(shí)時(shí)間就是默認(rèn)的

圖片圖片

默認(rèn)的就是連接超時(shí)10s,讀超時(shí)60s

所以可以通過(guò)設(shè)置Feign.Builder中的options來(lái)設(shè)置超時(shí)時(shí)間

來(lái)個(gè)demo

環(huán)境準(zhǔn)備,就是一個(gè)簡(jiǎn)單的SpringBoot項(xiàng)目,引入一個(gè)Feign的依賴

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
        <version>2.2.5.RELEASE</version>
    </dependency>
</dependencies>

聲明接口 + 注解

public interface UserApi {

    @RequestLine("GET /user/{userId}")
    User queryUser(@Param("userId") Integer userId);

}

這里演示的是Feign原生的使用方式,脫離于SpringCloud環(huán)境,所以Spring的那些@GetMappring就不支持了,改用Feign本身提供的注解

測(cè)試代碼

public class FeignDemo {

    public static void main(String[] args) {
        UserApi client = Feign.builder()
                //設(shè)置連接和讀超時(shí)間都是5s
                .options(new Request.Options(5, TimeUnit.SECONDS, 5, TimeUnit.SECONDS, true))
                .target(UserApi.class, "http://localhost:8088");

        User user = client.queryUser(123);
    }

}

這里面的請(qǐng)求路徑都是不存在的,因?yàn)槲覀冎魂P(guān)心傳給Client的Options參數(shù)值

Client在我們不設(shè)置的時(shí)候,就用默認(rèn)的實(shí)現(xiàn)Client.Default

圖片圖片

斷點(diǎn)打到execute方法的實(shí)現(xiàn),運(yùn)行,走起

圖片圖片

結(jié)果就是我們?cè)O(shè)置的5s

2、在接口方法參數(shù)設(shè)置

除了在通過(guò)Feign.Builder時(shí)設(shè)置之外,F(xiàn)eign還支持在接口的方法參數(shù)上設(shè)置

此時(shí)你只需要在接口的方法上加一個(gè)Options類型的參數(shù)

@RequestLine("GET /user/{userId}")
User queryUser(@Param("userId") Integer userId, Request.Options options);

這樣在傳參數(shù)時(shí)就可以設(shè)置超時(shí)時(shí)間了

User user = client.queryUser(123, new Request.Options(3, TimeUnit.SECONDS, 3, TimeUnit.SECONDS, true));

同樣地,debug就可以看見(jiàn)我們?cè)O(shè)置的3s了

圖片圖片

這兩種設(shè)置超時(shí)時(shí)間的主要區(qū)別就是方法參數(shù)設(shè)置超時(shí)時(shí)間的優(yōu)先級(jí)高于Feign.Builder設(shè)置的超時(shí)時(shí)間

用一張圖來(lái)總結(jié)一下上面的關(guān)系

圖片圖片

所以,如果你單獨(dú)使用Feign的時(shí)候,你就可以通過(guò)如上的兩種方式來(lái)設(shè)置超時(shí)時(shí)間。

SpringCloud下Feign單獨(dú)使用超時(shí)時(shí)間設(shè)置

在SpringCloud環(huán)境下,只是對(duì)Feign進(jìn)行了一層包裝,所以即使沒(méi)有Ribbon和注冊(cè)中心,F(xiàn)eign也是可以單獨(dú)使用的,但是用法有點(diǎn)變化

  • 注解都換成SpringMVC的注解
  • 接口上需要加@FeignClient注解
  • 用@EnableFeignClients掃描這些接口

不過(guò),默認(rèn)情況下Feign還是需要結(jié)合Ribbon來(lái)使用的

如果你只想單獨(dú)使用Feign,那么就設(shè)置一下@FeignClient注解的url屬性,指定請(qǐng)求的地址和端口就可以了

圖片圖片

所以,既然只是包裝,前面提到的兩種方式設(shè)置超時(shí)時(shí)間當(dāng)然可以繼續(xù)使用:

  • 通過(guò)Feign.Builder
  • 通過(guò)接口的方法參數(shù)

方法參數(shù)設(shè)置形式跟前面提到的一模一樣,但是通過(guò)Feign.Builder來(lái)設(shè)置卻不太一樣

由于SpringCloud會(huì)自己創(chuàng)建Feign.Builder,不需要我們創(chuàng)建,所以在設(shè)置Options時(shí),Spring提供了兩種快捷方式來(lái)設(shè)置

不過(guò)最終還是設(shè)置到Feign.Builder中

1、聲明一個(gè)Options Bean

Spring在構(gòu)建Feign.Builder的時(shí),會(huì)從容器中查找Options這個(gè)Bean,然后設(shè)置到Feign.Builder中

@Configuration
public class FeignConfiguration {

    @Bean
    public Request.Options options() {
        return new Request.Options(8, TimeUnit.SECONDS, 8, TimeUnit.SECONDS, true);
    }

}

此時(shí)debug就可以看到設(shè)置到Feign.Builder的代碼

圖片圖片

這段代碼在FeignClientFactoryBean中的configureUsingConfiguration方法中

2、配置文件中設(shè)置

除了聲明Bean之外,Spring還提供了通過(guò)配置文件的方式配置,如下:

feign:
  client:
    config:
      default:
        connectTimeout: 10000
        readTimeout: 10000

同樣地,debug就可以看見(jiàn)

圖片圖片

這段代碼在FeignClientFactoryBean中的configureUsingConfiguration方法中

聲明Bean和配置文件都可以設(shè)置,那么同時(shí)設(shè)置哪種優(yōu)先級(jí)高呢?

如無(wú)特殊配置,遵守SpringBoot本身的配置規(guī)定

約定 > 配置 > 編碼

所以基于這個(gè)規(guī)定,配置文件的配置優(yōu)先級(jí)大于手動(dòng)聲明Bean的優(yōu)先級(jí)。

到這,我們又學(xué)到了兩種Spring為了方便我們?cè)O(shè)置Feign.Builder提供的配置方式:

  • 聲明Options Bean
  • 配置文件

把他們倆加到前面畫(huà)的圖中

圖片圖片

所以,如果你使用了SpringCloud提供的方式來(lái)使用Feign,那么就可以通過(guò)聲明OptionsBean和配置文件的方式更加方便地來(lái)設(shè)置超時(shí)時(shí)間

最終其實(shí)還是通過(guò)Feign.Builder來(lái)設(shè)置的

SpringCloud下通過(guò)Ribbon來(lái)設(shè)置

當(dāng)Feign配合Ribbon使用時(shí),除了上面兩種方式之外,還可以通過(guò)Ribbon來(lái)設(shè)置超時(shí)時(shí)間。

但是這里我不知道你會(huì)不會(huì)好奇

Ribbon不是負(fù)載均衡組件,怎么可以設(shè)置超時(shí)時(shí)間?

其實(shí)這跟Ribbon的定位有關(guān),除了負(fù)載均衡組件之外,Ribbon也干發(fā)送Http請(qǐng)求的事,也就是不配合Feign,他照樣可以發(fā)送http請(qǐng)求。

來(lái)個(gè)簡(jiǎn)單demo

圖片圖片

解釋一下上面的代碼意思

  • 第一步,設(shè)置user服務(wù)的兩個(gè)服務(wù)實(shí)例地址
  • 第二步,獲取user服務(wù)對(duì)應(yīng)的RestClient,這RestClient就可以用來(lái)發(fā)送http請(qǐng)求
  • 第三步,構(gòu)建一個(gè)http請(qǐng)求
  • 第四步,就是發(fā)送http請(qǐng)求,以負(fù)載均衡的方式

這樣,此時(shí)就會(huì)從兩個(gè)服務(wù)實(shí)例中根據(jù)負(fù)載均衡選取一個(gè)服務(wù)地址發(fā)送http請(qǐng)求,

Ribbon既然可以發(fā)送Http請(qǐng)求,那么自然而然就可以設(shè)置超時(shí)時(shí)間

Feign在整合Ribbon的時(shí)候,為了統(tǒng)一配置,就默認(rèn)將自己的超時(shí)時(shí)間交由Ribbon管理

所以,在默認(rèn)情況下,F(xiàn)eign的超時(shí)時(shí)間可以由Ribbon配置

而Ribbon默認(rèn)連接和讀超時(shí)時(shí)間只有1s,所以在默認(rèn)情況下,F(xiàn)eign的超時(shí)時(shí)間只有1s。

圖片圖片

IClientConfig是Ribbon的配置類,Ribbon所有的配置都可以從IClientConfig中獲取。

所以,在默認(rèn)情況下,很容易就發(fā)生超時(shí),不過(guò)我們可以通過(guò)配置文件修改即可

ribbon:
  ConnectTimeout: 5000
  ReadTimeout: 5000

你知道你發(fā)現(xiàn)沒(méi),上面說(shuō)通過(guò)Ribbon設(shè)置Feign的超時(shí)時(shí)間,一直提到前面一直提到這個(gè)詞

默認(rèn)

什么情況下叫默認(rèn)呢?

所謂的默認(rèn),就是當(dāng)你不主動(dòng)設(shè)置Feign的超時(shí)時(shí)間的時(shí)候,就是默認(rèn)。

換句話說(shuō),一旦你通過(guò)上面說(shuō)的那些配置方式設(shè)置Feign的超時(shí)時(shí)間,就不是默認(rèn)了

此時(shí)通過(guò)Ribbon設(shè)置的超時(shí)時(shí)間就不會(huì)生效了

Feign是如何在默認(rèn)情況下將超時(shí)時(shí)間交給Ribbon管理的?

要想回答這個(gè)問(wèn)題,就得先搬出前面反復(fù)提到的Client接口了。

在SpringCloud的環(huán)境下,有一個(gè)Client的實(shí)現(xiàn),叫LoadBalancerFeignClient

圖片圖片

通過(guò)名字就可以看出,帶有負(fù)載均衡的Client實(shí)現(xiàn),負(fù)載均衡的實(shí)現(xiàn)肯定是交給Ribbon來(lái)實(shí)現(xiàn)的

所以當(dāng)Feign配合Ribbon時(shí)用的就是這個(gè)Client實(shí)現(xiàn)

既然實(shí)現(xiàn)了Client接口,那就看看execute方法的實(shí)現(xiàn)邏輯

圖片圖片

圖中g(shù)etClientConfig方法就是判斷使用Feign或者Ribbon配置的核心邏輯

核心的判斷邏輯就是這一行

options == DEFAULT_OPTIONS

DEFAULT_OPTIONS就是一個(gè)超時(shí)時(shí)間的常量

圖片圖片

當(dāng)上述判斷條件成立時(shí),就會(huì)通過(guò)this.clientFactory.getClientConfig(clientName)獲取到Ribbon配置

由于這是Ribbon的邏輯,這里就不深扒了,知道是這個(gè)意思就行

當(dāng)條件不成立時(shí),用Options構(gòu)建一個(gè)FeignOptionsClientConfig

圖片圖片

FeignOptionsClientConfig就是簡(jiǎn)單地將Options配置讀出來(lái),設(shè)置到父類DefaultClientConfigImpl超時(shí)時(shí)間配置上

DefaultClientConfigImpl就算你不知道是什么也無(wú)所謂,你能看出的一件事就是,超時(shí)時(shí)間用的是傳遞給Client的Options參數(shù)

所以,綜上,我們的問(wèn)題就變得非常easy了,那就是什么時(shí)候

options == DEFAULT_OPTIONS

只有當(dāng)這個(gè)條件成立時(shí),才使用Ribbon的配置。

這里我們先來(lái)捋一捋前面提到的東西

前面我們反復(fù)提到,Client的Options最終只來(lái)自于兩種配置

  • Feign.Builder
  • 方法參數(shù)

所以DEFAULT_OPTIONS這個(gè)Options一定是通過(guò)上面兩種方法中的其中一種設(shè)置的

而方法參數(shù)是不可能設(shè)置的成DEFAULT_OPTIONS

因?yàn)檫@是我們控制的,只要我們參數(shù)不傳DEFAULT_OPTIONS,那么永遠(yuǎn)都不可能是DEFAULT_OPTIONS。

此時(shí)只剩下一種情況,那就是Spring在構(gòu)建在Feign.Builder的時(shí)候,設(shè)置成DEFAULT_OPTIONS。

通過(guò)查找DEFAULT_OPTIONS的使用,我們可以追蹤到這么一段代碼

圖片圖片

這不就是前面提到的通過(guò)聲明Bean的方式來(lái)設(shè)置超時(shí)時(shí)間

不同的是它加了@ConditionalOnMissingBean,這個(gè)注解就是說(shuō),一旦我們自己沒(méi)有聲明Options,就用他這個(gè)Options

到這終于真像大白了。

我們不設(shè)置超時(shí)時(shí)間,Spring就會(huì)給Feign.Builder加一個(gè)DEFAULT_OPTIONS這個(gè)Options

在執(zhí)行的時(shí)候,發(fā)現(xiàn)是DEFAULT_OPTIONS,說(shuō)明我們沒(méi)有主動(dòng)設(shè)置過(guò)超是時(shí)間,就會(huì)使用Ribbon的超時(shí)時(shí)間。

為了方便理清上面的邏輯,這里整一張圖

圖片圖片

雖然Feign可以使用Ribbon的超時(shí)時(shí)間,但是Ribbon的配置的優(yōu)先級(jí)是最最低的

方法參數(shù) > Feign配置文件 > 聲明Options > Ribbon配置

Feign or Ribbon配置用哪個(gè)好?

其實(shí)我個(gè)人更傾向于使用Ribbon的配置方式。

因?yàn)镽ibbon除了可以設(shè)置超時(shí)時(shí)間之外,還可以配置重試機(jī)制、負(fù)載均衡等其它的配置

為了簡(jiǎn)化和統(tǒng)一管理配置,使用Ribbon來(lái)配置超時(shí)時(shí)間。

可能你會(huì)有疑問(wèn),F(xiàn)eign也支持重試機(jī)制,為什么不選擇Feign?

這是因?yàn)镕eign重試機(jī)制沒(méi)有Ribbon的好

Ribbon重試的時(shí)候會(huì)換一個(gè)服務(wù)實(shí)例來(lái)重試,因?yàn)樵瓉?lái)出錯(cuò)的可能不可用

而Feign并不會(huì)換一個(gè)服務(wù)實(shí)例重試,他并不知道上一次使用的是哪個(gè)服務(wù)實(shí)例,這就導(dǎo)致可能會(huì)出現(xiàn)在一個(gè)不可用的服務(wù)實(shí)例上多次重試的情況。

引入Hystrix時(shí)超時(shí)時(shí)間設(shè)置

如果你之前的確沒(méi)有研究過(guò)關(guān)于Feign超時(shí)時(shí)間的配置關(guān)系,那么此時(shí)你應(yīng)該有所收獲了。

但是這就結(jié)束了么?

不,事情沒(méi)那么簡(jiǎn)單。

如果你的項(xiàng)目中使用了Hystrix,那么就得小心前面說(shuō)的那些配置了。

由于Hystrix跟Feign畢竟是一家人,所以當(dāng)引入Hystrix時(shí),F(xiàn)eign就跟之前不一樣了。

Hystrix會(huì)去干一件事,那就是給每個(gè)Feign的http接口保護(hù)起來(lái),畢竟Hystrix就是干保鏢這個(gè)事的。

但是這沒(méi)保護(hù)還好,一保護(hù)問(wèn)題就不自覺(jué)地出現(xiàn)了。

Hystrix在保護(hù)的時(shí)候,一旦發(fā)現(xiàn)被保護(hù)的接口執(zhí)行的時(shí)間超過(guò)Hystrix設(shè)置的最大時(shí)間,就直接進(jìn)行降級(jí)操作。

怎么降級(jí)的,這里咱不關(guān)心,咱關(guān)心的是這個(gè)Hystrix超時(shí)的最大值是多少。

因?yàn)橐坏┻@個(gè)時(shí)間小于Feign的超時(shí)時(shí)間,那么就會(huì)出現(xiàn)Http接口正在執(zhí)行,也沒(méi)有異常,僅僅是因?yàn)閳?zhí)行時(shí)間長(zhǎng),就被降級(jí)了。

而Hystrix的默認(rèn)的超時(shí)時(shí)間的最大值就只有1s。

圖片圖片

所以就算你Feign超時(shí)時(shí)間設(shè)置的再大,超過(guò)1s就算超時(shí),然后被降級(jí),太坑了。。

所以我們需要修改這個(gè)默認(rèn)的超時(shí)時(shí)間的最大值,具體的配置項(xiàng)如下

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 30000

并且時(shí)間上大致要符合下面這個(gè)原則

Hystrix超時(shí)時(shí)間 >= (連接超時(shí)時(shí)間 + 讀超時(shí)時(shí)間) * 重試次數(shù)

重試次數(shù)我們前面也提到了,雖然一般我們不設(shè)置,但是為了嚴(yán)謹(jǐn)還是得加上,因?yàn)橐淮蜨ttp接口的執(zhí)行時(shí)間肯定跟重試次數(shù)有關(guān),重試次數(shù)越多,時(shí)間就越長(zhǎng)。

而連接超時(shí)時(shí)間 + 讀超時(shí)時(shí)間設(shè)置方式,前面提到很多次,不論是通過(guò)Feign本身設(shè)置還是通過(guò)Ribbon來(lái)設(shè)置,都是可以的

總結(jié)

今天給大家扒了扒在不同使用條件下Feign的超時(shí)時(shí)間設(shè)置,總結(jié)起來(lái)大致如下:

  • 單獨(dú)使用Feign時(shí):通過(guò)Feign.Builder和方法參數(shù)
  • SpringCloud環(huán)境下單獨(dú)使用Feign:方法參數(shù)、配置文件、聲明Options Bean
  • 跟Ribbon配合使用:通過(guò)Ribbon的超時(shí)參數(shù)設(shè)置
  • 跟Hystrix配合使用:修改默認(rèn)的超時(shí)時(shí)間,盡量符合 Hystrix超時(shí)時(shí)間 >= (連接超時(shí)時(shí)間 + 讀超時(shí)時(shí)間) * 重試次數(shù)
責(zé)任編輯:武曉燕 來(lái)源: 三友的java日記
相關(guān)推薦

2024-12-16 08:01:57

2012-03-07 17:24:10

戴爾咨詢

2012-12-20 10:17:32

IT運(yùn)維

2010-07-30 15:32:23

2017-05-25 15:02:46

聯(lián)宇益通SD-WAN

2015-10-19 12:33:01

華三/新IT

2016-05-09 18:40:26

VIP客戶緝拿

2018-05-09 15:42:24

新零售

2021-12-23 15:11:46

Web 3.0元宇宙Metaverse

2009-02-04 15:43:45

敏捷開(kāi)發(fā)PHPFleaPHP

2009-12-01 16:42:27

Gentoo Linu

2011-03-14 16:51:24

2017-11-03 07:57:19

2016-03-24 18:51:40

2018-07-10 11:05:55

Emoji蘋(píng)果Google

2011-02-28 10:38:13

Windows 8

2009-06-12 15:26:02

2009-01-07 09:20:00

2018-04-08 15:46:38

混合云多云云平臺(tái)

2023-03-20 08:19:23

GPT-4OpenAI
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)