目录
整合Spring-Cloud-Alibaba
服务发现nacos
实现负载均衡Ribbon
声明式HTTP客户端Feign
服务容错Sentinel
消息驱动的微服务SpringcloudAlibabaRocketMQ
API网关SpringCloudGateway
版本对齐
整合Spring-Cloud-Alibaba
- 整合Spring Cloud
- 在pom.xml加入
<properties>
<spring.cloud-version>Hoxton.SR8</spring.cloud-version>
</properties>
<dependencyManagement>
<dependencies>
<!--整合spring-cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencyManagement>
- 整合Spring Cloud Alibaba
根据使用的spring-cloud版本查询对应的Spring Cloud Alibaba的版本spring-cloud-alibaba组件版本关系
<properties>
<spring.cloud-version>Hoxton.SR8</spring.cloud-version>
</properties>
<dependencyManagement>
<dependencies>
<!--整合spring-cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--整合spring-cloud-alibaba-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.3.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
服务发现 nacos
- 搭建Nacos Server
下载spring-cloud-alibaba对应的nacos版本
- 加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.9.0.RELEASE</version>
</dependency>
- 加配置yml
spring:
cloud:
nacos:
discovery:
# 指定nacos server地址,不要加http/https
server-addr: 47.98.37.74:8848
application:
# 服务名称尽量用一,不要用_,不要用特殊字符
name: cloud-horizon-user
/**
* 测试: 服务发现,证明内容中心总能找到用户中心
* @return 商城中心所有实例的地址信息
*/
@GetMapping("/findAllInstance")
public Result findAllInstance(){
//查询指定服务的所有实例信息
List<ServiceInstance> instances = discoveryClient.getInstances("cloud-horizon-store");
return Result.success(instances);
}
RestTemplate 请求服务调用
- 整合RestTemplate
/**
* @author rp
*/
@Configuration
public class RestTemplateConfig {
/**
* @SentinelRestTemplate RestTemplate整合Sentinel
* */
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory){
return new RestTemplate(factory);
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(15000);
factory.setReadTimeout(10000);
return factory;
}
}
- 案例
@GetMapping("/restTemplateStore")
public Result restTemplateStore(){
String url = discoveryClient.getInstances("cloud-horizon-store").stream()
.map(instance -> instance.getUri().toString() + "cloud_horizon_store/userShop/test")
.findFirst()
.orElseThrow(() -> new CloudHorizonException(ResultEnum.NOT_FIND_INSTANCE));
JSONObject forObject = restTemplate.getForObject(url, JSONObject.class);
assert forObject != null;
return Result.successJsonToList(forObject,UserShopDTO.class);
}
- 服务发现的领域模型
spring:
cloud:
nacos:
discovery:
# 指定nacos server地址
server-addr: 47.98.37.74:8848
# 指定namespace
namespace: c01f509a-f12d-45ba-ad18-4a4ee6f65ad1
# 指定集群名称
cluster-name: CD
# 元数据
metadata:
auth: Mr培
vaersion: 1
实现负载均衡 Ribbon
- 随机负载均衡编码实现
/**
* 随机负载均衡
* */
@GetMapping("/randomRestTemplateStore")
public Result randomRestTemplateStore(){
List<String> urls = discoveryClient.getInstances("cloud-horizon-store").stream()
.map(instance -> instance.getUri().toString() + "cloud_horizon_store/userShop/test").collect(Collectors.toList());
int i = ThreadLocalRandom.current().nextInt(urls.size());
JSONObject forObject = restTemplate.getForObject(urls.get(i), JSONObject.class);
assert forObject != null;
return Result.successJsonToList(forObject,UserShopDTO.class);
}
- Ribbon 实现负载均衡
- 为RestTemplate整合Ribbon
/** * @LoadBalanced RestTemplate整合Sentinel * */ @Bean @LoadBalanced public RestTemplate restTemplate(ClientHttpRequestFactory factory){ return new RestTemplate(factory); }
- 重构代码
/** * ribbon重构 随机负载均衡 * */ @GetMapping("/ribbonRestTemplateStore") public Result ribbonRestTemplateStore(){ JSONObject forObject = restTemplate.getForObject("http://cloud-horizon-store/cloud_horizon_store/userShop/test", JSONObject.class); assert forObject != null; return Result.successJsonToList(forObject,UserShopDTO.class); }
- 细粒度配置自定义
1. Java代码配置 方式
创建configuration包,创建CloudHorizonStoreRibbonConfiguration类
/**
* ribbon规则代码配置
* RibbonClients 全局配置方式
* @author rp
*/
@Configuration
@RibbonClient(name = "cloud-horizon-store",configuration = RibbonConfiguration.class)
public class CloudHorizonStoreRibbonConfiguration {
}
创建ribbonconfiguration包,必须在启动类所在包之外,创建RibbonConfiguration
/**
* nacos调用规则
* @author rp
*/
@Configuration
public class RibbonConfiguration {
// 随机规则
// @Bean
// public IRule ribbonRule(){
// return new RandomRule();
// }
// 权重规则
// @Bean
// public IRule ribbonRule(){
// return new NacosWeightedRule();
// }
@Bean
public IRule ribbonRule(){
return new NacosSameClusterWeightedRule();
}
}
父子上下文重叠会造成事物不生效,重复扫描
2. 配置属性 方式
# ribbon规则属性配置
cloud-horizon-user:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
3. 全局配置 方式
/**
* ribbon规则代码配置
* RibbonClients 全局配置方式
* @author rp
*/
@Configuration
@RibbonClients(defaultConfiguration = RibbonConfiguration.class)
public class CloudHorizonStoreRibbonConfiguration {
}
- Ribbon 饥饿加载
第一次请求非常慢,开启饥饿加载
# 饥饿加载
# clients 请求哪些微服务的时候饥饿加载
ribbon:
eager-load:
enabled: true
clients: cloud-horizon-user,cloud-horizon-store
- 扩展Ribbon-权重支持
创建 NacosWeightedRule 类
/**
* ribbon支持nacos权重
* @author rp
*/
public class NacosWeightedRule implements IRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
private ILoadBalancer lb;
@Override
public Server choose(Object key) {
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
// 想要请求的微服务的名称
String name = loadBalancer.getName();
//拿到服务发现的相关API
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
// nacos client自动通过基 于权重的负载均衡算法,给我们选择一个实例
try {
Instance instance = namingService.selectOneHealthyInstance(name);
return new NacosServer(instance);
} catch (NacosException e) {
e.printStackTrace();
return null;
}
}
@Override
public void setLoadBalancer(ILoadBalancer lb) {
this.lb = lb;
}
@Override
public ILoadBalancer getLoadBalancer() {
return lb;
}
}
修改规则配置 RibbonConfiguration
/**
* nacos调用规则
* @author rp
*/
@Configuration
public class RibbonConfiguration {
@Bean
public IRule ribbonRule(){
return new NacosWeightedRule();
}
}
- 扩展Ribbon-同集群优先
属性配置集群名称
spring:
cloud:
nacos:
discovery:
# 指定nacos server地址
server-addr: 47.98.37.74:8848
#指定集群名称
cluster-name: CD
在configuration包下创建 NacosSameClusterWeightedRule类
/**
* 相同集群调用
* @author rp
*/
public class NacosSameClusterWeightedRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object key) {
try {
#拿到配置文件中的集群名称CD
String clusterName = nacosDiscoveryProperties.getClusterName();
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
//想要请求的微服务的名称
String name = loadBalancer.getName();
//拿到服务发现的相关API
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
// 1.找到指定服务的所有实例A
List<Instance> instances = namingService.selectInstances(name, true);
// 2.过滤出相同集群下的所有实例B
List<Instance> sameClusterInstances = instances.stream().filter(instance -> Objects.equals(instance.getClusterName(), clusterName)).collect(Collectors.toList());
// 3.如果B是空,就用A
List<Instance> instancesToBeChosen = new ArrayList<>();
if (CollectionUtil.isEmpty(sameClusterInstances)){
instancesToBeChosen = instances;
}else{
instancesToBeChosen = sameClusterInstances;
}
// 4.基于权重的负载均衡算法,返回1个实例
Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToBeChosen);
return new NacosServer(instance);
} catch (NacosException e) {
e.printStackTrace();
return null;
}
}
}
class ExtendBalancer extends Balancer {
public static Instance getHostByRandomWeight2(List<Instance> hosts){
return getHostByRandomWeight(hosts) ;
}
}
修改nacos调用规则
/**
* nacos调用规则
* @author rp
*/
@Configuration
public class RibbonConfiguration {
@Bean
public IRule ribbonRule(){
return new NacosSameClusterWeightedRule();
}
}
- 扩展Ribbon-基于元数据的版本控制
属性文件配置元数据
spring:
cloud:
nacos:
discovery:
metadata:
auth: Mr培
vaersion: 1
- 深入理解Nacos的Namespace
属性文件配置Namespace
spring:
cloud:
nacos:
discovery:
# 指定namespace
namespace: c01f509a-f12d-45ba-ad18-4a4ee6f65ad1
服务之间不能跨Namespace调用
声明式HTTP客户端-Feign
- 使用Feign实现远程HTTP调用
- 加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 在启动类上加注解 @EnableFeignClients
/**
* @author rp
*/
@EnableAsync
@SpringBootApplication
@EnableFeignClients
public class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
}
- 编写HTTP请求
创建feignclient包,创建interface接口 CloudHorizonStoreFeignClient类
/**
* name 请求微服务的名称
* @author rp
*/
@FeignClient(name = "cloud-horizon-store")
public interface CloudHorizonStoreFeignClient {
/**
* http://cloud-horizon-store/cloud_horizon_store/userShop/test
* 获取所有用户店铺信息
* @return
*/
@GetMapping("/cloud_horizon_store/userShop/test")
@SentinelResource("findUserShopAll")
JSONObject findUserShopAll();
}
- Feign的组成
自定义Feign日志级别
- Java代码方式
修改feign的interface接口文件添加
onfiguration = CloudHorizonStoreFeighConfiguration.class
/**
* @author rp
*/
@FeignClient(name = "cloud-horizon-store",configuration = CloudHorizonStoreFeighConfiguration.class)
public interface CloudHorizonStoreFeignClient {
/**
* 获取所有用户店铺信息
* @return
*/
@GetMapping("/cloud_horizon_store/userShop/test")
@SentinelResource("findUserShopAll")
JSONObject findUserShopAll();
}
在configuration包下编写 CloudHorizonStoreFeighConfiguration 配置类
/**
* feign的配置类
* 这个类别加@Configuration注解了,否则必须挪到@ComponentScan能扫描的包以外
* 父子上下文重复扫描问题
* @author rp
*/
public class CloudHorizonStoreFeighConfiguration {
@Bean
public Logger.Level level(){
// 让feign打印所有请求的细节
return Logger.Level.FULL;
}
}
将CloudHorizonStoreFeighConfiguration配置类通过属性文件配置logging
logging:
level:
com:
cloud:
horizon:
server:
domain:
mapper: DEBUG
com.cloud.horizon.user.server.feignclient.CloudHorizonStoreFeignClient: DEBUG
属性配置方式
feign:
client:
config:
#想要调用的微服务的名称
#cloud-horizon-store:
#loggerLevel: full
#全局配置
default:
loggerLevel: full
通过代码配置全局日志,在启动类上@EnableFeignClients注解添加defaultConfiguration属性
/**
* @author rp
*/
@EnableAsync
@SpringBootApplication
//全局配置feign日志级别
@EnableFeignClients(defaultConfiguration = CloudHorizonStoreFeighConfiguration.class)
public class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
}
总结
- Feign的继承
- Feign多参数请求组构造
跟springMVC用法一样
GET(其它传参方式百度)
- 提供服务方
@GetMapping("/findUserShop")
public Result findUserShop(ShopDTO shopDTO){
return Result.success(shopDTO);
}
- 调用方
@Autowired
private StoreCloudHorizonStoreFeignClient storeCloudHorizonStoreFeignClient;
@GetMapping("/findUserShop")
public Result findUserShop(ShopDTO shopDTO){
JSONObject forObject = storeCloudHorizonStoreFeignClient.findUserShop(shopDTO);
return Result.successJsonToList(forObject,UserShopDTO.class);
}
- feign接口
/**
* @author rp
*/
@FeignClient(name = "cloud-horizon-store")
public interface StoreCloudHorizonStoreFeignClient {
/**
* 获取所有用户店铺信息
* @return
*/
@GetMapping("/cloud_horizon_store/userShop/findUserShop")
JSONObject findUserShop(@SpringQueryMap ShopDTO shopDTO);
}
- 配置文件加注解,解决FeignClient重名问题(@FeignClient(name = "cloud-horizon-store"))
spring:
main:
allow-bean-definition-overriding: true
POST(自行百度)
跟springMVC用法一样
- Feign脱离Ribbon使用
@FeignClient(name = "baidu",url="http://www.baidu.com")
public interface TestBaiduFeignClient {
@GetMapping("/baidu")
public String index();
}
@FeignClient(name = "baidu",url="http://www.baidu.com") name必须存在
-
RestTemplate和Feign对比
Feign性能优化
连接池
- 加依赖
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
- 写配置
feign:
client:
config:
#全局配置
default:
loggerLevel: full
httpclient:
#让feign使用apache httpclient做请求;而不是默认的urlconnection
enabled: true
#feign的最大连接数
max-connections: 200
#feign单个路径的最大连接数
max-connections-per-route: 50
日志级别
不建议设置日志级别为full
- Feign其它问题,百度学习
服务容错-Sentinel
- 整合Sentinel
- 加依赖为应用整合sentinel
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
- 加依赖为应用整合actuator
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 加配置开启actuator
management:
#endpoint:
#health:
#show-details: always
#激活所有的actuator端点
endpoints:
web:
exposure:
include: '*'
访问 localhost:8010/actuator/sentinel
- Sentinel控制台
- 搭建Sentinel控制台
下载地址
下载与项目相同版本Sentinel
通过命令启动
java -jar sentinel-dashboard-1.6.2.jar
- 为应用整合控制台
spring:
cloud:
sentinel:
filter:
#关闭掉对Spring MVC端点的保护
enabled: false
transport:
#指定sentinel控制台的地址
dashboard: localhost:8085
相关文章
sentinel 控制台监控空白的问题
- 触点链路暴露 @SentinelResource("findUserShopAll")
/**
* @author rp
*/
@FeignClient(name = "cloud-horizon-store")
public interface CloudHorizonStoreFeignClient {
/**
* 获取所有用户店铺信息
* @return
*/
@GetMapping("/cloud_horizon_store/userShop/test")
@SentinelResource("findUserShopAll")
JSONObject findUserShopAll();
}
-
Sentinel具体用法百度
RestTemplate整合Sentinel
为RestTemplate加上@SentinelRestTemplate注解
/**
* @SentinelRestTemplate RestTemplate整合Sentinel
* */
@Bean
@LoadBalanced
@SentinelRestTemplate
public RestTemplate restTemplate(ClientHttpRequestFactory factory){
return new RestTemplate(factory);
}
关闭@SentinelRestTemplate注解
resttemplate :
sentinel :
#关闭@SentinelRestTemplate注解
enabled: false
限流友好提示 百度
@SentinelResource(value = "/blockTemplateStore",blockHandler = "block",fallback = "fallback")
blockHandler 限流或者降级,fallback降级
/**
* 发生降级时,定制处理
* */
@GetMapping("/blockTemplateStore")
@SentinelResource(value = "/blockTemplateStore",blockHandler = "block",fallback = "fallback")
public Result blockTemplateStore(){
JSONObject forObject = restTemplate.getForObject("http://cloud-horizon-store/cloud_horizon_store/userShop/test", JSONObject.class);
assert forObject != null;
return Result.successJsonToList(forObject,UserShopDTO.class);
}
/**
* 处理限流或降级
* 用户相同的返回值,参数
* */
public Result block(BlockException e) {
log.warn("限流,或者降级了",e);
return Result.success();
}
/**
* 处理降级 sentinel 1.6以上 可以处理Throwable(所有异常)
* 用户相同的返回值,参数 ,不能抛异常
* */
public Result fallback() {
log.warn("降级了");
return Result.success();
}
限流或降级 独立处理 blockHandlerClass 属性
@SentinelResource(value = "/blockTemplateStore",
fallback = "fallback",
blockHandlerClass = SentinelBlockHandlerClass.class)
创建 SentinelBlockHandlerClass类
/**
* @author rp
*/
@Slf4j
public class SentinelBlockHandlerClass {
/**
* 用户相同的返回值,参数
* */
public static Result block(BlockException e) {
log.warn("限流,或者降级了",e);
return Result.success();
}
}
sentinel 1.6 以上支持 fallbackClass
- Feign整合Sentinel
添加配置属性
feign:
sentinel:
# 为feign整合sentinel
enabled: true
限流降级发生时,如何定制自己的逻辑处理?
- 修改CloudHorizonStoreFeignClient类,添加属性 fallback
/**
* @author rp
*/
@FeignClient(name = "cloud-horizon-store",fallback = CloudHorizonStoreFeignClientFallback.class)
public interface CloudHorizonStoreFeignClient {
/**
* 获取所有用户店铺信息
* @return
*/
@GetMapping("/cloud_horizon_store/userShop/test")
@SentinelResource(value = "findUserShopAll")
JSONObject findUserShopAll();
}
- 创建 CloudHorizonStoreFeignClientFallback类
/**
* @author rp
*/
@Component
public class CloudHorizonStoreFeignClientFallback implements CloudHorizonStoreFeignClient{
@Override
public JSONObject findUserShopAll() {
//处理逻辑,比如返回一个默认对象 JSONObject
return null;
}
}
- 添加属性fallbackFactory,fallbackFactory 比 fallback强大,二者存其一
fallbackFactory可以拿到异常
/**
* @author rp
*/
//@FeignClient(name = "cloud-horizon-store",configuration = CloudHorizonStoreFeighConfiguration.class)
@FeignClient(name = "cloud-horizon-store",
// fallback = CloudHorizonStoreFeignClientFallback.class,
fallbackFactory = CloudHorizonStoreFeignClientFallbackFactory.class)
public interface CloudHorizonStoreFeignClient {
/**
* 获取所有用户店铺信息
* @return
*/
@GetMapping("/cloud_horizon_store/userShop/test")
@SentinelResource(value = "findUserShopAll")
JSONObject findUserShopAll();
}
- 创建CloudHorizonStoreFeignClientFallbackFactory类
/**
* @author rp
*/
@Slf4j
@Component
public class CloudHorizonStoreFeignClientFallbackFactory implements FallbackFactory<CloudHorizonStoreFeignClient> {
@Override
public CloudHorizonStoreFeignClient create(Throwable throwable) {
return new CloudHorizonStoreFeignClient(){
@Override
public JSONObject findUserShopAll() {
log.warn("远程调用被限流/降级了",throwable);
//处理逻辑,比如返回一个默认对象 JSONObject
return null;
}
};
}
}
-
Sentinel使用姿势总结
规则持久化-拉模式
Alibaba Sentinel规则持久化-拉模式-手把手教程【基于文件】
- 加依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-extension</artifactId>
</dependency>
- 写代码
/**
* 拉模式规则持久化
*
* @author itmuch.com
*/
public class FileDataSourceInit implements InitFunc {
@Override
public void init() throws Exception {
// TIPS: 如果你对这个路径不喜欢,可修改为你喜欢的路径
String ruleDir = System.getProperty("user.home") + "/sentinel/rules";
String flowRulePath = ruleDir + "/flow-rule.json";
String degradeRulePath = ruleDir + "/degrade-rule.json";
String systemRulePath = ruleDir + "/system-rule.json";
String authorityRulePath = ruleDir + "/authority-rule.json";
String paramFlowRulePath = ruleDir + "/param-flow-rule.json";
this.mkdirIfNotExits(ruleDir);
this.createFileIfNotExits(flowRulePath);
this.createFileIfNotExits(degradeRulePath);
this.createFileIfNotExits(systemRulePath);
this.createFileIfNotExits(authorityRulePath);
this.createFileIfNotExits(paramFlowRulePath);
// 流控规则
ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new FileRefreshableDataSource<>(
flowRulePath,
flowRuleListParser
);
// 将可读数据源注册至FlowRuleManager
// 这样当规则文件发生变化时,就会更新规则到内存
FlowRuleManager.register2Property(flowRuleRDS.getProperty());
WritableDataSource<List<FlowRule>> flowRuleWDS = new FileWritableDataSource<>(
flowRulePath,
this::encodeJson
);
// 将可写数据源注册至transport模块的WritableDataSourceRegistry中
// 这样收到控制台推送的规则时,Sentinel会先更新到内存,然后将规则写入到文件中
WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);
// 降级规则
ReadableDataSource<String, List<DegradeRule>> degradeRuleRDS = new FileRefreshableDataSource<>(
degradeRulePath,
degradeRuleListParser
);
DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());
WritableDataSource<List<DegradeRule>> degradeRuleWDS = new FileWritableDataSource<>(
degradeRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS);
// 系统规则
ReadableDataSource<String, List<SystemRule>> systemRuleRDS = new FileRefreshableDataSource<>(
systemRulePath,
systemRuleListParser
);
SystemRuleManager.register2Property(systemRuleRDS.getProperty());
WritableDataSource<List<SystemRule>> systemRuleWDS = new FileWritableDataSource<>(
systemRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS);
// 授权规则
ReadableDataSource<String, List<AuthorityRule>> authorityRuleRDS = new FileRefreshableDataSource<>(
authorityRulePath,
authorityRuleListParser
);
AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty());
WritableDataSource<List<AuthorityRule>> authorityRuleWDS = new FileWritableDataSource<>(
authorityRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS);
// 热点参数规则
ReadableDataSource<String, List<ParamFlowRule>> paramFlowRuleRDS = new FileRefreshableDataSource<>(
paramFlowRulePath,
paramFlowRuleListParser
);
ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty());
WritableDataSource<List<ParamFlowRule>> paramFlowRuleWDS = new FileWritableDataSource<>(
paramFlowRulePath,
this::encodeJson
);
ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);
}
private Converter<String, List<FlowRule>> flowRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<FlowRule>>() {
}
);
private Converter<String, List<DegradeRule>> degradeRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<DegradeRule>>() {
}
);
private Converter<String, List<SystemRule>> systemRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<SystemRule>>() {
}
);
private Converter<String, List<AuthorityRule>> authorityRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<AuthorityRule>>() {
}
);
private Converter<String, List<ParamFlowRule>> paramFlowRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<ParamFlowRule>>() {
}
);
private void mkdirIfNotExits(String filePath) throws IOException {
File file = new File(filePath);
if (!file.exists()) {
file.mkdirs();
}
}
private void createFileIfNotExits(String filePath) throws IOException {
File file = new File(filePath);
if (!file.exists()) {
file.createNewFile();
}
}
private <T> String encodeJson(T t) {
return JSON.toJSONString(t);
}
}
- 配置
在resources下创建目录META-INF/services,创建文件
com.alibaba.csp.sentinel.init.InitFunc
内容改成上面FileDataSourceInit的包名类名全路径即可。
com.itmuch.contentcenter.FileDataSourceInit
- 规则持久化-推模式
Alibaba Sentinel规则持久化-推模式-手把手教程【基于Nacos】
-
集群流控
扩展Sentinel-错误页优化
@Component
public class MyUrlBlockHandler implements UrlBlockHandler {
@Override
public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws IOException {
ErrorMsg msg = null;
if (ex instanceof FlowException) {
msg = ErrorMsg.builder()
.status(100)
.msg("限流了")
.build();
} else if (ex instanceof DegradeException) {
msg = ErrorMsg.builder()
.status(101)
.msg("降级了")
.build();
} else if (ex instanceof ParamFlowException) {
msg = ErrorMsg.builder()
.status(102)
.msg("热点参数限流")
.build();
} else if (ex instanceof SystemBlockException) {
msg = ErrorMsg.builder()
.status(103)
.msg("系统规则(负载/...不满足要求)")
.build();
} else if (ex instanceof AuthorityException) {
msg = ErrorMsg.builder()
.status(104)
.msg("授权规则不通过")
.build();
}
// http状态码
response.setStatus(500);
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Type", "application/json;charset=utf-8");
response.setContentType("application/json;charset=utf-8");
// spring mvc自带的json操作工具,叫jackson
new ObjectMapper()
.writeValue(
response.getWriter(),
msg
);
}
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
class ErrorMsg {
private Integer status;
private String msg;
}
- 扩展Sentinel-实现区分来源
@Component
public class MyRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
// 从请求参数中获取名为 origin 的参数并返回
// 如果获取不到origin参数,那么就抛异常
String origin = request.getParameter("origin");
if (StringUtils.isBlank(origin)) {
throw new IllegalArgumentException("origin must be specified");
}
return origin;
}
}
- 扩展Sentinel-RESTfulURL支持
@Component
@Slf4j
public class MyUrlCleaner implements UrlCleaner {
@Override
public String clean(String originUrl) {
// 让 /shares/1 与 /shares/2 的返回值相同
// 返回/shares/{number}
String[] split = originUrl.split("/");
return Arrays.stream(split)
.map(string -> {
if (NumberUtils.isNumber(string)) {
return "{number}";
}
return string;
})
.reduce((a, b) -> a + "/" + b)
.orElse("");
}
}
消息驱动的微服务-SpringCloudAlibabaRocketMQ
- 搭建RocketMQ
- 搭建RocketMQ控制台
- RocketMQ的详细使用教程
- Spring消息编程模型-生产者
- 加依赖,注意版本兼容
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.3</version>
</dependency>
- 写配置
rocketmq:
name-server: 127.0.0.1:9876
producer:
#小坑:必須指定group
group: test-group
- 写代码 RocketMQTemplate 发送消息
private final RocketMQTemplate rocketMQTemplate;
public ShareDTO update() {
# 发送消息
rocketMQTemplate.converAndSend("add-integarl",UserAddBonusMsgDTO.builder()
.userId(share.getUserId())
.bonus(50)
.build());
}
- Spring消息编程模型-消费者
- 加依赖,注意版本兼容
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.3</version>
</dependency>
- 写配置
rocketmq:
name-server: 127.0.0.1:9876
- 写代码 Listener
生产者的Group通过配置文件编写,消费者的Group通过consumerGroup属性配置,topic 要与生产者的一致
@Service
@RocketMQMessageListener(consumerGroup = "consumer-group", topic = "add-integral")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class AddBonusListener implements RocketMQListener<UserAddBonusMsgDTO> {
@Override
public void onMessage(UserAddBonusMsgDTO message) {
//为用户加积分
//记录日志
}
}
⚠️⚠️⚠️
-
实现分布式事物消息
编码实现
- 创建rocketMq事务表 rocketmq_transaction_log
USE `content_center`;
create table rocketmq_transaction_log
(
id int auto_increment comment 'id' primary key,
transaction_id varchar(45) not null comment '事努id',
log varchar(45) not null comment '日志'
)
comment 'RocketMQ事务日志表';
- 写代码,发送半消息(Spring Cloud Stream)
rocketMQTemplate.sendMessageInTransaction("tx-add-bonus-group","add-integral",MessageBuilder
.withPayload(
UserAddBonusMsgDTO.builder()
.userId(share.getUserId())
.bonus(50)
.build()
)
// header也有妙用...
.setHeader(RocketMQHeaders.TRANSACTION_ID, transactionId)
.setHeader("share_id", id)
.setHeader("dto", JSON.toJSONString(auditDTO))
.build());
private final RocketmqTransactionLogMapper rocketmqTransactionLogMapper;
private final Source source;
public Share auditById(Integer id, ShareAuditDTO auditDTO) {
// 1. 查询share是否存在,不存在或者当前的audit_status != NOT_YET,那么抛异常
Share share = this.shareMapper.selectByPrimaryKey(id);
if (share == null) {
throw new IllegalArgumentException("参数非法!该分享不存在!");
}
if (!Objects.equals("NOT_YET", share.getAuditStatus())) {
throw new IllegalArgumentException("参数非法!该分享已审核通过或审核不通过!");
}
// 3. 如果是PASS,那么发送消息给rocketmq,让用户中心去消费,并为发布人添加积分
if (AuditStatusEnum.PASS.equals(auditDTO.getAuditStatusEnum())) {
// 发送半消息。。
String transactionId = UUID.randomUUID().toString();
this.source.output()
.send(
MessageBuilder
.withPayload(
UserAddBonusMsgDTO.builder()
.userId(share.getUserId())
.bonus(50)
.build()
)
// header也有妙用...
.setHeader(RocketMQHeaders.TRANSACTION_ID, transactionId)
.setHeader("share_id", id)
.setHeader("dto", JSON.toJSONString(auditDTO))
.build()
);
}
else {
this.auditByIdInDB(id, auditDTO);
}
return share;
}
@Transactional(rollbackFor = Exception.class)
public void auditByIdInDB(Integer id, ShareAuditDTO auditDTO) {
Share share = Share.builder()
.id(id)
.auditStatus(auditDTO.getAuditStatusEnum().toString())
.reason(auditDTO.getReason())
.build();
this.shareMapper.updateByPrimaryKeySelective(share);
// 4. 把share写到缓存
}
@Transactional(rollbackFor = Exception.class)
public void auditByIdWithRocketMqLog(Integer id, ShareAuditDTO auditDTO, String transactionId) {
this.auditByIdInDB(id, auditDTO);
this.rocketmqTransactionLogMapper.insertSelective(
RocketmqTransactionLog.builder()
.transactionId(transactionId)
.log("审核分享...")
.build()
);
}
- 事务的检查
@RocketMQTransactionListener(txProducerGroup = "tx-add-bonus-group")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class AddBonusTransactionListener implements RocketMQLocalTransactionListener {
private final ShareService shareService;
private final RocketmqTransactionLogMapper rocketmqTransactionLogMapper;
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
MessageHeaders headers = msg.getHeaders();
String transactionId = (String) headers.get(RocketMQHeaders.TRANSACTION_ID);
Integer shareId = Integer.valueOf((String) headers.get("share_id"));
String dtoString = (String) headers.get("dto");
ShareAuditDTO auditDTO = JSON.parseObject(dtoString, ShareAuditDTO.class);
try {
this.shareService.auditByIdWithRocketMqLog(shareId, auditDTO, transactionId);
return RocketMQLocalTransactionState.COMMIT;
} catch (Exception e) {
return RocketMQLocalTransactionState.ROLLBACK;
}
}
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
MessageHeaders headers = msg.getHeaders();
String transactionId = (String) headers.get(RocketMQHeaders.TRANSACTION_ID);
// select * from xxx where transaction_id = xxx
RocketmqTransactionLog transactionLog = this.rocketmqTransactionLogMapper.selectOne(
RocketmqTransactionLog.builder()
.transactionId(transactionId)
.build()
);
if (transactionLog != null) {
return RocketMQLocalTransactionState.COMMIT;
}
return RocketMQLocalTransactionState.ROLLBACK;
}
}
Spring Cloud Stream 框架
Spring Cloud Stream编写生产者
- 加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
</dependency>
- 启动类加注解 @EnableBinding({Source.class})
- 写配置
spring:
cloud:
stream:
rocketmq:
binder:
name-server: 127.0.0.1:9876
bindings:
output:
# 用来指定topic
destination: add-bonus
- 发送消息
private final Source source;
@GetMapping("/test-stream")
public String testStream() {
source.output()
.send(
MessageBuilder
.withPayload("消息体")
.build()
);
return "success";
}
- 关闭nacos心跳日志,设计日志级别为error
logging:
level:
com.alibaba.nacos: error
- Spring Cloud Stream编写消费者
- 加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
</dependency>
- 加依赖
- 启动类加注解 @EnableBinding({Sink.class})
- 写配置
spring:
cloud:
stream:
rocketmq:
binder:
name-server: 127.0.0.1:9876
bindings:
input:
# 与内容中心一致
destination: stream-test-topic
# 如果用的是RocketMq,一定要设置
# 如果用的是其它MQ,可留空
group: binder-group
- 消费消息
@Service
@Slf4j
public class TestStreamConsumer {
private final UserService userService;
@StreamListener(Sink.INPUT)
public void receive(String message) {
log.info("通过stream收到了消息:{}",message);
}
}
- Spring Cloud Stream接口自定义
- 自定义接口实现消息生产者
- 创建interface
public interface MySource{
//my-output 要与配置文件一致
String MY_OUTPUT = ”my-output“;
@Output(MY_OUTPUT)
MessageChannel output();
}
- 在启动类引用 MySource.class
@EnableBinding({Source.class,MySource.class})
- 加配置
spring:
cloud:
stream:
rocketmq:
binder:
name-server: 127.0.0.1:9876
bindings:
output:
# 用来指定topic
destination: add-bonus
my-output:
destination: stream-my-topic
- 测试代码
private final MySource mySource;
@GetMapping("/test-stream-2")
public String testStream() {
mySource.output()
.send(
MessageBuilder
.withPayload("消息体")
.build()
);
return "success";
}
- 自定义接口实现消息消费者
- 创建interface
public interface MySink{
//my-input 要与配置文件一致
String MY_INPUT = ”my-input“;
@Input(MY_INPUT)
SubscribableChannel input();
}
- 在启动类引用 MySink.class
@EnableBinding({Sink.class,MySink.class})
- 加配置
spring:
cloud:
stream:
rocketmq:
binder:
name-server: 127.0.0.1:9876
bindings:
input:
# 与内容中心一致
destination: stream-test-topic
# 如果用的是RocketMq,一定要设置
# 如果用的是其它MQ,可留空
group: binder-group
my-input:
destination: stream-my-topic
group: my-group
- 消费消息
@Service
@Slf4j
public class MyTestStreamConsumer {
private final UserService userService;
@StreamListener(MySink.MY_INPUT)
public void receive(String message) {
log.info("自定义接口消费,通过stream收到了消息:{}",message);
}
}
@Service
@Slf4j
public class MyTestStreamConsumer {
private final UserService userService;
@StreamListener(MySink.MY_INPUT)
public void receive(String message) {
log.info("自定义接口消费,通过stream收到了消息:{}",message);
throw new RuntimeException("x");
}
@StreamListener("errorChannel")
public void error(Message<?> message) {
ErrorMessage errorMessage = (ErrorMessage) message;
log.warn("发生异常:{}",errorMessage);
}
}
- Spring Cloud Stream + RocketMq实现分布式事务
删除自定义代码(重构)生产者
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ShareService {
private final Source source;
public Share auditById(Integer id, ShareAuditDTO auditDTO) {
// 1. 查询share是否存在,不存在或者当前的audit_status != NOT_YET,那么抛异常
Share share = this.shareMapper.selectByPrimaryKey(id);
if (share == null) {
throw new IllegalArgumentException("参数非法!该分享不存在!");
}
if (!Objects.equals("NOT_YET", share.getAuditStatus())) {
throw new IllegalArgumentException("参数非法!该分享已审核通过或审核不通过!");
}
// 3. 如果是PASS,那么发送消息给rocketmq,让用户中心去消费,并为发布人添加积分
if (AuditStatusEnum.PASS.equals(auditDTO.getAuditStatusEnum())) {
// 发送半消息。。
String transactionId = UUID.randomUUID().toString();
this.source.output()
.send(
MessageBuilder
.withPayload(
UserAddBonusMsgDTO.builder()
.userId(share.getUserId())
.bonus(50)
.build()
)
// header也有妙用...
.setHeader(RocketMQHeaders.TRANSACTION_ID, transactionId)
.setHeader("share_id", id)
.setHeader("dto", JSON.toJSONString(auditDTO))
.build()
);
}
else {
this.auditByIdInDB(id, auditDTO);
}
return share;
}
@Transactional(rollbackFor = Exception.class)
public void auditByIdInDB(Integer id, ShareAuditDTO auditDTO) {
Share share = Share.builder()
.id(id)
.auditStatus(auditDTO.getAuditStatusEnum().toString())
.reason(auditDTO.getReason())
.build();
this.shareMapper.updateByPrimaryKeySelective(share);
// 4. 把share写到缓存
}
}
txProducerGroup与配置文件一致
@RocketMQTransactionListener(txProducerGroup = "tx-add-bonus-group")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class AddBonusTransactionListener implements RocketMQLocalTransactionListener {
private final ShareService shareService;
private final RocketmqTransactionLogMapper rocketmqTransactionLogMapper;
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
MessageHeaders headers = msg.getHeaders();
String transactionId = (String) headers.get(RocketMQHeaders.TRANSACTION_ID);
Integer shareId = Integer.valueOf((String) headers.get("share_id"));
String dtoString = (String) headers.get("dto");
ShareAuditDTO auditDTO = JSON.parseObject(dtoString, ShareAuditDTO.class);
try {
this.shareService.auditByIdWithRocketMqLog(shareId, auditDTO, transactionId);
return RocketMQLocalTransactionState.COMMIT;
} catch (Exception e) {
return RocketMQLocalTransactionState.ROLLBACK;
}
}
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
MessageHeaders headers = msg.getHeaders();
String transactionId = (String) headers.get(RocketMQHeaders.TRANSACTION_ID);
// select * from xxx where transaction_id = xxx
RocketmqTransactionLog transactionLog = this.rocketmqTransactionLogMapper.selectOne(
RocketmqTransactionLog.builder()
.transactionId(transactionId)
.build()
);
if (transactionLog != null) {
return RocketMQLocalTransactionState.COMMIT;
}
return RocketMQLocalTransactionState.ROLLBACK;
}
}
加配置
spring:
cloud:
stream:
rocketmq:
binder:
name-server: 127.0.0.1:9876
bindings:
output:
producer:
transactional: true
group: tx-add-bonus-group
bindings:
output:
# 用来指定topic
destination: add-bonus
删除自定义代码(重构)消费者
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Slf4j
public class AddBonusStreamConsumer {
private final UserService userService;
@StreamListener(Sink.INPUT)
public void receive(UserAddBonusMsgDTO message) {
message.setEvent("CONTRIBUTE");
message.setDescription("投稿加积分..");
userService.addBonus(message);
}
}
本地事务方法 addBonus
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class UserService {
private final UserMapper userMapper;
private final BonusEventLogMapper bonusEventLogMapper;
@Transactional(rollbackFor = Exception.class)
public void addBonus(UserAddBonusMsgDTO msgDTO) {
// 1. 为用户加积分
Integer userId = msgDTO.getUserId();
Integer bonus = msgDTO.getBonus();
User user = this.userMapper.selectByPrimaryKey(userId);
user.setBonus(user.getBonus() + bonus);
this.userMapper.updateByPrimaryKeySelective(user);
// 2. 记录日志到bonus_event_log表里面
this.bonusEventLogMapper.insert(
BonusEventLog.builder()
.userId(userId)
.value(bonus)
.event(msgDTO.getEvent())
.createTime(new Date())
.description(msgDTO.getDescription())
.build()
);
log.info("积分添加完毕...");
}
}
编写SpringCloudGateway
新建一个springboot项目
- 修改版本,和其它微服务版本一致
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
- 引入依赖
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>0.9.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 写配置
server:
port: 8040
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
# 让gateway通过服务发现组件找到其他的微服务
enabled: true
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
- 路由谓词配置predicates
- after
server:
port: 8040
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
# 让gateway通过服务发现组件找到其他的微服务
enabled: true
routes:
- id: after_route
uri: lb://user-center
predicates:
# 当且仅当请求时的时间After配置的时间时,才会转发到用户微服务
# 目前配置不会进该路由配置,所以返回404
# 将时间改成 < now的时间,则访问localhost:8040/** -> user-center/**
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- After=2030-01-20T17:42:47.789-07:00[America/Denver]
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
logging:
level:
org.springframework.cloud.gateway: trace
- Before
server:
port: 8040
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
# 让gateway通过服务发现组件找到其他的微服务
enabled: true
routes:
- id: before_route
uri: lb://user-center
predicates:
# 当且仅当请求时的时间Before配置的时间时,才会转发到用户微服务
# 目前配置不会进该路由配置,所以返回404
# 将时间改成 > now的时间,则访问localhost:8040/** -> user-center/**
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- Before=2018-01-20T17:42:47.789-07:00[America/Denver]
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
logging:
level:
org.springframework.cloud.gateway: trace
- Between
server:
port: 8040
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
# 让gateway通过服务发现组件找到其他的微服务
enabled: true
routes:
- id: between_route
uri: lb://user-center
predicates:
# 当且仅当请求时的时间Between配置的时间时,才会转发到用户微服务
# 因此,访问localhost:8040/** -> user-center/**
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2027-01-21T17:42:47.789-07:00[America/Denver]
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
logging:
level:
org.springframework.cloud.gateway: trace
- cookie
server:
port: 8040
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
# 让gateway通过服务发现组件找到其他的微服务
enabled: true
routes:
- id: cookie_route
uri: lb://user-center
predicates:
# 当且仅当带有名为somecookie,并且值符合正则ch.p的Cookie时,才会转发到用户微服务
# 如Cookie满足条件,则访问http://localhost:8040/** -> user-center/**
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- Cookie=somecookie, ch.p
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
logging:
level:
org.springframework.cloud.gateway: trace
- Header
server:
port: 8040
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
# 让gateway通过服务发现组件找到其他的微服务
enabled: true
routes:
- id: header_route
uri: lb://user-center
predicates:
# 当且仅当带有名为X-Request-Id,并且值符合正则\d+的Header时,才会转发到用户微服务
# 如Header满足条件,则访问http://localhost:8040/** -> user-center/**
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- Header=X-Request-Id, \d+
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
logging:
level:
org.springframework.cloud.gateway: trace
- host
server:
port: 8040
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
# 让gateway通过服务发现组件找到其他的微服务
enabled: true
routes:
- id: host_route
uri: lb://user-center
predicates:
# 当且仅当名为Host的Header符合**.somehost.org或**.anotherhost.org时,才会转发用户微服务
# 如Host满足条件,则访问http://localhost:8040/** -> user-center/**
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- Host=**.somehost.org,**.anotherhost.org
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
logging:
level:
org.springframework.cloud.gateway: trace
- Method
server:
port: 8040
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
# 让gateway通过服务发现组件找到其他的微服务
enabled: true
routes:
- id: method_route
uri: lb://user-center
predicates:
# 当且仅当HTTP请求方法是GET时,才会转发用户微服务
# 如请求方法满足条件,访问http://localhost:8040/** -> user-center/**
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- Method=GET
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
logging:
level:
org.springframework.cloud.gateway: trace
- Path
server:
port: 8040
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
# 让gateway通过服务发现组件找到其他的微服务
enabled: true
routes:
- id: path_route
uri: lb://user-center
predicates:
# 当且仅当访问路径是/users/*或者/some-path/**,才会转发用户微服务
# segment是一个特殊的占位符,单层路径匹配
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- Path=/users/{segment},/some-path/**
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
logging:
level:
org.springframework.cloud.gateway: trace
- query-1
server:
port: 8040
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
# 让gateway通过服务发现组件找到其他的微服务
enabled: true
routes:
- id: query_route
uri: lb://user-center
predicates:
# 当且仅当请求带有baz的参数,才会转发到用户微服务
# eg. 访问http://localhost:8040/users/1?baz=xx -> user-center的/users/1
- Query=baz
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
logging:
level:
org.springframework.cloud.gateway: trace
- query-2
server:
port: 8040
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
# 让gateway通过服务发现组件找到其他的微服务
enabled: true
routes:
- id: query_route
uri: lb://user-center
predicates:
# 当且仅当请求带有名为foo的参数,且参数值符合正则ba.,才会转发到用户微服务
# eg. 访问http://localhost:8040/users/1?baz=baz -> user-center的/users/1?baz=baz
- Query=foo, ba.
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
logging:
level:
org.springframework.cloud.gateway: trace
- RemoteAddr
server:
port: 8040
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
# 让gateway通过服务发现组件找到其他的微服务
enabled: true
routes:
- id: remoteaddr_route
uri: lb://user-center
predicates:
# 当且仅当请求IP是192.168.1.1/24网段,例如192.168.1.10,才会转发到用户微服务
# eg. 访问http://localhost:8040/users/1 -> user-center的/users/1
- RemoteAddr=192.168.1.1/24
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
logging:
level:
org.springframework.cloud.gateway: trace
- 自定义路由谓词工厂
/**
* 路由工厂必须以RoutePredicateFactory结尾
* @author renpei
*/
@Component
public class TimeBetweenRoutePredicateFactory
extends AbstractRoutePredicateFactory<TimeBeweenConfig> {
public TimeBetweenRoutePredicateFactory() {
super(TimeBeweenConfig.class);
}
@Override
public Predicate<ServerWebExchange> apply(TimeBeweenConfig config) {
LocalTime start = config.getStart();
LocalTime end = config.getEnd();
return exchange -> {
LocalTime now = LocalTime.now();
return now.isAfter(start) && now.isBefore(end);
};
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("start", "end");
}
}
@Data
public class TimeBeweenConfig {
private LocalTime start;
private LocalTime end;
}
- 编写一个过滤器工厂
过滤器工厂必须GatewayFilterFactory结尾
@Slf4j
@Component
public class PreLogGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
return ((exchange, chain) -> {
//config.getName() = a,config.getValue() = b
log.info("请求进来了...{},{}", config.getName(), config.getValue());
ServerHttpRequest modifiedRequest = exchange.getRequest()
.mutate()
.build();
ServerWebExchange modifiedExchange = exchange.mutate()
.request(modifiedRequest)
.build();
return chain.filter(modifiedExchange);
});
}
}
配置filters
server:
port: 8040
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
# 让gateway通过服务发现组件找到其他的微服务
enabled: true
routes:
- id: after_route
uri: lb://user-center
predicates:
# 当且仅当请求时的时间After配置的时间时,才会转发到用户微服务
# 目前配置不会进该路由配置,所以返回404
# 将时间改成 < now的时间,则访问localhost:8040/** -> user-center/**
# eg. 访问http://localhost:8040/users/1 -> user-center/users/1
- After=2030-01-20T17:42:47.789-07:00[America/Denver]
filters:
- AddRequestHeader=X-Request-Foo,Bar
- PreLog=a,b
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
logging:
level:
org.springframework.cloud.gateway: trace
百度SpringCloudGateway了解更多
生产环境配置文件
server:
port: 8040
spring:
application:
name: gateway
zipkin:
base-url: http://localhost:9411/
discoveryClientEnabled: false
sleuth:
sampler:
# 抽样率,默认是0.1(10%)
probability: 1.0
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
# 让gateway通过服务发现组件找到其他的微服务
enabled: true
routes:
- id: user_route
uri: lb://user-center
predicates:
- Path=/users/**
- id: content_route
uri: lb://content-center
predicates:
- Path=/shares/**,/admin/**
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
logging:
level:
org.springframework.cloud.gateway: trace
业务环节开发
- Feign实现Token传递
方式一
@FeignClient(name = "user-center")
public interface UserCenterFeignClient {
/**
* http://user-center/users/{id}
* @param id
* @return
*/
@GetMapping("/users/{id}")
UserDTO findById(@PathVariable Integer id,@RequestHeader("X-token") String token);
}
方式二
- 拦截器
public class TokenRelayRequestIntecepor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 1. 获取到token
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
HttpServletRequest request = attributes.getRequest();
String token = request.getHeader("X-Token");
// 2. 将token传递
if (StringUtils.isNotBlank(token)) {
template.header("X-Token", token);
}
}
}
- 配置拦截器
feign:
client:
config:
# 全局配置
default:
loggerLevel: full
requestInterceptors:
- com.itmuch.contentcenter.feignclient.interceptor.TokenRelayRequestIntecepor
- RestTemplate实现Token传递
方式一
@Autowired
private RestTemplate restTemplate;
@GetMapping("/tokenRelay/{userId}")
public ResponseEntity<UserDTO> tokenRelay(@PathVariable Integer userId, HttpServletRequest request) {
String token = request.getHeader("X-Token");
HttpHeaders headers = new HttpHeaders();
headers.add("X-Token", token);
return this.restTemplate
.exchange(
"http://user-center/users/{userId}",
HttpMethod.GET,
new HttpEntity<>(headers),
UserDTO.class,
userId
);
}
方式二 RestTemplate的拦截器
- Interceptor
public class RestTemplateTokenRelayInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
HttpServletRequest httpRequest = attributes.getRequest();
String token = httpRequest.getHeader("X-Token");
HttpHeaders headers = request.getHeaders();
headers.add("X-Token", token);
// 保证请求继续执行
return execution.execute(request, body);
}
}
- 配置
// 在spring容器中,创建一个对象,类型RestTemplate;名称/ID是:restTemplate
// <bean id="restTemplate" class="xxx.RestTemplate"/>
@Bean
@LoadBalanced
@SentinelRestTemplate
public RestTemplate restTemplate() {
RestTemplate template = new RestTemplate();
template.setInterceptors(
Collections.singletonList(
new RestTemplateTokenRelayInterceptor()
)
);
return template;
}
- 自定义注解实现
配置服务器nacos
- 使用nacos管理配置
百度
- 搭建生产服可用的nacos集群环境