AVt天堂网 手机版,亚洲va久久久噜噜噜久久4399,天天综合亚洲色在线精品,亚洲一级Av无码毛片久久精品

當前位置:首頁 > 科技  > 軟件

Feign如何設置超時時間,不同情況下還真不一樣

來源: 責編: 時間:2023-08-14 22:00:37 294觀看
導讀大家好,我是三友~~今天來聊一聊前段時間看到的一個面試題,也是在實際項目中需要考慮的一個問題,Feign的超時時間如何設置?Feign的超時時間設置方式并不固定,它取決于Feign在項目中是如何使用的,不同的使用方式,超時時間設置

大家好,我是三友~~8IA28資訊網——每日最新資訊28at.com

今天來聊一聊前段時間看到的一個面試題,也是在實際項目中需要考慮的一個問題,Feign的超時時間如何設置?8IA28資訊網——每日最新資訊28at.com

Feign的超時時間設置方式并不固定,它取決于Feign在項目中是如何使用的,不同的使用方式,超時時間設置方式也不大相同,甚至還可能有坑。8IA28資訊網——每日最新資訊28at.com

前置知識

由于文章會涉及到Feign的底層知識,如果不懂點Feign的基本概念的話,后面就看不下去了8IA28資訊網——每日最新資訊28at.com

所以為了方便不了解Feign的小伙伴也能夠讀得懂文章,這里我就簡單地說說Feign的原理,點到為止,雖然不深入,但足夠應付這篇文章了8IA28資訊網——每日最新資訊28at.com

Feign的作用

在項目中,我們經常需要調用第三方提供的Http接口,此時我們就可以使用一些Http框架來實現,比如HttpClient8IA28資訊網——每日最新資訊28at.com

