irpas技术客

Spring Cloud Alibaba_拒绝平庸yt

网络 7797

Spring Cloud Alibaba

创建父工程

Spring Cloud Alibaba 的环境在父工程中创建,微服务的各个组件作为子工程,继承父工程的环境。

Spring Boot —》Spring Cloud —》Spring Cloud Alibaba

pom.xml 中添加。

<dependencyManagement> <dependencies> <!-- Spring Cloud Hoxton --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR9</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.6.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> Nacos 服务注册

解压,启动服务。

sh startup.sh -m standalone

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); } } 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 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 控制台,解压,启动。

nohup java -Dserver.port=8081 -Dcsp.sentinel.dashboard.server=localhost:8081 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.1.jar >./sentinel.log 2>&1 & 流控规则

直接限流

关联限流

链路限流

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的请求称为慢调用,阈值RT由用户设置

最小请求数 : 允许通过的最小请求数量,在最小请求数量内不发生熔断,由用户设置

熔断有三种状态,分别为OPEN、HALF_OPEN、CLOSED。

状态说明OPEN表示熔断开启,拒绝所有请求HALF_OPEN探测恢复状态,如果接下来的一个请求顺利通过则结束熔断,否则继续熔断CLOSED表示熔断关闭,请求顺利通过
1.慢调用比例 属性说明最大RT需要设置的阈值,超过该值则为慢应用比例阈值慢调用占所有的调用的比率,范围:[0~1]熔断时长在这段时间内发生熔断、拒绝所有请求最小请求数即允许通过的最小请求数,在该数量内不发生熔断

执行逻辑

熔断(OPEN):请求数大于最小请求数并且慢调用的比率大于比例阈值则发生熔断,熔断时长为用户自定义设置。

探测(HALFOPEN):当熔断过了定义的熔断时长,状态由熔断(OPEN)变为探测(HALFOPEN)。

如果接下来的一个请求小于最大RT,说明慢调用已经恢复,结束熔断,状态由探测(HALF_OPEN)变更为关闭(CLOSED)如果接下来的一个请求大于最大RT,说明慢调用未恢复,继续熔断,熔断时长保持一致 2.异常比例

通过计算异常比例与设置阈值对比的一种策略。

属性说明异常比例阈值异常比例=发生异常的请求数÷请求总数取值范围:[0~1]熔断时长在这段时间内发生熔断、拒绝所有请求最小请求数即允许通过的最小请求数,在该数量内不发生熔断

执行逻辑

熔断(OPEN):当请求数大于最小请求并且异常比例大于设置的阈值时触发熔断,熔断时长由用户设置。

探测(HALFOPEN):当超过熔断时长时,由熔断(OPEN)转为探测(HALFOPEN)

如果接下来的一个请求未发生错误,说明应用恢复,结束熔断,状态由探测(HALF_OPEN)变更为关闭(CLOSED)

如果接下来的一个请求继续发生错误,说明应用未恢复,继续熔断,熔断时长保持一致

3.异常数

通过计算发生异常的请求数与设置阈值对比的一种策略。

属性说明异常数请求发生异常的数量熔断时长在这段时间内发生熔断、拒绝所有请求最小请求数即允许通过的最小请求数,在该数量内不发生熔断

执行逻辑

熔断(OPEN):当请求数大于最小请求并且异常数量大于设置的阈值时触发熔断,熔断时长由用户设置。探测(HALFOPEN):当超过熔断时长时,由熔断(OPEN)转为探测(HALFOPEN)

如果接下来的一个请求未发生错误,说明应用恢复,结束熔断,状态由探测(HALF_OPEN)变更为关闭(CLOSED)如果接下来的一个请求继续发生错误,说明应用未恢复,继续熔断,熔断时长保持一致 热点规则

热点规则是流控规则的更细粒度操作,可以具体到对某个热点参数的限流,设置限流之后,如果带着限流参数的请求量超过阈值,则进行限流,时间为统计窗口时长。

必须要添加 @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()); } } 整合 RocketMQ 安装 RocketMQ

1、传入 Linux 服务器

2、解压缩

unzip rocketmq-all-4.7.1-bin-release.zip

3、启动 NameServer

nohup sh mqnamesrv &

4、检查是否启动成功

netstat -an | grep 9876

5、启动 Broker

启动之前需要编辑配置文件,修改 JVM 内存设置,默认给的内存 4 GB,超过我们的 JVM 了。

cd bin vim runserver.sh

vim runbroker.sh

启动 Broker

nohup sh mqbroker -n localhost:9876 &

可以查看日志

tail -f ~/logs/rocketmqlogs/broker.log

启动成功

6、测试 RocketMQ

消息发送

cd bin export NAMESRV_ADDR=localhost:9876 sh tools.sh org.apache.rocketmq.example.quickstart.Producer

消息接收

cd bin export NAMESRV_ADDR=localhost:9876 sh tools.sh org.apache.rocketmq.example.quickstart.Consumer

7、关闭 RocketMQ

cd bin sh mqshutdown broker sh 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=10911/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("my-producer-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,加大超时时间就搞定了

producer.send(message, 100000);

打开 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("my-consumer-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); } } 服务网关

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: 8020 spring: application: name: gateway cloud: gateway: discovery: locator: enabled: true nacos: discovery: server-addr: 192.168.204.134:8848 Gateway 负载均衡

基于nacos负载均衡

1、pom.xml

<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-loadbalancer</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> </dependencies>

2、配置类

@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; } } }

3、application.yml

