Storage
1.新建模块
2.修改pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>order-parent</artifactId>
<groupId>cn.tedu</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>cn.tedu</groupId>
<artifactId>storage</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>storage</name>
</project>
3.修改yml文件
application.yml
spring:
application:
name: storage
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost/seata_storage?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: root
server:
port: 8082
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
mybatis-plus:
type-aliases-package: cn.tedu.storage.entity
mapper-locations:
- classpath:/mapper/*Mapper.xml
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.tedu.storage.mapper: DEBUG
bootstrap.yml
spring:
cloud:
inetutils:
preferred-networks:
- 192\.168\.1\..+
4.实体类
package cn.tedu.storage.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class Storage {
private Long id;
private Long productId;//产品id
private Integer total;//总数
private Integer used;//已售出
private Integer residue;//可用库存
private Integer frozen;//冻结库存
}
5.mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="cn.tedu.storage.mapper.StorageMapper" >
<resultMap id="BaseResultMap" type="Storage" >
<id column="id" property="id" jdbcType="BIGINT" />
<result column="product_id" property="productId" jdbcType="BIGINT" />
<result column="total" property="total" jdbcType="INTEGER" />
<result column="used" property="used" jdbcType="INTEGER" />
<result column="residue" property="residue" jdbcType="INTEGER" />
</resultMap>
<update id="decrease">
UPDATE storage SET used = used + #{count},residue = residue - #{count} WHERE product_id = #{productId}
</update>
</mapper>
6.service
StorageService
package cn.tedu.storage.service;
public interface StorageService {
void decrease(Long productId,Integer count);
}
StorageServiceImpl
package cn.tedu.storage.service;
import cn.tedu.storage.mapper.StorageMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class StorageServiceImpl implements StorageService{
@Autowired
private StorageMapper storageMapper;
@Override
public void decrease(Long productId, Integer count) {
}
}
7.controller
package cn.tedu.storage.controller;
import cn.tedu.storage.service.StorageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class StorageController {
@Autowired
private StorageService storageService;
@GetMapping("/decrease")
public String decrease(Long productId,Integer count){
storageService.decrease(productId,count);
return "减少库存成功";
}
}
实现效果
Order
1. 新建模块order
2. 修改pom.xml文件
application.yml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>order-parent</artifactId>
<groupId>cn.tedu</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>cn.tedu</groupId>
<artifactId>order</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>order</name>
</project>
3. 配置yml文件
spring:
application:
name: order
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost/seata_order?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: root
server:
port: 8083
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
mybatis-plus:
type-aliases-package: cn.tedu.order.entity
mapper-locations:
- classpath:/mapper/*Mapper.xml
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.tedu.order.mapper: DEBUG
bootstrap.yml
spring:
cloud:
inetutils:
preferred-networks:
- 192\.168\.1\..+
4.实体类
package cn.tedu.order.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class Order {
private Long id;
private Long userId;
private Long productId;
private Integer count;
private BigDecimal money;
private Integer status;
}
5. mapper
OrderMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="cn.tedu.order.mapper.OrderMapper" >
<resultMap id="BaseResultMap" type="Order" >
<id column="id" property="id" jdbcType="BIGINT" />
<result column="user_id" property="userId" jdbcType="BIGINT" />
<result column="product_id" property="productId" jdbcType="BIGINT" />
<result column="count" property="count" jdbcType="INTEGER" />
<result column="money" property="money" jdbcType="DECIMAL" />
<result column="status" property="status" jdbcType="INTEGER" />
</resultMap>
<insert id="create">
INSERT INTO `order` (`id`,`user_id`,`product_id`,`count`,`money`,`status`)
VALUES(#{id}, #{userId}, #{productId}, #{count}, #{money},1);
</insert>
</mapper>
order为sql关键词 作为表名需要使用反引号包裹
7. service
package cn.tedu.order.service;
import cn.tedu.order.entity.Order;
import cn.tedu.order.mapper.OrderMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Random;
@Service
public class OrderServiceImpl implements OrderService{
@Autowired
private OrderMapper orderMapper;
@Override
public void create(Order order) {
// TODO: 2021/11/25 远程调用订单发号器 生产订单ID
//先临时随机产生一个ID 完成发号器后此行代码可删除
Long orderId=Math.abs(new Random().nextLong());
orderMapper.create(order);
// TODO: 2021/11/25 远程调用库存,减少库存
// TODO: 2021/11/25 远程调用账户,扣减金额
}
}
8. controller
package cn.tedu.order.controller;
import cn.tedu.order.entity.Order;
import cn.tedu.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/create")
public String create(Order order){
orderService.create(order);
return "订单增加成功";
}
}
实现效果
全局唯一id发号器
1.下载网址:https://github.com/lookingatstarts/easyIdGenerator
2…导入:
3.添加eureka依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
4.添加bootstrap.yml
spring:
cloud:
inetutils:
preferred-networks:
- 192\.168\.1\..+
5.配置application.yml
server:
port: 9090
easy-id-generator:
snowflake: #雪花算法
enable: false
zk:
connection-string: 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183
load-worker-id-from-file-when-zk-down: true # 当zk不可访问时,从本地文件中读取之前备份的workerId
segment: # 有数据库就可以运算
enable: true
db-list: ["db1","db2"]
fetch-segment-retry-times: 3 # 从数据库获取号段失败重试次数
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
spring:
application:
name: easy-id
6.seata_order.properties
jdbcUrl=jdbc:mysql:///seata_order?serverTimezone=GMT%2B8&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8
driverClassName=com.mysql.cj.jdbc.Driver
dataSource.user=root
dataSource.password=root
dataSource.cachePrepStmts=true
dataSource.prepStmtCacheSize=250
dataSource.prepStmtCacheSqlLimit=2048
实现效果
访问http://localhost:9090/segment/ids/next_id?businessType=order_business
订单远程调用库存、账户和发号器
1.feign依赖
2.启动类注解
@EnableFeignClients
package cn.tedu.order;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@MapperScan("cn.tedu.order.mapper")
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
3.添加远程调用接口
storageclient
package cn.tedu.order.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "storage")
public interface StorageClient {
String decrease(@RequestParam("productId") Long productId,
@RequestParam("count")Integer count);
}
accountclient
package cn.tedu.order.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.math.BigDecimal;
@FeignClient(name = "account")
public interface AccountClient {
@GetMapping("/decrease")
String decrease(@RequestParam("userId") Long userId,
@RequestParam("money")BigDecimal money);
}
easy-idclient
package cn.tedu.order.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "easy-id")
public interface EasyIdClient {
@GetMapping("/segment/ids/next_id")
String getId(@RequestParam("businessType") String businessType);
}
4.修改orderserviceimpl完成远程调用
注入依赖
@Autowired
private EasyIdClient easyIdClient;
@Autowired
private AccountClient accountClient;
@Autowired
private StorageClient storageClient;
测试:http://localhost:8083/create?userId=1&productId=1&count=10&money=100
调整
order-parent的pom.xml
<modules>
<module>account</module>
<module>storage</module>
<module>order</module>
</modules>
order的application.yml禁用重试
ribbon:
MaxAutoRetriesNextServer: 0
Seata–AT模型
概念
- Seata 的 AT 模式(Automatic Transaction)是一种无侵入的分布式事务解决方案。
- 订单系统开始执行保存订单之前,首先启动 TM(Transaction Manager,事务管理器),由 TM 向 TC 申请开启一个全局事务,这时TC会产生一个全局事务ID,称为 XID,并将 XID 传回 TM
- TC(Transaction Coordinator),事务协调器,协调各个模块执行,各个模块执行结束后需要向TC协调器上报状态,再由TC通知各个模块整体事务成功/失败,一个模块失败,TC通知执行回滚操作
- 原理+机制: https://wanght.blog.csdn.net/article/details/107583229
步骤:
1.启动TC事务协调器:Seata Server
解压缩
2.修改三个配置文件
- registry.conf
向eureka注册
- file.conf
seata-server运行过程中产生的日志数据存储到什么位置
全局事务、分支事务、全局锁
- seata.server.bat
修改虚拟机使用的内存大小
3.运行seata.server.bat
- 前提:
JAVA_HOME、PATH
JDK1.8
命令行窗口不可关闭,内容不可以选中
4.添加Seata AT事务
1.添加seata依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
<version>${spring-cloud-alibaba-seata.version}</version>
<exclusions>
<exclusion>
<artifactId>seata-all</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>${seata.version}</version>
</dependency>
2.修改三个配置文件
application.yml
配置事务组的组名
registry.conf
设置注册中心的地址
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "eureka"
nacos {
serverAddr = "localhost"
namespace = ""
cluster = "default"
}
eureka {
serviceUrl = "http://localhost:8761/eureka"
# application = "default"
# weight = "1"
}
redis {
serverAddr = "localhost:6379"
db = "0"
password = ""
cluster = "default"
timeout = "0"
}
zk {
cluster = "default"
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
username = ""
password = ""
}
consul {
cluster = "default"
serverAddr = "127.0.0.1:8500"
}
etcd3 {
cluster = "default"
serverAddr = "http://localhost:2379"
}
sofa {
serverAddr = "127.0.0.1:9603"
application = "default"
region = "DEFAULT_ZONE"
datacenter = "DefaultDataCenter"
cluster = "default"
group = "SEATA_GROUP"
addressWaitTime = "3000"
}
file {
name = "file.conf"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3、springCloudConfig
type = "file"
nacos {
serverAddr = "localhost"
namespace = ""
group = "SEATA_GROUP"
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
app.id = "seata-server"
apollo.meta = "http://192.168.1.204:8801"
namespace = "application"
}
zk {
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
username = ""
password = ""
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}
file.conf
事务组对应使用的协调器
transport {
# tcp udt unix-domain-socket
type = "TCP"
#NIO NATIVE
server = "NIO"
#enable heartbeat
heartbeat = true
# the client batch send request enable
enableClientBatchSendRequest = true
#thread factory for netty
threadFactory {
bossThreadPrefix = "NettyBoss"
workerThreadPrefix = "NettyServerNIOWorker"
serverExecutorThread-prefix = "NettyServerBizHandler"
shareBossWorker = false
clientSelectorThreadPrefix = "NettyClientSelector"
clientSelectorThreadSize = 1
clientWorkerThreadPrefix = "NettyClientWorkerThread"
# netty boss thread size,will not be used for UDT
bossThreadSize = 1
#auto default pin or 8
workerThreadSize = "default"
}
shutdown {
# when destroy server, wait seconds
wait = 3
}
serialization = "seata"
compressor = "none"
}
service {
#transaction service group mapping
# order_tx_group 与 yml 中的 “tx-service-group: order_tx_group” 配置一致
# “seata-server” 与 TC 服务器的注册名一致
# 从eureka获取seata-server的地址,再向seata-server注册自己,设置group
vgroupMapping.order_tx_group = "seata-server"
#only support when registry.type=file, please don't set multiple addresses
order_tx_group.grouplist = "127.0.0.1:8091"
#degrade, current not support
enableDegrade = false
#disable seata
disableGlobalTransaction = false
}
client {
rm {
asyncCommitBufferLimit = 10000
lock {
retryInterval = 10
retryTimes = 30
retryPolicyBranchRollbackOnConflict = true
}
reportRetryCount = 5
tableMetaCheckEnable = false
reportSuccessEnable = false
}
tm {
commitRetryCount = 5
rollbackRetryCount = 5
}
undo {
dataValidation = true
logSerialization = "jackson"
logTable = "undo_log"
}
log {
exceptionRate = 100
}
}
3.新建自动配置类,创建数据源代理
@Primary标注首选对象
package cn.tedu.order;
import com.zaxxer.hikari.HikariDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
@Configuration
public class DSAutoConf {
//创建原始数据源
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource getDataSource(){
return new HikariDataSource();
}
//创建数据源代理
@Primary //首选对象
@Bean
public DataSource getDataSourceProxy(DataSource ds){
return new DataSourceProxy(ds);
}
}
排除spring默认数据源配置
package cn.tedu.order;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@MapperScan("cn.tedu.order.mapper")
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
4.在业务方法上添加事务注解
@Transactional–控制本地事务
@GlobalTransactional–启动全局事务,只在第一个模块添加