diff --git a/Dockerfile b/Dockerfile
index eaf5232..6082eb6 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM daocloud.io/atsctoo/golang:1.15
+FROM golang:1.15
WORKDIR /app/dtm
RUN go env -w GO111MODULE=on
RUN go env -w GOPROXY=https://goproxy.io,direct
diff --git a/README.md b/README.md
index fd3ccd3..d00f7e0 100644
--- a/README.md
+++ b/README.md
@@ -23,19 +23,19 @@
* 一站式解决分布式事务需求
- 支持TCC、SAGA、XA、事务消息、可靠消息
* 支持跨语言栈事务
- - 适合多语言栈的公司使用。支持Go、Python、PHP、Nodejs、Java、c# 各类语言SDK。
+ - 支持多语言栈混合的分布式事务。支持Go、Python、PHP、Nodejs、Java、c# 各类语言SDK。
* 自动处理子事务乱序
- 首创子事务屏障技术,框架层自动处理悬挂、空补偿、幂等网络异常问题
* 支持单服务多数据源访问
- 通过子事务屏障技术,保证单服务多数据源访问的一致性,也能保证本地长事务拆分多个子事务后的一致性
* 开箱即用,提供云上服务
- - 支持通用的HTTP、gRPC协议接入,云原生友好。支持Mysql接入。提供测试版本的云上服务,方便新用户测试
+ - 支持通用的HTTP、gRPC协议接入,云原生友好。支持Mysql接入。提供测试版本的云上服务,方便新用户测试接入
## 与其他框架对比
目前开源的分布式事务框架,暂未看到非Java语言有成熟的框架。而Java语言的较多,其中以seata应用最为广泛。
-下面是dtm和seata的主要特性对比:
+下面是DTM和SEATA的主要特性对比:
| 特性| DTM | SEATA |备注|
|:-----:|:----:|:----:|:----:|
@@ -46,7 +46,7 @@
|AT事务|✗|✓|AT与XA类似,性能更好,但有脏回滚|
| SAGA事务 |简单模式 |状态机复杂模式 |dtm的状态机模式在规划中|
|事务消息|✓|✗|dtm提供类似rocketmq的事务消息|
-|通信协议|HTTP、GRPC|dubbo等协议,无HTTP||
+|通信协议|HTTP、gRPC|dubbo等协议,无HTTP||
|star数量|
|
|dtm从20210604发布0.1,发展快|
从上面对比的特性来看,如果您的语言栈包含了Java之外的语言,那么dtm是您的首选。如果您的语言栈是Java,您也可以选择接入dtm,使用子事务屏障技术,简化您的业务编写。
diff --git a/common/types.go b/common/types.go
index 6e26204..9dba224 100644
--- a/common/types.go
+++ b/common/types.go
@@ -125,16 +125,22 @@ func DbGet(conf map[string]string) *DB {
type dtmConfigType struct {
TransCronInterval int64 `yaml:"TransCronInterval"` // 单位秒 当事务等待这个时间之后,还没有变化,则进行一轮处理,包括prepared中的任务和committed的任务
DB map[string]string `yaml:"DB"`
+ DisableLocalhost int64 `yaml:"DisableLocalhost"`
+ RetryLimit int64 `yaml:"RetryLimit"`
}
// DtmConfig 配置
var DtmConfig = dtmConfigType{}
+func getIntEnv(key string, defaultV string) int64 {
+ return int64(dtmcli.MustAtoi(dtmcli.OrString(os.Getenv(key), defaultV)))
+}
+
func init() {
if len(os.Args) == 1 {
return
}
- DtmConfig.TransCronInterval = int64(dtmcli.MustAtoi(dtmcli.OrString(os.Getenv("TRANS_CRON_INTERVAL"), "10")))
+ DtmConfig.TransCronInterval = getIntEnv("TRANS_CRON_INTERVAL", "10")
DtmConfig.DB = map[string]string{
"driver": dtmcli.OrString(os.Getenv("DB_DRIVER"), "mysql"),
"host": os.Getenv("DB_HOST"),
@@ -142,6 +148,8 @@ func init() {
"user": os.Getenv("DB_USER"),
"password": os.Getenv("DB_PASSWORD"),
}
+ DtmConfig.DisableLocalhost = getIntEnv("DISABLE_LOCALHOST", "0")
+ DtmConfig.RetryLimit = getIntEnv("RETRY_LIMIT", "2000000000")
cont := []byte{}
for d := MustGetwd(); d != "" && d != "/"; d = filepath.Dir(d) {
cont1, err := ioutil.ReadFile(d + "/conf.yml")
diff --git a/dtmsvr/api_http.go b/dtmsvr/api_http.go
index 81f207d..45f22bf 100644
--- a/dtmsvr/api_http.go
+++ b/dtmsvr/api_http.go
@@ -16,6 +16,7 @@ func addRoute(engine *gin.Engine) {
engine.POST("/api/dtmsvr/registerTccBranch", common.WrapHandler(registerTccBranch))
engine.POST("/api/dtmsvr/abort", common.WrapHandler(abort))
engine.GET("/api/dtmsvr/query", common.WrapHandler(query))
+ engine.GET("/api/dtmsvr/all", common.WrapHandler(all))
engine.GET("/api/dtmsvr/newGid", common.WrapHandler(newGid))
}
@@ -74,3 +75,14 @@ func query(c *gin.Context) (interface{}, error) {
db.Must().Where("gid", gid).Find(&branches)
return M{"transaction": trans, "branches": branches}, nil
}
+
+func all(c *gin.Context) (interface{}, error) {
+ lastId := c.Query("last_id")
+ if lastId == "" {
+ lastId = "2000000000"
+ }
+ lid := dtmcli.MustAtoi(lastId)
+ trans := []TransGlobal{}
+ dbGet().Must().Where("id < ?", lid).Order("id desc").Limit(100).Find(&trans)
+ return M{"transactions": trans}, nil
+}
diff --git a/dtmsvr/trans.go b/dtmsvr/trans.go
index 29bed06..00926c1 100644
--- a/dtmsvr/trans.go
+++ b/dtmsvr/trans.go
@@ -2,6 +2,7 @@ package dtmsvr
import (
"context"
+ "errors"
"fmt"
"strings"
"time"
@@ -222,6 +223,7 @@ func (t *TransGlobal) saveNew(db *common.DB) {
branches := t.getProcessor().GenBranches()
if len(branches) > 0 {
writeTransLog(t.Gid, "save branches", t.Status, "", dtmcli.MustMarshalString(branches))
+ checkLocalhost(branches)
db.Must().Clauses(clause.OnConflict{
DoNothing: true,
}).Create(&branches)
@@ -271,3 +273,14 @@ func TransFromDb(db *common.DB, gid string) *TransGlobal {
e2p(dbr.Error)
return &m
}
+
+func checkLocalhost(branches []TransBranch) {
+ if config.DisableLocalhost == 0 {
+ return
+ }
+ for _, branch := range branches {
+ if strings.HasPrefix(branch.URL, "http://localhost") || strings.HasPrefix(branch.URL, "localhost") {
+ panic(errors.New("url for localhost is disabled. check for your config"))
+ }
+ }
+}
diff --git a/dtmsvr/utils_test.go b/dtmsvr/utils_test.go
index 09576d3..f212a4a 100644
--- a/dtmsvr/utils_test.go
+++ b/dtmsvr/utils_test.go
@@ -19,3 +19,16 @@ func TestUtils(t *testing.T) {
CronExpiredTrans(1)
sleepCronTime(10)
}
+
+func TestCheckLocalHost(t *testing.T) {
+ config.DisableLocalhost = 1
+ err := dtmcli.CatchP(func() {
+ checkLocalhost([]TransBranch{{URL: "http://localhost"}})
+ })
+ assert.Error(t, err)
+ config.DisableLocalhost = 0
+ err = dtmcli.CatchP(func() {
+ checkLocalhost([]TransBranch{{URL: "http://localhost"}})
+ })
+ assert.Nil(t, err)
+}
diff --git a/helper/compose.cloud.yml b/helper/compose.cloud.yml
new file mode 100644
index 0000000..026fedc
--- /dev/null
+++ b/helper/compose.cloud.yml
@@ -0,0 +1,25 @@
+version: '3.3'
+services:
+ api:
+ build: .
+ environment:
+ IS_DOCKER: 1
+ DISABLE_LOCALHOST: 1
+ RETRY_LIMIT: 6
+ ports:
+ - '9080:8080'
+ volumes:
+ - .:/app/dtm
+ mysql:
+ image: 'mysql:5.7'
+ environment:
+ MYSQL_ALLOW_EMPTY_PASSWORD: 1
+ TZ: Asia/shanghai
+ command:
+ [
+ '--character-set-server=utf8mb4',
+ '--collation-server=utf8mb4_unicode_ci',
+ '--default-time-zone=+8:00',
+ ]
+ ports:
+ - '3306:3306'
diff --git a/test/dtmsvr_test.go b/test/dtmsvr_test.go
index 3577cc8..7da0b73 100644
--- a/test/dtmsvr_test.go
+++ b/test/dtmsvr_test.go
@@ -105,6 +105,9 @@ func transQuery(t *testing.T, gid string) {
dtmcli.MustUnmarshalString(resp.String(), &m)
assert.Equal(t, nil, m["transaction"])
assert.Equal(t, 0, len(m["branches"].([]interface{})))
+
+ resp, err = dtmcli.RestyClient.R().Get(examples.DtmServer + "/all")
+ assert.Nil(t, err)
}
func TestSqlDB(t *testing.T) {