博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring Cloud Alibaba——转载自楠哥
阅读量:3938 次
发布时间:2019-05-23

本文共 35953 字,大约阅读时间需要 119 分钟。

准备

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

Spring Cloud Alibaba的工程目录:

Spring Boot —> Spring Cloud —> Spring Cloud Alibaba

根据官方推荐,使用较稳定的Spring Cloud Alibaba.2.2.1.RELEASE版本,由此版本倒推所兼容的cloud和boot版本。

总结:

Spring 版本
Spring Cloud Alibaba 2.2.1.RELEASE
Spring Cloud Hoxton.SR3
Spring Boot 2.3.0.RELEASE

父pom.xml 中添加Spring Cloud和Spring Cloud Alibaba的依赖,

用dependencyManagement标签进行版本号的统一管理,子项目引入dependency时就不再需要写版本号:

org.springframework.cloud
spring-cloud-dependencies
Hoxton.SR3
pom
import
com.alibaba.cloud
spring-cloud-alibaba-dependencies
2.2.1.RELEASE
pom
import

Nacos 服务注册

Nacos下载和启动,请移步作者的另一篇博文:

在父工程路径下创建子工程,让子工程继承父工程的环境依赖,子工程pom.xml 中添加 nacos 发现组件(可以放在父pom,当项目庞大起来,子工程越来越多,此时继承父pom即可,避免重复代码)。

子项目(提供者)

pom.xml Nacos配置

com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery

完整pom.xml

4.0.0
com.gs
alibabademo
0.0.1-SNAPSHOT
com.gs.provider
provider
0.0.1-SNAPSHOT
provider
Demo project for Spring Boot
1.8
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.springframework.boot
spring-boot-maven-plugin

application.yml 中配置

server:  port: 8082spring:#  cloud:#    nacos:#      discovery:#        # 指定nacos server地址,不用写http://,不配置默认是本机localhost:8848#        server-addr: localhost:8848  application:    # 注册服务的关键因素,注释掉name就不会再注册    name: my-provider

项目启动就会自动在注册中心注册一个实例,如图所示:

在这里插入图片描述

Nacos 服务发现与调用

子项目(消费者)

pom.xml 添加 discovery,完成服务发现。

com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery

通过 discoveryClient 发现注册到 nacos 中的 provider 服务。

@RestControllerpublic class ConsumerController {
@Autowired private DiscoveryClient discoveryClient; @GetMapping("/instances") public List
instances(){
List
provider = discoveryClient.getInstances("provider"); return provider; }}

通过RestTemplate 进行服务调用,但是RestTemplate 没有被Spring容器管理,不能直接用,需要自己写一个配置类将其纳入Spring管理。

@Configurationpublic class ConsumerConfig {
@Bean public RestTemplate restTemplate(){
return new RestTemplate(); }}

整合:这种方式比较麻烦,而且随机数的生成也不合理,稍后介绍更便捷用法-Ribbon。

@RestControllerpublic class ConsumerController {
@Autowired private DiscoveryClient discoveryClient; @Autowired private RestTemplate restTemplate; @GetMapping("/index") public String index(){
List
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 负载均衡

使用Ribbon优化上述的DiscoveryClient 和 RestTemplate 用法。

在RestTemplate 配置类上使用@LoadBalanced注解。

@Configurationpublic class ConsumerConfig {
@Bean @LoadBalanced public RestTemplate restTemplate(){
return new RestTemplate(); }}
@RestControllerpublic class ConsumerController {
@Autowired private RestTemplate restTemplate; @GetMapping("/index") public String index(){
return "consumer远程调用provier:"+this.restTemplate.getForObject("http://provider/index", String.class); }}

负载均衡策略

随机

server:  port: 8180provider:  ribbon:    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

Nacos 权重

package com.southwind.configuration;@Slf4jpublic 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: 8180provider:  ribbon:    NFLoadBalancerRuleClassName: com.southwind.configuration.NacosWeightedRule

Sentinel 服务限流降级

雪崩效应

解决方案

1、设置线程超时

2、设置限流

3、熔断器 Sentinel、Hystrix

1、pom.xml 引入依赖

com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
org.springframework.boot
spring-boot-starter-actuator

2、application 配置

management:  endpoints:    web:      exposure:        include: '*'spring:  cloud:    sentinel:      transport:        dashboard: localhost:8080

3、下载 Sentinel 控制台,解压,启动。

流控规则

直接限流

关联限流

链路限流

1、pom.xml 添加依赖

com.alibaba.csp
sentinel-core
1.7.1
com.alibaba.csp
sentinel-web-servlet
1.7.1

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

@Servicepublic 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;@Configurationpublic 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); }}

