之前的文章介绍了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-web
或http://127.0.0.1:8085/gradle-web
,再与接口中@RequestLine
指定的地址进行拼接,得到最终请求地址。本例中最终请求地址为http://127.0.0.1:8080/gradle-web/users/list
或http://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的文档更全面一些。