1 Spring Cloud Alibaba 是什么?
1.1 什么是Spring Cloud?
是一系列分布式框架的集合,基于Spring Boot进行开发的。是一种规范,不是一个具体的实现。
将不同公司生产的不同组件进行集成,以Spring Boot的风格进行集成,开发者就不需要关注底层的整合实现,而是开箱即用,需要哪个组件就用Spring Boot整合进来,
1.1 什么是Spring Cloud Alibaba?
Spring Cloud Alibaba 是提供微服务开发的解决方案。
Spring Cloud Alibaba 的环境在父工程中创建,微服务的各个组件作为子工程,继承父工程的环境。
Spring Boot —》Spring Cloud —》Spring Cloud Alibaba
https://github.com/alibaba/spring-cloud-alibaba/wiki/
1.3 上手代码
1.创建工程
创建父工程
pom.xml 中添加。
<dependencyManagement>
<dependencies>
<!-- Spring Cloud Hoxton -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</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.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2 服务治理
服务注册+服务发现
Nacos 服务注册
解压,启动服务。
Nacos 搭建成功,接下来注册服务。
在父工程路径下创建子工程,让子工程继承父工程的环境依赖,pom.xml 中添加 nacos 发现组件。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
application.yml 中配置
spring:
cloud:
nacos:
discovery:
# 指定nacos server地址
server-addr: localhost:8848
application:
name: my-nacos
Nacos 服务发现与调用
pom.xml 添加 discovery,完成服务发现。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
通过 discoveryClient 发现注册到 nacos 中的 provider 服务。
@RestController
public class ConsumerController {
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/instances")
public List<ServiceInstance> instances(){
List<ServiceInstance> provider = discoveryClient.getInstances("provider");
return provider;
}
}
@Configuration
public class ConsumerConfig {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
@RestController
public class ConsumerController {
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/index")
public String index(){
List<ServiceInstance> provider = discoveryClient.getInstances("provider");
int index = ThreadLocalRandom.current().nextInt(provider.size());
String url = provider.get(index).getUri()+"/index";
return "consumer随机远程调用provier:"+this.restTemplate.getForObject(url, String.class);
}
}
3 Ribbon 负载均衡
@Configuration
public class ConsumerConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
@RestController
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/index")
public String index(){
return "consumer远程调用provier:"+this.restTemplate.getForObject("http://provider/index", String.class);
}
}
随机
server:
port: 8180
provider:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
Nacos 权重
@Slf4j
public class NacosWeightedRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
//读取配置文件
}
@Override
public Server choose(Object o) {
ILoadBalancer loadBalancer = this.getLoadBalancer();
BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) loadBalancer;
//获取要请求的微服务名称
String name = baseLoadBalancer.getName();
//获取服务发现的相关API
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
try {
Instance instance = namingService.selectOneHealthyInstance(name);
log.info("选择的实例是port={},instance={}",instance.getPort(),instance);
return new NacosServer(instance);
} catch (NacosException e) {
e.printStackTrace();
return null;
}
}
}
server:
port: 8180
provider:
ribbon:
NFLoadBalancerRuleClassName: com.southwind.configuration.NacosWeightedRule
4 Sentinel 服务限流降级
雪崩效应
解决方案
1、设置线程超时
2、设置限流
3、熔断器 Sentinel、Hystrix
1、pom.xml 引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2、application 配置
management:
endpoints:
web:
exposure:
include: '*'
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
3、下载 Sentinel 控制台,解压,启动。
流控规则
直接限流
关联限流
链路限流
1、pom.xml 添加依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-web-servlet</artifactId>
<version>1.7.1</version>
</dependency>
2、application.yml
spring:
cloud:
sentinel:
filter:
enabled: false
3、写配置类
package com.southwind.configuration;
import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FilterConfiguration {
@Bean
public FilterRegistrationBean registrationBean(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new CommonFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY,"false");
registrationBean.setName("sentinelFilter");
return registrationBean;
}
}
4、Service
@Service
public class HelloService {
@SentinelResource("test")
public void test(){
System.out.println("test");
}
}
5、Controller
@GetMapping("/test1")
public String test1(){
this.helloService.test();
return "test1";
}
@GetMapping("/test2")
public String test2(){
this.helloService.test();
return "test2";
}
流控效果
快速失败
直接抛出异常
Warm UP
给系统一个预热的时间,预热时间段内单机阈值较低,预热时间过后单机阈值增加,预热时间内当前的单机阈值是设置的阈值的三分之一,预热时间过后单机阈值恢复设置的值。
排队等待
当请求调用失败之后,不会立即抛出异常,等待下一次调用,时间范围是超时时间,在时间范围内如果能请求成功则不抛出异常,如果请求则抛出异常。
降级规则
RT
单个请求的响应时间超过阈值,则进入准降级状态,接下来 1 S 内连续 5 个请求响应时间均超过阈值,就进行降级,持续时间为时间窗口的值。
异常比例
每秒异常数量占通过量的比例大于阈值,就进行降级处理,持续时间为时间窗口的值。
异常数
1 分钟内的异常数超过阈值就进行降级处理,时间窗口的值要大于 60S,否则刚结束熔断又进入下一次熔断了。
热点规则
热点规则是流控规则的更细粒度操作,可以具体到对某个热点参数的限流,设置限流之后,如果带着限流参数的请求量超过阈值,则进行限流,时间为统计窗口时长。
必须要添加 @SentinelResource,即对资源进行流控。
@GetMapping("/hot")
@SentinelResource("hot")
public String hot(
@RequestParam(value = "num1",required = false) Integer num1,
@RequestParam(value = "num2",required = false) Integer num2){
return num1+"-"+num2;
}
授权规则
给指定的资源设置流控应用(追加参数),可以对流控应用进行访问权限的设置,具体就是添加白名单和黑名单。
如何给请求指定流控应用,通过实现 RequestOriginParser 接口来完成,代码如下所示。
package com.southwind.configuration;
import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
public class RequestOriginParserDefinition implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
String name = httpServletRequest.getParameter("name");
if(StringUtils.isEmpty(name)){
throw new RuntimeException("name is null");
}
return name;
}
}
要让 RequestOriginParserDefinition 生效,需要在配置类中进行配置。
package com.southwind.configuration;
import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
@Configuration
public class SentinelConfiguration {
@PostConstruct
public void init(){
WebCallbackManager.setRequestOriginParser(new RequestOriginParserDefinition());
}
}
自定义规则异常返回
创建异常处理类
package com.southwind.handler;
import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlBlockHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class ExceptionHandler implements UrlBlockHandler {
@Override
public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws IOException {
httpServletResponse.setContentType("text/html;charset=utf-8");
String msg = null;
if(e instanceof FlowException){
msg = "限流";
}else if(e instanceof DegradeException){
msg = "降级";
}
httpServletResponse.getWriter().write(msg);
}
}
进行配置。
@Configuration
public class SentinelConfiguration {
@PostConstruct
public void init(){
WebCallbackManager.setUrlBlockHandler(new ExceptionHandler());
}
}
5 整合 RocketMQ
安装 RocketMQ
1、传入 Linux 服务器
2、解压缩
unzip rocketmq-all-4.7.1-bin-release.zip
3、启动 NameServer
nohup sh bin/mqnamesrv &
4、检查是否启动成功
netstat -an | grep 9876
5、启动 Broker
启动之前需要编辑配置文件,修改 JVM 内存设置,默认给的内存 4 GB,超过我们的 JVM 了。
cd bin
vim runserver.sh
vim runbroker.sh
启动 Broker
nohup sh bin/mqbroker -n localhost:9876 &
可以查看日志
tail -f ~/logs/rocketmqlogs/broker.log
启动成功
6、测试 RocketMQ
消息发送
cd bin
export NAMESRV_ADDR=localhost:9876
./tools.sh org.apache.rocketmq.example.quickstart.Producer
消息接收
cd bin
export NAMESRV_ADDR=localhost:9876
./tools.sh org.apache.rocketmq.example.quickstart.Consumer
7、关闭 RocketMQ
cd bin
./mqshutdown broker
./mqshutdown namesrv
安装 RocketMQ 控制台
1、解压缩,修改配置,打包
mvn clean package -Dmaven.test.skip=true
2、进入 target 启动 jar
java -jar rocketmq-console-ng-1.0.0.jar
打开浏览器访问 localhost:9877,如果报错开启防火墙端口
这是因为我们的 RocketMQ 安装在 Linux 中,控制台在 windows,Linux 需要开放端口才能访问,开放 10909 和 9876 端口
firewall-cmd --zone=public --add-port=10909/tcp --permanent
firewall-cmd --zone=public --add-port=9876/tcp --permanent
systemctl restart firewalld.service
firewall-cmd --reload
重新启动控制台项目
Java 实现消息发送
1、pom.xml 中引入依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
2、生产消息
package com.southwind;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
public class Test {
public static void main(String[] args) throws Exception {
//创建消息生产者
DefaultMQProducer producer = new DefaultMQProducer("myproducer-group");
//设置NameServer
producer.setNamesrvAddr("192.168.248.129:9876");
//启动生产者
producer.start();
//构建消息对象
Message message = new Message("myTopic","myTag",("Test MQ").getBytes());
//发送消息
SendResult result = producer.send(message, 1000);
System.out.println(result);
//关闭生产者
producer.shutdown();
}
}
3、直接运行,如果报错 sendDefaultImpl call timeout,可以开放 10911 端口
firewall-cmd --zone=public --add-port=10911/tcp --permanent
systemctl restart firewalld.service
firewall-cmd --reload
打开 RocketMQ 控制台,可查看消息。
Java 实现消息消费
package com.southwind.service;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
@Slf4j
public class ConsumerTest {
public static void main(String[] args) throws MQClientException {
//创建消息消费者
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("myconsumer-group");
//设置NameServer
consumer.setNamesrvAddr("192.168.248.129:9876");
//指定订阅的主题和标签
consumer.subscribe("myTopic","*");
//回调函数
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
log.info("Message=>{}",list);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
//启动消费者
consumer.start();
}
}
Spring Boot 整合 RocketMQ
provider
1、pom.xml
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.7.0</version>
</dependency>
2、application.yml
rocketmq:
name-server: 192.168.248.129:9876
producer:
group: myprovider
3、Order
package com.southwind.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private Integer id;
private String buyerName;
private String buyerTel;
private String address;
private Date createDate;
}
4、Controller
@Autowired
private RocketMQTemplate rocketMQTemplate;
@GetMapping("/create")
public Order create(){
Order order = new Order(
1,
"张三",
"123123",
"软件园",
new Date()
);
this.rocketMQTemplate.convertAndSend("myTopic",order);
return order;
}
consumer
1、pom.xml
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.7.0</version>
</dependency>
2、application.yml
rocketmq:
name-server: 192.168.248.129:9876
3、Service
@Slf4j
@Service
@RocketMQMessageListener(consumerGroup = "myConsumer",topic = "myTopic")
public class SmsService implements RocketMQListener<Order> {
@Override
public void onMessage(Order order) {
log.info("新订单{},发短信",order);
}
}
6 服务网关
Spring Cloud Gateway 是基于 Netty,跟 Servlet 不兼容,所以你的工程中不能出现 Servlet 的组件 。
1、pom.xml
注意,一定不能出现 spring web 的依赖,因为 Gateway 与 Servlet 不兼容。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
2、application.yml
server:
port: 8010
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: provider_route
uri: http://localhost:8081
predicates:
- Path=/provider/**
filters:
- StripPrefix=1
上面这种做法其实没有用到 nacos ,现在我们让 gateway 直接去 nacos 中发现服务,配置更加简单了。
1、pom.xml 引入 nacos
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2、application.yml
server:
port: 8010
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
enabled: true
Gateway 限流
基于路由限流
1、pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
2、配置类
package com.southwind.configuration;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.*;
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
//配置限流的异常处理
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
//配置初始化的限流参数
@PostConstruct
public void initGatewayRules(){
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(
new GatewayFlowRule("provider_route")
.setCount(1)
.setIntervalSec(1)
);
GatewayRuleManager.loadRules(rules);
}
//初始化限流过滤器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
//自定义限流异常页面
@PostConstruct
public void initBlockHandlers(){
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map map = new HashMap();
map.put("code",0);
map.put("msg","被限流了");
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromObject(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
}
3、application.yml
server:
port: 8010
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: provider_route
uri: http://localhost:8081
predicates:
- Path=/provider/**
filters:
- StripPrefix=1
基于 API 分组限流
1、修改配置类,添加基于 API 分组限流的方法,修改初始化的限流参数
package com.southwind.configuration;
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.*;
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
//配置限流的异常处理
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
//配置初始化的限流参数
@PostConstruct
public void initGatewayRules(){
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("provider_api1").setCount(1).setIntervalSec(1));
rules.add(new GatewayFlowRule("provider_api2").setCount(1).setIntervalSec(1));
GatewayRuleManager.loadRules(rules);
}
//初始化限流过滤器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
//自定义限流异常页面
@PostConstruct
public void initBlockHandlers(){
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map map = new HashMap();
map.put("code",0);
map.put("msg","被限流了");
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromObject(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
//自定义API分组
@PostConstruct
private void initCustomizedApis(){
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("provider_api1")
.setPredicateItems(new HashSet<ApiPredicateItem>(){{
add(new ApiPathPredicateItem().setPattern("/provider/api1/**")
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
ApiDefinition api2 = new ApiDefinition("provider_api2")
.setPredicateItems(new HashSet<ApiPredicateItem>(){{
add(new ApiPathPredicateItem().setPattern("/provider/api2/demo1"));
}});
definitions.add(api1);
definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
}
2、Controller 添加方法
@GetMapping("/api1/demo1")
public String demo1(){
return "demo";
}
@GetMapping("/api1/demo2")
public String demo2(){
return "demo";
}
@GetMapping("/api2/demo1")
public String demo3(){
return "demo";
}
@GetMapping("/api2/demo2")
public String demo4(){
return "demo";
}
也可以基于 Nacos 服务发现组件进行限流
server:
port: 8010
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
enabled: true
API 分组代码修改,改为 discovery 中的服务名。
ApiDefinition api2 = new ApiDefinition("provider_api2")
.setPredicateItems(new HashSet<ApiPredicateItem>(){{
add(new ApiPathPredicateItem().setPattern("/p1/api2/demo1"));
}});
7 分布式事务
模拟分布式事务异常
1、创建两个工程 order、pay,pom.xml
<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>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
2、建两个数据库 order、pay,两个微服务分别访问。
3、分别写两个服务的 application.yml
server:
port: 8010
spring:
application:
name: order
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/order
server:
port: 8020
spring:
application:
name: pay
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/pay
4、分别写两个 Service
package com.southwind.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private JdbcTemplate jdbcTemplate;
public void save(){
this.jdbcTemplate.update("insert into orders(username) values ('张三')");
}
}
package com.southwind.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class PayService {
@Autowired
private JdbcTemplate jdbcTemplate;
public void save(){
this.jdbcTemplate.update("insert into pay(username) values ('张三')");
}
}
5、控制器 Order 通过 RestTemplate 调用 Pay 的服务
package com.southwind.controller;
import com.southwind.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/save")
public String save(){
//订单
this.orderService.save();
int i = 10/0;
//支付
this.restTemplate.getForObject("http://localhost:8020/save",String.class);
return "success";
}
}
package com.southwind.controller;
import com.southwind.service.PayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PayController {
@Autowired
private PayService payService;
@GetMapping("/save")
public String save(){
this.payService.save();
return "success";
}
}
6、启动类
package com.southwind;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
package com.southwind;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class PayApplication {
public static void main(String[] args) {
SpringApplication.run(PayApplication.class, args);
}
}
分布式异常模拟结束,Order 存储完成之后,出现异常,会导致 Pay 无法存储,但是 Order 数据库不会进行回滚。
Seata 解决
1、下载
2、解压,修改两个文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dvxpSfR2-1624339835265)(C:\Users\ningn\AppData\Roaming\Typora\typora-user-images\image-20200624165841578.png)]
regisry.conf
registry {
type = "nacos"
nacos {
serverAddr = "localhost"
namespace = "public"
cluster = "default"
}
}
config {
type = "nacos"
nacos {
serverAddr = "localhost"
namespace = "public"
cluster = "default"
}
}
nacos-config.txt
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UeBEVhhW-1624339835287)(C:\Users\ningn\AppData\Roaming\Typora\typora-user-images\image-20200624170027580.png)]
3、启动 Nacos,运行 nacos-config.sh 将 Seata 配置导入 Nacos
进入 conf,右键 Git Bash Here
cd conf
sh nacos-config.sh 127.0.0.1
执行成功,刷新 Nacos,配置加入
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2rdvb24c-1624339835291)(C:\Users\ningn\AppData\Roaming\Typora\typora-user-images\image-20200624170411851.png)]
nacos-config.txt 配置已生效
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2tlFkojE-1624339835295)(C:\Users\ningn\AppData\Roaming\Typora\typora-user-images\image-20200624170446667.png)]
4、启动 Seata Server, JDK 8 以上环境无法启动
cd bin
seata-server.bat -p 8090 -m file
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-85rrvzRk-1624339835297)(C:\Users\ningn\AppData\Roaming\Typora\typora-user-images\image-20200624170701118.png)]
启动成功,Nacos 注册成功。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-myDQsl2A-1624339835300)(C:\Users\ningn\AppData\Roaming\Typora\typora-user-images\image-20200624171016755.png)]
Seata 服务环境搭建完毕,接下来去应用中添加。
1、初始化数据库,在两个数据库中添加事务日志记录表,SQL Seata 已经提供。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9QwlxdAI-1624339835302)(C:\Users\ningn\AppData\Roaming\Typora\typora-user-images\image-20200624171211591.png)]
2、直接在两个数据库运行脚本。
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
3、两个工程的 pom.xml 添加 Seata 组件和 Nacos Config 组件。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
4、给 JDBCTemplate 添加代理数据源
package com.southwind;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.client.RestTemplate;
import javax.sql.DataSource;
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
return new JdbcTemplate(new DataSourceProxy(dataSource));
}
}
package com.southwind;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
@SpringBootApplication
public class PayApplication {
public static void main(String[] args) {
SpringApplication.run(PayApplication.class, args);
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
return new JdbcTemplate(new DataSourceProxy(dataSource));
}
}
5、将 registry.conf 复制到两个工程的 resources 下。
6、给两个工程添加 bootstrap.yml 读取 Nacos 配置。
spring:
application:
name: order
cloud:
nacos:
config:
server-addr: localhost:8848
namespace: public
group: SEATA_GROUP
alibaba:
seata:
tx-service-group: ${spring.application.name}
spring:
application:
name: pay
cloud:
nacos:
config:
server-addr: localhost:8848
namespace: public
group: SEATA_GROUP
alibaba:
seata:
tx-service-group: ${spring.application.name}
tx-service-group 需要和 Nacos 配置中的名称一致。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FQLPko9a-1624339835303)(C:\Users\ningn\AppData\Roaming\Typora\typora-user-images\image-20200624172215657.png)]
7、在 Order 调用 Pay 处添加注解 @GlobalTransactional
package com.southwind.controller;
import com.southwind.service.OrderService;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/save")
@GlobalTransactional
public String save(){
//订单
this.orderService.save();
int i = 10/0;
//支付
this.restTemplate.getForObject("http://localhost:8020/save",String.class);
return "success";
}
}