public class HttpClientDemo {    public static void main(String[] args) throws Exception {        //創建一個HttpClient        HttpClient httpClient = HttpClientBuilder.create().build();        //構建一個get請求        HttpGet httpGet = new HttpGet("http://192.168.100.1:8080/order/1");        //發送請求,獲取響應        HttpResponse httpResponse = httpClient.execute(httpGet);        HttpEntity httpEntity = httpResponse.getEntity();        //讀出響應值        String response = EntityUtils.toString(httpEntity);        System.out.println("Response: " + response);    }}

如果項目中只有一兩個這種第三方接口這樣寫還行,但是一旦這種三方接口過多的話,每次都得這樣組裝參數,發送請求,寫一堆同樣的代碼,就顯然很麻煩了。8IA28資訊網——每日最新資訊28at.com

所以為了簡化發送Http請求的開發,減少重復代碼,Feign就出現了。8IA28資訊網——每日最新資訊28at.com

Feign是一個聲明式的Http框架8IA28資訊網——每日最新資訊28at.com

當你需要調用Http接口時,你需要聲明一個接口,加一些注解就可以了8IA28資訊網——每日最新資訊28at.com

而像組裝參數、發送Http請求等重復性的工作都交給Feign來完成。8IA28資訊網——每日最新資訊28at.com

Feign的原理

雖然有了接口,但是僅僅有接口是不夠的,因為接口又不能創建對象,我們得需要對象。8IA28資訊網——每日最新資訊28at.com

Feign為了方便我們為接口創建對象,提供的Feign.Builder這個內部類8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

這個類的作用就是解析接口的上的注解,為接口生成一個動態代理對象,后面通過這個代理對象就可以發送請求了。8IA28資訊網——每日最新資訊28at.com

這個內部類有很多屬性,這些屬性都是Feign的核心組件。8IA28資訊網——每日最新資訊28at.com

在這些核心的組件中有一個叫Client的,上圖中我圈出來了。8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

這個Client類劃個重點,非常非常重要,本文討論的東西跟他有密切關系。8IA28資訊網——每日最新資訊28at.com

它只有一個方法Response execute(Request request, Options options)8IA28資訊網——每日最新資訊28at.com

方法的第一個參數Request就是封裝了http請求的url、請求方法,請求頭、請求體之類的參數8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

第二個參數Options就是本文的主題,封裝了超時時間。8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

返回值Response就是封裝了一些響應碼status、響應頭之類的8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

所以通過方法的參數和返回值也可以猜出來,這個Client作用是用來組裝Http請求參數,發送Http請求的8IA28資訊網——每日最新資訊28at.com

并且http請求超時時間是根據傳給Client的Options參數來決定的8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

如果想更深一步了解Feign原理,可在公眾號菜單欄springcloud分類中查看8IA28資訊網——每日最新資訊28at.com

Feign單獨使用時超時時間設置

Feign本身就是一個http客戶端,可獨立使用,Feign提供了兩種超時時間設置方式8IA28資訊網——每日最新資訊28at.com

1、通過Feign.Builder設置

前面提到,Feign.Builder的作用是為接口的動態代理對象的8IA28資訊網——每日最新資訊28at.com

Feign.Builder里面有很多屬性,其中就有關于超時時間的屬性Options8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

如果你不設置,那么超時時間就是默認的8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

默認的就是連接超時10s,讀超時60s8IA28資訊網——每日最新資訊28at.com

所以可以通過設置Feign.Builder中的options來設置超時時間8IA28資訊網——每日最新資訊28at.com

來個demo8IA28資訊網——每日最新資訊28at.com

環境準備,就是一個簡單的SpringBoot項目,引入一個Feign的依賴8IA28資訊網——每日最新資訊28at.com

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

聲明接口 + 注解8IA28資訊網——每日最新資訊28at.com

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

這里演示的是Feign原生的使用方式,脫離于SpringCloud環境,所以Spring的那些@GetMappring就不支持了,改用Feign本身提供的注解8IA28資訊網——每日最新資訊28at.com

測試代碼8IA28資訊網——每日最新資訊28at.com

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

這里面的請求路徑都是不存在的,因為我們只關心傳給Client的Options參數值8IA28資訊網——每日最新資訊28at.com

Client在我們不設置的時候,就用默認的實現Client.Default8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

斷點打到execute方法的實現,運行,走起8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

結果就是我們設置的5s8IA28資訊網——每日最新資訊28at.com

2、在接口方法參數設置

除了在通過Feign.Builder時設置之外,Feign還支持在接口的方法參數上設置8IA28資訊網——每日最新資訊28at.com

此時你只需要在接口的方法上加一個Options類型的參數8IA28資訊網——每日最新資訊28at.com

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

這樣在傳參數時就可以設置超時時間了8IA28資訊網——每日最新資訊28at.com

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

同樣地,debug就可以看見我們設置的3s了8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

這兩種設置超時時間的主要區別就是方法參數設置超時時間的優先級高于Feign.Builder設置的超時時間8IA28資訊網——每日最新資訊28at.com

用一張圖來總結一下上面的關系8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

所以,如果你單獨使用Feign的時候,你就可以通過如上的兩種方式來設置超時時間。8IA28資訊網——每日最新資訊28at.com

SpringCloud下Feign單獨使用超時時間設置

在SpringCloud環境下,只是對Feign進行了一層包裝,所以即使沒有Ribbon和注冊中心,Feign也是可以單獨使用的,但是用法有點變化8IA28資訊網——每日最新資訊28at.com

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

不過,默認情況下Feign還是需要結合Ribbon來使用的8IA28資訊網——每日最新資訊28at.com

如果你只想單獨使用Feign,那么就設置一下@FeignClient注解的url屬性,指定請求的地址和端口就可以了8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

所以,既然只是包裝,前面提到的兩種方式設置超時時間當然可以繼續使用:8IA28資訊網——每日最新資訊28at.com

  • 通過Feign.Builder
  • 通過接口的方法參數

方法參數設置形式跟前面提到的一模一樣,但是通過Feign.Builder來設置卻不太一樣8IA28資訊網——每日最新資訊28at.com

由于SpringCloud會自己創建Feign.Builder,不需要我們創建,所以在設置Options時,Spring提供了兩種快捷方式來設置8IA28資訊網——每日最新資訊28at.com

不過最終還是設置到Feign.Builder中8IA28資訊網——每日最新資訊28at.com

1、聲明一個Options Bean

Spring在構建Feign.Builder的時,會從容器中查找Options這個Bean,然后設置到Feign.Builder中8IA28資訊網——每日最新資訊28at.com

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

此時debug就可以看到設置到Feign.Builder的代碼8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

這段代碼在FeignClientFactoryBean中的configureUsingConfiguration方法中8IA28資訊網——每日最新資訊28at.com

2、配置文件中設置

除了聲明Bean之外,Spring還提供了通過配置文件的方式配置,如下:8IA28資訊網——每日最新資訊28at.com

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

同樣地,debug就可以看見8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

這段代碼在FeignClientFactoryBean中的configureUsingConfiguration方法中8IA28資訊網——每日最新資訊28at.com

聲明Bean和配置文件都可以設置,那么同時設置哪種優先級高呢?8IA28資訊網——每日最新資訊28at.com

如無特殊配置,遵守SpringBoot本身的配置規定8IA28資訊網——每日最新資訊28at.com

約定 > 配置 > 編碼8IA28資訊網——每日最新資訊28at.com

所以基于這個規定,配置文件的配置優先級大于手動聲明Bean的優先級。8IA28資訊網——每日最新資訊28at.com

到這,我們又學到了兩種Spring為了方便我們設置Feign.Builder提供的配置方式:8IA28資訊網——每日最新資訊28at.com

