feign更正确的使用方法--结合ribbon

之前的文章介绍了feign的正常使用方式,其实只是说明了API的使用。

在项目开发中,除了考虑正常的调用之外,负载均衡和故障转移也是关注的重点,这也是feign + 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>
</dependencies>

其中feign-core和feign-ribbon是必须的,如果需要在服务消费端和服务生产端之间进行对象交互,建议使用feign-jackson

服务消费端接口

public interface RemoteService {

    @Headers({"Content-Type: application/json","Accept: application/json"})
    @RequestLine("GET /users/list")
    User getOwner(User user);
}

RemoteService接口中定义了一个名为getUser的方法,该方法的参数与返回类型都是User(标准POJO),定义如下:

public class User {
    private Long id;
    
    private String username;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

配置及运行

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 {

        ConfigurationManager.loadPropertiesFromResources("sample-client.properties");

        User param = new User();
        param.setUsername("scott");

        RemoteService service = Feign.builder().client(RibbonClient.create()).encoder(new JacksonEncoder())
                .decoder(new JacksonDecoder()).target(RemoteService.class, "http://sample-client/gradle-web");

        /**
         * 调用测试
         */
        for (int i = 1; i <= 10; i++) {
            User result = service.getOwner(param);
            System.out.println(result.getId() + "," + result.getUsername());
        }
    }
}

首先利用com.netflix.config.ConfigurationManager读取配置文件sample-client.properties,该文件位于src/main/resources下。

然后声明了一个User类型的对象param,该对象将作为参数被发送至服务生产端。

重点在于通过RibbonClient.create()使得Feign对象获得了Ribbon的特性。之后通过encoder,decoder设置编码器与解码器,并通过target方法将之前定义的接口RemoteService与一个URL地址http://sample-client/gradle-web进行了绑定。

现在来看sample-client.properties中的配置项

sample-client.ribbon.MaxAutoRetries=1

sample-client.ribbon.MaxAutoRetriesNextServer=1

sample-client.ribbon.OkToRetryOnAllOperations=true

sample-client.ribbon.ServerListRefreshInterval=2000

sample-client.ribbon.ConnectTimeout=3000

sample-client.ribbon.ReadTimeout=3000

sample-client.ribbon.listOfServers=127.0.0.1:8080,127.0.0.1:8085

sample-client.ribbon.EnablePrimeConnections=false

所有的key都以sample-client开头,表明这些配置项作用于名为sample-client的服务。其实就是与之前绑定RemoteService接口的URL地址的schema相对应。

重点看sample-client.ribbon.listOfServers配置项,该配置项指定了服务生产端的真实地址。
之前与RemoteService接口绑定的URL地址是http://sample-client/gradle-web,在调用时会被替换为http://127.0.0.1:8080/gradle-webhttp://127.0.0.1:8085/gradle-web,再与接口中@RequestLine指定的地址进行拼接,得到最终请求地址。本例中最终请求地址为http://127.0.0.1:8080/gradle-web/users/listhttp://127.0.0.1:8085/gradle-web/users/list

由于使用的ribbon,所以feign不再需要配置超时时长,重试策略。ribbon提供了更为完善的策略实现。

本例中,服务生产端是一个简单的spring mvc,实现如下:

@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;
    }
}

简单的将容器运行的端口号设为id并将username改为大写字母后返回。

将服务生产端部署到2个容器中,分别在8080和8085端口运行。

运行服务消费端的main方法,观察输出。

8085,SCOTT
8080,SCOTT
8085,SCOTT
8080,SCOTT
8085,SCOTT
8080,SCOTT
8085,SCOTT
8080,SCOTT
8085,SCOTT
8080,SCOTT

可以发现,feign交替的访问2个服务端。

如果关闭其中一个服务端,再次运行客户端会发现通过访问了存活的服务端,客户端仍然能够运行,这就是ribbon提供负载均衡与故障转移的最简单实现。

故障转移是通过sample-client.properties中的配置项进行配置。

具体参数含义请查阅官方文档。

负载均衡的策略又是如何设置呢?

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("sample-client.properties");

        User param = new User();
        param.setUsername("scott");

        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()).target(RemoteService.class, "http://sample-client/gradle-web");

        /**
         * 调用测试
         */
        for (int i = 1; i <= 10; i++) {
            User result = service.getOwner(param);
            System.out.println(result.getId() + "," + result.getUsername());
        }
    }
}

不再使用RibbonClient.create()来创建默认的RibbonClient,而是通过RibbonClient.builder()获得feign.ribbon.Builder,进而设置LBClientFactory的实现来定制LBClient,在创建LBClient的过程中即可指定负载策略的具体实现。

参考http://www.idouba.net/netflix-source-ribbon-rule/?utm_source=tuicool&utm_medium=referral

这里使用的指定方式并不推荐,实际项目中应该创建LBClient的过程中指定ILoadBalancer实现,而不应该创建完毕后再强转类型来进行修改。

确认2个服务端正常启动,运行客户端,观察输出。

8080,SCOTT
8080,SCOTT
8080,SCOTT
8080,SCOTT
8085,SCOTT
8080,SCOTT
8085,SCOTT
8080,SCOTT
8085,SCOTT
8085,SCOTT

现在不再是交替访问服务端,而是随机访问。证明更换负载均衡策略有效。

后记

以上相关配置都是在main方法中完成的,如果是spring项目,改为spring相应的配置方式即可。

感谢netflix提供了优秀的开源实现,方便了我的工作。一定要多存钱买VPN,看netflix的节目。希望netflix的文档更全面一些。

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

推荐阅读更多精彩内容