You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

191 lines
4.6 KiB

package mq
import (
"context"
"github.com/google/uuid"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
"regexp"
"sonet/pkg/utils/logger"
"sync"
"time"
)
var (
uuidPattern = regexp.MustCompile("^\\w+(-\\w+){4}$")
)
// NatsJetStreamProducer use for msg ack, msg resend,
type NatsJetStreamProducer struct {
options nats.Options
nc *nats.Conn
js jetstream.JetStream
}
func NewNatsJetStreamProducer(options nats.Options) *NatsJetStreamProducer {
return &NatsJetStreamProducer{
options: options,
}
}
func (mq *NatsJetStreamProducer) Init() error {
nc, err := mq.options.Connect()
if err != nil {
return err
}
mq.nc = nc
mq.js, err = jetstream.New(mq.nc)
return err
}
func (mq *NatsJetStreamProducer) Publish(topic string, msg []byte) error {
// TODO 是否ack性能430倍差距,通过配置确定publish方式,大协程池等待ack的网络io(自动重试,失败通知[后续优化])
//err := mq.nc.Publish(topic, msg)
_, err := mq.js.Publish(context.Background(), topic, msg)
return err
}
func (mq *NatsJetStreamProducer) MultiPublish(topic string, msgs [][]byte) error {
for _, msg := range msgs {
err := mq.Publish(topic, msg)
if err != nil {
return err
}
}
return nil
}
func (mq *NatsJetStreamProducer) DelayPublish(topic string, delay time.Duration, body []byte) error {
panic("Nats JetStream nonsupport delay publish")
}
func (mq *NatsJetStreamProducer) Stop() {
mq.nc.Close()
}
type NatsJetStreamConsumer struct {
options nats.Options
streamConfig jetstream.StreamConfig
consumerConfig jetstream.ConsumerConfig
nc *nats.Conn
js jetstream.JetStream
stream jetstream.Stream
lock *sync.Mutex
consumers map[string]map[string]jetstream.Consumer
consumes map[string]jetstream.ConsumeContext
}
// NewNatsJetStreamConsumer
// streamConfig name,subjects需配置
// consumerConfig name, filterSubject 无需配置,在 Subscribe 时配置
func NewNatsJetStreamConsumer(
options nats.Options,
streamConfig jetstream.StreamConfig,
consumerConfig jetstream.ConsumerConfig) *NatsJetStreamConsumer {
return &NatsJetStreamConsumer{
options: options,
streamConfig: streamConfig,
consumerConfig: consumerConfig,
lock: &sync.Mutex{},
consumers: make(map[string]map[string]jetstream.Consumer),
consumes: make(map[string]jetstream.ConsumeContext),
}
}
func (mq *NatsJetStreamConsumer) Init(ctx context.Context) error {
nc, err := mq.options.Connect()
if err != nil {
return err
}
mq.nc = nc
mq.js, err = jetstream.New(nc)
mq.stream, err = mq.js.CreateStream(ctx, mq.streamConfig)
if err != nil {
return err
}
return nil
}
func (mq *NatsJetStreamConsumer) Subscribe(filterSubject string, channel string, handler func(*Message) error) error {
mq.lock.Lock()
defer mq.lock.Unlock()
// get consumer
channelConsumers, ok := mq.consumers[channel]
if !ok {
channelConsumers = make(map[string]jetstream.Consumer)
mq.consumers[channel] = channelConsumers
}
consumer, ok := channelConsumers[filterSubject]
if !ok {
consumerConfig := mq.consumerConfig
consumerConfig.Name = channel
consumerConfig.Durable = channel
consumerConfig.FilterSubject = filterSubject
var err error
consumer, err = mq.stream.CreateOrUpdateConsumer(context.Background(), consumerConfig)
if err != nil {
return err
}
channelConsumers[filterSubject] = consumer
}
// consume message
consumeKey := channel + "/" + filterSubject
consumeContext, ok := mq.consumes[consumeKey]
if ok {
consumeContext.Stop()
delete(mq.consumers, consumeKey)
}
consumeContext, err := consumer.Consume(func(msg jetstream.Msg) {
// todo recover error
message := &Message{
Body: msg.Data(),
Time: time.Now().UnixMilli(),
}
err := handler(message)
if err == nil {
// ack message
err := msg.Ack()
if err != nil {
logger.Error("ack message error:", err, message)
}
}
})
if err != nil {
return err
}
mq.consumes[consumeKey] = consumeContext
return nil
}
func (mq *NatsJetStreamConsumer) SubscribeBroadcast(filterSubject string, handler func(*Message) error) error {
return mq.Subscribe(filterSubject, uuid.New().String(), handler)
}
func (mq *NatsJetStreamConsumer) Stop() {
mq.lock.Lock()
defer mq.lock.Unlock()
for _, consume := range mq.consumes {
consume.Stop()
}
for channel, _ := range mq.consumers {
// 删除 uuid 的广播 consumer
if uuidPattern.MatchString(channel) {
err := mq.js.DeleteConsumer(context.Background(), mq.streamConfig.Name, channel)
if err != nil {
logger.Errorf("delete consumer error, stream=%s, consumer=%s\n", mq.streamConfig.Name, channel)
}
}
}
mq.nc.Close()
}