  • 聲明Options Bean
  • 配置文件

把他們倆加到前面畫的圖中8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

所以,如果你使用了SpringCloud提供的方式來使用Feign,那么就可以通過聲明OptionsBean和配置文件的方式更加方便地來設置超時時間8IA28資訊網——每日最新資訊28at.com

最終其實還是通過Feign.Builder來設置的8IA28資訊網——每日最新資訊28at.com

SpringCloud下通過Ribbon來設置

當Feign配合Ribbon使用時,除了上面兩種方式之外,還可以通過Ribbon來設置超時時間。8IA28資訊網——每日最新資訊28at.com

但是這里我不知道你會不會好奇8IA28資訊網——每日最新資訊28at.com

Ribbon不是負載均衡組件,怎么可以設置超時時間?8IA28資訊網——每日最新資訊28at.com

其實這跟Ribbon的定位有關,除了負載均衡組件之外,Ribbon也干發送Http請求的事,也就是不配合Feign,他照樣可以發送http請求。8IA28資訊網——每日最新資訊28at.com

來個簡單demo8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

解釋一下上面的代碼意思8IA28資訊網——每日最新資訊28at.com

  • 第一步,設置user服務的兩個服務實例地址
  • 第二步,獲取user服務對應的RestClient,這RestClient就可以用來發送http請求
  • 第三步,構建一個http請求
  • 第四步,就是發送http請求,以負載均衡的方式

這樣,此時就會從兩個服務實例中根據負載均衡選取一個服務地址發送http請求,8IA28資訊網——每日最新資訊28at.com

Ribbon既然可以發送Http請求,那么自然而然就可以設置超時時間8IA28資訊網——每日最新資訊28at.com

Feign在整合Ribbon的時候,為了統一配置,就默認將自己的超時時間交由Ribbon管理8IA28資訊網——每日最新資訊28at.com

所以,在默認情況下,Feign的超時時間可以由Ribbon配置8IA28資訊網——每日最新資訊28at.com

而Ribbon默認連接和讀超時時間只有1s,所以在默認情況下,Feign的超時時間只有1s。8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

IClientConfig是Ribbon的配置類,Ribbon所有的配置都可以從IClientConfig中獲取。8IA28資訊網——每日最新資訊28at.com

所以,在默認情況下,很容易就發生超時,不過我們可以通過配置文件修改即可8IA28資訊網——每日最新資訊28at.com

ribbon:  ConnectTimeout: 5000  ReadTimeout: 5000

你知道你發現沒,上面說通過Ribbon設置Feign的超時時間,一直提到前面一直提到這個詞8IA28資訊網——每日最新資訊28at.com

默認8IA28資訊網——每日最新資訊28at.com

什么情況下叫默認呢?8IA28資訊網——每日最新資訊28at.com

所謂的默認,就是當你不主動設置Feign的超時時間的時候,就是默認。8IA28資訊網——每日最新資訊28at.com

換句話說,一旦你通過上面說的那些配置方式設置Feign的超時時間,就不是默認了8IA28資訊網——每日最新資訊28at.com

此時通過Ribbon設置的超時時間就不會生效了8IA28資訊網——每日最新資訊28at.com

Feign是如何在默認情況下將超時時間交給Ribbon管理的?

要想回答這個問題,就得先搬出前面反復提到的Client接口了。8IA28資訊網——每日最新資訊28at.com

在SpringCloud的環境下,有一個Client的實現,叫LoadBalancerFeignClient8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

通過名字就可以看出,帶有負載均衡的Client實現,負載均衡的實現肯定是交給Ribbon來實現的8IA28資訊網——每日最新資訊28at.com

所以當Feign配合Ribbon時用的就是這個Client實現8IA28資訊網——每日最新資訊28at.com

既然實現了Client接口,那就看看execute方法的實現邏輯8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

圖中getClientConfig方法就是判斷使用Feign或者Ribbon配置的核心邏輯8IA28資訊網——每日最新資訊28at.com

核心的判斷邏輯就是這一行8IA28資訊網——每日最新資訊28at.com

options == DEFAULT_OPTIONS8IA28資訊網——每日最新資訊28at.com

DEFAULT_OPTIONS就是一個超時時間的常量8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

當上述判斷條件成立時,就會通過this.clientFactory.getClientConfig(clientName)獲取到Ribbon配置8IA28資訊網——每日最新資訊28at.com

由于這是Ribbon的邏輯,這里就不深扒了,知道是這個意思就行8IA28資訊網——每日最新資訊28at.com

當條件不成立時,用Options構建一個FeignOptionsClientConfig8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

FeignOptionsClientConfig就是簡單地將Options配置讀出來,設置到父類DefaultClientConfigImpl超時時間配置上8IA28資訊網——每日最新資訊28at.com

DefaultClientConfigImpl就算你不知道是什么也無所謂,你能看出的一件事就是,超時時間用的是傳遞給Client的Options參數8IA28資訊網——每日最新資訊28at.com

所以,綜上,我們的問題就變得非常easy了,那就是什么時候8IA28資訊網——每日最新資訊28at.com

options == DEFAULT_OPTIONS8IA28資訊網——每日最新資訊28at.com

只有當這個條件成立時,才使用Ribbon的配置。8IA28資訊網——每日最新資訊28at.com

這里我們先來捋一捋前面提到的東西8IA28資訊網——每日最新資訊28at.com

前面我們反復提到,Client的Options最終只來自于兩種配置8IA28資訊網——每日最新資訊28at.com