server: port: 8020 spring: application: name: gateway cloud: gateway: discovery: locator: enabled: true nacos: discovery: server-addr: 192.168.204.134:8848 provider: ribbon: NFLoadBalancerRuleClassName: com.yt.config.NacosWeightedRule 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.yt.config; 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 javax.annotation.PostConstruct; import java.util.*; /** * ClassName:GatewayConfiguration * Package:com.yt.config * Description: * * @date:2022/1/19 14:56 * @author:yt */ @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; } /** * 配置限流的异常处理 * * @return */ @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); } /** * 初始化限流过滤器 * * @return */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } /** * 自定义限流异常页面 */ @PostConstruct public void initBlockHandlers() { BlockRequestHandler blockRequestHandler = (serverWebExchange, throwable) -> { Map<String, Object> 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.yt.config; 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 javax.annotation.PostConstruct; import java.util.*; /** * ClassName:GatewayConfiguration * Package:com.yt.config * Description: * * @date:2022/1/19 14:56 * @author:yt */ @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; } /** * 配置限流的异常处理 * * @return */ @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); } /** * 初始化限流过滤器 * * @return */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } /** * 自定义限流异常页面 */ @PostConstruct public void initBlockHandlers() { BlockRequestHandler blockRequestHandler = (serverWebExchange, throwable) -> { Map<String, Object> 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")); }}); 分布式事务 模拟分布式事务异常

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 解决

安装参考:https://·/zjdxr-up/p/15221388.html

https://blog.csdn.net/calonmo/article/details/106630754

安装Seata,指定nacos作为配置中心和注册中心

1、下载地址:https://seata.io/zh-cn/blog/download.html,版本为1.4.0,解压后修改conf/registry.conf文件

registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "nacos" loadBalance = "RandomLoadBalance" loadBalanceVirtualNodes = 10 nacos { application = "seata-server" serverAddr = "127.0.0.1:8848" group = "SEATA_GROUP" namespace = "" cluster = "default" username = "" password = "" } } config { # file、nacos 、apollo、zk、consul、etcd3 type = "nacos" nacos { serverAddr = "127.0.0.1:8848" namespace = "" group = "SEATA_GROUP" username = "nacos" password = "nacos" } }

由于使用nacos作为注册中心,所以conf目录下的file.conf无需理会。然后就可以直接启动seata:sh bin/seata-server.sh,可以在nacos里看到一个名为seata-server的服务了。

2、由于seata使用mysql作为db高可用数据库,故需要在mysql创建一个seata库,并导入数据库脚本,脚本地址:https://github.com/seata/seata/tree/develop/script/server/db

3、导入配置到nacos的配置中心,到https://github.com/seata/seata/tree/develop/script/config-center这里下载跟目录的confix.txt配置文件以及到nacos目录下载nacos-config.sh脚本,修改config.txt文件内容:

service.vgroupMapping.my_test_tx_group=default store.mode=db store.db.datasource=druid store.db.dbType=mysql store.db.driverClassName=com.mysql.jdbc.Driver store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true ## 数据库用户密码自行更改 store.db.user=root store.db.password=root store.db.minConn=5 store.db.maxConn=30 store.db.globalTable=global_table store.db.branchTable=branch_table store.db.queryLimit=100 store.db.lockTable=lock_table store.db.maxWait=5000

上面关于vgroupMapping.my_test_tx_group是和在项目里配置的一致。

seata: # seata 服务分组,要与服务端config.txt中service.vgroup_mapping的后缀对应 tx-service-group: my_test_tx_group

4、执行nacos-config.sh把config.txt文件导入到nacos配置中心,执行出现找不到config.txt的话修改一下nacos-config.sh里面关于config.txt的路径。

sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t 5a3c7d6c-f497-4d68-a71a-2e5e3340b3ca -u username -w password

参数说明:

-h: host,默认值 localhost

-p: port,默认值 8848

-g: 配置分组,默认值为 ‘SEATA_GROUP’

-t: 租户信息,对应 Nacos 的命名空间ID字段, 默认值为空。

也可以直接使用以下命令进行同步

sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP

5、启动Seata Server命令

sh bin/seata-server.sh

Seata整合到Spring Cloud微服务

1、两个工程的 pom.xml 添加对应依赖。

<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <exclusions> <exclusion> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>

2、给 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)); } }

3、直接在两个数据库运行脚本。

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, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

4、给两个工程application.yml添加读取 Nacos 配置。

seata: enabled: true application-id: ${spring.application.name} # seata 服务分组,要与服务端nacos‐config.txt中service.vgroup_mapping的后缀对应 tx-service-group: order config: # 指定nacos作为配置中心 type: nacos nacos: namespace: serverAddr: 192.168.204.134:8848 group: SEATA_GROUP registry: # 指定nacos作为注册中心 type: nacos nacos: application: seata-server server-addr: 192.168.204.134:8848 namespace: seata: enabled: true application-id: ${spring.application.name} # seata 服务分组,要与服务端nacos‐config.txt中service.vgroup_mapping的后缀对应 tx-service-group: pay config: # 指定nacos作为配置中心 type: nacos nacos: namespace: serverAddr: 192.168.204.134:8848 group: SEATA_GROUP registry: # 指定nacos作为注册中心 type: nacos nacos: application: seata-server server-addr: 192.168.204.134:8848 namespace:

注意如果nacos不在本地运行,需要添加nacos发现

spring: cloud: nacos: discovery: server-addr: 192.168.204.134:8848

5、在 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"; } }

注意如果nacos不在本地运行,需要添加nacos发现

spring: cloud: nacos: discovery: server-addr: 192.168.204.134:8848

5、在 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"; } }


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #Spring #Cloud #Alibaba