目录
2.3.2、在application配置文件中给某个服务配置负载均衡策略:
2.3.4、服务间使用Ribbon通过RestTemplate调用服务示例、以及获取服务节点信息:
3.2.1、在feign接口提供方工程中创建client或者api模块
3.3.3、如何支持feign的注解来替换springmvc的注解
3.3.4、创建拦截器设置公用参数实现:RequestInterceptor
3.3.4.4、Feign调用时在客户端(服务消费方)做拦截,将token等添加到请求里,给到服务提供端,的代码实现(实际项目中):
Spring Cloud 中文网:https://www.springcloud.cc/
Ribbon官网资料:https://cloud.spring.io/spring-cloud-static/Camden.SR7/#spring-cloud-ribbon
Ribbon是一个客户端负载平衡器,可以对HTTP和TCP客户端的行为进行大量控制。Feign已经使用了Ribbon,所以如果使用的是@FeignClient,那么Ribbon也已经包含。
Ribbon中的一个核心概念是named client,即客户端通过服务名调用服务端。每个负载平衡器都是一个组件集成的一部分,这些组件协同工作以按需联系远程服务器,并且集成有一个名称,即服务名(例如,使用@FeignClient注解设置)。Spring Cloud creates a new ensemble(整体) as an ApplicationContext
on demand(按需求) for each named client using RibbonClientConfiguration
.Spring Cloud会将每个使用RibbonClientConfiguration配置的相同服务名的服务作为一个整体放在客户端应用上下文里。其中包括一个iloadballancer、一个RestClient和一个ServerListFilter。
一、负载均衡
1.1、服务端负载均衡
负载均衡包括服务端负载均衡和客户端负载均衡。以前我们通常所说的负载均衡都指的是服务端负载均衡,分为硬件负载均衡和软件负载均衡。硬件负载均衡主要通过在服务器节点之间安装专门用于负载均衡的设备,比如F5等;而软件负载均衡则是通过在服务器上安装一些用于负载均衡功能或模块等软件来完成请求分发工作,比如Nginx等。
硬件负载均衡的设备或是软件负载均衡的软件模块都会维护一个下挂可用的服务端清单,通过心跳检测来剔除故障的服务端节点以保证清单中都是可以正常访问的服务端节点。当客户端发送请求到负载均衡设备的时候,该设备按某种算法(比如线性轮询、按权重负载、按流量负载等)从维护的可用服务端清单中取出一台服务端端地址,然后进行转发。
1.2、客户端负载均衡
而客户端负载均衡和服务端负载均衡最大的不同点在于上面所提到服务清单所存储的位置。在客户端负载均衡中,所有客户端节点都维护着自己要访问的服务端清单,而这些服务端清单来自于服务注册中心,比如Eureka服务端。同服务端负载均衡的架构类似,在客户端负载均衡中也需要心跳去维护服务端清单的健康性,默认会创建针对各个服务治理框架的Ribbon自动化整合配置,比如Eureka中的org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,Consul中的org.springframework.cloud.consul.discovery.RibbonConsulAutoConfiguration。在实际使用的时候,我们可以通过查看这两个类的实现,以找到它们的配置详情来帮助我们更好地使用它。
二、Ribbon使用
spring cloud ribbon是 基于NetFilix ribbon 实现的一套客户端的负载均衡工具,Ribbon客户端组件提供一系列的完善的配置,如超时,重试等。 通过Load Balancer(LB)获取到服务提供的所有机器实例,Ribbon会自动基于某种规则(轮询,随机)去调用这些服务。Ribbon也可以实现 我们自己的负载均衡算法。
通过Spring Cloud Ribbon的封装,我们在微服务架构中使用客户端负载均衡调用非常简单,只需要如下两步:
1、服务提供者只需要启动多个服务实例并注册到一个注册中心或是多个相关联的服务注册中心。
2、服务消费者直接通过调用被@LoadBalanced注解修饰过的RestTemplate来实现面向服务的接口调用。
2.1、使用Eureka时集成Ribbon
使用步骤:
2.1.1、搭建启动eureka注册中心
2.1.2、把Ribbon集成到消费端consumer:
1)、引入依赖
eureka 客户端的依赖(其实他集成了ribbon的依赖,所以下面那个不需要引。)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
集成ribbon的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
2)、写application配置文件
server.port=8001
#注册到eureka服务端的微服务名称
spring.application.name=ms-consumer-user
#注册到eureka服务端的地址
eureka.client.service-url.defaultZone=http://www.eureka9000.com:9000/eureka/,http://www.eureka9001.com:9001/eureka/
#点击具体的微服务,右下角是否显示ip
eureka.instance.prefer-ip-address=true
#显示微服务的名称
eureka.instance.instance-id=ms-consumer-ribbon-8001
#配置数据库
spring.datasource.url=jdbc:mysql://47.104.128.12:3306/tuling-cloud02
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.tomcat.test-on-borrow=true
spring.datasource.tomcat.test-on-return=true
3)、修改调用配置类
@Configuration
public class MainConfig {
@Bean
@LoadBalanced //TODO 加上这个注解
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@RequestMapping("/queryUserInfoById/{userId}")
public UserInfoVo queryUserInfoById(@PathVariable("userId") Integer userId) {
User user = userServiceImpl.queryUserById(userId);
//TODO 通过微服务实例名称进行调用
ResponseEntity<List> responseEntity = restTemplate.getForEntity("http://MS-PROVIDER-ORDER/order/ queryOrdersByUserId/"+userId,List.class);
//ResponseEntity<List> responseEntity = restTemplate.getForEntity("http://localhost:8002/order/queryOrdersByUserId/"+userId,List.class);
List<OrderVo> orderVoList = responseEntity.getBody();
UserInfoVo userInfoVo = new UserInfoVo();
userInfoVo.setOrderVoList(orderVoList);
userInfoVo.setUserName(user.getUserName());
return userInfoVo;
}
2.1.3、搭建服务提供者provider:
1)、引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
</dependency>
<dependencies>
2)、修改applicaiton配置文件
server.port=8002
spring.application.name=ms-provider-order
eureka.client.service-url.defaultZone=http://www.eureka9000.com:9000/eureka/,http://www.eureka9001.com:9001/eureka/
eureka.instance.instance-id=ms-provider-order-8002
eureka.instance.prefer-ip-address=true
#配置mybatis
mybatis.configuration.map-underscore-to-camel-case=true
#配置数据库
spring.datasource.url=jdbc:mysql://47.104.128.12:3306/tuling-cloud
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.tomcat.test-on-borrow=true
spring.datasource.tomcat.test-on-return=true
logging.level.com.tuling.dao=debug
可搭建多个服务名一样的provider服务,会负载调用。
2.2、Ribbon核心知识点
2.2.1、Ribbon所包含的负载均衡策略分类
Ribbon内置负载均衡策略介绍:
内置策略 | 规则描述 | 实现说明 |
RoundRobinRule(默认) | 简单轮询服务列表来选择服务器。 | 轮询index,选择index对应位置的server |
AvailabilityFilteringRule | 对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。 注意:可以通过修改配置loadbalancer.<clientName>.connectionFailureCountThreshold来修改连接失败多少次之后被设置为短路状态。默认是3次。 (2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上线,可以由客户端的<clientName>.<clientConfigNameSpace>.ActiveConnectionsLimit属性进行配置。 | 过滤掉一直连接失败的被标记为circuit tripped的后端Server,并过滤掉那些高并发的后端Server或者 使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个server的运行状态 |
WeightedResponseTimeRule | 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。(根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低;) | 一个后台线程定期的从status里面读取评价响应时间,为每个server计算一个weight。Weight的计算也比较简单responsetime 减去每个server自己平均的responsetime是server的权重。当刚开始运行,没有形成status时,使用roubine策略选择server。 |
ZoneAvoidanceRule | 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。(复合判断Server所在区域的性能和Server的可用性选择Server) | 使用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server,前一个判断判定一个zone的运行性能是否可用,剔除不可用的zone(的所有server),AvailabilityPredicate用于过滤掉连接数过多的Server。 |
BestAvailableRule | 忽略哪些短路的服务器,并选择并发数较低的服务器。 | 逐个考察Server,如果Server被tripped了,则忽略,在选择其中ActiveRequestsCount最小的server(选择一个最小的并发请求的Server) |
RandomRule | 随机选择一个可用的服务器。 | 在index上随机,选择index对应位置的server |
RetryRule | 重试机制的选择逻辑 | 对选定的负载均衡策略机上重试机制,在一个配置时间段内当选择server不成功,则一直尝试使用subRule的方式选择一个可用的server |
2.2.2、如何修改默认负载均衡的策略
写一个配置类来修改ribbon的负载均衡的配置:
@Configuration
public class MainConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
/**
* 重新注入新的实现了IRule接口的策略类
* @return
*/
@Bean
public IRule TulingRule() {
//return new RandomRule();
return new RetryRule();
}
}
2.2.3、如何自定义负载均衡策略
2.2.3.1、只给某个服务指定自定义的,不是全局都用自定义的。
1)、引用规则,使用@RibbonClient给某个服务指定负载均衡策略
@SpringBootApplication
@EnableDiscoveryClient
@RibbonClient(name = "MS-PROVIDER-ORDER",configuration = TulingRandomRule.class)//TODO 加上该注解
public class Tulingvip02MsConsumerUser8001Application{}
2)、给某个服务自定义的负载均衡策略配置不能写在@SpringbootApplication注解的@CompentScan扫描得到的地方
2.2.3.2、给某个服务或者全局,都可以扩展自定义自己的负载均衡策略。
1)、原生的RandomRule策略源码:
public class RandomRule extends AbstractLoadBalancerRule {
Random rand;
public RandomRule() {
rand = new Random();
}
@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
//获取所注册的机器
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
/*
* No servers. End regardless of pass, because subsequent passes
* only get more restrictive.
*/
return null;
}
//随机获取一个服务下标
int index = rand.nextInt(serverCount);
//获取一个服务列表
server = upList.get(index);
if (server == null) {
/*
* The only time this should happen is if the server list were
* somehow trimmed. This is a transient condition. Retry after
* yielding.
*/
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
// Shouldn't actually happen.. but must be transient or a bug.
server = null;
Thread.yield();
}
//直接返回
return server;
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
// TODO Auto-generated method stub
}
2)、自定义一个Rule(然后按上面的方式使用该Rule)
package com.config;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* 自定义的随机策略
*/
public class TulingRandomRule extends AbstractLoadBalancerRule {
Random rand;
public TulingRandomRule() {
rand = new Random();
}
private int currentIndex = 0;
private List<Server> currentChooseList = new ArrayList<Server>();
/**
* Randomly choose from all living servers
*/
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
//第一次进来 随机选取一个下标
int index = rand.nextInt(serverCount);
//当前轮询的次数小于等于5
if(currentIndex<5) {
//保存当前选择的服务列表ip
if(currentChooseList.isEmpty()) {
currentChooseList.add(upList.get(index));
}
//当前的++
currentIndex++;
//返回保存的
return currentChooseList.get(0);
}else {
currentChooseList.clear();
currentChooseList.add(0,upList.get(index));
currentIndex=0;
}
if (server == null) {
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
server = null;
Thread.yield();
}
return server;
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
// TODO Auto-generated method stub
}
}
2.3、Ribbon使用扩展
2.3.1、默认负载均衡策略和自定义负载均衡策略同时使用
注意服务ID不一样,实际是一个服务,但是服务名不一样了。
2.3.2、在application配置文件中给某个服务配置负载均衡策略:
2.3.3、没有Eureka时Ribbon使用配置:
microservice-provider-user:
ribbon:
listOfServers: localhost:8001,localhost:8002
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
2.3.4、服务间使用Ribbon通过RestTemplate调用服务示例、以及获取服务节点信息:
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping("/user/{id}")
public User findById(@PathVariable Long id) {
return this.restTemplate.getForObject("http://microservice-provider-user/" + id, User.class);
}
@GetMapping("/user/getIpAndPort")
public String getIpAndPort() {
return this.restTemplate.getForObject("http://microservice-provider-user/getIpAndPort", String.class);
}
@GetMapping("/log-user-instance")
public void logUserInstance() {
ServiceInstance serviceInstance = this.loadBalancerClient.choose("microservice-provider-user");
// 打印当前选择的是哪个节点
OrderController.LOGGER.info("{}:{}:{}", serviceInstance.getServiceId(), serviceInstance.getHost(), serviceInstance.getPort());
}
三、Feign使用
3.1、什么是Feign
1)、Feign是一个声明式的http客户端,使用Feign可以实现声明式REST调用。spring cloud为Feign整合了Eureka,Ribbon,以提供服务发现及负载均衡等能力,同时整合了SpringMVC注解。
2)、Feign默认使用SpringMVC注解提供契约来进行REST访问,例如@RequestMapping,@PathVariable等。
3)、在之前的订单微服务中,调用用户微服务,我们使用的是RestTemplate.getForObject(), URL是我们自己拼接的字符串。如果参数较多的情况下,这种URL拼接参数的方式很低效,很不方便的。而Feign就解决了这些问题。
3.2、Feign使用
3.2.1、在feign接口提供方工程中创建client或者api模块
(该模块会被打成jar包供服务消费方使用,所以单独建个模块,而不是将整个服务提供方打成jar包。)
1)、引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
扩展依赖(更多功能可参考):
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2)、编写调用接口+注解
最简单的:
@FeignClient(name = "ms-provider-order",path = "/order")
public interface OrderApi {
@RequestMapping("/queryOrdersByUserId/{userId}")
public List<OrderVo> queryOrdersByUserId(@PathVariable("userId") Integer userId);
}
实际项目:服务名可配置,使用Hystrix。
@FeignClient(fallback = UserProviderServiceHystrix.class, name = "${service.feignclient.name.yuml-app-oca:yuml-app-oca}", path = "/api/core")
public interface UserProviderService {
@RequestMapping(method = RequestMethod.GET, value = "/contr/url")
public List<ContrBean> getAllContrUrl();
@RequestMapping(method = RequestMethod.GET, value = "/session/info")
public UserInfoDTO getUserInfo(@RequestParam("loginCode") String loginCode, @RequestParam("sysId") String sysId);
@GetMapping(value = "/user/org/{orgId}")
public List<String> queryUserByOrgId(@PathVariable("orgId") String orgId);
@PostMapping(value = "/user/orgs")
public List<String> queryUserByOrgIds(@RequestBody List<String> orgIds);
@RequestMapping(value = "/toVoidByBatch", method = RequestMethod.POST)
public String[] toVoidByBatch(@Valid @RequestBody ToVoidFeignDTO dto);
}
该接口上的name指定微服务的名称, path是指定服务消费者的调用前缀。
3)、打成jar包给调用方依赖。
3.2.2、服务调用端工程使用
1)、在服务调用端工程中引入上面打的client端的jar包:
<dependency>
<groupId>com.tuling</groupId>
<artifactId>tulingvip02-ms-feign-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
2)、在服务调用端主启动类上加入注解@EnableFeignClients(basePackages = "com.tuling")。在要使用的地方注入client端的接口类,像自己工程里的接口那样直接调用接口即可。
3.3、Feign使用扩展
3.3.1、抽取公共接口
实际应用:可以定义公共接口,然后对外提供的Feign的client接口,和正真实现该接口的Resource或者Controller,继承公共接口来统一管理:
示例:
/**
* 抽取公共接口
*/
public interface UserService {
@GetMapping("user/{id}")
public User getUser(@PathVariable(value = "id") Long id);
}
/**
* client对外接口,直接集成公共接口里的接口
*/
@FeignClient(name = "mystart", path = "/api/myTest/")
public interface RefactorUserService extends UserService {
}
/**
* 接口的url实现,实现公共接口,继承其中的请求路径
*/
@RestController
@RequestMapping("/api/myTest/")
public class RefactorUserController implements UserService {
@Override
public User getUser(@PathVariable Long id) {
User user = new User();
user.setName("RefactorUserController的");
user.setId(id);
return user;
}
}
调用api示例及结果图:
@RestController
@RequestMapping("/api/startTest/")
public class StartTestController {
@Autowired
private RefactorUserService refactorUserService;
@RequestMapping("ttt")
public User ttt(){
User user = refactorUserService.getUser(new Long(1234567));
return user;
}
}
通过Feign接口api调用时实际会发送远程请求到对应服务URL:
接下来的自定义Feign配置需要注意:
- * 该Feign Client的配置类,注意:
- * 1. 该类可以独立出去;
- * 2. 该类上也可添加@Configuration声明是一个配置类;
- * 配置类上也可添加@Configuration注解,声明这是一个配置类;但此时千万别将该放置在主应用程序上下文@ComponentScan所扫描的包中, 否则,该配置将会被所有Feign Client共享,无法实现细粒度配置!
- * 个人建议:像我一样,不加@Configuration注解
3.3.2、自定义Feign日志级别
3.3.2.1、Feign自己的日志级别:
Feign默认没有开启日志。
1)、NONE【性能最佳,适用于生产】:不记录任何日志(默认值)。
public class MsProvider8007CustomCfg {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.NONE;
}
}
2)、BASIC【适用于生产环境追踪问题】:仅记录请求方法、URL、响应状态代码以及执行时间。
public class MsProvider8007CustomCfg {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.BASIC;
}
}
3)、HEADERS:记录BASIC级别的基础上,记录请求和响应的header。
public class MsProvider8007CustomCfg {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.HEADERS;
}
}
4)、FULL【比较适用于开发及测试环境定位问题】:记录请求和响应的header、body和元数据。
public class MsProvider8007CustomCfg {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
3.3.2.2、代码中如何自定义:
一:配置类
public class MsProvider8007CustomCfg {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.BASIC;
}
}
@FeignClient(name = "ms-provider-order-feign-custom01",configuration = MsProvider8007CustomCfg.class,path = "/order")
public interface MsCustomFeignOrderApi {
@RequestLine("GET /queryOrdersByUserId/{userId}")
public List<OrderVo> queryOrdersByUserId(@Param("userId") Integer userId);
}
3.3.2.3、yml中如何配置:
feign.client.config.ms-provider-order-feign-custom01.loggerLevel=full
3.3.3、如何支持feign的注解来替换springmvc的注解
feign提供了自己的请求注解:@RequestLine(""),但默认用springmvc的。
示例:
RequestLine("POST /servers")
RequestLine("GET /servers/{serverId}?count={count}")
RequestLine("GET")
RequestLine("POST /servers HTTP/1.1")
eg:
@FeignClient(name="store",configuration=FooConfiguration .class)
public interface UserFeignClient {
@RequestLine("GET /simple/{id}")
public User findById(@Param("id") Long id);
}
启动报错: Method getLinksForTrack not annotated with HTTP method type (ex. GET, POST)
官方文档说明:@RequestLine is a core Feign annotation, but you are using the Spring Cloud @FeignClientwhich uses Spring MVC annotations.
意思就是feign 默认使用的是spring mvc 注解(就是RequestMapping 之类的) ,所以需要通过新增一个配置类来修改其“契约”。
@Configuration
public class FooConfiguration {
@Bean
public Contract feignContract() {
return new feign.Contract.Default();
//使用feign自带契约
}
}
3.3.3.1、代码配置:
public class MsProvider8007CustomCfg {
修改协议契约
@Bean
public Contract feignContract() {
return new Contract.Default();//有没有前面的feign.都行
}
}
@FeignClient(name = "ms-provider-order-feign-custom01",configuration = MsProvider8007CustomCfg.class,path = "/order")
public interface MsCustomFeignOrderApi {
@RequestLine("GET /queryOrdersByUserId/{userId}")
public List<OrderVo> queryOrdersByUserId(@Param("userId") Integer userId);
}
3.3.3.2、配置文件配置:
feign.client.config.ms-provider-order-feign-custom01.contract=feign.Contract.Default
3.3.4、创建拦截器设置公用参数实现:RequestInterceptor
3.3.4.1、编写拦截器
public class TulingInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
System.out.println("自定义拦截器");
template.header("token","123456");
}
}
3.3.4.2、加入拦截器配置
1)、代码配置:
public class MsProvider8007CustomCfg {
@Bean
public RequestInterceptor tulingInterceptor() {
return new TulingInterceptor();
}
}
2)、Yml配置
feign.client.config.ms-provider-order-feign-custom01.requestInterceptors[0]=com.tuling.interceptor.TulingInterceptor
3.3.4.3、服务提供端获取公共参数
注意接口里多出来的参数:@RequestHeader("token") String token
@RequestMapping("/queryOrdersByUserId/{userId}")
public List<OrderVo> queryOrdersByUserId(@PathVariable("userId") Integer userId, @RequestHeader("token") String token) {
System.out.println("TOken:"+token);
return orderServiceImpl.queryOrdersByUserId(userId);
}
3.3.4.4、Feign调用时在客户端(服务消费方)做拦截,将token等添加到请求里,给到服务提供端,的代码实现(实际项目中):
import java.io.IOException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
/**
* Authorizationd传递token.
* <p>
* 用户feign请求过程中,token的传递;
* <pre>
* 通过Feign调用时,默认将请求的服务名加入请求头中
* </pre>
*/
public class TokenCopyInterceptor implements Interceptor {
private static final Logger logger = LoggerFactory.getLogger(TokenCopyInterceptor.class);
/**
* 客户端服务名
*/
private String appName;
public TokenCopyInterceptor(String serviceName) {//在项目启动Bean注解注入拦截器的地方将当前服务名注入。
this.appName = serviceName;
}
/**
* token传递并在header中添加客户端服务名
*/
@Override
public Response intercept(Chain chain) throws IOException {
String accessToken = SecurityUtils.getCurrentUserToken();
Request request;
// 对服务名进行AES加密
String encryptName = "";
if(StringUtils.isNotBlank(appName)) {
encryptName = AESUtil.encrypt(appName, RequestHeadConstants.ENCRYPT_KEY);
}
if (accessToken != null && !"".equals(accessToken)) {
logger.info("开始注入,token[{}].", accessToken);
Request original = chain.request();
Request.Builder requestBuilder = original.newBuilder()
.addHeader(SecurityUtils.OAUTH2_AUTHORIZATION, SecurityUtils.OAUTH2_BEARER_TYPE + " " + SecurityUtils.getCurrentUserToken());
if(StringUtils.isNotBlank(appName)) {
requestBuilder.addHeader(RequestHeadConstants.HEAD_APP_NAME, encryptName);
}
request = requestBuilder.build();
} else {
logger.info("不注入,token[{}].", accessToken);
Request original = chain.request();
Request.Builder requestBuilder = original.newBuilder();
if(StringUtils.isNotBlank(appName)) {
requestBuilder.addHeader(RequestHeadConstants.HEAD_APP_NAME, encryptName);
}
request = requestBuilder.build();
}
logger.info("发送请求,url[{}].", request.url());
Response response = chain.proceed(request);//一直到调用返回,调用超时时间以及出错重试等Ribbon提供了可配置。见本文配置文件等。
logger.info("完成token注入,响应code[{}]", response.code());
return response;
}
}
@Bean
public TokenCopyInterceptor tokenCopyInterceptor() {
log.info("Auto configuration >> okHttp auto configuration interceptor completed");
return new TokenCopyInterceptor(appName);
}
spring cloud feign的各种配置的使用:https://www.iteye.com/blog/huan1993-2424108
Spring Cloud相关配置
#########################
#Spring Cloud相关配置
#########################
eureka:
instance:
prefer-ip-address: true #以IP地址注册到服务中心
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port} #eureka实例id
client:
service-url:
defaultZone: http://localhost:8761/eureka/ #微服务注册中心地址
# actuator监控配置
management:
endpoints:
enabled-by-default: true
web:
exposure:
include: "*"
health:
mail:
enabled: false
#
#Feign启动okhttp3
feign:
httpclient:
enabled: false
okhttp:
enabled: true
hystrix:
enabled: true
#如当前微服务未调用其他的微服务应用,如下配置无效,可删除
#ribbon配置
ribbon:
ConnectTimeout: 10000 #ribbon请求连接的超时时间(ms)
ReadTimeout: 10000 #请求处理的超时时间(ms)
OkToRetryOnAllOperations: true #是否对所有请求操作都进行重试
MaxAutoRetries: 0 #对当前服务的重试次数(第一次分配给实例1的时候,如果404,则再重试MaxAutoRetries次,如果还是404,则切换到其他服务MaxAutoRetriesNextServer决定)
MaxAutoRetriesNextServer: 1 #切换服务的次数(比如本次请求分配给实例1处理,发现404,则切换分配给实例2处理,如果还是404,则返回404给客户端)
serverListRefreshInterval: 2000 #刷新服务列表时间间隔(ms)
eureka:
enabled: true #是否启用eureka列表拉取
#
#Hystrix 配置
hystrix:
command:
default:
execution:
timeout:
enabled: true #Hystrix是否启用超时时间
isolation:
thread:
timeoutInMilliseconds: 10000 #Hystrix断路器的超时时间,默认是1s,断路器的超时时间需要大于ribbon的超时时间,不然不会触发重试。