  • Feign.Builder
  • 方法參數

所以DEFAULT_OPTIONS這個Options一定是通過上面兩種方法中的其中一種設置的8IA28資訊網——每日最新資訊28at.com

而方法參數是不可能設置的成DEFAULT_OPTIONS8IA28資訊網——每日最新資訊28at.com

因為這是我們控制的,只要我們參數不傳DEFAULT_OPTIONS,那么永遠都不可能是DEFAULT_OPTIONS。8IA28資訊網——每日最新資訊28at.com

此時只剩下一種情況,那就是Spring在構建在Feign.Builder的時候,設置成DEFAULT_OPTIONS。8IA28資訊網——每日最新資訊28at.com

通過查找DEFAULT_OPTIONS的使用,我們可以追蹤到這么一段代碼8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

這不就是前面提到的通過聲明Bean的方式來設置超時時間8IA28資訊網——每日最新資訊28at.com

不同的是它加了@ConditionalOnMissingBean,這個注解就是說,一旦我們自己沒有聲明Options,就用他這個Options8IA28資訊網——每日最新資訊28at.com

到這終于真像大白了。8IA28資訊網——每日最新資訊28at.com

我們不設置超時時間,Spring就會給Feign.Builder加一個DEFAULT_OPTIONS這個Options8IA28資訊網——每日最新資訊28at.com

在執行的時候,發現是DEFAULT_OPTIONS,說明我們沒有主動設置過超是時間,就會使用Ribbon的超時時間。8IA28資訊網——每日最新資訊28at.com

為了方便理清上面的邏輯,這里整一張圖8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

雖然Feign可以使用Ribbon的超時時間,但是Ribbon的配置的優先級是最最低的8IA28資訊網——每日最新資訊28at.com

方法參數 > Feign配置文件 > 聲明Options > Ribbon配置8IA28資訊網——每日最新資訊28at.com

Feign or Ribbon配置用哪個好?

其實我個人更傾向于使用Ribbon的配置方式。8IA28資訊網——每日最新資訊28at.com

因為Ribbon除了可以設置超時時間之外,還可以配置重試機制、負載均衡等其它的配置8IA28資訊網——每日最新資訊28at.com

為了簡化和統一管理配置,使用Ribbon來配置超時時間。8IA28資訊網——每日最新資訊28at.com

可能你會有疑問,Feign也支持重試機制,為什么不選擇Feign?8IA28資訊網——每日最新資訊28at.com

這是因為Feign重試機制沒有Ribbon的好8IA28資訊網——每日最新資訊28at.com

Ribbon重試的時候會換一個服務實例來重試,因為原來出錯的可能不可用8IA28資訊網——每日最新資訊28at.com

而Feign并不會換一個服務實例重試,他并不知道上一次使用的是哪個服務實例,這就導致可能會出現在一個不可用的服務實例上多次重試的情況。8IA28資訊網——每日最新資訊28at.com

引入Hystrix時超時時間設置

如果你之前的確沒有研究過關于Feign超時時間的配置關系,那么此時你應該有所收獲了。8IA28資訊網——每日最新資訊28at.com

但是這就結束了么?8IA28資訊網——每日最新資訊28at.com

不,事情沒那么簡單。8IA28資訊網——每日最新資訊28at.com

如果你的項目中使用了Hystrix,那么就得小心前面說的那些配置了。8IA28資訊網——每日最新資訊28at.com

由于Hystrix跟Feign畢竟是一家人,所以當引入Hystrix時,Feign就跟之前不一樣了。8IA28資訊網——每日最新資訊28at.com

Hystrix會去干一件事,那就是給每個Feign的http接口保護起來,畢竟Hystrix就是干保鏢這個事的。8IA28資訊網——每日最新資訊28at.com

但是這沒保護還好,一保護問題就不自覺地出現了。8IA28資訊網——每日最新資訊28at.com

Hystrix在保護的時候,一旦發現被保護的接口執行的時間超過Hystrix設置的最大時間,就直接進行降級操作。8IA28資訊網——每日最新資訊28at.com

怎么降級的,這里咱不關心,咱關心的是這個Hystrix超時的最大值是多少。8IA28資訊網——每日最新資訊28at.com

因為一旦這個時間小于Feign的超時時間,那么就會出現Http接口正在執行,也沒有異常,僅僅是因為執行時間長,就被降級了。8IA28資訊網——每日最新資訊28at.com

而Hystrix的默認的超時時間的最大值就只有1s。8IA28資訊網——每日最新資訊28at.com

圖片圖片8IA28資訊網——每日最新資訊28at.com

所以就算你Feign超時時間設置的再大,超過1s就算超時,然后被降級,太坑了。。8IA28資訊網——每日最新資訊28at.com

所以我們需要修改這個默認的超時時間的最大值,具體的配置項如下8IA28資訊網——每日最新資訊28at.com

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

并且時間上大致要符合下面這個原則8IA28資訊網——每日最新資訊28at.com

Hystrix超時時間 >= (連接超時時間 + 讀超時時間) * 重試次數8IA28資訊網——每日最新資訊28at.com

重試次數我們前面也提到了,雖然一般我們不設置,但是為了嚴謹還是得加上,因為一次Http接口的執行時間肯定跟重試次數有關,重試次數越多,時間就越長。8IA28資訊網——每日最新資訊28at.com

而連接超時時間 + 讀超時時間設置方式,前面提到很多次,不論是通過Feign本身設置還是通過Ribbon來設置,都是可以的8IA28資訊網——每日最新資訊28at.com

總結

今天給大家扒了扒在不同使用條件下Feign的超時時間設置,總結起來大致如下:8IA28資訊網——每日最新資訊28at.com

  • 單獨使用Feign時:通過Feign.Builder和方法參數
  • SpringCloud環境下單獨使用Feign:方法參數、配置文件、聲明Options Bean
  • 跟Ribbon配合使用:通過Ribbon的超時參數設置
  • 跟Hystrix配合使用:修改默認的超時時間,盡量符合 Hystrix超時時間 >= (連接超時時間 + 讀超時時間) * 重試次數

本文鏈接:http://www.tebozhan.com/showinfo-26-5697-0.htmlFeign如何設置超時時間,不同情況下還真不一樣

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 阿里云2024財年Q1 經調整EBITA利潤3.87億元

下一篇: CSS 如何改變網格布局偶數行的排序?

標簽:
  • 熱門焦點
Top