540 likes | 556 Views
Learn how Michael Laing Architect leverages a global mesh with a memory to optimize message routing and retrieval, ensuring efficient communication for millions of users. Discover the benefits of message-based technologies like WebSocket, AMQP, and SockJS, and explore the concepts of idempotent, replicating, racy, and resolving messaging.
E N D
Michael Laing Architect New York Times michael.laing@nytimes.com
Millions of users • Classes of service: • Gold: replicate/race/resolve • Silver: prioritize • Bronze: queueable A Global Mesh with a Memory Message-based: WebSocket, AMQP, SockJS • Idempotent: • Replicating • Racy • Resolving • If in doubt: • Resend • Reconnect • Reread Event-driven: async using libev
Message: an event with data • Envelope: Routing while in motion & Locating when at rest • Metadata • Body (opaque to us) Message Envelope Metadata Body (may be absent)
Publish S3 / CloudFront Init sync Core Message Gateway Device AMQP CQL WebSocket HTTP Cassandra
Subscribe S3 / CloudFront Init Core Message Gateway Device AMQP CQL WebSocket HTTP Cassandra
Cassandra Dismiss Init Core Gateway Device Core Message Gateway Device AMQP CQL WebSocket Cassandra
S3 / CloudFront S3 / CloudFront Connect S3 / CloudFront S3 / CloudFront Message Device Message Device Message Device Message Device Device Message Device Message several Device Message Gateway Device Message Gateway Device Message Gateway Device Message Gateway Core Device Message Gateway Core Core Device Message Gateway Core Message Gateway Device millions dozens millions dozens millions Cassandra Cassandra Cassandra Cassandra Cassandra Cassandra dozens
Envelope – 2 forms of addressing • “Path”: 1) Routing a message to a user 2) Finding a message for a user Message nyt⨍aбrik
Envelope – 2 forms of addressing • “Path”: 1) Routing a message to a user 2) Finding a message for a user • “Postoffice”: Routing a message internally in the nyt⨍aбrik Message nyt⨍aбrik Core Gateway Core Gateway
The Path hierarchy • Path elements are text (utf-8 but “.” is reserved) – the 1stelement is the “category” • “category”: “feeds”, • “2nd element”: “breaking-news” • “3rd element”: “0012345”
The Path hierarchy • Path elements are text (utf-8 but “.” is reserved) – the 1stelement is the “category” • “category”: “feeds”, • “2nd element”: “breaking-news” • “3rd element”: “0012345” • The elements are joined by “.” for routing • “path”: “feeds.breaking-news.00123456”
Deeper into the Path hierarchy • For persistence, the path denotes a sorted “folder” containing messages in reverse datetime order (using the timestamp from the version 1 uuid uniquely identifying each message) • “feeds.breaking-news.56”/bd1961f5-1062-11e4-a630-406c8f1838fa • “feeds.breaking-news.56”/b94e8b45-1062-11e4-900d-406c8f1838fa
Deeper into the Path hierarchy • For persistence, the path denotes a sorted “folder” containing messages in reverse datetime order (using the timestamp from the version 1 uuid uniquely identifying each message) • “feeds.breaking-news.56”/bd1961f5-1062-11e4-a630-406c8f1838fa • “feeds.breaking-news.56”/b94e8b45-1062-11e4-900d-406c8f1838fa • Subscribing to a path is done by “binding”, typically with wildcards: “*” matches any one element, “#” matches any sequence of elements • All breaking-news messages: “feeds.breaking-news.#”
More on subscribing & retrieving • Retrieving from persistent storage can be done by path, e.g. the “latest” breaking-news messages for item 56: • “feeds.breaking-news.56”
More on subscribing & retrieving • Retrieving from persistent storage can be done by path, e.g. the “latest” breaking-news messages for item 56: • “feeds.breaking-news.56” • But retrieval can also be done using trailing wild cards: • “feeds.breaking-news.#” will return the “latest” breaking-news messages for all “current” items
More on subscribing & retrieving • Retrieving from persistent storage can be done by path, e.g. the “latest” breaking-news messages for item 56: • “feeds.breaking-news.56” • But retrieval can also be done using trailing wild cards: • “feeds.breaking-news.#” will return the “latest” breaking-news messages for all “current” items • The Cassandra data store is designed to return hierarchical queries with a single request and in the desired order
A notable simplification: • Paths for subscribing to messages and paths for retrieving persisted messages, including the use of wild cards, are the same, e.g.:
A notable simplification: • Paths for subscribing to messages and paths for retrieving persisted messages, including the use of wild cards, are the same, e.g.: • When a user logs in she is “subscribed” using her ID; messages “published” to her will be received while “persisted” messages and subscription preferences are retrieved (a few 10’s of milliseconds)
A notable simplification: • Paths for subscribing to messages and paths for retrieving persisted messages, including the use of wild cards, are the same, e.g.: • When a user logs in she is “subscribed” using her ID; messages “published” to her will be received while “persisted” messages and subscription preferences are retrieved (a few 10’s of milliseconds) • Once subscription preferences arrive, she will be “subscribed” to them and any corresponding “persisted” messages retrieved • The same paths are used for subscription and retrieval
Special Paths for individual routing • Our subscribers (millions of them) have numeric IDs – using those IDs directly for routing, specifically for the “binding” function, would be inefficient • “id.prefs.09067832” (namespace of 3rd element is too large)
Special Paths for individual routing • Our subscribers (millions of them) have numeric IDs – using those IDs directly for routing, specifically for the “binding” function, would be inefficient • “id.prefs.09067832” (namespace of 3rd element is too large) • Instead we convert the ID to base62 elements and take advantage of the patricia trie search structures built into RabbitMQ and our gateway • “id.prefs.c.2.x.M” (equivalent to the above, used for routing)
Postoffice addressing postoffice Core • The “postoffice” is a logical “bus” that connects all the services in all the nyt⨍aбrik instances globally Gateway Gateway logical view Gateway Core Gateway
Postoffice addressing postoffice Core • The “postoffice” is a logical “bus” that connects all the services in all the nyt⨍aбrik instances globally • It is physically segmented and the segments are connected using RabbitMQ “federation” Gateway Gateway logical view Gateway Core Gateway
Postoffice address elements • Each nyt⨍aбrik service has 3 basic uniquifying elements: • “region”: “us-west-2”, • “instance”: “i-123”, • “pid”: “12”
Postoffice address elements • Each nyt⨍aбrik service has 3 basic uniquifying elements: • “region”: “us-west-2”, • “instance”: “i-123”, • “pid”: “12” • And some additional qualifiers: • “product”: “search”, • “service”: “route”
Postoffice routing key • Each routing key has a “from” address embedded in it: • “region”: “us-west-2”, • “instance”: “i-123”, • “pid”: “12”, • “product”: “search”, • “service”: “resolve”
Postoffice routing key • And a “to” address: • “region”: “us-west-2”, • “instance”: “-”, • “pid”: “-”, • “product”: “search”, • “service”: “route” • Each routing key has a “from” address embedded in it: • “region”: “us-west-2”, • “instance”: “i-123”, • “pid”: “12”, • “product”: “search”, • “service”: “resolve” (the “–” means “any”)
Postoffice routing key • And a “to” address: • “region”: “us-west-2”, • “instance”: “-”, • “pid”: “-”, • “product”: “search”, • “service”: “route” • Each routing key has a “from” address embedded in it: • “region”: “us-west-2”, • “instance”: “i-123”, • “pid”: “12”, • “product”: “search”, • “service”: “resolve” (the “–” means “any”) • And an “action”: “action”: “route”
Postoffice routing key detail • And they are put together as an ordered sequence like this: • <action>.<from address>.<to address>
Postoffice routing key detail • And they are put together as an ordered sequence like this: • <action>.<from address>.<to address> • “route.\ • us-west-2.search.resolve.i-123.12.\ • us-west-2.search.route.-.-”
Postoffice routing key detail • And they are put together as an ordered sequence like this: • <action>.<from address>.<to address> • “route.\ • us-west-2.search.resolve.i-123.12.\ • us-west-2.search.route.-.-” • Meaning: This is a request for a “route” action from a specific invocation of the “search” product “resolve” service addressed to any “search” product “route” service in region “us-west-2”
Postoffice binding • Each service invocation “binds” (subscribes) to the postoffice using its unique address to get messages specifically directed to it, e.g. asynchronous RPC responses • <any action>.<any address>.<my address> • “*.\ • *.*.*.*.*.\ • us-west-2.search.route.i-123.12”
Postoffice binding for services • Each service invocation also “binds” to the postoffice using addresses that will select messages appropriate for its service • <my action>.<my domain>.<my service> • “route.\ • us-west-2.*.*.*.*.\ • *.*.route.*.*”
Postoffice binding for services • Each service invocation also “binds” to the postoffice using addresses that will select messages appropriate for its service • <my action>.<my domain>.<my service> • “route.\ • us-west-2.*.*.*.*.\ • *.*.route.*.*” • All this address manipulation is handled by common methods in the nyt⨍aбrik
Routing in the Core • For load balancing on entry to the nyt⨍aбrik Core Core Message or Core
Routing in the Core • For replication of important (gold service) messages Core Message and Core
Routing in the Core • For distribution to all consumers Core Gateway Device Core Gateway Device
Problems with Core instances • Complex connectivity: N(N-1) federation + clustering + …
Problems with Core instances • Complex connectivity: N(N-1) federation + clustering + … • Many services: input, process, resolve, reject, cache_push, …
Problems with Core instances • Complex connectivity: N(N-1) federation + clustering + … • Many services: input, process, resolve, reject, cache_push, … • Hence, problematic to manage
Problems with Core instances • Complex connectivity: N(N-1) federation + clustering + … • Many services: input, process, resolve, reject, cache_push, … • Hence, problematic to manage • And difficult to autoscale
Possible solution: refactor and simplify • A new component, the Rabbit Router, to focus on connectivity and routing