Data Consistency Across Microservices

2019-03-08 15:52发布

While each microservice generally will have its own data - certain entities are required to be consistent across multiple services.

For such data consistency requirement in a highly distributed landscape such as microservices architecture, what are the choices for design? Of course, I do not want shared database architecture, where a single DB manages the state across all the services. That violates isolation and shared-nothing principles.

I do understand that, a microservice can publish an event when an entity is created, updated or deleted. All other microservices which are interested in this event can accordingly update the linked entities in their respective databases.

This is workable, however it leads to a lot of careful and coordinated programming effort across the services.

Can Akka or any other framework solve this use case? How?

EDIT1:
Adding the below diagram for clarity.
Basically, I am trying to understand, if there are available frameworks today that can solve this data consistency problem.

For the queue I can use any AMQP software such as RabbitMQ or Qpid etc. For the data consistency framework, I am not sure if presently Akka or any other software can help. Or is this scenario so uncommon, and such an anti-pattern that no framework should be ever needed?
enter image description here

5条回答
狗以群分
2楼-- · 2019-03-08 16:14

I think there are 2 main forces at play here:

  • decoupling - that's why you have microservices in the first place and want a shared-nothing approach to data persistence
  • consistency requirement - if I understood correctly you're already fine with eventual consistency

The diagram makes perfect sense to me, but I don't know of any framework to do it out of the box, probably due to the many use-case specific trade-offs involved. I'd approach the problem as follows:

The upstream service emits events on to the message bus, as you've shown. For the purpose of serialisation I'd carefully choose the wire format that doesn't couple the producer and consumer too much. The ones I know of are protobuf and avro. You can evolve your event model upstream without having to change the downstream if it doesn't care about the newly added fields and can do a rolling upgrade if it does.

The downstream services subscribe to the events - the message bus must provide fault-tolerance. We're using kafka for this but since you chose AMQP I'm assuming it gives you what you need.

In case of network failures (e.g. the downstream consumer cannot connect to the broker) if you favour (eventual) consistency over availability you may choose to refuse to serve requests that rely on data that you know can be more stale than some preconfigured threshold.

查看更多
聊天终结者
3楼-- · 2019-03-08 16:15

Theoretical Limitations

One important caveat to remember is the CAP theorem:

In the presence of a partition, one is then left with two options: consistency or availability. When choosing consistency over availability, the system will return an error or a time-out if particular information cannot be guaranteed to be up to date due to network partitioning.

So by "requiring" that certain entities are consistent across multiple services you increase the probability that you will have to deal with timeout issues.

Akka Distributed Data

Akka has a distributed data module to share information within a cluster:

All data entries are spread to all nodes, or nodes with a certain role, in the cluster via direct replication and gossip based dissemination. You have fine grained control of the consistency level for reads and writes.

查看更多
小情绪 Triste *
4楼-- · 2019-03-08 16:18

The Microservices architectural style tries to allow organizations to have small teams own services independent in development and at runtime. See this read. And the hardest part is to define the service boundaries in a useful way. When you discover that the way you split up your application results in requirements impacting multiple services frequently that would tell you to rethink the service boundaries. The same is true for when you feel a strong need to share entities between the services.

So the general advice would be to try very hard to avoid such scenarios. However there may be cases where you cannot avoid this. Since a good architecture is often about making the right trade-offs, here some ideas.

  1. Consider expressing the dependency using service interfaces (API) instead of a direct DB dependency. That would allow each service team to change their internal data schema as much as required and only worry about the interface design when it comes to dependencies. This is helpful because it is easier to add additional APIs and slowly deprecate older APIs instead of changing a DB design along with all dependent Microservices (potentially at the same time). In other words you are still able to deploy new Microservice versions independently, as long as the old APIs are still supported. This is the approach recommended by the Amazon CTO, who was pioneering a lot of the Microservices approach. Here is a recommended read of an interview in 2006 with him.

  2. Whenever you really really cannot avoid using the same DBs and you are splitting your service boundaries in a way that multiple teams/services require the same entities, you introduce two dependencies between the Microservice team and the team that is responsible for the data scheme: a) Data Format, b) Actual Data. This is not impossible to solve, but only with some overhead in organization. And if you introduce too many of such dependencies your organization will likely be crippled and slowed down in development.

a) Dependency on the data scheme. The entities data format cannot be modified without requiring changes in the Microservices. To decouple this you will have to version the entities data scheme strictly and in the database support all versions of the data that the Microservices are currently using. This would allow the Microservices teams to decide for themselves when to update their service to support the new version of the data scheme. This is not feasible with all use cases, but it works with many.

b) Dependency on the actual collected data. The data that has been collected and is of a known version for a Microservice is OK to use, but the issue occurs when you have some services producing a newer version of the data and another service depends on it - But was not yet upgraded to being able to read the latest version. This problem is hard to solve and in many cases suggests you did not chose the service boundaries correctly. Typically you have no choice but to roll out all services that depend on the data at the same time as upgrading the data in the database. A more wacky approach is to write different versions of the data concurrently (which works mostly when the data is not mutable).

To solve both a) and b) in some other cases the dependency can be reduced by hidden data duplication and eventual consistency. Meaning each service stores its own version of the data and only modifies it whenever the requirements for that service change. The services can do so by listening to a public data flow. In such scenarios you would be using an event based architecture where you define a set of public events that can be queued up and consumed by listeners from the different services that will process the event and store whatever data out of it that is relevant for it (potentially creating data duplication). Now some other events may indicate that internally stored data has to be updated and it is each services responsibility to do so with its own copy of the data. A technology to maintain such a public event queue is Kafka.

查看更多
戒情不戒烟
5楼-- · 2019-03-08 16:18

"accordingly update the linked entities in their respective databases" -> data duplication -> FAIL.

Using events to update other databases is identical to caching which brings cache consistency problem which is the problem you arise in your question.

Keep your local databases as separated as possible and use pull semantics instead of push, i.e. make RPC calls when you need some data and be prepared to gracefully handle possible errors like timeouts, missing data or service unavailability. Akka or Finagle gives enough tools to do that right.

This approach might hurt performance but at least you can choose what to trade and where. Possible ways to decrease latency and increase throughput are:

  • scale data provider services so they can handle more req/sec with lower latency
  • use local caches with short expiration time. That will introduce eventual consistency but really helps with performance.
  • use distributed cache and face cache consistency problem directly
查看更多
爱情/是我丢掉的垃圾
6楼-- · 2019-03-08 16:21

I think you can approach this issue from 2 angles, service collaboration and data modelling:

Service collaboration

Here you can choose between service orchestration and service choreography. You already mentioned the exchange of messages or events between services. This would be the choreography approach which as you said might work but involves writing code in each service that deals with the messaging part. I'm sure there are libraries for that though. Or you can choose service orchestration where you introduce a new composite service - the orchestrator, which can be responsible for managing the data updates between the services. Because the data consistency management is now extracted into a separate component, this would allow you to switch between eventual consistency and strong consistency without touching the downstream services.

Data modelling

You can also choose to redesign the data models behind the participating microservices and to extract the entities that are required to be consistent across multiple services into relationships managed by a dedicated relationship microservice. Such a microservice would be somewhat similar to the orchestrator but the coupling would be reduced because the relationships can be modelled in a generic way.

查看更多
登录 后发表回答