parent
6b69d06381
commit
3e350dc766
Binary file not shown.
After Width: | Height: | Size: 147 KiB |
@ -0,0 +1,57 @@
|
|||||||
|
package org.example.feign;
|
||||||
|
|
||||||
|
import feign.Logger;
|
||||||
|
import feign.Request;
|
||||||
|
import feign.Retryer;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h1>OpenFeign 配置类</h1>
|
||||||
|
* */
|
||||||
|
@Configuration
|
||||||
|
public class FeignConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h2>开启 OpenFeign 日志</h2>
|
||||||
|
* */
|
||||||
|
@Bean
|
||||||
|
public Logger.Level feignLogger() {
|
||||||
|
return Logger.Level.FULL; // 需要注意, 日志级别需要修改成 debug, 因为 feign 基本都是 debug 级别的日志, info 的日志很少
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <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>
|
||||||
|
* */
|
||||||
|
@Bean
|
||||||
|
public Request.Options options() {
|
||||||
|
|
||||||
|
return new Request.Options(
|
||||||
|
CONNECT_TIMEOUT_MILLS, TimeUnit.MICROSECONDS,
|
||||||
|
READ_TIMEOUT_MILLS, TimeUnit.MILLISECONDS,
|
||||||
|
// 转发请求是否要进行限制
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package org.example.feign;
|
||||||
|
|
||||||
|
import feign.Feign;
|
||||||
|
import okhttp3.ConnectionPool;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h1>OpenFeign 使用 OkHttp 配置类</h1>
|
||||||
|
* 前置条件是 Feign
|
||||||
|
* */
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
package org.example.feign;
|
||||||
|
|
||||||
|
import feign.Feign;
|
||||||
|
import feign.Logger;
|
||||||
|
import feign.gson.GsonDecoder;
|
||||||
|
import feign.gson.GsonEncoder;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.example.common.vo.JwtToken;
|
||||||
|
import org.example.common.vo.UsernameAndPassword;
|
||||||
|
import org.example.service.AuthorityFeignClient;
|
||||||
|
import org.springframework.cloud.client.ServiceInstance;
|
||||||
|
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||||
|
import org.springframework.cloud.openfeign.FeignClient;
|
||||||
|
import org.springframework.cloud.openfeign.support.SpringMvcContract;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h1>使用 Feign 的原生 Api, 而不是 OpenFeign = Feign + Ribbon</h1>
|
||||||
|
* */
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class UseFeignApi {
|
||||||
|
|
||||||
|
private final DiscoveryClient discoveryClient;
|
||||||
|
|
||||||
|
public UseFeignApi(DiscoveryClient discoveryClient) {
|
||||||
|
this.discoveryClient = discoveryClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h2>使用 Feign 原生 api 调用远端服务</h2>
|
||||||
|
* Feign 默认配置初始化、设置自定义配置、生成代理对象
|
||||||
|
* */
|
||||||
|
public JwtToken thinkingInFeign(UsernameAndPassword usernameAndPassword) {
|
||||||
|
|
||||||
|
// 通过反射去拿 serviceId
|
||||||
|
String serviceId = null;
|
||||||
|
Annotation[] annotations = AuthorityFeignClient.class.getAnnotations();
|
||||||
|
for (Annotation annotation : annotations) {
|
||||||
|
if (annotation.annotationType().equals(FeignClient.class)) {
|
||||||
|
serviceId = ((FeignClient) annotation).value();
|
||||||
|
log.info("get service id from AuthorityFeignClient: [{}]", serviceId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果服务 id 不存在, 直接抛异常
|
||||||
|
if (null == serviceId) {
|
||||||
|
throw new RuntimeException("can not get serviceId");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过 serviceId 去拿可用服务实例 OpenFeign 是使用 Ribbon 做这个事
|
||||||
|
List<ServiceInstance> targetInstances = discoveryClient.getInstances(serviceId);
|
||||||
|
if (CollectionUtils.isEmpty(targetInstances)) {
|
||||||
|
throw new RuntimeException("can not get target instance from serviceId: " +
|
||||||
|
serviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 随机选择一个服务实例: 负载均衡
|
||||||
|
ServiceInstance randomInstance = targetInstances.get(
|
||||||
|
new Random().nextInt(targetInstances.size())
|
||||||
|
);
|
||||||
|
log.info("choose service instance: [{}], [{}], [{}]", serviceId,
|
||||||
|
randomInstance.getHost(), randomInstance.getPort());
|
||||||
|
|
||||||
|
// Feign 客户端初始化, 必须要配置 encoder、decoder、contract
|
||||||
|
// 默认的 encoder、decoder 不支持对象,不能实现编解码, 所以要自己进行定义
|
||||||
|
// 默认的 contract 默认的协议不支持 SpringCloud 对应的 Feign 接口
|
||||||
|
AuthorityFeignClient feignClient = Feign.builder() // 1. Feign 默认配置初始化
|
||||||
|
.encoder(new GsonEncoder()) // 2.1 设置自定义配置
|
||||||
|
.decoder(new GsonDecoder()) // 2.2 设置自定义配置
|
||||||
|
.logLevel(Logger.Level.FULL) // 2.3 设置自定义配置
|
||||||
|
.contract(new SpringMvcContract())
|
||||||
|
.target( // 3 生成代理对象
|
||||||
|
AuthorityFeignClient.class,
|
||||||
|
String.format("http://%s:%s",
|
||||||
|
randomInstance.getHost(), randomInstance.getPort())
|
||||||
|
);
|
||||||
|
|
||||||
|
return feignClient.getTokenByFeign(usernameAndPassword);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package org.example.ribbon;
|
||||||
|
|
||||||
|
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h1>使用 Ribbon 之前的配置, 增强 RestTemplate</h1>
|
||||||
|
* */
|
||||||
|
@Component
|
||||||
|
public class RibbonConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h2>注入 RestTemplate</h2>
|
||||||
|
* */
|
||||||
|
@Bean
|
||||||
|
@LoadBalanced
|
||||||
|
public RestTemplate restTemplate() {
|
||||||
|
return new RestTemplate();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package org.example.service;
|
||||||
|
|
||||||
|
import org.example.common.vo.JwtToken;
|
||||||
|
import org.example.common.vo.UsernameAndPassword;
|
||||||
|
import org.springframework.cloud.openfeign.FeignClient;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h1>与 Authority 服务通信的 Feign Client 接口定义</h1>
|
||||||
|
* */
|
||||||
|
@FeignClient(
|
||||||
|
// contextId 是对 FeignClient 的声明, 每一个进行的通信都要进行定义, value 表示需要进行通信的服务id是什么
|
||||||
|
contextId = "AuthorityFeignClient", value = "e-commerce-authority-center"
|
||||||
|
// fallback = AuthorityFeignClientFallback.class
|
||||||
|
// fallbackFactory = AuthorityFeignClientFallbackFactory.class
|
||||||
|
)
|
||||||
|
public interface AuthorityFeignClient {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h2>通过 OpenFeign 访问 Authority 获取 Token</h2>
|
||||||
|
*
|
||||||
|
* value 只需要定义路径即可, 不需要定义 ip + port
|
||||||
|
* consumes, produces: 标识请求和返回的数据格式, 可以不指定, 但是在使用原生 Api 接口的时候必须要进行指定, 要不会不能进行识别对应的接口
|
||||||
|
* */
|
||||||
|
@RequestMapping(value = "/ecommerce-authority-center/authority/token",
|
||||||
|
method = RequestMethod.POST,
|
||||||
|
consumes = "application/json", produces = "application/json")
|
||||||
|
JwtToken getTokenByFeign(@RequestBody UsernameAndPassword usernameAndPassword);
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
package org.example.service;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.netflix.loadbalancer.*;
|
||||||
|
import com.netflix.loadbalancer.reactive.LoadBalancerCommand;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.example.common.constant.CommonConstant;
|
||||||
|
import org.example.common.vo.JwtToken;
|
||||||
|
import org.example.common.vo.UsernameAndPassword;
|
||||||
|
import org.springframework.cloud.client.ServiceInstance;
|
||||||
|
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
import rx.Observable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h1>使用 Ribbon 实现微服务通信</h1>
|
||||||
|
* */
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class UseRibbonService {
|
||||||
|
|
||||||
|
private final RestTemplate restTemplate;
|
||||||
|
private final DiscoveryClient discoveryClient;
|
||||||
|
|
||||||
|
public UseRibbonService(RestTemplate restTemplate,
|
||||||
|
DiscoveryClient discoveryClient) {
|
||||||
|
this.restTemplate = restTemplate;
|
||||||
|
this.discoveryClient = discoveryClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
# Feign 的相关配置
|
||||||
|
feign:
|
||||||
|
# feign 开启 gzip 压缩
|
||||||
|
compression:
|
||||||
|
request:
|
||||||
|
enabled: true # 对请求的数据开启压缩
|
||||||
|
mime-types: text/xml,application/xml,application/json # 压缩的数据类型
|
||||||
|
min-request-size: 1024 # 最小多大的请求数据开启压缩
|
||||||
|
response:
|
||||||
|
enabled: true # 对响应的数据开启压缩
|
||||||
|
# 禁用默认的 http, 启用 okhttp
|
||||||
|
httpclient:
|
||||||
|
enabled: false
|
||||||
|
okhttp:
|
||||||
|
enabled: true
|
||||||
|
# # OpenFeign 集成 Hystrix
|
||||||
|
# hystrix:
|
||||||
|
# enabled: true
|
Loading…
Reference in New Issue