DDMQ

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"))
}

每个字符串都会生成固定的哈希值,即使是空字符串

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值