Skip to content

Breaking changes: The v1.10.0 is not fully compatible with the older QueryPrepared result #535

@wooln

Description

@wooln

问题场景

使用csharp的sdk开发MSG模式, 抄sample中QueryPrepared代码实现,当配置的Barrier表连接串有问题(如数据库账号没有写权现),DTM状态误变成commited.

[HttpGet("msg-queryprepared")]
public async Task<IActionResult> MsgMySqlQueryPrepared(CancellationToken cancellationToken)
{
    var bb = _factory.CreateBranchBarrier(Request.Query);
    _logger.LogInformation("bb {0}", bb);
    using (MySqlConnection conn = GetConn())
    {
        var res = await bb.QueryPrepared(conn);

        return Ok(new { dtm_result = res });
    }
}

问题分析

v1.9.1时候,body不包含dtmcli三个参量ResultSuccess、ResultFailure、ResultOngoing的QueryPrepared会当成getting result failed,如http200 body:{ dtm_result: "MySql connection timeout, balabala... " },走touchCronTime。

func (t *TransGlobal) mayQueryPrepared() {
	if !t.needProcess() || t.Status == dtmcli.StatusSubmitted {
		return
	}
	body, err := t.getURLResult(t.QueryPrepared, "00", "msg", nil)
	if strings.Contains(body, dtmcli.ResultSuccess) {
		t.changeStatus(dtmcli.StatusSubmitted)
	} else if strings.Contains(body, dtmcli.ResultFailure) {
		t.changeStatus(dtmcli.StatusFailed)
	} else if strings.Contains(body, dtmcli.ResultOngoing) {
		t.touchCronTime(cronReset)
	} else {
		logger.Errorf("getting result failed for %s. error: %v body %s", t.QueryPrepared, err, body)
		t.touchCronTime(cronBackoff)
	}
}

v1.10.0后使用新的规则升级指南 1.9.x 升级到 1.10.xSDK与服务端判断 SUCCESS http:状态码 200 && 结果不包含 FAILURE|ONGOING; grpc:err == nil,上面http200示例会当成正常,走设置成Submitted。

QueryPrepared的调用链
- func (t *TransGlobal) mayQueryPrepared(ctx context.Context)
- func (t *TransGlobal) getURLResult(ctx context.Context, uri string, branchID, op string, branchPayload []byte) error
- func (t *TransGlobal) getHTTPResult(uri string, branchID, op string, branchPayload []byte) error
- func HTTPResp2DtmError(resp *resty.Response) error {

// HTTPResp2DtmError translate a resty response to error
// compatible with version < v1.10
func HTTPResp2DtmError(resp *resty.Response) error {
	code := resp.StatusCode()
	str := resp.String()
	if code == http.StatusTooEarly || strings.Contains(str, ResultOngoing) {
		return ErrorMessage2Error(str, ErrOngoing)
	} else if code == http.StatusConflict || strings.Contains(str, ResultFailure) {
		return ErrorMessage2Error(str, ErrFailure)
	} else if code != http.StatusOK {
		return errors.New(str)
	}
	return nil
}

因此,1.10.0并没有完全的兼容之前的QueryPrepared返回值。 c# 这边QueryPrepared方法只封装了BranchBarrier实现,没封装到http状态码级别。Sample中也一直按照v1.10.0之前的协议以http200靠body区分状态。
dtmcli-csharp-sample/DtmSample/DtmSample/Controllers/MsgTestController.cs at main · dtm-labs/dtmcli-csharp-sample

[HttpGet("msg-queryprepared")]
public async Task<IActionResult> MsgMySqlQueryPrepared(CancellationToken cancellationToken)
{
    var bb = _factory.CreateBranchBarrier(Request.Query);
    _logger.LogInformation("bb {0}", bb);
    using (MySqlConnection conn = GetConn())
    {
        var res = await bb.QueryPrepared(conn);

        return Ok(new { dtm_result = res });
    }
}

建议

v1.10.0发布到现在的时间跨度已经很大,已经无法做到两面都兼容,建议:

  1. 修改升级指南升级指南 1.9.x 升级到 1.10.x, 描述好这次breaking changes 0e59ccb ("change to new error protocal ok", 2022-01-10)。双向兼容仅限go sdk,其他需要自己核实

    新版本的SDK可以和新旧版本的协议兼容;新版本的服务器能够与新旧版本的协议兼容。因此理论上可以随意升级。

  2. 开发文档里明确接口调用返回值规则
  3. .net这边,封装QueryPrepared服务到http状态码的转换,并修改Sample, 避免更多人抄错
  4. 其他语言的SDK也有必要核实有没有这种状况,正常跑不会出现问题

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions