Spring Cloud Alibaba
Nacos
一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
- 替代Eureka做服务注册中心
- 替代Config做服务配置中心
文档:spring-cloud-alibaba-group.github.io/github-page…
-- nacos安装并运行
nacos单机启动
startup.cmd -m standalone
nacos服务注册中心
基于nacos的服务提供者9001
核心依赖
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
配置文件
server:
port: 9001
spring:
application:
name: alibaba-nacos-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址
#暴露所有监控端点
management:
endpoints:
web:
exposure:
include: '*'
主启动类 @EnableDiscoveryClient
/**
* Created by KingsLanding on 2022/11/3 15:20
*/
@EnableDiscoveryClient
@SpringBootApplication
public class AlibabaNacosOrderTest9001 {
public static void main(String[] args) {
SpringApplication.run(AlibabaNacosOrderTest9001.class,args);
}
}
业务类
/**
* Created by KingsLanding on 2022/11/3 15:23
*/
@RestController
public class NacosOrderController {
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "/alibaba/nacos/{id}")
public String getPayment(@PathVariable("id") Integer id)
{
return "nacos registry, serverPort: "+ serverPort+"\t id"+id;
}
}
基于nacos的服务提供者9002
同9001
基于nacos的服务调用方90
核心依赖
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
配置文件
server:
port: 90
spring:
application:
name: alibaba-nacos-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
nacos-user-service: http://alibaba-nacos-provider
主启动类
/**
* Created by KingsLanding on 2022/11/3 15:45
*/
@SpringBootApplication
@EnableDiscoveryClient
public class AlibabaNacosConsumerTest90 {
public static void main(String[] args) {
SpringApplication.run(AlibabaNacosConsumerTest90.class,args);
}
}
业务类
- alibaba nacos自带Ribbon负载均衡
/**
* Created by KingsLanding on 2022/11/3 15:47
*
* alibaba nacos自带Ribbon负载均衡
*/
@RestController
public class NacosConsumerController {
@Resource
private RestTemplate restTemplate;
@Value("${service-url.nacos-user-service}")//获取application.yml的service-url.nacos-user-service的value数据
private String serverURL;
@GetMapping("/consumer/alibaba/nacos/{id}")
public String paymentInfo(@PathVariable("id") Long id)
{
return restTemplate.getForObject(serverURL+"/alibaba/nacos/"+id,String.class);
}
}
注册中心对比
Nacos 支持AP和CP模式的切换
C是所有节点在同一时间看到的数据是一致的;而A的定义是所有的请求都会收到响应。
何时选择使用何种模式? 一般来说,如果不需要存储服务级别的信息且服务实例是通过nacos-client注册,并能够保持心跳上报,那么就可以选择AP模式。当前主流的服务如 Spring cloud 和 Dubbo 服务,都适用于AP模式,AP模式为了服务的可能性而减弱了一致性,因此AP模式下只支持注册临时实例。
如果需要在服务级别编辑或者存储配置信息,那么 CP 是必须,K8S服务和DNS服务则适用于CP模式。CP模式下则支持注册持久化实例,此时则是以 Raft 协议为集群运行模式,该模式下注册实例之前必须先注册服务,如果服务不存在,则会返回错误。
Nacos服务配置中心3377
核心依赖
<!--nacos-config-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--nacos-discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
配置文件
bootstrop.yml
# nacos配置
server:
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格式的配置
group: DEV_GROUP #nacos配置文件分组读取,环境隔离
namespace: 6ee63955-8237-4919-8d83-572d35de6691 #命名空间ID ,隔离效果;namespaceID-->groupID-->dataID
# ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
application.yml
spring:
profiles:
active: dev # 表示开发环境;根据不同的开发环境,可以实现拉取指定的配置文件
# active: test
主启动类
/**
* Created by KingsLanding on 2022/11/3 19:26
*/
@SpringBootApplication
@EnableDiscoveryClient
public class AlibabaNacosConfigClient3377 {
public static void main(String[] args) {
SpringApplication.run(AlibabaNacosConfigClient3377.class,args);
}
}
业务类 @RefreshScope
/**
* Created by KingsLanding on 2022/11/3 19:40
*/
@RestController
@RefreshScope //在控制器类加入@RefreshScope注解使当前类下的配置支持Nacos的动态刷新功能。
public class NacosConfigController {
@Value("${config.info}")
private String configInfo;
@GetMapping("/config/info")
public String getConfigInfo() {
return configInfo;
}
}
nacos中配置信息添加与匹配规则
匹配规则对应配置文件
data ID:
- spring.application.name:nacos-config-client
- spring.profile.active:dev
- spring.cloud.nacos.config.file-extension:yaml**(注意此处必须写yaml)**
{spring.profile.active}.${spring.cloud.nacos.config.file-extension}
nacos命名空间与Group和Data ID
Namespace+Group+Data ID
默认情况: Namespace=public,Group=DEFAULT_GROUP, 默认Cluster是DEFAULT
Namespace主要用来实现隔离 比方说我们现在有三个环境:开发、测试、生产环境,我们就可以创建三个Namespace,不同的Namespace之间是隔离的。
Group可以把不同的微服务划分到同一个分组里面去
Service就是微服务;一个Service可以包含多个Cluster(集群),Nacos默认Cluster是DEFAULT,Cluster是对指定微服务的一个虚拟划分。 比方说为了容灾,将Service微服务分别部署在了杭州机房和广州机房, 这时就可以给杭州机房的Service微服务起一个集群名称(HZ), 给广州机房的Service微服务起一个集群名称(GZ),还可以尽量让同一个机房的微服务互相调用,以提升性能。
最后是Instance,就是微服务的实例。
隔离效果;namespaceID-->groupID-->dataID
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos服务注册中心地址
config:
server-addr: localhost:8848 #Nacos作为配置中心地址
file-extension: yaml #指定yaml格式的配置
group: DEV_GROUP #nacos配置文件分组读取,环境隔离
namespace: 6ee63955-8237-4919-8d83-572d35de6691 #命名空间ID ,隔离效果;namespaceID-->groupID-->dataID
Nacos持久化配置
Nacos默认自带的是嵌入式数据库derby
#### 将nacos数据库从derby切换到MySQL
- \nacos\conf目录下找到application.properties
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://localhost:3306/nacos_config?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false
db.user=root
db.password=zmj.666.999
Sentinel实现熔断与限流
启动:java -jar sentinel-dashboard-1.7.0.jar
登录账号密码均为sentinel
Sentinel采用的懒加载
构建Sentinel服务
核心依赖
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!--SpringCloud ailibaba sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
配置文件
server:
port: 8401
spring:
application:
name: cloud-alibaba-sentinel-service
cloud:
nacos:
discovery:
#Nacos服务注册中心地址
server-addr: localhost:8848
sentinel:
transport:
#配置Sentinel dashboard地址
dashboard: localhost:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
management:
endpoints:
web:
exposure:
include: '*'
主启动类
/**
* Created by KingsLanding on 2022/11/4 19:07
*/
@SpringBootApplication
@EnableDiscoveryClient
public class AlibabaSentinelService8401 {
public static void main(String[] args) {
SpringApplication.run(AlibabaSentinelService8401.class,args);
}
}
效果
Sentinel流控
直接模式
/testA这个请求每秒可成功一次,超过一秒请求的阈值就会降级异常
关联模式
- 当关联的资源达到阈值时,就限流自己
- 当与A关联的资源B达到阀值后,就限流A自己
链路模式
多个请求调用同一个微服务
Sentinel流控效果
快速失败
出错或者超出阈值直接返回错误异常,恢复正常后继续使用
Warm Up
默认 coldFactor 为 3,即请求QPS从(threshold / 3) 开始,经多少预热时长才逐渐升至设定的 QPS 阈值。 案例,阀值为10+预热时长设置5秒。 系统初始化的阀值为10 / 3 约等于3,即阀值刚开始为3;然后过了5秒后阀值才慢慢升高恢复到10
应用场景:
如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值。
排队等待
匀速排队,阈值必须设置为QPS
匀速排队,让请求以均匀的速度通过
设置含义:/testA每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒。
当多次点击,虽然请求没有被立即执行,但是进入了排队等待,排队中的请求会一步步被执行(似乎是有排队数量限制设置)
Sentinel降级
RT(平均响应时间,秒级) 平均响应时间 超出阈值 且 在时间窗口内通过的请求>=5,两个条件同时满足后触发降级 窗口期过后关闭断路器 RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)
异常比列(秒级) QPS >= 5 且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
异常数(分钟级) 异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制, 让请求快速失败,避免影响到其它的资源而导致级联错误。
当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
@SentinelResource(初谈)
sentinel系统默认的提示:Blocked by Sentinel (flow limiting)
使用@SentinelResource自定义blockHandler或fallback
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "dealHandler_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
@RequestParam(value = "p2",required = false) String p2){
/*
@SentinelResource
处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;
RuntimeException
int age = 10/0,这个是java运行时报出的运行时异常RunTimeException,@SentinelResource不管
*/
// System.out.println(10/0);
return "------testHotKey";
}
public String dealHandler_testHotKey(String p1, String p2, BlockException exception)
{
return "-----dealHandler_testHotKey";
}
注意:
像RuntimeException
int age = 10/0,这个是**java运行时报出的运行时异常RunTimeException,@SentinelResource不管,**后面熔断篇再解释
Sentinel热点key限流
何为热点 热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的TopN数据,并对其访问进行限流或者其它操作
特殊数据有不同的限流规则
 @GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "dealHandler_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