进行配置。

@Configurationpublic 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 ./bin/mqnamesrv &

4、检查是否启动成功

netstat -an | grep 9876

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mJHFa5nM-1604210838732)(C:\Users\ningn\AppData\Roaming\Typora\typora-user-images\image-20200612162924121.png)]

5、启动 Broker

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

cd binvim runserver.sh

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z2ZJ5wc6-1604210838734)(C:\Users\ningn\AppData\Roaming\Typora\typora-user-images\image-20200612163432947.png)]

vim runbroker.sh

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a2ZZK11z-1604210838736)(C:\Users\ningn\AppData\Roaming\Typora\typora-user-images\image-20200612163711475.png)]

启动 Broker

nohup ./mqbroker -n localhost:9876 &

可以查看日志

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MN5dFnUY-1604210838737)(C:\Users\ningn\AppData\Roaming\Typora\typora-user-images\image-20200612164059688.png)]

启动成功

6、测试 RocketMQ

消息发送

cd binexport NAMESRV_ADDR=localhost:9876./tools.sh org.apache.rocketmq.example.quickstart.Producer

消息接收

cd binexport NAMESRV_ADDR=localhost:9876./tools.sh org.apache.rocketmq.example.quickstart.Consumer

7、关闭 RocketMQ

cd bin./mqshutdown broker./mqshutdown namesrv

安装 RocketMQ 控制台

1、解压缩,修改配置,打包

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-etS3NSSn-1604210838739)(C:\Users\ningn\AppData\Roaming\Typora\typora-user-images\image-20200612190405331.png)]

mvn clean package -Dmaven.test.skip=true

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dOwrvPS8-1604210838740)(C:\Users\ningn\AppData\Roaming\Typora\typora-user-images\image-20200612190454926.png)]

2、进入 target 启动 jar

java -jar rocketmq-console-ng-1.0.0.jar

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0DtGabIl-1604210838740)(C:\Users\ningn\AppData\Roaming\Typora\typora-user-images\image-20200612191058068.png)]

打开浏览器访问 localhost:9877,如果报错

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hLwxi7HT-1604210838741)(C:\Users\ningn\AppData\Roaming\Typora\typora-user-images\image-20200612191202652.png)]

这是因为我们的 RocketMQ 安装在 Linux 中,控制台在 windows,Linux 需要开放端口才能访问,开放 10909 和 9876 端口

firewall-cmd --zone=public --add-port=10909/tcp --permanentfirewall-cmd --zone=public --add-port=9876/tcp --permanentsystemctl restart firewalld.servicefirewall-cmd --reload

重新启动控制台项目

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-419we0Eb-1604210838742)(C:\Users\ningn\AppData\Roaming\Typora\typora-user-images\image-20200612191417412.png)]

Java 实现消息发送

1、pom.xml 中引入依赖

org.apache.rocketmq
rocketmq-spring-boot-starter
2.1.0

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 --permanentsystemctl restart firewalld.servicefirewall-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;@Slf4jpublic 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
list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
log.info("Message=>{}",list); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); //启动消费者 consumer.start(); }}

Spring Boot 整合 RocketMQ

provider

1、pom.xml

org.apache.rocketmq
rocketmq-spring-boot-starter
2.1.0
org.apache.rocketmq
rocketmq-client
4.7.0

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@NoArgsConstructorpublic class Order {
private Integer id; private String buyerName; private String buyerTel; private String address; private Date createDate;}

4、Controller

@Autowiredprivate 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

org.apache.rocketmq
rocketmq-spring-boot-starter
2.1.0
org.apache.rocketmq
rocketmq-client
4.7.0

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
{
@Override public void onMessage(Order order) {
log.info("新订单{},发短信",order); }}

服务网关

Spring Cloud Gateway 是基于 Netty,跟 Servlet 不兼容,所以你的工程中不能出现 Servlet 的组件 。

1、pom.xml

注意,一定不能出现 spring web 的依赖,因为 Gateway 与 Servlet 不兼容。

org.springframework.cloud
spring-cloud-starter-gateway

2、application.yml

server:  port: 8010spring:  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

org.springframework.cloud
spring-cloud-starter-gateway
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery

2、application.yml

server:  port: 8010spring:  application:    name: gateway  cloud:      gateway:        discovery:          locator:            enabled: true

Gateway 限流

基于路由限流

1、pom.xml

