250 lines
7.3 KiB
Go
250 lines
7.3 KiB
Go
package mq
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"github.com/apache/rocketmq-client-go/v2"
|
||
"github.com/apache/rocketmq-client-go/v2/primitive"
|
||
"github.com/apache/rocketmq-client-go/v2/producer"
|
||
"go.opentelemetry.io/otel"
|
||
"go.opentelemetry.io/otel/codes"
|
||
"go.opentelemetry.io/otel/propagation"
|
||
semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
|
||
"go.opentelemetry.io/otel/trace"
|
||
"strings"
|
||
)
|
||
|
||
type Producer struct {
|
||
ProducerClient rocketmq.Producer
|
||
IsOpenTelemetry bool // 默认开启链路追踪,及时未配置上报地址也不影响业务,只是实际不会上报上去而已
|
||
}
|
||
|
||
// WithProducerCredentials 设置生产者的凭证
|
||
func WithProducerCredentials(accessKey, secretKey, securityToken string) producer.Option {
|
||
return producer.WithCredentials(primitive.Credentials{
|
||
AccessKey: accessKey,
|
||
SecretKey: secretKey,
|
||
SecurityToken: securityToken,
|
||
})
|
||
|
||
}
|
||
|
||
// NewProducer 创建一个生产者
|
||
// nameServer: 连接地址,多个中间用,号隔开
|
||
// opts: 配置项
|
||
func NewProducer(nameServer string, opts ...producer.Option) (*Producer, error) {
|
||
//检查参数
|
||
if nameServer == "" {
|
||
return nil, fmt.Errorf("rocketMQ NameServer 不能为空")
|
||
}
|
||
//创建生产者
|
||
nameServers := strings.Split(nameServer, ",")
|
||
opts = append(opts, producer.WithNameServer(nameServers))
|
||
p, err := rocketmq.NewProducer(opts...)
|
||
if err != nil {
|
||
fmt.Println("创建 rocketMQ producer 失败: ", err)
|
||
return nil, err
|
||
}
|
||
|
||
//此时并没有发起连接,在使用时才会连接
|
||
return &Producer{ProducerClient: p, IsOpenTelemetry: true}, nil
|
||
}
|
||
|
||
// DisableTelemetry 关闭链路追踪
|
||
func (p *Producer) DisableTelemetry() {
|
||
p.IsOpenTelemetry = false
|
||
}
|
||
|
||
// Start 启动生产者
|
||
func (p *Producer) Start() error {
|
||
return p.ProducerClient.Start()
|
||
}
|
||
|
||
// Shutdown 关闭生产者
|
||
func (p *Producer) Shutdown() error {
|
||
return p.ProducerClient.Shutdown()
|
||
}
|
||
|
||
// SendOption 发送消息选项
|
||
type SendOption func(*primitive.Message)
|
||
|
||
// WithSendKeysOption 设置消息的key
|
||
func WithSendKeysOption(keys []string) SendOption {
|
||
return func(msg *primitive.Message) {
|
||
msg.WithKeys(keys)
|
||
}
|
||
}
|
||
|
||
// WithSendShardingKeysOption 设置消息的key
|
||
func WithSendShardingKeysOption(key string) SendOption {
|
||
return func(msg *primitive.Message) {
|
||
msg.WithShardingKey(key)
|
||
}
|
||
}
|
||
|
||
// WithSendTagOption 设置消息的Tag
|
||
func WithSendTagOption(tag string) SendOption {
|
||
return func(msg *primitive.Message) {
|
||
msg.WithTag(tag)
|
||
}
|
||
}
|
||
|
||
// WithSendDelayLevelOption 设置消息的延迟级别
|
||
// reference delay level definition: 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
|
||
// delay level starts from 1. for example, if we set param level=1, then the delay time is 1s.
|
||
func WithSendDelayLevelOption(level int) SendOption {
|
||
return func(msg *primitive.Message) {
|
||
msg.WithDelayTimeLevel(level)
|
||
}
|
||
}
|
||
|
||
const openTelemetryPropertyName = "traceparent"
|
||
|
||
// WithOpenTelemetryOption 设置消息的链接追踪信息
|
||
func WithOpenTelemetryOption(value string) SendOption {
|
||
return func(msg *primitive.Message) {
|
||
msg.WithProperty(openTelemetryPropertyName, value)
|
||
}
|
||
}
|
||
|
||
// WithSendWithPropertyOption 设置消息的属性
|
||
func WithSendWithPropertyOption(key, value string) SendOption {
|
||
return func(msg *primitive.Message) {
|
||
msg.WithProperty(key, value)
|
||
}
|
||
}
|
||
|
||
// SendSync 同步发送消息
|
||
// topic: 主题
|
||
// sendOptions: 发送选项,如WithSendTagOption
|
||
// bodyList: 支持发送多个
|
||
func (p *Producer) SendSync(ctx context.Context, topic string, body []byte, sendOptions ...SendOption) error {
|
||
return p.BatchSendSync(ctx, topic, sendOptions, body)
|
||
}
|
||
|
||
// SendAsync 异步发送消息
|
||
// topic: 主题
|
||
// sendOptions: 发送选项,如WithSendTagOption
|
||
// callbackFn: 回调函数,无论成功与否都会回调,失败时err!=nil
|
||
// bodyList: 支持发送多个
|
||
func (p *Producer) SendAsync(ctx context.Context, topic string, body []byte, callbackFn func(error), sendOptions ...SendOption) error {
|
||
return p.BatchSendAsync(ctx, topic, callbackFn, sendOptions, body)
|
||
}
|
||
|
||
// BatchSendSync 同步发送消息
|
||
// topic: 主题
|
||
// sendOptions: 发送选项,如WithSendTagOption
|
||
// bodyList: 支持发送多个
|
||
func (p *Producer) BatchSendSync(ctx context.Context, topic string, sendOptions []SendOption, bodyList ...[]byte) error {
|
||
if err := p.checkSend(topic); err != nil {
|
||
return err
|
||
}
|
||
msgList := make([]*primitive.Message, len(bodyList))
|
||
for i, body := range bodyList {
|
||
msgList[i] = &primitive.Message{
|
||
Topic: topic,
|
||
Body: body,
|
||
}
|
||
for _, option := range sendOptions {
|
||
option(msgList[i])
|
||
}
|
||
}
|
||
|
||
// 链路追踪
|
||
var err error
|
||
if p.IsOpenTelemetry {
|
||
_, span := p.generateTraceSpan(ctx, topic, msgList)
|
||
defer func() {
|
||
// 记录错误
|
||
if err != nil {
|
||
span.RecordError(err)
|
||
span.SetStatus(codes.Error, err.Error())
|
||
}
|
||
span.End()
|
||
}()
|
||
}
|
||
|
||
_, err = p.ProducerClient.SendSync(context.Background(), msgList...)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// generateTraceSpan 生成链路追踪的span
|
||
var produceTraceContext = propagation.TraceContext{}
|
||
|
||
func (p *Producer) generateTraceSpan(ctx context.Context, topic string, msgList []*primitive.Message) (context.Context, trace.Span) {
|
||
tracer := otel.GetTracerProvider().Tracer("LSXD_Util")
|
||
spanName := fmt.Sprintf("%s %s", topic, semconv.MessagingOperationPublish.Value.AsString())
|
||
spanCtx, span := tracer.Start(ctx, spanName, trace.WithSpanKind(trace.SpanKindProducer))
|
||
span.SetAttributes(
|
||
semconv.MessagingSystem("RocketMQ"),
|
||
semconv.MessagingDestinationName(topic),
|
||
semconv.MessagingBatchMessageCount(len(msgList)),
|
||
semconv.MessagingRocketmqMessageKeys(msgList[0].GetKeys()),
|
||
semconv.MessagingRocketmqMessageTag(msgList[0].GetTags()),
|
||
)
|
||
//将span的trace数据写入
|
||
carrier := propagation.MapCarrier{}
|
||
produceTraceContext.Inject(spanCtx, carrier)
|
||
traceParent := carrier[openTelemetryPropertyName]
|
||
for _, message := range msgList {
|
||
message.WithProperty(openTelemetryPropertyName, traceParent)
|
||
}
|
||
|
||
return spanCtx, span
|
||
}
|
||
|
||
// BatchSendAsync 异步发送消息
|
||
// topic: 主题
|
||
// callbackFn: 回调方法,无论成功与否都会回调,失败时err!=nil
|
||
// sendOptions: 发送选项,如WithSendTagOption
|
||
// bodyList: 支持发送多个
|
||
func (p *Producer) BatchSendAsync(ctx context.Context, topic string, callbackFn func(error), sendOptions []SendOption, bodyList ...[]byte) error {
|
||
if err := p.checkSend(topic); err != nil {
|
||
return err
|
||
}
|
||
|
||
msgList := make([]*primitive.Message, len(bodyList))
|
||
for i, body := range bodyList {
|
||
msgList[i] = &primitive.Message{
|
||
Topic: topic,
|
||
Body: body,
|
||
}
|
||
for _, option := range sendOptions {
|
||
option(msgList[i])
|
||
}
|
||
}
|
||
|
||
var err error
|
||
var span trace.Span
|
||
if p.IsOpenTelemetry {
|
||
_, span = p.generateTraceSpan(ctx, topic, msgList)
|
||
}
|
||
|
||
err = p.ProducerClient.SendAsync(context.Background(), func(ctxErr context.Context, result *primitive.SendResult, err error) {
|
||
if err != nil {
|
||
span.RecordError(err)
|
||
span.SetStatus(codes.Error, err.Error())
|
||
}
|
||
span.End()
|
||
|
||
callbackFn(err)
|
||
}, msgList...)
|
||
return err
|
||
}
|
||
|
||
// checkTopicName 检查topicName是否符合规范
|
||
// 名字分三部分,由字母和.号组成:${模块名}_${业务名}_${事件},如:trade-order-created
|
||
// 权限:
|
||
func (p *Producer) checkSend(topicName string) error {
|
||
arr := strings.Split(topicName, "_")
|
||
isOk := len(arr) == 3 && arr[0] != "" && arr[1] != "" && arr[2] != ""
|
||
if !isOk {
|
||
return fmt.Errorf("topic名称不符合规范")
|
||
}
|
||
// 检查权限:待完善
|
||
return nil
|
||
}
|