@RequestParam(value = "p2",required = false) String p2){
/*
@SentinelResource
处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;
RuntimeException
int age = 10/0,这个是java运行时报出的运行时异常RunTimeException,@SentinelResource不管
*/
// System.out.println(10/0);
return "------testHotKey";
}
public String dealHandler_testHotKey(String p1, String p2, BlockException exception)
{
return "-----dealHandler_testHotKey";
}
设置当p1的值等于5时,它的阈值可以达到200
当访问:http://localhost:8401/testHotKey?p1=5时,限流阈值为200次/秒,如果p1不是5的话,那么限流任然为默认1次/秒(没设置的情况下)
Sentinel按资源名称限流及异常返回
@GetMapping("/byResource")
@SentinelResource(value = "byResource",blockHandler = "handleException")
public CommonResult byResource()
{
return new CommonResult(200,"按资源名称限流测试OK",new User(null,"zmj","123123"));
}
//降级处理方法,与主业务在同一个类中,产生耦合
public CommonResult handleException(BlockException exception)
{
return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");
}
全局Handler处理
/**
* 自定义通用的限流处理逻辑,--->全局处理
blockHandlerClass = CustomerBlockHandler.class
blockHandler = handleException2
上述配置:找CustomerBlockHandler类里的handleException2方法进行兜底处理
*/
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handleException1")
public CommonResult customerBlockHandler()
{
return new CommonResult(200,"符合客户自定义限流处理逻辑");
}
Handler
/**
* Created by KingsLanding on 2022/11/5 16:49
*/
@RestController
public class CustomerBlockHandler {
public static CommonResult handleException1(BlockException exception){
return new CommonResult(2022,"自定义的限流处理信息......CustomerBlockHandler...handleException1");
}
public static CommonResult handleException2(BlockException exception){
return new CommonResult(2022,"自定义的限流处理信息......CustomerBlockHandler...handleException2");
}
}
Sentinel按照Url地址限流及异常返回
通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息
@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")
public CommonResult byUrl()
{
return new CommonResult(200,"按url限流测试OK",new Payment(2020L,"serial002"));
}
@SentinelResource(初谈)
sentinel系统默认的提示:Blocked by Sentinel (flow limiting)
使用@SentinelResource自定义blockHandler或fallback
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "dealHandler_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
@RequestParam(value = "p2",required = false) String p2){
/*
@SentinelResource
处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;
RuntimeException
int age = 10/0,这个是java运行时报出的运行时异常RunTimeException,@SentinelResource不管
*/
// System.out.println(10/0);
return "------testHotKey";
}
public String dealHandler_testHotKey(String p1, String p2, BlockException exception)
{
return "-----dealHandler_testHotKey";
}
@SentinelResource处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;
像RuntimeException
int age = 10/0,这个是java运行时报出的运行时异常RunTimeException,@SentinelResource不管
Sentinel熔断
@SentinelResource(详解)
sentinel系统默认的提示:Blocked by Sentinel (flow limiting)
使用@SentinelResource自定义blockHandler或fallback
- blockHandler:管Sentinel配置违规
- fallback:管运行异常
- 注意:如果fallback和blockHandler都配置;被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑
/**
* Created by KingsLanding on 2022/11/5 19:02
*/
@RestController
public class ConsumerController {
public static final String SERVICE_URL = "http://nacos-order-provider90";
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
// @SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler负责在sentinel里面配置的降级限流
// @SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback负责业务异常
//fallback和blockHandler都配置;被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。
@SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler")
public CommonResult<User> fallback(@PathVariable Integer id)
{
CommonResult<User> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id, CommonResult.class,id);
if (id == 4) {
throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
}else if (result.getData() == null) {
throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
}
return result;
}
public CommonResult handlerFallback(@PathVariable Integer id,Throwable e) {
User payment = new User(id,"null",null);
return new CommonResult<>(444,"fallback,无此流水,exception "+e.getMessage(),payment);
}
public CommonResult blockHandler(@PathVariable Integer id, BlockException blockException) {
User payment = new User(id,"null",null);
return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException "+blockException.getMessage(),payment);
}
}
Sentinel结合OpenFeign
注意启动类加@EnableFeignClients
配置文件
#激活feign功能
feign:
sentinel:
enabled: true
feign接口
contextId的作用,作为一个标识,新起一个controller层的时候需要指明,否则产生冲突
/**
* Created by KingsLanding on 2022/11/6 0:32
* 记住做笔记记录contextId的作用,作为一个标识,新起一个controller层的时候需要指明,否则产生冲突
*/
@FeignClient(value = "nacos-order-provider90",fallback = FeignServiceFallback.class,contextId = "feign")
public interface FeignService {
@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<User> paymentSQL(@PathVariable("id") Integer id);
}
fallback方法
/**
* Created by KingsLanding on 2022/11/6 0:33
* fallback方法
*/
@Component
public class FeignServiceFallback implements FeignService{
@Override
public CommonResult<User> paymentSQL(Integer id) {
return new CommonResult<>(444,"服务降级返回,没有该流水信息",new User(id,"zmj", "errorSerial......"));
}
}
业务类
/**
* Created by KingsLanding on 2022/11/6 0:40
*/
@RestController
public class ConsumerFeignController {
@Resource
private FeignService feignService;
//==================OpenFeign
@GetMapping(value = "/consumer/openfeign/{id}")
public CommonResult<User> paymentSQL(@PathVariable("id") Integer id)
{
if(id == 4)
{
throw new RuntimeException("没有该id");
}
return feignService.paymentSQL(id);
}
}
Sentinel规则持久化
一旦我们重启应用,sentinel规则将消失,生产环境需要将配置规则进行持久化
将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效
配置文件
spring:
application:
name: nacos-sentinel-consumer80
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
#配置Sentinel dashboard地址
dashboard: localhost:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
#nacos规则持久化
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: nacos-sentinel-consumer80
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
添加Nacos业务规则配置
重启服务后Sentinel中自动添加规则
Seata处理分布式事务
Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
单体应用被拆分成微服务应用,原来的多个模块被拆分成多个独立的应用,分别使用独立的数据源,业务操作需要调用多个服务来完成。
此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。
简而言之:一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题
3组件概念
Transaction Coordinator (TC)
事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;
Transaction Manager (TM)
控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
Resource Manager (RM)
控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚
处理过程
- TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;
- XID 在微服务调用链路的上下文中传播;
- RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
- TM 向 TC 发起针对 XID 的全局提交或回滚决议;
- TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。
Seata-Server安装
这里的安装过程:seata-server-0.9.0.zip
修改conf目录下的file.conf配置文件
service模块
service {
vgroup_mapping.zmj_tx_group = "default" #修改自定义事务组名称
default.grouplist = "127.0.0.1:8091"
enableDegrade = false
disable = false
max.commit.retry.timeout = "-1"
max.rollback.retry.timeout = "-1"
disableGlobalTransaction = false
}
store模块
## transaction log store
store {
## store mode: file、db
mode = "db"
## file store
file {
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
max-branch-session-size = 16384
# globe session size , if exceeded throws exceptions
max-global-session-size = 512
# file buffer size , if exceeded allocate new buffer
file-write-buffer-cache-size = 16384
# when recover batch read size
session.reload.read_size = 100
# async, sync
flush-disk-mode = async
}
## database store
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
datasource = "dbcp"
## mysql/oracle/h2/oceanbase etc.
db-type = "mysql"
driver-class-name = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://127.0.0.1:3306/seata"
user = "root"
password = "zmj.666.999"
min-conn = 1
max-conn = 3
global.table = "global_table"
branch.table = "branch_table"
lock-table = "lock_table"
query-limit = 100
}
}
数据库新建库seata
新版本seata步骤已改:没有sql脚本需要自己执行了(生成数据表的方式改变)
修改conf目录下的registry.conf配置文件
目的是:指明注册中心为nacos,及修改nacos连接信息
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
serverAddr = "localhost:8848"
namespace = ""
cluster = "default"
}
注意:先启动Nacos再启动Seata
构建Seata模块
核心依赖
<!--nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<artifactId>seata-all</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
配置文件
spring:
application:
name: seata-storage-service
cloud:
alibaba:
seata:
#自定义事务组名称需要与seata-server中的对应
tx-service-group: zmj_tx_group
nacos:
discovery:
server-addr: localhost:8848
复制修改后的file.conf和register
MyBatisConfig
目的,因为mapper文件的全路径是com.springloud.mapper,而mapper.xml的路径是mapper,并不一致,所以必须设定,也可在启动类指定
/**
* Created by KingsLanding on 2022/11/6 17:10
*/
@Configuration
//目的,因为mapper文件的全路径是com.springloud.mapper,而mapper.xml的路径是mapper,并不一致,所以必须设定
//也可在启动类指定
@MapperScan({"com.springcloud.mapper"})
public class MyBatisConfig {
}
StorageDataSourceConfig
使用Seata对数据源进行代理
/**
* Created by KingsLanding on 2022/11/6 16:15
* 使用Seata对数据源进行代理
*/
@Configuration
public class StorageDataSourceConfig {
@Value("${mybatis.mapperLocations}")
private String mapperLocations;
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource(){
return new DruidDataSource();
}
@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
return sqlSessionFactoryBean.getObject();
}
}
主启动
/**
* Created by KingsLanding on 2022/11/6 17:30
*/
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源的自动创建
@EnableDiscoveryClient
@EnableFeignClients
public class AlibabaSeataStorage2002 {
public static void main(String[] args) {
SpringApplication.run(AlibabaSeataStorage2002.class,args);
}
}
服务调用方
@GlobalTransactional
实现微服务之间的事务管理
/**
* Created by KingsLanding on 2022/11/6 15:58
*/
@Service
@Slf4j
public class OrderServiceImpl implements OrderService{
//本地数据库
@Resource
private OrderMapper orderMapper;
//库存微服务数据库
@Resource
private StorageService storageService;
//账户微服务数据库
@Resource
private AccountService accountService;
/**
* 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
* 简单说:
* 下订单->减库存->减余额->改状态
* name: 一个代号罢了
*/
@Override
@GlobalTransactional(name = "zmj-create-order",rollbackFor = Exception.class)
public void create(Order order) {
log.info("------->下单开始");
//本应用创建订单
orderMapper.create(order);
//远程调用库存服务扣减库存
log.info("------->order-service中扣减库存开始");
storageService.decrease(order.getProductId(),order.getCount());
log.info("------->order-service中扣减库存结束");
//远程调用账户服务扣减余额
log.info("------->order-service中扣减余额开始");
accountService.decrease(order.getUserId(),order.getMoney());
log.info("------->order-service中扣减余额结束");
//修改订单状态为已完成
log.info("------->order-service中修改订单状态开始");
orderMapper.update(order.getUserId(),0);
log.info("------->order-service中修改订单状态结束");
log.info("------->下单结束");
}
}