-
适用场景:Go micro 4或以上
背景
我有一个go micro开发的微服务要引入分布式事务,用的是Saga模式,结果遇到DTM访问gRPC接口报错,返回Unsupported Content-Type: application/grpc+dtm_raw,网上搜了一圈没有任何资料,AI也给不出答案,分布式事务是必不可少的,很明显框架默认不支持DTM但还是要解决。
分析
我们可以通过错误分析得知是不支持application/grpc+dtm_raw这种格式,因为go micro的GRPC Server有个newGRPCCodec方法,源码如下:
go
func (g *grpcServer) newGRPCCodec(contentType string) (encoding.Codec, error) {
codecs := make(map[string]encoding.Codec)
if g.opts.Context != nil {
if v, ok := g.opts.Context.Value(codecsKey{}).(map[string]encoding.Codec); ok && v != nil {
codecs = v
}
}
if c, ok := codecs[contentType]; ok {
return c, nil
}
if c, ok := defaultGRPCCodecs[contentType]; ok {
return c, nil
}
return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType)
}
Content-Type对应的Codec来实现,先检查Context里面有没有对应的编解码器,如果没有就从defaultGRPCCodecs选择一个来解码,还是找不到就报上文提到的错误。这个格式是DTM特有的,defaultGRPCCodecs找不到没有相应的编解码器所以会报错,默认编解码器映射表源码如下:
go
defaultGRPCCodecs = map[string]encoding.Codec{
"application/json": jsonCodec{},
"application/proto": protoCodec{},
"application/protobuf": protoCodec{},
"application/octet-stream": protoCodec{},
"application/grpc": protoCodec{},
"application/grpc+json": jsonCodec{},
"application/grpc+proto": protoCodec{},
"application/grpc+bytes": bytesCodec{},
}
解决方法
由于采用的是GRPC通信,application/grpc+dtm_raw和application/grpc是相通的,所以我们找到protoCodec{}结构体把它的所有方法复制出来放到自己的项目目录下,修改该编解码器的结构体名称,Name方法改成其他名字比如"grpc+dtm_raw",然后在创建GRPC Server的时候注入自定义编解码器,部分代码如下:(重点看grpcserver.Codec)这一行。
go
import (
grpcserver "github.com/go-micro/plugins/v4/server/grpc"
"github.com/go-micro/plugins/v4/transport/grpc"
"go-micro.dev/v4"
"go-micro.dev/v4/server"
"time"
)
func newServer(conf *config.SysConfig) micro.Option {
// 注册到Consul
consulRegistry := infrastructure.ConsulRegister(conf.Consul)
return micro.Server(
grpcserver.NewServer(
server.Name(conf.Service.Name),
server.Version(conf.Service.Version),
server.Address(conf.Service.Listen),
server.Transport(grpc.NewTransport()),
server.Registry(consulRegistry),
server.RegisterTTL(time.Duration(conf.Consul.RegisterTtl)*time.Second),
server.RegisterInterval(time.Duration(conf.Consul.RegisterInterval)*time.Second),
grpcserver.Codec("application/grpc+dtm_raw", codec.NewDtmCodec()), // 在这里注入你的Codec,这样框架底层会先从Context里取出这个编解码器来解析DTM发出的消息格式
grpcserver.MaxConn(conf.Service.MaxConn),
grpcserver.MaxMsgSize(conf.Service.MaxMsgSize),
))
}
这样我们就可以在DTM里使用自定义的编解码器正常解析DTM发来的消息,完成分布式事务的所有流程。