org.springframework.cloud
spring-cloud-starter-gateway
com.alibaba.csp
sentinel-spring-cloud-gateway-adapter

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.*;@Configurationpublic class GatewayConfiguration {
private final List
viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; public GatewayConfiguration(ObjectProvider
> 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
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
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: 8010spring:  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.*;@Configurationpublic class GatewayConfiguration {
private final List
viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; public GatewayConfiguration(ObjectProvider
> 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
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
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
definitions = new HashSet<>(); ApiDefinition api1 = new ApiDefinition("provider_api1") .setPredicateItems(new HashSet
(){ { add(new ApiPathPredicateItem().setPattern("/provider/api1/**") .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); }}); ApiDefinition api2 = new ApiDefinition("provider_api2") .setPredicateItems(new HashSet
(){ { 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: 8010spring:  application:    name: gateway  cloud:    gateway:      discovery:        locator:          enabled: true

API 分组代码修改,改为 discovery 中的服务名。

ApiDefinition api2 = new ApiDefinition("provider_api2")        .setPredicateItems(new HashSet
(){
{
add(new ApiPathPredicateItem().setPattern("/p1/api2/demo1")); }});

分布式事务

模拟分布式事务异常

1、创建两个工程 order、pay,pom.xml

org.springframework.boot
spring-boot-starter-jdbc
org.springframework.boot
spring-boot-starter-web
mysql
mysql-connector-java
runtime
org.projectlombok
lombok
true

2、建两个数据库 order、pay,两个微服务分别访问。

3、分别写两个服务的 application.yml

server:  port: 8010spring:  application:    name: order  datasource:    driver-class-name: com.mysql.cj.jdbc.Driver    username: root    password: 123456    url: jdbc:mysql://localhost:3306/order
server:  port: 8020spring:  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;@Servicepublic 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;@Servicepublic 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;@RestControllerpublic 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;@RestControllerpublic 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;@SpringBootApplicationpublic 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;@SpringBootApplicationpublic class PayApplication {
public static void main(String[] args) {
SpringApplication.run(PayApplication.class, args); }}

分布式异常模拟结束,Order 存储完成之后,出现异常,会导致 Pay 无法存储,但是 Order 数据库不会进行回滚。

Seata 解决

1、下载

2、解压,修改两个文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CEW1xJMH-1604210838743)(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-vuSviQik-1604210838744)(C:\Users\ningn\AppData\Roaming\Typora\typora-user-images\image-20200624170027580.png)]

3、启动 Nacos,运行 nacos-config.sh 将 Seata 配置导入 Nacos

进入 conf,右键 Git Bash Here

cd confsh nacos-config.sh 127.0.0.1

执行成功,刷新 Nacos,配置加入

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BwM6r6Pl-1604210838744)(C:\Users\ningn\AppData\Roaming\Typora\typora-user-images\image-20200624170411851.png)]

nacos-config.txt 配置已生效

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EWUn2Vt8-1604210838745)(C:\Users\ningn\AppData\Roaming\Typora\typora-user-images\image-20200624170446667.png)]

4、启动 Seata Server, JDK 8 以上环境无法启动

cd binseata-server.bat -p 8090 -m file

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lkG0QVd9-1604210838746)(C:\Users\ningn\AppData\Roaming\Typora\typora-user-images\image-20200624170701118.png)]

启动成功,Nacos 注册成功。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l3JwI2ur-1604210838747)(C:\Users\ningn\AppData\Roaming\Typora\typora-user-images\image-20200624171016755.png)]

Seata 服务环境搭建完毕,接下来去应用中添加。

1、初始化数据库,在两个数据库中添加事务日志记录表,SQL Seata 已经提供。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O6c5xIjx-1604210838747)(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 组件。

com.alibaba.cloud
spring-cloud-starter-alibaba-seata
2.1.1.RELEASE
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config

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;@SpringBootApplicationpublic 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;@SpringBootApplicationpublic 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-dyQ5mZjY-1604210838748)(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;@RestControllerpublic 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"; }}

转载地址:http://zzqgn.baihongyu.com/

你可能感兴趣的文章
hadoop + zookeeper 安装部署
查看>>
rake应用
查看>>
opera插件开发
查看>>
2012工作日志
查看>>
MongoDB基本介绍及一些用法
查看>>
hash对象
查看>>
基本数据类型和对象
查看>>
mongoDB应用
查看>>
MongoDB 和 MongoMapper的示例用法简介
查看>>
欢迎使用CSDN-markdown编辑器
查看>>
Hadoop和大数据开源工具推荐
查看>>
聚类算法
查看>>
大数据的六大规则
查看>>
rails加载方式
查看>>
Hadoop Storm Spark比较
查看>>
职业测试~~
查看>>
Ruby on Rails调试经验分享
查看>>
ruby中保留2位小数
查看>>
ruby 字符串处理
查看>>
rails console环境下显示AR sql
查看>>