GitHub链接:https://github.com/didi/DDMQ
DDMQ SDK目前支持Java、go、C/C++
producer
producer:生产方,每个producer只能关联一个group,初始化prodecer时需要指定config文件,config里指定ProxyList,可以手动传递也可以自动发现,还可以指定连接池的数量,不建议修改,使用默认的大小50个已能满足大部分需求,如果设置的过小会影响生产的性能
发送消息给mq有4种方式
1、Send(TOPIC, STRING_MESSAGE_BODY):传递topic,message
2、SendBinaryData(TOPIC, []byte{1, 2, 3}):传递topic,二进制message
3、SendWithKey(TOPIC, STRING_MESSAGE_BODY, “指定key值”):传递topic,message,和指定的key
4、SendWithPartition(TOPIC, carrera.PARTITION_HASH, hashId, STRING_MESSAGE_BODY, “key”):传递topic,message,hashid,key(key不传默认自动生成),PARTITION_HASH(此值固定)这种方式传递,hashid相同的会存放在一个Partition,保证有序
3种保序方式:
1、选择 MsgKey 作为保序依据,则 hashId = key.hashCode()
2、选择 JsonPath 作为保序依据,则 hashId = JsonPath规则,从message里选择的指定字段(类似正则)
3、选择 Qid 作为保序依据,则 hashId 可自定义
消息成功发送进mq后的返回格式:
result=Result({Code:0 Msg:OK Key:e24fdda5-b212-11ea-a3d5-6c92bf89acbq})
code=0代表发送成功,会自动生成message对应的key
延迟消息
如果message传递进mq,但不希望立即被消费,可以使用延迟消息的方式传递,需要在初始化producer时指定延迟的时间,放入结构体delayMeta
1、发送延迟消息topic,messsage,delayinfo:SendDelay(TOPIC, STRING_MESSAGE_BODY, delayMeta)
2、取消发送延迟消息topic,messageID:CancelDelay(TOPIC, uniqDelayMsgId)
package main
import (
"fmt"
"carrera"
"carrera/CarreraProducer"
"carrera/common/qlog"
"go.intra.xiaojukeji.com/golang/thrift-lib/0.9.2"
"time"
)
const (
TOPIC = "test" // topic name.
STRING_MESSAGE_BODY = "this is message body." // 测试的消息体。
)
func initLocalAllDefaultModel() *carrera.CarreraConfig {
//-------------- 构造本地配置,全部默认配置,但必须指定proxy列表----------------
config := carrera.NewDefaultLocalCarreraConfig()
//指定proxy列表,每个集群的proxy必须全部配置上
config.SetCarreraProxyList([]string{"127.0.0.1:9613"})
return config
}
func initLocalAllSpecifyModel() *carrera.CarreraConfig {
//-------------- 构造本地配置,全部默认配置,但必须指定proxy列表----------------
config := carrera.NewDefaultLocalCarreraConfig()
//指定proxy列表,每个集群的proxy必须全部配置上
config.SetCarreraProxyList([]string{"127.0.0.1:9613"})
//-------------- 构造配置,参数全部单独设置-------------------------
//指定proxy列表,每个集群的proxy必须全部配置上
config.SetCarreraProxyList([]string{"127.0.0.1:9613"})
//producer 实例池,可以并发发送,如果实例都被占用,没有放回池子的话,send会等待获取producer实例
config.SetCarreraPoolSize(20)
//Proxy端处理请求的超时时间。写队列超时之后会尝试Cache。Cache成功后会返回CACHE_OK
config.SetCarreraProxyTimeout(50)
//client和proxy server的超时时间,一般不建议设太小。必须大于carreraProxyTimeout的值,建议设置2倍的比例
config.SetCarreraClientTimeout(100)
//客户端失败重试次数,总共发送n+1次
config.SetCarreraClientRetry(3)
//是否开启自动从drop log文件中恢复消息,重试carreraClientRetry次后,仍然失败的消息,
//会写入本地drop.log文件,然后sdk会根据设置的周期定时读取消息,重新发送,失败后,仍然会写入drop.log文件
//默认每个drop文件50M,写够一个文件后才会触发自动恢复
config.SetRecoverFromDropLog(true)
//指定自动从drop log文件中恢复消息周期,不设置,默认30分钟,见#carrera.DEFAULT_RECOVER_FROM_DROP_LOG_INTERVAL
config.SetRecoverFromDropLogInterval(carrera.DEFAULT_RECOVER_FROM_DROP_LOG_INTERVAL) //从drop log恢复数据时间间隔
return config
}
func main() {
//
//--------------- 构造、启动生产实例 ---------------
//
//mq日志信息和drop日志信息都在一个路径下!
//初始化日志文件大小、drop日志文件大小,单位GB,日志级别默认INFO,日志路径./log/mq/,
// 日志文件为 ./log/mq/mq.log ./log/mq/drop/drop.log
qlog.InitLog(10, 50)
//如果需要单独指定日志级别,日志路径,使用下面的函数
//qlog.InitQLog(10,50, qlog.LEVEL_INFO, qlog.LOG_PATH)
//本地配置proxy列表模式,全部使用默认值
config := initLocalAllDefaultModel()
//本地配置proxy列表模式,参数全部单独指定
//config := initLocalAllSpecifyModel()
//初始化producer
producer := carrera.NewCarreraPooledProducer(config)
//启动producer,不可忽略
producer.Start()
//--------------- 生产用法 ---------------
//0.最简单的用法,只指定topic和字符串消息体
ret := producer.Send(TOPIC, STRING_MESSAGE_BODY)
//强烈建议一定要在日志中打印生产的结果。
//Result 包含三个属性:code和msg表示生产的结果。key是用来表示消息的唯一ID,后续要追踪这条消息的生产消费情况,都需要这个值
fmt.Println(ret)
if ret.Code == carrera.OK || ret.Code == carrera.CACHE_OK {
fmt.Println("produce success") // OK 和 CACHE_OK 两个结果都可以认为是生产成功了。
} else if ret.Code > carrera.CACHE_OK {
fmt.Println("produce failure") // 失败的情况,根据code和msg做相应处理。
}
//1. 生产二进制数据
ret = producer.SendBinaryData(TOPIC, []byte{1, 2, 3})
fmt.Println(ret)
//2. 自己指定消息Key。比如使用业务方的自己的traceId。 消息key只要做到尽量唯一即可
ret = producer.SendWithKey(TOPIC, STRING_MESSAGE_BODY, "指定key值")
fmt.Println(ret)
//3. 指定消息路由。相同hashId的消息,会被存储到同一个Partition中, hashId 比如是driver_id的hashcode
var hashId int64 = 1
ret = producer.SendWithPartition(TOPIC, carrera.PARTITION_HASH, hashId, STRING_MESSAGE_BODY, "指定key值,如果需要自动生成,请设置为空串")
fmt.Println(ret)
//-------------------延时消息生产用法---------------------------
delayMeta := &CarreraProducer.DelayMeta{
Timestamp: time.Now().Unix() + 60, // 延迟60s执行
Dmsgtype: thrift.Int32Ptr(2), // 2-延迟消息
}
delayResult := producer.SendDelay(TOPIC, STRING_MESSAGE_BODY, delayMeta)
fmt.Println(delayResult)
//-------------------延时循环消息生产用法---------------------------
timestamp := time.Now().Unix() + 60 // 延迟60s执行第一次
delayMeta = &CarreraProducer.DelayMeta{
Timestamp: timestamp,
Dmsgtype: thrift.Int32Ptr(3), // 3-延迟循环消息
Expire: thrift.Int64Ptr(timestamp + 86400), // 消息触发的24之后过期
Interval: thrift.Int64Ptr(10), // 循环间隔10s
Times: thrift.Int64Ptr(100), // 循环执行100次
}
delayResult = producer.SendDelay(TOPIC, STRING_MESSAGE_BODY, delayMeta)
fmt.Println(delayResult)
//-------------------延时或者延迟循环消息取消用法---------------------------
uniqDelayMsgId := "1514992938-2-1515165738-0-0-0-0-bf039782-f099-11e7-90d4-d0a637ed6097"
delayResult = producer.CancelDelay(TOPIC, uniqDelayMsgId)
fmt.Println(delayResult)
//--------------- 关闭生产实例 ---------------
producer.Shutdown()
}
consumer
Consumer:消费方,每个consumer只能关联一个group,如果是按partition方式发送进mq,partition中的每个message只能被一个goroutine消费,需要注意的是多个goroutine消费都必须是顺序读取partition里面的message,新启动的goroutine默认从partition队列最头端最新的地方开始阻塞的读message
config:每初始化一个consumer,都要指定配置文件,配置文件里指定绑定group,GoroutineNum数量,ProxyList服务器,MsgProceedFunc要执行的操作,这里注意如果指定的GoroutineNum小于ProxyList服务器的数量,consumer会自动将GoroutineNum升级为ProxyList数量,以保证消费端性能,而且GoroutineNum值也不宜设置的过小,会影响消费的性能
ProxyList:mq服务器的列表,可以手动在config文件里指定具体的ip,不写的话默认根据csdID传入的环境参数来自动发现
package main
import (
"carrera/consumer"
"carrera/consumer/CarreraConsumer"
"fmt"
"time"
)
func main() {
//指定日志输出路径
consumer.InitLogger("./mq/log")
/*
ProxyList 中的svr会并发拉取,拉取后投递到GoroutineNum数量的goroutine中,进行处理
当GoroutineNum小于proxyList中svr数量时,sdk会强制将goroutine数量设定为proxysvr的数量
例如:
1. proxysvr为2台,3个 goroutine则有1个goroutine会随机消费两个svr中的1个
svr1 svr2--------
| | |
goroutine1 goroutine2 goroutine3
2. proxysvr为4台,2个goroutine的情况下
svr1 svr2 svr3 svr4
| | | |
| | | |
| | | |
goroutine1 goroutine2 goroutine3 goroutine4
*/
//consumer := consumer.NewDiscoveryCarreraConsumer(consumer.Config{GoroutineNum:5,Group:"test-thrift-client",Topic:"test-1",MsgProceedFunc:testMsgProceed},"alias!carrera_cproducer")
consumer := consumer.NewCarreraConsumer(consumer.Config{GoroutineNum: 340, Group: "cg_test", ProxyList: []string{"127.0.0.1:9713"}, MsgProceedFunc: testMsgProceed})
time.Sleep(2 * time.Second)
request := &CarreraConsumer.ConsumeStatsRequest{
Group: "cg_test",
}
ret, _ := consumer.GetConsumeStats(request)
fmt.Printf("consume stats:[%s]", ret)
topic := "test"
request = &CarreraConsumer.ConsumeStatsRequest{
Group: "cg_test",
Topic: &topic,
}
ret, _ = consumer.GetConsumeStats(request)
fmt.Printf("consume stats:[%s]", ret)
consumer.Shutdown()
}
func testMsgProceed(context *CarreraConsumer.Context, msg *CarreraConsumer.Message) bool {
fmt.Printf("receive msg context[%s] offset[%d] msg[%s] \n", context.String(), msg.Offset, msg.Key)
time.Sleep(time.Second)
return true
}
producer、consumer示例
package main
import (
"fmt"
"go.intra.xiaojukeji.com/foundation/carrera-go-sdk/producer/src/carrera"
"go.intra.xiaojukeji.com/foundation/carrera-go-sdk/common/qlog"
"go.intra.xiaojukeji.com/foundation/carrera-go-sdk/common/csd"
"time"
)
func initCsdModel() *carrera.CarreraConfig {
topics := []string{"mq_topic"}
// Csd 服务必须提前指定要发送的topic列表,SDK会根据topic,idc查找proxy列表
s := carrera.NewCsdCarreraConfig(csd.ENV_TEST, topics)
s.SetRecoverFromDropLog(false)
s.SetRecoverFromDropLogInterval(30 * time.Minute)
s.SetCarreraPoolSize(100)
return s
}
func sendMsg(producer *carrera.PooledProducer, topic string) {
str := "{\"ip\":\"10.9.1.2\",\"port\":22}"
ret := producer.MessageBuilder().SetTopic(topic).SetBody(str).Send()
fmt.Println(ret)
if ret.Code == carrera.OK || ret.Code == carrera.CACHE_OK {
fmt.Println("produce success")
} else if ret.Code > carrera.CACHE_OK {
fmt.Println("produce failure")
}
}
func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
//LOG_PATH是qlog的默认路径
qlog.InitQLog(10, 10, qlog.LEVEL_DEBUG, qlog.LOG_PATH)
//csd DDMQ服务发现
config := initCsdModel()
//初始化producer
producer := carrera.NewCarreraPooledProducer(config)
//启动producer,不可忽略
producer.Start()
fmt.Println("hello mq")
//生产过程
sendMsg(producer, TOPIC)
//关闭生产实例
producer.Shutdown()
}
}
__________________________________________________________
package main
import (
"go.intra.xiaojukeji.com/foundation/carrera-go-sdk/consumer/src/carrera/consumer"
"go.intra.xiaojukeji.com/foundation/carrera-go-sdk/consumer/src/carrera/consumer/CarreraConsumer"
"go.intra.xiaojukeji.com/foundation/carrera-go-sdk/common/csd"
"fmt"
)
var group_name "amber"
func csdMode() {
conf := &consumer.Config{
Group: group_name,
MsgProceedFunc: testMsgProceed,
GoroutineNum: 10,
CsdEnv: csd.ENV_TEST,
}
consumer := consumer.NewCsdCarreraConsumer(conf)
consumer.Shutdown()
}
//consumer拿到topic中内容后仅打印
func testMsgProceed(context *CarreraConsumer.Context, msg *CarreraConsumer.Message) bool {
fmt.Println(context.Topic, string(msg.Value))
return true
}
func main() {
csdMode()
}
Golang中生成字符串的哈希值
package main
import (
"fmt"
"hash/fnv"
)
func hash(s string) uint32 {
h := fnv.New32a()
h.Write([]byte(s))
return h.Sum32()
}
func main() {
fmt.Println(hash(""))
fmt.Println(hash("Amber"))
}
每个字符串都会生成固定的哈希值,即使是空字符串