1.OpenFeign
Spring Cloud openfeign对Feign进行了增强,使其支持Spring MVC注解,另外还整合了Ribbon和Nacos,从而使得Feign的使用更加方便.
1.1 优势
Feign可以做到使用 HTTP 请求远程服务时就像调用本地方法一样的体验,开发者完全感知不到这是远程方法,更感知不到这是个 HTTP 请求。它像 Dubbo 一样,consumer 直接调用接口方法调用 provider,而不需要通过常规的 Http Client 构造请求再解析返回数据。它解决了让开发者调用远程接口就跟调用本地方法一样,无需关注与远程的交互细节,更无需关注分布式环境开发。
1.2快速整合OpenFeign
1.2.1引入依赖
<!‐‐ openfeign 远程调用 ‐‐>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring‐cloud‐starter‐openfeign</artifactId>
</dependency>
1.2.2编写调用接口+@FeignClient注解
@FeignClient(value = "mall‐order",path = "/order")
public interface OrderFeignService {
@RequestMapping("/findOrderByUserId/{userId}")
public R findOrderByUserId(@PathVariable("userId") Integer userId);
}
1.2.3调用端在启动类上添加@EnableFeignClients注解
@SpringBootApplication
@EnableFeignClients
public class MallUserFeignDemoApplication {
public static void main(String[] args) {
SpringApplication.run(MallUserFeignDemoApplication.class, args);
}
}
1.2.4调用端发起调用,像调用本地方法一样调用远程服务
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
OrderFeignService orderFeignService;
@RequestMapping(value = "/findOrderByUserId/{id}")
public R findOrderByUserId(@PathVariable("id") Integer id) {
//feign调用
R result = orderFeignService.findOrderByUserId(id);
return result;
}
}
========================================================
使用配置
/**
* <h1>使用 Ribbon 之前的配置, 增强 RestTemplate</h1>
* */
@Component
public class RibbonConfig {
/**
* <h2>注入 RestTemplate</h2>
* */
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
创建UseRibbonService
**
* <h2>通过 Ribbon 调用 Authority 服务获取 Token</h2>
* */
public JwtToken getTokenFromAuthorityServiceByRibbon(
UsernameAndPassword usernameAndPassword) {
// 注意到 url 中的 ip 和端口换成了服务名称
String requestUrl = String.format(
"http://%s/ecommerce-authority-center/authority/token",
CommonConstant.AUTHORITY_CENTER_SERVICE_ID
);
log.info("login request url and body: [{}], [{}]", requestUrl,
JSON.toJSONString(usernameAndPassword));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
// 这里一定要使用自己注入的 RestTemplate
return restTemplate.postForObject(
requestUrl,
new HttpEntity<>(JSON.toJSONString(usernameAndPassword), headers),
JwtToken.class
);
}
Ribbon使用SERVICE_ID简化(ip+port)
使用原生的 Ribbon Api, 看看 Ribbon 是如何完成: 服务调用 + 负载均衡
- 找到服务提供方的地址和端口号
- 构造 Ribbon 服务列表(com.netflix.loadbalancer.Serve)
- 使用负载均衡策略实现远端服务调用
- 构建 Ribbon 负载实例
- 设置负载均衡策略
/**
* <h2>使用原生的 Ribbon Api, 看看 Ribbon 是如何完成: 服务调用 + 负载均衡</h2>
* */
public JwtToken thinkingInRibbon(UsernameAndPassword usernameAndPassword) {
String urlFormat = "http://%s/ecommerce-authority-center/authority/token";
// 1. 找到服务提供方的地址和端口号
List<ServiceInstance> targetInstances = discoveryClient.getInstances(
CommonConstant.AUTHORITY_CENTER_SERVICE_ID
);
// 构造 Ribbon 服务列表
List<Server> servers = new ArrayList<>(targetInstances.size());
targetInstances.forEach(i -> {
servers.add(new Server(i.getHost(), i.getPort()));
log.info("found target instance: [{}] -> [{}]", i.getHost(), i.getPort());
});
// 2. 使用负载均衡策略实现远端服务调用
// 构建 Ribbon 负载实例
BaseLoadBalancer loadBalancer = LoadBalancerBuilder.newBuilder()
.buildFixedServerListLoadBalancer(servers);
// 设置负载均衡策略
loadBalancer.setRule(new RetryRule(new RandomRule(), 300));
String result = LoadBalancerCommand.builder().withLoadBalancer(loadBalancer)
.build().submit(server -> {
String targetUrl = String.format(
urlFormat,
String.format("%s:%s", server.getHost(), server.getPort())
);
log.info("target request url: [{}]", targetUrl);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
String tokenStr = new RestTemplate().postForObject(
targetUrl,
new HttpEntity<>(JSON.toJSONString(usernameAndPassword), headers),
String.class
);
return Observable.just(tokenStr);
}).toBlocking().first().toString();
return JSON.parseObject(result, JwtToken.class);
}
OpenFeign
OpenFeign只定义接口,不用实现,实现由OpenFeign生成代理
/**
* <h1>与 Authority 服务通信的 Feign Client 接口定义</h1>
* */
@FeignClient(
contextId = "AuthorityFeignClient", value = "e-commerce-authority-center",
// fallback = AuthorityFeignClientFallback.class
fallbackFactory = AuthorityFeignClientFallbackFactory.class
)
public interface AuthorityFeignClient {
/**
* <h2>通过 OpenFeign 访问 Authority 获取 Token</h2>
* */
@RequestMapping(value = "/ecommerce-authority-center/authority/token",
method = RequestMethod.POST,
consumes = "application/json", produces = "application/json")
JwtToken getTokenByFeign(@RequestBody UsernameAndPassword usernameAndPassword);
}
配置OpenFeign,让它更好用
OpenFeign原生没有连接池,所以用okhttp替换
bootstrap.yml配置
# Feign 的相关配置
feign:
# feign 开启 gzip 压缩
compression:
request:
enabled: true
mime-types: text/xml,application/xml,application/json
# 大于1024才压缩
min-request-size: 1024
response:
enabled: true
# 禁用默认的 http, 启用 okhttp
httpclient:
enabled: false
okhttp:
enabled: true
# OpenFeign 集成 Hystrix
hystrix:
enabled: true
OpenFeign 配置类
/**
* <h1>OpenFeign 配置类</h1>
* */
@Configuration
public class FeignConfig {
/**
* <h2>开启 OpenFeign 日志</h2>
* */
@Bean
public Logger.Level feignLogger() {
return Logger.Level.FULL; // 需要注意, 日志级别需要修改成 debug
}
/**
* <h2>OpenFeign 开启重试</h2>
* period = 100 发起当前请求的时间间隔, 单位是 ms
* maxPeriod = 1000 发起当前请求的最大时间间隔, 单位是 ms
* maxAttempts = 5 最多请求次数
* */
@Bean
public Retryer feignRetryer() {
return new Retryer.Default(
100,
SECONDS.toMillis(1),
5
);
}
public static final int CONNECT_TIMEOUT_MILLS = 5000;
public static final int READ_TIMEOUT_MILLS = 5000;
/**
* <h2>对请求的连接和响应时间进行限制</h2>
* 1.连接超时时间
* 2.读超时时间
* 3.转发时间算不算在内true/false
* */
@Bean
public Request.Options options() {
return new Request.Options(
CONNECT_TIMEOUT_MILLS, TimeUnit.MICROSECONDS,
READ_TIMEOUT_MILLS, TimeUnit.MILLISECONDS,
true
);
}
}
注入 OkHttp, 并自定义配置
/**
* <h1>OpenFeign 使用 OkHttp 配置类</h1>
* */
@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignOkHttpConfig {
/**
* <h2>注入 OkHttp, 并自定义配置</h2>
* */
@Bean
public okhttp3.OkHttpClient okHttpClient() {
return new OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS) // 设置连接超时
.readTimeout(5, TimeUnit.SECONDS) // 设置读超时
.writeTimeout(5, TimeUnit.SECONDS) // 设置写超时
.retryOnConnectionFailure(true) // 是否自动重连
// 配置连接池中的最大空闲线程个数为 10, 并保持 5 分钟
.connectionPool(new ConnectionPool(
10, 5L, TimeUnit.MINUTES))
.build();
}
}
OpenFeign 实现原理
Feign实现流程图