add nats mq wrappers (#24445)

bug fixup, configurable natsmq, add unittest, pass e2e.



move natsmq to pkg project

Signed-off-by: chyezh <ye.zhen@zilliz.com>
Co-authored-by: yiwangdr <yiwangdr@gmail.com>
This commit is contained in:
chyezh 2023-06-07 10:00:37 +08:00 committed by GitHub
parent 5f7099a9bd
commit f97127ae55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 3297 additions and 244 deletions

5
.gitignore vendored
View File

@ -83,6 +83,11 @@ cpp_coverage/
# virtualenv
venv/
.venv/
# gopls generated
go.work
go.work.sum
# docker compose volumes
deployments/docker/*/volumes

View File

@ -88,7 +88,8 @@ func runComponent[T component](ctx context.Context,
localMsg bool,
runWg *sync.WaitGroup,
creator func(context.Context, dependency.Factory) (T, error),
metricRegister func(*prometheus.Registry)) T {
metricRegister func(*prometheus.Registry),
) T {
var role T
var wg sync.WaitGroup
@ -247,19 +248,6 @@ func (mr *MilvusRoles) Run(local bool, alias string) {
paramtable.Init()
params := paramtable.Get()
if params.RocksmqEnable() {
path, err := params.Load("rocksmq.path")
if err != nil {
panic(err)
}
if err = rocksmqimpl.InitRocksMQ(path); err != nil {
panic(err)
}
defer stopRocksmq()
}
if params.EtcdCfg.UseEmbedEtcd.GetAsBool() {
// Start etcd server.
etcd.InitEtcdServer(

View File

@ -75,23 +75,28 @@ minio:
# For more information, refer to
# aws: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html
# gcp: https://cloud.google.com/storage/docs/access-control/iam
# aliyun (ack): https://www.alibabacloud.com/help/en/container-service-for-kubernetes/latest/use-rrsa-to-enforce-access-control
# aliyun (ack): https://www.alibabacloud.com/help/en/container-service-for-kubernetes/latest/use-rrsa-to-enforce-access-control
# aliyun (ecs): https://www.alibabacloud.com/help/en/elastic-compute-service/latest/attach-an-instance-ram-role
useIAM: false
# Cloud Provider of S3. Supports: "aws", "gcp", "aliyun".
# Cloud Provider of S3. Supports: "aws", "gcp", "aliyun".
# You can use "aws" for other cloud provider supports S3 API with signature v4, e.g.: minio
# You can use "gcp" for other cloud provider supports S3 API with signature v2
# You can use "aliyun" for other cloud provider uses virtual host style bucket
# You can use "aliyun" for other cloud provider uses virtual host style bucket
# When useIAM enabled, only "aws", "gcp", "aliyun" is supported for now
cloudProvider: aws
# Custom endpoint for fetch IAM role credentials. when useIAM is true & cloudProvider is "aws".
# Leave it empty if you want to use AWS default endpoint
iamEndpoint:
iamEndpoint:
# Milvus supports three MQ: rocksmq(based on RockDB), Pulsar and Kafka, which should be reserved in config what you use.
# There is a note about enabling priority if we config multiple mq in this file
# 1. standalone(local) mode: rocksmq(default) > Pulsar > Kafka
# 2. cluster mode: Pulsar(default) > Kafka (rocksmq is unsupported)
# Milvus supports four MQ: rocksmq(based on RockDB), natsmq(embedded nats-server), Pulsar and Kafka.
# You can change your mq by setting mq.type field.
# If you don't set mq.type field as default, there is a note about enabling priority if we config multiple mq in this file.
# 1. standalone(local) mode: rocksmq(default) > natsmq > Pulsar > Kafka
# 2. cluster mode: Pulsar(default) > Kafka (rocksmq and natsmq is unsupported in cluster mode)
mq:
# Default value: "default"
# Valid values: [default, pulsar, kafka, rocksmq, natsmq]
type: default
# Related configuration of pulsar, used to manage Milvus logs of recent mutation operations, output streaming log, and provide log publish-subscribe services.
pulsar:
@ -104,9 +109,9 @@ pulsar:
# If you want to enable kafka, needs to comment the pulsar configs
# kafka:
# brokerList:
# saslUsername:
# saslPassword:
# brokerList:
# saslUsername:
# saslPassword:
# saslMechanisms: PLAIN
# securityProtocol: SASL_SSL
@ -120,6 +125,21 @@ rocksmq:
retentionSizeInMB: 8192 # 8 GB, 8 * 1024 MB, The retention size of the message in rocksmq.
compactionInterval: 86400 # 1 day, trigger rocksdb compaction every day to remove deleted data
# natsmq configuration.
natsmq:
server: # server side configuration for natsmq.
port: 4222 # 4222 by default, Port for nats server listening.
storeDir: /var/lib/milvus/nats # /var/lib/milvus/nats by default, directory to use for JetStream storage of nats.
maxFileStore: 17179869184 # (B) 16GB by default, Maximum size of the 'file' storage.
maxPayload: 8388608 # (B) 8MB by default, Maximum number of bytes in a message payload.
maxPending: 67108864 # (B) 64MB by default, Maximum number of bytes buffered for a connection Applies to client connections.
initializeTimeout: 4000 # (ms) 4s by default, waiting for initialization of natsmq finished.
monitor:
debug: false # false by default, If true enable debug log messages.
logTime: true # true by default, If set to false, log without timestamps.
logFile: # no log file by default, Log file path relative to.. .
logSizeLimit: 0 # (B) 0, unlimited by default, Size in bytes after the log file rolls over to a new one.
# Related configuration of rootCoord, used to handle data definition language (DDL) and data control language (DCL) requests
rootCoord:
dmlChannelNum: 16 # The number of dml channels created at system startup
@ -192,7 +212,7 @@ queryCoord:
clientMaxRecvSize: 268435456
taskMergeCap: 1
taskExecutionCap: 256
enableActiveStandby: false # Enable active-standby
enableActiveStandby: false # Enable active-standby
# Related configuration of queryNode, used to run hybrid search between vector and scalar data.
queryNode:
@ -203,7 +223,7 @@ queryNode:
stats:
publishInterval: 1000 # Interval for querynode to report node information (milliseconds)
segcore:
knowhereThreadPoolNumRatio: 4
knowhereThreadPoolNumRatio: 4
# Use more threads to make better use of SSD throughput in disk index.
# This parameter is only useful when enable-disk = true.
# And this value should be a number greater than 1 and less than 32.
@ -288,7 +308,7 @@ dataCoord:
compactableProportion: 0.5
# over (compactableProportion * segment max # of rows) rows.
# MUST BE GREATER THAN OR EQUAL TO <smallProportion>!!!
# During compaction, the size of segment # of rows is able to exceed segment max # of rows by (expansionRate-1) * 100%.
# During compaction, the size of segment # of rows is able to exceed segment max # of rows by (expansionRate-1) * 100%.
expansionRate: 1.25
enableCompaction: true # Enable data segment compaction
compaction:
@ -331,7 +351,7 @@ dataNode:
log:
level: info # Only supports debug, info, warn, error, panic, or fatal. Default 'info'.
file:
rootPath: # root dir path to put logs, default "" means no log file will print. please adjust in embedded Milvus: /tmp/milvus/logs
rootPath: # root dir path to put logs, default "" means no log file will print. please adjust in embedded Milvus: /tmp/milvus/logs
maxSize: 300 # MB
maxAge: 10 # Maximum time for log retention in day.
maxBackups: 20
@ -552,7 +572,7 @@ trace:
# Fractions >= 1 will always sample. Fractions < 0 are treated as zero.
sampleFraction: 0
jaeger:
url: # when exporter is jaeger should set the jaeger's URL
url: # when exporter is jaeger should set the jaeger's URL
autoIndex:
params:

24
go.mod
View File

@ -14,11 +14,10 @@ require (
github.com/casbin/casbin/v2 v2.44.2
github.com/casbin/json-adapter/v2 v2.0.0
github.com/cockroachdb/errors v1.9.1
github.com/confluentinc/confluent-kafka-go v1.9.1
github.com/gin-gonic/gin v1.9.0
github.com/gofrs/flock v0.8.1
github.com/golang/protobuf v1.5.3
github.com/klauspost/compress v1.14.4
github.com/klauspost/compress v1.16.5
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
github.com/milvus-io/milvus-proto/go-api v0.0.0-20230531124827-410c849303a9
github.com/milvus-io/milvus/pkg v0.0.0-00010101000000-000000000000
@ -39,11 +38,11 @@ require (
go.uber.org/atomic v1.10.0
go.uber.org/multierr v1.6.0
go.uber.org/zap v1.17.0
golang.org/x/crypto v0.5.0
golang.org/x/crypto v0.8.0
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17
golang.org/x/oauth2 v0.6.0
golang.org/x/sync v0.1.0
golang.org/x/text v0.8.0
golang.org/x/text v0.9.0
google.golang.org/grpc v1.54.0
gorm.io/driver/mysql v1.3.5
gorm.io/gorm v1.23.8
@ -56,13 +55,20 @@ require (
github.com/benesch/cgosymbolizer v0.0.0-20190515212042-bec6fe6e597b // indirect
github.com/bytedance/sonic v1.8.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/confluentinc/confluent-kafka-go v1.9.1 // indirect
github.com/containerd/cgroups v1.1.0 // indirect
github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c // indirect
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 // indirect
github.com/minio/highwayhash v1.0.2 // indirect
github.com/nats-io/jwt/v2 v2.4.1 // indirect
github.com/nats-io/nats-server/v2 v2.9.17 // indirect
github.com/nats-io/nats.go v1.24.0 // indirect
github.com/nats-io/nkeys v0.4.4 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
go.uber.org/automaxprocs v1.4.0 // indirect
go.uber.org/automaxprocs v1.5.1 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
)
@ -190,10 +196,10 @@ require (
go.opentelemetry.io/otel/sdk v1.13.0 // indirect
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/term v0.6.0 // indirect
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/term v0.7.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.7.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
gonum.org/v1/gonum v0.9.3 // indirect

43
go.sum
View File

@ -511,8 +511,9 @@ github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0
github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.14.4 h1:eijASRJcobkVtSt81Olfh7JX43osYLwy5krOJo6YEu4=
github.com/klauspost/compress v1.14.4/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
@ -589,6 +590,8 @@ github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpsp
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI=
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4=
github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
github.com/minio/minio-go/v7 v7.0.17 h1:5SiS3pqiQDbNhmXMxtqn2HzAInbN5cbHT7ip9F0F07E=
@ -619,8 +622,17 @@ github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ib
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt/v2 v2.4.1 h1:Y35W1dgbbz2SQUYDPCaclXcuqleVmpbRa7646Jf2EX4=
github.com/nats-io/jwt/v2 v2.4.1/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+vu/+ibI=
github.com/nats-io/nats-server/v2 v2.9.17 h1:gFpUQ3hqIDJrnqog+Bl5vaXg+RhhYEZIElasEuRn2tw=
github.com/nats-io/nats-server/v2 v2.9.17/go.mod h1:eQysm3xDZmIjfkjr7DuD9DjRFpnxQc2vKVxtEg0Dp6s=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nats.go v1.24.0 h1:CRiD8L5GOQu/DcfkmgBcTTIQORMwizF+rPk6T0RaHVQ=
github.com/nats-io/nats.go v1.24.0/go.mod h1:dVQF+BK3SzUZpwyzHedXsvH3EO38aVKuOPkkHlv5hXA=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA=
github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nrwiersma/avro-benchmarks v0.0.0-20210913175520-21aec48c8f76/go.mod h1:iKyFMidsk/sVYONJRE372sJuX/QTRPacU7imPqqsu7g=
@ -674,6 +686,7 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
@ -905,8 +918,8 @@ go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0=
go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk=
go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
@ -934,8 +947,8 @@ golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWP
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -1044,8 +1057,8 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -1082,6 +1095,7 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -1153,13 +1167,13 @@ golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1169,14 +1183,15 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -1,91 +1,24 @@
// Licensed to the LF AI & Data foundation under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msgstream
import (
"context"
"go.uber.org/zap"
rmqimplserver "github.com/milvus-io/milvus/internal/mq/mqimpl/rocksmq/server"
rmqwrapper "github.com/milvus-io/milvus/internal/mq/msgstream/mqwrapper/rmq"
"github.com/milvus-io/milvus/internal/mq/mqimpl/rocksmq/server"
"github.com/milvus-io/milvus/internal/mq/msgstream/mqwrapper/rmq"
"github.com/milvus-io/milvus/pkg/log"
"github.com/milvus-io/milvus/pkg/mq/msgstream"
"github.com/milvus-io/milvus/pkg/mq/msgstream/mqwrapper"
"go.uber.org/zap"
)
// RmsFactory is a rocksmq msgstream factory that implemented Factory interface(msgstream.go)
type RmsFactory struct {
dispatcherFactory msgstream.ProtoUDFactory
// the following members must be public, so that mapstructure.Decode() can access them
ReceiveBufSize int64
RmqBufSize int64
}
// NewMsgStream is used to generate a new Msgstream object
func (f *RmsFactory) NewMsgStream(ctx context.Context) (msgstream.MsgStream, error) {
rmqClient, err := rmqwrapper.NewClientWithDefaultOptions()
if err != nil {
return nil, err
// NewRocksmqFactory creates a new message stream factory based on rocksmq.
func NewRocksmqFactory(path string) msgstream.Factory {
if err := server.InitRocksMQ(path); err != nil {
log.Fatal("fail to init rocksmq", zap.Error(err))
}
return msgstream.NewMqMsgStream(ctx, f.ReceiveBufSize, f.RmqBufSize, rmqClient, f.dispatcherFactory.NewUnmarshalDispatcher())
}
log.Info("init rocksmq msgstream success", zap.String("path", path))
// NewTtMsgStream is used to generate a new TtMsgstream object
func (f *RmsFactory) NewTtMsgStream(ctx context.Context) (msgstream.MsgStream, error) {
rmqClient, err := rmqwrapper.NewClientWithDefaultOptions()
if err != nil {
return nil, err
}
return msgstream.NewMqTtMsgStream(ctx, f.ReceiveBufSize, f.RmqBufSize, rmqClient, f.dispatcherFactory.NewUnmarshalDispatcher())
}
// NewQueryMsgStream is used to generate a new QueryMsgstream object
func (f *RmsFactory) NewQueryMsgStream(ctx context.Context) (msgstream.MsgStream, error) {
rmqClient, err := rmqwrapper.NewClientWithDefaultOptions()
if err != nil {
return nil, err
}
return msgstream.NewMqMsgStream(ctx, f.ReceiveBufSize, f.RmqBufSize, rmqClient, f.dispatcherFactory.NewUnmarshalDispatcher())
}
func (f *RmsFactory) NewMsgStreamDisposer(ctx context.Context) func([]string, string) error {
return func(channels []string, subname string) error {
msgstream, err := f.NewMsgStream(ctx)
if err != nil {
return err
}
msgstream.AsConsumer(channels, subname, mqwrapper.SubscriptionPositionUnknown)
msgstream.Close()
return nil
}
}
// NewRmsFactory is used to generate a new RmsFactory object
func NewRmsFactory(path string) *RmsFactory {
f := &RmsFactory{
dispatcherFactory: msgstream.ProtoUDFactory{},
return &msgstream.CommonFactory{
Newer: rmq.NewClientWithDefaultOptions,
DispatcherFactory: msgstream.ProtoUDFactory{},
ReceiveBufSize: 1024,
RmqBufSize: 1024,
MQBufSize: 1024,
}
err := rmqimplserver.InitRocksMQ(path)
if err != nil {
log.Error("init rmq error", zap.Error(err))
}
return f
}

View File

@ -32,7 +32,7 @@ func TestRmsFactory(t *testing.T) {
dir := t.TempDir()
rmsFactory := NewRmsFactory(dir)
rmsFactory := NewRocksmqFactory(dir)
ctx := context.Background()
_, err := rmsFactory.NewMsgStream(ctx)

View File

@ -19,6 +19,7 @@ package rmq
import (
"strconv"
"github.com/cockroachdb/errors"
"github.com/milvus-io/milvus/internal/mq/mqimpl/rocksmq/client"
"github.com/milvus-io/milvus/internal/mq/mqimpl/rocksmq/server"
"go.uber.org/zap"
@ -27,12 +28,15 @@ import (
"github.com/milvus-io/milvus/pkg/mq/msgstream/mqwrapper"
)
// nmqClient implements mqwrapper.Client.
var _ mqwrapper.Client = &rmqClient{}
// rmqClient contains a rocksmq client
type rmqClient struct {
client client.Client
}
func NewClientWithDefaultOptions() (*rmqClient, error) {
func NewClientWithDefaultOptions() (mqwrapper.Client, error) {
option := client.Options{Server: server.Rmq}
return NewClient(option)
}
@ -60,6 +64,11 @@ func (rc *rmqClient) CreateProducer(options mqwrapper.ProducerOptions) (mqwrappe
// Subscribe subscribes a consumer in rmq client
func (rc *rmqClient) Subscribe(options mqwrapper.ConsumerOptions) (mqwrapper.Consumer, error) {
if options.BufSize == 0 {
err := errors.New("subscription bufSize of rmq should never be zero")
log.Warn("unexpected subscription consumer options", zap.Error(err))
return nil, err
}
receiveChannel := make(chan client.Message, options.BufSize)
cli, err := rc.client.Subscribe(client.ConsumerOptions{

View File

@ -36,10 +36,11 @@ import (
)
func TestMain(m *testing.M) {
paramtable.Init()
rand.Seed(time.Now().UnixNano())
path := "/tmp/milvus/rdb_data"
defer os.RemoveAll(path)
paramtable.Init()
_ = rocksmqimplserver.InitRocksMQ(path)
exitCode := m.Run()
defer rocksmqimplserver.CloseRocksMQ()
@ -153,13 +154,23 @@ func TestRmqClient_Subscribe(t *testing.T) {
subName := "subName"
consumerOpts := mqwrapper.ConsumerOptions{
Topic: subName,
SubscriptionName: subName,
SubscriptionInitialPosition: mqwrapper.SubscriptionPositionEarliest,
BufSize: 0,
}
consumer, err := client.Subscribe(consumerOpts)
assert.NotNil(t, err)
assert.Nil(t, consumer)
consumerOpts = mqwrapper.ConsumerOptions{
Topic: "",
SubscriptionName: subName,
SubscriptionInitialPosition: mqwrapper.SubscriptionPositionEarliest,
BufSize: 1024,
}
consumer, err := client.Subscribe(consumerOpts)
consumer, err = client.Subscribe(consumerOpts)
assert.NotNil(t, err)
assert.Nil(t, consumer)

View File

@ -14,7 +14,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package msgstream
package rmq
import (
"context"
@ -25,8 +25,6 @@ import (
"sync"
"testing"
"github.com/apache/pulsar-client-go/pulsar"
"github.com/confluentinc/confluent-kafka-go/kafka"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -35,48 +33,13 @@ import (
"github.com/milvus-io/milvus/internal/allocator"
etcdkv "github.com/milvus-io/milvus/internal/kv/etcd"
"github.com/milvus-io/milvus/internal/mq/mqimpl/rocksmq/server"
"github.com/milvus-io/milvus/internal/mq/msgstream/mqwrapper/rmq"
"github.com/milvus-io/milvus/pkg/common"
"github.com/milvus-io/milvus/pkg/mq/msgstream"
"github.com/milvus-io/milvus/pkg/mq/msgstream/mqwrapper"
pulsarwrapper "github.com/milvus-io/milvus/pkg/mq/msgstream/mqwrapper/pulsar"
"github.com/milvus-io/milvus/pkg/util/etcd"
"github.com/milvus-io/milvus/pkg/util/funcutil"
"github.com/milvus-io/milvus/pkg/util/paramtable"
)
const (
DefaultPulsarTenant = "public"
DefaultPulsarNamespace = "default"
)
var Params paramtable.ComponentParam
func TestMain(m *testing.M) {
Params.Init()
mockKafkaCluster, err := kafka.NewMockCluster(1)
defer mockKafkaCluster.Close()
if err != nil {
// nolint
fmt.Printf("Failed to create MockCluster: %s\n", err)
os.Exit(1)
}
broker := mockKafkaCluster.BootstrapServers()
Params.Save("kafka.brokerList", broker)
exitCode := m.Run()
os.Exit(exitCode)
}
func getPulsarAddress() string {
pulsarHost := Params.GetWithDefault("pulsar.address", "")
port := Params.GetWithDefault("pulsar.port", "")
if len(pulsarHost) != 0 && len(port) != 0 {
return "pulsar://" + pulsarHost + ":" + port
}
panic("invalid pulsar address")
}
type fixture struct {
t *testing.T
etcdKV *etcdkv.EtcdKV
@ -87,10 +50,6 @@ type parameters struct {
}
func (f *fixture) setup() []parameters {
pulsarAddress := getPulsarAddress()
pulsarClient, err := pulsarwrapper.NewClient(DefaultPulsarTenant, DefaultPulsarNamespace, pulsar.ClientOptions{URL: pulsarAddress})
assert.Nil(f.t, err)
rocksdbName := "/tmp/rocksmq_unittest_" + f.t.Name()
endpoints := os.Getenv("ETCD_ENDPOINTS")
if endpoints == "" {
@ -110,10 +69,9 @@ func (f *fixture) setup() []parameters {
log.Fatalf("InitRmq error = %v", err)
}
rmqClient, _ := rmq.NewClientWithDefaultOptions()
rmqClient, _ := NewClientWithDefaultOptions()
parameters := []parameters{
{pulsarClient},
{rmqClient},
}
return parameters
@ -437,10 +395,11 @@ func initRmqStream(ctx context.Context,
producerChannels []string,
consumerChannels []string,
consumerGroupName string,
opts ...msgstream.RepackFunc) (msgstream.MsgStream, msgstream.MsgStream) {
opts ...msgstream.RepackFunc,
) (msgstream.MsgStream, msgstream.MsgStream) {
factory := msgstream.ProtoUDFactory{}
rmqClient, _ := rmq.NewClientWithDefaultOptions()
rmqClient, _ := NewClientWithDefaultOptions()
inputStream, _ := msgstream.NewMqMsgStream(ctx, 100, 100, rmqClient, factory.NewUnmarshalDispatcher())
inputStream.AsProducer(producerChannels)
for _, opt := range opts {
@ -448,7 +407,7 @@ func initRmqStream(ctx context.Context,
}
var input msgstream.MsgStream = inputStream
rmqClient2, _ := rmq.NewClientWithDefaultOptions()
rmqClient2, _ := NewClientWithDefaultOptions()
outputStream, _ := msgstream.NewMqMsgStream(ctx, 100, 100, rmqClient2, factory.NewUnmarshalDispatcher())
outputStream.AsConsumer(consumerChannels, consumerGroupName, mqwrapper.SubscriptionPositionEarliest)
var output msgstream.MsgStream = outputStream
@ -460,10 +419,11 @@ func initRmqTtStream(ctx context.Context,
producerChannels []string,
consumerChannels []string,
consumerGroupName string,
opts ...msgstream.RepackFunc) (msgstream.MsgStream, msgstream.MsgStream) {
opts ...msgstream.RepackFunc,
) (msgstream.MsgStream, msgstream.MsgStream) {
factory := msgstream.ProtoUDFactory{}
rmqClient, _ := rmq.NewClientWithDefaultOptions()
rmqClient, _ := NewClientWithDefaultOptions()
inputStream, _ := msgstream.NewMqMsgStream(ctx, 100, 100, rmqClient, factory.NewUnmarshalDispatcher())
inputStream.AsProducer(producerChannels)
for _, opt := range opts {
@ -471,7 +431,7 @@ func initRmqTtStream(ctx context.Context,
}
var input msgstream.MsgStream = inputStream
rmqClient2, _ := rmq.NewClientWithDefaultOptions()
rmqClient2, _ := NewClientWithDefaultOptions()
outputStream, _ := msgstream.NewMqTtMsgStream(ctx, 100, 100, rmqClient2, factory.NewUnmarshalDispatcher())
outputStream.AsConsumer(consumerChannels, consumerGroupName, mqwrapper.SubscriptionPositionEarliest)
var output msgstream.MsgStream = outputStream
@ -578,7 +538,7 @@ func TestStream_RmqTtMsgStream_DuplicatedIDs(t *testing.T) {
factory := msgstream.ProtoUDFactory{}
rmqClient, _ := rmq.NewClientWithDefaultOptions()
rmqClient, _ := NewClientWithDefaultOptions()
outputStream, _ = msgstream.NewMqTtMsgStream(context.Background(), 100, 100, rmqClient, factory.NewUnmarshalDispatcher())
consumerSubName = funcutil.RandomString(8)
outputStream.AsConsumer(consumerChannels, consumerSubName, mqwrapper.SubscriptionPositionUnknown)
@ -682,7 +642,7 @@ func TestStream_RmqTtMsgStream_Seek(t *testing.T) {
factory := msgstream.ProtoUDFactory{}
rmqClient, _ := rmq.NewClientWithDefaultOptions()
rmqClient, _ := NewClientWithDefaultOptions()
outputStream, _ = msgstream.NewMqTtMsgStream(context.Background(), 100, 100, rmqClient, factory.NewUnmarshalDispatcher())
consumerSubName = funcutil.RandomString(8)
outputStream.AsConsumer(consumerChannels, consumerSubName, mqwrapper.SubscriptionPositionUnknown)
@ -731,7 +691,7 @@ func TestStream_RMqMsgStream_SeekInvalidMessage(t *testing.T) {
outputStream.Close()
factory := msgstream.ProtoUDFactory{}
rmqClient2, _ := rmq.NewClientWithDefaultOptions()
rmqClient2, _ := NewClientWithDefaultOptions()
outputStream2, _ := msgstream.NewMqMsgStream(ctx, 100, 100, rmqClient2, factory.NewUnmarshalDispatcher())
outputStream2.AsConsumer(consumerChannels, funcutil.RandomString(8), mqwrapper.SubscriptionPositionUnknown)
@ -764,7 +724,6 @@ func TestStream_RMqMsgStream_SeekInvalidMessage(t *testing.T) {
}
func TestStream_RmqTtMsgStream_AsConsumerWithPosition(t *testing.T) {
producerChannels := []string{"insert1"}
consumerChannels := []string{"insert1"}
consumerSubName := "subInsert"
@ -773,7 +732,7 @@ func TestStream_RmqTtMsgStream_AsConsumerWithPosition(t *testing.T) {
etcdKV := initRmq(rocksdbName)
factory := msgstream.ProtoUDFactory{}
rmqClient, _ := rmq.NewClientWithDefaultOptions()
rmqClient, _ := NewClientWithDefaultOptions()
otherInputStream, _ := msgstream.NewMqMsgStream(context.Background(), 100, 100, rmqClient, factory.NewUnmarshalDispatcher())
otherInputStream.AsProducer([]string{"root_timetick"})
@ -786,7 +745,7 @@ func TestStream_RmqTtMsgStream_AsConsumerWithPosition(t *testing.T) {
inputStream.Produce(getTimeTickMsgPack(int64(i)))
}
rmqClient2, _ := rmq.NewClientWithDefaultOptions()
rmqClient2, _ := NewClientWithDefaultOptions()
outputStream, _ := msgstream.NewMqMsgStream(context.Background(), 100, 100, rmqClient2, factory.NewUnmarshalDispatcher())
outputStream.AsConsumer(consumerChannels, consumerSubName, mqwrapper.SubscriptionPositionLatest)

View File

@ -3,12 +3,31 @@ package dependency
import (
"context"
rmqstream "github.com/milvus-io/milvus/internal/mq/msgstream"
"github.com/cockroachdb/errors"
smsgstream "github.com/milvus-io/milvus/internal/mq/msgstream"
"github.com/milvus-io/milvus/internal/storage"
"github.com/milvus-io/milvus/pkg/log"
"github.com/milvus-io/milvus/pkg/mq/msgstream"
"github.com/milvus-io/milvus/pkg/util/paramtable"
"go.uber.org/zap"
)
const (
mqTypeDefault = "default"
mqTypeNatsmq = "natsmq"
mqTypeRocksmq = "rocksmq"
mqTypeKafka = "kafka"
mqTypePulsar = "pulsar"
)
type mqEnable struct {
Rocksmq bool
Natsmq bool
Pulsar bool
Kafka bool
}
// DefaultFactory is a factory that produces instances of storage.ChunkManager and message queue.
type DefaultFactory struct {
standAlone bool
chunkManagerFactory storage.Factory
@ -19,20 +38,23 @@ type DefaultFactory struct {
func NewDefaultFactory(standAlone bool) *DefaultFactory {
return &DefaultFactory{
standAlone: standAlone,
msgStreamFactory: rmqstream.NewRmsFactory("/tmp/milvus/rocksmq/"),
msgStreamFactory: smsgstream.NewRocksmqFactory("/tmp/milvus/rocksmq/"),
chunkManagerFactory: storage.NewChunkManagerFactory("local",
storage.RootPath("/tmp/milvus")),
}
}
// NewFactory creates a new instance of the DefaultFactory type.
// If standAlone is true, the factory will operate in standalone mode.
func NewFactory(standAlone bool) *DefaultFactory {
return &DefaultFactory{standAlone: standAlone}
}
// Init create a msg factory(TODO only support one mq at the same time.)
// In order to guarantee backward compatibility of config file, we still support multiple mq configs.
// 1. Rocksmq only run on local mode, and it has the highest priority
// 2. Pulsar has higher priority than Kafka within remote msg
// The initialization of MQ follows the following rules, if the mq.type is default.
// 1. standalone(local) mode: rocksmq(default) > natsmq > Pulsar > Kafka
// 2. cluster mode: Pulsar(default) > Kafka (rocksmq and natsmq is unsupported in cluster mode)
func (f *DefaultFactory) Init(params *paramtable.ComponentParam) {
// skip if using default factory
if f.msgStreamFactory != nil {
@ -41,41 +63,67 @@ func (f *DefaultFactory) Init(params *paramtable.ComponentParam) {
f.chunkManagerFactory = storage.NewChunkManagerFactoryWithParam(params)
// init mq storage
if f.standAlone {
f.msgStreamFactory = f.initMQLocalService(params)
if f.msgStreamFactory != nil {
return
}
}
f.msgStreamFactory = f.initMQRemoteService(params)
if f.msgStreamFactory == nil {
panic("no available remote mq configuration, must config Pulsar or Kafka at least one of these!")
// initialize mq client or embedded mq.
if err := f.initMQ(f.standAlone, params); err != nil {
panic(err)
}
}
func (f *DefaultFactory) initMQLocalService(params *paramtable.ComponentParam) msgstream.Factory {
if params.RocksmqEnable() {
path, err := params.Load("rocksmq.path")
if err != nil {
panic(err)
}
return rmqstream.NewRmsFactory(path)
func (f *DefaultFactory) initMQ(standalone bool, params *paramtable.ComponentParam) error {
mqType := mustSelectMQType(standalone, params.MQCfg.Type.GetValue(), mqEnable{params.RocksmqEnable(), params.NatsmqEnable(), params.PulsarEnable(), params.KafkaEnable()})
log.Info("try to init mq", zap.Bool("standalone", standalone), zap.String("mqType", mqType))
switch mqType {
case mqTypeNatsmq:
f.msgStreamFactory = msgstream.NewNatsmqFactory()
case mqTypeRocksmq:
f.msgStreamFactory = smsgstream.NewRocksmqFactory(params.RocksmqCfg.Path.GetValue())
case mqTypePulsar:
f.msgStreamFactory = msgstream.NewPmsFactory(&params.PulsarCfg)
case mqTypeKafka:
f.msgStreamFactory = msgstream.NewKmsFactory(&params.KafkaCfg)
}
if f.msgStreamFactory == nil {
return errors.New("failed to create MQ: check the milvus log for initialization failures")
}
return nil
}
// initRemoteService Pulsar has higher priority than Kafka.
func (f *DefaultFactory) initMQRemoteService(params *paramtable.ComponentParam) msgstream.Factory {
if params.PulsarEnable() {
return msgstream.NewPmsFactory(&params.PulsarCfg)
// Select valid mq if mq type is default.
func mustSelectMQType(standalone bool, mqType string, enable mqEnable) string {
if mqType != mqTypeDefault {
if err := validateMQType(standalone, mqType); err != nil {
panic(err)
}
return mqType
}
if params.KafkaEnable() {
return msgstream.NewKmsFactory(&params.KafkaCfg)
if standalone {
if enable.Rocksmq {
return mqTypeRocksmq
}
if enable.Natsmq {
return mqTypeNatsmq
}
}
if enable.Pulsar {
return mqTypePulsar
}
if enable.Kafka {
return mqTypeKafka
}
panic(errors.Errorf("no available mq config found, %s, enable: %+v", mqType, enable))
}
// Validate mq type.
func validateMQType(standalone bool, mqType string) error {
if mqType != mqTypeNatsmq && mqType != mqTypeRocksmq && mqType != mqTypeKafka && mqType != mqTypePulsar {
return errors.Newf("mq type %s is invalid", mqType)
}
if !standalone && (mqType == mqTypeRocksmq || mqType == mqTypeNatsmq) {
return errors.Newf("mq %s is only valid in standalone mode")
}
return nil
}

View File

@ -0,0 +1,35 @@
package dependency
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestValidateMQType(t *testing.T) {
assert.Error(t, validateMQType(true, mqTypeDefault))
assert.Error(t, validateMQType(false, mqTypeDefault))
assert.Error(t, validateMQType(false, mqTypeNatsmq))
assert.Error(t, validateMQType(false, mqTypeRocksmq))
}
func TestSelectMQType(t *testing.T) {
assert.Equal(t, mustSelectMQType(true, mqTypeDefault, mqEnable{true, true, true, true}), mqTypeRocksmq)
assert.Equal(t, mustSelectMQType(true, mqTypeDefault, mqEnable{false, true, true, true}), mqTypeNatsmq)
assert.Equal(t, mustSelectMQType(true, mqTypeDefault, mqEnable{false, false, true, true}), mqTypePulsar)
assert.Equal(t, mustSelectMQType(true, mqTypeDefault, mqEnable{false, false, false, true}), mqTypeKafka)
assert.Panics(t, func() { mustSelectMQType(true, mqTypeDefault, mqEnable{false, false, false, false}) })
assert.Equal(t, mustSelectMQType(false, mqTypeDefault, mqEnable{true, true, true, true}), mqTypePulsar)
assert.Equal(t, mustSelectMQType(false, mqTypeDefault, mqEnable{false, true, true, true}), mqTypePulsar)
assert.Equal(t, mustSelectMQType(false, mqTypeDefault, mqEnable{false, false, true, true}), mqTypePulsar)
assert.Equal(t, mustSelectMQType(false, mqTypeDefault, mqEnable{false, false, false, true}), mqTypeKafka)
assert.Panics(t, func() { mustSelectMQType(false, mqTypeDefault, mqEnable{false, false, false, false}) })
assert.Equal(t, mustSelectMQType(true, mqTypeRocksmq, mqEnable{true, true, true, true}), mqTypeRocksmq)
assert.Equal(t, mustSelectMQType(true, mqTypeNatsmq, mqEnable{true, true, true, true}), mqTypeNatsmq)
assert.Equal(t, mustSelectMQType(true, mqTypePulsar, mqEnable{true, true, true, true}), mqTypePulsar)
assert.Equal(t, mustSelectMQType(true, mqTypeKafka, mqEnable{true, true, true, true}), mqTypeKafka)
assert.Panics(t, func() { mustSelectMQType(false, mqTypeRocksmq, mqEnable{true, true, true, true}) })
assert.Panics(t, func() { mustSelectMQType(false, mqTypeNatsmq, mqEnable{true, true, true, true}) })
assert.Equal(t, mustSelectMQType(false, mqTypePulsar, mqEnable{true, true, true, true}), mqTypePulsar)
assert.Equal(t, mustSelectMQType(false, mqTypeKafka, mqEnable{true, true, true, true}), mqTypeKafka)
}

View File

@ -10,7 +10,7 @@ require (
github.com/containerd/cgroups v1.0.4
github.com/golang/protobuf v1.5.2
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/klauspost/compress v1.14.4
github.com/klauspost/compress v1.16.5
github.com/lingdor/stackerror v0.0.0-20191119040541-976d8885ed76
github.com/milvus-io/milvus-proto/go-api v0.0.0-20230529034923-4579ee9d5723
github.com/panjf2000/ants/v2 v2.7.2
@ -25,9 +25,9 @@ require (
go.etcd.io/etcd/client/v3 v3.5.5
go.etcd.io/etcd/server/v3 v3.5.5
go.uber.org/atomic v1.10.0
go.uber.org/automaxprocs v1.4.0
go.uber.org/automaxprocs v1.5.1
go.uber.org/zap v1.17.0
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
golang.org/x/crypto v0.8.0
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17
golang.org/x/sync v0.1.0
google.golang.org/grpc v1.52.3
@ -104,10 +104,10 @@ require (
go.opentelemetry.io/otel/sdk v1.13.0
go.opentelemetry.io/otel/trace v1.13.0
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/term v0.7.0 // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
@ -120,6 +120,8 @@ require github.com/ianlancetaylor/cgosymbolizer v0.0.0-20221217025313-27d3c9f66b
require (
github.com/cockroachdb/errors v1.9.1
github.com/nats-io/nats-server/v2 v2.9.17
github.com/nats-io/nats.go v1.24.0
github.com/uber/jaeger-client-go v2.30.0+incompatible
go.opentelemetry.io/otel/exporters/jaeger v1.13.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.13.0
@ -138,6 +140,10 @@ require (
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/minio/highwayhash v1.0.2 // indirect
github.com/nats-io/jwt/v2 v2.4.1 // indirect
github.com/nats-io/nkeys v0.4.4 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rogpeppe/go-internal v1.8.1 // indirect
@ -146,7 +152,7 @@ require (
go.opentelemetry.io/otel/metric v0.35.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
)
replace (

View File

@ -412,8 +412,9 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.14.4 h1:eijASRJcobkVtSt81Olfh7JX43osYLwy5krOJo6YEu4=
github.com/klauspost/compress v1.14.4/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -469,6 +470,8 @@ github.com/milvus-io/milvus-proto/go-api v0.0.0-20230529034923-4579ee9d5723 h1:V
github.com/milvus-io/milvus-proto/go-api v0.0.0-20230529034923-4579ee9d5723/go.mod h1:148qnlmZ0Fdm1Fq+Mj/OW2uDoEP25g3mjh0vMGtkgmk=
github.com/milvus-io/pulsar-client-go v0.6.10 h1:eqpJjU+/QX0iIhEo3nhOqMNXL+TyInAs1IAHZCrCM/A=
github.com/milvus-io/pulsar-client-go v0.6.10/go.mod h1:lQqCkgwDF8YFYjKA+zOheTk1tev2B+bKj5j7+nm8M1w=
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@ -491,8 +494,17 @@ github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ib
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt/v2 v2.4.1 h1:Y35W1dgbbz2SQUYDPCaclXcuqleVmpbRa7646Jf2EX4=
github.com/nats-io/jwt/v2 v2.4.1/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+vu/+ibI=
github.com/nats-io/nats-server/v2 v2.9.17 h1:gFpUQ3hqIDJrnqog+Bl5vaXg+RhhYEZIElasEuRn2tw=
github.com/nats-io/nats-server/v2 v2.9.17/go.mod h1:eQysm3xDZmIjfkjr7DuD9DjRFpnxQc2vKVxtEg0Dp6s=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nats.go v1.24.0 h1:CRiD8L5GOQu/DcfkmgBcTTIQORMwizF+rPk6T0RaHVQ=
github.com/nats-io/nats.go v1.24.0/go.mod h1:dVQF+BK3SzUZpwyzHedXsvH3EO38aVKuOPkkHlv5hXA=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA=
github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
@ -541,6 +553,7 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
@ -742,8 +755,8 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0=
go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk=
go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
@ -764,8 +777,9 @@ golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -853,8 +867,8 @@ golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -891,6 +905,7 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -958,12 +973,12 @@ golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -973,14 +988,15 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -0,0 +1,73 @@
package msgstream
import (
"context"
"github.com/cockroachdb/errors"
"github.com/milvus-io/milvus/pkg/mq/msgstream/mqwrapper"
)
var _ Factory = &CommonFactory{}
// CommonFactory is a Factory for creating message streams with common logic.
//
// It contains a function field named newer, which is a function that creates
// an mqwrapper.Client when called.
type CommonFactory struct {
Newer func() (mqwrapper.Client, error) // client constructor
DispatcherFactory ProtoUDFactory
ReceiveBufSize int64
MQBufSize int64
}
// NewMsgStream is used to generate a new Msgstream object
func (f *CommonFactory) NewMsgStream(ctx context.Context) (ms MsgStream, err error) {
defer wrapError(&err, "NewMsgStream")
cli, err := f.Newer()
if err != nil {
return nil, err
}
return NewMqMsgStream(ctx, f.ReceiveBufSize, f.MQBufSize, cli, f.DispatcherFactory.NewUnmarshalDispatcher())
}
// NewTtMsgStream is used to generate a new TtMsgstream object
func (f *CommonFactory) NewTtMsgStream(ctx context.Context) (ms MsgStream, err error) {
defer wrapError(&err, "NewTtMsgStream")
cli, err := f.Newer()
if err != nil {
return nil, err
}
return NewMqTtMsgStream(ctx, f.ReceiveBufSize, f.MQBufSize, cli, f.DispatcherFactory.NewUnmarshalDispatcher())
}
// NewQueryMsgStream is used to generate a new QueryMsgstream object
func (f *CommonFactory) NewQueryMsgStream(ctx context.Context) (ms MsgStream, err error) {
defer wrapError(&err, "NewQueryMsgStream")
cli, err := f.Newer()
if err != nil {
return nil, err
}
return NewMqMsgStream(ctx, f.ReceiveBufSize, f.MQBufSize, cli, f.DispatcherFactory.NewUnmarshalDispatcher())
}
// NewMsgStreamDisposer returns a function that can be used to dispose of a message stream.
// The returned function takes a slice of channel names and a subscription name, and
// disposes of the message stream associated with those arguments.
func (f *CommonFactory) NewMsgStreamDisposer(ctx context.Context) func([]string, string) error {
return func(channels []string, subName string) (err error) {
defer wrapError(&err, "NewMsgStreamDisposer")
msgs, err := f.NewMsgStream(ctx)
if err != nil {
return err
}
msgs.AsConsumer(channels, subName, mqwrapper.SubscriptionPositionUnknown)
msgs.Close()
return nil
}
}
func wrapError(err *error, method string) {
if *err != nil {
*err = errors.Wrapf(*err, "in method: %s", method)
}
}

View File

@ -0,0 +1,844 @@
package msgstream
import (
"context"
"fmt"
"log"
"reflect"
"runtime"
"testing"
"github.com/milvus-io/milvus-proto/go-api/commonpb"
"github.com/milvus-io/milvus-proto/go-api/msgpb"
"github.com/milvus-io/milvus/pkg/mq/msgstream/mqwrapper"
"github.com/milvus-io/milvus/pkg/util/funcutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type streamNewer func(ctx context.Context) (MsgStream, error)
// test all stream operation on stream factory
func testMsgStreamOperation(t *testing.T, factories []Factory, pg positionGenerator) {
testFuncs := []func(t *testing.T, f []Factory){
testInsert,
testDelete,
testTimeTick,
testBroadCast,
testInsertWithRepack,
testInsertRepackFuncWithDifferentClient,
testDeleteRepackFuncWithDifferentClient,
testDefaultRepack,
testTimeTickerAndInsert,
testTimeTickerNoSeek,
testSeekToLast,
testTimeTickerSeek,
testTimeTickUnmarshalHeader,
testTimeTickerStream1,
testTimeTickerStream2,
testMqMsgStreamSeek,
func(t *testing.T, f []Factory) {
testMqMsgStreamSeekInvalidMessage(t, f, pg)
},
testMqMsgStreamSeekLatest,
testBroadcastMark,
}
for _, testFunc := range testFuncs {
t.Run(
runtime.FuncForPC(reflect.ValueOf(testFunc).Pointer()).Name(),
func(t *testing.T) {
testFunc(t, factories)
},
)
}
}
func testInsert(t *testing.T, f []Factory) {
msgPack := MsgPack{}
msgPack.Msgs = append(msgPack.Msgs, getTsMsg(commonpb.MsgType_Insert, 1))
msgPack.Msgs = append(msgPack.Msgs, getTsMsg(commonpb.MsgType_Insert, 3))
applyProduceAndConsume(t, &msgPack, []streamNewer{f[0].NewMsgStream, f[0].NewMsgStream}, 2)
}
func testDelete(t *testing.T, f []Factory) {
msgPack := MsgPack{}
msgPack.Msgs = append(msgPack.Msgs, getTsMsg(commonpb.MsgType_Delete, 1))
applyProduceAndConsume(t, &msgPack, []streamNewer{f[0].NewMsgStream, f[0].NewMsgStream}, 1)
}
func testTimeTick(t *testing.T, f []Factory) {
msgPack := MsgPack{}
msgPack.Msgs = append(msgPack.Msgs, getTsMsg(commonpb.MsgType_TimeTick, 1))
msgPack.Msgs = append(msgPack.Msgs, getTsMsg(commonpb.MsgType_TimeTick, 3))
applyProduceAndConsume(t, &msgPack, []streamNewer{f[0].NewTtMsgStream, f[0].NewTtMsgStream}, 1)
}
func testBroadCast(t *testing.T, f []Factory) {
msgPack := MsgPack{}
msgPack.Msgs = append(msgPack.Msgs, getTsMsg(commonpb.MsgType_TimeTick, 1))
msgPack.Msgs = append(msgPack.Msgs, getTsMsg(commonpb.MsgType_TimeTick, 3))
applyBroadCastAndConsume(t, &msgPack, []streamNewer{f[0].NewTtMsgStream, f[0].NewTtMsgStream}, 2)
}
func testInsertWithRepack(t *testing.T, f []Factory) {
msgPack := MsgPack{}
msgPack.Msgs = append(msgPack.Msgs, getTsMsg(commonpb.MsgType_Insert, 1))
msgPack.Msgs = append(msgPack.Msgs, getTsMsg(commonpb.MsgType_Insert, 3))
applyProduceAndConsumeWithRepack(t, &msgPack, []streamNewer{f[0].NewMsgStream, f[0].NewMsgStream}, 2)
}
func testInsertRepackFuncWithDifferentClient(t *testing.T, f []Factory) {
baseMsg := BaseMsg{
BeginTimestamp: 0,
EndTimestamp: 0,
HashValues: []uint32{1, 3},
}
insertRequest := msgpb.InsertRequest{
Base: &commonpb.MsgBase{
MsgType: commonpb.MsgType_Insert,
MsgID: 1,
Timestamp: 1,
SourceID: 1,
},
CollectionName: "Collection",
PartitionName: "Partition",
SegmentID: 1,
ShardName: "1",
Timestamps: []Timestamp{1, 1},
RowIDs: []int64{1, 3},
RowData: []*commonpb.Blob{{}, {}},
}
insertMsg := &InsertMsg{
BaseMsg: baseMsg,
InsertRequest: insertRequest,
}
msgPack := MsgPack{}
msgPack.Msgs = append(msgPack.Msgs, insertMsg)
applyProduceAndConsumeWithRepack(t, &msgPack, []streamNewer{f[0].NewMsgStream, f[1].NewMsgStream}, 2)
}
func testDeleteRepackFuncWithDifferentClient(t *testing.T, f []Factory) {
baseMsg := BaseMsg{
BeginTimestamp: 0,
EndTimestamp: 0,
HashValues: []uint32{1},
}
deleteRequest := msgpb.DeleteRequest{
Base: &commonpb.MsgBase{
MsgType: commonpb.MsgType_Delete,
MsgID: 1,
Timestamp: 1,
SourceID: 1,
},
CollectionName: "Collection",
ShardName: "chan-1",
Timestamps: []Timestamp{1},
Int64PrimaryKeys: []int64{1},
NumRows: 1,
}
deleteMsg := &DeleteMsg{
BaseMsg: baseMsg,
DeleteRequest: deleteRequest,
}
msgPack := MsgPack{}
msgPack.Msgs = append(msgPack.Msgs, deleteMsg)
applyProduceAndConsumeWithRepack(t, &msgPack, []streamNewer{f[0].NewMsgStream, f[1].NewMsgStream}, 2)
}
func testDefaultRepack(t *testing.T, f []Factory) {
msgPack := MsgPack{}
msgPack.Msgs = append(msgPack.Msgs, getTsMsg(commonpb.MsgType_TimeTick, 1))
msgPack.Msgs = append(msgPack.Msgs, getTsMsg(commonpb.MsgType_Insert, 2))
msgPack.Msgs = append(msgPack.Msgs, getTsMsg(commonpb.MsgType_Delete, 3))
applyProduceAndConsumeWithRepack(t, &msgPack, []streamNewer{f[0].NewMsgStream, f[1].NewMsgStream}, 2)
}
func testTimeTickerAndInsert(t *testing.T, f []Factory) {
msgPack0 := MsgPack{}
msgPack0.Msgs = append(msgPack0.Msgs, getTimeTickMsg(0))
msgPack1 := MsgPack{}
msgPack1.Msgs = append(msgPack1.Msgs, getTsMsg(commonpb.MsgType_Insert, 1))
msgPack1.Msgs = append(msgPack1.Msgs, getTsMsg(commonpb.MsgType_Insert, 3))
msgPack2 := MsgPack{}
msgPack2.Msgs = append(msgPack2.Msgs, getTimeTickMsg(5))
ctx := context.Background()
producer, consumer := createStream(ctx, t, []streamNewer{f[0].NewMsgStream, f[1].NewMsgStream}, getChannel(2))
defer producer.Close()
defer consumer.Close()
var err error
_, err = producer.Broadcast(&msgPack0)
assert.Nil(t, err)
err = producer.Produce(&msgPack1)
assert.Nil(t, err)
_, err = producer.Broadcast(&msgPack2)
assert.Nil(t, err)
receiveAndValidateMsg(ctx, consumer, len(msgPack1.Msgs))
}
func testTimeTickerNoSeek(t *testing.T, f []Factory) {
msgPack0 := MsgPack{}
msgPack0.Msgs = append(msgPack0.Msgs, getTimeTickMsg(0))
msgPack1 := MsgPack{}
msgPack1.Msgs = append(msgPack1.Msgs, getTsMsg(commonpb.MsgType_Insert, 1))
msgPack1.Msgs = append(msgPack1.Msgs, getTsMsg(commonpb.MsgType_Insert, 19))
msgPack2 := MsgPack{}
msgPack2.Msgs = append(msgPack2.Msgs, getTimeTickMsg(5))
msgPack3 := MsgPack{}
msgPack3.Msgs = append(msgPack3.Msgs, getTsMsg(commonpb.MsgType_Insert, 14))
msgPack3.Msgs = append(msgPack3.Msgs, getTsMsg(commonpb.MsgType_Insert, 9))
msgPack4 := MsgPack{}
msgPack4.Msgs = append(msgPack4.Msgs, getTimeTickMsg(11))
msgPack5 := MsgPack{}
msgPack5.Msgs = append(msgPack5.Msgs, getTimeTickMsg(15))
channels := getChannel(1)
ctx := context.Background()
producer, consumer := createStream(ctx, t, []streamNewer{f[0].NewMsgStream, f[1].NewTtMsgStream}, channels)
defer producer.Close()
var err error
_, err = producer.Broadcast(&msgPack0)
assert.Nil(t, err)
err = producer.Produce(&msgPack1)
assert.Nil(t, err)
_, err = producer.Broadcast(&msgPack2)
assert.Nil(t, err)
err = producer.Produce(&msgPack3)
assert.Nil(t, err)
_, err = producer.Broadcast(&msgPack4)
assert.Nil(t, err)
_, err = producer.Broadcast(&msgPack5)
assert.Nil(t, err)
o1 := consume(ctx, consumer)
o2 := consume(ctx, consumer)
o3 := consume(ctx, consumer)
t.Log(o1.BeginTs)
t.Log(o2.BeginTs)
t.Log(o3.BeginTs)
consumer.Close()
producer2, consumer := createStream(ctx, t, []streamNewer{f[0].NewMsgStream, f[1].NewTtMsgStream}, channels)
defer producer2.Close()
defer consumer.Close()
p1 := consume(ctx, consumer)
p2 := consume(ctx, consumer)
p3 := consume(ctx, consumer)
t.Log(p1.BeginTs)
t.Log(p2.BeginTs)
t.Log(p3.BeginTs)
assert.Equal(t, o1.BeginTs, p1.BeginTs)
assert.Equal(t, o2.BeginTs, p2.BeginTs)
assert.Equal(t, o3.BeginTs, p3.BeginTs)
}
func testSeekToLast(t *testing.T, f []Factory) {
ctx := context.Background()
channels := getChannel(1)
producer, consumer := createStream(ctx, t, []streamNewer{f[0].NewMsgStream, f[1].NewMsgStream}, channels)
defer producer.Close()
msgPack := &MsgPack{}
for i := 0; i < 10; i++ {
insertMsg := getTsMsg(commonpb.MsgType_Insert, int64(i))
msgPack.Msgs = append(msgPack.Msgs, insertMsg)
}
// produce test data
err := producer.Produce(msgPack)
assert.Nil(t, err)
// pick a seekPosition
var seekPosition *msgpb.MsgPosition
for i := 0; i < 10; i++ {
result := consume(ctx, consumer)
assert.Equal(t, result.Msgs[0].ID(), int64(i))
if i == 5 {
seekPosition = result.EndPositions[0]
}
}
consumer.Close()
// Create a unknown position consumer and seek.
consumer = createAndSeekConsumer(ctx, t, f[1].NewMsgStream, channels, []*msgpb.MsgPosition{seekPosition})
defer consumer.Close()
// Get latest MsgID.
lastMsgID, err := consumer.GetLatestMsgID(channels[0])
assert.Nil(t, err)
cnt := 0
var value int64 = 6
hasMore := true
for hasMore {
select {
case <-ctx.Done():
hasMore = false
case msgPack, ok := <-consumer.Chan():
if !ok {
assert.Fail(t, "Should not reach here")
}
assert.Equal(t, 1, len(msgPack.Msgs))
for _, tsMsg := range msgPack.Msgs {
assert.Equal(t, value, tsMsg.ID())
value++
cnt++
ret, err := lastMsgID.LessOrEqualThan(tsMsg.Position().MsgID)
assert.Nil(t, err)
if ret {
hasMore = false
break
}
}
}
}
assert.Equal(t, 4, cnt)
}
func testTimeTickerSeek(t *testing.T, f []Factory) {
msgPack0 := MsgPack{}
msgPack0.Msgs = append(msgPack0.Msgs, getTimeTickMsg(0))
msgPack1 := MsgPack{}
msgPack1.Msgs = append(msgPack1.Msgs, getTsMsg(commonpb.MsgType_Insert, 1))
msgPack1.Msgs = append(msgPack1.Msgs, getTsMsg(commonpb.MsgType_Insert, 3))
msgPack1.Msgs = append(msgPack1.Msgs, getTsMsg(commonpb.MsgType_Insert, 19))
msgPack2 := MsgPack{}
msgPack2.Msgs = append(msgPack2.Msgs, getTimeTickMsg(5))
msgPack3 := MsgPack{}
msgPack3.Msgs = append(msgPack3.Msgs, getTsMsg(commonpb.MsgType_Insert, 14))
msgPack3.Msgs = append(msgPack3.Msgs, getTsMsg(commonpb.MsgType_Insert, 9))
msgPack4 := MsgPack{}
msgPack4.Msgs = append(msgPack4.Msgs, getTimeTickMsg(11))
msgPack5 := MsgPack{}
msgPack5.Msgs = append(msgPack5.Msgs, getTsMsg(commonpb.MsgType_Insert, 12))
msgPack5.Msgs = append(msgPack5.Msgs, getTsMsg(commonpb.MsgType_Insert, 13))
msgPack6 := MsgPack{}
msgPack6.Msgs = append(msgPack6.Msgs, getTimeTickMsg(15))
msgPack7 := MsgPack{}
msgPack7.Msgs = append(msgPack7.Msgs, getTimeTickMsg(20))
ctx := context.Background()
channels := getChannel(1)
producer, consumer := createStream(ctx, t, []streamNewer{f[0].NewMsgStream, f[1].NewTtMsgStream}, channels)
defer producer.Close()
// Send message
_, err := producer.Broadcast(&msgPack0)
assert.Nil(t, err)
err = producer.Produce(&msgPack1)
assert.Nil(t, err)
_, err = producer.Broadcast(&msgPack2)
assert.Nil(t, err)
err = producer.Produce(&msgPack3)
assert.Nil(t, err)
_, err = producer.Broadcast(&msgPack4)
assert.Nil(t, err)
err = producer.Produce(&msgPack5)
assert.Nil(t, err)
_, err = producer.Broadcast(&msgPack6)
assert.Nil(t, err)
_, err = producer.Broadcast(&msgPack7)
assert.Nil(t, err)
// Test received message
receivedMsg := consume(ctx, consumer)
assert.Equal(t, len(receivedMsg.Msgs), 2)
assert.Equal(t, receivedMsg.BeginTs, uint64(0))
assert.Equal(t, receivedMsg.EndTs, uint64(5))
assert.Equal(t, receivedMsg.StartPositions[0].Timestamp, uint64(0))
assert.Equal(t, receivedMsg.EndPositions[0].Timestamp, uint64(5))
receivedMsg2 := consume(ctx, consumer)
assert.Equal(t, len(receivedMsg2.Msgs), 1)
assert.Equal(t, receivedMsg2.BeginTs, uint64(5))
assert.Equal(t, receivedMsg2.EndTs, uint64(11))
assert.Equal(t, receivedMsg2.StartPositions[0].Timestamp, uint64(5))
assert.Equal(t, receivedMsg2.EndPositions[0].Timestamp, uint64(11))
receivedMsg3 := consume(ctx, consumer)
assert.Equal(t, len(receivedMsg3.Msgs), 3)
assert.Equal(t, receivedMsg3.BeginTs, uint64(11))
assert.Equal(t, receivedMsg3.EndTs, uint64(15))
assert.Equal(t, receivedMsg3.StartPositions[0].Timestamp, uint64(11))
assert.Equal(t, receivedMsg3.EndPositions[0].Timestamp, uint64(15))
receivedMsg4 := consume(ctx, consumer)
assert.Equal(t, len(receivedMsg4.Msgs), 1)
assert.Equal(t, receivedMsg4.BeginTs, uint64(15))
assert.Equal(t, receivedMsg4.EndTs, uint64(20))
assert.Equal(t, receivedMsg4.StartPositions[0].Timestamp, uint64(15))
assert.Equal(t, receivedMsg4.EndPositions[0].Timestamp, uint64(20))
consumer.Close()
consumer = createAndSeekConsumer(ctx, t, f[1].NewTtMsgStream, channels, receivedMsg3.StartPositions)
seekMsg := consume(ctx, consumer)
assert.Equal(t, len(seekMsg.Msgs), 3)
result := []uint64{14, 12, 13}
for i, msg := range seekMsg.Msgs {
assert.Equal(t, msg.BeginTs(), result[i])
}
seekMsg2 := consume(ctx, consumer)
assert.Equal(t, len(seekMsg2.Msgs), 1)
for _, msg := range seekMsg2.Msgs {
assert.Equal(t, msg.BeginTs(), uint64(19))
}
consumer.Close()
consumer = createAndSeekConsumer(ctx, t, f[0].NewTtMsgStream, channels, receivedMsg3.EndPositions)
seekMsg = consume(ctx, consumer)
assert.Equal(t, len(seekMsg.Msgs), 1)
for _, msg := range seekMsg.Msgs {
assert.Equal(t, msg.BeginTs(), uint64(19))
}
consumer.Close()
}
func testTimeTickUnmarshalHeader(t *testing.T, f []Factory) {
msgPack0 := MsgPack{}
msgPack0.Msgs = append(msgPack0.Msgs, getTimeTickMsg(0))
msgPack1 := MsgPack{}
msgPack1.Msgs = append(msgPack1.Msgs, getTsMsg(commonpb.MsgType_Insert, 1))
msgPack1.Msgs = append(msgPack1.Msgs, getTsMsg(commonpb.MsgType_Insert, 3))
msgPack2 := MsgPack{}
msgPack2.Msgs = append(msgPack2.Msgs, getTimeTickMsg(5))
channels := getChannel(2)
ctx := context.Background()
producer, consumer := createStream(ctx, t, []streamNewer{f[0].NewMsgStream, f[1].NewTtMsgStream}, channels)
defer producer.Close()
defer consumer.Close()
_, err := producer.Broadcast(&msgPack0)
require.NoErrorf(t, err, fmt.Sprintf("broadcast error = %v", err))
err = producer.Produce(&msgPack1)
require.NoErrorf(t, err, fmt.Sprintf("produce error = %v", err))
_, err = producer.Broadcast(&msgPack2)
require.NoErrorf(t, err, fmt.Sprintf("broadcast error = %v", err))
receiveAndValidateMsg(ctx, consumer, len(msgPack1.Msgs))
}
func testTimeTickerStream1(t *testing.T, f []Factory) {
consumeChannels := getChannel(2)
pubChannel1 := []string{consumeChannels[0]}
pubChannel2 := []string{consumeChannels[1]}
ctx := context.Background()
producer1 := createProducer(ctx, t, f[0].NewMsgStream, pubChannel1)
defer producer1.Close()
msgPacks1 := createRandMsgPacks(3, 10, 10)
assert.Nil(t, sendMsgPacks(producer1, msgPacks1))
producer2 := createProducer(ctx, t, f[0].NewMsgStream, pubChannel2)
defer producer2.Close()
msgPacks2 := createRandMsgPacks(5, 10, 10)
assert.Nil(t, sendMsgPacks(producer2, msgPacks2))
// consume msg
consumer := createConsumer(ctx, t, f[1].NewTtMsgStream, consumeChannels)
defer consumer.Close()
log.Println("===============receive msg=================")
checkNMsgPack := func(t *testing.T, outputStream MsgStream, num int) int {
rcvMsg := 0
for i := 0; i < num; i++ {
msgPack := consume(ctx, consumer)
rcvMsg += len(msgPack.Msgs)
if len(msgPack.Msgs) > 0 {
for _, msg := range msgPack.Msgs {
log.Println("msg type: ", msg.Type(), ", msg value: ", msg)
assert.Greater(t, msg.BeginTs(), msgPack.BeginTs)
assert.LessOrEqual(t, msg.BeginTs(), msgPack.EndTs)
}
log.Println("================")
}
}
return rcvMsg
}
msgCount := checkNMsgPack(t, consumer, len(msgPacks1)/2)
cnt1 := (len(msgPacks1)/2 - 1) * len(msgPacks1[0].Msgs)
cnt2 := (len(msgPacks2)/2 - 1) * len(msgPacks2[0].Msgs)
assert.Equal(t, (cnt1 + cnt2), msgCount)
}
// This testcase will generate MsgPacks as following:
//
// Insert Insert Insert Insert Insert Insert
//
// c1 |----------|----------|----------|----------|----------|----------|
//
// ^ ^ ^ ^ ^ ^
// TT(10) TT(20) TT(30) TT(40) TT(50) TT(100)
//
// Insert Insert Insert Insert Insert Insert
//
// c2 |----------|----------|----------|----------|----------|----------|
//
// ^ ^ ^ ^ ^ ^
// TT(10) TT(20) TT(30) TT(40) TT(50) TT(100)
//
// Then check:
// 1. ttMsgStream consumer can seek to the right position and resume
// 2. The count of consumed msg should be equal to the count of produced msg
func testTimeTickerStream2(t *testing.T, f []Factory) {
consumeChannels := getChannel(2)
pubChannel1 := []string{consumeChannels[0]}
pubChannel2 := []string{consumeChannels[1]}
ctx := context.Background()
producer1 := createProducer(ctx, t, f[0].NewMsgStream, pubChannel1)
defer producer1.Close()
msgPacks1 := createRandMsgPacks(3, 10, 10)
assert.Nil(t, sendMsgPacks(producer1, msgPacks1))
producer2 := createProducer(ctx, t, f[0].NewMsgStream, pubChannel2)
defer producer2.Close()
msgPacks2 := createRandMsgPacks(5, 10, 10)
assert.Nil(t, sendMsgPacks(producer2, msgPacks2))
// consume msg
log.Println("=============receive msg===================")
rcvMsgPacks := make([]*MsgPack, 0)
resumeMsgPack := func(t *testing.T) int {
var consumer MsgStream
msgCount := len(rcvMsgPacks)
if msgCount == 0 {
consumer = createConsumer(ctx, t, f[1].NewTtMsgStream, consumeChannels)
} else {
consumer = createAndSeekConsumer(ctx, t, f[1].NewTtMsgStream, consumeChannels, rcvMsgPacks[msgCount-1].EndPositions)
}
msgPack := consume(ctx, consumer)
rcvMsgPacks = append(rcvMsgPacks, msgPack)
if len(msgPack.Msgs) > 0 {
for _, msg := range msgPack.Msgs {
log.Println("msg type: ", msg.Type(), ", msg value: ", msg)
assert.Greater(t, msg.BeginTs(), msgPack.BeginTs)
assert.LessOrEqual(t, msg.BeginTs(), msgPack.EndTs)
}
log.Println("================")
}
consumer.Close()
return len(rcvMsgPacks[msgCount].Msgs)
}
msgCount := 0
for i := 0; i < len(msgPacks1)/2; i++ {
msgCount += resumeMsgPack(t)
}
cnt1 := (len(msgPacks1)/2 - 1) * len(msgPacks1[0].Msgs)
cnt2 := (len(msgPacks2)/2 - 1) * len(msgPacks2[0].Msgs)
assert.Equal(t, (cnt1 + cnt2), msgCount)
}
func testMqMsgStreamSeek(t *testing.T, f []Factory) {
channels := getChannel(1)
ctx := context.Background()
producer, consumer := createStream(ctx, t, []streamNewer{f[0].NewMsgStream, f[1].NewMsgStream}, channels)
defer producer.Close()
msgPack := &MsgPack{}
for i := 0; i < 10; i++ {
insertMsg := getTsMsg(commonpb.MsgType_Insert, int64(i))
msgPack.Msgs = append(msgPack.Msgs, insertMsg)
}
err := producer.Produce(msgPack)
assert.Nil(t, err)
var seekPosition *msgpb.MsgPosition
for i := 0; i < 10; i++ {
result := consume(ctx, consumer)
assert.Equal(t, result.Msgs[0].ID(), int64(i))
if i == 5 {
seekPosition = result.EndPositions[0]
}
}
consumer.Close()
consumer = createAndSeekConsumer(ctx, t, f[0].NewMsgStream, channels, []*msgpb.MsgPosition{seekPosition})
for i := 6; i < 10; i++ {
result := consume(ctx, consumer)
assert.Equal(t, result.Msgs[0].ID(), int64(i))
}
consumer.Close()
}
func testMqMsgStreamSeekInvalidMessage(t *testing.T, f []Factory, pg positionGenerator) {
channels := getChannel(1)
ctx := context.Background()
producer, consumer := createStream(ctx, t, []streamNewer{f[0].NewMsgStream, f[1].NewMsgStream}, channels)
defer producer.Close()
defer consumer.Close()
msgPack := &MsgPack{}
for i := 0; i < 10; i++ {
insertMsg := getTsMsg(commonpb.MsgType_Insert, int64(i))
msgPack.Msgs = append(msgPack.Msgs, insertMsg)
}
err := producer.Produce(msgPack)
assert.Nil(t, err)
var seekPosition *msgpb.MsgPosition
for i := 0; i < 10; i++ {
result := consume(ctx, consumer)
assert.Equal(t, result.Msgs[0].ID(), int64(i))
seekPosition = result.EndPositions[0]
}
p := pg(seekPosition.ChannelName, seekPosition.Timestamp, seekPosition.MsgGroup, []uint64{13})
consumer2 := createAndSeekConsumer(ctx, t, f[1].NewMsgStream, channels, p)
defer consumer2.Close()
for i := 10; i < 20; i++ {
insertMsg := getTsMsg(commonpb.MsgType_Insert, int64(i))
msgPack.Msgs = append(msgPack.Msgs, insertMsg)
}
err = producer.Produce(msgPack)
assert.Nil(t, err)
result := consume(ctx, consumer2)
assert.Equal(t, result.Msgs[0].ID(), int64(1))
}
func testMqMsgStreamSeekLatest(t *testing.T, f []Factory) {
channels := getChannel(1)
ctx := context.Background()
producer, consumer := createStream(ctx, t, []streamNewer{f[0].NewMsgStream, f[1].NewMsgStream}, channels)
defer producer.Close()
defer consumer.Close()
msgPack := &MsgPack{}
for i := 0; i < 10; i++ {
insertMsg := getTsMsg(commonpb.MsgType_Insert, int64(i))
msgPack.Msgs = append(msgPack.Msgs, insertMsg)
}
err := producer.Produce(msgPack)
assert.Nil(t, err)
consumer2 := createLatestConsumer(ctx, t, f[1].NewMsgStream, channels)
defer consumer2.Close()
msgPack.Msgs = nil
// produce another 10 tsMs
for i := 10; i < 20; i++ {
insertMsg := getTsMsg(commonpb.MsgType_Insert, int64(i))
msgPack.Msgs = append(msgPack.Msgs, insertMsg)
}
err = producer.Produce(msgPack)
assert.Nil(t, err)
for i := 10; i < 20; i++ {
result := consume(ctx, consumer2)
assert.Equal(t, result.Msgs[0].ID(), int64(i))
}
}
func testBroadcastMark(t *testing.T, f []Factory) {
channels := getChannel(2)
ctx := context.Background()
producer, consumer := createStream(ctx, t, []streamNewer{f[0].NewMsgStream, f[1].NewMsgStream}, channels)
defer producer.Close()
defer consumer.Close()
msgPack0 := MsgPack{}
msgPack0.Msgs = append(msgPack0.Msgs, getTimeTickMsg(0))
ids, err := producer.Broadcast(&msgPack0)
assert.Nil(t, err)
assert.NotNil(t, ids)
assert.Equal(t, len(channels), len(ids))
for _, c := range channels {
ids, ok := ids[c]
assert.True(t, ok)
assert.Equal(t, len(msgPack0.Msgs), len(ids))
}
msgPack1 := MsgPack{}
msgPack1.Msgs = append(msgPack1.Msgs, getTsMsg(commonpb.MsgType_Insert, 1))
msgPack1.Msgs = append(msgPack1.Msgs, getTsMsg(commonpb.MsgType_Insert, 3))
ids, err = producer.Broadcast(&msgPack1)
assert.Nil(t, err)
assert.NotNil(t, ids)
assert.Equal(t, len(channels), len(ids))
for _, c := range channels {
ids, ok := ids[c]
assert.True(t, ok)
assert.Equal(t, len(msgPack1.Msgs), len(ids))
}
// edge cases
_, err = producer.Broadcast(nil)
assert.NotNil(t, err)
msgPack2 := MsgPack{}
msgPack2.Msgs = append(msgPack2.Msgs, &MarshalFailTsMsg{})
_, err = producer.Broadcast(&msgPack2)
assert.NotNil(t, err)
}
func applyBroadCastAndConsume(t *testing.T, msgPack *MsgPack, newer []streamNewer, channelNum int) {
producer, consumer := createStream(context.Background(), t, newer, getChannel(channelNum))
defer producer.Close()
defer consumer.Close()
_, err := producer.Broadcast(msgPack)
assert.Nil(t, err)
receiveAndValidateMsg(context.Background(), consumer, len(msgPack.Msgs)*channelNum)
}
func applyProduceAndConsumeWithRepack(
t *testing.T,
msgPack *MsgPack,
newer []streamNewer,
channelNum int,
) {
producer, consumer := createStream(context.Background(), t, newer, getChannel(channelNum))
producer.SetRepackFunc(repackFunc)
defer producer.Close()
defer consumer.Close()
err := producer.Produce(msgPack)
assert.Nil(t, err)
receiveAndValidateMsg(context.Background(), consumer, len(msgPack.Msgs))
}
func applyProduceAndConsume(
t *testing.T,
msgPack *MsgPack,
newer []streamNewer,
channelNum int,
) {
producer, consumer := createStream(context.Background(), t, newer, getChannel(channelNum))
defer producer.Close()
defer consumer.Close()
err := producer.Produce(msgPack)
assert.Nil(t, err)
receiveAndValidateMsg(context.Background(), consumer, len(msgPack.Msgs))
}
func consume(ctx context.Context, mq MsgStream) *MsgPack {
for {
select {
case msgPack, ok := <-mq.Chan():
if !ok {
panic("Should not reach here")
}
return msgPack
case <-ctx.Done():
return nil
}
}
}
func createAndSeekConsumer(ctx context.Context, t *testing.T, newer streamNewer, channels []string, seekPositions []*msgpb.MsgPosition) MsgStream {
consumer, err := newer(ctx)
assert.Nil(t, err)
consumer.AsConsumer(channels, funcutil.RandomString(8), mqwrapper.SubscriptionPositionUnknown)
err = consumer.Seek(seekPositions)
assert.Nil(t, err)
return consumer
}
func createProducer(ctx context.Context, t *testing.T, newer streamNewer, channels []string) MsgStream {
producer, err := newer(ctx)
assert.Nil(t, err)
producer.AsProducer(channels)
return producer
}
func createConsumer(ctx context.Context, t *testing.T, newer streamNewer, channels []string) MsgStream {
consumer, err := newer(ctx)
assert.Nil(t, err)
consumer.AsConsumer(channels, funcutil.RandomString(8), mqwrapper.SubscriptionPositionEarliest)
return consumer
}
func createLatestConsumer(ctx context.Context, t *testing.T, newer streamNewer, channels []string) MsgStream {
consumer, err := newer(ctx)
assert.Nil(t, err)
consumer.AsConsumer(channels, funcutil.RandomString(8), mqwrapper.SubscriptionPositionLatest)
return consumer
}
func createStream(ctx context.Context, t *testing.T, newer []streamNewer, channels []string) (
MsgStream, MsgStream,
) {
assert.NotEmpty(t, channels)
producer, err := newer[0](ctx)
assert.Nil(t, err)
producer.AsProducer(channels)
consumer, err := newer[1](ctx)
assert.Nil(t, err)
consumer.AsConsumer(channels, funcutil.RandomString(8), mqwrapper.SubscriptionPositionEarliest)
return producer, consumer
}
func getChannel(n int) []string {
channels := make([]string, 0, n)
for i := 0; i < n; i++ {
channels = append(channels, funcutil.RandomString(8))
}
return channels
}
func receiveAndValidateMsg(ctx context.Context, outputStream MsgStream, msgCount int) {
receiveCount := 0
for {
select {
case <-ctx.Done():
return
case result, ok := <-outputStream.Chan():
if !ok || result == nil || len(result.Msgs) == 0 {
return
}
if len(result.Msgs) > 0 {
msgs := result.Msgs
for _, v := range msgs {
receiveCount++
log.Println("msg type: ", v.Type(), ", msg value: ", v)
}
log.Println("================")
}
if receiveCount >= msgCount {
return
}
}
}
}

View File

@ -0,0 +1,90 @@
// Licensed to the LF AI & Data foundation under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msgstream
import (
"context"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/milvus-io/milvus-proto/go-api/msgpb"
"github.com/milvus-io/milvus/pkg/mq/msgstream/mqwrapper"
"github.com/milvus-io/milvus/pkg/mq/msgstream/mqwrapper/nmq"
"github.com/milvus-io/milvus/pkg/util/paramtable"
)
type positionGenerator func(channelName string, timestamp uint64, msgGroup string, targetMsgIDs []uint64) []*msgpb.MsgPosition
func TestNmq(t *testing.T) {
storeDir, err := os.MkdirTemp("", "milvus_mq_nmq")
assert.Nil(t, err)
defer os.RemoveAll(storeDir)
paramtable.Init()
cfg := nmq.ParseServerOption(paramtable.Get())
cfg.Opts.StoreDir = storeDir
nmq.MustInitNatsMQ(cfg)
defer nmq.CloseNatsMQ()
f1 := NewNatsmqFactory()
f2 := NewNatsmqFactory()
client, err := nmq.NewClientWithDefaultOptions()
if err != nil {
panic(err)
}
testMQ(t, client, []Factory{f1, f2}, func(channelName string, timestamp uint64, msgGroup string, targetMsgIDs []uint64) []*msgpb.MsgPosition {
result := make([]*msgpb.MsgPosition, 0, len(targetMsgIDs))
for _, targetMsgID := range targetMsgIDs {
msgID := nmq.NewNmqID(targetMsgID).Serialize()
result = append(result, &msgpb.MsgPosition{
ChannelName: channelName,
Timestamp: timestamp,
MsgGroup: msgGroup,
MsgID: msgID,
})
}
return result
})
}
func testMQ(t *testing.T, client mqwrapper.Client, factories []Factory, pg positionGenerator) {
testStreamOperation(t, client)
testFactoryCommonOperation(t, factories[0])
testMsgStreamOperation(t, factories, pg)
}
// testFactoryOperation test common factory operation.
func testFactoryCommonOperation(t *testing.T, f Factory) {
var err error
ctx := context.Background()
_, err = f.NewMsgStream(ctx)
assert.Nil(t, err)
_, err = f.NewTtMsgStream(ctx)
assert.Nil(t, err)
_, err = f.NewQueryMsgStream(ctx)
assert.Nil(t, err)
err = f.NewMsgStreamDisposer(ctx)([]string{"hello"}, "xx")
assert.Nil(t, err)
}

View File

@ -29,6 +29,7 @@ import (
"github.com/milvus-io/milvus/pkg/log"
"github.com/milvus-io/milvus/pkg/mq/msgstream/mqwrapper"
kafkawrapper "github.com/milvus-io/milvus/pkg/mq/msgstream/mqwrapper/kafka"
"github.com/milvus-io/milvus/pkg/mq/msgstream/mqwrapper/nmq"
pulsarmqwrapper "github.com/milvus-io/milvus/pkg/mq/msgstream/mqwrapper/pulsar"
"github.com/milvus-io/milvus/pkg/util/paramtable"
"github.com/milvus-io/milvus/pkg/util/retry"
@ -99,7 +100,6 @@ func (f *PmsFactory) NewTtMsgStream(ctx context.Context) (MsgStream, error) {
func (f *PmsFactory) getAuthentication() (pulsar.Authentication, error) {
auth, err := pulsar.NewAuthentication(f.PulsarAuthPlugin, f.PulsarAuthParams)
if err != nil {
log.Error("build authencation from config failed, please check it!",
zap.String("authPlugin", f.PulsarAuthPlugin),
@ -188,3 +188,15 @@ func NewKmsFactory(config *paramtable.KafkaConfig) Factory {
}
return f
}
// NewNatsmqFactory create a new nats-mq factory.
func NewNatsmqFactory() Factory {
paramtable.Init()
nmq.MustInitNatsMQ(nmq.ParseServerOption(paramtable.Get()))
return &CommonFactory{
Newer: nmq.NewClientWithDefaultOptions,
DispatcherFactory: ProtoUDFactory{},
ReceiveBufSize: 1024,
MQBufSize: 1024,
}
}

View File

@ -0,0 +1,148 @@
// Licensed to the LF AI & Data foundation under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nmq
import (
"fmt"
"strconv"
"github.com/cockroachdb/errors"
"github.com/nats-io/nats.go"
"go.uber.org/zap"
"github.com/milvus-io/milvus/pkg/log"
"github.com/milvus-io/milvus/pkg/mq/msgstream/mqwrapper"
)
// nmqClient implements mqwrapper.Client.
var _ mqwrapper.Client = &nmqClient{}
// nmqClient contains a natsmq client
type nmqClient struct {
conn *nats.Conn
}
// NewClientWithDefaultOptions returns a new NMQ client with default options.
// It retrieves the NMQ client URL from the server configuration.
func NewClientWithDefaultOptions() (mqwrapper.Client, error) {
url := Nmq.ClientURL()
log.Info("123123 ", zap.String("url", url))
return NewClient(url)
}
// NewClient returns a new nmqClient object
func NewClient(url string, options ...nats.Option) (*nmqClient, error) {
c, err := nats.Connect(url, options...)
if err != nil {
return nil, errors.Wrap(err, "failed to set nmq client")
}
return &nmqClient{conn: c}, nil
}
// CreateProducer creates a producer for natsmq client
func (nc *nmqClient) CreateProducer(options mqwrapper.ProducerOptions) (mqwrapper.Producer, error) {
// TODO: inject jetstream options.
js, err := nc.conn.JetStream()
if err != nil {
return nil, errors.Wrap(err, "failed to create jetstream context")
}
// TODO: (1) investigate on performance of multiple streams vs multiple topics.
// (2) investigate if we should have topics under the same stream.
_, err = js.AddStream(&nats.StreamConfig{
Name: options.Topic,
Subjects: []string{options.Topic},
})
if err != nil {
return nil, errors.Wrap(err, "failed to add/connect to jetstream for producer")
}
rp := nmqProducer{js: js, topic: options.Topic}
return &rp, nil
}
func (nc *nmqClient) Subscribe(options mqwrapper.ConsumerOptions) (mqwrapper.Consumer, error) {
if options.Topic == "" {
return nil, fmt.Errorf("invalid consumer config: empty topic")
}
if options.SubscriptionName == "" {
return nil, fmt.Errorf("invalid consumer config: empty subscription name")
}
// TODO: inject jetstream options.
js, err := nc.conn.JetStream()
if err != nil {
return nil, errors.Wrap(err, "failed to create jetstream context")
}
// TODO: do we allow passing in an existing natsChan from options?
// also, revisit the size or make it a user param
natsChan := make(chan *nats.Msg, options.BufSize)
// TODO: should we allow subscribe to a topic that doesn't exist yet? Current logic allows it.
_, err = js.AddStream(&nats.StreamConfig{
Name: options.Topic,
Subjects: []string{options.Topic},
})
if err != nil {
return nil, errors.Wrap(err, "failed to add/connect to jetstream for consumer")
}
closeChan := make(chan struct{})
var sub *nats.Subscription
position := options.SubscriptionInitialPosition
// TODO: should we only allow exclusive subscribe? Current logic allows double subscribe.
switch position {
case mqwrapper.SubscriptionPositionLatest:
sub, err = js.ChanSubscribe(options.Topic, natsChan, nats.DeliverNew())
case mqwrapper.SubscriptionPositionEarliest:
sub, err = js.ChanSubscribe(options.Topic, natsChan, nats.DeliverAll())
}
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("failed to get consumer info, subscribe position: %d", position))
}
return &Consumer{
js: js,
sub: sub,
topic: options.Topic,
groupName: options.SubscriptionName,
options: options,
natsChan: natsChan,
closeChan: closeChan,
}, nil
}
// EarliestMessageID returns the earliest message ID for nmq client
func (nc *nmqClient) EarliestMessageID() mqwrapper.MessageID {
return &nmqID{messageID: 1}
}
// StringToMsgID converts string id to MessageID
func (nc *nmqClient) StringToMsgID(id string) (mqwrapper.MessageID, error) {
rID, err := strconv.ParseUint(id, 10, 64)
if err != nil {
return nil, errors.Wrap(err, "failed to parse string to MessageID")
}
return &nmqID{messageID: rID}, nil
}
// BytesToMsgID converts a byte array to messageID
func (nc *nmqClient) BytesToMsgID(id []byte) (mqwrapper.MessageID, error) {
rID := DeserializeNmqID(id)
return &nmqID{messageID: rID}, nil
}
func (nc *nmqClient) Close() {
nc.conn.Close()
}

View File

@ -0,0 +1,237 @@
// Licensed to the LF AI & Data foundation under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nmq
import (
"context"
"fmt"
"math/rand"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/milvus-io/milvus/pkg/mq/msgstream/mqwrapper"
)
func createNmqClient() (*nmqClient, error) {
return NewClient(natsServerAddress)
}
func Test_NewNmqClient(t *testing.T) {
client, err := createNmqClient()
assert.Nil(t, err)
assert.NotNil(t, client)
client.Close()
}
func TestNmqClient_CreateProducer(t *testing.T) {
client, err := createNmqClient()
assert.Nil(t, err)
assert.NotNil(t, client)
defer client.Close()
topic := "TestNmqClient_CreateProducer"
proOpts := mqwrapper.ProducerOptions{Topic: topic}
producer, err := client.CreateProducer(proOpts)
assert.Nil(t, err)
assert.NotNil(t, producer)
defer producer.Close()
nmqProducer := producer.(*nmqProducer)
assert.Equal(t, nmqProducer.Topic(), topic)
msg := &mqwrapper.ProducerMessage{
Payload: []byte{},
Properties: nil,
}
_, err = nmqProducer.Send(context.TODO(), msg)
assert.Nil(t, err)
invalidOpts := mqwrapper.ProducerOptions{Topic: ""}
producer, e := client.CreateProducer(invalidOpts)
assert.Nil(t, producer)
assert.Error(t, e)
}
func TestNmqClient_GetLatestMsg(t *testing.T) {
client, err := createNmqClient()
assert.Nil(t, err)
defer client.Close()
topic := fmt.Sprintf("t2GetLatestMsg-%d", rand.Int())
proOpts := mqwrapper.ProducerOptions{Topic: topic}
producer, err := client.CreateProducer(proOpts)
assert.Nil(t, err)
defer producer.Close()
for i := 0; i < 10; i++ {
msg := &mqwrapper.ProducerMessage{
Payload: []byte{byte(i)},
Properties: nil,
}
_, err = producer.Send(context.TODO(), msg)
assert.Nil(t, err)
}
subName := "subName"
consumerOpts := mqwrapper.ConsumerOptions{
Topic: topic,
SubscriptionName: subName,
SubscriptionInitialPosition: mqwrapper.SubscriptionPositionEarliest,
BufSize: 1024,
}
consumer, err := client.Subscribe(consumerOpts)
assert.Nil(t, err)
expectLastMsg, err := consumer.GetLatestMsgID()
assert.Nil(t, err)
var actualLastMsg mqwrapper.Message
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
for i := 0; i < 10; i++ {
select {
case <-ctx.Done():
fmt.Println(i)
assert.FailNow(t, "consumer failed to yield message in 100 milliseconds")
case msg := <-consumer.Chan():
consumer.Ack(msg)
actualLastMsg = msg
}
}
require.NotNil(t, actualLastMsg)
ret, err := expectLastMsg.LessOrEqualThan(actualLastMsg.ID().Serialize())
assert.Nil(t, err)
assert.True(t, ret)
}
func TestNmqClient_IllegalSubscribe(t *testing.T) {
client, err := createNmqClient()
assert.Nil(t, err)
assert.NotNil(t, client)
defer client.Close()
sub, err := client.Subscribe(mqwrapper.ConsumerOptions{
Topic: "",
})
assert.Nil(t, sub)
assert.Error(t, err)
sub, err = client.Subscribe(mqwrapper.ConsumerOptions{
Topic: "123",
SubscriptionName: "",
})
assert.Nil(t, sub)
assert.Error(t, err)
}
func TestNmqClient_Subscribe(t *testing.T) {
client, err := createNmqClient()
assert.Nil(t, err)
assert.NotNil(t, client)
defer client.Close()
topic := "TestNmqClient_Subscribe"
proOpts := mqwrapper.ProducerOptions{Topic: topic}
producer, err := client.CreateProducer(proOpts)
assert.Nil(t, err)
assert.NotNil(t, producer)
defer producer.Close()
subName := "subName"
consumerOpts := mqwrapper.ConsumerOptions{
Topic: "",
SubscriptionName: subName,
SubscriptionInitialPosition: mqwrapper.SubscriptionPositionEarliest,
BufSize: 1024,
}
consumer, err := client.Subscribe(consumerOpts)
assert.NotNil(t, err)
assert.Nil(t, consumer)
consumerOpts.Topic = topic
consumer, err = client.Subscribe(consumerOpts)
assert.Nil(t, err)
assert.NotNil(t, consumer)
defer consumer.Close()
assert.Equal(t, consumer.Subscription(), subName)
msg := &mqwrapper.ProducerMessage{
Payload: []byte{1},
Properties: nil,
}
_, err = producer.Send(context.TODO(), msg)
assert.Nil(t, err)
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
select {
case <-ctx.Done():
assert.FailNow(t, "consumer failed to yield message in 100 milliseconds")
case msg := <-consumer.Chan():
consumer.Ack(msg)
nmqmsg := msg.(*nmqMessage)
msgPayload := nmqmsg.Payload()
assert.NotEmpty(t, msgPayload)
msgTopic := nmqmsg.Topic()
assert.Equal(t, msgTopic, topic)
msgProp := nmqmsg.Properties()
assert.Empty(t, msgProp)
msgID := nmqmsg.ID()
rID := msgID.(*nmqID)
assert.Equal(t, rID.messageID, MessageIDType(1))
}
}
func TestNmqClient_EarliestMessageID(t *testing.T) {
client, _ := createNmqClient()
defer client.Close()
mid := client.EarliestMessageID()
assert.NotNil(t, mid)
nmqmsg := mid.(*nmqID)
assert.Equal(t, nmqmsg.messageID, MessageIDType(1))
}
func TestNmqClient_StringToMsgID(t *testing.T) {
client, _ := createNmqClient()
defer client.Close()
str := "5"
res, err := client.StringToMsgID(str)
assert.Nil(t, err)
assert.NotNil(t, res)
str = "X"
res, err = client.StringToMsgID(str)
assert.Nil(t, res)
assert.NotNil(t, err)
}
func TestNmqClient_BytesToMsgID(t *testing.T) {
client, _ := createNmqClient()
defer client.Close()
mid := client.EarliestMessageID()
res, err := client.BytesToMsgID(mid.Serialize())
assert.Nil(t, err)
assert.NotNil(t, res)
}

View File

@ -0,0 +1,185 @@
// Licensed to the LF AI & Data foundation under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nmq
import (
"fmt"
"sync"
"github.com/cockroachdb/errors"
"github.com/nats-io/nats.go"
"go.uber.org/zap"
"github.com/milvus-io/milvus/pkg/log"
"github.com/milvus-io/milvus/pkg/mq/msgstream/mqwrapper"
"github.com/milvus-io/milvus/pkg/util/merr"
)
// Consumer is a client that used to consume messages from natsmq
type Consumer struct {
options mqwrapper.ConsumerOptions
js nats.JetStreamContext
sub *nats.Subscription
topic string
groupName string
natsChan chan *nats.Msg
msgChan chan mqwrapper.Message
closeChan chan struct{}
once sync.Once
closeOnce sync.Once
skip bool
wg sync.WaitGroup
}
// Subscription returns the subscription name of this consumer
func (nc *Consumer) Subscription() string {
return nc.groupName
}
// Chan returns a channel to read messages from natsmq
func (nc *Consumer) Chan() <-chan mqwrapper.Message {
if err := nc.closed(); err != nil {
panic(err)
}
if nc.sub == nil {
log.Error("accessing Chan of an uninitialized subscription.", zap.String("topic", nc.topic), zap.String("groupName", nc.groupName))
panic("failed to chan a consumer without assign")
}
if nc.msgChan == nil {
nc.once.Do(func() {
nc.msgChan = make(chan mqwrapper.Message, 256)
nc.wg.Add(1)
go func() {
defer nc.wg.Done()
for {
select {
case msg := <-nc.natsChan:
if nc.skip {
nc.skip = false
continue
}
nc.msgChan <- &nmqMessage{
raw: msg,
}
case <-nc.closeChan:
log.Info("close nmq consumer ", zap.String("topic", nc.topic), zap.String("groupName", nc.groupName))
close(nc.msgChan)
return
}
}
}()
})
}
return nc.msgChan
}
// Seek is used to seek the position in natsmq topic
func (nc *Consumer) Seek(id mqwrapper.MessageID, inclusive bool) error {
if err := nc.closed(); err != nil {
return err
}
if nc.sub != nil {
return errors.New("can not seek() on an initilized consumer")
}
if nc.msgChan != nil {
return errors.New("Seek should be called before Chan")
}
log.Info("Seek is called", zap.String("topic", nc.topic), zap.Any("id", id))
msgID := id.(*nmqID).messageID
// skip the first message when consume
nc.skip = !inclusive
var err error
nc.sub, err = nc.js.ChanSubscribe(nc.topic, nc.natsChan, nats.StartSequence(msgID))
if err != nil {
log.Warn("fail to Seek", zap.Error(err))
}
return err
}
// Ack is used to ask a natsmq message
func (nc *Consumer) Ack(message mqwrapper.Message) {
if err := message.(*nmqMessage).raw.Ack(); err != nil {
log.Warn("failed to ack message of nmq", zap.String("topic", message.Topic()), zap.Reflect("msgID", message.ID()))
}
}
// Close is used to free the resources of this consumer
func (nc *Consumer) Close() {
nc.closeOnce.Do(func() {
if err := nc.sub.Unsubscribe(); err != nil {
log.Warn("failed to unsubscribe subscription of nmq", zap.String("topic", nc.topic))
}
close(nc.closeChan)
nc.wg.Wait()
})
}
// GetLatestMsgID returns the ID of the most recent message processed by the consumer.
func (nc *Consumer) GetLatestMsgID() (mqwrapper.MessageID, error) {
if err := nc.closed(); err != nil {
return nil, err
}
info, err := nc.js.StreamInfo(nc.topic)
if err != nil {
log.Warn("fail to get stream info of nats", zap.String("topic", nc.topic), zap.Error(err))
return nil, errors.Wrap(err, "failed to get stream info of nats jetstream")
}
msgID := info.State.LastSeq
return &nmqID{messageID: msgID}, nil
}
// CheckTopicValid verifies if the given topic is valid for this consumer.
// 1. topic should exist.
// 2. topic should be empty.
func (nc *Consumer) CheckTopicValid(topic string) error {
if err := nc.closed(); err != nil {
return err
}
// A consumer is tied to a topic. In a multi-tenant situation,
// a consumer is not supposed to check on other topics.
if topic != nc.topic {
return fmt.Errorf("consumer of topic %s checking validness of topic %s", nc.topic, topic)
}
// check if topic valid or exist.
streamInfo, err := nc.js.StreamInfo(topic)
if errors.Is(err, nats.ErrStreamNotFound) {
return merr.WrapErrTopicNotFound(topic, err.Error())
} else if err != nil {
log.Warn("fail to get stream info of nats", zap.String("topic", nc.topic), zap.Error(err))
return errors.Wrap(err, "failed to get stream info of nats jetstream")
}
// check if topic stream is empty.
if streamInfo.State.Msgs > 0 {
return merr.WrapErrTopicNotEmpty(topic, "stream in nats is not empty")
}
return nil
}
// Closed check if Consumer is closed.
func (nc *Consumer) closed() error {
select {
case <-nc.closeChan:
return errors.Newf("Closed Nmq Consumer, topic: %s, subscription name:", nc.topic, nc.groupName)
default:
return nil
}
}

View File

@ -0,0 +1,470 @@
// Licensed to the LF AI & Data foundation under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nmq
import (
"context"
"reflect"
"strings"
"testing"
"time"
"github.com/nats-io/nats.go"
"github.com/stretchr/testify/assert"
"github.com/milvus-io/milvus/pkg/mq/msgstream/mqwrapper"
"github.com/milvus-io/milvus/pkg/util/merr"
)
func TestNatsConsumer_Subscription(t *testing.T) {
client, err := createNmqClient()
assert.NoError(t, err)
defer client.Close()
topic := t.Name()
proOpts := mqwrapper.ProducerOptions{Topic: topic}
_, err = client.CreateProducer(proOpts)
assert.NoError(t, err)
consumer, err := client.Subscribe(mqwrapper.ConsumerOptions{
Topic: topic,
SubscriptionName: topic,
SubscriptionInitialPosition: mqwrapper.SubscriptionPositionEarliest,
BufSize: 1024,
})
assert.NoError(t, err)
assert.NotNil(t, consumer)
defer consumer.Close()
str := consumer.Subscription()
assert.NotNil(t, str)
}
func Test_GetEarliestMessageID(t *testing.T) {
client, err := createNmqClient()
assert.NoError(t, err)
defer client.Close()
mid := client.EarliestMessageID()
assert.NotNil(t, mid)
assert.Equal(t, mid.(*nmqID).messageID, MessageIDType(1))
}
func Test_BadLatestMessageID(t *testing.T) {
topic := t.Name()
client, err := createNmqClient()
assert.NoError(t, err)
defer client.Close()
consumer, err := client.Subscribe(mqwrapper.ConsumerOptions{
Topic: topic,
SubscriptionName: topic,
SubscriptionInitialPosition: mqwrapper.SubscriptionPositionEarliest,
BufSize: 1024,
})
assert.NoError(t, err)
consumer.Close()
id, err := consumer.GetLatestMsgID()
assert.Nil(t, id)
assert.Error(t, err)
}
func TestComsumeMessage(t *testing.T) {
client, err := createNmqClient()
assert.NoError(t, err)
defer client.Close()
topic := t.Name()
p, err := client.CreateProducer(mqwrapper.ProducerOptions{Topic: topic})
assert.NoError(t, err)
c, err := client.Subscribe(mqwrapper.ConsumerOptions{
Topic: topic,
SubscriptionName: topic,
SubscriptionInitialPosition: mqwrapper.SubscriptionPositionEarliest,
BufSize: 1024,
})
assert.NoError(t, err)
defer c.Close()
msg := []byte("test the first message")
prop := map[string]string{"k1": "v1", "k2": "v2"}
_, err = p.Send(context.Background(), &mqwrapper.ProducerMessage{
Payload: msg,
Properties: prop,
})
assert.NoError(t, err)
recvMsg, err := c.(*Consumer).sub.NextMsg(1 * time.Second)
assert.NoError(t, err)
recvMsg.Ack()
assert.NoError(t, err)
assert.Equal(t, msg, recvMsg.Data)
properties := make(map[string]string)
for k, vs := range recvMsg.Header {
if len(vs) > 0 {
properties[k] = vs[0]
}
}
assert.True(t, reflect.DeepEqual(prop, properties))
msg2 := []byte("test the second message")
prop2 := map[string]string{"k1": "v3", "k4": "v4"}
_, err = p.Send(context.Background(), &mqwrapper.ProducerMessage{
Payload: msg2,
Properties: prop2,
})
assert.NoError(t, err)
recvMsg, err = c.(*Consumer).sub.NextMsg(1 * time.Second)
assert.NoError(t, err)
recvMsg.Ack()
assert.Equal(t, msg2, recvMsg.Data)
properties = make(map[string]string)
for k, vs := range recvMsg.Header {
if len(vs) > 0 {
properties[k] = vs[0]
}
}
assert.True(t, reflect.DeepEqual(prop2, properties))
assert.NoError(t, err)
assert.NotNil(t, c)
}
func TestNatsConsumer_Close(t *testing.T) {
client, err := createNmqClient()
assert.NoError(t, err)
defer client.Close()
topic := t.Name()
c, err := client.Subscribe(mqwrapper.ConsumerOptions{
Topic: topic,
SubscriptionName: topic,
SubscriptionInitialPosition: mqwrapper.SubscriptionPositionEarliest,
BufSize: 1024,
})
assert.NoError(t, err)
assert.NotNil(t, c)
str := c.Subscription()
assert.NotNil(t, str)
c.Close()
// Disallow double close.
assert.Panics(t, func() { c.Chan() })
assert.Error(t, c.Seek(NewNmqID(1), false))
assert.Error(t, func() error { _, err := c.GetLatestMsgID(); return err }())
c.Close() // Allow double close, nothing happened.
}
func TestNatsClientErrorOnUnsubscribeTwice(t *testing.T) {
topic := t.Name()
client, err := createNmqClient()
assert.NoError(t, err)
defer client.Close()
consumer, err := client.Subscribe(mqwrapper.ConsumerOptions{
Topic: topic,
SubscriptionName: topic,
SubscriptionInitialPosition: mqwrapper.SubscriptionPositionEarliest,
BufSize: 1024,
})
assert.NoError(t, err)
defer consumer.Close()
err = consumer.(*Consumer).sub.Unsubscribe()
assert.NoError(t, err)
err = consumer.(*Consumer).sub.Unsubscribe()
assert.True(t, strings.Contains(err.Error(), "invalid subscription"))
t.Log(err)
}
func TestCheckTopicValid(t *testing.T) {
client, err := createNmqClient()
assert.NoError(t, err)
defer client.Close()
topic := t.Name()
consumer, err := client.Subscribe(mqwrapper.ConsumerOptions{
Topic: topic,
SubscriptionName: topic,
SubscriptionInitialPosition: mqwrapper.SubscriptionPositionEarliest,
BufSize: 1024,
})
assert.NoError(t, err)
assert.NotNil(t, consumer)
str := consumer.Subscription()
assert.NotNil(t, str)
// empty topic should pass
err = consumer.CheckTopicValid(topic)
assert.NoError(t, err)
// Not allowed to check other topic's validness.
err = consumer.CheckTopicValid("BadTopic")
assert.Error(t, err)
// non empty topic should fail
pub, err := client.CreateProducer(mqwrapper.ProducerOptions{
Topic: topic,
})
assert.NoError(t, err)
_, err = pub.Send(context.TODO(), &mqwrapper.ProducerMessage{
Payload: []byte("123123123"),
})
assert.NoError(t, err)
err = consumer.CheckTopicValid(topic)
assert.ErrorIs(t, err, merr.ErrTopicNotEmpty)
consumer.Close()
err = consumer.CheckTopicValid(topic)
assert.Error(t, err)
}
func newTestConsumer(topic string, fromEarliest bool, fromLatest bool) (mqwrapper.Consumer, error) {
conn, err := nats.Connect(natsServerAddress)
if err != nil {
return nil, err
}
js, err := conn.JetStream()
if err != nil {
return nil, err
}
groupName := topic
_, err = js.AddStream(&nats.StreamConfig{
Name: groupName,
Subjects: []string{topic},
})
if err != nil {
return nil, err
}
natsChan := make(chan *nats.Msg, 8192)
closeChan := make(chan struct{})
return &Consumer{
js: js,
topic: topic,
groupName: groupName,
natsChan: natsChan,
closeChan: closeChan,
skip: false,
}, nil
}
func newProducer(t *testing.T, topic string) (*nmqClient, mqwrapper.Producer) {
client, err := createNmqClient()
assert.NoError(t, err)
producer, err := client.CreateProducer(mqwrapper.ProducerOptions{Topic: topic})
assert.NoError(t, err)
return client, producer
}
func process(t *testing.T, msgs []string, p mqwrapper.Producer) {
for _, msg := range msgs {
_, err := p.Send(context.Background(), &mqwrapper.ProducerMessage{
Payload: []byte(msg),
Properties: map[string]string{},
})
assert.NoError(t, err)
}
}
func TestNmqConsumer_GetLatestMsgID(t *testing.T) {
client, err := createNmqClient()
assert.NoError(t, err)
defer client.Close()
topic := t.Name()
p, err := client.CreateProducer(mqwrapper.ProducerOptions{Topic: topic})
assert.NoError(t, err)
c, err := client.Subscribe(mqwrapper.ConsumerOptions{
Topic: topic,
SubscriptionName: topic,
SubscriptionInitialPosition: mqwrapper.SubscriptionPositionEarliest,
BufSize: 1024,
})
assert.NoError(t, err)
defer c.Close()
latestMsgID, err := c.GetLatestMsgID()
assert.NoError(t, err)
msgs := []string{"111", "222", "333", "444", "555"}
newMessageID := latestMsgID.(*nmqID).messageID + uint64(len(msgs))
process(t, msgs, p)
latestMsgID, err = c.GetLatestMsgID()
assert.NoError(t, err)
assert.Equal(t, newMessageID, latestMsgID.(*nmqID).messageID)
}
func TestNmqConsumer_ConsumeFromLatest(t *testing.T) {
client, err := createNmqClient()
assert.NoError(t, err)
defer client.Close()
topic := t.Name()
p, err := client.CreateProducer(mqwrapper.ProducerOptions{Topic: topic})
assert.NoError(t, err)
msgs := []string{"111", "222", "333"}
process(t, msgs, p)
c, err := client.Subscribe(mqwrapper.ConsumerOptions{
Topic: topic,
SubscriptionName: topic,
SubscriptionInitialPosition: mqwrapper.SubscriptionPositionLatest,
BufSize: 1024,
})
assert.NoError(t, err)
defer c.Close()
msgs = []string{"444", "555"}
process(t, msgs, p)
msg := <-c.Chan()
assert.Equal(t, "444", string(msg.Payload()))
msg = <-c.Chan()
assert.Equal(t, "555", string(msg.Payload()))
}
func TestNmqConsumer_ConsumeFromEarliest(t *testing.T) {
client, err := createNmqClient()
assert.NoError(t, err)
defer client.Close()
topic := t.Name()
p, err := client.CreateProducer(mqwrapper.ProducerOptions{Topic: topic})
assert.NoError(t, err)
msgs := []string{"111", "222"}
process(t, msgs, p)
c, err := client.Subscribe(mqwrapper.ConsumerOptions{
Topic: topic,
SubscriptionName: topic,
SubscriptionInitialPosition: mqwrapper.SubscriptionPositionEarliest,
BufSize: 1024,
})
assert.NoError(t, err)
defer c.Close()
msgs = []string{"333", "444", "555"}
process(t, msgs, p)
msg := <-c.Chan()
assert.Equal(t, "111", string(msg.Payload()))
msg = <-c.Chan()
assert.Equal(t, "222", string(msg.Payload()))
c2, err := client.Subscribe(mqwrapper.ConsumerOptions{
Topic: topic,
SubscriptionName: topic,
SubscriptionInitialPosition: mqwrapper.SubscriptionPositionEarliest,
BufSize: 1024,
})
assert.NoError(t, err)
defer c2.Close()
msgs = []string{"777"}
process(t, msgs, p)
msg = <-c2.Chan()
assert.Equal(t, "111", string(msg.Payload()))
msg = <-c2.Chan()
assert.Equal(t, "222", string(msg.Payload()))
}
func TestNatsConsumer_SeekExclusive(t *testing.T) {
topic := t.Name()
c, p := newProducer(t, topic)
defer c.Close()
defer p.Close()
msgs := []string{"111", "222", "333", "444", "555"}
process(t, msgs, p)
msgID := &nmqID{messageID: 2}
consumer, err := newTestConsumer(topic, false, false)
assert.NoError(t, err)
defer consumer.Close()
err = consumer.Seek(msgID, false)
assert.NoError(t, err)
msg := <-consumer.Chan()
assert.Equal(t, "333", string(msg.Payload()))
msg = <-consumer.Chan()
assert.Equal(t, "444", string(msg.Payload()))
}
func TestNatsConsumer_SeekInclusive(t *testing.T) {
topic := t.Name()
c, p := newProducer(t, topic)
defer c.Close()
defer p.Close()
msgs := []string{"111", "222", "333", "444", "555"}
process(t, msgs, p)
msgID := &nmqID{messageID: 2}
consumer, err := newTestConsumer(topic, false, false)
assert.NoError(t, err)
defer consumer.Close()
err = consumer.Seek(msgID, true)
assert.NoError(t, err)
msg := <-consumer.Chan()
assert.Equal(t, "222", string(msg.Payload()))
msg = <-consumer.Chan()
assert.Equal(t, "333", string(msg.Payload()))
}
func TestNatsConsumer_NoDoubleSeek(t *testing.T) {
topic := t.Name()
c, p := newProducer(t, topic)
defer c.Close()
defer p.Close()
msgID := &nmqID{messageID: 2}
consumer, err := newTestConsumer(topic, false, false)
assert.NoError(t, err)
defer consumer.Close()
err = consumer.Seek(msgID, true)
assert.NoError(t, err)
err = consumer.Seek(msgID, true)
assert.Error(t, err)
}
func TestNatsConsumer_ChanWithNoAssign(t *testing.T) {
topic := t.Name()
c, p := newProducer(t, topic)
defer c.Close()
defer p.Close()
msgs := []string{"111", "222", "333", "444", "555"}
process(t, msgs, p)
consumer, err := newTestConsumer(topic, false, false)
assert.NoError(t, err)
defer consumer.Close()
assert.Panics(t, func() {
<-consumer.Chan()
})
}

View File

@ -0,0 +1,69 @@
// Licensed to the LF AI & Data foundation under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nmq
import (
"github.com/milvus-io/milvus/pkg/common"
"github.com/milvus-io/milvus/pkg/mq/msgstream/mqwrapper"
)
// MessageIDType is a type alias for server.UniqueID that represents the ID of a Nmq message.
type MessageIDType = uint64
// nmqID wraps message ID for natsmq
type nmqID struct {
messageID MessageIDType
}
// Check if nmqID implements MessageID interface
var _ mqwrapper.MessageID = &nmqID{}
// NewNmqID creates and returns a new instance of the nmqID struct with the given MessageID.
func NewNmqID(id MessageIDType) mqwrapper.MessageID {
return &nmqID{
messageID: id,
}
}
// Serialize convert nmq message id to []byte
func (nid *nmqID) Serialize() []byte {
return SerializeNmqID(nid.messageID)
}
func (nid *nmqID) AtEarliestPosition() bool {
return nid.messageID <= 1
}
func (nid *nmqID) LessOrEqualThan(msgID []byte) (bool, error) {
return nid.messageID <= DeserializeNmqID(msgID), nil
}
func (nid *nmqID) Equal(msgID []byte) (bool, error) {
return nid.messageID == DeserializeNmqID(msgID), nil
}
// SerializeNmqID is used to serialize a message ID to byte array
func SerializeNmqID(messageID MessageIDType) []byte {
b := make([]byte, 8)
common.Endian.PutUint64(b, messageID)
return b
}
// DeserializeNmqID is used to deserialize a message ID from byte array
func DeserializeNmqID(messageID []byte) MessageIDType {
return common.Endian.Uint64(messageID)
}

View File

@ -0,0 +1,102 @@
// Licensed to the LF AI & Data foundation under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nmq
import (
"math"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNmqID_Serialize(t *testing.T) {
rid := &nmqID{
messageID: 8,
}
bin := rid.Serialize()
assert.NotNil(t, bin)
assert.NotZero(t, len(bin))
}
func Test_AtEarliestPosition(t *testing.T) {
rid := &nmqID{
messageID: 0,
}
assert.True(t, rid.AtEarliestPosition())
rid = &nmqID{
messageID: math.MaxInt64,
}
assert.False(t, rid.AtEarliestPosition())
}
func TestLessOrEqualThan(t *testing.T) {
rid1 := &nmqID{
messageID: 0,
}
rid2 := &nmqID{
messageID: math.MaxInt64,
}
ret, err := rid1.LessOrEqualThan(rid2.Serialize())
assert.Nil(t, err)
assert.True(t, ret)
ret, err = rid2.LessOrEqualThan(rid1.Serialize())
assert.Nil(t, err)
assert.False(t, ret)
ret, err = rid1.LessOrEqualThan(rid1.Serialize())
assert.Nil(t, err)
assert.True(t, ret)
}
func Test_Equal(t *testing.T) {
rid1 := &nmqID{
messageID: 0,
}
rid2 := &nmqID{
messageID: math.MaxInt64,
}
{
ret, err := rid1.Equal(rid1.Serialize())
assert.Nil(t, err)
assert.True(t, ret)
}
{
ret, err := rid1.Equal(rid2.Serialize())
assert.Nil(t, err)
assert.False(t, ret)
}
}
func Test_SerializeNmqID(t *testing.T) {
bin := SerializeNmqID(10)
assert.NotNil(t, bin)
assert.NotZero(t, len(bin))
}
func Test_DeserializeNmqID(t *testing.T) {
bin := SerializeNmqID(5)
id := DeserializeNmqID(bin)
assert.Equal(t, id, MessageIDType(5))
}

View File

@ -0,0 +1,75 @@
// Licensed to the LF AI & Data foundation under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nmq
import (
"log"
"github.com/milvus-io/milvus/pkg/mq/msgstream/mqwrapper"
"github.com/nats-io/nats.go"
"go.uber.org/zap"
)
// Check nmqMessage implements ConsumerMessage
var (
_ mqwrapper.Message = (*nmqMessage)(nil)
)
// nmqMessage wraps the message for natsmq
type nmqMessage struct {
raw *nats.Msg
// lazy initialized field.
meta *nats.MsgMetadata
}
// Topic returns the topic name of natsmq message
func (nm *nmqMessage) Topic() string {
// TODO: Dependency: implement of subscription logic of nmq.
// 1:1 Subject:Topic model is appied on this implementation.
// M:N model should be a optimize option in future.
return nm.raw.Subject
}
// Properties returns the properties of natsmq message
func (nm *nmqMessage) Properties() map[string]string {
properties := make(map[string]string, len(nm.raw.Header))
for k, vs := range nm.raw.Header {
if len(vs) > 0 {
properties[k] = vs[0]
}
}
return properties
}
// Payload returns the payload of natsmq message
func (nm *nmqMessage) Payload() []byte {
return nm.raw.Data
}
// ID returns the id of natsmq message
func (nm *nmqMessage) ID() mqwrapper.MessageID {
if nm.meta == nil {
var err error
// raw is always a jetstream message, should never fail.
nm.meta, err = nm.raw.Metadata()
if err != nil {
log.Fatal("raw is always a jetstream message, should never fail", zap.Error(err))
}
}
return &nmqID{messageID: nm.meta.Sequence.Stream}
}

View File

@ -0,0 +1,44 @@
// Licensed to the LF AI & Data foundation under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nmq
import (
"testing"
"github.com/nats-io/nats.go"
"github.com/stretchr/testify/assert"
)
func TestNmqMessage_All(t *testing.T) {
topic := t.Name()
raw := nats.NewMsg(topic)
raw.Data = []byte(`test payload`)
raw.Header.Add("test", "test")
nm := nmqMessage{
meta: &nats.MsgMetadata{
Sequence: nats.SequencePair{
Stream: 12,
},
},
raw: raw,
}
payload := []byte("test payload")
assert.Equal(t, topic, nm.Topic())
assert.Equal(t, MessageIDType(12), nm.ID().(*nmqID).messageID)
assert.Equal(t, payload, nm.Payload())
assert.Equal(t, nm.Properties()["test"], "test")
}

View File

@ -0,0 +1,66 @@
// Licensed to the LF AI & Data foundation under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nmq
import (
"context"
"github.com/milvus-io/milvus/pkg/log"
"github.com/milvus-io/milvus/pkg/mq/msgstream/mqwrapper"
"github.com/nats-io/nats.go"
"go.uber.org/zap"
)
var _ mqwrapper.Producer = (*nmqProducer)(nil)
// nmqProducer contains a natsmq producer
type nmqProducer struct {
js nats.JetStreamContext
topic string
}
// Topic returns the topic of nmq producer
func (np *nmqProducer) Topic() string {
return np.topic
}
// Send send the producer messages to natsmq
func (np *nmqProducer) Send(ctx context.Context, message *mqwrapper.ProducerMessage) (mqwrapper.MessageID, error) {
// Encode message
msg := &nats.Msg{
Subject: np.topic,
Header: make(nats.Header, len(message.Properties)),
Data: message.Payload,
}
for k, v := range message.Properties {
msg.Header.Add(k, v)
}
// publish to nats-server
pa, err := np.js.PublishMsg(msg)
if err != nil {
log.Warn("failed to publish message by nmq", zap.String("topic", np.topic), zap.Error(err), zap.Int("payload_size", len(message.Payload)))
return nil, err
}
return &nmqID{messageID: pa.Sequence}, err
}
// Close does nothing currently
func (np *nmqProducer) Close() {
// No specific producer to be closed.
// stream doesn't close here.
}

View File

@ -0,0 +1,47 @@
// Licensed to the LF AI & Data foundation under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nmq
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/milvus-io/milvus/pkg/mq/msgstream/mqwrapper"
)
func TestNatsMQProducer(t *testing.T) {
c, err := createNmqClient()
assert.NoError(t, err)
defer c.Close()
topic := t.Name()
pOpts := mqwrapper.ProducerOptions{Topic: topic}
// Check Topic()
p, err := c.CreateProducer(pOpts)
assert.Nil(t, err)
assert.Equal(t, p.(*nmqProducer).Topic(), topic)
// Check Send()
msg := &mqwrapper.ProducerMessage{
Payload: []byte{},
Properties: map[string]string{},
}
_, err = p.Send(context.TODO(), msg)
assert.Nil(t, err)
}

View File

@ -0,0 +1,93 @@
// Licensed to the LF AI & Data foundation under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nmq
import (
"sync"
"time"
"github.com/nats-io/nats-server/v2/server"
"go.uber.org/zap"
"github.com/milvus-io/milvus/pkg/log"
"github.com/milvus-io/milvus/pkg/util/paramtable"
)
// Nmq is global natsmq instance that will be initialized only once
var Nmq *server.Server
// once is used to init global natsmq
var once sync.Once
// NatsMQConfig is used to initialize NatsMQ.
type NatsMQConfig struct {
Opts server.Options
InitializeTimeout time.Duration
}
// MustInitNatsMQ init global local natsmq instance.
// Panic if initailizing operation failed.
func MustInitNatsMQ(cfg *NatsMQConfig) {
once.Do(func() {
log.Info("try to initialize global nmq", zap.Any("config", cfg))
var err error
Nmq, err = server.NewServer(&cfg.Opts)
if err != nil {
log.Fatal("fail to initailize nmq", zap.Error(err))
}
log.Info("initialize nmq finished", zap.Error(err))
// Start Nmq in background and wait until it's ready for connection.
go Nmq.Start()
// Wait for server to be ready for connections
if !Nmq.ReadyForConnections(cfg.InitializeTimeout) {
log.Fatal("nmq is not ready")
}
})
}
// ParseServerOption get nats server option from paramstable.
func ParseServerOption(params *paramtable.ComponentParam) *NatsMQConfig {
return &NatsMQConfig{
Opts: server.Options{
Host: "127.0.0.1", // Force to use loopback address.
Port: params.NatsmqCfg.ServerPort.GetAsInt(),
MaxPayload: params.NatsmqCfg.ServerMaxPayload.GetAsInt32(),
MaxPending: params.NatsmqCfg.ServerMaxPending.GetAsInt64(),
JetStream: true,
JetStreamMaxStore: params.NatsmqCfg.ServerMaxFileStore.GetAsInt64(),
StoreDir: params.NatsmqCfg.ServerStoreDir.GetValue(),
Debug: params.NatsmqCfg.ServerMonitorDebug.GetAsBool(),
Logtime: params.NatsmqCfg.ServerMonitorLogTime.GetAsBool(),
LogFile: params.NatsmqCfg.ServerMonitorLogFile.GetValue(),
LogSizeLimit: params.NatsmqCfg.ServerMonitorLogSizeLimit.GetAsInt64(),
},
InitializeTimeout: time.Duration(params.NatsmqCfg.ServerInitializeTimeout.GetAsInt()) * time.Millisecond,
}
}
// CloseNatsMQ is used to close global natsmq
func CloseNatsMQ() {
log.Debug("Closing Natsmq!")
if Nmq != nil {
// Shut down the server.
Nmq.Shutdown()
// Wait for server shutdown.
Nmq.WaitForShutdown()
Nmq = nil
}
}

View File

@ -0,0 +1,60 @@
// Licensed to the LF AI & Data foundation under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nmq
import (
"os"
"testing"
"time"
"github.com/milvus-io/milvus/pkg/util/paramtable"
"github.com/stretchr/testify/assert"
)
var natsServerAddress string
func TestMain(m *testing.M) {
paramtable.Init()
storeDir, _ := os.MkdirTemp("", "milvus_mq_nmq")
defer os.RemoveAll(storeDir)
cfg := ParseServerOption(paramtable.Get())
cfg.Opts.StoreDir = storeDir
MustInitNatsMQ(cfg)
defer CloseNatsMQ()
natsServerAddress = Nmq.ClientURL()
exitCode := m.Run()
os.Exit(exitCode)
}
func TestGetServerOptionDefault(t *testing.T) {
cfg := ParseServerOption(paramtable.Get())
assert.Equal(t, "127.0.0.1", cfg.Opts.Host)
assert.Equal(t, 4222, cfg.Opts.Port)
assert.Equal(t, true, cfg.Opts.JetStream)
assert.Equal(t, "/var/lib/milvus/nats", cfg.Opts.StoreDir)
assert.Equal(t, int64(17179869184), cfg.Opts.JetStreamMaxStore)
assert.Equal(t, int32(8388608), cfg.Opts.MaxPayload)
assert.Equal(t, int64(67108864), cfg.Opts.MaxPending)
assert.Equal(t, 4000*time.Millisecond, cfg.InitializeTimeout)
assert.Equal(t, false, cfg.Opts.Debug)
assert.Equal(t, true, cfg.Opts.Logtime)
assert.Equal(t, "", cfg.Opts.LogFile)
assert.Equal(t, int64(0), cfg.Opts.LogSizeLimit)
}

View File

@ -0,0 +1,261 @@
package msgstream
import (
"context"
"math/rand"
"reflect"
"runtime"
"sync"
"testing"
"time"
"github.com/milvus-io/milvus/pkg/log"
"github.com/milvus-io/milvus/pkg/mq/msgstream/mqwrapper"
"github.com/milvus-io/milvus/pkg/util/funcutil"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
)
func testStreamOperation(t *testing.T, mqClient mqwrapper.Client) {
testFuncs := []func(t *testing.T, c mqwrapper.Client){
testConcurrentStream,
testConcurrentStreamAndSubscribeLast,
testConcurrentStreamAndSeekInclusive,
testConcurrentStreamAndSeekNoInclusive,
testConcurrentStreamAndSeekToLast,
}
for _, testFunc := range testFuncs {
t.Run(
runtime.FuncForPC(reflect.ValueOf(testFunc).Pointer()).Name(),
func(t *testing.T) {
testFunc(t, mqClient)
},
)
}
}
func testConcurrentStream(t *testing.T, mqClient mqwrapper.Client) {
topics := getChannel(2)
producer, err := mqClient.CreateProducer(mqwrapper.ProducerOptions{
Topic: topics[0],
})
defer producer.Close()
assert.Nil(t, err)
consumer, err := mqClient.Subscribe(mqwrapper.ConsumerOptions{
Topic: topics[0],
SubscriptionName: funcutil.RandomString(8),
SubscriptionInitialPosition: mqwrapper.SubscriptionPositionEarliest,
BufSize: 1024,
})
defer consumer.Close()
assert.Nil(t, err)
testSendAndRecv(t, producer, consumer)
}
func testConcurrentStreamAndSubscribeLast(t *testing.T, mqClient mqwrapper.Client) {
topics := getChannel(2)
producer, err := mqClient.CreateProducer(mqwrapper.ProducerOptions{
Topic: topics[0],
})
defer producer.Close()
assert.Nil(t, err)
ids := sendMessages(context.Background(), t, producer, generateRandMessage(1024, 1000))
consumer, err := mqClient.Subscribe(mqwrapper.ConsumerOptions{
Topic: topics[0],
SubscriptionName: funcutil.RandomString(8),
SubscriptionInitialPosition: mqwrapper.SubscriptionPositionLatest,
BufSize: 1024,
})
assert.Nil(t, err)
latestID, err := consumer.GetLatestMsgID()
assert.Nil(t, err)
compare, err := ids[len(ids)-1].Equal(latestID.Serialize())
assert.Nil(t, err)
assert.True(t, compare)
defer consumer.Close()
testSendAndRecv(t, producer, consumer)
}
func testConcurrentStreamAndSeekInclusive(t *testing.T, mqClient mqwrapper.Client) {
topics := getChannel(2)
producer, err := mqClient.CreateProducer(mqwrapper.ProducerOptions{
Topic: topics[0],
})
defer producer.Close()
assert.Nil(t, err)
cases := generateRandMessage(1024, 1000)
ids := sendMessages(context.Background(), t, producer, cases)
consumer, err := mqClient.Subscribe(mqwrapper.ConsumerOptions{
Topic: topics[0],
SubscriptionName: funcutil.RandomString(8),
SubscriptionInitialPosition: mqwrapper.SubscriptionPositionUnknown,
BufSize: 1024,
})
assert.Nil(t, err)
defer consumer.Close()
// seek half and inclusive.
// consume all and compare.
half := len(ids) / 2
ids = ids[half:]
err = consumer.Seek(ids[0], true)
assert.Nil(t, err)
consumerIDs := recvMessages(context.Background(), t, consumer, cases[half:], time.Minute)
compareMultiIDs(t, ids, consumerIDs)
assert.Empty(t, recvMessages(context.Background(), t, consumer, cases, 5*time.Second))
testSendAndRecv(t, producer, consumer)
}
func testConcurrentStreamAndSeekNoInclusive(t *testing.T, mqClient mqwrapper.Client) {
topics := getChannel(2)
producer, err := mqClient.CreateProducer(mqwrapper.ProducerOptions{
Topic: topics[0],
})
defer producer.Close()
assert.Nil(t, err)
cases := generateRandMessage(1024, 1000)
ids := sendMessages(context.Background(), t, producer, cases)
consumer, err := mqClient.Subscribe(mqwrapper.ConsumerOptions{
Topic: topics[0],
SubscriptionName: funcutil.RandomString(8),
SubscriptionInitialPosition: mqwrapper.SubscriptionPositionUnknown,
BufSize: 1024,
})
assert.Nil(t, err)
defer consumer.Close()
// seek half and inclusive.
// consume all and compare.
half := len(ids) / 2
ids = ids[half:]
err = consumer.Seek(ids[0], false)
assert.Nil(t, err)
consumerIDs := recvMessages(context.Background(), t, consumer, cases[half+1:], time.Minute)
compareMultiIDs(t, ids[1:], consumerIDs)
assert.Empty(t, recvMessages(context.Background(), t, consumer, cases, 5*time.Second))
testSendAndRecv(t, producer, consumer)
}
func testConcurrentStreamAndSeekToLast(t *testing.T, mqClient mqwrapper.Client) {
topics := getChannel(2)
producer, err := mqClient.CreateProducer(mqwrapper.ProducerOptions{
Topic: topics[0],
})
defer producer.Close()
assert.Nil(t, err)
cases := generateRandMessage(1024, 1000)
sendMessages(context.Background(), t, producer, cases)
consumer, err := mqClient.Subscribe(mqwrapper.ConsumerOptions{
Topic: topics[0],
SubscriptionName: funcutil.RandomString(8),
SubscriptionInitialPosition: mqwrapper.SubscriptionPositionUnknown,
BufSize: 1024,
})
assert.Nil(t, err)
defer consumer.Close()
latestID, err := consumer.GetLatestMsgID()
assert.Nil(t, err)
// seek half and inclusive.
// consume all and compare.
err = consumer.Seek(latestID, false)
assert.Nil(t, err)
testSendAndRecv(t, producer, consumer)
}
func testSendAndRecv(t *testing.T, p mqwrapper.Producer, c mqwrapper.Consumer) {
ctx := context.Background()
msg := generateRandMessage(1024*5, 10)
var (
producerIDs []mqwrapper.MessageID
consumerIDs []mqwrapper.MessageID
)
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
producerIDs = sendMessages(ctx, t, p, msg)
log.Debug("producing finished", zap.Any("id", producerIDs[0].Serialize()), zap.Any("ids", producerIDs))
}()
go func() {
defer wg.Done()
consumerIDs = recvMessages(ctx, t, c, msg, 10*time.Second)
}()
wg.Wait()
compareMultiIDs(t, producerIDs, consumerIDs)
// should be empty.
assert.Empty(t, recvMessages(context.Background(), t, c, msg, time.Second))
}
func compareMultiIDs(t *testing.T, producerIDs []mqwrapper.MessageID, consumerIDs []mqwrapper.MessageID) {
assert.Equal(t, len(producerIDs), len(consumerIDs))
for i := range producerIDs {
compare, err := producerIDs[i].Equal(consumerIDs[i].Serialize())
assert.Nil(t, err)
assert.True(t, compare)
}
}
func generateRandMessage(m int, n int) []string {
cases := make([]string, 0, n)
for i := 0; i < n; i++ {
l := rand.Intn(m)
cases = append(cases, funcutil.RandomString(l))
}
return cases
}
func sendMessages(ctx context.Context, t *testing.T, p mqwrapper.Producer, testCase []string) []mqwrapper.MessageID {
ids := make([]mqwrapper.MessageID, 0, len(testCase))
for _, s := range testCase {
id, err := p.Send(ctx, &mqwrapper.ProducerMessage{
Payload: []byte(s),
})
assert.Nil(t, err)
ids = append(ids, id)
}
return ids
}
func recvMessages(ctx context.Context, t *testing.T, c mqwrapper.Consumer, testCase []string, timeout time.Duration) []mqwrapper.MessageID {
ids := make([]mqwrapper.MessageID, 0, len(testCase))
timeoutTicker := time.NewTicker(timeout)
defer timeoutTicker.Stop()
for {
select {
case msg := <-c.Chan():
ids = append(ids, msg.ID())
c.Ack(msg)
case <-timeoutTicker.C:
return ids
}
if len(ids) >= len(testCase) {
break
}
}
return ids
}

View File

@ -120,7 +120,7 @@ func TestGlobalMethods(t *testing.T) {
err = ZstdCompress(strings.NewReader(data), compressed, zstd.WithWindowSize(3))
assert.Error(t, err)
err = ZstdDecompress(compressed, origin, zstd.WithDecoderConcurrency(0))
err = ZstdDecompress(compressed, origin, zstd.WithDecoderConcurrency(-1))
assert.Error(t, err)
}

View File

@ -48,9 +48,11 @@ type ServiceParam struct {
MetaStoreCfg MetaStoreConfig
EtcdCfg EtcdConfig
DBCfg MetaDBConfig
MQCfg MQConfig
PulsarCfg PulsarConfig
KafkaCfg KafkaConfig
RocksmqCfg RocksmqConfig
NatsmqCfg NatsmqConfig
MinioCfg MinioConfig
}
@ -61,9 +63,11 @@ func (p *ServiceParam) init() {
p.MetaStoreCfg.Init(&p.BaseTable)
p.EtcdCfg.Init(&p.BaseTable)
p.DBCfg.Init(&p.BaseTable)
p.MQCfg.Init(&p.BaseTable)
p.PulsarCfg.Init(&p.BaseTable)
p.KafkaCfg.Init(&p.BaseTable)
p.RocksmqCfg.Init(&p.BaseTable)
p.NatsmqCfg.Init(&p.BaseTable)
p.MinioCfg.Init(&p.BaseTable)
}
@ -71,6 +75,11 @@ func (p *ServiceParam) RocksmqEnable() bool {
return p.RocksmqCfg.Path.GetValue() != ""
}
// NatsmqEnable checks if NATS messaging queue is enabled.
func (p *ServiceParam) NatsmqEnable() bool {
return p.NatsmqCfg.ServerStoreDir.GetValue() != ""
}
func (p *ServiceParam) PulsarEnable() bool {
return p.PulsarCfg.Address.GetValue() != ""
}
@ -353,7 +362,27 @@ func (p *MetaDBConfig) Init(base *BaseTable) {
Export: true,
}
p.MaxIdleConns.Init(base.mgr)
}
// /////////////////////////////////////////////////////////////////////////////
// --- mq ---
// MQConfig represents the configuration settings for the message queue.
type MQConfig struct {
Type ParamItem `refreshable:"false"`
}
// Init initializes the MQConfig object with a BaseTable.
func (p *MQConfig) Init(base *BaseTable) {
p.Type = ParamItem{
Key: "mq.type",
Version: "2.3.0",
DefaultValue: "",
Doc: `Default value: "default"
Valid values: [default, pulsar, kafka, rocksmq, natsmq]`,
Export: true,
}
p.Type.Init(base.mgr)
}
// /////////////////////////////////////////////////////////////////////////////
@ -477,7 +506,6 @@ func (p *PulsarConfig) Init(base *BaseTable) {
},
}
p.AuthParams.Init(base.mgr)
}
// --- kafka ---
@ -625,6 +653,104 @@ please adjust in embedded Milvus: /tmp/milvus/rdb_data`,
r.TickerTimeInSeconds.Init(base.mgr)
}
// NatsmqConfig describes the configuration options for the Nats message queue
type NatsmqConfig struct {
ServerPort ParamItem `refreshable:"false"`
ServerStoreDir ParamItem `refreshable:"false"`
ServerMaxFileStore ParamItem `refreshable:"false"`
ServerMaxPayload ParamItem `refreshable:"false"`
ServerMaxPending ParamItem `refreshable:"false"`
ServerInitializeTimeout ParamItem `refreshable:"false"`
ServerMonitorDebug ParamItem `refreshable:"false"`
ServerMonitorLogTime ParamItem `refreshable:"false"`
ServerMonitorLogFile ParamItem `refreshable:"false"`
ServerMonitorLogSizeLimit ParamItem `refreshable:"false"`
}
// Init sets up a new NatsmqConfig instance using the provided BaseTable
func (r *NatsmqConfig) Init(base *BaseTable) {
r.ServerPort = ParamItem{
Key: "natsmq.server.port",
Version: "2.3.0",
DefaultValue: "4222",
Doc: `Port for nats server listening`,
Export: true,
}
r.ServerPort.Init(base.mgr)
r.ServerStoreDir = ParamItem{
Key: "natsmq.server.storeDir",
Version: "2.3.0",
Doc: `Directory to use for JetStream storage of nats`,
Export: true,
}
r.ServerStoreDir.Init(base.mgr)
r.ServerMaxFileStore = ParamItem{
Key: "natsmq.server.maxFileStore",
Version: "2.3.0",
DefaultValue: "17179869184",
Doc: `Maximum size of the 'file' storage`,
Export: true,
}
r.ServerMaxFileStore.Init(base.mgr)
r.ServerMaxPayload = ParamItem{
Key: "natsmq.server.maxPayload",
Version: "2.3.0",
DefaultValue: "8388608",
Doc: `Maximum number of bytes in a message payload`,
Export: true,
}
r.ServerMaxPayload.Init(base.mgr)
r.ServerMaxPending = ParamItem{
Key: "natsmq.server.maxPending",
Version: "2.3.0",
DefaultValue: "67108864",
Doc: `Maximum number of bytes buffered for a connection Applies to client connections`,
Export: true,
}
r.ServerMaxPending.Init(base.mgr)
r.ServerInitializeTimeout = ParamItem{
Key: "natsmq.server.initializeTimeout",
Version: "2.3.0",
DefaultValue: "4000",
Doc: `waiting for initialization of natsmq finished`,
Export: true,
}
r.ServerInitializeTimeout.Init(base.mgr)
r.ServerMonitorDebug = ParamItem{
Key: "natsmq.server.monitor.debug",
Version: "2.3.0",
DefaultValue: "false",
Doc: `If true enable debug log messages`,
Export: true,
}
r.ServerMonitorDebug.Init(base.mgr)
r.ServerMonitorLogTime = ParamItem{
Key: "natsmq.server.monitor.logTime",
Version: "2.3.0",
DefaultValue: "true",
Doc: `If set to false, log without timestamps.`,
Export: true,
}
r.ServerMonitorLogTime.Init(base.mgr)
r.ServerMonitorLogFile = ParamItem{
Key: "natsmq.server.monitor.logFile",
Version: "2.3.0",
DefaultValue: "",
Doc: `Log file path relative to..`,
Export: true,
}
r.ServerMonitorLogFile.Init(base.mgr)
r.ServerMonitorLogSizeLimit = ParamItem{
Key: "natsmq.server.monitor.logSizeLimit",
Version: "2.3.0",
DefaultValue: "0",
Doc: `Size in bytes after the log file rolls over to a new one`,
Export: true,
}
r.ServerMonitorLogSizeLimit.Init(base.mgr)
}
// /////////////////////////////////////////////////////////////////////////////
// --- minio ---
type MinioConfig struct {