github.com/wfusion/gofusion@v1.1.14/common/infra/watermill/pubsub/amqp/config.go (about) 1 package amqp 2 3 import ( 4 "crypto/tls" 5 "time" 6 7 "github.com/cenkalti/backoff/v4" 8 "github.com/pkg/errors" 9 "go.uber.org/multierr" 10 11 amqp "github.com/rabbitmq/amqp091-go" 12 ) 13 14 // NewDurablePubSubConfig creates config for durable PubSub. 15 // generateQueueName is optional, when passing to the publisher. 16 // Exchange name is set to the topic name and routing key is empty. 17 // 18 // IMPORTANT: Watermill's topic is not mapped directly to the AMQP's topic exchange type. 19 // It is used to generate exchange name, routing key and queue name, depending on the context. 20 // To check how topic is mapped, please check Exchange.GenerateName, Queue.GenerateName and Publish.GenerateRoutingKey. 21 // 22 // This config is based on this example: https://www.rabbitmq.com/tutorials/tutorial-three-go.html 23 // with durable added for exchange, queue and amqp.Persistent DeliveryMode. 24 // Thanks to this, we don't lose messages on broker restart. 25 func NewDurablePubSubConfig(amqpURI string, generateQueueName QueueNameGenerator) Config { 26 return Config{ 27 Connection: ConnectionConfig{ 28 AmqpURI: amqpURI, 29 }, 30 31 Marshaler: DefaultMarshaler{}, 32 33 Exchange: ExchangeConfig{ 34 GenerateName: func(topic string) string { 35 return topic 36 }, 37 Type: "fanout", 38 Durable: true, 39 }, 40 Queue: QueueConfig{ 41 GenerateName: generateQueueName, 42 Durable: true, 43 }, 44 QueueBind: QueueBindConfig{ 45 GenerateRoutingKey: func(topic string) string { 46 return "" 47 }, 48 }, 49 Publish: PublishConfig{ 50 GenerateRoutingKey: func(topic string) string { 51 return "" 52 }, 53 }, 54 Consume: ConsumeConfig{ 55 Qos: QosConfig{ 56 PrefetchCount: 1, 57 }, 58 }, 59 TopologyBuilder: &DefaultTopologyBuilder{}, 60 } 61 } 62 63 // NewNonDurablePubSubConfig creates config for non durable PubSub. 64 // generateQueueName is optional, when passing to the publisher. 65 // Exchange name is set to the topic name and routing key is empty. 66 // 67 // IMPORTANT: Watermill's topic is not mapped directly to the AMQP's topic exchange type. 68 // It is used to generate exchange name, routing key and queue name, depending on the context. 69 // To check how topic is mapped, please check Exchange.GenerateName, Queue.GenerateName and Publish.GenerateRoutingKey. 70 // 71 // This config is based on this example: https://www.rabbitmq.com/tutorials/tutorial-three-go.html. 72 // This config is not durable, so on the restart of the broker all messages will be lost. 73 func NewNonDurablePubSubConfig(amqpURI string, generateQueueName QueueNameGenerator) Config { 74 return Config{ 75 Connection: ConnectionConfig{ 76 AmqpURI: amqpURI, 77 }, 78 79 Marshaler: DefaultMarshaler{NotPersistentDeliveryMode: true}, 80 81 Exchange: ExchangeConfig{ 82 GenerateName: func(topic string) string { 83 return topic 84 }, 85 Type: "fanout", 86 }, 87 Queue: QueueConfig{ 88 GenerateName: generateQueueName, 89 }, 90 QueueBind: QueueBindConfig{ 91 GenerateRoutingKey: func(topic string) string { 92 return "" 93 }, 94 }, 95 Publish: PublishConfig{ 96 GenerateRoutingKey: func(topic string) string { 97 return "" 98 }, 99 }, 100 Consume: ConsumeConfig{ 101 Qos: QosConfig{ 102 PrefetchCount: 1, 103 }, 104 }, 105 TopologyBuilder: &DefaultTopologyBuilder{}, 106 } 107 } 108 109 // NewDurableQueueConfig creates config for durable Queue. 110 // Queue name and routing key is set to the topic name by default. Default ("") exchange is used. 111 // 112 // IMPORTANT: Watermill's topic is not mapped directly to the AMQP's topic exchange type. 113 // It is used to generate exchange name, routing key and queue name, depending on the context. 114 // To check how topic is mapped, please check Exchange.GenerateName, Queue.GenerateName and Publish.GenerateRoutingKey. 115 // 116 // This config is based on this example: https://www.rabbitmq.com/tutorials/tutorial-two-go.html 117 // with durable added for exchange, queue and amqp.Persistent DeliveryMode. 118 // Thanks to this, we don't lose messages on broker restart. 119 func NewDurableQueueConfig(amqpURI string) Config { 120 return Config{ 121 Connection: ConnectionConfig{ 122 AmqpURI: amqpURI, 123 }, 124 125 Marshaler: DefaultMarshaler{}, 126 127 Exchange: ExchangeConfig{ 128 GenerateName: func(topic string) string { 129 return "" 130 }, 131 }, 132 Queue: QueueConfig{ 133 GenerateName: GenerateQueueNameTopicName, 134 Durable: true, 135 }, 136 QueueBind: QueueBindConfig{ 137 GenerateRoutingKey: func(topic string) string { 138 return "" 139 }, 140 }, 141 Publish: PublishConfig{ 142 GenerateRoutingKey: func(topic string) string { 143 return topic 144 }, 145 }, 146 Consume: ConsumeConfig{ 147 Qos: QosConfig{ 148 PrefetchCount: 1, 149 }, 150 }, 151 TopologyBuilder: &DefaultTopologyBuilder{}, 152 } 153 } 154 155 // NewNonDurableQueueConfig creates config for non durable Queue. 156 // Queue name and routing key is set to the topic name by default. Default ("") exchange is used. 157 // 158 // IMPORTANT: Watermill's topic is not mapped directly to the AMQP's topic exchange type. 159 // It is used to generate exchange name, routing key and queue name, depending on the context. 160 // To check how topic is mapped, please check Exchange.GenerateName, Queue.GenerateName and Publish.GenerateRoutingKey. 161 // 162 // This config is based on this example: https://www.rabbitmq.com/tutorials/tutorial-two-go.html. 163 // This config is not durable, so on the restart of the broker all messages will be lost. 164 func NewNonDurableQueueConfig(amqpURI string) Config { 165 return Config{ 166 Connection: ConnectionConfig{ 167 AmqpURI: amqpURI, 168 }, 169 170 Marshaler: DefaultMarshaler{NotPersistentDeliveryMode: true}, 171 172 Exchange: ExchangeConfig{ 173 GenerateName: func(topic string) string { 174 return "" 175 }, 176 }, 177 Queue: QueueConfig{ 178 GenerateName: GenerateQueueNameTopicName, 179 }, 180 QueueBind: QueueBindConfig{ 181 GenerateRoutingKey: func(topic string) string { 182 return "" 183 }, 184 }, 185 Publish: PublishConfig{ 186 GenerateRoutingKey: func(topic string) string { 187 return topic 188 }, 189 }, 190 Consume: ConsumeConfig{ 191 Qos: QosConfig{ 192 PrefetchCount: 1, 193 }, 194 }, 195 TopologyBuilder: &DefaultTopologyBuilder{}, 196 } 197 } 198 199 type Config struct { 200 Connection ConnectionConfig 201 202 Marshaler Marshaler 203 204 Exchange ExchangeConfig 205 Queue QueueConfig 206 QueueBind QueueBindConfig 207 208 Publish PublishConfig 209 Consume ConsumeConfig 210 211 TopologyBuilder TopologyBuilder 212 } 213 214 func (c Config) validate(validateConnection bool) error { 215 var err error 216 217 if validateConnection { 218 if c.Connection.AmqpURI == "" { 219 err = multierr.Append(err, errors.New("empty Config.AmqpURI")) 220 } 221 } 222 if c.Marshaler == nil { 223 err = multierr.Append(err, errors.New("missing Config.Marshaler")) 224 } 225 if c.Exchange.GenerateName == nil { 226 err = multierr.Append(err, errors.New("missing Config.GenerateName")) 227 } 228 229 return err 230 } 231 232 func (c Config) validatePublisher(validateConnection bool) error { 233 err := c.validate(validateConnection) 234 235 if c.Publish.GenerateRoutingKey == nil { 236 err = multierr.Append(err, errors.New("missing Config.GenerateRoutingKey")) 237 } 238 239 return err 240 } 241 242 func (c Config) validateSubscriber(validateConnection bool) error { 243 err := c.validate(validateConnection) 244 245 if c.Queue.GenerateName == nil { 246 err = multierr.Append(err, errors.New("missing Config.Queue.GenerateName")) 247 } 248 249 return err 250 } 251 252 func (c Config) ValidatePublisher() error { 253 return c.validatePublisher(true) 254 } 255 256 func (c Config) ValidatePublisherWithConnection() error { 257 return c.validatePublisher(false) 258 } 259 260 func (c Config) ValidateSubscriber() error { 261 return c.validateSubscriber(true) 262 } 263 264 func (c Config) ValidateSubscriberWithConnection() error { 265 return c.validateSubscriber(false) 266 } 267 268 type ConnectionConfig struct { 269 AmqpURI string 270 271 TLSConfig *tls.Config 272 AmqpConfig *amqp.Config 273 274 Reconnect *ReconnectConfig 275 } 276 277 // Config descriptions are based on descriptions from: https://github.com/streadway/amqp 278 // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. 279 // BSD 2-Clause "Simplified" License 280 281 type ExchangeConfig struct { 282 // GenerateName is generated based on the topic provided for Publish or Subscribe method. 283 // 284 // Exchange names starting with "amq." are reserved for pre-declared and 285 // standardized exchanges. The client MAY declare an exchange starting with 286 // "amq." if the passive option is set, or the exchange already exists. Names can 287 // consist of a non-empty sequence of letters, digits, hyphen, underscore, 288 // period, or colon. 289 GenerateName func(topic string) string 290 291 // Each exchange belongs to one of a set of exchange kinds/types implemented by 292 // the server. The exchange types define the functionality of the exchange - i.e. 293 // how messages are routed through it. Once an exchange is declared, its type 294 // cannot be changed. The common types are "direct", "fanout", "topic" and 295 // "headers". 296 Type string 297 298 // Durable and Non-Auto-Deleted exchanges will survive server restarts and remain 299 // declared when there are no remaining bindings. This is the best lifetime for 300 // long-lived exchange configurations like stable routes and default exchanges. 301 Durable bool 302 303 // Non-Durable and Auto-Deleted exchanges will be deleted when there are no 304 // remaining bindings and not restored on server restart. This lifetime is 305 // useful for temporary topologies that should not pollute the virtual host on 306 // failure or after the consumers have completed. 307 // 308 // Non-Durable and Non-Auto-deleted exchanges will remain as long as the server is 309 // running including when there are no remaining bindings. This is useful for 310 // temporary topologies that may have long delays between bindings. 311 // 312 AutoDeleted bool 313 314 // Exchanges declared as `internal` do not accept accept publishings. Internal 315 // exchanges are useful when you wish to implement inter-exchange topologies 316 // that should not be exposed to users of the broker. 317 Internal bool 318 319 // When noWait is true, declare without waiting for a confirmation from the server. 320 // The channel may be closed as a result of an error. Add a NotifyClose listener- 321 // to respond to any exceptions. 322 NoWait bool 323 324 // Optional amqp.Table of arguments that are specific to the server's implementation of 325 // the exchange can be sent for exchange types that require extra parameters. 326 Arguments amqp.Table 327 } 328 329 // QueueNameGenerator generates QueueName based on the topic. 330 type QueueNameGenerator func(topic string) string 331 332 // GenerateQueueNameTopicName generates queueName equal to the topic. 333 func GenerateQueueNameTopicName(topic string) string { 334 return topic 335 } 336 337 // GenerateQueueNameConstant generate queue name equal to queueName. 338 func GenerateQueueNameConstant(queueName string) QueueNameGenerator { 339 return func(topic string) string { 340 return queueName 341 } 342 } 343 344 // GenerateQueueNameTopicNameWithSuffix generates queue name equal to: 345 // 346 // topic + "_" + suffix 347 func GenerateQueueNameTopicNameWithSuffix(suffix string) QueueNameGenerator { 348 return func(topic string) string { 349 return topic + "_" + suffix 350 } 351 } 352 353 type QueueConfig struct { 354 // GenerateRoutingKey is generated based on the topic provided for Subscribe. 355 GenerateName QueueNameGenerator 356 357 // Durable and Non-Auto-Deleted queues will survive server restarts and remain 358 // when there are no remaining consumers or bindings. Persistent publishings will 359 // be restored in this queue on server restart. These queues are only able to be 360 // bound to durable exchanges. 361 Durable bool 362 363 // Non-Durable and Auto-Deleted exchanges will be deleted when there are no 364 // remaining bindings and not restored on server restart. This lifetime is 365 // useful for temporary topologies that should not pollute the virtual host on 366 // failure or after the consumers have completed. 367 // 368 // Non-Durable and Non-Auto-deleted exchanges will remain as long as the server is 369 // running including when there are no remaining bindings. This is useful for 370 // temporary topologies that may have long delays between bindings. 371 AutoDelete bool 372 373 // Exclusive queues are only accessible by the connection that declares them and 374 // will be deleted when the connection closes. Channels on other connections 375 // will receive an error when attempting to declare, bind, consume, purge or 376 // delete a queue with the same name. 377 Exclusive bool 378 379 // When noWait is true, the queue will assume to be declared on the server. A 380 // channel exception will arrive if the conditions are met for existing queues 381 // or attempting to modify an existing queue from a different connection. 382 NoWait bool 383 384 // Optional amqpe.Table of arguments that are specific to the server's implementation of 385 // the queue can be sent for queue types that require extra parameters. 386 Arguments amqp.Table 387 } 388 389 // QueueBind binds an exchange to a queue so that publishings to the exchange will 390 // be routed to the queue when the publishing routing key matches the binding 391 // routing key. 392 type QueueBindConfig struct { 393 GenerateRoutingKey func(topic string) string 394 395 // When noWait is false and the queue could not be bound, the channel will be 396 // closed with an error. 397 NoWait bool 398 399 // Optional amqpe.Table of arguments that are specific to the server's implementation of 400 // the queue bind can be sent for queue bind types that require extra parameters. 401 Arguments amqp.Table 402 } 403 404 type PublishConfig struct { 405 // GenerateRoutingKey is generated based on the topic provided for Publish. 406 GenerateRoutingKey func(topic string) string 407 408 // Publishings can be undeliverable when the mandatory flag is true and no queue is 409 // bound that matches the routing key, or when the immediate flag is true and no 410 // consumer on the matched queue is ready to accept the delivery. 411 Mandatory bool 412 413 // Publishings can be undeliverable when the mandatory flag is true and no queue is 414 // bound that matches the routing key, or when the immediate flag is true and no 415 // consumer on the matched queue is ready to accept the delivery. 416 Immediate bool 417 418 // With transactional enabled, all messages wil be added in transaction. 419 Transactional bool 420 421 // ChannelPoolSize specifies the size of a channel pool. All channels in the pool are opened when the publisher is 422 // created. When a Publish operation is performed then a channel is taken from the pool to perform the operation and 423 // then returned to the pool once the operation has finished. If all channels are in use then the Publish operation 424 // waits until a channel is returned to the pool. 425 // If this value is set to 0 (default) then channels are not pooled and a new channel is opened/closed for every 426 // Publish operation. 427 ChannelPoolSize int 428 429 // ConfirmDelivery indicates whether the Publish function should wait until a confirmation is received from 430 // the AMQP server in order to guarantee that the message is delivered. Setting this value to true may 431 // negatively impact performance but will increase reliability. 432 ConfirmDelivery bool 433 } 434 435 type ConsumeConfig struct { 436 // When true, message will be not requeued when nacked. 437 NoRequeueOnNack bool 438 439 // The consumer is identified by a string that is unique and scoped for all 440 // consumers on this channel. If you wish to eventually cancel the consumer, use 441 // the same non-empty identifier in Channel.Cancel. An empty string will cause 442 // the library to generate a unique identity. The consumer identity will be 443 // included in every Delivery in the ConsumerTag field 444 Consumer string 445 446 // When exclusive is true, the server will ensure that this is the sole consumer 447 // from this queue. When exclusive is false, the server will fairly distribute 448 // deliveries across multiple consumers. 449 Exclusive bool 450 451 // The noLocal flag is not supported by RabbitMQ. 452 NoLocal bool 453 454 // When noWait is true, do not wait for the server to confirm the request and 455 // immediately begin deliveries. If it is not possible to consume, a channel 456 // exception will be raised and the channel will be closed. 457 NoWait bool 458 459 Qos QosConfig 460 461 // Optional arguments can be provided that have specific semantics for the queue 462 // or server. 463 Arguments amqp.Table 464 } 465 466 // Qos controls how many messages or how many bytes the server will try to keep on 467 // the network for consumers before receiving delivery acks. The intent of Qos is 468 // to make sure the network buffers stay full between the server and client. 469 type QosConfig struct { 470 // With a prefetch count greater than zero, the server will deliver that many 471 // messages to consumers before acknowledgments are received. The server ignores 472 // this option when consumers are started with noAck because no acknowledgments 473 // are expected or sent. 474 // 475 // In order to defeat that we can set the prefetch count with the value of 1. 476 // This tells RabbitMQ not to give more than one message to a worker at a time. 477 // Or, in other words, don't dispatch a new message to a worker until it has 478 // processed and acknowledged the previous one. 479 // Instead, it will dispatch it to the next worker that is not still busy. 480 PrefetchCount int 481 482 // With a prefetch size greater than zero, the server will try to keep at least 483 // that many bytes of deliveries flushed to the network before receiving 484 // acknowledgments from the consumers. This option is ignored when consumers are 485 // started with noAck. 486 PrefetchSize int 487 488 // When global is true, these Qos settings apply to all existing and future 489 // consumers on all channels on the same connection. When false, the Channel.Qos 490 // settings will apply to all existing and future consumers on this channel. 491 // 492 // Please see the RabbitMQ Consumer Prefetch documentation for an explanation of 493 // how the global flag is implemented in RabbitMQ, as it differs from the 494 // AMQP 0.9.1 specification in that global Qos settings are limited in scope to 495 // channels, not connections (https://www.rabbitmq.com/consumer-prefetch.html). 496 Global bool 497 } 498 499 type ReconnectConfig struct { 500 BackoffInitialInterval time.Duration 501 BackoffRandomizationFactor float64 502 BackoffMultiplier float64 503 BackoffMaxInterval time.Duration 504 } 505 506 func DefaultReconnectConfig() *ReconnectConfig { 507 return &ReconnectConfig{ 508 BackoffInitialInterval: 500 * time.Millisecond, 509 BackoffRandomizationFactor: 0.5, 510 BackoffMultiplier: 1.5, 511 BackoffMaxInterval: 60 * time.Second, 512 } 513 } 514 515 func (r ReconnectConfig) backoffConfig() *backoff.ExponentialBackOff { 516 return &backoff.ExponentialBackOff{ 517 InitialInterval: r.BackoffInitialInterval, 518 RandomizationFactor: r.BackoffRandomizationFactor, 519 Multiplier: r.BackoffMultiplier, 520 MaxInterval: r.BackoffMaxInterval, 521 MaxElapsedTime: 0, // no support for disabling reconnect, only close of Pub/Sub can stop reconnecting 522 Clock: backoff.SystemClock, 523 } 524 }