- SpringCloud
- SpringCloud Alibaba
- https://nacos.io/zh-cn/docs/quick-start-spring-cloud.html
虚拟映射,指定端口:">检查端点全部打开
management:
endpoints:
web:
exposure:
include: ‘*’
nacos官网:https://nacos.io/zh-cn/docs/quick-start-spring-cloud.html
虚拟映射,指定端口: - http://nacos-payment-provider">消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
nacos-user-service: http://nacos-payment-provider - 单机模式支持mysql,nacos默认数据库derby
- Sentinel
- Seata处理分布式事务
SpringCloud
微服务cloud整体聚合父工程Project
父工程步骤
1.new project
2.聚合总父工程名字
3.Maven选版本,不要使用自带的maven工程
finish后选择自动导入
4.工程名字
5.字符编码
6.注解生效激活
7.java编译版本
8.File Type过滤(可做可不做)
父工程POM
dependencyManagement
maven中跳过单元测试
父工程创建完成执行mvn:install将父工程发布到仓库方便子工程继承
支付模块构建
微服务模块
1.建module
创建完成后回父pom中看一眼是否存在
2.改POM
payment模块pom
3.写YML
server:
port: 8001
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: com.mysql.jdbc.Driver # org.gjt.mm.mysql.Driver 较老的写法,不通用 # mysql驱动包
url: jdbc:mysql://localhost:3306/cloud2020?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.yunusen.springcloud.entites # 所有Entity别名类所在包
4.主启动
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class, args);
}
}
5.业务类
1) 建表SQL
# TABLE payment
CREATE TABLE payment(
id bigint(20) NOT NULL AUTO_INCREMENT COMMENT ‘ID’ PRIMARY KEY,
serial varchar(200) DEFAULT ‘’
)ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
2)entities
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment implements Serializable {
private Long id;
private String serial;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult
private Integer code;
private String message;
private T data;
public CommonResult(Integer code, String message){
this(code, message, null);// 报错在setting->pluigns中安装lombok,重启idea
}
}
3)dao
@Mapper
public interface PaymentDao {
public int create(Payment payment);
public Payment getPayment(@Param("id") Long id);<br />}<br />在resources中Jmapper文件包,编写PaymentMapper<br /><?xml version="1.0" encoding="utf-8" ?><br /><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
insert into payment(serial) values (#{serial});
<resultMap id="BaseResultMap" type="com.yunusen.springcloud.entites.Payment"><br /> <id column="id" property="id" jdbcType="BIGINT"></id><br /> <id column="serial" property="serial" jdbcType="VARCHAR"></id><br /> </resultMap><br /> <select id="getPaymentById" parameterType="Long" resultMap="BaseResultMap"><br /> select * from payment where id=#{id};<br /> </select><br /></mapper><br />4)service<br />public interface PaymentService {<br /> public int create(Payment payment);
public Payment getPaymentById(@Param("id") Long id);<br />}<br />@Service<br />public class PaymentServiceImpl implements PaymentService {
@Resource<br /> private PaymentDao paymentDao;
@Override<br /> public int create(Payment payment) {<br /> return paymentDao.create(payment);<br /> }
@Override<br /> public Payment getPaymentById(Long id) {<br /> return paymentDao.getPaymentById(id);<br /> }<br />}<br />5)controller<br />@RestController<br />@Slf4j<br />public class PaymentController {
@Resource<br /> private PaymentService paymentService;
@PostMapping(value = "/payment/create")<br /> public CommonResult create(Payment payment){<br /> int result = paymentService.create(payment);<br /> log.info("**插入结果:" + result);
if (result > 0){<br /> return new CommonResult(200, "插入数据库成功", result);<br /> }else{<br /> return new CommonResult(444,"插入数据库失败", null);<br /> }<br /> }
@GetMapping(value = "/payment/get/{id}")<br /> public CommonResult getPaymentById(@PathVariable("id") Long id){<br /> Payment payment = paymentService.getPaymentById(id);<br /> log.info("**查询结果:" + payment);
if (payment != null){<br /> return new CommonResult(200, "查询成功", payment);<br /> }else{<br /> return new CommonResult(444,"没有对应记录,查询ID" + id, null);<br /> }<br /> }<br />}<br />启动,get请求直接网页测试,post请求使用postman测试
热部署
子工程
父工程
消费者模块构建
1.建model
2.改POM
3.写YML
server:
port: 80
4.主启动
5.业务类
RestTemplate:提供了多种便捷访问远程Http服务的方法,是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集。
6.测试
如果404了可能是热部署问题,重启服务器即可
工程重构
POM依赖
构建新模块cloud-api-commons,把重复使用的类(例:entites)放到该模块中,其他使用重复类的模块pom中添加
服务注册中心Eureka模块
cloud-eureka-server7001
pom依赖
application.yml
server:
port: 7001
eureka:
instance:
hostname: localhost # eureka服务端的实例名称
client:
# false表示不想注册中心注册自己
register-with-eureka: false
# false表示自己端就是注册中心,职责就是维护服务实例,不需要去检索服务
fetch-registry: false
service-url:
# 设置与eureka server 交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
主启动添加@EnableEurekaServer
启动出现下图即eureka安装成功
微服务模块入驻eureka,在pom中添加依赖
application.yml添加eureka配置
eureka:
client:
# 表示是否将自己注册进eureka-server,默认为true
register-with-eureka: true
# 是否从eureka-server抓取已有的注册信息,默认为true
# 单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
# 设置与eureka server 交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://localhost:7001/eureka
主启动添加@EnableEurekaClient
在eureka-server启动情况下启动eureka-client,可看到有eureka有服务注册了
Eureka集群原理
建module,参考7001建7002eureka-server
修改映射配置添加进hosts文件C:\Windows\System32\drivers\etc\hosts
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
127.0.0.1 eureka7003.com
写yml,相互注册
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com # eureka服务端的实例名称
client:
# false表示不想注册中心注册自己
register-with-eureka: false
# false表示自己端就是注册中心,职责就是维护服务实例,不需要去检索服务
fetch-registry: false
service-url:
# 设置与eureka server 交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://eureka7002.com:7002/eureka/
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com # eureka服务端的实例名称
client:
# false表示不想注册中心注册自己
register-with-eureka: false
# false表示自己端就是注册中心,职责就是维护服务实例,不需要去检索服务
fetch-registry: false
service-url:
# 设置与eureka server 交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://eureka7001.com:7001/eureka/
将订单支付两微服务注册进eureka集群
修改yml
eureka:
client:
# 表示是否将自己注册进eureka-server,默认为true
register-with-eureka: false
# 是否从eureka-server抓取已有的注册信息,默认为true
# 单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
# 设置与eureka server 交互的地址查询服务和注册服务都需要依赖这个地址
#defaultZone: http://localhost:7001/eureka # 单机版
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # eureka集群版
支付微服务集群配置
建module,参考8001建8002,改主启动名
Order80Controller中端口改变,不在写死,通过服务器别名获取订单访问地址
public final static String PAYMENT_URL = “http://CLOUD-PAYMENT-SERVICE“;
在配置类中添加@LoadBalanced,使RestTemplate具有负载均衡的能力
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
可在controller中获取端口号
@Value(“${server.port}”)
private String serverPort;
actuator微服务信息完善
修改主机名称和显示ip地址,在application.yml中添加主机名称
server:
port: 8001
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: com.mysql.jdbc.Driver # org.gjt.mm.mysql.Driver 较老的写法,不通用 # mysql驱动包
url: jdbc:mysql://localhost:3306/cloud2020?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
eureka:
client:
# 表示是否将自己注册进eureka-server,默认为true
register-with-eureka: true
# 是否从eureka-server抓取已有的注册信息,默认为true
# 单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
# 设置与eureka server 交互的地址查询服务和注册服务都需要依赖这个地址
#defaultZone: http://localhost:7001/eureka # 单机版
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # eureka集群版
# 主机名称
instance:
instance_id: payment8001
prefer-ip-address: true # 访问路径可以显示ip地址
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.yunusen.springcloud.entites # 所有Entity别名类所在包
服务发现Discovery
controller中使用DiscoveryClient
@Resource
private DiscoveryClient discoveryClient;
@GetMapping(value = “/payment/discovery”)
private Object discovery(){
List
for (String element : services){
log.info(“**element: “ + element);
}
List
for (ServiceInstance instance : instances){
log.info(instance.getServiceId() + “\t” + instance.getHost() + “\t” + instance.getPort() + “\t” + instance.getUri());
}<br /> return this.discoveryClient;<br /> }<br />然后主启动类添加@EnableDiscoveryClient
Eureka自我保护机制(默认开启)
属于CAP里的AP分支
在自我保护模式中,Eureka Server会保护服务注册表中的信息,不在注销任何服务实例
如何禁止自我保护:
server7001yml配置:
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com # eureka服务端的实例名称
client:
# false表示不想注册中心注册自己
register-with-eureka: false
# false表示自己端就是注册中心,职责就是维护服务实例,不需要去检索服务
fetch-registry: false
service-url:
# 单机就是指向自己
defaultZone: http://eureka7001.com:7001/eureka/
# 设置与eureka server 交互的地址查询服务和注册服务都需要依赖这个地址
# 集群指向其他eureka
#defaultZone: http://eureka7002.com:7002/eureka/
server:
# 关闭自我保护机制,保护不可用服务及时剔除
enable-self-preservation: false
# 设置服务不可用后剔除间隔时间
eviction-interval-timer-in-ms: 2000
payment8001yml配置:
server:
port: 8001
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: com.mysql.jdbc.Driver # org.gjt.mm.mysql.Driver 较老的写法,不通用 # mysql驱动包
url: jdbc:mysql://localhost:3306/cloud2020?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
eureka:
client:
# 表示是否将自己注册进eureka-server,默认为true
register-with-eureka: true
# 是否从eureka-server抓取已有的注册信息,默认为true
# 单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
# 设置与eureka server 交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://localhost:7001/eureka # 单机版
#defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # eureka集群版
# 主机名称
instance:
instance_id: payment8001
prefer-ip-address: true # 访问路径可以显示ip地址
#eureka自我保护禁止
# Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认30秒)
lease-renewal-interval-in-seconds: 1
# Eureka服务器在收到最后一次心跳后等待时间上限,单位为秒(默认90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 2
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.yunusen.springcloud.entites # 所有Entity别名类所在包
Consul
Eureka、Zookeeper、Consul的异同点
CAP
Ribbon
什么是Ribbon
负载均衡+RestTemplate调用
LB(LoadBalancer负载均衡)
集中式LB
进程内LB
Rest调用
getForObject方法和getForEntity方法
getForObject:返回对象为响应体中的数据转化成的对象,基本可以理解为Json
getForEntity:返回对象为ResponseEntity对象,包含了响应中的一些重要信息,包括响应头,响应状态码,响应体等
报错:org.springframework.web.client.HttpClientErrorException$NotFound: 404 : [{“timestamp”:”2020-08-07T03:45:12.121+0000”,”status”:404,”error”:”Not Found”,”message”:”No message available”,”path”:”/payment/get/2”}]
解决:8002没有方法,添加即可
IRule:根据特定算法中从服务列表中选取一个要访问的服务
如何替换
自定义配置类不能放在@ComponentScan所扫描的当前包以及子包下,否则我们自定义的配置类会被所有而Ribbon客户端共享,达不到特殊化定制的目的了
在订单80模块自启动类包外新建一个包com.yunusen.myrule,新建规则类MySelfRule
@Configuration
public class MySelfRule {
@Bean<br /> public IRule myRule(){<br /> return new RandomRule();// 定义为随机<br /> }<br />}<br />主启动类添加注释@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)
Ribbon负载均衡算法
原理
手写轮询算法
支付8001和8002controller类中添加如下方法输出端口号
@GetMapping(“/payment/lb”)
public String getPaymentLB(){
return serverPort;
}
订单80config中注释掉@LoadBalanced,使用自定义算法
springcloud建自定义包lb,定义LoadBalancer接口
public interface LoadBalancer {
ServiceInstance instances(List
}
实现类MyLB
@Component
public class MyLB implements LoadBalancer {
private AtomicInteger atomicInteger = new AtomicInteger(0);
public final int getAndIncrement(){<br /> int current;<br /> int next;<br /> do{<br /> current = this.atomicInteger.get();<br /> next = current >= Integer.MAX_VALUE ? 0 : current + 1;//Integer.MAX_VALUE=2147483647<br /> }while (!this.atomicInteger.compareAndSet(current, next));<br /> System.out.println("**次数next: " + next);<br /> return next;<br /> }
// 负载均衡算法:rest接口第几次请求树 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务器重启后rest接口从1开始计数<br /> @Override<br /> public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
int index = getAndIncrement() % serviceInstances.size();<br /> return serviceInstances.get(index);<br /> }<br />}<br />controller中加入方法测试<br />@Resource<br />private LoadBalancer loadBalancer;
@Resource
private DiscoveryClient discoveryClient;
@GetMapping(value = “/consumer/payment/lb”)
public String getPaymentLB(){
List
if (instances == null || instances.size() <= 0){
return null;
}
ServiceInstance serviceInstance = loadBalancer.instances(instances);<br /> URI uri = serviceInstance.getUri();
return restTemplate.getForObject(uri + "/payment/lb", String.class);
OpenFeign
Feign和OpenFeign的区别
新建消费端module:cloud-consumer-feign-order80
pom依赖
application.yml
server:
port: 80
eureka:
client:
# 表示是否将自己注册进eureka-server,默认为true
register-with-eureka: false
service-url:
# 设置与eureka server 交互的地址查询服务和注册服务都需要依赖这个地址
#defaultZone: http://localhost:7001/eureka # 单机版
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # eureka集群版
主启动类添加@EnableFeignClients
业务类接口
@Component
@FeignClient(value = “CLOUD-PAYMENT-SERVICE”)
public interface PaymentFeignService {
@GetMapping(value = "/payment/get/{id}")<br /> public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);<br />}
controller类
@RestController
@Slf4j
public class OrderFeignController {
@Resource<br /> private PaymentFeignService paymentFeignService;
@GetMapping(value = "/consumer/payment/get/{id}")<br /> public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){<br /> return paymentFeignService.getPaymentById(id);<br /> }<br />}
OpenFeign超时控制
超时测试
订单8001controller中添加超时方法timeout
@GetMapping(value = “/payment/feign/timeout”)
public String paymentFeignTimeout(){
try {
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e){
e.printStackTrace();
}
return serverPort;
}
消费80service中添加timeout方法
@GetMapping(value = “/payment/feign/timeout”)
public String paymentFeignTimeout();
消费80controller添加timeout方法
@GetMapping(value = “/consumer/payment/feign/timeout”)
public String paymentFeignTimeout(){
// openFeign-ribbon, 客户端一般默认等待一秒钟
return paymentFeignService.paymentFeignTimeout();
}
报错:status 404 reading PaymentFeignService#paymentFeignTimeout()
原因:测试超时的方法只在一台服务提供者有,另一台没有,url不存在,造成了404
报错:java.net.SocketTimeoutException: Read timed out
原因:服务提供者方法超时,Feign默认1秒没有响应就超时报错
设置超时控制
yml添加
# 设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
# 指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
# 指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
OpenFeign日志打印功能
添加一个config类
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
yml配置
logging:
level:
# feign日志以什么级别监控哪个接口
com.yunusen.springcloud.service.PaymentFeignService: debug
Hystrix
Gateway
Config
Bus
Stream
屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型
Stream中的消息通信方式遵循了发布-订阅模式–Topic模式进行广播(RabbitMQ中Exchange,Kafka中Topic)
建cloud-provider-payment8001生产者module
pom依赖
<!--eureka-client--><br /> <dependency><br /> <groupId>org.springframework.cloud</groupId><br /> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId><br /> </dependency><br /> <dependency><br /> <groupId>org.springframework.cloud</groupId><br /> <artifactId>spring-cloud-starter-stream-rabbit</artifactId><br /> </dependency><br /> <!--热部署--><br /> <dependency><br /> <groupId>org.springframework.boot</groupId><br /> <artifactId>spring-boot-devtools</artifactId><br /> <scope>runtime</scope><br /> <optional>true</optional><br /> </dependency><br /> <dependency><br /> <groupId>org.projectlombok</groupId><br /> <artifactId>lombok</artifactId><br /> <optional>true</optional><br /> </dependency><br /> <dependency><br /> <groupId>org.springframework.boot</groupId><br /> <artifactId>spring-boot-starter-test</artifactId><br /> <scope>test</scope><br /> </dependency><br /> </dependencies><br />yml<br />server:<br /> port: 8801
spring:
application:
name: cloud-stream-provider
cloud:
stream:
binders: #在此处配置要绑定的rabbitmq的服务信息
defaultRabbit: # 表示定义的名称,用于binding的整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: yunusen
password: 123456
bindings: # 服务的整合处理
output: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
context-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
bind er: defaultRabbit # 设置要绑定的消息服务的具体设置
eureka:
client:
service-url:
# 设置与eureka server 交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://localhost:7001/eureka # 单机版
#defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # eureka集群版
# 主机名称
instance:
instance_id: send8801.com
prefer-ip-address: true # 访问路径可以显示ip地址
# Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认30秒)
lease-renewal-interval-in-seconds: 2
# Eureka服务器在收到最后一次心跳后等待时间上限,单位为秒(默认90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 5
service
@EnableBinding(Source.class) // 定义消息的推送管道
@Slf4j
public class MessageProviderImpl implements IMessageProvider {
@Resource<br /> private MessageChannel output;// 消息发送管道
@Override<br /> public String send() {<br /> String serial = UUID.randomUUID().toString();<br /> output.send(MessageBuilder.withPayload(serial).build());<br /> log.info("***serial: " + serial);<br /> return null;<br /> }<br />}<br />controller<br />@RestController<br />public class SendMessageController {
@Resource<br /> private IMessageProvider messageProvider;
@GetMapping(value = "/sendMessage")<br /> public String sendMessage(){<br /> return messageProvider.send();<br /> }<br />}<br />建cloud-stream-rabbitmq-consumer8802消费者moduel<br />配置与8801一致,yml有改动output–>input<br />controller<br />@RestController<br />@EnableBinding(Sink.class)<br />public class ReceiveMessageListenerController {
@Value("${server.port}")<br /> private String serverPort;
@StreamListener(Sink.INPUT)<br /> public void input(Message<String> message){<br /> log.info("消费者1号,接收到的信息:" + message.getPayload() + "\t" + "serverPort:" + serverPort);<br /> }<br />}
分组消费和持久化
复制8802建module8803消费者
存在重复消费问题
原因:默认分组group是不同的,组流水号不一样,被认为不同组,可以消费
自定义配置分组,分为同一个组,解决重复消费问题
yml添加group,自定义分组,指定group即持久化
spring:
application:
name: cloud-stream-provider
cloud:
stream:
binders: #在此处配置要绑定的rabbitmq的服务信息
defaultRabbit: # 表示定义的名称,用于binding的整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: yunusen
password: 123456
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
context-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
group: yunusen1
Sleuth:监控链路
spring:
application:
name: cloud-payment-service
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
# 采样率介于0到1之间,1则表示全部采集
probability: 1
Zipkin安装搭建:
zipkin jar下载地址:https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/2.12.9/
安装命令:java -jar zipkin-server-2.12.9-exec.jar
SpringCloud Alibaba
Nacos
Nacos:Dynamic Naming and Configuration Service
一个更易于构建云原生应用的动态服务发现,配置管理和服务管理凭条
Nacos就是注册中心+配置中心的组合
Nacos = Eureka + Config + Bus
下载地址:https://github.com/alibaba/nacos/releases/tag/1.1.4
解压运行startup.cmd启动,登录http://localhost:8848/nacos/,默认账号密码都为nacos
新建module cloudalibaba-provider-payment9001
pom添加nacos依赖
yml
server:
port: 9001
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 # 配置nacos地址
检查端点全部打开
management:
endpoints:
web:
exposure:
include: ‘*’
nacos官网:https://nacos.io/zh-cn/docs/quick-start-spring-cloud.html
虚拟映射,指定端口:


nacos注册和负载
复制9001建9002
建module cloudalibaba-consumer-nacos-order83
pom和9001一致
yml
server:
port: 83
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848 # 配置nacos地址
消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
nacos-user-service: http://nacos-payment-provider
报错:java.net.UnknownHostException: nacos-payment-provider
原因:未在配置类添加@LoadBalanced
服务中心对比:
Nacos支持AP和CP模式的切换
Nacos服务配置中心
新建module cloudalibaba-config-nacos-client3377
pom
<!--热部署--><br /> <dependency><br /> <groupId>org.springframework.boot</groupId><br /> <artifactId>spring-boot-devtools</artifactId><br /> <scope>runtime</scope><br /> <optional>true</optional><br /> </dependency><br /> <dependency><br /> <groupId>org.projectlombok</groupId><br /> <artifactId>lombok</artifactId><br /> <optional>true</optional><br /> </dependency><br /> <dependency><br /> <groupId>org.springframework.boot</groupId><br /> <artifactId>spring-boot-starter-test</artifactId><br /> <scope>test</scope><br /> </dependency><br /> </dependencies><br />bootstrap.yml<br />server:<br /> port: 3377
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos服务注册中心地址
config:
server-addr: localhost:8848 # nacos作为配置中心地址
file-extension: yaml # 指定yaml格式的配置
application.yml
spring:
profiles:
active: dev #表示开发环境
controller
@RestController
@Slf4j
@RefreshScope // 支持nacos的动态刷新功能
public class ConfigClientController {
@Value("${config.info}")<br /> private String configInfo;
@GetMapping("/config/info")<br /> public String getConfigInfo(){<br /> return configInfo;<br /> }<br />}<br />nacos8848配置dataId格式<br />#${prefix}-${spring.profiles.active}.${file-extension}<br />#${spring.application.name}-${spring.profiles.active}.${file-extension}<br />#nacos-config-client-dev.yaml<br /><br />报错:org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.configClientController': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'config.info' in value "${config.info}"<br />原因:nacos配置文件改为yaml后缀<br />**DataID配置**<br /><br />spring:<br /> profiles:<br /> #active: dev #表示开发环境<br /> active: test #表示测试环境<br />**Group配置**<br /><br />在bootstrap.yml中添加spring.cloud.nacos.config.group: TEST_GROUP<br />**NameSpace空间方案**<br /><br />在bootstrap.yml中添加spring.cloud.nacos.config.namespace: 37d60695-fbae-461d-9a00-e3f2929691b3 #dev
单机模式支持mysql,nacos默认数据库derby
在0.7版本之前,在单机模式时nacos使用嵌入式数据库实现数据的存储,不方便观察数据存储的基本情况。0.7版本增加了支持mysql数据源能力,具体的操作步骤:
- 1.安装数据库,版本要求:5.6.5+
- 2.初始化mysql数据库,数据库初始化文件:nacos-mysql.sql
- 3.修改conf/application.properties文件,增加支持mysql数据源配置(目前只支持mysql),添加mysql数据源的url、用户名和密码。
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://11.162.196.16:3306/nacos_devtest?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true08:
db.user=nacos_devtest
db.password=youdontknow
再以单机模式启动nacos,nacos所有写嵌入式数据库的数据都写到了mysql
Nacos集群和持久化配置
Sentinel
下载地址:https://github.com/alibaba/Sentinel/releases
使用命令打开sentinel:java -jar sentinel-dashboard-1.7.2.jar
新建module cloudalibaba-sentinel-service8401
pom
<dependency><br /> <groupId>com.alibaba.cloud</groupId><br /> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId><br /> </dependency><br /><!-- 持久化--><br /> <dependency><br /> <groupId>com.alibaba.csp</groupId><br /> <artifactId>sentinel-datasource-nacos</artifactId><br /> </dependency><br /> <dependency><br /> <groupId>com.alibaba.cloud</groupId><br /> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId><br /> </dependency><br /> <dependency><br /> <groupId>org.springframework.cloud</groupId><br /> <artifactId>spring-cloud-starter-openfeign</artifactId><br /> </dependency><br /> <!-- 引用自己定义的api通用包,可以使用Payment支付Entity --><br /> <dependency><br /> <groupId>com.yunusen</groupId><br /> <artifactId>cloud-api-commons</artifactId><br /> <version>${project.version}</version><br /> </dependency><br /> <dependency><br /> <groupId>org.springframework.boot</groupId><br /> <artifactId>spring-boot-starter-web</artifactId><br /> </dependency><br /> <!--监控--><br /> <dependency><br /> <groupId>org.springframework.boot</groupId><br /> <artifactId>spring-boot-starter-actuator</artifactId><br /> </dependency>
<!--热部署--><br /> <dependency><br /> <groupId>org.springframework.boot</groupId><br /> <artifactId>spring-boot-devtools</artifactId><br /> <scope>runtime</scope><br /> <optional>true</optional><br /> </dependency><br /> <dependency><br /> <groupId>org.projectlombok</groupId><br /> <artifactId>lombok</artifactId><br /> <optional>true</optional><br /> </dependency><br /> <dependency><br /> <groupId>org.springframework.boot</groupId><br /> <artifactId>spring-boot-starter-test</artifactId><br /> <scope>test</scope><br /> </dependency><br /> </dependencies><br />yml:注意格式<br />server:<br /> port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos服务注册中心地址
sentinel:
transport:
# 配置sentinel dashboard地址
dashboard: localhost:8080
# 默认端口。假如被占用会自动从8719开始依次+1扫描,直到找到未被占用等1端口
port: 8719
management:
endpoints:
web:
exposure:
include: ‘*’
发送请求测试
流控规则
QPS直接失败:sentinel客户端设置qps
线程数直接失败:sentinel客户端设置线程数
关联失败:当该方法关联的另一个方法被限流了,该方法也被限流
预热:公式:阈值/冷加载因子coldFactor(默认值3),刚开始被限流,再规定时间内大道阈值,应用场景:秒杀设计
排队等待:让请求以均匀的速度通过,对应的是漏桶算法
链路:只记录链路路口的流量
@Service
@Slf4j
public class OrderService {
@SentinelResource("getOrder")<br /> public String getOrder(){<br /> log.info("==order==");<br /> return "order";<br /> }<br />}<br />@RestController<br />@Slf4j<br />public class FlowLimitController {
@Autowired<br /> private OrderService orderService;
@GetMapping("/testA")<br /> public String testA(){<br /> orderService.getOrder();<br />// try{<br />// TimeUnit.MILLISECONDS.sleep(800);<br />// }catch (InterruptedException e){<br />// e.printStackTrace();<br />// }<br /> return "---testA";<br /> }
@GetMapping("/testB")<br /> public String testB(){<br /> orderService.getOrder();//鏈路调用<br /> //log.info(Thread.currentThread().getName()+ "\t" + "/testB");<br /> return "---testB";<br /> }<br />}<br /><br />请求接口testB会调用getOrder进行流量控制,testA不会。
针对来源是微服务级别的,链路模式的入口资源是针对方法接口的。
熔断降级
- 慢调用比例 (
SLOW_REQUEST_RATIO
):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。 - 异常比例 (
ERROR_RATIO
):当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是[0.0, 1.0]
,代表 0% - 100%。 异常数 (
ERROR_COUNT
):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
热点规则
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
- 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
// value为资源名,blockHandler为自定义限流方法
@GetMapping(value = “/testHotKey”)
@SentinelResource(value = “testHotKey”, blockHandler = “deal_testHotKey”)
public String testHotKey(
@RequestParam(value = “p1”, required = false) String p1,@RequestParam(value = “p2”, required = false) String p2){
return “testHotKey”;
}
public String deal_testHotKey(String p1, String p2, BlockException ex){<br /> return "deal_testHotKey";<br /> }<br />
系统规则
系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN
),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。
系统规则支持以下的模式:
- Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的
maxQps * minRt
估算得出。设定参考值一般是CPU cores * 2.5
。 - CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
- 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
- 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
- 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
@SentinelResource注解
注意:注解方式埋点不支持 private 方法。
@SentinelResource
用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource
注解包含以下属性:
value
:资源名称,必需项(不能为空)entryType
:entry 类型,可选项(默认为EntryType.OUT
)blockHandler
/blockHandlerClass
:blockHandler
对应处理BlockException
的函数名称,可选项。blockHandler 函数访问范围需要是public
,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为BlockException
。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定blockHandlerClass
为对应的类的Class
对象,注意对应的函数必需为 static 函数,否则无法解析。- fallback/fallbackClass:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要和原函数一致,或者可以额外多一个
Throwable
类型的参数用于接收对应的异常。 - fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定
fallbackClass
为对应的类的Class
对象,注意对应的函数必需为 static 函数,否则无法解析。
- 返回值类型必须与原函数返回值类型一致;
- defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要为空,或者可以额外多一个
Throwable
类型的参数用于接收对应的异常。 - defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定
fallbackClass
为对应的类的Class
对象,注意对应的函数必需为 static 函数,否则无法解析。
- 返回值类型必须与原函数返回值类型一致;
exceptionsToIgnore
(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
Sentinel服务熔断Ribbon
public static final String SERVICE_URL = “http://nacos-payment-provider“;
@Resource
private RestTemplate restTemplate;
/
@SentinelResource(value = “fallback”)//没有配置
@SentinelResource(value = “fallback”, fallback = “handlerFallback”)//fallback只负责业务异常
@SentinelResource(value = “fallback”, blockHandler = “blockHandler”)// blockHandler只负责sentinel控制台配置异常
@SentinelResource(value = “fallback”, fallback = “handlerFallback”, blockHandler = “blockHandler”)
/
@RequestMapping(“/consumer/fallback/{id}”)
@SentinelResource(value = “fallback”, fallback = “handlerFallback”, blockHandler = “blockHandler”, exceptionsToIgnore = IllegalAccessException.class)
public CommonResult
CommonResult
if (id == 4){
throw new IllegalAccessException(“IllegalAccessException, 非法参数异常”);
}else if(result.getData() == null) {
throw new NullPointerException(“NullPointerException, 该id无对应记录,空指针异常”);
}
return result;
}
public CommonResult
Payment payment = new Payment(id, null);
return new CommonResult<>(444, “handlerException兜底异常,内容:” + throwable.getMessage(), payment);
}
public CommonResult
Payment payment = new Payment(id, null);
return new CommonResult<>(445, “blockHandler-sentinel限流,内容:” + ex.getMessage(), payment);
}
Sentinel服务熔断OpenFeign
yml
feign:
sentinel:
enabled: true
@FeignClient(value = “nacos-payment-provider”, fallback = PaymentFallbackService.class)
public interface PaymentService {
@GetMapping(value = "/paymentSQL/{id}")<br /> CommonResult<Payment> paymentSQL(@PathVariable("id")Long id);<br />}<br />@Component<br />public class PaymentFallbackService implements PaymentService {<br /> @Override<br /> public CommonResult<Payment> paymentSQL(Long id) {<br /> return new CommonResult<>(444, "服务降级返回,--PaymentFallbackService", new Payment(id, "errorSerial"));<br /> }<br />}<br />controller<br />// openFeign<br />@Resource<br />private PaymentService paymentService;
@GetMapping(value = “/consumer/paymentSQL/{id}”)
public CommonResult
return paymentService.paymentSQL(id);
};
sentinel持久化规则
yml
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos服务注册中心地址
sentinel:
transport:
# 配置sentinel dashboard地址
dashboard: localhost:8080
# 默认端口。假如被占用会自动从8719开始依次+1扫描,直到找到未被占用等1端口
port: 8719
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: cloudalibaba-sentinel-service
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
nacos客户端配置
[
{
“resource”: “/rateLimit/byUrl”,
“limitApp”: “default”,
“grade”: 1,
“count”: 1,
“strategy”: 0,
“controlBehavior”: 0,
“clusterMode”: false
}
]
resource:资源名称
limitApp:来源应用
grade:阈值类型,0表示线程数,1表示QPS
count:单机阈值
strategy:流控模式,0表示直接,1表示关联,2表示链路
controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待
clusterMode:是否集群
Seata处理分布式事务
Seate是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
官网地址:http://seata.io/zh-cn/
分布式事务处理过程的一ID+三组件模型(全局唯一的事务ID+seata术语三组件)
Seata术语
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
下载地址:https://github.com/seata/seata/releases/tag/v0.9.0
1.修改file.conf文件,先备份在修改
修改:自定义事务组名称+事务日志存储模式为db+数据库连接信息
service模块:
vgroup_mapping.my_test_tx_group = “yunusen_tx_group”
store模块:
mode = “db”
db 模块: url = “jdbc:mysql://127.0.0.1:3306/seata” user = “root” password = “root”
2.mysql新建库seata
3.在seata库建表:建表db_store.sql在conf里面,直接在seata库中运行
4.修改conf里面的registry.conf配置文件
registry模块:
type = “nacos”
nacos模块:
serverAddr = “localhost:8848”
5.先启动nacos,后启动seata
6.创建业务数据库:
seata_order:存储订单的数据库
seata_storage:存储库存的数据库
seata_account:存储账户信息的数据库
7.在各个数据库下建立业务表:
seata_order建立t_order表:
CREATE TABLE t_order(
id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT(11) DEFAULT NULL COMMENT ‘用户id’,
product_id BIGINT(1) DEFAULT NULL COMMENT ‘产品id’,
count INT(11) DEFAULT NULL COMMENT ‘数量’,
money DECIMAL(11,0) DEFAULT NULL COMMENT ‘金额’,
status INT(1) DEFAULT NULL COMMENT ‘订单状态: 0:创建中,1:已完结’
)ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8
seata_storage建立t_storage表:
CREATE TABLE t_storage(
id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
product_id BIGINT(1) DEFAULT NULL COMMENT ‘产品id’,
total INT(11) DEFAULT NULL COMMENT ‘总库存’,
used INT(11) DEFAULT NULL COMMENT ‘已用库存’,
residue INT(11) DEFAULT NULL COMMENT ‘剩余库存’
)ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
INSERT INTO t_storage(id,product_id,total,used,residue)
VALUES(1,1,100,0,100);
seata_account建立t_account表:
CREATE TABLE t_account(
id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT(11) DEFAULT NULL COMMENT ‘用户id’,
total INT(11) DEFAULT NULL COMMENT ‘总额度’,
used INT(11) DEFAULT NULL COMMENT ‘已用余额’,
residue INT(11) DEFAULT NULL COMMENT ‘剩余可用额度’
)ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
INSERT INTO t_account(id,user_id,total,used,residue)
VALUES(1,1,1000,0,1000);
三个业务库建立日志回滚undo_log表,sql在conf中db_undo_log.sql,复制运行即可
8.新建module seata-order-service2001
pom依赖
<dependency><br /> <groupId>org.mybatis.spring.boot</groupId><br /> <artifactId>mybatis-spring-boot-starter</artifactId><br /> </dependency><br /> <dependency><br /> <groupId>com.alibaba</groupId><br /> <artifactId>druid-spring-boot-starter</artifactId><br /> <version>1.1.10</version><br /> <!--如果没写版本,从父层面找,找到了就直接用,全局统一--><br /> </dependency><br /> <!--mysql-connector-java--><br /> <dependency><br /> <groupId>mysql</groupId><br /> <artifactId>mysql-connector-java</artifactId><br /> </dependency><br /> <!--jdbc--><br /> <dependency><br /> <groupId>org.springframework.boot</groupId><br /> <artifactId>spring-boot-starter-jdbc</artifactId><br /> </dependency>
<!--热部署--><br /> <dependency><br /> <groupId>org.springframework.boot</groupId><br /> <artifactId>spring-boot-devtools</artifactId><br /> <scope>runtime</scope><br /> <optional>true</optional><br /> </dependency><br /> <dependency><br /> <groupId>org.projectlombok</groupId><br /> <artifactId>lombok</artifactId><br /> <optional>true</optional><br /> </dependency><br /> <dependency><br /> <groupId>org.springframework.boot</groupId><br /> <artifactId>spring-boot-starter-test</artifactId><br /> <scope>test</scope><br /> </dependency><br /> </dependencies><br />yml<br />server:<br /> port: 2001
spring:
application:
name: seata-order-service
cloud:
alibaba:
seata:
#自定义事务组名称需要与seata-server中的对应
tx-service-group: yunusen_tx_group
nacos:
discovery:
server-addr: localhost:8848
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_order
username: root
password: root
feign:
hystrix:
enabled: false
logging:
level:
io:
seata: info
mybatis:
mapperLocations: classpath:mapper/*.xml
9.复制conf下file.conf,registry.conf到项目resources下
10.建domain包,填写基本类
CommonResult
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult
private Integer code;<br /> private String message;<br /> private T data;<br /> public CommonResult(Integer code, String message){<br /> this(code, message, null);<br /> }<br />}<br />Order<br />@Data<br />@AllArgsConstructor<br />@NoArgsConstructor<br />public class Order {
private Long id;
private Long userId;
private Long productId;
private Integer count;
private BigDecimal money;
private Integer status;// 订单状态: 0:创建中,1:已完结<br />}<br />11.Dao接口及实现类<br />@Mapper<br />public interface OrderDao {
// 1.新建订单<br /> void create(Order order);
// 2.修改订单状态,从0改为1<br /> void update(@Param("userId") Long userId, @Param("status") Integer status);<br />}<br /><?xml version="1.0" encoding="utf-8" ?><br /><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<resultMap id="BaseResultMap" type="com.yunusen.springcloud.alibaba.domain.Order"><br /> <id column="id" property="id" jdbcType="BIGINT"></id><br /> <result column="user_id" property="userId" jdbcType="BIGINT"></result><br /> <result column="product_id" property="product_id" jdbcType="BIGINT"></result><br /> <result column="count" property="count" jdbcType="INTEGER"></result><br /> <result column="money" property="money" jdbcType="DECIMAL"></result><br /> <result column="status" property="status" jdbcType="INTEGER"></result><br /> </resultMap><br /> <br /> <insert id="create"><br /> insert into t_order(id, user_id, product_id, count, money, status)<br /> values (null, #{userId}, #{product_id}, #{count}, #{money}, 0);<br /> </insert>
<update id="update"><br /> update t_order set status=1 where user_id=#{userId} and status=#{status};<br /> </update><br /></mapper><br />@Service<br />@Slf4j<br />public class OrderServiceImpl implements OrderService {<br /> @Resource<br /> private OrderDao orderDao;<br /> @Resource<br /> private StorageService storageService;<br /> @Resource<br /> private AccountService accountService;
/**<br /> * 下订单-->减库存-->减余额-->改状态<br /> * @param order<br /> */<br /> @Override<br /> public void create(Order order) {<br /> log.info("-->开始新建订单");<br /> // 1.新建订单<br /> orderDao.create(order);
log.info("-->订单微服务开始调用库存,做扣减count");<br /> // 2.扣减库存<br /> storageService.decrease(order.getProductId(), order.getCount());<br /> log.info("-->订单微服务开始调用库存,做扣减end");
log.info("-->订单微服务开始调用账户,做扣减money");<br /> // 3.扣减账户余额<br /> accountService.decrease(order.getUserId(), order.getMoney());<br /> log.info("-->订单微服务开始调用账户,做扣减end");
// 4.修改订单的状态,从0到1,1代表已经完成<br /> log.info("-->修改订单状态开始");<br /> orderDao.update(order.getUserId(), 0);<br /> log.info("-->修改订单状态结束");
log.info("-->订单结束");
}<br />}}<br />controller<br />@RestController<br />public class OrderController {
@Resource<br /> private OrderService orderService;
@GetMapping("/order/create")<br /> public CommonResult create(Order order){<br /> orderService.create(order);<br /> return new CommonResult(200, "订单创建成功");<br /> }
}
config
package com.yunusen.springcloud.alibaba.config;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
/*
@author yunusen
@version 1.0
@date 2020/8/29 3:08
使用seata对数据源进行代理
/
@Configuration
public class DataSourceProxyConfig {
@Value("${mybatis.mapperLocations}")<br /> private String mapperLocations;
@Bean<br /> @ConfigurationProperties(prefix = "spring.datasource")<br /> public DataSource druidDataSource(){<br /> return new DruidDataSource();<br /> }
@Bean<br /> public DataSourceProxy dataSourceProxy(DataSource dataSource){<br /> return new DataSourceProxy(dataSource);<br /> }
@Bean<br /> public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();<br /> sqlSessionFactoryBean.setDataSource(dataSourceProxy);<br /> sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));<br /> sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());<br /> return sqlSessionFactoryBean.getObject();<br /> }<br />}<br />@Configuration<br />@MapperScan({"com.yunusen.springcloud.alibaba.dao"})<br />public class MyBatisConfig {<br />}<br />主启动类<br />@EnableDiscoveryClient<br />@EnableFeignClients<br />@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)// 取消数据源的自动配置<br />public class SeataOrderMain2001 {
public static void main(String[] args) {<br /> SpringApplication.run(SeataOrderMain2001.class, args);<br /> }<br />}<br />依样对seata-storage-service2002,seata-account-service2003进行编写<br />报错:feign.FeignException$MethodNotAllowed: status 405 reading StorageService#decrease(Long,Integer)<br />解决:@PostMapping该为@GetMapping<br />报错:feign.FeignException$InternalServerError: status 500 reading StorageService#decrease(Long,Integer)<br />解决:sql字段拼写错误<br />报错:java.net.SocketTimeoutException: Read timed out<br />解决:feign.client.config.default.connect-timeout=300000<br />feign.client.config.default.read-timeout=300000<br />//业务类方法加上,保证事务一致性<br />@GlobalTransactional(name = "yunusen-create-order", rollbackFor = Exception.class)
分布式事务执行流程
一阶段得到前镜像和后镜像,二阶段回滚(有数据校验)或者提交
详见官网:http://seata.io/zh-cn/docs/overview/what-is-seata.html