Reading Reminder:
- This article is for those who have a certain springboot foundation
- The Spring Cloud Hoxton RELEASE used for this tutorial
- This article relies on the previous project, please check the previous article for seamless connection, or directly download the source: github.com/WinterChenS…
Seata, a popular distributed transaction framework, is integrated. Distributed transactions appear because micro-services cause business branches in different services, and cannot use transactions like local transactions.
Before a summary
- The first part of the SpringCloud tutorial series
- Nacos of SpringCloud series (2) | August more challenges
- SpringCloud series (3) of the Open Feign | August more challenges
- SpringCloud series (4) the SpringCloud Gateway | August more challenges
- Swagger Knife4J and Unified Login Permission Verification
- SpringCloud uses sentinel as a fuse in part 6 of the SpringCloud series
- Link tracing using SpringCloud Sleuth+Zipkin
What is Seata?
Seata is an open source distributed transaction solution dedicated to providing high performance and easy to use distributed transaction services. Seata will provide users with AT, TCC, SAGA and XA transaction modes to create a one-stop distributed solution for users.
For more information, see the official documentation: What is Seata
Download and install SEata-Server
download
Download address: Releases · Seata/Seata (github.com)
Download zip for Windows and tar.gz for Linux/MAC
Note: How to install NacOS see this: NacOS Quick Start. It is recommended to check out the previous article for slip-in play.
The installation
After unpacking, modify the registry. Conf configuration file (in this case, configure NACOS as the configuration center) :
config {
type = "nacos"Change this to nacos and change the corresponding configuration nacos {serverAddr ="127.0.0.1:8848"
namespace = "public"
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"}}Copy the code
Then continue to modify the registry (NACOS as the registry) :
registry {
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848"
group = "DEFAULT_GROUP"# heregroupNeed to work with business services in onegroupWithin the namespace ="public"
cluster = "default"
username = "nacos"
password = "nacos"}}Copy the code
Nacos is used as the registry and configuration center for this article, but you can check the official documentation if you need something else.
Upload the configuration
Download the configuration file template before uploading it
- Seata /script/config-center at develop · seata/seata (github.com) See illustration 1
- Then locate the shell script in the corresponding configuration center directory, using nacos-config.shScript /config-center/nacos at develop · seata/seata (github.com)And put it into
${SEATA_DIR}/script/config-center/nacos/nacos-config.sh
Note:${SEATA_DIR}
Is the root of seata; See illustration 2 - Run it in the unzip root of Seata
Sh -h 127.0.0.1 -p 8848 -g SEATA_GROUP -u nacos -w nacos
Note: nacos-config.sh is the script file downloaded in step 2, as shown in legend 3
Illustrations 1:
Legend 2:
Legend 3:
Legend 4:
On the NACOS console (localhost:8848/ nacOS, account secret: nacOS/nacOS) :
You can see that the configuration has been uploaded successfully
For details, see seata/config. TXT at develop · seata/seata (github.com)
Configuration parameter official comparison table: Seata parameter configuration
Caution Modify the configuration of the corresponding database. You can directly modify the configuration in NACOS.
One of the key points, and one of the things that can go wrong, is a configuration parameter,
Service. VgroupMapping.< your service name >-group=default This group needs to be consistent between seata-server and client. My_test_tx_group =default if the default configuration file is service.vgroupMapping.my_test_tx_group=default then we need to configure the group in the application.
seata:
tx-service-group: my_test_tx_group
Copy the code
Start the seata – server:
Windows: Open the console:./seata-server.bat -h 127.0.0.1
Linux/MacOS: sh [seata-server.sh](http://seata-server.sh) -h 127.0.0.1
Successful startup:
Nacos Registry:
Multiple deployment modes:
- Deploy Seata Server using Docker
- Deploy Seata Server using Kubernetes
- Seata Server is deployed using the Helm
- Seata high availability deployment
New seATA data table:
· seata/seata (github.com)
After downloading, create a new library in the database: seata, and import the library build script.
engineered
1. Copy engineering
Copy the project: Spring-Cloud-nacos-consumer and change it to: order-server
Copy the project: Spring-Cloud-nacos-provider and change it to: stock-server
Note: Part of the POM configuration needs to be modified after replication (to the corresponding name) :
<artifactId>order-server</artifactId>
<version>0.0.1 - the SNAPSHOT</version>
<name>order-server</name>
<description>Demo project for Spring Boot</description>
Copy the code
And add to the parent POM:
<modules>.<module>stock-server</module>
<module>order-server</module>
</modules>
Copy the code
Then re-import the dependency
If exceptions still exist, delete the. Imi file
2. Increase dependency
Add dependencies to the POM of two engineering modules:
<! -- seata -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
Copy the code
3. Modify the application. Yml configuration
order-server
logging:
level:
io:
seata: debug
seata:
tx-service-group: my_test_tx_group
Copy the code
stock-server
logging:
level:
io:
seata: debug
seata:
tx-service-group: my_test_tx_group
Copy the code
4. Initialize the database
Create order library, business table, undo_log table
create database seata_order;
use seata_order;
DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT 0,
`money` int(11) DEFAULT 0.PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
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;
Create stock, business table, undo_log table
create database seata_stock;
use seata_stock;
DROP TABLE IF EXISTS `stock_tbl`;
CREATE TABLE `stock_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT 0.PRIMARY KEY (`id`),
UNIQUE KEY (`commodity_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
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;
Initialize inventory simulation data
INSERT INTO seata_stock.stock_tbl (id, commodity_code, count) VALUES (1.'product-1'.9999999);
INSERT INTO seata_stock.stock_tbl (id, commodity_code, count) VALUES (2.'product-2'.0);
Copy the code
5. Introduce MyBatis Plus
Parent POM introduces dependencies:
<! -- mybatis plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.4</version>
</dependency>
<! Mysql driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
Copy the code
Two projects introduce dependencies respectively:
<! -- mybatis plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
Copy the code
6. Modify the order-server service
New Partial class
@Data
@TableName("order_tbl")
@Accessors(chain = true)
@Builder
public class Order implements Serializable {
private static final longserialVersionUID= 1L;
@JsonFormat(shape = JsonFormat.Shape.STRING)
@TableId(value="id" ,type = IdType.AUTO)
/ * * * /
@TableField("id")
private Integer id;
/ * * * /
@TableField("user_id")
private String userId;
/ * * * /
@TableField("commodity_code")
private String commodityCode;
/ * * * /
@TableField("count")
private Integer count;
/ * * * /
@TableField("money")
private BigDecimal money;
@Tolerate
public Order(a){}}Copy the code
@Mapper
public interface OrderTblMapper extends BaseMapper<Order> {}Copy the code
mapperxml:
<! DOCTYPEmapper PUBLIC "- / / mybatis.org//DTD Mapper / 3.0 / EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.winterchen.nacos.mapper.OrderTblMapper">
</mapper>
Copy the code
service:
public interface OrderTblService extends IService<Order> {
void placeOrder(String userId, String commodityCode, Integer count);
}
Copy the code
@Service
public class OrderTblServiceImpl extends ServiceImpl<OrderTblMapper.Order> implements OrderTblService {
@Autowired
private StockFeignClient stockFeignClient;
/** * place an order: create an order, reduce inventory, which involves two services **@param userId
* @param commodityCode
* @param count
*/
@GlobalTransactional
@Transactional(rollbackFor = Exception.class)
@Override
public void placeOrder(String userId, String commodityCode, Integer count) {
BigDecimal orderMoney = new BigDecimal(count).multiply(new BigDecimal(5));
Order order = newOrder().setUserId(userId).setCommodityCode(commodityCode).setCount(count).setMoney(orderMoney); baseMapper.insert(order); stockFeignClient.deduct(commodityCode, count); }}Copy the code
StockFeignClient
@FeignClient(name = "stock-server")
public interface StockFeignClient {
@PostMapping("/api/stock/deduct")
Boolean deduct(@RequestParam("commodityCode") String commodityCode, @RequestParam("count") Integer count);
}
Copy the code
@ Api (tags = "order Api")
@RestController
@RequestMapping("/api/order")
public class OrderTblController {
@Autowired
private OrderTblService orderTblService;
/** * : insert order table, subtract inventory, simulate rollback **@return* /
@PostMapping("/placeOrder/commit")
public Boolean placeOrderCommit(a) {
orderTblService.placeOrder("1"."product-1".1);
return true;
}
/** * : insert order table, subtract inventory, simulate rollback **@return* /
@PostMapping("/placeOrder/rollback")
public Boolean placeOrderRollback(a) {
// Product -2 ()
orderTblService.placeOrder("1"."product-2".1);
return true;
}
@PostMapping("/placeOrder")
public Boolean placeOrder(String userId, String commodityCode, Integer count) {
orderTblService.placeOrder(userId, commodityCode, count);
return true; }}Copy the code
7. Stock -server service modification
entity:
@Data
@TableName("stock_tbl")
@Accessors(chain = true)
@Builder
public class Stock implements Serializable {
private static final long serialVersionUID = 1L;
@JsonFormat(shape = JsonFormat.Shape.STRING)
@TableId(value="id" ,type = IdType.AUTO)
/ * * * /
@TableField("id")
private Integer id;
/ * * * /
@TableField("commodity_code")
private String commodityCode;
/ * * * /
@TableField("count")
private Integer count;
@Tolerate
public Stock(a){}}Copy the code
mapper:
@Mapper
public interface StockTblMapper extends BaseMapper<Stock> {}Copy the code
mapperxml:
<? 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="com.winterchen.nacos.mapper.StockTblMapper">
</mapper>
Copy the code
service:
public interface StockTblService extends IService<Stock> {
void deduct(String commodityCode, int count);
}
Copy the code
@Service
public class StockTblServiceImpl extends ServiceImpl<StockTblMapper.Stock> implements StockTblService {
@Transactional(rollbackFor = Exception.class)
@Override
public void deduct(String commodityCode, int count) {
if (commodityCode.equals("product-2")) {
throw new RuntimeException("Exception: Simulated business exception: Stock Branch Exception");
}
QueryWrapper<Stock> wrapper = new QueryWrapper<>();
wrapper.setEntity(newStock().setCommodityCode(commodityCode)); Stock stock = baseMapper.selectOne(wrapper); stock.setCount(stock.getCount() - count); baseMapper.updateById(stock); }}Copy the code
Controller:
@ Api (tags = "inventory Api")
@RestController
@RequestMapping("/api/stock")
public class StockTblController {
@Autowired
private StockTblService stockTblService;
/** ** reduce inventory **@paramCommodityCode *@paramCount the number of *@return* /
@PostMapping(path = "/deduct")
public Boolean deduct(String commodityCode, Integer count) {
stockTblService.deduct(commodityCode, count);
return true; }}Copy the code
8. Modify gateway:
Modify GatewayConfiguration
InitCustomizedApis Added initialization for order-server and stock-server
ApiDefinition api3 = new ApiDefinition("order")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/order/**")
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
ApiDefinition api4 = new ApiDefinition("stock")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/stock/**")
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
definitions.add(api3);
definitions.add(api4);
Copy the code
InitGatewayRules Adds a rule
rules.add(new GatewayFlowRule("order")
.setCount(10)
.setIntervalSec(1)); rules.add(new GatewayFlowRule("order")
.setCount(2)
.setIntervalSec(2)
.setBurst(2)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP)
)
);
rules.add(new GatewayFlowRule("stock")
.setCount(10)
.setIntervalSec(1)
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)
.setMaxQueueingTimeoutMs(600)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER)
.setFieldName("X-Sentinel-Flag"))); rules.add(new GatewayFlowRule("stock")
.setCount(1)
.setIntervalSec(1)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
.setFieldName("pa"))); rules.add(new GatewayFlowRule("stock")
.setCount(2)
.setIntervalSec(30)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
.setFieldName("type")
.setPattern("warn")
.setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_CONTAINS)
)
);
rules.add(new GatewayFlowRule("stock")
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME)
.setCount(5)
.setIntervalSec(1)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
.setFieldName("pn")));Copy the code
Appilcation. Yml Added route configuration for order-server and stock-server:
spring:
cloud:
nacos:
discovery:
server-addr: 127.0. 01.: 8848
gateway:
discovery:
locator:
enabled: false
lowerCaseServiceId: true
routes:
- id: provider
uri: lb://winter-nacos-provider
predicates:
- Path=/provider/**
filters:
- StripPrefix=1 # StripPrefix = 1 means capture path number is 1, such as front-end to request/test/good / 1 / view, after the success of the match, routed to the backend request path will become http://localhost:8888/good/1/view
- id: consumer
uri: lb://winter-nacos-consumer
predicates:
- Path=/consumer/**
filters:
- StripPrefix=1
- id: auth
uri: lb://auth
predicates:
- Path=/auth/**
filters:
- StripPrefix=1
- id: order-server ------- Added routing rules for order-server
uri: lb://order-server
predicates:
- Path=/order/**
filters:
- StripPrefix=1
- id: stock-server --------- The routing rule of stock-server is added
uri: lb://stock-server
predicates:
- Path=/stock/**
filters:
- StripPrefix=1
Copy the code
Testing:
Start the corresponding service first:
- spring-cloud-gateway
- spring-cloud-auth
- order-server
- stock-server
Then open up swagger to test: Consumer Services
Test submission:
Order created successfully:
Inventory deduction successful:
Test rollback:
At this point, the database is normally rolled back.
conclusion
Above is all there is in this tutorial, seata distributed transaction is a very useful framework, provides a simpler API, developers seata default is used AT mode of transaction, of course, can be combined with their own business to choose more appropriate mode of distributed transactions, specific configuration can be reference for the official documentation.
WinterChenS/ Spring-Cloud-Hoxton-Study: Spring Cloud Hoxton Release Study (github.com)
References:
What is the Seata
Seata (Fescar) Distributed transaction integration Spring Cloud