【SpringCloud技術專題】「原生態Fegin」開啟Fegin之RPC技術的開端,你會使用原生態的Fegin嗎?(下)

李浩宇Alex發表於2021-08-12

前提回顧

【SpringCloud技術專題】「原生態Fegin」開啟Fegin之RPC技術的開端,你會使用原生態的Fegin嗎?(中)

【SpringCloud技術專題】「原生態Fegin」開啟Fegin之RPC技術的開端,你會使用原生態的Fegin嗎?(上)

內容簡介

在專案開發中,除了考慮正常的呼叫之外,負載均衡和故障轉移也是關注的重點,這也是feign + ribbon的優勢所在,基於上面兩篇文章的基礎,接下來我們開展最後一篇原生態fegin結合ribbon服務進行服務遠端呼叫且實現負載均衡機制,也幫助大家學習ribbon奠定基礎。

maven依賴

<dependencies>
    <dependency>
        <groupId>com.netflix.feign</groupId>
        <artifactId>feign-core</artifactId>
        <version>8.18.0</version>
    </dependency>
    <dependency>
        <groupId>com.netflix.feign</groupId>
        <artifactId>feign-jackson</artifactId>
        <version>8.18.0</version>
    </dependency>
    <dependency>
        <groupId>com.netflix.feign</groupId>
        <artifactId>feign-ribbon</artifactId>
        <version>8.18.0</version>
    </dependency>
	<dependency>
    <groupId>com.netflix.archaius</groupId>
    <artifactId>archaius-core</artifactId>
</dependency>

</dependencies>

其中feign-core和feign-ribbon是必須的,如果需要在服務消費端和服務生產端之間進行物件互動,建議使用feign-jackson

配置讀取

import com.netflix.config.ConfigurationManager;
import feign.Feign;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import feign.ribbon.RibbonClient;
public class AppRun {
    public static void main(String[] args) throws Exception {
        User param = new User();
        param.setUsername("test");
        RemoteService service = Feign.builder().client(RibbonClient.create())
				.encoder(new JacksonEncoder())
                .decoder(new JacksonDecoder())
			    .options(new Options(1000, 3500))
                .retryer(new Retryer.Default(5000, 5000, 3))
			    .target(RemoteService.class, "http://remote-client/gradle-web");
        /**
         * 呼叫測試
         */
        for (int i = 1; i <= 10; i++) {
            User result = service.getOwner(param);
            System.out.println(result.getId() + "," + result.getUsername());
        }
    }
}
  • 宣告瞭一個User型別的物件param,該物件將作為引數被髮送至服務生產端。

  • 重點在於通過RibbonClient.create()使得Feign物件獲得了Ribbon的特性。之後通過encoder,decoder設定編碼器與解碼器,並通過target方法將之前定義的介面RemoteService與一個URL地址http://remote-client/gradle-web進行了繫結。

現在來看remote-client.properties中的配置項,主要多是RemoteClient的配置機制

remote-client.ribbon.MaxAutoRetries=1
remote-client.ribbon.MaxAutoRetriesNextServer=1
remote-client.ribbon.OkToRetryOnAllOperations=true
remote-client.ribbon.ServerListRefreshInterval=2000
remote-client.ribbon.ConnectTimeout=3000
remote-client.ribbon.ReadTimeout=3000
remote-client.ribbon.listOfServers=127.0.0.1:8080,127.0.0.1:8085
remote-client.ribbon.EnablePrimeConnections=false

所有的key都以remote-client開頭,表明這些配置項作用於名為remote-client的服務。其實就是與之前繫結RemoteService介面的URL地址的schema相對應。

重點看remote-client.ribbon.listOfServers配置項,該配置項指定了服務生產端的真實地址。

之前與RemoteService介面繫結的URL地址是 : http://remote-client/gradle-web

在呼叫時會被替換為:

@RequestLine指定的地址進行拼接,得到最終請求地址。本例中最終請求地址為:

由於使用的ribbon,所以feign不再需要配置超時時長,重試策略。ribbon提供了更為完善的策略實現。

本例中,服務生產端是一個簡單的springMvc,實現如下:

@RestController
@RequestMapping(value="users")
public class UserController {
    @RequestMapping(value="/list",method={RequestMethod.GET,RequestMethod.POST,RequestMethod.PUT})
    public User list(@RequestBody User user) throws InterruptedException{
        HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
        user.setId(new Long(request.getLocalPort()));
        user.setUsername(user.getUsername().toUpperCase());
        return user;
    }
}

故障轉移是通過remote-client.properties中的配置項進行配置。

  • 首先利用archaius專案的com.netflix.config.ConfigurationManager讀取配置檔案remote-client.properties,該檔案位於src/main/resources下。

負載均衡的策略又是如何設定呢?

import com.netflix.client.ClientFactory;
import com.netflix.client.config.IClientConfig;
import com.netflix.config.ConfigurationManager;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.RandomRule;
import com.netflix.loadbalancer.ZoneAwareLoadBalancer;
import feign.Feign;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import feign.ribbon.LBClient;
import feign.ribbon.LBClientFactory;
import feign.ribbon.RibbonClient;
public class AppRun {
    public static void main(String[] args) throws Exception {
        ConfigurationManager.loadPropertiesFromResources("remote-client.properties");
        User param = new User();
        param.setUsername("test");
        RibbonClient client = RibbonClient.builder().lbClientFactory(new LBClientFactory() {
            @Override
            public LBClient create(String clientName) {
                IClientConfig config = ClientFactory.getNamedConfig(clientName);
                ILoadBalancer lb = ClientFactory.getNamedLoadBalancer(clientName);
                ZoneAwareLoadBalancer zb = (ZoneAwareLoadBalancer) lb;
                zb.setRule(new RandomRule());
                return LBClient.create(lb, config);
            }
        }).build();
        RemoteService service = Feign.builder().client(client).encoder(new JacksonEncoder())
                .decoder(new JacksonDecoder()).options(new Options(1000, 3500))
                .retryer(new Retryer.Default(5000, 5000, 3)).target(RemoteService.class, "http://remote-client/gradle-web");
        /**
         * 呼叫測試
         */
        for (int i = 1; i <= 10; i++) {
            User result = service.getOwner(param);
            System.out.println(result.getId() + "," + result.getUsername());
        }
    }
}

其他負載均衡策略

 /**
     * Ribbon負載均衡策略實現
     * 使用ZoneAvoidancePredicate和AvailabilityPredicate來判斷是否選擇某個server,前一個判斷判定一個zone的執行效能是否可用,
     * 剔除不可用的zone(的所有server),AvailabilityPredicate用於過濾掉連線數過多的Server。
     * @return
     */

	private IRule zoneAvoidanceRule() {
        return new ZoneAvoidanceRule();
    }

    /**
     * Ribbon負載均衡策略實現
     * 隨機選擇一個server。
     * @return
     */
    private IRule randomRule() {
        return new RandomRule();
    }

不再使用RibbonClient.create()來建立預設的RibbonClient,而是通過RibbonClient.builder()獲得feign.ribbon.Builder,進而設定LBClientFactory的實現來定製LBClient,在建立LBClient的過程中即可指定負載策略的具體實現。

相關文章