Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单, 它的使用方法是定义一个接口,然后在上面添加注解,同时也支持JAX-RS标准的注解。Feign也支持可拔插式的编码器和解码器。SpringCloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
简而言之:
Feign 采用的是基于接口的注解
Feign 整合了ribbon,具有负载均衡的能力
整合了Hystrix,具有熔断的能力
Feign旨在使编写Java Http客户端变得更容易。 前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。
1、在客户端(User)引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2、在启动类上面加上注解:@EnableFeignClients
@SpringBootApplication
@EnableEurekaClient
@RibbonClients({
@RibbonClient(name = "SERVER-ORDER",configuration = OrderRuleConfig.class),
@RibbonClient(name = "SERVER-POWER",configuration = PowerRuleConfig.class)
})
@EnableFeignClients
public class AppUserClient {
public static void main(String[] args) {
SpringApplication.run(AppUserClient.class);
}
}
3、然后编写一个service类加上@FeignClient()注解 参数就是你的微服务名字
@FeignClient(name = "SERVER-POWER")
public interface PowerFeignClient {
@RequestMapping("/getPower.do")
Object getPower();
}
4、下面是调用代码:
@RestController
public class UserFeignController {
@Autowired
private PowerFeignClient powerFeignClient;
@RequestMapping("/getFeignPower.do")
public R getFeignPower(){
return R.success("操作成功",powerFeignClient.getPower());
}
这里可以拿微服务架专题四中RestTemplate做对比 可以看看2者区别。
Feign集成了Ribbon利用Ribbon维护了服务列表信息,并且融合了Ribbon的负载均衡配置,也就是说之前自定义的负载均衡也有效,这里需要你们自己跑一遍理解一下。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用;
MICROSERVICE-ORDER 表示作用到哪个微服务,com.netflix.loadbalancer.RandomRule 即前面介绍 Ribbon 时里面的随机策略,当然,我们也可以指定为其他策略,包括我们自己定义的,只要把相应的包路径写到这即可,很方便。
也很简单,我们可以在 application.properties 配置文件中来指定,如下:
# feign和ribbon结合,指定策略。feign默认的是轮询的策略,这里的配置可以自定义
MICROSERVICE-ORDER.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RoundRobinRule
MICROSERVICE-ORDER.ribbon.ConnectTimeout=500 #请求连接超时时间
MICROSERVICE-ORDER.ribbon.ReadTimeout=1000 #请求处理的超时时间
MICROSERVICE-ORDER.ribbon.OkToRetryOnAllOperations=true #对所有请求都进行重试
MICROSERVICE-ORDER.ribbon.MaxAutoRetriesNextServer=2 #切换实例的重试次数
MICROSERVICE-ORDER.ribbon.MaxAutoRetries=1 #对当前实例的重试次数
这里我们可以看到ribbon的策略主要有以下几种:
com.netflix.loadbalancer.RandomRule #配置规则 随机
com.netflix.loadbalancer.RoundRobinRule #配置规则 轮询
com.netflix.loadbalancer.RetryRule #配置规则 重试
com.netflix.loadbalancer.WeightedResponseTimeRule #配置规则 响应时间权重
com.netflix.loadbalancer.BestAvailableRule #配置规则 最空闲连接策略
随机:几个提供者间随机访问
轮询:轮流访问
重试:在一段时间内通过RoundRobinRule选择服务实例,一段时间内没有选择出服务则线程终止
响应时间权重:根据平均响应时间来计算权重
在网络请求时,可能会出现异常请求,如果还想再异常情况下使系统可用,那么就需要容错处理,比如:网络请求超时时给用户提示“稍后重试”或使用本地快照数据等等。
Spring Cloud Feign HTTP请求异常Fallback容错机制,它是基于Hystrix实现的,所以要通过配置参数feign.hystrix.enabled=true开启该功能,及其两种实现方式。
Spring Cloud Feign就是通过Fallback实现的,有两种方式:
1、@FeignClient.fallback = UserFeignFallback.class指定一个实现Feign接口的实现类。
2、@FeignClient.fallbackFactory = UserFeignFactory.class指定一个实现FallbackFactory工厂接口类
UserFeignFallback回调实现,由spring创建使用@Component(其他的注册也可以)注解
HystrixTargeter.targetWithFallback方法实现了@FeignClient.fallback处理逻辑,通过源码可以知道UserFeignFallback回调类是从Spring容器中获取的,所以UserFeignFallback由spring创建。
UserFeignService:
@FeignClient(value = "HOUSE-USER",fallbackFactory = UserClientFallBack.class)
public interface UserFeignService {
@RequestMapping("/user/getById")
User getUserDetailById(@RequestParam("id")Long id);
}
UserClientFallBack:
@Component
public class UserClientFallBack implements UserFeignService {
@Override
public User getUserDetailById(Long id) {
return null;
}
}
上面的实现方式简单,但是获取不到HTTP请求错误状态码和信息 ,这时就可以使用工厂模式来实现Fallback
同样工厂实现类也要交由spring管理,同时结合UserFeignFallback使用,这里需要注意的create方法返回值类型一定要实现Feign接口,否则会报错。
在feign中开启hystrix功能,默认情况下feign不开启hystrix功能,添加配置开启
feign.hystrix.enabled=true
service类:
@FeignClient(value = "HOUSE-COMMENT",fallbackFactory = CommentFeignFallBackFactory.class)
public interface CommentFeignService {
@RequestMapping("/comment/add")
void addComment(@RequestBody CommentReq commentReq);
@RequestMapping("/blog/list")
RestResponse<ListResponse<Blog>> getBlogs(@RequestBody BlogQueryReq blogQueryReq);
@RequestMapping("/blog/one")
RestResponse<Blog> getBlog(@RequestParam("id") int id);
@RequestMapping(value = "/group/{groupId}", method = RequestMethod.PUT)
void update(@PathVariable("groupId") Integer groupId, @RequestParam("groupName") String groupName);
}
方式一:
该方式可以获取到异常信息throwable.printStackTrace();
CommentFeignFallBackFactory类
@Component
public class CommentFeignFallBackFactory implements FallbackFactory<CommentFeignService> {
@Override
public CommentFeignService create(Throwable throwable) {
return new CommentFeignService(){
@Override
public void addComment(CommentReq commentReq) {
throw new RuntimeException(throwable.getMessage());
}
@Override
public RestResponse<ListResponse<Blog>> getBlogs(BlogQueryReq blogQueryReq) {
return new RestResponse<>();
}
@Override
public RestResponse<Blog> getBlog(int id) {
throw new RuntimeException("服务异常,请稍后重试!");
}
@Override
public void update() {
throw new RuntimeException("服务异常,请稍后重试!");
}
};
}
}
方式二:
CommentFeignServiceHystrix:
@Component
public class CommentFeignServiceHystrix implements CommentFeignService {
@Override
public void addComment(CommentReq commentReq) {
throw new RuntimeException("服务异常,请稍后重试!");
}
@Override
public RestResponse<List<Comment>> listComment(CommentReq commentReq) {
return new RestResponse<>();
}
@Override
public RestResponse<ListResponse<Blog>> getBlogs(BlogQueryReq blogQueryReq) {
return new RestResponse<>();
}
@Override
public RestResponse<Blog> getBlog(int id) {
throw new RuntimeException("服务异常,请稍后重试!");
}
}
CommentFeignFallBackFactory:
@Component
public class CommentFeignFallBackFactory implements FallbackFactory<CommentFeignService> {
@Override
public CommentFeignService create(Throwable throwable) {
return new CommentFeignServiceHystrix();
}
}
概述
在默认情况下 spring cloud feign在进行各个子服务之间的调用时,http组件使用的是jdk的HttpURLConnection,没有使用线程池。效率非常低。
为了提高效率,可以通过连接池提高效率,本节我们使用appache httpclient做为连接池。配置OkHttpClient连接池,也是类似的方法,这里略。
pom.xml中引入feign-httpclient.jar
io.github.openfeign
feign-httpclient
我在配置时遇到了一个坑,不要随便指定feign-httpclient的version,否则会冲突报错,运行是报
配置参数
只需在application.properties中新增配置项,启动httpclient,就可以实现httpclient替换urlconnection。
feign.httpclient.enabled=true
自定义配置类
使用配置类,生成HttpClient 对象。因为使用PoolingHttpClientConnectionManager连接池,我们需要启动定时器,定时回收过期的连接。配置定时回收连接池的原因,见问题备忘: httpclient连接池异常引发的惨案。
http pool setting:
#http pool setting
http.pool.conn.maxTotal=500
http.pool.conn.defaultMaxPerRoute=100
http.pool.conn.connectTimeout=10000
http.pool.conn.connectionRequestTimeout=1000
http.pool.conn.socketTimeout=65000
http.pool.conn.validateAfterInactivity=2000
http.pool.conn.maxConnPerRoute=10
http.pool.conn.maxConnTotaol=50
HttpClientProperties:
@Component
@ConfigurationProperties(prefix = "http.pool.conn")
@Data
public class HttpClientProperties {
private String agent = "agent";
private Integer maxTotal;
private Integer defaultMaxPerRoute;
private Integer connectTimeout;
private Integer connectionRequestTimeout;
private Integer socketTimeout;
private Integer validateAfterInactivity;
private Integer maxConnPerRoute;
private Integer maxConnTotaol;
}
FeignConfig:配置httpclient和 访问https地址
@Configuration
public class FeignConfig {
@Autowired
private HttpClientProperties httpPoolProperties;
/**
* 配置httpclient
* @return
*/
@Bean
public HttpClient httpClient(){
System.out.println("init feign httpclient configuration " );
// 生成默认请求配置
RequestConfig requestConfig = RequestConfig.custom()
//服务器返回数据(response)的时间,超过抛出read timeout
.setSocketTimeout(httpPoolProperties.getSocketTimeout())
//连接上服务器(握手成功)的时间,超出抛出connect timeout
.setConnectTimeout(httpPoolProperties.getConnectTimeout())
//从连接池中获取连接的超时时间,超时间未拿到可用连接,会抛出ConnectionPoolTimeoutException
.setConnectionRequestTimeout(httpPoolProperties.getConnectionRequestTimeout())
.build();
// 连接池配置
// 长连接保持30秒
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.MILLISECONDS);
connectionManager.setMaxTotal(httpPoolProperties.getMaxTotal());// 总连接数
connectionManager.setDefaultMaxPerRoute(httpPoolProperties.getDefaultMaxPerRoute());// 同路由的并发数
connectionManager.setValidateAfterInactivity(httpPoolProperties.getValidateAfterInactivity());
// httpclient 配置
HttpClient client
= HttpClientBuilder.create()
// 保持长连接配置,需要在头添加Keep-Alive
.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
.setDefaultRequestConfig(requestConfig)//设置requestConfig
.setUserAgent(httpPoolProperties.getAgent())//设置User-Agent
.setMaxConnPerRoute(httpPoolProperties.getMaxConnPerRoute())//设置一个远端IP最大的连接数
.setMaxConnTotal(httpPoolProperties.getMaxConnTotaol())//设置总的连接数
//不重用连接,默认是重用,建议保持默认开启连接池,节省建立连接开销
//.setConnectionReuseStrategy(new NoConnectionReuseStrategy())
.setConnectionManager(connectionManager)
.build();
// 启动定时器,定时回收过期的连接
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
// System.out.println("=====closeIdleConnections===");
connectionManager.closeExpiredConnections();
connectionManager.closeIdleConnections(5, TimeUnit.SECONDS);
}
}, 10 * 1000, 5 * 1000);
System.out.println("===== Apache httpclient 初始化连接池===");
return client;
}
/**
* 访问https地址
* @param cachingFactory
* @param clientFactory
* @return
* @throws NoSuchAlgorithmException
* @throws KeyManagementException
*/
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) throws NoSuchAlgorithmException, KeyManagementException {
SSLContext ctx = SSLContext.getInstance("SSL");
X509TrustManager tm = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
ctx.init(null, new TrustManager[]{tm}, null);
return new LoadBalancerFeignClient(new Client.Default(ctx.getSocketFactory(),
new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
// TODO Auto-generated method stub
return true;
}
}) ,
cachingFactory, clientFactory);
}
}
原理:
按照导入从上往下的顺序:HttpClientFeignLoadBalancedConfiguration>OkHttpFeignLoadBalancedConfiguration>DefaultFeignLoadBalancedConfiguration,对应的底层http工具:httpclient>okhttp>HttpURLConnection
所以pom中引入feign-httpclient–》类路径下存在ApacheHttpClient.class–》走HttpClientFeignLoadBalancedConfiguration–》请求时走HttpClient