:110 -The configuration file used is file:/root/seata-config/registry.conf
-2020-04-15 00:55:12.426 INFO [main]com.alibaba.druid.pool.DruidDataSource.init:947 -{dataSource-1} inited
-2020-04-15 00:55:13.127 INFO [main]io.seata.core.rpc.netty.RpcServerBootstrap.start:155 -Server started
-```
-
-其中`{dataSource-1} `说明使用了数据库,并正常初始化完成
-
-- 查看注册中心,此时seata-serve 这个服务会有三个实例
-
-![seata-ha-nacos-list.png](/img/blog/seata-ha-nacos-list.png)
-
-
-## 部署业务服务
-
-- 创建业务表并初始化数据
-
-具体的业务表可以参考 [cloud-seata-nacos/README.md](https://github.com/helloworlde/spring-cloud-alibaba-component/blob/master/cloud-seata-nacos/README.md)
-
-- 添加 Nacos 配置
-
-在 public 的命名空间下,分别创建 data-id 为 `order-service.properties`, `pay-service.properties`, `storage-service.properties` 的配置,内容相同,需要修改数据库的地址、用户名和密码
-
-```
-# MySQL
-spring.datasource.url=jdbc:mysql://192.168.199.2:30060/seata?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false
-spring.datasource.username=root
-spring.datasource.password=123456
-spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
-# Seata
-spring.cloud.alibaba.seata.tx-service-group=my_test_tx_group
-```
-
-- 部署服务
-
-通过 application.yaml 配置文件部署服务,需要注意的是修改 ConfigMap 的 `NACOS_ADDR`为自己的 Nacos 地址
-
-```yaml
-apiVersion: v1
-kind: Service
-metadata:
- namespace: default
- name: seata-ha-service
- labels:
- app.kubernetes.io/name: seata-ha-service
-spec:
- type: NodePort
- ports:
- - port: 8081
- nodePort: 30081
- protocol: TCP
- name: http
- selector:
- app.kubernetes.io/name: seata-ha-service
-
----
-apiVersion: v1
-kind: ConfigMap
-metadata:
- name: seata-ha-service-config
-data:
- NACOS_ADDR: 192.168.199.2:8848
-
----
-apiVersion: v1
-kind: ServiceAccount
-metadata:
- name: seata-ha-account
- namespace: default
-
----
-apiVersion: rbac.authorization.k8s.io/v1beta1
-kind: ClusterRoleBinding
-metadata:
- name: seata-ha-account
-roleRef:
- apiGroup: rbac.authorization.k8s.io
- kind: ClusterRole
- name: cluster-admin
-subjects:
- - kind: ServiceAccount
- name: seata-ha-account
- namespace: default
-
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- namespace: default
- name: seata-ha-service
- labels:
- app.kubernetes.io/name: seata-ha-service
-spec:
- replicas: 1
- selector:
- matchLabels:
- app.kubernetes.io/name: seata-ha-service
- template:
- metadata:
- labels:
- app.kubernetes.io/name: seata-ha-service
- spec:
- serviceAccountName: seata-ha-account
- containers:
- - name: seata-ha-order-service
- image: "registry.cn-qingdao.aliyuncs.com/hellowoodes/seata-ha-order-service:1.1"
- imagePullPolicy: IfNotPresent
- env:
- - name: NACOS_ADDR
- valueFrom:
- configMapKeyRef:
- key: NACOS_ADDR
- name: seata-ha-service-config
- ports:
- - name: http
- containerPort: 8081
- protocol: TCP
- - name: seata-ha-pay-service
- image: "registry.cn-qingdao.aliyuncs.com/hellowoodes/seata-ha-pay-service:1.1"
- imagePullPolicy: IfNotPresent
- env:
- - name: NACOS_ADDR
- valueFrom:
- configMapKeyRef:
- key: NACOS_ADDR
- name: seata-ha-service-config
- ports:
- - name: http
- containerPort: 8082
- protocol: TCP
- - name: seata-ha-storage-service
- image: "registry.cn-qingdao.aliyuncs.com/hellowoodes/seata-ha-storage-service:1.1"
- imagePullPolicy: IfNotPresent
- env:
- - name: NACOS_ADDR
- valueFrom:
- configMapKeyRef:
- key: NACOS_ADDR
- name: seata-ha-service-config
- ports:
- - name: http
- containerPort: 8083
- protocol: TCP
-```
-
-通过以下命令,将应用部署到集群中
-
-```bash
-kubectl apply -f application.yaml
-```
-
-然后查看创建的 pod,seata-ha-service 这个服务下有三个 pod
-
-```bash
-kubectl get pod | grep seata-ha-service
-
-seata-ha-service-7dbdc6894b-5r8q4 3/3 Running 0 12m
-```
-
-待应用启动后,在 Nacos 的服务列表中,会有相应的服务
-
-![seata-ha-service-list.png](/img/blog/seata-ha-service-list.png)
-
-此时查看服务的日志,会看到服务向每一个 TC 都注册了
-
-```bash
-kubectl logs -f seata-ha-service-7dbdc6894b-5r8q4 seata-ha-order-service
-```
-
-![seata-ha-service-register.png](/img/blog/seata-ha-service-register.png)
-
-查看任意的 TC 日志,会发现每一个服务都向 TC 注册了
-
-```bash
-kubelet logs -f seata-ha-server-645844b8b6-9qh5j
-```
-
-![seata-ha-tc-register.png](/img/blog/seata-ha-tc-register.png)
-
-
-## 测试
-
-
-### 测试成功场景
-
-调用下单接口,将 price 设置为 1,因为初始化的余额为 10,可以下单成功
-
-```bash
-curl -X POST \
- http://192.168.199.2:30081/order/placeOrder \
- -H 'Content-Type: application/json' \
- -d '{
- "userId": 1,
- "productId": 1,
- "price": 1
-}'
-```
-
-此时返回结果为:
-
-```json
-{"success":true,"message":null,"data":null}
-```
-
-查看TC 的日志,事务成功提交:
-
-![seata-ha-commit-tc-success.png](/img/blog/seata-ha-commit-tc-success.png)
-
-查看 order-service 服务日志
-![seata-ha-commit-success.png](/img/blog/seata-ha-commit-service-success.png)
-
-
-### 测试失败场景
-
-设置 price 为 100,此时余额不足,会下单失败抛出异常,事务会回滚
-
-```bash
-curl -X POST \
- http://192.168.199.2:30081/order/placeOrder \
- -H 'Content-Type: application/json' \
- -d '{
- "userId": 1,
- "productId": 1,
- "price": 100
-}'
-```
-
-查看 TC 的日志:
-![seata-ha-commit-tc-rollback.png](/img/blog/seata-ha-commit-tc-rollback.png)
-
-查看服务的日志 :
-![seata-ha-commit-service-rollback.png](/img/blog/seata-ha-commit-service-rollback.png)
-
-多次调用查看服务日志,发现会随机的向其中某台TC发起事务注册,当扩容或缩容后,有相应的 TC 参与或退出,证明高可用部署生效
-
+Placeholder. DO NOT DELETE.
\ No newline at end of file
diff --git a/blog/seata-meetup-hangzhou.md b/blog/seata-meetup-hangzhou.md
index 5671ad3e96..e872b67af1 100644
--- a/blog/seata-meetup-hangzhou.md
+++ b/blog/seata-meetup-hangzhou.md
@@ -1,50 +1 @@
----
-title: Seata Community Meetup·杭州站
-keywords: [Seata, 杭州, meetup]
-description: Seata Community Meetup·杭州站,于12月21号在杭州市梦想小镇浙江青年众创空间完美举办
----
-
-# Seata Meetup·杭州站成功举办
-
-![](https://img.alicdn.com/tfs/TB1qH2YwVP7gK0jSZFjXXc5aXXa-2002-901.jpg)
-
-### 活动介绍
-
-### 亮点解读
-
-- Seata 开源项目发起人带来《Seata 过去、现在和未来》以及 Seata 1.0 的新特性。
-- Seata 核心贡献者详解 Seata AT, TCC, Saga 模式。
-- Seata 落地互联网医疗,滴滴出行实践剖析。
-
-- [回放福利(开发者社区)](https://developer.aliyun.com/live/1760)
-- [加入 Seata 千人钉钉群](http://w2wz.com/h2nb)
-
-### 分享嘉宾
-
-- 季敏(清铭) 《Seata 的过去、现在和未来》 [slides](https://github.com/a364176773/awesome-seata/blob/master/slides/meetup/201912%40hangzhou/%E5%AD%A3%E6%95%8F%EF%BC%88%E6%B8%85%E9%93%AD%EF%BC%89%E3%80%8ASeata%20%E7%9A%84%E8%BF%87%E5%8E%BB%E3%80%81%E7%8E%B0%E5%9C%A8%E5%92%8C%E6%9C%AA%E6%9D%A5%E3%80%8B.pdf)
-
- ![](https://img.alicdn.com/tfs/TB1BALWw4z1gK0jSZSgXXavwpXa-6720-4480.jpg)
-
-- 吴江坷《我与SEATA的开源之路以及SEATA在互联网医疗系统中的应用》 [slides](https://github.com/seata/awesome-seata/blob/master/slides/meetup/201912%40hangzhou/%E5%AD%A3%E6%95%8F%EF%BC%88%E6%B8%85%E9%93%AD%EF%BC%89%E3%80%8ASeata%20%E7%9A%84%E8%BF%87%E5%8E%BB%E3%80%81%E7%8E%B0%E5%9C%A8%E5%92%8C%E6%9C%AA%E6%9D%A5%E3%80%8B.pdf)
-
- ![1577282651](https://img.alicdn.com/tfs/TB1Xzz1w4v1gK0jSZFFXXb0sXXa-6720-4480.jpg)
-
-- 申海强(煊檍)《带你读透 Seata AT 模式的精髓》 [slides](https://github.com/seata/awesome-seata/tree/master/slides/meetup/201912%40hangzhou)
-
- ![1577282652](https://img.alicdn.com/tfs/TB1UK22w7T2gK0jSZPcXXcKkpXa-6720-4480.jpg)
-
-- 张森 《分布式事务Seata之TCC模式详解》
-
- ![1577282653](https://img.alicdn.com/tfs/TB1fCPZw.T1gK0jSZFhXXaAtVXa-6720-4480.jpg)
-
-- 陈龙(屹远)《Seata 长事务解决方案 Saga 模式》
-
- ![1577282654](https://img.alicdn.com/tfs/TB1zLv3wYj1gK0jSZFuXXcrHpXa-6720-4480.jpg)
-
-- 陈鹏志《Seata%20在滴滴两轮车业务的实践》 [slides](https://github.com/seata/awesome-seata/blob/master/slides/meetup/201912%40hangzhou/%E9%99%88%E9%B9%8F%E5%BF%97%E3%80%8ASeata%20%E5%9C%A8%E6%BB%B4%E6%BB%B4%E4%B8%A4%E8%BD%AE%E8%BD%A6%E4%B8%9A%E5%8A%A1%E7%9A%84%E5%AE%9E%E8%B7%B5%E3%80%8B.pdf)
-
- ![1577282655](https://img.alicdn.com/tfs/TB1phvYw4n1gK0jSZKPXXXvUXXa-6720-4480.jpg)
-
-### 特别嘉奖
-
-![](https://img.alicdn.com/tfs/TB1khDVw.z1gK0jSZLeXXb9kVXa-6720-4480.jpg)
\ No newline at end of file
+Placeholder. DO NOT DELETE.
\ No newline at end of file
diff --git a/blog/seata-mybatisplus-analysis.md b/blog/seata-mybatisplus-analysis.md
index f85bd6fa90..e872b67af1 100644
--- a/blog/seata-mybatisplus-analysis.md
+++ b/blog/seata-mybatisplus-analysis.md
@@ -1,540 +1 @@
----
-title: 透过源码解决SeataAT模式整合Mybatis-Plus失去MP特性的问题
-keywords: [Seata,Mybatis-Plus,分布式事务]
-description: 本文讲述如何透过源码解决Seata整合Mybatis-Plus失去MP特性的问题
-author: FUNKYE
-date: 2019/11/30
-
----
-
-# 透过源码解决SeataAT模式整合Mybatis-Plus失去MP特性的问题
-
-项目地址:https://gitee.com/itCjb/springboot-dubbo-mybatisplus-seata
-
-本文作者:FUNKYE(陈健斌),杭州某互联网公司主程。
-
-# 介绍
-
-Mybatis-Plus:[MyBatis-Plus](https://github.com/baomidou/mybatis-plus)(简称 MP)是一个 [MyBatis](http://www.mybatis.org/mybatis-3/) 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
-
-MP配置:
-
-```xml
-
-
-
-```
-
-Seata:Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
-
-AT模式机制:
-
-- 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
-- 二阶段:
- - 提交异步化,非常快速地完成。
- - 回滚通过一阶段的回滚日志进行反向补偿。
-
-## 分析原因
-
- 1.首先我们通过介绍,可以看到,mp是需要注册sqlSessionFactory,注入数据源,而Seata是通过代理数据源来保证事务的正常回滚跟提交。
-
- 2.我们来看基于seata的官方demo提供的SeataAutoConfig的代码
-
-```java
-package org.test.config;
-
-import javax.sql.DataSource;
-
-import org.apache.ibatis.session.SqlSessionFactory;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Primary;
-
-import com.alibaba.druid.pool.DruidDataSource;
-import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
-
-import io.seata.rm.datasource.DataSourceProxy;
-import io.seata.spring.annotation.GlobalTransactionScanner;
-
-@Configuration
-public class SeataAutoConfig {
- @Autowired(required = true)
- private DataSourceProperties dataSourceProperties;
- private final static Logger logger = LoggerFactory.getLogger(SeataAutoConfig.class);
-
- @Bean(name = "dataSource") // 声明其为Bean实例
- @Primary // 在同样的DataSource中,首先使用被标注的DataSource
- public DataSource druidDataSource() {
- DruidDataSource druidDataSource = new DruidDataSource();
- logger.info("dataSourceProperties.getUrl():{}",dataSourceProperties.getUrl());
- druidDataSource.setUrl(dataSourceProperties.getUrl());
- druidDataSource.setUsername(dataSourceProperties.getUsername());
- druidDataSource.setPassword(dataSourceProperties.getPassword());
- druidDataSource.setDriverClassName(dataSourceProperties.getDriverClassName());
- druidDataSource.setInitialSize(0);
- druidDataSource.setMaxActive(180);
- druidDataSource.setMaxWait(60000);
- druidDataSource.setMinIdle(0);
- druidDataSource.setValidationQuery("Select 1 from DUAL");
- druidDataSource.setTestOnBorrow(false);
- druidDataSource.setTestOnReturn(false);
- druidDataSource.setTestWhileIdle(true);
- druidDataSource.setTimeBetweenEvictionRunsMillis(60000);
- druidDataSource.setMinEvictableIdleTimeMillis(25200000);
- druidDataSource.setRemoveAbandoned(true);
- druidDataSource.setRemoveAbandonedTimeout(1800);
- druidDataSource.setLogAbandoned(true);
- logger.info("装载dataSource........");
- return druidDataSource;
- }
-
- /**
- * init datasource proxy
- *
- * @Param: druidDataSource datasource bean instance
- * @Return: DataSourceProxy datasource proxy
- */
- @Bean
- public DataSourceProxy dataSourceProxy(DataSource dataSource) {
- logger.info("代理dataSource........");
- return new DataSourceProxy(dataSource);
- }
-
- @Bean
- public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy) throws Exception {
- MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
- factory.setDataSource(dataSourceProxy);
- factory.setMapperLocations(new PathMatchingResourcePatternResolver()
- .getResources("classpath*:/mapper/*.xml"));
- return factory.getObject();
- }
-
- /**
- * init global transaction scanner
- *
- * @Return: GlobalTransactionScanner
- */
- @Bean
- public GlobalTransactionScanner globalTransactionScanner() {
- logger.info("配置seata........");
- return new GlobalTransactionScanner("test-service", "test-group");
- }
-}
-
-```
-
-首先看到我们的seata配置数据源的类里,我们配置了一个数据源,然后又配置了一个seata代理datasource的bean,这时候.
-
-然后我们如果直接启动mp整合seata的项目会发现,分页之类的插件会直接失效,连扫描mapper都得从代码上写,这是为什么呢?
-
-通过阅读以上代码,是因为我们另外的配置了一个sqlSessionFactory,导致mp的sqlSessionFactory失效了,这时候我们发现了问题的所在了,即使我们不配置sqlSessionFactoryl,也会因为mp所使用的数据源不是被seata代理过后的数据源,导致分布式事务失效.但是如何解决这个问题呢?
-
-这时候我们需要去阅读mp的源码,找到他的启动类,一看便知
-
-```java
-/*
- * Copyright (c) 2011-2020, baomidou (jobob@qq.com).
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.baomidou.mybatisplus.autoconfigure;
-
-
-import com.baomidou.mybatisplus.core.MybatisConfiguration;
-import com.baomidou.mybatisplus.core.config.GlobalConfig;
-import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
-import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
-import com.baomidou.mybatisplus.core.injector.ISqlInjector;
-import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
-import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.mapping.DatabaseIdProvider;
-import org.apache.ibatis.plugin.Interceptor;
-import org.apache.ibatis.scripting.LanguageDriver;
-import org.apache.ibatis.session.ExecutorType;
-import org.apache.ibatis.session.SqlSessionFactory;
-import org.apache.ibatis.type.TypeHandler;
-import org.mybatis.spring.SqlSessionFactoryBean;
-import org.mybatis.spring.SqlSessionTemplate;
-import org.mybatis.spring.mapper.MapperFactoryBean;
-import org.mybatis.spring.mapper.MapperScannerConfigurer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.BeanWrapper;
-import org.springframework.beans.BeanWrapperImpl;
-import org.springframework.beans.factory.BeanFactory;
-import org.springframework.beans.factory.BeanFactoryAware;
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.beans.factory.ObjectProvider;
-import org.springframework.beans.factory.support.BeanDefinitionBuilder;
-import org.springframework.beans.factory.support.BeanDefinitionRegistry;
-import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
-import org.springframework.boot.autoconfigure.AutoConfigureAfter;
-import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
-import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Import;
-import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
-import org.springframework.core.io.Resource;
-import org.springframework.core.io.ResourceLoader;
-import org.springframework.core.type.AnnotationMetadata;
-import org.springframework.util.Assert;
-import org.springframework.util.CollectionUtils;
-import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
-
-import javax.sql.DataSource;
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Stream;
-
-/**
- * {@link EnableAutoConfiguration Auto-Configuration} for Mybatis. Contributes a
- * {@link SqlSessionFactory} and a {@link SqlSessionTemplate}.
- *
- * If {@link org.mybatis.spring.annotation.MapperScan} is used, or a
- * configuration file is specified as a property, those will be considered,
- * otherwise this auto-configuration will attempt to register mappers based on
- * the interface definitions in or under the root auto-configuration package.
- *
- * copy from {@link org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration}
- *
- * @author Eddú Meléndez
- * @author Josh Long
- * @author Kazuki Shimizu
- * @author Eduardo Macarrón
- */
-@Configuration
-@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
-@ConditionalOnSingleCandidate(DataSource.class)
-@EnableConfigurationProperties(MybatisPlusProperties.class)
-@AutoConfigureAfter(DataSourceAutoConfiguration.class)
-public class MybatisPlusAutoConfiguration implements InitializingBean {
-
- private static final Logger logger = LoggerFactory.getLogger(MybatisPlusAutoConfiguration.class);
-
- private final MybatisPlusProperties properties;
-
- private final Interceptor[] interceptors;
-
- private final TypeHandler[] typeHandlers;
-
- private final LanguageDriver[] languageDrivers;
-
- private final ResourceLoader resourceLoader;
-
- private final DatabaseIdProvider databaseIdProvider;
-
- private final List configurationCustomizers;
-
- private final List mybatisPlusPropertiesCustomizers;
-
- private final ApplicationContext applicationContext;
-
-
- public MybatisPlusAutoConfiguration(MybatisPlusProperties properties,
- ObjectProvider interceptorsProvider,
- ObjectProvider typeHandlersProvider,
- ObjectProvider languageDriversProvider,
- ResourceLoader resourceLoader,
- ObjectProvider databaseIdProvider,
- ObjectProvider> configurationCustomizersProvider,
- ObjectProvider> mybatisPlusPropertiesCustomizerProvider,
- ApplicationContext applicationContext) {
- this.properties = properties;
- this.interceptors = interceptorsProvider.getIfAvailable();
- this.typeHandlers = typeHandlersProvider.getIfAvailable();
- this.languageDrivers = languageDriversProvider.getIfAvailable();
- this.resourceLoader = resourceLoader;
- this.databaseIdProvider = databaseIdProvider.getIfAvailable();
- this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
- this.mybatisPlusPropertiesCustomizers = mybatisPlusPropertiesCustomizerProvider.getIfAvailable();
- this.applicationContext = applicationContext;
- }
-
- @Override
- public void afterPropertiesSet() {
- if (!CollectionUtils.isEmpty(mybatisPlusPropertiesCustomizers)) {
- mybatisPlusPropertiesCustomizers.forEach(i -> i.customize(properties));
- }
- checkConfigFileExists();
- }
-
- private void checkConfigFileExists() {
- if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
- Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
- Assert.state(resource.exists(),
- "Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)");
- }
- }
-
- @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
- @Bean
- @ConditionalOnMissingBean
- public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
- // TODO 使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBean
- MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
- factory.setDataSource(dataSource);
- factory.setVfs(SpringBootVFS.class);
- if (StringUtils.hasText(this.properties.getConfigLocation())) {
- factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
- }
- applyConfiguration(factory);
- if (this.properties.getConfigurationProperties() != null) {
- factory.setConfigurationProperties(this.properties.getConfigurationProperties());
- }
- if (!ObjectUtils.isEmpty(this.interceptors)) {
- factory.setPlugins(this.interceptors);
- }
- if (this.databaseIdProvider != null) {
- factory.setDatabaseIdProvider(this.databaseIdProvider);
- }
- if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
- factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
- }
- if (this.properties.getTypeAliasesSuperType() != null) {
- factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
- }
- if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
- factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
- }
- if (!ObjectUtils.isEmpty(this.typeHandlers)) {
- factory.setTypeHandlers(this.typeHandlers);
- }
- if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
- factory.setMapperLocations(this.properties.resolveMapperLocations());
- }
-
- // TODO 对源码做了一定的修改(因为源码适配了老旧的mybatis版本,但我们不需要适配)
- Class extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
- if (!ObjectUtils.isEmpty(this.languageDrivers)) {
- factory.setScriptingLanguageDrivers(this.languageDrivers);
- }
- Optional.ofNullable(defaultLanguageDriver).ifPresent(factory::setDefaultScriptingLanguageDriver);
-
- // TODO 自定义枚举包
- if (StringUtils.hasLength(this.properties.getTypeEnumsPackage())) {
- factory.setTypeEnumsPackage(this.properties.getTypeEnumsPackage());
- }
- // TODO 此处必为非 NULL
- GlobalConfig globalConfig = this.properties.getGlobalConfig();
- // TODO 注入填充器
- if (this.applicationContext.getBeanNamesForType(MetaObjectHandler.class,
- false, false).length > 0) {
- MetaObjectHandler metaObjectHandler = this.applicationContext.getBean(MetaObjectHandler.class);
- globalConfig.setMetaObjectHandler(metaObjectHandler);
- }
- // TODO 注入主键生成器
- if (this.applicationContext.getBeanNamesForType(IKeyGenerator.class, false,
- false).length > 0) {
- IKeyGenerator keyGenerator = this.applicationContext.getBean(IKeyGenerator.class);
- globalConfig.getDbConfig().setKeyGenerator(keyGenerator);
- }
- // TODO 注入sql注入器
- if (this.applicationContext.getBeanNamesForType(ISqlInjector.class, false,
- false).length > 0) {
- ISqlInjector iSqlInjector = this.applicationContext.getBean(ISqlInjector.class);
- globalConfig.setSqlInjector(iSqlInjector);
- }
- // TODO 设置 GlobalConfig 到 MybatisSqlSessionFactoryBean
- factory.setGlobalConfig(globalConfig);
- return factory.getObject();
- }
-
- // TODO 入参使用 MybatisSqlSessionFactoryBean
- private void applyConfiguration(MybatisSqlSessionFactoryBean factory) {
- // TODO 使用 MybatisConfiguration
- MybatisConfiguration configuration = this.properties.getConfiguration();
- if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
- configuration = new MybatisConfiguration();
- }
- if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
- for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
- customizer.customize(configuration);
- }
- }
- factory.setConfiguration(configuration);
- }
-
- @Bean
- @ConditionalOnMissingBean
- public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
- ExecutorType executorType = this.properties.getExecutorType();
- if (executorType != null) {
- return new SqlSessionTemplate(sqlSessionFactory, executorType);
- } else {
- return new SqlSessionTemplate(sqlSessionFactory);
- }
- }
-
- /**
- * This will just scan the same base package as Spring Boot does. If you want more power, you can explicitly use
- * {@link org.mybatis.spring.annotation.MapperScan} but this will get typed mappers working correctly, out-of-the-box,
- * similar to using Spring Data JPA repositories.
- */
- public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
-
- private BeanFactory beanFactory;
-
- @Override
- public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
-
- if (!AutoConfigurationPackages.has(this.beanFactory)) {
- logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
- return;
- }
-
- logger.debug("Searching for mappers annotated with @Mapper");
-
- List packages = AutoConfigurationPackages.get(this.beanFactory);
- if (logger.isDebugEnabled()) {
- packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
- }
-
- BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
- builder.addPropertyValue("processPropertyPlaceHolders", true);
- builder.addPropertyValue("annotationClass", Mapper.class);
- builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
- BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
- Stream.of(beanWrapper.getPropertyDescriptors())
- // Need to mybatis-spring 2.0.2+
- .filter(x -> x.getName().equals("lazyInitialization")).findAny()
- .ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}"));
- registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
- }
-
- @Override
- public void setBeanFactory(BeanFactory beanFactory) {
- this.beanFactory = beanFactory;
- }
- }
-
- /**
- * If mapper registering configuration or mapper scanning configuration not present, this configuration allow to scan
- * mappers based on the same component-scanning path as Spring Boot itself.
- */
- @Configuration
- @Import(AutoConfiguredMapperScannerRegistrar.class)
- @ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
- public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
-
- @Override
- public void afterPropertiesSet() {
- logger.debug(
- "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
- }
- }
-}
-
-```
-
-看到mp启动类里的sqlSessionFactory方法了吗,他也是一样的注入一个数据源,这时候大家应该都知道解决方法了吧?
-
-没错,就是把被代理过的数据源给放到mp的sqlSessionFactory中.
-
-很简单,我们需要稍微改动一下我们的seata配置类就行了
-
-```java
-package org.test.config;
-
-import javax.sql.DataSource;
-
-import org.mybatis.spring.annotation.MapperScan;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Primary;
-
-import com.alibaba.druid.pool.DruidDataSource;
-
-import io.seata.rm.datasource.DataSourceProxy;
-import io.seata.spring.annotation.GlobalTransactionScanner;
-
-@Configuration
-@MapperScan("com.baomidou.springboot.mapper*")
-public class SeataAutoConfig {
- @Autowired(required = true)
- private DataSourceProperties dataSourceProperties;
- private final static Logger logger = LoggerFactory.getLogger(SeataAutoConfig.class);
- private DataSourceProxy dataSourceProxy;
-
- @Bean(name = "dataSource") // 声明其为Bean实例
- @Primary // 在同样的DataSource中,首先使用被标注的DataSource
- public DataSource druidDataSource() {
- DruidDataSource druidDataSource = new DruidDataSource();
- logger.info("dataSourceProperties.getUrl():{}", dataSourceProperties.getUrl());
- druidDataSource.setUrl(dataSourceProperties.getUrl());
- druidDataSource.setUsername(dataSourceProperties.getUsername());
- druidDataSource.setPassword(dataSourceProperties.getPassword());
- druidDataSource.setDriverClassName(dataSourceProperties.getDriverClassName());
- druidDataSource.setInitialSize(0);
- druidDataSource.setMaxActive(180);
- druidDataSource.setMaxWait(60000);
- druidDataSource.setMinIdle(0);
- druidDataSource.setValidationQuery("Select 1 from DUAL");
- druidDataSource.setTestOnBorrow(false);
- druidDataSource.setTestOnReturn(false);
- druidDataSource.setTestWhileIdle(true);
- druidDataSource.setTimeBetweenEvictionRunsMillis(60000);
- druidDataSource.setMinEvictableIdleTimeMillis(25200000);
- druidDataSource.setRemoveAbandoned(true);
- druidDataSource.setRemoveAbandonedTimeout(1800);
- druidDataSource.setLogAbandoned(true);
- logger.info("装载dataSource........");
- dataSourceProxy = new DataSourceProxy(druidDataSource);
- return dataSourceProxy;
- }
-
- /**
- * init datasource proxy
- *
- * @Param: druidDataSource datasource bean instance
- * @Return: DataSourceProxy datasource proxy
- */
- @Bean
- public DataSourceProxy dataSourceProxy() {
- logger.info("代理dataSource........");
- return dataSourceProxy;
- }
-
- /**
- * init global transaction scanner
- *
- * @Return: GlobalTransactionScanner
- */
- @Bean
- public GlobalTransactionScanner globalTransactionScanner() {
- logger.info("配置seata........");
- return new GlobalTransactionScanner("test-service", "test-group");
- }
-}
-
-```
-
-看代码,我们去掉了自己配置的sqlSessionFactory,直接让DataSource bean返回的是一个被代理过的bean,并且我们加入了@Primary,导致mp优先使用我们配置的数据源,这样就解决了mp因为seata代理了数据源跟创建了新的sqlSessionFactory,导致mp的插件,组件失效的bug了!
-
-# 总结
-
-踩到坑不可怕,主要又耐心的顺着每个组件实现的原理,再去思考,查找对应冲突的代码块,你一定能找到个兼容二者的方法。
+Placeholder. DO NOT DELETE.
\ No newline at end of file
diff --git a/blog/seata-nacos-analysis.md b/blog/seata-nacos-analysis.md
index 4654b50ba0..e872b67af1 100644
--- a/blog/seata-nacos-analysis.md
+++ b/blog/seata-nacos-analysis.md
@@ -1,435 +1 @@
----
-title: Seata分布式事务启用Nacos做配置中心
-keywords: [Seata,Nacos,分布式事务]
-description: 本文讲述如何使用Seata整合Nacos配置
-author: FUNKYE
-date: 2019/12/02
----
-
-# Seata分布式事务启用Nacos做配置中心
-
-[项目地址](https://gitee.com/itCjb/springboot-dubbo-mybatisplus-seata )
-
-本文作者:FUNKYE(陈健斌),杭州某互联网公司主程。
-
-# 前言
-
-上次发布了直连方式的seata配置,详细可以看这篇[博客](http://seata.io/zh-cn/blog/springboot-dubbo-mybatisplus-seata.html)
-
-我们接着上一篇的基础上去配置nacos做配置中心跟dubbo注册中心.
-
-## 准备工作
-
- 1.首先去nacos的github上下载[最新版本](https://github.com/alibaba/nacos/releases/tag/1.1.4)
-
- ![](/img/blog/20191202203649.png)
-
- 2.下载好了后,很简单,解压后到bin目录下去启动就好了,看到如图所示就成了:
-
-![](/img/blog/20191202203943.png)
-
- 3.启动完毕后访问:http://127.0.0.1:8848/nacos/#/login
-
-![](/img/blog/20191202204101.png)
-
-是不是看到这样的界面了?输入nacos(账号密码相同),先进去看看吧.
-
-这时候可以发现没有任何服务注册
-
-![20191202204147](/img/blog/20191202204147.png)
-
-别急我们马上让seata服务连接进来.
-
-## Seata配置
-
- 1.进入seata的conf文件夹看到这个木有?
-
-![](/img/blog/20191202204259.png)
-
-就是它,编辑它:
-
-![20191202204353](/img/blog/20191202204353.png)
-
-![20191202204437](/img/blog/20191202204437.png)
-
- 2.然后记得保存哦!接着我们把registry.conf文件打开编辑:
-
-```
-registry {
- # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
- type = "nacos"
-
- nacos {
- serverAddr = "localhost"
- namespace = ""
- cluster = "default"
- }
- eureka {
- serviceUrl = "http://localhost:8761/eureka"
- application = "default"
- weight = "1"
- }
- redis {
- serverAddr = "localhost:6379"
- db = "0"
- }
- zk {
- cluster = "default"
- serverAddr = "127.0.0.1:2181"
- session.timeout = 6000
- connect.timeout = 2000
- }
- 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
- type = "nacos"
-
- nacos {
- serverAddr = "localhost"
- namespace = ""
- }
- consul {
- serverAddr = "127.0.0.1:8500"
- }
- apollo {
- app.id = "seata-server"
- apollo.meta = "http://192.168.1.204:8801"
- }
- zk {
- serverAddr = "127.0.0.1:2181"
- session.timeout = 6000
- connect.timeout = 2000
- }
- etcd3 {
- serverAddr = "http://localhost:2379"
- }
- file {
- name = "file.conf"
- }
-}
-
-```
-
-都编辑好了后,我们运行nacos-config.sh,这时候我们配置的nacos-config.txt的内容已经被发送到nacos中了详细如图:
-
-![20191202205743](/img/blog/20191202205743.png)
-
-出现以上类似的代码就是说明成功了,接着我们登录nacos配置中心,查看配置列表,出现如图列表说明配置成功了:
-
-![20191202205912](/img/blog/20191202205912.png)
-
-看到了吧,你的配置已经全部都提交上去了,如果再git工具内运行sh不行的话,试着把编辑sh文件,试试改成如下操作
-
-```shell
-for line in $(cat nacos-config.txt)
-
-do
-
-key=${line%%=*}
-value=${line#*=}
-echo "\r\n set "${key}" = "${value}
-
-result=`curl -X POST "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=$key&group=SEATA_GROUP&content=$value"`
-
-if [ "$result"x == "true"x ]; then
-
- echo "\033[42;37m $result \033[0m"
-
-else
-
- echo "\033[41;37 $result \033[0m"
- let error++
-
-fi
-
-done
-
-
-if [ $error -eq 0 ]; then
-
-echo "\r\n\033[42;37m init nacos config finished, please start seata-server. \033[0m"
-
-else
-
-echo "\r\n\033[41;33m init nacos config fail. \033[0m"
-
-fi
-```
-
- 3.目前我们的准备工作全部完成,我们去seata-service/bin去运行seata服务吧,如图所示就成功啦!
-
-![20191202210112](/img/blog/20191202210112.png)
-
-# 进行调试
-
- 1.首先把springboot-dubbo-mybatsiplus-seata项目的pom的依赖更改,去除掉zk这些配置,因为我们使用nacos做注册中心了.
-
-```java
-
- 3.1
- UTF-8
- UTF-8
- 1.8
- 1.8
- 3.2.0
- 3.2.0
-
-
- org.springframework.boot
- spring-boot-starter-parent
- 2.1.8.RELEASE
-
-
-
- com.alibaba.nacos
- nacos-client
- 1.1.4
-
-
- org.apache.dubbo
- dubbo-registry-nacos
- 2.7.4.1
-
-
- org.apache.dubbo
- dubbo-spring-boot-starter
- 2.7.4.1
-
-
- org.apache.commons
- commons-lang3
-
-
- com.alibaba
- fastjson
- 1.2.60
-
-
-
- io.springfox
- springfox-swagger2
- 2.9.2
-
-
- io.springfox
- springfox-swagger-ui
- 2.9.2
-
-
-
-
- com.baomidou
- mybatis-plus-boot-starter
- ${mybatis-plus-boot-starter.version}
-
-
-
-
- org.projectlombok
- lombok
- provided
-
-
- io.seata
- seata-all
- 0.9.0.1
-
-
-
-
-
-
- org.freemarker
- freemarker
-
-
-
- com.alibaba
- druid-spring-boot-starter
- 1.1.20
-
-
-
- com.fasterxml.jackson.dataformat
- jackson-dataformat-yaml
-
-
- org.springframework.boot
- spring-boot-starter-log4j2
-
-
-
- mysql
- mysql-connector-java
-
-
- org.springframework.boot
- spring-boot-starter-web
-
-
- org.springframework.boot
- spring-boot-starter-logging
-
-
- org.slf4j
- slf4j-log4j12
-
-
-
-
- org.springframework.boot
- spring-boot-starter-aop
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
-
- org.springframework.boot
- spring-boot-configuration-processor
- true
-
-
-
-```
-
- 2.然后更改test-service的目录结构,删除zk的配置并更改application.yml文件,目录结构与代码:
-
-```yaml
-server:
- port: 38888
-spring:
- application:
- name: test-service
- datasource:
- type: com.alibaba.druid.pool.DruidDataSource
- url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
- driver-class-name: com.mysql.cj.jdbc.Driver
- username: root
- password: 123456
-dubbo:
- protocol:
- loadbalance: leastactive
- threadpool: cached
- scan:
- base-packages: org。test.service
- application:
- qos-enable: false
- name: testserver
- registry:
- id: my-registry
- address: nacos://127.0.0.1:8848
-mybatis-plus:
- mapper-locations: classpath:/mapper/*Mapper.xml
- typeAliasesPackage: org.test.entity
- global-config:
- db-config:
- field-strategy: not-empty
- id-type: auto
- db-type: mysql
- configuration:
- map-underscore-to-camel-case: true
- cache-enabled: true
- auto-mapping-unknown-column-behavior: none
-```
-
-
-
- 3.再更改registry.conf文件,如果你的nacos是其它服务器,请改成对应都ip跟端口
-
-```java
-registry {
- type = "nacos"
- file {
- name = "file.conf"
- }
- zk {
- cluster = "default"
- serverAddr = "127.0.0.1:2181"
- session.timeout = 6000
- connect.timeout = 2000
- }
- nacos {
- serverAddr = "localhost"
- namespace = ""
- cluster = "default"
- }
-}
-config {
- type = "nacos"
- file {
- name = "file.conf"
- }
- zk {
- serverAddr = "127.0.0.1:2181"
- session.timeout = 6000
- connect.timeout = 2000
- }
- nacos {
- serverAddr = "localhost"
- namespace = ""
- cluster = "default"
- }
-}
-```
-
- 4.接着我们运行provideApplication
-
-![20191202212000](/img/blog/20191202212000.png)
-
-启动成功啦,我们再去看seata的日志:
-
-![20191202212028](/img/blog/20191202212028.png)
-
-成功了,这下我们一样,去修改test-client的内容,首先一样application.yml,把zk换成nacos,这里就不详细描述了,把test-service内的registry.conf,复制到client项目的resources中覆盖原来的registry.conf.
-
-然后我们可以运行clientApplication:
-
-![20191202212114](/img/blog/20191202212114.png)
-
- 5.确认服务已经被发布并测试事务运行是否正常
-
-![20191202212203](/img/blog/20191202212203.png)
-
-服务成功发布出来,也被成功消费了.这下我们再去swagger中去测试回滚是否一切正常,访问http://127.0.0.1:28888/swagger-ui.html
-
-![20191202212240](/img/blog/20191202212240.png)
-
-恭喜你,看到这一定跟我一样成功了!
-
-# 总结
-
-关于nacos的使用跟seata的简单搭建已经完成了,更详细的内容希望希望大家访问以下地址阅读详细文档
-
-[nacos官网](https://nacos.io/zh-cn/index.html)
-
-[dubbo官网](http://dubbo.apache.org/en-us/)
-
-[seata官网](http://seata.io/zh-cn/)
+Placeholder. DO NOT DELETE.
\ No newline at end of file
diff --git a/blog/seata-nacos-docker.md b/blog/seata-nacos-docker.md
index 984953f429..e872b67af1 100644
--- a/blog/seata-nacos-docker.md
+++ b/blog/seata-nacos-docker.md
@@ -1,576 +1 @@
----
-title: Docker部署Seata与Nacos整合
-keywords: [Seata,Nacos,分布式事务]
-description: 本文讲述如何使用Seata整合Nacos配置的Docker部署
-author: FUNKYE
-date: 2019/12/03
----
-
-# Docker部署Seata与Nacos整合
-
-运行所使用的demo[项目地址](https://gitee.com/itCjb/springboot-dubbo-mybatisplus-seata )
-
-本文作者:FUNKYE(陈健斌),杭州某互联网公司主程。
-
-# 前言
-
-直连方式的Seata配置[博客](http://seata.io/zh-cn/blog/springboot-dubbo-mybatisplus-seata.html)
-
-Seata整合Nacos配置[博客](http://seata.io/zh-cn/blog/seata-nacos-analysis.html)
-
-我们接着前几篇篇的基础上去配置nacos做配置中心跟dubbo注册中心.
-
-## 准备工作
-
- 1.安装docker
-
-```shell
-yum -y install docker
-```
-
- 2.创建nacos与seata的数据库
-
-```mysql
-/******************************************/
-/* 数据库全名 = nacos */
-/* 表名称 = config_info */
-/******************************************/
-CREATE TABLE `config_info` (
- `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
- `data_id` varchar(255) NOT NULL COMMENT 'data_id',
- `group_id` varchar(255) DEFAULT NULL,
- `content` longtext NOT NULL COMMENT 'content',
- `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
- `gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
- `gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
- `src_user` text COMMENT 'source user',
- `src_ip` varchar(20) DEFAULT NULL COMMENT 'source ip',
- `app_name` varchar(128) DEFAULT NULL,
- `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
- `c_desc` varchar(256) DEFAULT NULL,
- `c_use` varchar(64) DEFAULT NULL,
- `effect` varchar(64) DEFAULT NULL,
- `type` varchar(64) DEFAULT NULL,
- `c_schema` text,
- PRIMARY KEY (`id`),
- UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';
-
-/******************************************/
-/* 数据库全名 = nacos_config */
-/* 表名称 = config_info_aggr */
-/******************************************/
-CREATE TABLE `config_info_aggr` (
- `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
- `data_id` varchar(255) NOT NULL COMMENT 'data_id',
- `group_id` varchar(255) NOT NULL COMMENT 'group_id',
- `datum_id` varchar(255) NOT NULL COMMENT 'datum_id',
- `content` longtext NOT NULL COMMENT '内容',
- `gmt_modified` datetime NOT NULL COMMENT '修改时间',
- `app_name` varchar(128) DEFAULT NULL,
- `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
- PRIMARY KEY (`id`),
- UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段';
-
-
-/******************************************/
-/* 数据库全名 = nacos_config */
-/* 表名称 = config_info_beta */
-/******************************************/
-CREATE TABLE `config_info_beta` (
- `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
- `data_id` varchar(255) NOT NULL COMMENT 'data_id',
- `group_id` varchar(128) NOT NULL COMMENT 'group_id',
- `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
- `content` longtext NOT NULL COMMENT 'content',
- `beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps',
- `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
- `gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
- `gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
- `src_user` text COMMENT 'source user',
- `src_ip` varchar(20) DEFAULT NULL COMMENT 'source ip',
- `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
- PRIMARY KEY (`id`),
- UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';
-
-/******************************************/
-/* 数据库全名 = nacos_config */
-/* 表名称 = config_info_tag */
-/******************************************/
-CREATE TABLE `config_info_tag` (
- `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
- `data_id` varchar(255) NOT NULL COMMENT 'data_id',
- `group_id` varchar(128) NOT NULL COMMENT 'group_id',
- `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
- `tag_id` varchar(128) NOT NULL COMMENT 'tag_id',
- `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
- `content` longtext NOT NULL COMMENT 'content',
- `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
- `gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
- `gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
- `src_user` text COMMENT 'source user',
- `src_ip` varchar(20) DEFAULT NULL COMMENT 'source ip',
- PRIMARY KEY (`id`),
- UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';
-
-/******************************************/
-/* 数据库全名 = nacos_config */
-/* 表名称 = config_tags_relation */
-/******************************************/
-CREATE TABLE `config_tags_relation` (
- `id` bigint(20) NOT NULL COMMENT 'id',
- `tag_name` varchar(128) NOT NULL COMMENT 'tag_name',
- `tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type',
- `data_id` varchar(255) NOT NULL COMMENT 'data_id',
- `group_id` varchar(128) NOT NULL COMMENT 'group_id',
- `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
- `nid` bigint(20) NOT NULL AUTO_INCREMENT,
- PRIMARY KEY (`nid`),
- UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),
- KEY `idx_tenant_id` (`tenant_id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';
-
-/******************************************/
-/* 数据库全名 = nacos_config */
-/* 表名称 = group_capacity */
-/******************************************/
-CREATE TABLE `group_capacity` (
- `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
- `group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
- `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
- `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
- `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
- `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值',
- `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
- `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
- `gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
- `gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
- PRIMARY KEY (`id`),
- UNIQUE KEY `uk_group_id` (`group_id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';
-
-/******************************************/
-/* 数据库全名 = nacos_config */
-/* 表名称 = his_config_info */
-/******************************************/
-CREATE TABLE `his_config_info` (
- `id` bigint(64) unsigned NOT NULL,
- `nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
- `data_id` varchar(255) NOT NULL,
- `group_id` varchar(128) NOT NULL,
- `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
- `content` longtext NOT NULL,
- `md5` varchar(32) DEFAULT NULL,
- `gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00',
- `gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00',
- `src_user` text,
- `src_ip` varchar(20) DEFAULT NULL,
- `op_type` char(10) DEFAULT NULL,
- `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
- PRIMARY KEY (`nid`),
- KEY `idx_gmt_create` (`gmt_create`),
- KEY `idx_gmt_modified` (`gmt_modified`),
- KEY `idx_did` (`data_id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';
-
-
-/******************************************/
-/* 数据库全名 = nacos_config */
-/* 表名称 = tenant_capacity */
-/******************************************/
-CREATE TABLE `tenant_capacity` (
- `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
- `tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',
- `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
- `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
- `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
- `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',
- `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
- `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
- `gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
- `gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
- PRIMARY KEY (`id`),
- UNIQUE KEY `uk_tenant_id` (`tenant_id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';
-
-
-CREATE TABLE `tenant_info` (
- `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
- `kp` varchar(128) NOT NULL COMMENT 'kp',
- `tenant_id` varchar(128) default '' COMMENT 'tenant_id',
- `tenant_name` varchar(128) default '' COMMENT 'tenant_name',
- `tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc',
- `create_source` varchar(32) DEFAULT NULL COMMENT 'create_source',
- `gmt_create` bigint(20) NOT NULL COMMENT '创建时间',
- `gmt_modified` bigint(20) NOT NULL COMMENT '修改时间',
- PRIMARY KEY (`id`),
- UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),
- KEY `idx_tenant_id` (`tenant_id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';
-
-CREATE TABLE users (
- username varchar(50) NOT NULL PRIMARY KEY,
- password varchar(500) NOT NULL,
- enabled boolean NOT NULL
-);
-
-CREATE TABLE roles (
- username varchar(50) NOT NULL,
- role varchar(50) NOT NULL
-);
-
-INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);
-
-INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
-
-```
-
-```mysql
--- the table to store GlobalSession data
-CREATE TABLE IF NOT EXISTS `global_table`
-(
- `xid` VARCHAR(128) NOT NULL,
- `transaction_id` BIGINT,
- `status` TINYINT NOT NULL,
- `application_id` VARCHAR(32),
- `transaction_service_group` VARCHAR(32),
- `transaction_name` VARCHAR(128),
- `timeout` INT,
- `begin_time` BIGINT,
- `application_data` VARCHAR(2000),
- `gmt_create` DATETIME,
- `gmt_modified` DATETIME,
- PRIMARY KEY (`xid`),
- KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
- KEY `idx_transaction_id` (`transaction_id`)
-) ENGINE = InnoDB
- DEFAULT CHARSET = utf8;
-
--- the table to store BranchSession data
-CREATE TABLE IF NOT EXISTS `branch_table`
-(
- `branch_id` BIGINT NOT NULL,
- `xid` VARCHAR(128) NOT NULL,
- `transaction_id` BIGINT,
- `resource_group_id` VARCHAR(32),
- `resource_id` VARCHAR(256),
- `branch_type` VARCHAR(8),
- `status` TINYINT,
- `client_id` VARCHAR(64),
- `application_data` VARCHAR(2000),
- `gmt_create` DATETIME(6),
- `gmt_modified` DATETIME(6),
- PRIMARY KEY (`branch_id`),
- KEY `idx_xid` (`xid`)
-) ENGINE = InnoDB
- DEFAULT CHARSET = utf8;
-
--- the table to store lock data
-CREATE TABLE IF NOT EXISTS `lock_table`
-(
- `row_key` VARCHAR(128) NOT NULL,
- `xid` VARCHAR(128),
- `transaction_id` BIGINT,
- `branch_id` BIGINT NOT NULL,
- `resource_id` VARCHAR(256),
- `table_name` VARCHAR(32),
- `pk` VARCHAR(36),
- `gmt_create` DATETIME,
- `gmt_modified` DATETIME,
- PRIMARY KEY (`row_key`),
- KEY `idx_branch_id` (`branch_id`)
-) ENGINE = InnoDB
- DEFAULT CHARSET = utf8;
-
-```
-
- 3.拉取nacos以及seata镜像并运行
-
-```shell
-docker run -d --name nacos -p 8848:8848 -e MODE=standalone -e MYSQL_MASTER_SERVICE_HOST=你的mysql所在ip -e MYSQL_MASTER_SERVICE_DB_NAME=nacos -e MYSQL_MASTER_SERVICE_USER=root -e MYSQL_MASTER_SERVICE_PASSWORD=mysql密码 -e MYSQL_SLAVE_SERVICE_HOST=你的mysql所在ip -e SPRING_DATASOURCE_PLATFORM=mysql -e MYSQL_DATABASE_NUM=1 nacos/nacos-server:latest
-```
-
-```shell
-docker run -d --name seata -p 8091:8091 -e SEATA_IP=你想指定的ip -e SEATA_PORT=8091 seataio/seata-server:1.4.2
-```
-
-## Seata配置
-
- 1.由于seata容器内没有内置vim,我们可以直接将要文件夹cp到宿主机外来编辑好了,再cp回去
-
-```
-docker cp 容器id:seata-server/resources 你想放置的目录
-```
-
- 2.使用如下代码获取两个容器的ip地址
-
-```
-docker inspect --format='{{.NetworkSettings.IPAddress}}' ID/NAMES
-```
-
- 3.nacos-config.txt编辑为如下内容
-
-```
-transport.type=TCP
-transport.server=NIO
-transport.heartbeat=true
-transport.enableClientBatchSendRequest=false
-transport.threadFactory.bossThreadPrefix=NettyBoss
-transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
-transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
-transport.threadFactory.shareBossWorker=false
-transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
-transport.threadFactory.clientSelectorThreadSize=1
-transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
-transport.threadFactory.bossThreadSize=1
-transport.threadFactory.workerThreadSize=default
-transport.shutdown.wait=3
-service.vgroupMapping.你的事务组名=default
-service.default.grouplist=127.0.0.1:8091
-service.enableDegrade=false
-service.disableGlobalTransaction=false
-client.rm.asyncCommitBufferLimit=10000
-client.rm.lock.retryInterval=10
-client.rm.lock.retryTimes=30
-client.rm.lock.retryPolicyBranchRollbackOnConflict=true
-client.rm.reportRetryCount=5
-client.rm.tableMetaCheckEnable=false
-client.rm.tableMetaCheckerInterval=60000
-client.rm.sqlParserType=druid
-client.rm.reportSuccessEnable=false
-client.rm.sagaBranchRegisterEnable=false
-client.tm.commitRetryCount=5
-client.tm.rollbackRetryCount=5
-client.tm.defaultGlobalTransactionTimeout=60000
-client.tm.degradeCheck=false
-client.tm.degradeCheckAllowTimes=10
-client.tm.degradeCheckPeriod=2000
-store.mode=file
-store.publicKey=
-store.file.dir=file_store/data
-store.file.maxBranchSessionSize=16384
-store.file.maxGlobalSessionSize=512
-store.file.fileWriteBufferCacheSize=16384
-store.file.flushDiskMode=async
-store.file.sessionReloadReadSize=100
-store.db.datasource=druid
-store.db.dbType=mysql
-store.db.driverClassName=com.mysql.jdbc.Driver
-store.db.url=jdbc:mysql://你的mysql所在ip:3306/seata?useUnicode=true&rewriteBatchedStatements=true
-store.db.user=mysql账号
-store.db.password=mysql密码
-store.db.minConn=5
-store.db.maxConn=30
-store.db.globalTable=global_table
-store.db.branchTable=branch_table
-store.db.queryLimit=100
-store.db.lockTable=lock_table
-store.db.maxWait=5000
-server.recovery.committingRetryPeriod=1000
-server.recovery.asynCommittingRetryPeriod=1000
-server.recovery.rollbackingRetryPeriod=1000
-server.recovery.timeoutRetryPeriod=1000
-server.maxCommitRetryTimeout=-1
-server.maxRollbackRetryTimeout=-1
-server.rollbackRetryTimeoutUnlockEnable=false
-client.undo.dataValidation=true
-client.undo.logSerialization=jackson
-client.undo.onlyCareUpdateColumns=true
-server.undo.logSaveDays=7
-server.undo.logDeletePeriod=86400000
-client.undo.logTable=undo_log
-client.undo.compress.enable=true
-client.undo.compress.type=zip
-client.undo.compress.threshold=64k
-log.exceptionRate=100
-transport.serialization=seata
-transport.compressor=none
-metrics.enabled=false
-metrics.registryType=compact
-metrics.exporterList=prometheus
-metrics.exporterPrometheusPort=9898
-```
-
-详细参数配置请点[此处](http://seata.io/zh-cn/docs/user/configurations.html)
-
- 4.registry.conf编辑为如下内容
-
-```
-registry {
- # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
- type = "nacos"
-
- nacos {
- serverAddr = "nacos容器ip:8848"
- namespace = ""
- cluster = "default"
- }
-}
-
-config {
- # file、nacos 、apollo、zk、consul、etcd3
- type = "nacos"
-
- nacos {
- serverAddr = "nacos容器ip:8848"
- namespace = ""
- }
-}
-```
-
- 5.配置完成后使用如下命令,把修改完成的registry.conf复制到容器中,并重启查看日志运行
-
-```shell
-docker cp /home/seata/resources/registry.conf seata:seata-server/resources/
-docker restart seata
-docker logs -f seata
-```
-
- 6.运行nacos-config.sh导入Nacos配置
-
-eg: sh ${SEATAPATH}/script/config-center/nacos/nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t 5a3c7d6c-f497-4d68-a71a-2e5e3340b3ca -u username -w password
-
-具体参数释义参考:[配置导入说明](https://github.com/seata/seata/blob/1.4.2/script/config-center/README.md)
-
-
- 7.登录nacos控制中心查看
-
-![20191202205912](/img/blog/20191202205912.png)
-
- 如图所示便是成功了.
-
-# 进行调试
-
- 1.拉取博文中所示的项目,修改test-service的application.yml与registry.conf
-
-```
-registry {
- type = "nacos"
- nacos {
- serverAddr = "宿主机ip:8848"
- namespace = ""
- cluster = "default"
- }
-}
-config {
- type = "nacos"
- nacos {
- serverAddr = "宿主机ip:8848"
- namespace = ""
- cluster = "default"
- }
-}
-
-```
-
-```
-server:
- port: 38888
-spring:
- application:
- name: test-service
- datasource:
- type: com.alibaba.druid.pool.DruidDataSource
- url: jdbc:mysql://mysqlip:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
- driver-class-name: com.mysql.cj.jdbc.Driver
- username: root
- password: 123456
-dubbo:
- protocol:
- threadpool: cached
- scan:
- base-packages: com.example
- application:
- qos-enable: false
- name: testserver
- registry:
- id: my-registry
- address: nacos://宿主机ip:8848
-mybatis-plus:
- mapper-locations: classpath:/mapper/*Mapper.xml
- typeAliasesPackage: org.test.entity
- global-config:
- db-config:
- field-strategy: not-empty
- id-type: auto
- db-type: mysql
- configuration:
- map-underscore-to-camel-case: true
- cache-enabled: true
- log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
- auto-mapping-unknown-column-behavior: none
-```
-
- 2.把修改完成的registry.conf复制到test-client-resources中,并修改application
-
-```
-spring:
- application:
- name: test
- datasource:
- driver-class-name: com.mysql.cj.jdbc.Driver
- url: jdbc:mysql://mysqlIp:3306/test?userSSL=true&useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai
- username: root
- password: 123456
- mvc:
- servlet:
- load-on-startup: 1
- http:
- encoding:
- force: true
- charset: utf-8
- enabled: true
- multipart:
- max-file-size: 10MB
- max-request-size: 10MB
-dubbo:
- registry:
- id: my-registry
- address: nacos://宿主机ip:8848
- application:
- name: dubbo-demo-client
- qos-enable: false
-server:
- port: 28888
- max-http-header-size: 8192
- address: 0.0.0.0
- tomcat:
- max-http-post-size: 104857600
-```
- 4. 在每个所涉及的 db 执行 undo_log 脚本.
-```sql
-CREATE TABLE IF NOT EXISTS `undo_log`
-(
- `branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
- `xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
- `context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
- `rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
- `log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
- `log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
- `log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
- UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
-) ENGINE = InnoDB
- AUTO_INCREMENT = 1
- DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';
-```
-
- 5.依次运行test-service,test-client.
-
- 6.查看nacos中服务列表是否如下图所示
-
-![20191203132351](/img/blog/20191203132351.png)
-
-# 总结
-
-关于nacos与seata的docker部署已经完成了,更详细的内容希望希望大家访问以下地址阅读详细文档
-
-[nacos官网](https://nacos.io/zh-cn/index.html)
-
-[dubbo官网](http://dubbo.apache.org/en-us/)
-
-[seata官网](http://seata.io/zh-cn/)
-
-[docker官网](https://www.docker.com/)
\ No newline at end of file
+Placeholder. DO NOT DELETE.
\ No newline at end of file
diff --git a/blog/seata-observable-practice.md b/blog/seata-observable-practice.md
index 31397eccb9..e872b67af1 100644
--- a/blog/seata-observable-practice.md
+++ b/blog/seata-observable-practice.md
@@ -1,141 +1 @@
----
-title: Seata的可观测实践
-keywords: [Seata、分布式事务、数据一致性、微服务、可观测]
-description: 本文介绍Seata在可观测领域的探索和实践
-author: 刘戎-Seata
-date: 2023/06/25
----
-
-## Seata简介
-Seata的前身是阿里巴巴集团内大规模使用保证分布式事务一致性的中间件,Seata是其开源产品,由社区维护。在介绍Seata前,先与大家讨论下我们业务发展过程中经常遇到的一些问题场景。
-### 业务场景
-我们业务在发展的过程中,基本上都是从一个简单的应用,逐渐过渡到规模庞大、业务复杂的应用。这些复杂的场景难免遇到分布式事务管理问题,Seata的出现正是解决这些分布式场景下的事务管理问题。介绍下其中几个经典的场景:
-#### 场景一:分库分表场景下的分布式事务
-![image.png](/img/blog/metrics-分库分表场景下的分布式事务.png)
-起初我们的业务规模小、轻量化,单一数据库就能保障我们的数据链路。但随着业务规模不断扩大、业务不断复杂化,通常单一数据库在容量、性能上会遭遇瓶颈。通常的解决方案是向分库、分表的架构演进。此时,即引入了**分库分表场景下**的分布式事务场景。
-#### 场景二:跨服务场景下的分布式事务
-![image.png](/img/blog/metrics-跨服务场景下的分布式事务.png)
-降低单体应用复杂度的方案:应用微服务化拆分。拆分后,我们的产品由多个功能各异的微服务组件构成,每个微服务都使用独立的数据库资源。在涉及到跨服务调用的数据一致性场景时,就引入了**跨服务场景下**的分布式事务。
-### Seata架构
-![image.png](/img/blog/metrics-Seata架构.png)
-其核心组件主要如下:
-
-- **Transaction Coordinator(TC)**
-
-事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。
-
-- **Transaction Manager(TM)**
-
-控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议,TM定义全局事务的边界。
-
-- **Resource Manager(RM)**
-
-控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。RM负责定义分支事务的边界和行为。
-## Seata的可观测实践
-### 为什么需要可观测?
-
-- **分布式事务消息链路较复杂**
-
-Seata在解决了用户易用性和分布式事务一致性这些问题的同时,需要多次TC与TM、RM之间的交互,尤其当微服务的链路变复杂时,Seata的交互链路也会呈正相关性增加。这种情况下,其实我们就需要引入可观测的能力来观察、分析事物链路。
-
-- **异常链路、故障排查难定位,性能优化无从下手**
-
-在排查Seata的异常事务链路时,传统的方法需要看日志,这样检索起来比较麻烦。在引入可观测能力后,帮助我们直观的分析链路,快速定位问题;为优化耗时的事务链路提供依据。
-
-- **可视化、数据可量化**
-
-可视化能力可让用户对事务执行情况有直观的感受;借助可量化的数据,可帮助用户评估资源消耗、规划预算。
-### 可观测能力概览
-| **可观测维度** | **seata期望的能力** | **技术选型参考** |
-| --- | --- | --- |
-| Metrics | 功能层面:可按业务分组隔离,采集事务总量、耗时等重要指标
-性能层面:高度量性能,插件按需加载
-架构层面:减少第三方依赖,服务端、客户端能够采用统一的架构,减少技术复杂度
-兼容性层面:至少兼容Prometheus生态 | Prometheus:指标存储和查询等领域有着业界领先的地位
-OpenTelemetry:可观测数据采集和规范的事实标准。但自身并不负责数据的存储,展示和分析 |
-| Tracing | 功能层面:全链路追踪分布式事务生命周期,反应分布式事务执行性能消耗
-易用性方面:对使用seata的用户而言简单易接入 | SkyWalking:利用Java的Agent探针技术,效率高,简单易用。 |
-| Logging | 功能层面:记录服务端、客户端全部生命周期信息
-易用性层面:能根据XID快速匹配全局事务对应链路日志 | Alibaba Cloud Service
-ELK |
-
-### Metrics维度
-#### 设计思路
-
-1. Seata作为一个被集成的数据一致性框架,Metrics模块将尽可能少的使用第三方依赖以降低发生冲突的风险
-2. Metrics模块将竭力争取更高的度量性能和更低的资源开销,尽可能降低开启后带来的副作用
-3. 配置时,Metrics是否激活、数据如何发布,取决于对应的配置;开启配置则自动启用,并默认将度量数据通过prometheusexporter的形式发布
-4. 不使用Spring,使用SPI(Service Provider Interface)加载扩展
-#### 模块设计
-![图片 1.png](/img/blog/metrics-模块设计.png)
-
-- seata-metrics-core:Metrics核心模块,根据配置组织(加载)1个Registry和N个Exporter;
-- seata-metrics-api:定义了Meter指标接口,Registry指标注册中心接口;
-- seata-metrics-exporter-prometheus:内置的prometheus-exporter实现;
-- seata-metrics-registry-compact:内置的Registry实现,并轻量级实现了Gauge、Counter、Summay、Timer指标;
-#### metrics模块工作流
-![图片 1.png](/img/blog/metrics-模块工作流.png)
-上图是metrics模块的工作流,其工作流程如下:
-
-1. 利用SPI机制,根据配置加载Exporter和Registry的实现类;
-2. 基于消息订阅与通知机制,监听所有全局事务的状态变更事件,并publish到EventBus;
-3. 事件订阅者消费事件,并将生成的metrics写入Registry;
-4. 监控系统(如prometheus)从Exporter中拉取数据。
-#### TC核心指标
-![image.png](/img/blog/metrics-TC核心指标.png)
-#### TM核心指标
-![image.png](/img/blog/metrics-TM核心指标.png)
-#### RM核心指标
-![image.png](/img/blog/metrics-RM核心指标.png)
-#### 大盘展示
-![lQLPJxZhZlqESU3NBpjNBp6w8zYK6VbMgzYCoKVrWEDWAA_1694_1688.png](/img/blog/metrics-大盘展示.png)
-### Tracing维度
-#### Seata为什么需要tracing?
-
-1. 对业务侧而言,引入Seata后,对业务性能会带来多大损耗?主要时间消耗在什么地方?如何针对性的优化业务逻辑?这些都是未知的。
-2. Seata的所有消息记录都通过日志持久化落盘,但对不了解Seata的用户而言,日志非常不友好。能否通过接入Tracing,提升事务链路排查效率?
-3. 对于新手用户,可通过Tracing记录,快速了解seata的工作原理,降低seata使用门槛。
-#### Seata的tracing解决方案
-
-- Seata在自定义的RPC消息协议中定义了Header信息;
-- SkyWalking拦截指定的RPC消息,并注入tracing相关的span信息;
-- 以RPC消息的发出&接收为临界点,定义了span的生命周期范围。
-
-基于上述的方式,Seata实现了事务全链路的tracing,具体接入可参考[为[Seata应用 | Seata-server]接入Skywalking](https://seata.io/zh-cn/docs/user/apm/skywalking.html)。
-#### tracing效果
-
-- 基于的demo场景:
-1. 用户请求交易服务
-2. 交易服务锁定库存
-3. 交易服务创建账单
-4. 账单服务进行扣款
-
-![image.png](/img/blog/metrics-tracing效果-业务逻辑图.png)
-
-- GlobalCommit成功的事务链路(事例)
-
-![image.png](/img/blog/metrics-tracing效果-tracing链1.png)
-![image.png](/img/blog/metrics-tracing效果-tracing链2.png)
-![image.png](/img/blog/metrics-tracing效果-tracing链3.png)
-### Logging维度
-#### 设计思路
-![image.png](/img/blog/metrics-logging设计思路.png)
-Logging这一块其实承担的是可观测这几个维度当中的兜底角色。放在最底层的,其实就是我们日志格式的设计,只有好日志格式,我们才能对它进行更好的采集、模块化的存储和展示。在其之上,是日志的采集、存储、监控、告警、数据可视化,这些模块更多的是有现成的工具,比如阿里的SLS日志服务、还有ELK的一套技术栈,我们更多是将开销成本、接入复杂度、生态繁荣度等作为考量。
-#### 日志格式设计
-这里拿Seata-Server的一个日志格式作为案例:
-![image.png](/img/blog/metrics-logging日志效果.png)
-
-- 线程池规范命名:当线程池、线程比较多时,规范的线程命名能将无序执行的线程执行次序清晰展示。
-- 方法全类名可追溯:快速定位到具体的代码块。
-- 重点运行时信息透出:重点突出关键日志,不关键的日志不打印,减少日志冗余。
-- 消息格式可扩展:通过扩展消息类的输出格式,减少日志的代码修改量。
-## 总结&展望
-#### Metrics
-总结:基本实现分布式事务的可量化、可观测。
-展望:更细粒度的指标、更广阔的生态兼容。
-#### Tracing
-总结:分布式事务全链路的可追溯。
-展望:根据xid追溯事务链路,异常链路根因快速定位。
-#### Logging
-总结:结构化的日志格式。
-展望:日志可观测体系演进。
+Placeholder. DO NOT DELETE.
\ No newline at end of file
diff --git a/blog/seata-quick-start.md b/blog/seata-quick-start.md
index 1a06b25649..e872b67af1 100644
--- a/blog/seata-quick-start.md
+++ b/blog/seata-quick-start.md
@@ -1,340 +1 @@
----
-title: Seata 极简入门
-description: 从 0 开始入门 Seata,搭建 Seata 服务,并接入 Java 项目中实现分布式事务
-keywords: [fescar、seata、分布式事务]
-author: 芋道源码
-date: 2020/04/19
----
-
-- [1. 概述](#)
-- [2. 部署单机 TC Server](#)
-- [3. 部署集群 TC Server](#)
-- [4. 接入 Java 应用](#)
-
-# 1. 概述
-
-[Seata](https://github.com/seata/seata) 是**阿里**开源的一款开源的**分布式事务**解决方案,致力于提供高性能和简单易用的分布式事务服务。
-
-## 1.1 四种事务模式
-
-Seata 目标打造**一站式**的分布事务的解决方案,最终会提供四种事务模式:
-* AT 模式:参见[《Seata AT 模式》](https://seata.io/zh-cn/docs/dev/mode/at-mode.html)文档
-* TCC 模式:参见[《Seata TCC 模式》](https://seata.io/zh-cn/docs/dev/mode/tcc-mode.html)文档
-* Saga 模式:参见[《SEATA Saga 模式》](https://seata.io/zh-cn/docs/dev/mode/saga-mode.html)文档
-* XA 模式:正在开发中...
-
-目前使用的**流行度**情况是:AT > TCC > Saga。因此,我们在学习 Seata 的时候,可以花更多精力在 **AT 模式**上,最好搞懂背后的实现原理,毕竟分布式事务涉及到数据的正确性,出问题需要快速排查定位并解决。
-
-> 友情提示:具体的流行度,胖友可以选择看看 [Wanted: who's using Seata](https://github.com/seata/seata/issues/1246) 每个公司登记的使用方式。
-
-## 1.2 三种角色
-
-在 Seata 的架构中,一共有三个角色:
-
-![三个角色](http://www.iocoder.cn/images/Seata/2017-01-01/02.png)
-
-* **TC** (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,驱动**全局事务**提交或回滚。
-* **TM** (Transaction Manager) - 事务管理器:定义**全局事务**的范围,开始全局事务、提交或回滚全局事务。
-* **RM** ( Resource Manager ) - 资源管理器:管理**分支事务**处理的资源( Resource ),与 TC 交谈以注册分支事务和报告分支事务的状态,并驱动**分支事务**提交或回滚。
-
-其中,TC 为单独部署的 **Server** 服务端,TM 和 RM 为嵌入到应用中的 **Client** 客户端。
-
-在 Seata 中,一个分布式事务的**生命周期**如下:
-
-![架构图](http://www.iocoder.cn/images/Seata/2017-01-01/01.png)
-
-> 友情提示:看下艿艿添加的红色小勾。
-
-* TM 请求 TC 开启一个全局事务。TC 会生成一个 **XID** 作为该全局事务的编号。
- > **XID**,会在微服务的调用链路中传播,保证将多个微服务的子事务关联在一起。
-
-* RM 请求 TC 将本地事务注册为全局事务的分支事务,通过全局事务的 **XID** 进行关联。
-* TM 请求 TC 告诉 **XID** 对应的全局事务是进行提交还是回滚。
-* TC 驱动 RM 们将 **XID** 对应的自己的本地事务进行提交还是回滚。
-
-## 1.3 框架支持情况
-
-Seata 目前提供了对主流的**微服务框架**的支持:
-* Dubbo
- > 通过 [`seata-dubbo`](https://github.com/seata/seata/blob/develop/integration/dubbo/) 集成
-
-* SOFA-RPC
- > 通过 [`seata-sofa-rpc`](https://github.com/seata/seata/blob/develop/integration/sofa-rpc/) 集成
-
-* Motan
- > 通过 [`seata-motan`](https://github.com/seata/seata/blob/develop/integration/motan/) 集成
-
-* gRPC
- > 通过 [`seata-grpc`](https://github.com/seata/seata/blob/develop/integration/gprc/) 集成
-
-* Apache HttpClient
- > 通过 [`seata-http`](https://github.com/seata/seata/blob/develop/integration/http/) 集成
-
-* Spring Cloud OpenFeign
- > 通过 [`spring-cloud-starter-alibaba-seata`](https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/java/com/alibaba/cloud/seata/) 的 [`feign`](https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/java/com/alibaba/cloud/seata/feign/) 模块
-
-* Spring RestTemplate
- > 通过 [`spring-cloud-starter-alibaba-seata`](https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/java/com/alibaba/cloud/seata/feign/SeataBeanPostProcessor.java) 的 [`rest`](https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/java/com/alibaba/cloud/seata/rest/) 模块
-
-同时方便我们集成到 Java 项目当中,Seata 也提供了相应的 Starter 库:
-* [`seata-spring-boot-starter`](https://mvnrepository.com/artifact/io.seata/seata-spring-boot-starter)
-* [`spring-cloud-starter-alibaba-seata`](https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-seata)
-
-因为 Seata 是基于 [DataSource](https://docs.oracle.com/javase/7/docs/api/javax/sql/DataSource.html) 数据源进行**代理**来拓展,所以天然对主流的 ORM 框架提供了非常好的支持:
-* MyBatis、MyBatis-Plus
-* JPA、Hibernate
-
-## 1.4 案例情况
-
-从 [Wanted: who's using Seata](https://github.com/seata/seata/issues/1246) 的登记情况,Seata 已经在国内很多团队开始落地,其中不乏有滴滴、韵达等大型公司。可汇总如下图:
-
-![汇总图](http://www.iocoder.cn/images/Seata/2017-01-01/03.png)
-
-另外,在 [awesome-seata](https://github.com/seata/awesome-seata) 仓库中,艿艿看到了滴滴等等公司的落地时的技术分享,还是非常真实可靠的。如下图所示:![awesome-seata 滴滴](http://www.iocoder.cn/images/Seata/2017-01-01/04.png)
-
-从案例的情况来说,Seata 可能给是目前已知最可靠的分布式事务解决方案,至少对它进行技术投入是非常不错的选择。
-
-# 2. 部署单机 TC Server
-
-本小节,我们来学习部署**单机** Seata **TC** Server,常用于学习或测试使用,不建议在生产环境中部署单机。
-
-因为 TC 需要进行全局事务和分支事务的记录,所以需要对应的**存储**。目前,TC 有两种存储模式( `store.mode` ):
-
-* file 模式:适合**单机**模式,全局事务会话信息在**内存**中读写,并持久化本地文件 `root.data`,性能较高。
-* db 模式:适合**集群**模式,全局事务会话信息通过 **db** 共享,相对性能差点。
-
-显然,我们将采用 file 模式,最终我们部署单机 TC Server 如下图所示:![单机 TC Server](http://www.iocoder.cn/images/Seata/2017-01-01/11.png)
-
-哔哔完这么多,我们开始正式部署单机 TC Server,这里艿艿使用 macOS 系统,和 Linux、Windows 是差不多的,胖友脑补翻译。
-
-## 2.1 下载 Seata 软件包
-
-打开 [Seata 下载页面](https://github.com/seata/seata/releases),选择想要的 Seata 版本。这里,我们选择 [v1.1.0](https://github.com/seata/seata/releases/tag/v1.1.0) 最新版本。
-
-```Bash
-# 创建目录
-$ mkdir -p /Users/yunai/Seata
-$ cd /Users/yunai/Seata
-
-# 下载
-$ wget https://github.com/seata/seata/releases/download/v1.1.0/seata-server-1.1.0.tar.gz
-
-# 解压
-$ tar -zxvf seata-server-1.1.0.tar.gz
-
-# 查看目录
-$ cd seata
-$ ls -ls
-24 -rw-r--r-- 1 yunai staff 11365 May 13 2019 LICENSE
- 0 drwxr-xr-x 4 yunai staff 128 Apr 2 07:46 bin # 执行脚本
- 0 drwxr-xr-x 9 yunai staff 288 Feb 19 23:49 conf # 配置文件
- 0 drwxr-xr-x 138 yunai staff 4416 Apr 2 07:46 lib # seata-*.jar + 依赖库
-```
-
-## 2.2 启动 TC Server
-
-执行 `nohup sh bin/seata-server.sh &` 命令,启动 TC Server 在后台。在 `nohup.out` 文件中,我们看到如下日志,说明启动成功:
-
-```Java
-# 使用 File 存储器
-2020-04-02 08:36:01.302 INFO [main]io.seata.common.loader.EnhancedServiceLoader.loadFile:247 -load TransactionStoreManager[FILE] extension by class[io.seata.server.store.file.FileTransactionStoreManager]
-2020-04-02 08:36:01.302 INFO [main]io.seata.common.loader.EnhancedServiceLoader.loadFile:247 -load SessionManager[FILE] extension by class[io.seata.server.session.file.FileBasedSessionManager]
-# 启动成功
-2020-04-02 08:36:01.597 INFO [main]io.seata.core.rpc.netty.RpcServerBootstrap.start:155 -Server started ...
-```
-* 默认配置下,Seata TC Server 启动在 **8091** 端点。
-
-因为我们使用 file 模式,所以可以看到用于持久化的本地文件 `root.data`。操作命令如下:
-
-```Bash
-$ ls -ls sessionStore/
-total 0
-0 -rw-r--r-- 1 yunai staff 0 Apr 2 08:36 root.data
-```
-
-后续,胖友可以阅读[「4. 接入 Java 应用」](#)小节,开始使用 Seata 实现分布式事务。
-
-# 3. 部署集群 TC Server
-
-本小节,我们来学习部署**集群** Seata **TC** Server,实现高可用,生产环境下必备。在集群时,多个 Seata TC Server 通过 **db** 数据库,实现全局事务会话信息的共享。
-
-同时,每个 Seata TC Server 可以注册自己到注册中心上,方便应用从注册中心获得到他们。最终我们部署 集群 TC Server 如下图所示:![集群 TC Server](http://www.iocoder.cn/images/Seata/2017-01-01/21.png)
-
-Seata TC Server 对主流的注册中心都提供了集成,具体可见 [discovery](https://github.com/seata/seata/tree/develop/discovery) 目录。考虑到国内使用 Nacos 作为注册中心越来越流行,这里我们就采用它。
-
-> 友情提示:如果对 Nacos 不了解的胖友,可以参考[《Nacos 安装部署》](http://www.iocoder.cn/Nacos/install/?self)文章。
-
-哔哔完这么多,我们开始正式部署单机 TC Server,这里艿艿使用 macOS 系统,和 Linux、Windows 是差不多的,胖友脑补翻译。
-
-## 3.1 下载 Seata 软件包
-
-打开 [Seata 下载页面](https://github.com/seata/seata/releases),选择想要的 Seata 版本。这里,我们选择 [v1.1.0](https://github.com/seata/seata/releases/tag/v1.1.0) 最新版本。
-
-```Bash
-# 创建目录
-$ mkdir -p /Users/yunai/Seata
-$ cd /Users/yunai/Seata
-
-# 下载
-$ wget https://github.com/seata/seata/releases/download/v1.1.0/seata-server-1.1.0.tar.gz
-
-# 解压
-$ tar -zxvf seata-server-1.1.0.tar.gz
-
-# 查看目录
-$ cd seata
-$ ls -ls
-24 -rw-r--r-- 1 yunai staff 11365 May 13 2019 LICENSE
- 0 drwxr-xr-x 4 yunai staff 128 Apr 2 07:46 bin # 执行脚本
- 0 drwxr-xr-x 9 yunai staff 288 Feb 19 23:49 conf # 配置文件
- 0 drwxr-xr-x 138 yunai staff 4416 Apr 2 07:46 lib # seata-*.jar + 依赖库
-```
-
-## 3.2 初始化数据库
-
-① 使用 [`mysql.sql`](https://github.com/seata/seata/blob/develop/script/server/db/mysql.sql) 脚本,初始化 Seata TC Server 的 db 数据库。脚本内容如下:
-
-```SQL
--- -------------------------------- The script used when storeMode is 'db' --------------------------------
--- the table to store GlobalSession data
-CREATE TABLE IF NOT EXISTS `global_table`
-(
- `xid` VARCHAR(128) NOT NULL,
- `transaction_id` BIGINT,
- `status` TINYINT NOT NULL,
- `application_id` VARCHAR(32),
- `transaction_service_group` VARCHAR(32),
- `transaction_name` VARCHAR(128),
- `timeout` INT,
- `begin_time` BIGINT,
- `application_data` VARCHAR(2000),
- `gmt_create` DATETIME,
- `gmt_modified` DATETIME,
- PRIMARY KEY (`xid`),
- KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
- KEY `idx_transaction_id` (`transaction_id`)
-) ENGINE = InnoDB
- DEFAULT CHARSET = utf8;
-
--- the table to store BranchSession data
-CREATE TABLE IF NOT EXISTS `branch_table`
-(
- `branch_id` BIGINT NOT NULL,
- `xid` VARCHAR(128) NOT NULL,
- `transaction_id` BIGINT,
- `resource_group_id` VARCHAR(32),
- `resource_id` VARCHAR(256),
- `branch_type` VARCHAR(8),
- `status` TINYINT,
- `client_id` VARCHAR(64),
- `application_data` VARCHAR(2000),
- `gmt_create` DATETIME(6),
- `gmt_modified` DATETIME(6),
- PRIMARY KEY (`branch_id`),
- KEY `idx_xid` (`xid`)
-) ENGINE = InnoDB
- DEFAULT CHARSET = utf8;
-
--- the table to store lock data
-CREATE TABLE IF NOT EXISTS `lock_table`
-(
- `row_key` VARCHAR(128) NOT NULL,
- `xid` VARCHAR(96),
- `transaction_id` BIGINT,
- `branch_id` BIGINT NOT NULL,
- `resource_id` VARCHAR(256),
- `table_name` VARCHAR(32),
- `pk` VARCHAR(36),
- `gmt_create` DATETIME,
- `gmt_modified` DATETIME,
- PRIMARY KEY (`row_key`),
- KEY `idx_branch_id` (`branch_id`)
-) ENGINE = InnoDB
- DEFAULT CHARSET = utf8;
-```
-
-在 MySQL 中,创建 `seata` 数据库,并在该库下执行该脚本。最终结果如下图:![`seata` 数据库 - MySQL 5.X](http://www.iocoder.cn/images/Seata/2017-01-01/22.png)
-
-② 修改 `conf/file` 配置文件,修改使用 db 数据库,实现 Seata TC Server 的全局事务会话信息的共享。如下图所示:![`conf/file` 配置文件](http://www.iocoder.cn/images/Seata/2017-01-01/23.png)
-
-③ MySQL8 的支持
-
-> 如果胖友使用的 MySQL 是 8.X 版本,则需要看该步骤。否则,可以直接跳过。
-
-首先,需要下载 MySQL 8.X JDBC 驱动,命令行操作如下:
-
-```Bash
-$ cd lib
-$ wget https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.19/mysql-connector-java-8.0.19.jar
-```
-
-然后,修改 `conf/file` 配置文件,使用该 MySQL 8.X JDBC 驱动。如下图所示:![`seata` 数据库 - MySQL 8.X](http://www.iocoder.cn/images/Seata/2017-01-01/24.png)
-
-## 3.3 设置使用 Nacos 注册中心
-
-修改 `conf/registry.conf` 配置文件,设置使用 Nacos 注册中心。如下图所示:![`conf/registry.conf` 配置文件](http://www.iocoder.cn/images/Seata/2017-01-01/25.png)
-
-## 3.4 启动 TC Server
-
-① 执行 `nohup sh bin/seata-server.sh -p 18091 -n 1 &` 命令,启动**第一个** TC Server 在后台。
-* `-p`:Seata TC Server 监听的端口。
-* `-n`:Server node。在多个 TC Server 时,需区分各自节点,用于生成不同区间的 transactionId 事务编号,以免冲突。
-
-在 `nohup.out` 文件中,我们看到如下日志,说明启动成功:
-
-```Java
-# 使用 DB 存储器
-2020-04-05 16:54:12.793 INFO [main]io.seata.common.loader.EnhancedServiceLoader.loadFile:247 -load DataSourceGenerator[dbcp] extension by class[io.seata.server.store.db.DbcpDataSourceGenerator]
-Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
-2020-04-05 16:54:13.442 INFO [main]io.seata.common.loader.EnhancedServiceLoader.loadFile:247 -load LogStore[DB] extension by class[io.seata.core.store.db.LogStoreDataBaseDAO]
-2020-04-05 16:54:13.442 INFO [main]io.seata.common.loader.EnhancedServiceLoader.loadFile:247 -load TransactionStoreManager[DB] extension by class[io.seata.server.store.db.DatabaseTransactionStoreManager]
-2020-04-05 16:54:13.442 INFO [main]io.seata.common.loader.EnhancedServiceLoader.loadFile:247 -load SessionManager[DB] extension by class[io.seata.server.session.db.DataBaseSessionManager]
-# 启动成功
-2020-04-05 16:54:13.779 INFO [main]io.seata.core.rpc.netty.RpcServerBootstrap.start:155 -Server started ...
-# 使用 Nacos 注册中心
-2020-04-05 16:54:13.788 INFO [main]io.seata.common.loader.EnhancedServiceLoader.loadFile:247 -load RegistryProvider[Nacos] extension by class[io.seata.discovery.registry.nacos.NacosRegistryProvider]
-```
-
-② 执行 `nohup sh bin/seata-server.sh -p 28091 -n 2 &` 命令,启动**第二个** TC Server 在后台。
-
-③ 打开 Nacos 注册中心的控制台,我们可以看到有**两个** Seata TC Server 示例。如下图所示:![Nacos 控制台](http://www.iocoder.cn/images/Seata/2017-01-01/26.png)
-
-# 4. 接入 Java 应用
-
-## 4.1 AT 模式
-
-**① Spring Boot**
-
-1、[《芋道 Spring Boot 分布式事务 Seata 入门》](http://www.iocoder.cn/Spring-Boot/Seata/?self)的[「2. AT 模式 + 多数据源」](#)小节,实现单体 Spring Boot 项目在多数据源下的分布式事务。
-
-![整体图](http://www.iocoder.cn/images/Spring-Boot/2020-10-01/01.png)
-
-2、[《芋道 Spring Boot 分布式事务 Seata 入门》](http://www.iocoder.cn/Spring-Boot/Seata/?self)的[「AT 模式 + HttpClient 远程调用」](#)小节,实现多个 Spring Boot 项目的分布事务。
-
-![整体图](http://www.iocoder.cn/images/Spring-Boot/2020-10-01/21.png)
-
-**② Dubbo**
-
-[《Dubbo 分布式事务 Seata 入门》](http://www.iocoder.cn/Dubbo/Seata/?sef)的[「2. AT 模式」](#)小节,实现多个 Dubbo 服务下的分布式事务。
-
-![整体图](http://www.iocoder.cn/images/Dubbo/2020-04-15/01.png)
-
-**③ Spring Cloud**
-
-[《芋道 Spring Cloud Alibaba 分布式事务 Seata 入门》](http://www.iocoder.cn/Spring-Cloud-Alibaba/Seata/?self)的[「3. AT 模式 + Feign」](#)小节,实现多个 Spring Cloud 服务下的分布式事务。
-
-![整体图](http://www.iocoder.cn/images/Spring-Cloud/2020-07-15/01.png)
-
-## 4.2 TCC 模式
-
-* 文档:[《Seata 文档 —— TCC 模式》](https://seata.io/zh-cn/docs/dev/mode/tcc-mode.html)
-* 示例:
-
-## 4.3 Saga 模式
-
-* 文档:[《Seata 文档 —— Saga 模式》](https://seata.io/zh-cn/docs/dev/mode/saga-mode.html)
-* 示例:
-
-## 4.4 XA 模式
-
-Seata 正在开发中...
+Placeholder. DO NOT DELETE.
\ No newline at end of file
diff --git a/blog/seata-rpc-refactor.md b/blog/seata-rpc-refactor.md
index 59366cfaf2..e872b67af1 100644
--- a/blog/seata-rpc-refactor.md
+++ b/blog/seata-rpc-refactor.md
@@ -1,183 +1 @@
----
-title: Seata RPC 模块的重构之路
-author: 张乘辉
-keywords: [Seata、RPC模块、重构 ]
-date: 2020/07/13
----
-
-# 前言
-
-RPC 模块是我最初研究 Seata 源码开始的地方,因此我对 Seata 的 RPC 模块有过一些深刻研究,在我研究了一番后,发现 RPC 模块中的代码需要进行优化,使得代码更加优雅,交互逻辑更加清晰易懂,本着 “**让天下没有难懂的
-RPC 通信代码**” 的初衷,我开始了 RPC 模块的重构之路。
-
-这里建议想要深入了解 Seata 交互细节的,不妨从 RPC 模块的源码入手,RPC 模块相当于 Seata 的中枢,Seata 所有的交互逻辑在 RPC 模块中表现得淋漓尽致。
-
-这次 RPC 模块的重构将会使得 Seata 的中枢变得更加健壮和易于解读。
-
-# 重构继承关系
-
-在 Seata 的旧版本中,RPC 模块的整体结构有点混乱,尤其是在各个类的继承关系上,主要体现在:
-
-1. 直接在 Remoting 类继承 Netty Handler,使得 Remoting 类与 Netty Handler 处理逻辑耦合在一起;
-2. 客户端和服务端的 Reomting 类继承关系不统一;
-3. RemotingClient 被 RpcClientBootstrap 实现,而 RemotingServer 却被 RpcServer 实现,没有一个独立的 ServerBootstrap,这个看起来关系非常混乱;
-4. 有些接口没必要抽取出来,比如 ClientMessageSender、ClientMessageListener、ServerMessageSender 等接口,因这些接口会增加整体结构继承关系的复杂性。
-
-针对上面发现的问题,在重构过程中我大致做了如下事情:
-
-1. 将 Netty Handler 抽象成一个内部类放在 Remoting 类中;
-2. 将 RemotingClient 为客户端顶级接口,定义客户端与服务端交互的基本方法,抽象一层 AbstractNettyRemotingClient,下面分别有
- RmNettyRemotingClient、TmNettyRemotingClient;将 RemotingServer 为服务端顶级接口,定义服务端与客户端交互的基本方法,实现类 NettyRemotingServer;
-3. 同时将 ClientMessageSender、ClientMessageListener、ServerMessageSender 等接口方法归入到 RemotingClient、RemotingServer 中,由 Reomting
- 类实现 RemotingClient、RemotingServer,统一 Remoting 类继承关系;
-4. 新建 RemotingBootstrap 接口,客户端和服务端分别实现 NettyClientBootstrap、NettyServerBootstrap,将引导类逻辑从 Reomting 类抽离出来。
-
-在最新的 RPC 模块中的继承关系简单清晰,用如下类关系图表示:
-
-![](https://gitee.com/objcoding/md-picture/raw/master/img/20200711111637.png)
-
-1. AbstractNettyRemoting:Remoting 类的最顶层抽象,包含了客户端和服务端公用的成员变量与公用方法,拥有通用的请求方法(文章后面会讲到),Processor 处理器调用逻辑(文章后面会讲到);
-2. RemotingClient:客户端最顶级接口,定义客户端与服务端交互的基本方法;
-3. RemotingServer:服务端最顶级接口,定义服务端与客户端交互的基本方法;
-4. AbstractNettyRemotingClient:客户端抽象类,继承 AbstractNettyRemoting 类并实现了 RemotingClient 接口;
-5. NettyRemotingServer:服务端实现类,继承 AbstractNettyRemoting 类并实现了 RemotingServer 接口;
-6. RmNettyRemotingClient:Rm 客户端实现类,继承 AbstractNettyRemotingClient 类;
-7. TmNettyRemotingClient:Tm 客户端实现类,继承 AbstractNettyRemotingClient 类。
-
-同时将客户端和服务端的引导类逻辑抽象出来,如下类关系图表示:
-
-![](https://gitee.com/objcoding/md-picture/raw/master/img/20200510225359.png)
-
-1. RemotingBootstrap:引导类接口,有 start 和 stop 两个抽象方法;
-2. NettyClientBootstrap:客户端引导实现类;
-3. NettyServerBootstrap:服务端引导实现类。
-
-# 解耦处理逻辑
-
-解耦处理逻辑即是将 RPC 交互的处理逻辑从 Netty Handler 中抽离出来,并将处理逻辑抽象成一个个 Processor,为什么要这么做呢?我大致讲下现在存在的一些问题:
-
-1. Netty Handler 与 处理逻辑是糅合在一起的,由于客户端与服务端都共用了一套处理逻辑,因此为了兼容更多的交互,在处理逻辑中你可以看到非常多难以理解的判断逻辑;
-2. 在 Seata 的交互中有些请求是异步处理的,也有一些请求是同步处理的,但是在旧的处理代码逻辑中对同步异步处理的表达非常隐晦,而且难以看明白;
-3. 无法从代码逻辑当中清晰地表达出请求消息类型与对应的处理逻辑关系;
-4. 在 Seata 后面的更新迭代中,如果不将处理处理逻辑抽离出来,这部分代码想要增加新的交互逻辑,将会非常困难。
-
-在将处理逻辑从 Netty Handler 进行抽离之前,我们先梳理一下 Seata 现有的交互逻辑:
-
-- RM 客户端请求服务端的交互逻辑:
-
-![](https://gitee.com/objcoding/md-picture/raw/master/img/Xnip2020-05-12_21-41-45.png)
-
-- TM 客户端请求服务端的交互逻辑:
-
-![](https://gitee.com/objcoding/md-picture/raw/master/img/Xnip2020-05-12_21-44-04.png)
-
-- 服务端请求 RM 客户端的交互逻辑:
-
-![](https://gitee.com/objcoding/md-picture/raw/master/img/20200513000620.png)
-
-从以上的交互图中可以清晰地看到了 Seata 的交互逻辑。
-
-客户端总共接收服务端的消息:
-
-1)服务端请求消息
-
-1. BranchCommitRequest、BranchRollbackRequest、UndoLogDeleteRequest
-
-2)服务端响应消息
-
-1. RegisterRMResponse、BranchRegisterResponse、BranchReportResponse、GlobalLockQueryResponse
-2.
-RegisterTMResponse、GlobalBeginResponse、GlobalCommitResponse、GlobalRollbackResponse、GlobalStatusResponse、GlobalReportResponse
-3. HeartbeatMessage(PONG)
-
-服务端总共接收客户端的消息:
-
-1)客户端请求消息:
-
-1. RegisterRMRequest、BranchRegisterRequest、BranchReportRequest、GlobalLockQueryRequest
-2.
-RegisterTMRequest、GlobalBeginRequest、GlobalCommitRequest、GlobalRollbackRequest、GlobalStatusRequest、GlobalReportRequest
-3. HeartbeatMessage(PING)
-
-2)客户端响应消息:
-
-1. BranchCommitResponse、BranchRollbackResponse
-
-基于以上的交互逻辑分析,我们可以将处理消息的逻辑抽象成若干个 Processor,一个 Processor 可以处理一个或者多个消息类型的消息,只需在 Seata 启动时注册将消息类型注册到 ProcessorTable
-中即可,形成一个映射关系,这样就可以根据消息类型调用对应的 Processor 对消息进行处理,用如下图表示:
-
-![](https://gitee.com/objcoding/md-picture/raw/master/img/Xnip2020-05-12_22-09-17.png)
-
-在抽象 Remoting 类中定一个 processMessage 方法,方法逻辑是根据消息类型从 ProcessorTable 中拿到消息类型对应的 Processor。
-
-这样就成功将处理逻辑从 Netty Handler 中彻底抽离出来了,Handler#channelRead 方法只需要调用 processMessage 方法即可,且还可以灵活根据消息类型动态注册 Processor 到
-ProcessorTable 中,处理逻辑的可扩展性得到了极大的提升。
-
-以下是 Processor 的调用流程:
-
-1)客户端
-
-![](https://gitee.com/objcoding/md-picture/raw/master/img/20200510234047.png)
-
-1. RmBranchCommitProcessor:处理服务端全局提交请求;
-2. RmBranchRollbackProcessor:处理服务端全局回滚请求;
-3. RmUndoLogProcessor:处理服务端 undo log 删除请求;
-4. ClientOnResponseProcessor:客户端处理服务端响应请求,如:BranchRegisterResponse、GlobalBeginResponse、GlobalCommitResponse 等;
-5. ClientHeartbeatProcessor:处理服务端心跳响应。
-
-2)服务端
-
-![](https://gitee.com/objcoding/md-picture/raw/master/img/20200510234016.png)
-
-1. RegRmProcessor:处理 RM 客户端注册请求;
-2. RegTmProcessor:处理 TM 客户端注册请求;
-3. ServerOnRequestProcessor:处理客户端相关请求,如:BranchRegisterRequest、GlobalBeginRequest、GlobalLockQueryRequest 等;
-4. ServerOnResponseProcessor:处理客户端相关响应,如:BranchCommitResponse、BranchRollbackResponse 等;
-5. ServerHeartbeatProcessor:处理客户端心跳响应。
-
-下面我以 TM 发起全局事务提交请求为例子,让大家感受下 Processor 在整个交互中所处的位置:
-
-![](https://gitee.com/objcoding/md-picture/raw/master/img/20200514191842.png)
-
-# 重构请求方法
-
-在 Seata 的旧版本当中,RPC 的请求方法也是欠缺优雅,主要体现在:
-
-1. 请求方法过于杂乱无章,没有层次感;
-2. sendAsyncRequest 方法耦合的代码太多,逻辑过于混乱,客户端与服务端都共用了一套请求逻辑,方法中决定是否批量发送是根据参数 address 是否为 null 决定,决定是否同步请求是根据 timeout 是否大于 0
- 决定,显得极为不合理,且批量请求只有客户端有用到,服务端并没有批量请求,共用一套请求逻辑还会导致服务端异步请求也会创建 MessageFuture 放入 futures 中;
-3. 请求方法名称风格不统一,比如客户端 sendMsgWithResponse,服务端却叫 sendSyncRequest;
-
-针对以上旧版本 RPC 请求方法的各种缺点,我作了以下改动:
-
-1. 将请求方法统一放入 RemotingClient、RemotingServer 接口当中,并作为顶级接口;
-2. 分离客户端与服务端请求逻辑,将批量请求逻辑单独抽到客户端相关请求方法中,使得是否批量发送不再根据参数 address 是否为 null 决定;
-3. 由于 Seata
- 自身的逻辑特点,客户端服务端请求方法的参数无法统一,可通过抽取通用的同步/异步请求方法,客户端和服务端根据自身请求逻辑特点实现自身的同步/异步请求逻辑,最后再调用通用的同步/异步请求方法,使得同步/异步请求都有明确的方法,不再根据
- timeout 是否大于 0 决定;
-4. 统一请求名称风格。
-
-最终,Seata RPC 的请求方法终于看起来更加优雅且有层次感了。
-
-同步请求:
-
-![](https://gitee.com/objcoding/md-picture/raw/master/img/20200513103838.png)
-
-异步请求:
-
-![](https://gitee.com/objcoding/md-picture/raw/master/img/20200513103904.png)
-
-# 其它
-
-1. 类目录调整:RPC 模块目录中还有一个 netty 目录,也可以从目录结构中发现 Seata 的初衷是兼容多个 RPC 框架,目前只实现了 netty,但发现 netty 模块中有些类并不 ”netty“,且 RPC
- 跟目录的类也并不通用,因此需要将相关类的位置进行调整;
-2. 某些类重新命名,比如 netty 相关类包含 「netty」;
-
-最终 RPC 模块看起来是这样的:
-
-![](https://gitee.com/objcoding/md-picture/raw/master/img/20200711213204.png)
-
-# 作者简介
-
-张乘辉,目前就职于蚂蚁集团,热爱分享技术,微信公众号「后端进阶」作者,技术博客([https://objcoding.com/](https://objcoding.com/))博主,Seata Contributor,GitHub
-ID:objcoding。
\ No newline at end of file
+Placeholder. DO NOT DELETE.
\ No newline at end of file
diff --git a/blog/seata-snowflake-explain.md b/blog/seata-snowflake-explain.md
index c1a8bc14bf..e872b67af1 100644
--- a/blog/seata-snowflake-explain.md
+++ b/blog/seata-snowflake-explain.md
@@ -1,58 +1 @@
----
-title: 关于新版雪花算法的答疑
-author: selfishlover
-keywords: [Seata, snowflake, UUID, page split]
-date: 2021/06/21
----
-
-# 关于新版雪花算法的答疑
-
-在上一篇关于新版雪花算法的解析中,我们提到新版算法所做出的2点改变:
-1. 时间戳不再时刻追随系统时钟。
-2. 节点ID和时间戳互换位置。由原版的:
-![原版位分配策略](/img/blog/seata/uuid/before.png)
-改成:
-![改进版位分配策略](/img/blog/seata/uuid/after.png)
-
-有细心的同学提出了一个问题:新版算法在单节点内部确实是单调递增的,但是在多实例部署时,它就不再是全局单调递增了啊!因为显而易见,节点ID排在高位,那么节点ID大的,生成的ID一定大于节点ID小的,不管时间上谁先谁后。而原版算法,时间戳在高位,并且始终追随系统时钟,可以保证早生成的ID小于晚生成的ID,只有当2个节点恰好在同一时间戳生成ID时,2个ID的大小才由节点ID决定。这样看来,新版算法是不是错的?
-
-这是一个很好的问题!能提出这个问题的同学,说明已经深入思考了标准版雪花算法和新版雪花算法的本质区别,这点值得鼓励!在这里,我们先说结论:新版算法的确不具备全局的单调递增性,但这不影响我们的初衷(减少数据库的页分裂)。这个结论看起来有点违反直觉,但可以被证明。
-
-在证明之前,我们先简单回顾一下数据库关于页分裂的知识。以经典的mysql innodb为例,innodb使用B+树索引,其中,主键索引的叶子节点还保存了数据行的完整记录,叶子节点之间以双向链表的形式串联起来。叶子节点的物理存储形式为数据页,一个数据页内最多可以存储N条行记录(N与行的大小成反比)。如图所示:
-![数据页](/img/blog/seata/uuid/page1.png)
-B+树的特性要求,左边的节点应小于右边的节点。如果此时要插入一条ID为25的记录,会怎样呢(假设每个数据页只够存放4条记录)?答案是会引起页分裂,如图:
-![页分裂](/img/blog/seata/uuid/page2.png)
-页分裂是IO不友好的,需要新建数据页,拷贝转移旧数据页的部分记录等,我们应尽量避免。
-
-理想的情况下,主键ID最好是顺序递增的(例如把主键设置为auto_increment),这样就只会在当前数据页放满了的时候,才需要新建下一页,双向链表永远是顺序尾部增长的,不会有中间的节点发生分裂的情况。
-
-最糟糕的情况下,主键ID是随机无序生成的(例如java中一个UUID字符串),这种情况下,新插入的记录会随机分配到任何一个数据页,如果该页已满,就会触发页分裂。
-
-如果主键ID由标准版雪花算法生成,最好的情况下,是每个时间戳内只有一个节点在生成ID,这时候算法的效果等同于理想情况的顺序递增,即跟auto_increment无差。最坏的情况下,是每个时间戳内所有节点都在生成ID,这时候算法的效果接近于无序(但仍比UUID的完全无序要好得多,因为workerId只有10位决定了最多只有1024个节点)。实际生产中,算法的效果取决于业务流量,并发度越低,算法越接近理想情况。
-
-那么,换成新版算法又会如何呢?
-新版算法从全局角度来看,ID是无序的,但对于每一个workerId,它生成的ID都是严格单调递增的,又因为workerId是有限的,所以最多可划分出1024个子序列,每个子序列都是单调递增的。
-对于数据库而言,也许它初期接收的ID都是无序的,来自各个子序列的ID都混在一起,就像这样:
-![初期](/img/blog/seata/uuid/page3.png)
-如果这时候来了个worker1-seq2,显然会造成页分裂:
-![首次分裂](/img/blog/seata/uuid/page4.png)
-但分裂之后,有趣的事情发生了,对于worker1而言,后续的seq3,seq4不会再造成页分裂(因为还装得下),seq5也只需要像顺序增长那样新建页进行链接(区别是这个新页不是在双向链表的尾部)。注意,worker1的后续ID,不会排到worker2及之后的任意节点(因而不会造成后边节点的页分裂),因为它们总比worker2的ID小;也不会排到worker1当前节点的前边(因而不会造成前边节点的页分裂),因为worker1的子序列总是单调递增的。在这里,我们称worker1这样的子序列达到了稳态,意为这条子序列已经"稳定"了,它的后续增长只会出现在子序列的尾部,而不会造成其它节点的页分裂。
-
-同样的事情,可以推广到各个子序列上。无论前期数据库接收到的ID有多乱,经过有限次的页分裂后,双向链表总能达到这样一个稳定的终态:
-![终态](/img/blog/seata/uuid/page5.png)
-到达终态后,后续的ID只会在该ID所属的子序列上进行顺序增长,而不会造成页分裂。该状态下的顺序增长与auto_increment的顺序增长的区别是,前者有1024个增长位点(各个子序列的尾部),后者只有尾部一个。
-
-到这里,我们可以回答开头所提出的问题了:新算法从全局来看的确不是全局递增的,但该算法是**收敛**的,达到稳态后,新算法同样能达成像全局顺序递增一样的效果。
-
-***
-## 扩展思考
-
-以上只提到了序列不停增长的情况,而实践生产中,不光有新数据的插入,也有旧数据的删除。而数据的删除有可能会导致页合并(innodb若发现相邻2个数据页的空间利用率都不到50%,就会把它俩合并),这对新算法的影响如何呢?
-
-经过上面的流程,我们可以发现,新算法的本质是利用前期的页分裂,把不同的子序列逐渐分离开来,让算法不断收敛到稳态。而页合并则恰好相反,它有可能会把不同的子序列又合并回同一个数据页里,妨碍算法的收敛。尤其是在收敛的前期,频繁的页合并甚至可以让算法永远无法收敛(你刚分离出来我就又把它们合并回去,一夜回到解放前~)!但在收敛之后,只有在各个子序列的尾节点进行的页合并,才有可能破坏稳态(一个子序列的尾节点和下一个子序列的头节点进行合并)。而在子序列其余节点上的页合并,不影响稳态,因为子序列仍然是有序的,只不过长度变短了而已。
-
-以seata的服务端为例,服务端那3张表的数据的生命周期都是比较短的,一个全局事务结束之后,它们就会被清除了,这对于新算法是不友好的,没有给时间它进行收敛。不过已经有延迟删除的PR在review中,搭配这个PR,效果会好很多。比如定期每周清理一次,前期就有足够的时间给算法进行收敛,其余的大部分时间,数据库就能从中受益了。到期清理时,最坏的结果也不过是表被清空,算法从头再来。
-
-如果您希望把新算法应用到业务系统当中,请务必确保算法有时间进行收敛。比如用户表之类的,数据本就打算长期保存的,算法可以自然收敛。或者也做了延迟删除的机制,给算法足够的时间进行收敛。
-
-如果您有更好的意见和建议,也欢迎跟seata社区联系!
+Placeholder. DO NOT DELETE.
\ No newline at end of file
diff --git a/blog/seata-sourcecode-client-bootstrap.md b/blog/seata-sourcecode-client-bootstrap.md
index cceb29cd66..e872b67af1 100644
--- a/blog/seata-sourcecode-client-bootstrap.md
+++ b/blog/seata-sourcecode-client-bootstrap.md
@@ -1,304 +1 @@
----
-title: 分布式事务Seata源码-Client端启动流程
-author: 杨晓兵|中原银行
-date: 2020/08/25
-keywords: [fescar、seata、分布式事务]
----
-
-## 【分布式事务Seata源码解读二】Client端启动流程
-
-本文从源码的角度分析一下AT模式下Client端启动流程,所谓的Client端,即业务应用方。分布式事务分为三个模块:TC、TM、RM。其中TC位于seata-server端,而TM、RM通过SDK的方式运行在client端。
-
-下图展示了Seata官方Demo的一个分布式事务场景,分为如下几个微服务,共同实现了一个下订单、扣库存、扣余额的分布式事务。
-* **BusinessService:** 业务服务,下单服务的入口
-* **StorageService:** 库存微服务,用于扣减商品库存
-* **OrderService:** 订单微服务,创建订单
-* **AccountService:** 账户微服务,扣减用户账户的余额
-
-![在这里插入图片描述](https://img-blog.csdnimg.cn/20200820184156758.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTE0NTg0OA==,size_16,color_FFFFFF,t_70#pic_center)
-
-从上图也可以看出,在AT模式下Seata Client端主要通过如下三个模块来实现分布式事务:
-* **GlobalTransactionScanner:** GlobalTransactionScanner负责初始TM、RM模块,并为添加分布式事务注解的方法添加拦截器,拦截器负责全局事务的开启、提交或回滚
-* **DatasourceProxy:** DatasourceProxy为DataSource添加拦截,拦截器会拦截所有SQL执行,并作为RM事务参与方的角色参与分布式事务执行。
-* **Rpc Interceptor:** 在上一篇[分布式事务Seata源码解读一](https://blog.csdn.net/weixin_45145848/article/details/106930538)中有提到分布式事务的几个核心要点,其中有一个是分布式事务的跨服务实例传播。Rpc Interceptor的职责就是负责在多个微服务之间传播事务。
-
-## seata-spring-boot-starter
-引用seata分布式事务SDK有两种方式,依赖seata-all或者seata-spring-boot-starter,推荐使用seata-spring-boot-starter,因为该starter已经自动注入了上面提到的三个模块,用户只要添加相应的配置,在业务代码添加全局分布式事务注解即可。下面从seata-spring-boot-starter项目中的代码入手:
-
-如下图所示是seata-spring-boot-starter的项目结构:
-![在这里插入图片描述](https://img-blog.csdnimg.cn/20200810204518853.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTE0NTg0OA==,size_16,color_FFFFFF,t_70)
-主要分为以下几个模块:
-* **properties:** properties目录下都是Springboot 适配seata的相关配置类,即可以通过SpringBoot的配置方式来Seata的相关参数
-* **provider:** provider目录下的类负责把Springboot、SpringCloud的配置适配到Seata配置中
-* **resources:** resources目录下主要有两个文件,spring.factories用于注册Springboot的自动装配类,ExtConfigurationProvider用于注册SpringbootConfigurationProvider类,该Provider类负责把SpringBoot的相关配置类适配到Seata中。
-
-对于springboot-starter项目,我们先查看resources/META-INF/spring.factories文件:
-```properties
-# Auto Configure
-org.springframework.boot.autoconfigure.EnableAutoConfiguration=
-io.seata.spring.boot.autoconfigure.SeataAutoConfiguration
-```
-可以看到在spring.factories中配置了自动装配类:SeataAutoConfiguration,在该装配类中主要注入了GlobalTransactionScanner和seataAutoDataSourceProxyCreator两个实例。代码如下:
-```java
-@ComponentScan(basePackages = "io.seata.spring.boot.autoconfigure.properties")
-@ConditionalOnProperty(prefix = StarterConstants.SEATA_PREFIX, name = "enabled",
- havingValue = "true",
- matchIfMissing = true)
-@Configuration
-@EnableConfigurationProperties({SeataProperties.class})
-public class SeataAutoConfiguration {
-
- ...
-
- // GlobalTransactionScanner负责为添加GlobalTransaction注解的方法添加拦截器,
- // 并且负责初始化RM、TM
- @Bean
- @DependsOn({BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER, BEAN_NAME_FAILURE_HANDLER})
- @ConditionalOnMissingBean(GlobalTransactionScanner.class)
- public GlobalTransactionScanner globalTransactionScanner(SeataProperties seataProperties,
- FailureHandler failureHandler) {
- if (LOGGER.isInfoEnabled()) {
- LOGGER.info("Automatically configure Seata");
- }
- return new GlobalTransactionScanner(seataProperties.getApplicationId(),
- seataProperties.getTxServiceGroup(),
- failureHandler);
- }
-
- // SeataAutoDataSourceProxyCreator负责为Spring中的所有DataSource生成代理对象,
- // 从而实现拦截所有SQL的执行
- @Bean(BEAN_NAME_SEATA_AUTO_DATA_SOURCE_PROXY_CREATOR)
- @ConditionalOnProperty(prefix = StarterConstants.SEATA_PREFIX, name = {
- "enableAutoDataSourceProxy", "enable-auto" +
- "-data-source-proxy"}, havingValue = "true", matchIfMissing = true)
- @ConditionalOnMissingBean(SeataAutoDataSourceProxyCreator.class)
- public SeataAutoDataSourceProxyCreator seataAutoDataSourceProxyCreator(SeataProperties seataProperties) {
- return new SeataAutoDataSourceProxyCreator(seataProperties.isUseJdkProxy(),
- seataProperties.getExcludesForAutoProxying());
- }
-}
-```
-
-### GlobalTransactionScanner
-GlobalTransactionScanner继承于AutoProxyCreator,AutoProxyCreator是Spring中实现AOP的一种方式,可以拦截Spring中的所有实例,判断是否需要进行代理。下面列出了GlobalTransactionScanner中一些比较重要的字段和拦截代理的核心方法:
-```java
-public class GlobalTransactionScanner extends AbstractAutoProxyCreator
- implements InitializingBean, ApplicationContextAware,
- DisposableBean {
- ...
- // interceptor字段是对应一个代理对象的拦截器,
- // 可以认为是一个临时变量,有效期是一个被代理对象
- private MethodInterceptor interceptor;
-
- // globalTransactionalInterceptor是通用的Interceptor,
- // 非TCC事务方式的都使用该Interceptor
- private MethodInterceptor globalTransactionalInterceptor;
-
- // PROXYED_SET存储已经代理过的实例,防止重复处理
- private static final Set PROXYED_SET = new HashSet<>();
-
- // applicationId是一个服务的唯一标识,
- // 对应springcloud项目中的spring.application.name
- private final String applicationId;
- // 事务的分组标识,参考文章wiki:http://seata.io/zh-cn/docs/user/txgroup/transaction-group.html
- private final String txServiceGroup;
-
- ...
-
- // 判断是否需要代理目标对象,如果需要代理,则生成拦截器赋值到类变量interceptor中
- @Override
- protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
- // 判断是否禁用分布式事务
- if (disableGlobalTransaction) {
- return bean;
- }
- try {
- synchronized (PROXYED_SET) {
- if (PROXYED_SET.contains(beanName)) {
- return bean;
- }
-
- // 每次处理一个被代理对象时先把interceptor置为null,所以interceptor的
- // 生命周期是一个被代理对象,由于是在另外一个方法getAdvicesAndAdvisorsForBean
- // 中使用interceptor,所以该interceptor要定义为一个类变量
- interceptor = null;
-
- // 判断是否是TCC事务模式,判断的主要依据是方法上是否有TwoPhaseBusinessAction注解
- if (TCCBeanParserUtils.isTccAutoProxy(bean, beanName,
- applicationContext)) {
- // 创建一个TCC事务的拦截器
- interceptor =
- new TccActionInterceptor(TCCBeanParserUtils.getRemotingDesc(beanName));
- } else {
- // 获取待处理对象的class类型
- Class> serviceInterface = SpringProxyUtils.findTargetClass(bean);
- // 获取待处理对象继承的所有接口
- Class>[] interfacesIfJdk = SpringProxyUtils.findInterfaces(bean);
-
- // 如果待处理对象的class或者继承的接口上有GlobalTransactional注解,
- // 或者待处理对象的class的任一个方法上有GlobalTransactional或者
- // GlobalLock注解则返回true,即需要被代理
- if (!existsAnnotation(new Class[]{serviceInterface})
- && !existsAnnotation(interfacesIfJdk)) {
- return bean;
- }
-
- // 如果interceptor为null,即不是TCC模式,
- // 则使用globalTransactionalInterceptor作为拦截器
- if (interceptor == null) {
- // globalTransactionalInterceptor只会被创建一次
- if (globalTransactionalInterceptor == null) {
- globalTransactionalInterceptor =
- new GlobalTransactionalInterceptor(failureHandlerHook);
- ConfigurationCache.addConfigListener(
- ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
- (ConfigurationChangeListener) globalTransactionalInterceptor);
- }
- interceptor = globalTransactionalInterceptor;
- }
- }
-
- if (!AopUtils.isAopProxy(bean)) {
- // 如果bean本身不是Proxy对象,则直接调用父类的wrapIfNecessary生成代理对象即可
- // 在父类中会调用getAdvicesAndAdvisorsForBean获取到上面定义的interceptor
- bean = super.wrapIfNecessary(bean, beanName, cacheKey);
- } else {
- // 如果该bean已经是代理对象了,则直接在代理对象的拦截调用链AdvisedSupport
- // 上直接添加新的interceptor即可。
- AdvisedSupport advised = SpringProxyUtils.getAdvisedSupport(bean);
- Advisor[] advisor = buildAdvisors(beanName,
- getAdvicesAndAdvisorsForBean(null, null, null));
- for (Advisor avr : advisor) {
- advised.addAdvisor(0, avr);
- }
- }
- // 标识该beanName已经处理过了
- PROXYED_SET.add(beanName);
- return bean;
- }
- } catch (Exception exx) {
- throw new RuntimeException(exx);
- }
- }
-
- // 返回wrapIfNecessary方法中计算出的interceptor对象
- @Override
- protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName,
- TargetSource customTargetSource)
- throws BeansException {
- return new Object[]{interceptor};
- }
-}
-```
-
-上面介绍了GlobalTransactionScanner是如何通过注解拦截全局事务的,具体拦截器实现为TccActionInterceptor和GlobalTransactionalInterceptor,对于AT模式来说我们主要关心GlobalTransactionalInterceptor,在后续的文章中会介绍GlobalTransactionalInterceptor的具体实现。
-
-另外GloabalTransactionScanner还负责TM、RM的初始化工作,是在initClient方法中实现的:
-```java
-private void initClient() {
- ...
-
- //初始化TM
- TMClient.init(applicationId, txServiceGroup);
- ...
-
- //初始化RM
- RMClient.init(applicationId, txServiceGroup);
- ...
-
- // 注册Spring shutdown的回调,用来释放资源
- registerSpringShutdownHook();
-
- }
-```
-
-TMClient、RMClient都是Seata基于Netty实现的Rpc框架的客户端类,只是业务逻辑不同,由于TMClient相对来说更简单一些,我们以RMClient为例看一下源码:
-```java
-public class RMClient {
- // RMClient的init是一个static方法,创建了一个RmNettyRemotingClient实例,并调用init方法
- public static void init(String applicationId, String transactionServiceGroup) {
- RmNettyRemotingClient rmNettyRemotingClient =
- RmNettyRemotingClient.getInstance(applicationId, transactionServiceGroup);
- rmNettyRemotingClient.setResourceManager(DefaultResourceManager.get());
- rmNettyRemotingClient.setTransactionMessageHandler(DefaultRMHandler.get());
- rmNettyRemotingClient.init();
- }
-}
-```
-
-RmNettyRemotingClient的实现如下:
-```java
-@Sharable
-public final class RmNettyRemotingClient extends AbstractNettyRemotingClient {
- // ResourceManager负责处理事务参与方,支持AT、TCC、Saga三种模式
- private ResourceManager resourceManager;
- // RmNettyRemotingClient单例
- private static volatile RmNettyRemotingClient instance;
- private final AtomicBoolean initialized = new AtomicBoolean(false);
- // 微服务的唯一标识
- private String applicationId;
- // 分布式事务分组名称
- private String transactionServiceGroup;
-
- // RMClient中init方法会调用该init方法
- public void init() {
- // 注册Seata自定义Rpc的Processor
- registerProcessor();
- if (initialized.compareAndSet(false, true)) {
- // 调用父类的init方法,在父类中负责Netty的初始化,与Seata-Server建立连接
- super.init();
- }
- }
-
- // 注册Seata自定义Rpc的Processor
- private void registerProcessor() {
- // 1.注册Seata-Server发起branchCommit的处理Processor
- RmBranchCommitProcessor rmBranchCommitProcessor =
- new RmBranchCommitProcessor(getTransactionMessageHandler(), this);
- super.registerProcessor(MessageType.TYPE_BRANCH_COMMIT, rmBranchCommitProcessor,
- messageExecutor);
-
- // 2.注册Seata-Server发起branchRollback的处理Processor
- RmBranchRollbackProcessor rmBranchRollbackProcessor =
- new RmBranchRollbackProcessor(getTransactionMessageHandler(), this);
- super.registerProcessor(MessageType.TYPE_BRANCH_ROLLBACK, rmBranchRollbackProcessor
- , messageExecutor);
-
- // 3.注册Seata-Server发起删除undoLog的处理Processor
- RmUndoLogProcessor rmUndoLogProcessor =
- new RmUndoLogProcessor(getTransactionMessageHandler());
- super.registerProcessor(MessageType.TYPE_RM_DELETE_UNDOLOG, rmUndoLogProcessor,
- messageExecutor);
-
- // 4.注册Seata-Server返回Response的处理Processor,ClientOnResponseProcessor
- // 用于处理由Client主动发起Request,Seata-Server返回的Response。
- // ClientOnResponseProcessor负责把Client发送的Request和Seata-Server
- // 返回的Response对应起来,从而实现Rpc
- ClientOnResponseProcessor onResponseProcessor =
- new ClientOnResponseProcessor(mergeMsgMap, super.getFutures(),
- getTransactionMessageHandler());
- super.registerProcessor(MessageType.TYPE_SEATA_MERGE_RESULT, onResponseProcessor,
- null);
- super.registerProcessor(MessageType.TYPE_BRANCH_REGISTER_RESULT,
- onResponseProcessor, null);
- super.registerProcessor(MessageType.TYPE_BRANCH_STATUS_REPORT_RESULT,
- onResponseProcessor, null);
- super.registerProcessor(MessageType.TYPE_GLOBAL_LOCK_QUERY_RESULT,
- onResponseProcessor, null);
- super.registerProcessor(MessageType.TYPE_REG_RM_RESULT, onResponseProcessor, null);
-
- // 5. 处理Seata-Server返回的Pong消息
- ClientHeartbeatProcessor clientHeartbeatProcessor = new ClientHeartbeatProcessor();
- super.registerProcessor(MessageType.TYPE_HEARTBEAT_MSG, clientHeartbeatProcessor,
- null);
- }
-}
-```
-
-上面的逻辑看起来比较复杂,相关类也比较多,如:各种Processor、各种MessageType、TransactionMessageHandler、ResourceManager。其实本质上就是Rpc调用,分为Rm主动调用和Seata主动调用。
-* **Rm主动调用方法:** 如:注册分支、汇报分支状态、申请全局锁等。Rm主动调用的方法都需要在ClientOnResponseProcessor中处理Seata-Server返回的Response
-* **Seata-Server主动调用方法:** 如:提交分支事务、回滚分支事务、删除undolog日志。Seata-Server主动调用的方法,Client端分别对应不同的Processor来处理,并且处理结束后要返回给Seata-Server处理结果Response。而事务提交、回滚的核心实现逻辑都在TransactionMessageHandler、ResourceManager中。
-
-关于TransactionMessageHandler、ResourceManager的具体实现也会在后续的章节中详细描述。
-
-下一篇会介绍一下SeataAutoDataSourceProxyCreator、Rpc Interceptor是如何初始化以及拦截的。
\ No newline at end of file
+Placeholder. DO NOT DELETE.
\ No newline at end of file
diff --git a/blog/seata-sourcecode-server-bootstrap.md b/blog/seata-sourcecode-server-bootstrap.md
index 84ae1b0d7a..e872b67af1 100644
--- a/blog/seata-sourcecode-server-bootstrap.md
+++ b/blog/seata-sourcecode-server-bootstrap.md
@@ -1,575 +1 @@
----
-title: 分布式事务Seata源码-Server端启动流程
-author: 杨晓兵|中原银行
-date: 2020/07/02
-keywords: [fescar、seata、分布式事务]
----
-
-## 【分布式事务Seata源码解读一】Server端启动流程
-
-### 实现分布式事务的核心要点:
-1. 事务的持久化,事务所处的各种状态事务参与方的各种状态都需要持久化,当实例宕机时才能基于持久化的数据对事务回滚或提交,实现最终一致性
-2. 定时对超时未完成事务的处理(继续尝试提交或回滚),即通过重试机制实现事务的最终一致性
-3. 分布式事务的跨服务实例传播,当分布式事务跨多个实例时需要实现事务的传播,一般需要适配不同的rpc框架
-4. 事务的隔离级别:大多数分布式事务为了性能,默认的隔离级别是读未提交
-5. 幂等性:对于XA或者seata的AT这样的分布式事务来说,都已经默认实现了幂等性,而TCC、Saga这种接口级别实现的分布式事务都还需要业务开发者自己实现幂等性。
-
-本片文章主要从seata-server的启动流程的角度介绍一下seata-server的源码,启动流程图如下:
-
-![在这里插入图片描述](https://img-blog.csdnimg.cn/20200726213919467.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTE0NTg0OA==,size_16,color_FFFFFF,t_70)
-
-
-#### 1. 启动类Server
-seata-server的入口类在Server类中,源码如下:
-```java
-public static void main(String[] args) throws IOException {
- // 从环境变量或运行时参数中获取监听端口,默认端口8091
- int port = PortHelper.getPort(args);
-
- // 把监听端口设置到SystemProperty中,Logback的LoggerContextListener实现类
- // SystemPropertyLoggerContextListener会把Port写入到Logback的Context中,
- // 在logback.xml文件中会使用Port变量来构建日志文件名称。
- System.setProperty(ConfigurationKeys.SERVER_PORT, Integer.toString(port));
-
- // 创建Logger
- final Logger logger = LoggerFactory.getLogger(Server.class);
- if (ContainerHelper.isRunningInContainer()) {
- logger.info("The server is running in container.");
- }
-
- // 解析启动以及配置文件的各种配置参数
- ParameterParser parameterParser = new ParameterParser(args);
-
- // metrics相关,这里是使用SPI机制获取Registry实例对象
- MetricsManager.get().init();
-
- // 把从配置文件中读取到的storeMode写入SystemProperty中,方便其他类使用。
- System.setProperty(ConfigurationKeys.STORE_MODE, parameterParser.getStoreMode());
-
- // 创建NettyRemotingServer实例,NettyRemotingServer是一个基于Netty实现的Rpc框架,
- // 此时并没有初始化,NettyRemotingServer负责与客户端SDK中的TM、RM进行网络通信。
- nettyRemotingServer = new NettyRemotingServer(WORKING_THREADS);
-
- // 设置监听端口
- nettyRemotingServer.setListenPort(parameterParser.getPort());
-
- // UUIDGenerator初始化,UUIDGenerator基于雪花算法实现,
- // 用于生成全局事务、分支事务的id。
- // 多个Server实例配置不同的ServerNode,保证id的唯一性
- UUIDGenerator.init(parameterParser.getServerNode());
-
- // SessionHodler负责事务日志(状态)的持久化存储,
- // 当前支持file、db、redis三种存储模式,集群部署模式要使用db或redis模式
- SessionHolder.init(parameterParser.getStoreMode());
-
- // 创建初始化DefaultCoordinator实例,DefaultCoordinator是TC的核心事务逻辑处理类,
- // 底层包含了AT、TCC、SAGA等不同事务类型的逻辑处理。
- DefaultCoordinator coordinator = new DefaultCoordinator(nettyRemotingServer);
- coordinator.init();
- nettyRemotingServer.setHandler(coordinator);
- // register ShutdownHook
- ShutdownHook.getInstance().addDisposable(coordinator);
- ShutdownHook.getInstance().addDisposable(nettyRemotingServer);
-
- // 127.0.0.1 and 0.0.0.0 are not valid here.
- if (NetUtil.isValidIp(parameterParser.getHost(), false)) {
- XID.setIpAddress(parameterParser.getHost());
- } else {
- XID.setIpAddress(NetUtil.getLocalIp());
- }
- XID.setPort(nettyRemotingServer.getListenPort());
-
- try {
- // 初始化Netty,开始监听端口并阻塞在这里,等待程序关闭
- nettyRemotingServer.init();
- } catch (Throwable e) {
- logger.error("nettyServer init error:{}", e.getMessage(), e);
- System.exit(-1);
- }
-
- System.exit(0);
-}
-```
-
-#### 2. 解析配置
-参数解析的实现代码在ParameterParser类中,init方法源码如下:
-```java
-private void init(String[] args) {
- try {
- // 判断是否运行在容器中,如果运行在容器中则配置从环境变量中获取
- if (ContainerHelper.isRunningInContainer()) {
- this.seataEnv = ContainerHelper.getEnv();
- this.host = ContainerHelper.getHost();
- this.port = ContainerHelper.getPort();
- this.serverNode = ContainerHelper.getServerNode();
- this.storeMode = ContainerHelper.getStoreMode();
- } else {
- // 基于JCommander获取启动应用程序时配置的参数,
- // JCommander通过注解、反射的方式把参数赋值到当前类的字段上。
- JCommander jCommander = JCommander.newBuilder().addObject(this).build();
- jCommander.parse(args);
- if (help) {
- jCommander.setProgramName(PROGRAM_NAME);
- jCommander.usage();
- System.exit(0);
- }
- }
- // serverNode用于雪花算中的实例的唯一标识,需要保证唯一。
- // 如果没有指定基于当前服务器的I随机生成一个
- if (this.serverNode == null) {
- this.serverNode = IdWorker.initWorkerId();
- }
- if (StringUtils.isNotBlank(seataEnv)) {
- System.setProperty(ENV_PROPERTY_KEY, seataEnv);
- }
- if (StringUtils.isBlank(storeMode)) {
- // 这里牵扯到一个重要的Configuration类,ParameterParser只负责获取ip、port、storeMode等核心参数,
- // 其他的参数都是从Configuration中获取的。这里如果没有启动参数没有指定storeMode,
- // 就从Configuration类中获取。
- storeMode = ConfigurationFactory.getInstance().getConfig(ConfigurationKeys.STORE_MODE,
- SERVER_DEFAULT_STORE_MODE);
- }
- } catch (ParameterException e) {
- printError(e);
- }
-
-}
-```
-
-在ParameterParser的init方法中第一次调用了ConfigurationFactory.getInstance(),初始化了一个单例的Configuration对象,Configuration负责初始化所有的其他配置参数信息。从Seata Server端的源码中我们能看到两个配置文件file.conf、registry.conf。那么这两个配置文件的区别是什么,两个文件都是必须的吗?我们继续看代码。
-
-ConfigurationFactory.getInstance方法其实就是获取一个单例对象,核心在buildConfiguration方法中,不过在buidlConfiguration方法前,ConfigurationFactory类有一段static代码块会先执行。
-```java
-// 获取Configuration的单例对象
-public static Configuration getInstance() {
- if (instance == null) {
- synchronized (Configuration.class) {
- if (instance == null) {
- instance = buildConfiguration();
- }
- }
- }
- return instance;
-}
-
-// ConfigurationFactory的static代码块
-static {
- // 获取配置文件的名称,默认为registry.conf
- String seataConfigName = System.getProperty(SYSTEM_PROPERTY_SEATA_CONFIG_NAME);
- if (seataConfigName == null) {
- seataConfigName = System.getenv(ENV_SEATA_CONFIG_NAME);
- }
- if (seataConfigName == null) {
- seataConfigName = REGISTRY_CONF_PREFIX;
- }
- String envValue = System.getProperty(ENV_PROPERTY_KEY);
- if (envValue == null) {
- envValue = System.getenv(ENV_SYSTEM_KEY);
- }
-
- // 读取registry.conf文件的配置,构建基础的Configuration对象
- Configuration configuration = (envValue == null) ? new FileConfiguration(seataConfigName + REGISTRY_CONF_SUFFIX,
- false) : new FileConfiguration(seataConfigName + "-" + envValue + REGISTRY_CONF_SUFFIX, false);
- Configuration extConfiguration = null;
- try {
- // ExtConfigurationProvider当前只有一个SpringBootConfigurationProvider实现类
- // 用于支持客户端SDK SpringBoot的配置文件方式,对于Server端来说这段逻辑可以忽略。
- extConfiguration = EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration);
- if (LOGGER.isInfoEnabled()) {
- LOGGER.info("load Configuration:{}", extConfiguration == null ? configuration.getClass().getSimpleName()
- : extConfiguration.getClass().getSimpleName());
- }
- } catch (EnhancedServiceNotFoundException ignore) {
-
- } catch (Exception e) {
- LOGGER.error("failed to load extConfiguration:{}", e.getMessage(), e);
- }
- CURRENT_FILE_INSTANCE = extConfiguration == null ? configuration : extConfiguration;
-}
-```
-
-ConfigurationFactory中的static代码块是从registry.conf中读取配置信息。registry.conf中主有两个配置信息,**注册中心**和**配置源**,**配置源**用来指定其他更详细的配置项是file.conf或者是apollo等其他配置源。所以registry.conf配置文件时必须的,registry.conf配置文件中指定其他详细配置的配置源,当前配置源支持file、zk、apollo、nacos、etcd3等。所以file.conf不是必须的,只有当设置配置源为file类型时才会读取file.conf文件中的内容。
-
-接下来ConfigurationFactory中的buildConfiguration就是根据registry.conf中设置的配置源来加载更多的配置项。
-
-```java
-private static Configuration buildConfiguration() {
- ConfigType configType;
- String configTypeName;
- try {
- // 从registry.conf配置文件中读取config.type字段值,并解析为枚举ConfigType
- configTypeName = CURRENT_FILE_INSTANCE.getConfig(
- ConfigurationKeys.FILE_ROOT_CONFIG + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR
- + ConfigurationKeys.FILE_ROOT_TYPE);
-
- if (StringUtils.isBlank(configTypeName)) {
- throw new NotSupportYetException("config type can not be null");
- }
-
- configType = ConfigType.getType(configTypeName);
- } catch (Exception e) {
- throw e;
- }
- Configuration extConfiguration = null;
- Configuration configuration;
- if (ConfigType.File == configType) {
- // 如果配置文件为file类型,则从registry.conf中读取config.file.name配置项,
- // 即file类型配置文件的路径,示例中默认为file.conf
- String pathDataId = String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR,
- ConfigurationKeys.FILE_ROOT_CONFIG, FILE_TYPE, NAME_KEY);
- String name = CURRENT_FILE_INSTANCE.getConfig(pathDataId);
-
- // 根据file配置文件的路径构建FileConfuguration对象
- configuration = new FileConfiguration(name);
- try {
- // configuration的额外扩展,也是只对客户端SpringBoot的SDK才生效
- extConfiguration = EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration);
- if (LOGGER.isInfoEnabled()) {
- LOGGER.info("load Configuration:{}", extConfiguration == null
- ? configuration.getClass().getSimpleName() : extConfiguration.getClass().getSimpleName());
- }
- } catch (EnhancedServiceNotFoundException ignore) {
-
- } catch (Exception e) {
- LOGGER.error("failed to load extConfiguration:{}", e.getMessage(), e);
- }
- } else {
- // 如果配置文件的类型不是file,如:nacos、zk等,
- // 则通过SPI的方式生成对应的ConfigurationProvider对象
- configuration = EnhancedServiceLoader
- .load(ConfigurationProvider.class, Objects.requireNonNull(configType).name()).provide();
- }
- try {
- // ConfigurationCache是对Configuration做了一次层代理内存缓存,提升获取配置的性能
- Configuration configurationCache;
- if (null != extConfiguration) {
- configurationCache = ConfigurationCache.getInstance().proxy(extConfiguration);
- } else {
- configurationCache = ConfigurationCache.getInstance().proxy(configuration);
- }
- if (null != configurationCache) {
- extConfiguration = configurationCache;
- }
- } catch (EnhancedServiceNotFoundException ignore) {
-
- } catch (Exception e) {
- LOGGER.error("failed to load configurationCacheProvider:{}", e.getMessage(), e);
- }
- return null == extConfiguration ? configuration : extConfiguration;
-}
-```
-
-#### 3. 初始化UUIDGenerator
-UUIDGenertor初始化接收一个serverNode参数,UUIDGenertor当前是使用了雪花算法来生成唯一Id,该serverNode用来保证多个seata-server实例生成的唯一id不重复。
-```java
-public class UUIDGenerator {
-
- /**
- * Generate uuid long.
- *
- * @return the long
- */
- public static long generateUUID() {
- return IdWorker.getInstance().nextId();
- }
-
- /**
- * Init.
- *
- * @param serverNode the server node id
- */
- public static void init(Long serverNode) {
- IdWorker.init(serverNode);
- }
-}
-```
-
-UUIDGenerator是对IdWorker做了封装,唯一id的核心实现逻辑在IdWoker类中,IdWorker是一个雪花算法实现的。此处的IdWorker又是一个单例
-```java
-public class IdWorker
-/**
- * Constructor
- *
- * @param workerId就是上面提到的ServerNode, 取值范围在0·1023,也就是在64位的UUID中占10位
- */
- public IdWorker(long workerId) {
- if (workerId > maxWorkerId || workerId < 0) {
- throw new IllegalArgumentException(
- String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
- }
- this.workerId = workerId;
- }
-
- /**
- * Get the next ID (the method is thread-safe)
- *
- * @return SnowflakeId
- */
- public long nextId() {
- long timestamp = timeGen();
-
- if (timestamp < lastTimestamp) {
- throw new RuntimeException(String.format(
- "clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
- }
-
- synchronized (this) {
- if (lastTimestamp == timestamp) {
- sequence = (sequence + 1) & sequenceMask;
- if (sequence == 0) {
- timestamp = tilNextMillis(lastTimestamp);
- }
- } else {
- sequence = 0L;
- }
- lastTimestamp = timestamp;
- }
- //雪花算法64位唯一id组成:第一位0 + 41位时间戳 + 10位workerId + 12位自增序列化(同一时间戳内自增)
- return ((timestamp - twepoch) << timestampLeftShift) | (workerId << workerIdShift) | sequence;
- }
-```
-
-#### 4. SessionHolder初始化
-SessionHolder负责Session的持久化,一个Session对象对应一个事务,事务分为两种:全局事务(GlobalSession)和分支事务(BranchSession)。
-SessionHolder支持file和db两种持久化方式,其中db支持集群模式,推荐使用db。SessionHolder中最主要的四个字段如下:
-```java
-// ROOT_SESSION_MANAGER用于获取所有的Setssion,以及Session的创建、更新、删除等。
-private static SessionManager ROOT_SESSION_MANAGER;
-// 用于获取、更新所有的异步commit的Session
-private static SessionManager ASYNC_COMMITTING_SESSION_MANAGER;
-// 用于获取、更新所有需要重试commit的Session
-private static SessionManager RETRY_COMMITTING_SESSION_MANAGER;
-// 用于获取、更新所有需要重试rollback的Session
-private static SessionManager RETRY_ROLLBACKING_SESSION_MANAGER;
-```
-
-SessionHolder的init方法
-```java
-public static void init(String mode) throws IOException {
- if (StringUtils.isBlank(mode)) {
- mode = CONFIG.getConfig(ConfigurationKeys.STORE_MODE);
- }
- StoreMode storeMode = StoreMode.get(mode);
- if (StoreMode.DB.equals(storeMode)) {
- // 这里又用到了SPI的方式加载SessionManager,
- // 其实下面获取的四个SessionManager实例都是同一个类DataBaseSessionManager的不同实例,
- // 只是给DataBaseSessionManager的构造函数传参不同。
- ROOT_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName());
- ASYNC_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName(),
- new Object[] {ASYNC_COMMITTING_SESSION_MANAGER_NAME});
- RETRY_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName(),
- new Object[] {RETRY_COMMITTING_SESSION_MANAGER_NAME});
- RETRY_ROLLBACKING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName(),
- new Object[] {RETRY_ROLLBACKING_SESSION_MANAGER_NAME});
- } else if (StoreMode.FILE.equals(storeMode)) {
- //file模式可以先不关心
- ...
- } else {
- throw new IllegalArgumentException("unknown store mode:" + mode);
- }
- // reload方法对于db模式可以忽略
- reload();
-}
-```
-
-上面看到SessionHolder中的四个SessionManager本质都是类DataBaseSessionManager的实例,只是给构造函数传参不同,看下DataBaseSessionManager的定义:
-```java
-public DataBaseSessionManager(String name) {
- super();
- this.taskName = name;
-}
-
-// 根据实例的taskName来决定allSessions返回的事务列表,
-// 如taskName等于ASYNC_COMMITTING_SESSION_MANAGER_NAME的
-// 就返回所有状态为AsyncCommitting的事务。
-public Collection allSessions() {
- // get by taskName
- if (SessionHolder.ASYNC_COMMITTING_SESSION_MANAGER_NAME.equalsIgnoreCase(taskName)) {
- return findGlobalSessions(new SessionCondition(GlobalStatus.AsyncCommitting));
- } else if (SessionHolder.RETRY_COMMITTING_SESSION_MANAGER_NAME.equalsIgnoreCase(taskName)) {
- return findGlobalSessions(new SessionCondition(new GlobalStatus[] {GlobalStatus.CommitRetrying}));
- } else if (SessionHolder.RETRY_ROLLBACKING_SESSION_MANAGER_NAME.equalsIgnoreCase(taskName)) {
- return findGlobalSessions(new SessionCondition(new GlobalStatus[] {GlobalStatus.RollbackRetrying,
- GlobalStatus.Rollbacking, GlobalStatus.TimeoutRollbacking, GlobalStatus.TimeoutRollbackRetrying}));
- } else {
- // taskName为null,则对应ROOT_SESSION_MANAGER,即获取所有状态的事务
- return findGlobalSessions(new SessionCondition(new GlobalStatus[] {
- GlobalStatus.UnKnown, GlobalStatus.Begin,
- GlobalStatus.Committing, GlobalStatus.CommitRetrying, GlobalStatus.Rollbacking,
- GlobalStatus.RollbackRetrying,
- GlobalStatus.TimeoutRollbacking,
- GlobalStatus.TimeoutRollbackRetrying,
- GlobalStatus.AsyncCommitting}));
- }
-}
-```
-
-#### 5. 初始化DefaultCoordinator
-DefaultCoordinator是事务协调器的核心,如:开启、提交、回滚全局事务,注册、提交、回滚分支事务都是由DefaultCoordinator负责协调处理的。DefaultCoordinato通过RpcServer与远程的TM、RM通信来实现分支事务的提交、回滚等。
-```java
-public DefaultCoordinator(ServerMessageSender messageSender) {
- // 接口messageSender的实现类就是上文提到的RpcServer
- this.messageSender = messageSender;
-
- // DefaultCore封装了AT、TCC、Saga等分布式事务模式的具体实现类
- this.core = new DefaultCore(messageSender);
-}
-
-// init方法初始化了5个定时器,主要用于分布式事务的重试机制,
-// 因为分布式环境的不稳定性会造成事务处于中间状态,
-// 所以要通过不断的重试机制来实现事务的最终一致性。
-// 下面的定时器除了undoLogDelete之外,其他的定时任务默认都是1秒执行一次。
-public void init() {
- // 处理处于回滚状态可重试的事务
- retryRollbacking.scheduleAtFixedRate(() -> {
- try {
- handleRetryRollbacking();
- } catch (Exception e) {
- LOGGER.info("Exception retry rollbacking ... ", e);
- }
- }, 0, ROLLBACKING_RETRY_PERIOD, TimeUnit.MILLISECONDS);
-
- // 处理二阶段可以重试提交的状态可重试的事务
- retryCommitting.scheduleAtFixedRate(() -> {
- try {
- handleRetryCommitting();
- } catch (Exception e) {
- LOGGER.info("Exception retry committing ... ", e);
- }
- }, 0, COMMITTING_RETRY_PERIOD, TimeUnit.MILLISECONDS);
-
- // 处理异步提交的事务
- asyncCommitting.scheduleAtFixedRate(() -> {
- try {
- handleAsyncCommitting();
- } catch (Exception e) {
- LOGGER.info("Exception async committing ... ", e);
- }
- }, 0, ASYNC_COMMITTING_RETRY_PERIOD, TimeUnit.MILLISECONDS);
-
- // 检查事务的第一阶段已经超时的事务,设置事务状态为TimeoutRollbacking,
- // 该事务会由其他定时任务执行回滚操作
- timeoutCheck.scheduleAtFixedRate(() -> {
- try {
- timeoutCheck();
- } catch (Exception e) {
- LOGGER.info("Exception timeout checking ... ", e);
- }
- }, 0, TIMEOUT_RETRY_PERIOD, TimeUnit.MILLISECONDS);
-
- // 根据unlog的保存天数调用RM删除unlog
- undoLogDelete.scheduleAtFixedRate(() -> {
- try {
- undoLogDelete();
- } catch (Exception e) {
- LOGGER.info("Exception undoLog deleting ... ", e);
- }
- }, UNDO_LOG_DELAY_DELETE_PERIOD, UNDO_LOG_DELETE_PERIOD, TimeUnit.MILLISECONDS);
-}
-```
-
-#### 6. 初始化NettyRemotingServer
-NettyRemotingServer是基于Netty实现的简化版的Rpc服务端,NettyRemotingServer初始化时主要做了两件事:
-1. **registerProcessor**:注册与Client通信的Processor。
-2. **super.init()**:super.init()方法中负责初始化Netty,并把当前实例的IP端口注册到注册中心中
-
-```java
-public void init() {
- // registry processor
- registerProcessor();
- if (initialized.compareAndSet(false, true)) {
- super.init();
- }
-}
-
-private void registerProcessor() {
- // 1. 注册核心的ServerOnRequestProcessor,即与事务处理相关的Processor,
- // 如:全局事务开始、提交,分支事务注册、反馈当前状态等。
- // ServerOnRequestProcessor的构造函数中传入getHandler()返回的示例,这个handler
- // 就是前面提到的DefaultCoordinator,DefaultCoordinator是分布式事务的核心处理类
- ServerOnRequestProcessor onRequestProcessor =
- new ServerOnRequestProcessor(this, getHandler());
- super.registerProcessor(MessageType.TYPE_BRANCH_REGISTER, onRequestProcessor, messageExecutor);
- super.registerProcessor(MessageType.TYPE_BRANCH_STATUS_REPORT, onRequestProcessor, messageExecutor);
- super.registerProcessor(MessageType.TYPE_GLOBAL_BEGIN, onRequestProcessor, messageExecutor);
- super.registerProcessor(MessageType.TYPE_GLOBAL_COMMIT, onRequestProcessor, messageExecutor);
- super.registerProcessor(MessageType.TYPE_GLOBAL_LOCK_QUERY, onRequestProcessor, messageExecutor);
- super.registerProcessor(MessageType.TYPE_GLOBAL_REPORT, onRequestProcessor, messageExecutor);
- super.registerProcessor(MessageType.TYPE_GLOBAL_ROLLBACK, onRequestProcessor, messageExecutor);
- super.registerProcessor(MessageType.TYPE_GLOBAL_STATUS, onRequestProcessor, messageExecutor);
- super.registerProcessor(MessageType.TYPE_SEATA_MERGE, onRequestProcessor, messageExecutor);
-
- // 2.注册ResponseProcessor,ResponseProcessor用于处理当Server端主动发起请求时,
- // Client端回复的消息,即Response。如:Server向Client端发送分支事务提交或者回滚的请求时,
- // Client返回提交/回滚的结果
- ServerOnResponseProcessor onResponseProcessor =
- new ServerOnResponseProcessor(getHandler(), getFutures());
- super.registerProcessor(MessageType.TYPE_BRANCH_COMMIT_RESULT, onResponseProcessor, messageExecutor);
- super.registerProcessor(MessageType.TYPE_BRANCH_ROLLBACK_RESULT, onResponseProcessor, messageExecutor);
-
- // 3. Client端发起RM注册请求时对应的Processor
- RegRmProcessor regRmProcessor = new RegRmProcessor(this);
- super.registerProcessor(MessageType.TYPE_REG_RM, regRmProcessor, messageExecutor);
-
- // 4. Client端发起TM注册请求时对应的Processor
- RegTmProcessor regTmProcessor = new RegTmProcessor(this);
- super.registerProcessor(MessageType.TYPE_REG_CLT, regTmProcessor, null);
-
- // 5. Client端发送心跳请求时对应的Processor
- ServerHeartbeatProcessor heartbeatMessageProcessor = new ServerHeartbeatProcessor(this);
- super.registerProcessor(MessageType.TYPE_HEARTBEAT_MSG, heartbeatMessageProcessor, null);
-}
-```
-
-在NettyRemotingServer中有调用基类AbstractNettyRemotingServer的init方法,代码如下:
-```java
-public void init() {
- // super.init()方法中启动了一个定时清理超时Rpc请求的定时任务,3S执行一次。
- super.init();
- // 配置Netty Server端,开始监听端口。
- serverBootstrap.start();
-}
-
-// serverBootstrap.start();
-public void start() {
- // Netty server端的常规配置,其中添加了两个ChannelHandler:
- // ProtocolV1Decoder、ProtocolV1Encoder,
- // 分别对应Seata自定义RPC协议的解码器和编码器
- this.serverBootstrap.group(this.eventLoopGroupBoss, this.eventLoopGroupWorker)
- .channel(NettyServerConfig.SERVER_CHANNEL_CLAZZ)
- .option(ChannelOption.SO_BACKLOG, nettyServerConfig.getSoBackLogSize())
- .option(ChannelOption.SO_REUSEADDR, true)
- .childOption(ChannelOption.SO_KEEPALIVE, true)
- .childOption(ChannelOption.TCP_NODELAY, true)
- .childOption(ChannelOption.SO_SNDBUF, nettyServerConfig.getServerSocketSendBufSize())
- .childOption(ChannelOption.SO_RCVBUF, nettyServerConfig.getServerSocketResvBufSize())
- .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK,
- new WriteBufferWaterMark(nettyServerConfig.getWriteBufferLowWaterMark(),
- nettyServerConfig.getWriteBufferHighWaterMark()))
- .localAddress(new InetSocketAddress(listenPort))
- .childHandler(new ChannelInitializer() {
- @Override
- public void initChannel(SocketChannel ch) {
- ch.pipeline().addLast(new IdleStateHandler(nettyServerConfig.getChannelMaxReadIdleSeconds(), 0, 0))
- .addLast(new ProtocolV1Decoder())
- .addLast(new ProtocolV1Encoder());
- if (channelHandlers != null) {
- addChannelPipelineLast(ch, channelHandlers);
- }
-
- }
- });
-
- try {
- // 开始监听配置的端口
- ChannelFuture future = this.serverBootstrap.bind(listenPort).sync();
- LOGGER.info("Server started, listen port: {}", listenPort);
- // Netty启动成功之后把当前实例注册到registry.conf配置文件配置的注册中心上
- RegistryFactory.getInstance().register(new InetSocketAddress(XID.getIpAddress(), XID.getPort()));
- initialized.set(true);
- future.channel().closeFuture().sync();
- } catch (Exception exx) {
- throw new RuntimeException(exx);
- }
-}
-```
+Placeholder. DO NOT DELETE.
\ No newline at end of file
diff --git a/blog/seata-spring-boot-aop-aspectj.md b/blog/seata-spring-boot-aop-aspectj.md
index afc177dda1..e872b67af1 100644
--- a/blog/seata-spring-boot-aop-aspectj.md
+++ b/blog/seata-spring-boot-aop-aspectj.md
@@ -1,163 +1 @@
----
-title: 通过AOP动态创建/关闭Seata分布式事务
-keywords: [Seata,Nacos,分布式事务,spring]
-description: 本文讲述如何通过AOP动态创建/关闭Seata分布式事务
-author: FUNKYE
-date: 2019/12/23
----
-
-# 通过AOP动态创建/关闭Seata分布式事务
-
-本文作者:FUNKYE(陈健斌),杭州某互联网公司主程。
-
-# 前言
-
-通过GA大会上滴滴出行的高级研发工程陈鹏志的在滴滴两轮车业务中的实践,发现动态降级的必要性是非常的高,所以这边简单利用spring boot aop来简单的处理降级相关的处理,这边非常感谢陈鹏志的分享!
-
-可利用此demo[项目地址](https://gitee.com/itCjb/springboot-dubbo-mybatisplus-seata )
-
-通过以下代码改造实践.
-
-## 准备工作
-
- 1.创建测试用的TestAspect:
-
-```java
-package org.test.config;
-
-import java.lang.reflect.Method;
-
-import org.apache.commons.lang3.StringUtils;
-import org.aspectj.lang.JoinPoint;
-import org.aspectj.lang.annotation.AfterReturning;
-import org.aspectj.lang.annotation.AfterThrowing;
-import org.aspectj.lang.annotation.Aspect;
-import org.aspectj.lang.annotation.Before;
-import org.aspectj.lang.reflect.MethodSignature;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.stereotype.Component;
-
-import io.seata.core.context.RootContext;
-import io.seata.core.exception.TransactionException;
-import io.seata.tm.api.GlobalTransaction;
-import io.seata.tm.api.GlobalTransactionContext;
-
-@Aspect
-@Component
-public class TestAspect {
- private final static Logger logger = LoggerFactory.getLogger(TestAspect.class);
-
- @Before("execution(* org.test.service.*.*(..))")
- public void before(JoinPoint joinPoint) throws TransactionException {
- MethodSignature signature = (MethodSignature)joinPoint.getSignature();
- Method method = signature.getMethod();
- logger.info("拦截到需要分布式事务的方法," + method.getName());
- // 此处可用redis或者定时任务来获取一个key判断是否需要关闭分布式事务
- // 模拟动态关闭分布式事务
- if ((int)(Math.random() * 100) % 2 == 0) {
- GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
- tx.begin(300000, "test-client");
- } else {
- logger.info("关闭分布式事务");
- }
- }
-
- @AfterThrowing(throwing = "e", pointcut = "execution(* org.test.service.*.*(..))")
- public void doRecoveryActions(Throwable e) throws TransactionException {
- logger.info("方法执行异常:{}", e.getMessage());
- if (!StringUtils.isBlank(RootContext.getXID()))
- GlobalTransactionContext.reload(RootContext.getXID()).rollback();
- }
-
- @AfterReturning(value = "execution(* org.test.service.*.*(..))", returning = "result")
- public void afterReturning(JoinPoint point, Object result) throws TransactionException {
- logger.info("方法执行结束:{}", result);
- if ((Boolean)result) {
- if (!StringUtils.isBlank(RootContext.getXID())) {
- logger.info("分布式事务Id:{}", RootContext.getXID());
- GlobalTransactionContext.reload(RootContext.getXID()).commit();
- }
- }
- }
-
-}
-```
-
-请注意上面的包名可改为你自己的service包名:
-
- 2.改动service代码:
-
-```java
- public Object seataCommit() {
- testService.Commit();
- return true;
- }
-```
-
-因为异常跟返回结果我们都会拦截,所以这边可以trycatch或者直接让他抛异常来拦截也行,或者直接判断返回结果,比如你的业务代码code=200为成功,那么就commit,反之在拦截返回值那段代码加上rollback;
-
-# 进行调试
-
- 1.更改代码主动抛出异常
-
-```java
- public Object seataCommit() {
- try {
- testService.Commit();
- int i = 1 / 0;
- return true;
- } catch (Exception e) {
- // TODO: handle exception
- throw new RuntimeException();
- }
- }
-```
-
- 查看日志:
-
-```java
-2019-12-23 11:57:55.386 INFO 23952 --- [.0-28888-exec-7] org.test.controller.TestController : 拦截到需要分布式事务的方法,seataCommit
-2019-12-23 11:57:55.489 INFO 23952 --- [.0-28888-exec-7] i.seata.tm.api.DefaultGlobalTransaction : Begin new global transaction [192.168.14.67:8092:2030765910]
-2019-12-23 11:57:55.489 INFO 23952 --- [.0-28888-exec-7] org.test.controller.TestController : 创建分布式事务完毕192.168.14.67:8092:2030765910
-2019-12-23 11:57:55.709 INFO 23952 --- [.0-28888-exec-7] org.test.controller.TestController : 方法执行异常:null
-2019-12-23 11:57:55.885 INFO 23952 --- [.0-28888-exec-7] i.seata.tm.api.DefaultGlobalTransaction : [192.168.14.67:8092:2030765910] rollback status: Rollbacked
-2019-12-23 11:57:55.888 ERROR 23952 --- [.0-28888-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException] with root cause
-
-```
-
- 可以看到已被拦截也触发了rollback了.
-
- 2.恢复代码调试正常情况:
-
-```java
- public Object seataCommit() {
- testService.Commit();
- return true;
- }
-```
-
- 查看日志:
-
-```
-2019-12-23 12:00:20.876 INFO 23952 --- [.0-28888-exec-2] org.test.controller.TestController : 拦截到需要分布式事务的方法,seataCommit
-2019-12-23 12:00:20.919 INFO 23952 --- [.0-28888-exec-2] i.seata.tm.api.DefaultGlobalTransaction : Begin new global transaction [192.168.14.67:8092:2030765926]
-2019-12-23 12:00:20.920 INFO 23952 --- [.0-28888-exec-2] org.test.controller.TestController : 创建分布式事务完毕192.168.14.67:8092:2030765926
-2019-12-23 12:00:21.078 INFO 23952 --- [.0-28888-exec-2] org.test.controller.TestController : 方法执行结束:true
-2019-12-23 12:00:21.078 INFO 23952 --- [.0-28888-exec-2] org.test.controller.TestController : 分布式事务Id:192.168.14.67:8092:2030765926
-2019-12-23 12:00:21.213 INFO 23952 --- [.0-28888-exec-2] i.seata.tm.api.DefaultGlobalTransaction : [192.168.14.67:8092:2030765926] commit status: Committed
-```
-
- 可以看到事务已经被提交了.
-
-# 总结
-
-更详细的内容希望希望大家访问以下地址阅读详细文档
-
-[nacos官网](https://nacos.io/zh-cn/index.html)
-
-[dubbo官网](http://dubbo.apache.org/en-us/)
-
-[seata官网](http://seata.io/zh-cn/)
-
-[docker官网](https://www.docker.com/)
\ No newline at end of file
+Placeholder. DO NOT DELETE.
\ No newline at end of file
diff --git a/blog/seata-tcc-fence.md b/blog/seata-tcc-fence.md
index 1901a81456..e872b67af1 100644
--- a/blog/seata-tcc-fence.md
+++ b/blog/seata-tcc-fence.md
@@ -1,336 +1 @@
----
-title: 阿里 Seata 新版本终于解决了 TCC 模式的幂等、悬挂和空回滚问题
-
-author: 朱晋君
-
-keywords: [Seata、TCC、幂等、悬挂、空回滚]
-
-description: Seata 在 1.5.1 版本解决了 TCC 模式的幂等、悬挂和空回滚问题,这篇文章主要讲解 Seata 是怎么解决的。
-
-date: 2022/06/25
----
-今天来聊一聊阿里巴巴 Seata 新版本(1.5.1)是怎么解决 TCC 模式下的幂等、悬挂和空回滚问题的。
-
-## 1 TCC 回顾
-
-TCC 模式是最经典的分布式事务解决方案,它将分布式事务分为两个阶段来执行,try 阶段对每个分支事务进行预留资源,如果所有分支事务都预留资源成功,则进入 commit 阶段提交全局事务,如果有一个节点预留资源失败则进入 cancel 阶段回滚全局事务。
-
-以传统的订单、库存、账户服务为例,在 try 阶段尝试预留资源,插入订单、扣减库存、扣减金额,这三个服务都是要提交本地事务的,这里可以把资源转入中间表。在 commit 阶段,再把 try 阶段预留的资源转入最终表。而在 cancel 阶段,把 try 阶段预留的资源进行释放,比如把账户金额返回给客户的账户。
-
-**注意:try 阶段必须是要提交本地事务的,比如扣减订单金额,必须把钱从客户账户扣掉,如果不扣掉,在 commit 阶段客户账户钱不够了,就会出问题。**
-
-### 1.1 try-commit
-
-try 阶段首先进行预留资源,然后在 commit 阶段扣除资源。如下图:
-
-![fence-try-commit](/img/blog/fence-try-commit.png)
-
-
-### 1.2 try-cancel
-
-try 阶段首先进行预留资源,预留资源时扣减库存失败导致全局事务回滚,在 cancel 阶段释放资源。如下图:
-
-![fence-try-cancel](/img/blog/fence-try-cancel.png)
-
-
-## 2 TCC 优势
-
-TCC 模式最大的优势是效率高。TCC 模式在 try 阶段的锁定资源并不是真正意义上的锁定,而是真实提交了本地事务,将资源预留到中间态,并不需要阻塞等待,因此效率比其他模式要高。
-
-同时 TCC 模式还可以进行如下优化:
-
-### 2.1 异步提交
-
-try 阶段成功后,不立即进入 confirm/cancel 阶段,而是认为全局事务已经结束了,启动定时任务来异步执行 confirm/cancel,扣减或释放资源,这样会有很大的性能提升。
-
-### 2.2 同库模式
-
-TCC 模式中有三个角色:
-
-- TM:管理全局事务,包括开启全局事务,提交/回滚全局事务;
-- RM:管理分支事务;
-- TC: 管理全局事务和分支事务的状态。
-
-下图来自 Seata 官网:
-
-![fence-fiffrent-db](/img/blog/fence-fiffrent-db.png)
-
-TM 开启全局事务时,RM 需要向 TC 发送注册消息,TC 保存分支事务的状态。TM 请求提交或回滚时,TC 需要向 RM 发送提交或回滚消息。这样包含两个个分支事务的分布式事务中,TC 和 RM 之间有四次 RPC。
-
-优化后的流程如下图:
-
-![fence-same-db](/img/blog/fence-same-db.png)
-
-
-TC 保存全局事务的状态。TM 开启全局事务时,RM 不再需要向 TC 发送注册消息,而是把分支事务状态保存在了本地。TM 向 TC 发送提交或回滚消息后,RM 异步线程首先查出本地保存的未提交分支事务,然后向 TC 发送消息获取(本地分支事务)所在的全局事务状态,以决定是提交还是回滚本地事务。
-
-这样优化后,RPC 次数减少了 50%,性能大幅提升。
-
-## 3 RM 代码示例
-
-以库存服务为例,RM 库存服务接口代码如下:
-```Java
-@LocalTCC
-public interface StorageService {
-
- /**
- * 扣减库存
- * @param xid 全局xid
- * @param productId 产品id
- * @param count 数量
- * @return
- */
- @TwoPhaseBusinessAction(name = "storageApi", commitMethod = "commit", rollbackMethod = "rollback", useTCCFence = true)
- boolean decrease(String xid, Long productId, Integer count);
-
- /**
- * 提交事务
- * @param actionContext
- * @return
- */
- boolean commit(BusinessActionContext actionContext);
-
- /**
- * 回滚事务
- * @param actionContext
- * @return
- */
- boolean rollback(BusinessActionContext actionContext);
-}
-```
-
-通过 @LocalTCC 这个注解,RM 初始化的时候会向 TC 注册一个分支事务。在 try 阶段的方法(decrease方法)上有一个 @TwoPhaseBusinessAction 注解,这里定义了分支事务的 resourceId,commit 方法和 cancel 方法,useTCCFence 这个属性下一节再讲。
-
-## 4 TCC 存在问题
-
-TCC 模式中存在的三大问题是幂等、悬挂和空回滚。在 Seata1.5.1 版本中,增加了一张事务控制表,表名是 tcc_fence_log 来解决这个问题。而在上一节 @TwoPhaseBusinessAction 注解中提到的属性 useTCCFence 就是来指定是否开启这个机制,这个属性值默认是 false。
-
-tcc_fence_log 建表语句如下(MySQL 语法):
-```SQL
-CREATE TABLE IF NOT EXISTS `tcc_fence_log`
-(
- `xid` VARCHAR(128) NOT NULL COMMENT 'global id',
- `branch_id` BIGINT NOT NULL COMMENT 'branch id',
- `action_name` VARCHAR(64) NOT NULL COMMENT 'action name',
- `status` TINYINT NOT NULL COMMENT 'status(tried:1;committed:2;rollbacked:3;suspended:4)',
- `gmt_create` DATETIME(3) NOT NULL COMMENT 'create time',
- `gmt_modified` DATETIME(3) NOT NULL COMMENT 'update time',
- PRIMARY KEY (`xid`, `branch_id`),
- KEY `idx_gmt_modified` (`gmt_modified`),
- KEY `idx_status` (`status`)
-) ENGINE = InnoDB
-DEFAULT CHARSET = utf8mb4;
-```
-
-### 4.1 幂等
-
-在 commit/cancel 阶段,因为 TC 没有收到分支事务的响应,需要进行重试,这就要分支事务支持幂等。
-
-我们看一下新版本是怎么解决的。下面的代码在 TCCResourceManager 类:
-```Java
-@Override
-public BranchStatus branchCommit(BranchType branchType, String xid, long branchId, String resourceId,
- String applicationData) throws TransactionException {
- TCCResource tccResource = (TCCResource)tccResourceCache.get(resourceId);
- //省略判断
- Object targetTCCBean = tccResource.getTargetBean();
- Method commitMethod = tccResource.getCommitMethod();
- //省略判断
- try {
- //BusinessActionContext
- BusinessActionContext businessActionContext = getBusinessActionContext(xid, branchId, resourceId,
- applicationData);
- Object[] args = this.getTwoPhaseCommitArgs(tccResource, businessActionContext);
- Object ret;
- boolean result;
- //注解 useTCCFence 属性是否设置为 true
- if (Boolean.TRUE.equals(businessActionContext.getActionContext(Constants.USE_TCC_FENCE))) {
- try {
- result = TCCFenceHandler.commitFence(commitMethod, targetTCCBean, xid, branchId, args);
- } catch (SkipCallbackWrapperException | UndeclaredThrowableException e) {
- throw e.getCause();
- }
- } else {
- //省略逻辑
- }
- LOGGER.info("TCC resource commit result : {}, xid: {}, branchId: {}, resourceId: {}", result, xid, branchId, resourceId);
- return result ? BranchStatus.PhaseTwo_Committed : BranchStatus.PhaseTwo_CommitFailed_Retryable;
- } catch (Throwable t) {
- //省略
- return BranchStatus.PhaseTwo_CommitFailed_Retryable;
- }
-}
-```
-上面的代码可以看到,执行分支事务提交方法时,首先判断 useTCCFence 属性是否为 true,如果为 true,则走 TCCFenceHandler 类中的 commitFence 逻辑,否则走普通提交逻辑。
-
-TCCFenceHandler 类中的 commitFence 方法调用了 TCCFenceHandler 类的 commitFence 方法,代码如下:
-```Java
-public static boolean commitFence(Method commitMethod, Object targetTCCBean,
- String xid, Long branchId, Object[] args) {
- return transactionTemplate.execute(status -> {
- try {
- Connection conn = DataSourceUtils.getConnection(dataSource);
- TCCFenceDO tccFenceDO = TCC_FENCE_DAO.queryTCCFenceDO(conn, xid, branchId);
- if (tccFenceDO == null) {
- throw new TCCFenceException(String.format("TCC fence record not exists, commit fence method failed. xid= %s, branchId= %s", xid, branchId),
- FrameworkErrorCode.RecordAlreadyExists);
- }
- if (TCCFenceConstant.STATUS_COMMITTED == tccFenceDO.getStatus()) {
- LOGGER.info("Branch transaction has already committed before. idempotency rejected. xid: {}, branchId: {}, status: {}", xid, branchId, tccFenceDO.getStatus());
- return true;
- }
- if (TCCFenceConstant.STATUS_ROLLBACKED == tccFenceDO.getStatus() || TCCFenceConstant.STATUS_SUSPENDED == tccFenceDO.getStatus()) {
- if (LOGGER.isWarnEnabled()) {
- LOGGER.warn("Branch transaction status is unexpected. xid: {}, branchId: {}, status: {}", xid, branchId, tccFenceDO.getStatus());
- }
- return false;
- }
- return updateStatusAndInvokeTargetMethod(conn, commitMethod, targetTCCBean, xid, branchId, TCCFenceConstant.STATUS_COMMITTED, status, args);
- } catch (Throwable t) {
- status.setRollbackOnly();
- throw new SkipCallbackWrapperException(t);
- }
- });
-}
-```
-
-从代码中可以看到,提交事务时首先会判断 tcc_fence_log 表中是否已经有记录,如果有记录,则判断事务执行状态并返回。这样如果判断到事务的状态已经是 STATUS_COMMITTED,就不会再次提交,保证了幂等。如果 tcc_fence_log 表中没有记录,则插入一条记录,供后面重试时判断。
-
-Rollback 的逻辑跟 commit 类似,逻辑在类 TCCFenceHandler 的 rollbackFence 方法。
-
-### 4.2 空回滚
-
-如下图,账户服务是两个节点的集群,在 try 阶段账户服务 1 这个节点发生了故障,try 阶段在不考虑重试的情况下,全局事务必须要走向结束状态,这样就需要在账户服务上执行一次 cancel 操作。
-
-![fence-empty-rollback](/img/blog/fence-empty-rollback.png)
-
-
-Seata 的解决方案是在 try 阶段 往 tcc_fence_log 表插入一条记录,status 字段值是 STATUS_TRIED,在 Rollback 阶段判断记录是否存在,如果不存在,则不执行回滚操作。代码如下:
-```Java
-//TCCFenceHandler 类
-public static Object prepareFence(String xid, Long branchId, String actionName, Callback