RocketMQ5.0 gRPC 协议go客户端使用,gRPC SDK 仅支持版本大于等于5.0的服务端
gRPC SDK和原来的 Remoting SDK的关系,看下面官网截图
目前gRPC 协议客户端还没有完善,go 只有 SimpleConsumer
目录
1、安装RocketMQ5.0
安装过程可以看笔者的《linux 安装 RocketMQ》,这篇文章中使用的是RocketMQ 4.9.4版本,换成5的版本安装依然是可以的,文章地址:https://blog.csdn.net/wsjzzcbq/article/details/125562966
区别是启动方式改变了,增加了 proxy 的启动,下面是启动方式
进入bin目录
先启动 nameserver
nohup sh mqnamesrv &
然后启动 broker
nohup sh mqbroker -n localhost:9876 --enable-proxy &
这里 gRPC 默认的端口是8081,如果想使用其他端口,需要修改conf目录下的 rmq-proxy.json 配置文件,如将 gRPC端口修改为 8086,需要在rmq-proxy.json文件中添加 grpcServerPort": 8086
grpcServerPort": 8086
如下面截图
rmq-proxy.json 文件
添加端口配置
配置完成后,需要在启动 broker的命令上添加配置文件路径
nohup sh mqbroker -n localhost:9876 -pc /usr/local/rocketmq/rocketmq-all-5.0.0-bin-release/conf/rmq-proxy.json --enable-proxy &
这样启动后,gRPC端口为 8086
同时注意,如果想让客户端连接服务端,要开放防火墙端口
firewall-cmd --zone=public --add-port=8081/tcp --permanent
firewall-cmd --reload
查看防火墙端口
firewall-cmd --zone=public --list-ports
笔者使用默认的8081端口
2、 Go SDK 使用
Go版本要求 1.11+
安装 Go SDK
go get -u github.com/apache/rocketmq-clients/golang/v5
创建 topic
在 bin 目录下
笔者创建的 topic 名称为 demo_topic
sh ./mqadmin updatetopic -n localhost:9876 -c DefaultCluster -t demo_topic
查看 topic
sh ./mqadmin topicList -n localhost:9876
创建消费者组
在bin目录下
笔者创建名称为 demo_group 的消费者组
sh ./mqadmin updateSubGroup -n localhost:9876 -c DefaultCluster -g demo_group
当然,创建 topic 和消费者组通过 RocketMQ Dashboard 更加方便
2.1、普通消息
消费者
package normal
import (
"context"
"fmt"
"github.com/apache/rocketmq-clients/golang/v5"
"github.com/apache/rocketmq-clients/golang/v5/credentials"
"log"
"os"
"time"
)
var (
// maximum waiting time for receive func
awaitDuration = time.Second * 5
// maximum number of messages received at one time
maxMessageNum int32 = 16
// invisibleDuration should > 20s
invisibleDuration = time.Second * 20
// receive messages in a loop
)
func Consumer() {
// log to console
os.Setenv("mq.consoleAppender.enabled", "true")
golang.ResetLogger()
// In most case, you don't need to create many consumers, singleton pattern is more recommended.
simpleConsumer, err := golang.NewSimpleConsumer(&golang.Config{
Endpoint: Endpoint,
ConsumerGroup: ConsumerGroup,
Credentials: &credentials.SessionCredentials{
AccessKey: AccessKey,
AccessSecret: SecretKey,
},
},
golang.WithAwaitDuration(awaitDuration),
golang.WithSubscriptionExpressions(map[string]*golang.FilterExpression{
Topic: golang.SUB_ALL,
}),
)
if err != nil {
log.Fatal(err)
}
// start simpleConsumer
err = simpleConsumer.Start()
if err != nil {
log.Fatal(err)
}
// graceful stop simpleConsumer
defer simpleConsumer.GracefulStop()
go func() {
for {
fmt.Println("开始接受消息")
mvs, err := simpleConsumer.Receive(context.TODO(), maxMessageNum, invisibleDuration)
if err != nil {
fmt.Println(err)
}
fmt.Println(len(mvs))
// ack message
for _, mv := range mvs {
//消息内容
fmt.Println(string(mv.GetBody()))
simpleConsumer.Ack(context.TODO(), mv)
fmt.Println(mv)
}
fmt.Println("wait a moment")
fmt.Println()
time.Sleep(time.Second * 3)
}
}()
// run for a while
time.Sleep(time.Minute)
}
生产者
同步发送消息
package normal
import (
"context"
"fmt"
"github.com/apache/rocketmq-clients/golang/v5"
"github.com/apache/rocketmq-clients/golang/v5/credentials"
"log"
"os"
"strconv"
"time"
)
const (
ConsumerGroup = "demo_group"
Topic = "demo_topic"
Endpoint = "192.168.5.14:8081"
AccessKey = "xxxxxx"
SecretKey = "xxxxxx"
)
func ProducerStart() {
os.Setenv("mq.consoleAppender.enabled", "true")
golang.ResetLogger()
// In most case, you don't need to create many producers, singleton pattern is more recommended.
producer, err := golang.NewProducer(&golang.Config{
Endpoint: Endpoint,
Credentials: &credentials.SessionCredentials{
AccessKey: AccessKey,
AccessSecret: SecretKey,
},
},
golang.WithTopics(Topic),
)
if err != nil {
log.Fatal(err)
}
// start producer
err = producer.Start()
if err != nil {
log.Fatal(err)
}
// graceful stop producer
defer producer.GracefulStop()
for i := 0; i < 5; i++ {
// new a message
msg := &golang.Message{
Topic: Topic,
Body: []byte("消息内容 窗外下着潇潇雨 : " + strconv.Itoa(i+10)),
}
// set keys and tag
msg.SetKeys("a", "b")
msg.SetTag("ab")
// send message in sync
resp, err := producer.Send(context.TODO(), msg)
if err != nil {
log.Fatal(err)
}
for i := 0; i < len(resp); i++ {
fmt.Printf("%#v\n", resp[i])
}
// wait a moment
time.Sleep(time.Second * 1)
}
}
笔者消费者和生产者代码在同一包下,故 ConsumerGroup、Topic、Endpoint都放在了生产者常量里
下面分别创建生产者和消费者的测试代码
package normal
import "testing"
func TestProducerStart(t *testing.T) {
ProducerStart()
}
package normal
import "testing"
func Test_consumer(t *testing.T) {
Consumer()
}
工程结构看下图
先启动消费者建立topic和消费者组的绑定关系,再启动生产者发送消息
运行效果
异步发送消息
生成者
package normal
import (
"context"
"fmt"
"github.com/apache/rocketmq-clients/golang/v5"
"github.com/apache/rocketmq-clients/golang/v5/credentials"
"log"
"os"
"strconv"
"time"
)
func AsyncProducer() {
// log to console
os.Setenv("mq.consoleAppender.enabled", "true")
golang.ResetLogger()
// In most case, you don't need to create many producers, singleton pattern is more recommended.
producer, err := golang.NewProducer(&golang.Config{
Endpoint: Endpoint,
Credentials: &credentials.SessionCredentials{
AccessKey: AccessKey,
AccessSecret: SecretKey,
},
},
golang.WithTopics(Topic),
)
if err != nil {
log.Fatal(err)
}
// start producer
err = producer.Start()
if err != nil {
log.Fatal(err)
}
// graceful stop producer
defer producer.GracefulStop()
for i := 0; i < 5; i++ {
// new a message
msg := &golang.Message{
Topic: Topic,
Body: []byte("花月两模糊,隔窗看欲无 : " + strconv.Itoa(i)),
}
// set keys and tag
msg.SetKeys("a", "b")
msg.SetTag("ab")
//异步发送消息
producer.SendAsync(context.TODO(), msg, func(ctx context.Context, resp []*golang.SendReceipt, err error) {
if err != nil {
log.Fatal(err)
}
for i := 0; i < len(resp); i++ {
fmt.Println("回调")
fmt.Printf("%#v\n", resp[i])
}
})
// wait a moment
time.Sleep(time.Second * 1)
}
time.Sleep(time.Minute * 1)
}
测试代码
package normal
import "testing"
func TestAsyncProducer(t *testing.T) {
AsyncProducer()
}
测试
2.2、顺序消息
Apache RocketMQ 顺序消息的顺序关系通过消息组(MessageGroup)判定和识别,发送顺序消息时需要为每条消息设置归属的消息组,相同消息组的多条消息之间遵循先进先出的顺序关系,不同消息组、无消息组的消息之间不涉及顺序性
顺序消息仅支持使用MessageType为FIFO的主题,即顺序消息只能发送至类型为顺序消息的主题中,发送的消息的类型必须和主题的类型一致
创建顺序消息的 topic
./mqadmin updateTopic -c DefaultCluster -t FIFO_Demo_Topic -o true -n localhost:9876 -a +message.type=FIFO
创建新的消费者组
sh ./mqadmin updateSubGroup -n localhost:9876 -c DefaultCluster -g fifo_demo_group
生产者
package fifo
import (
"context"
"fmt"
"github.com/apache/rocketmq-clients/golang/v5"
"github.com/apache/rocketmq-clients/golang/v5/credentials"
"log"
"os"
"strconv"
"time"
)
const (
ConsumerGroup = "fifo_demo_group"
Topic = "FIFO_Demo_Topic"
Endpoint = "192.168.5.14:8081"
AccessKey = "xxxxxx"
SecretKey = "xxxxxx"
)
func FifoProducer() {
// log to console
os.Setenv("mq.consoleAppender.enabled", "true")
golang.ResetLogger()
// In most case, you don't need to create many producers, singleton pattern is more recommended.
producer, err := golang.NewProducer(&golang.Config{
Endpoint: Endpoint,
Credentials: &credentials.SessionCredentials{
AccessKey: AccessKey,
AccessSecret: SecretKey,
},
},
golang.WithTopics(Topic),
)
if err != nil {
log.Fatal(err)
}
// start producer
err = producer.Start()
if err != nil {
log.Fatal(err)
}
// graceful stop producer
defer producer.GracefulStop()
for i := 0; i < 5; i++ {
// new a message
msg := &golang.Message{
Topic: Topic,
Body: []byte("长相思,长相思。若问相思甚了期,除非相见时: " + strconv.Itoa(i)),
}
// set keys and tag
msg.SetKeys("a", "b")
msg.SetTag("ab")
msg.SetMessageGroup("fifo")
// send message in sync
resp, err := producer.Send(context.TODO(), msg)
if err != nil {
log.Fatal(err)
}
for i := 0; i < len(resp); i++ {
fmt.Printf("%#v\n", resp[i])
}
// wait a moment
time.Sleep(time.Second * 1)
}
}
消费者
package fifo
import (
"context"
"fmt"
"github.com/apache/rocketmq-clients/golang/v5"
"github.com/apache/rocketmq-clients/golang/v5/credentials"
"log"
"os"
"time"
)
var (
// maximum waiting time for receive func
awaitDuration = time.Second * 5
// maximum number of messages received at one time
maxMessageNum int32 = 16
// invisibleDuration should > 20s
invisibleDuration = time.Second * 20
// receive messages in a loop
)
func Consumer() {
// log to console
os.Setenv("mq.consoleAppender.enabled", "true")
golang.ResetLogger()
// In most case, you don't need to create many consumers, singleton pattern is more recommended
simpleConsumer, err := golang.NewSimpleConsumer(&golang.Config{
Endpoint: Endpoint,
ConsumerGroup: ConsumerGroup,
Credentials: &credentials.SessionCredentials{
AccessKey: AccessKey,
AccessSecret: SecretKey,
},
},
golang.WithAwaitDuration(awaitDuration),
golang.WithSubscriptionExpressions(map[string]*golang.FilterExpression{
Topic: golang.SUB_ALL,
}),
)
if err != nil {
log.Fatal(err)
}
// start simpleConsumer
err = simpleConsumer.Start()
if err != nil {
log.Fatal(err)
}
// graceful stop simpleConsumer
defer simpleConsumer.GracefulStop()
go func() {
for {
fmt.Println("开始接受消息")
mvs, err := simpleConsumer.Receive(context.TODO(), maxMessageNum, invisibleDuration)
if err != nil {
fmt.Println(err)
}
fmt.Println(len(mvs))
// ack message
for _, mv := range mvs {
//消息内容
fmt.Println(string(mv.GetBody()))
simpleConsumer.Ack(context.TODO(), mv)
fmt.Println(mv)
}
fmt.Println("wait a moment")
fmt.Println()
time.Sleep(time.Second * 3)
}
}()
// run for a while
time.Sleep(time.Minute)
}
测试代码
package fifo
import "testing"
func TestFifoProducer(t *testing.T) {
FifoProducer()
}
package fifo
import "testing"
func TestConsumer(t *testing.T) {
Consumer()
}
运行效果
2.3、延时消息
定时消息仅支持在 MessageType为Delay 的主题内使用,即定时消息只能发送至类型为定时消息的主题中,发送的消息的类型必须和主题的类型一致
创建延迟topic
./mqadmin updateTopic -c DefaultCluster -t Delay_Demo_Topic -n localhost:9876 -a +message.type=DELAY
查看topic
sh ./mqadmin topicList -n localhost:9876
创建新的消费者组
sh ./mqadmin updateSubGroup -n localhost:9876 -c DefaultCluster -g delay_demo_group
生产者
package delay
import (
"context"
"fmt"
"github.com/apache/rocketmq-clients/golang/v5"
"github.com/apache/rocketmq-clients/golang/v5/credentials"
"log"
"os"
"strconv"
"time"
)
const (
ConsumerGroup = "delay_demo_group"
Topic = "Delay_Demo_Topic"
Endpoint = "192.168.5.14:8081"
AccessKey = "xxxxxx"
SecretKey = "xxxxxx"
)
func DelayProducer() {
// log to console
os.Setenv("mq.consoleAppender.enabled", "true")
golang.ResetLogger()
// In most case, you don't need to create many producers, singleton pattern is more recommended.
producer, err := golang.NewProducer(&golang.Config{
Endpoint: Endpoint,
Credentials: &credentials.SessionCredentials{
AccessKey: AccessKey,
AccessSecret: SecretKey,
},
},
golang.WithTopics(Topic),
)
if err != nil {
log.Fatal(err)
}
// start producer
err = producer.Start()
if err != nil {
log.Fatal(err)
}
// graceful stop producer
defer producer.GracefulStop()
for i := 0; i < 1; i++ {
// new a message
msg := &golang.Message{
Topic: Topic,
Body: []byte("长相思,长相思。欲把相思说似谁,浅情人不知 : " + strconv.Itoa(i)),
}
// set keys and tag
msg.SetKeys("a", "b")
msg.SetTag("ab")
// 设置延时时间 5 秒钟
msg.SetDelayTimestamp(time.Now().Add(time.Second * 10))
// send message in sync
resp, err := producer.Send(context.TODO(), msg)
log.Println("延时消息发送完成")
if err != nil {
log.Fatal(err)
}
for i := 0; i < len(resp); i++ {
fmt.Printf("%#v\n", resp[i])
}
// wait a moment
time.Sleep(time.Second * 1)
}
}
消费者
package delay
import (
"context"
"fmt"
"github.com/apache/rocketmq-clients/golang/v5"
"github.com/apache/rocketmq-clients/golang/v5/credentials"
"log"
"os"
"time"
)
var (
// maximum waiting time for receive func
awaitDuration = time.Second * 1
// maximum number of messages received at one time
maxMessageNum int32 = 16
// invisibleDuration should > 20s
invisibleDuration = time.Second * 10
// receive messages in a loop
)
func Consumer() {
// log to console
os.Setenv("mq.consoleAppender.enabled", "true")
golang.ResetLogger()
// In most case, you don't need to create many consumers, singleton pattern is more recommended
simpleConsumer, err := golang.NewSimpleConsumer(&golang.Config{
Endpoint: Endpoint,
ConsumerGroup: ConsumerGroup,
Credentials: &credentials.SessionCredentials{
AccessKey: AccessKey,
AccessSecret: SecretKey,
},
},
golang.WithAwaitDuration(awaitDuration),
golang.WithSubscriptionExpressions(map[string]*golang.FilterExpression{
Topic: golang.SUB_ALL,
}),
)
if err != nil {
log.Fatal(err)
}
// start simpleConsumer
err = simpleConsumer.Start()
if err != nil {
log.Fatal(err)
}
// graceful stop simpleConsumer
defer simpleConsumer.GracefulStop()
go func() {
for {
mvs, err := simpleConsumer.Receive(context.TODO(), maxMessageNum, invisibleDuration)
if err != nil {
fmt.Println(err)
}
// ack message
for _, mv := range mvs {
//消息内容
log.Println(string(mv.GetBody()))
simpleConsumer.Ack(context.TODO(), mv)
}
//fmt.Println("wait a moment")
//fmt.Println()
//time.Sleep(time.Second * 3)
}
}()
// run for a while
time.Sleep(time.Minute)
}
测试代码
package delay
import "testing"
func TestDelayProducer(t *testing.T) {
DelayProducer()
}
package delay
import "testing"
func TestConsumer(t *testing.T) {
Consumer()
}
测试
因为SimpleConsumer有20秒的 invisibleDuration,因此打印出的延时时间是30秒
2.4、事物消息
创建事物topic
./mqadmin updatetopic -n localhost:9876 -t Transaction_Demo_Topic -c DefaultCluster -a +message.type=TRANSACTION
查看topic
sh ./mqadmin topicList -n localhost:9876
创建消费者组
sh ./mqadmin updateSubGroup -n localhost:9876 -c DefaultCluster -g transaction_demo_group
生产者
3、事物消息
创建事物topic
./mqadmin updatetopic -n localhost:9876 -t Transaction_Demo_Topic -c DefaultCluster -a +message.type=TRANSACTION
查看topic
sh ./mqadmin topicList -n localhost:9876
创建新的消费者组
sh ./mqadmin updateSubGroup -n localhost:9876 -c DefaultCluster -g transaction_demo_group
生产者
事物相关内容已经写在代码里
package transaction
import (
"context"
"fmt"
"github.com/apache/rocketmq-clients/golang/v5"
"github.com/apache/rocketmq-clients/golang/v5/credentials"
"log"
"os"
"strconv"
"time"
)
const (
ConsumerGroup = "transaction_demo_group"
Topic = "Transaction_Demo_Topic"
Endpoint = "192.168.5.14:8081"
AccessKey = "xxxxxx"
SecretKey = "xxxxxx"
)
func TransactionProducer() {
// log to console
os.Setenv("mq.consoleAppender.enabled", "true")
golang.ResetLogger()
// In most case, you don't need to create many producers, singleton pattern is more recommended.
producer, err := golang.NewProducer(&golang.Config{
Endpoint: Endpoint,
Credentials: &credentials.SessionCredentials{
AccessKey: AccessKey,
AccessSecret: SecretKey,
},
},
golang.WithTransactionChecker(&golang.TransactionChecker{
Check: func(msg *golang.MessageView) golang.TransactionResolution {
fmt.Println("检查本地事物是否已经提交")
log.Printf("check transaction message: %v", msg)
return golang.COMMIT
},
}),
golang.WithTopics(Topic),
)
if err != nil {
log.Fatal(err)
}
// start producer
err = producer.Start()
if err != nil {
log.Fatal(err)
}
// graceful stop producer
defer producer.GracefulStop()
for i := 0; i < 1; i++ {
// new a message
msg := &golang.Message{
Topic: Topic,
Body: []byte("相思只在,丁香枝上,豆蔻梢头 : " + strconv.Itoa(i)),
}
// set keys and tag
msg.SetKeys("a", "b")
msg.SetTag("ab")
// send message in sync
transaction := producer.BeginTransaction()
resp, err := producer.SendWithTransaction(context.TODO(), msg, transaction)
if err != nil {
log.Fatal(err)
}
for i := 0; i < len(resp); i++ {
fmt.Printf("%#v\n", resp[i])
}
// 提交本地事物,如果这里调用RollBack则消息会被取消,消费者不能消费
//如果调用Commit则消息会被消费者消费
//如果什么都没调用,相当于UNKNOWN,rocektmq会回调上面 TransactionChecker判断事物状态
//err = transaction.RollBack()
err = transaction.Commit()
if err != nil {
log.Fatal(err)
}
// wait a moment
time.Sleep(time.Second * 1)
}
time.Sleep(time.Minute)
}
消费者
package transaction
import (
"context"
"fmt"
"github.com/apache/rocketmq-clients/golang/v5"
"github.com/apache/rocketmq-clients/golang/v5/credentials"
"log"
"os"
"time"
)
var (
// maximum waiting time for receive func
awaitDuration = time.Second * 1
// maximum number of messages received at one time
maxMessageNum int32 = 16
// invisibleDuration should > 20s
invisibleDuration = time.Second * 10
// receive messages in a loop
)
func Consumer() {
// log to console
os.Setenv("mq.consoleAppender.enabled", "true")
golang.ResetLogger()
// In most case, you don't need to create many consumers, singleton pattern is more recommended
simpleConsumer, err := golang.NewSimpleConsumer(&golang.Config{
Endpoint: Endpoint,
ConsumerGroup: ConsumerGroup,
Credentials: &credentials.SessionCredentials{
AccessKey: AccessKey,
AccessSecret: SecretKey,
},
},
golang.WithAwaitDuration(awaitDuration),
golang.WithSubscriptionExpressions(map[string]*golang.FilterExpression{
Topic: golang.SUB_ALL,
}),
)
if err != nil {
log.Fatal(err)
}
// start simpleConsumer
err = simpleConsumer.Start()
if err != nil {
log.Fatal(err)
}
// graceful stop simpleConsumer
defer simpleConsumer.GracefulStop()
go func() {
for {
mvs, err := simpleConsumer.Receive(context.TODO(), maxMessageNum, invisibleDuration)
if err != nil {
fmt.Println(err)
}
// ack message
for _, mv := range mvs {
//消息内容
log.Println(string(mv.GetBody()))
simpleConsumer.Ack(context.TODO(), mv)
}
//fmt.Println("wait a moment")
//fmt.Println()
//time.Sleep(time.Second * 3)
}
}()
// run for a while
time.Sleep(time.Minute)
}
测试代码
package transaction
import "testing"
func TestTransactionProducer(t *testing.T) {
TransactionProducer()
}
package transaction
import "testing"
func TestConsumer(t *testing.T) {
Consumer()
}
测试
这里测试本地事物正常提交的情况
下面测试本地事物是未知,rocketmq回调生成者方法检查的情况
修改生成者代码
将事物提交部分注释掉
package transaction
import (
"context"
"fmt"
"github.com/apache/rocketmq-clients/golang/v5"
"github.com/apache/rocketmq-clients/golang/v5/credentials"
"log"
"os"
"strconv"
"time"
)
const (
ConsumerGroup = "transaction_demo_group"
Topic = "Transaction_Demo_Topic"
Endpoint = "192.168.5.14:8081"
AccessKey = "xxxxxx"
SecretKey = "xxxxxx"
)
func TransactionProducer() {
// log to console
os.Setenv("mq.consoleAppender.enabled", "true")
golang.ResetLogger()
// In most case, you don't need to create many producers, singleton pattern is more recommended.
producer, err := golang.NewProducer(&golang.Config{
Endpoint: Endpoint,
Credentials: &credentials.SessionCredentials{
AccessKey: AccessKey,
AccessSecret: SecretKey,
},
},
golang.WithTransactionChecker(&golang.TransactionChecker{
Check: func(msg *golang.MessageView) golang.TransactionResolution {
fmt.Println("检查本地事物是否已经提交")
log.Printf("check transaction message: %v", msg)
return golang.COMMIT
},
}),
golang.WithTopics(Topic),
)
if err != nil {
log.Fatal(err)
}
// start producer
err = producer.Start()
if err != nil {
log.Fatal(err)
}
// graceful stop producer
defer producer.GracefulStop()
for i := 0; i < 1; i++ {
// new a message
msg := &golang.Message{
Topic: Topic,
Body: []byte("相思只在,丁香枝上,豆蔻梢头 : " + strconv.Itoa(i)),
}
// set keys and tag
msg.SetKeys("a", "b")
msg.SetTag("ab")
// send message in sync
transaction := producer.BeginTransaction()
resp, err := producer.SendWithTransaction(context.TODO(), msg, transaction)
if err != nil {
log.Fatal(err)
}
for i := 0; i < len(resp); i++ {
fmt.Printf("%#v\n", resp[i])
}
// 提交本地事物,如果这里调用RollBack则消息会被取消,消费者不能消费
//如果调用Commit则消息会被消费者消费
//如果什么都没调用,相当于UNKNOWN,rocektmq会回调上面 TransactionChecker判断事物状态
//err = transaction.RollBack()
//err = transaction.Commit()
//if err != nil {
// log.Fatal(err)
//}
// wait a moment
time.Sleep(time.Second * 1)
}
time.Sleep(time.Minute)
}
测试
至此完