Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building Composable Services (with notes)

Building Composable Services (with notes)

Noah Kantrowitz

July 20, 2014
Tweet

More Decks by Noah Kantrowitz

Other Decks in Technology

Transcript

  1. What? Why? How? Perils? Press Start This talk is going

    to cover four aspects of composable services. What they are, why to build them, how to build them, and what common pitfalls to avoid.
  2. f(x, y) = z A function, in the mathematical sense

    is some operation that takes inputs and produces an output.
  3. f(x) = f(f(x)) Idempotence means that a function's output doesn't

    change when you run it twice. This is important when talking about operations that might be run 1-or-more times. If you click "log in" twice, you should get the same result as if you clicked it once, this is idempotence.
  4. f(x) g(x) f(g(x)) Composability is a property of functions, where

    you can use the output of one function as an input to another.
  5. req_user(x) user_id(x) user_id(req_user(x)) This is already a common pattern in

    many applications, and maps nicely to an object oriented style.
  6. post('http://login') get('http://search') The fun part is applying this idea across

    a network. Rather than having one big application with code modules for each task, build lots of little services that talk to each other.
  7. Availability The single biggest reason to use this style is

    the fault tolerance. A failure can bring down some of the services, but the rest will continue to operate as best they can. If your search service goes down, the search box will be disabled, but users should still be able to log in.
  8. Scaling You can also scale up services more easily. If

    your search service gets overloaded, simply launch more of them behind a load balancer.
  9. Testing The difficulty of testing a service goes up exponentially

    as the service gets bigger and has more interacting features. Small, isolated services lead to easier testing and thus often better test coverage.
  10. Logistics As long as APIs are agreed upon between services,

    the deployment and operations of one need not impact the others. In many organizations, these are handled by different teams so this leads to a natural separation of concerns.
  11. Frameworks Storage Rich Data Discovery Resilience Containers Level Up Select

    a Skill There are a lot of little things that contribute to successful microservices.
  12. µ-frameworks While anything can be used to build a small,

    self-contained service, some tools are easier than others. I will focus on HTTP and REST-ish tools for most of this talk as they are the most common.
  13. Flask (Python) Sinatra (Ruby) Express (JavaScript) The three most popular

    frameworks in their respective languages are Flask, Sinatra, and Express. These all share a simple API, basic URL routing, and minimal integration with things like an ORM or HTML rendering library. As most of our services will be making HTTP queries to other services and rendering results as JSON, this saves on unnecessary complexity.
  14. ZeroMQ nanomsg ProtoBufs Cap'nProto While HTTP and JSON are the

    most common formats used, you should know about a few of the alternatives. ZeroMQ and nanomsg provide a more compact wire protocol than HTTP, and Protocol Buffers and Cap'n Proto provide a more compact message serialization than JSON.
  15. Data Storage (aka state) I mentioned most of your services

    will consume data from other services, but eventually some information does need to be stored somewhere. Just as we build models to wrap the database to control database access, in a composable world we make model services. This helps keep the surface area between the services and the databases to minimum.
  16. AP Database While the specifics of different databases are beyond

    the scope of this talk, the decentralized nature of this style does mesh very well with AP databases like Riak and Cassandra.
  17. Cache is the enemy The conventional wisdom in many web

    development circles is to cache early and cache often. I am here to tell you down this path lies madness. Each of those caches is really a new database to worry about, and all the earlier issues with service/storage interactions apply again. If something must be cached, perhaps due to being very slow to compute but too big to store ahead of time, just like with the database there should be a model service that wraps the cache and hides it from the rest.
  18. Rich Data {id: me, cart: http...} Rather than passing around

    very large data structures, you can break it into more manageable chunks and include links to them. Having the links included in the data rather than simply included tokens or opaque identifiers helps keep all the logic for accessing that bit of data in the service that manages it.
  19. Hypermedia APIs And as a natural extension of rich data,

    hypermedia APIs provide some structures for common problems, like related objects and pagination.
  20. Service Discovery Now you have two services that want to

    communicate. How do they know where to find each other? Service discovery provides a way for cooperating services to locate each other.
  21. Self-Organization One of the most important properties in any distributed

    system is self-organization, the ability of the system to shape itself to some extent. This allows the system to effectively route around minor failures, like a load balancer removing servers that are failing a health check.
  22. DNS nslookup('login') DNS is one of the earliest forms of

    service discovery. In modern systems this can take one of two approaches, either use multiple records and round-robin on the client side or have the name map to a load balancer like HAProxy and have services register themselves with it. For the former approach, often this means that new services must be registered manually by an admin, and the latter means you need some other system to register with the load balancer. In cloud platforms that offer DNS or load balancer APIs, this can still be quite powerful.
  23. ZooKeeper CP Database If you want more fine-grained control over

    service registration and discovery, ZooKeeper is the most widely used tool for cluster management. It also enables higher-level operations like leader election and ephemeral registration.
  24. Etcd Serf Consul Archaius Many services have grown in the

    shadow of ZooKeeper. As with databases, the specifics are beyond this talk, but be sure to check out all the options as each makes different tradeoffs and offers different APIs.
  25. Resilience Building microservices doesn't automatically make them fault tolerant, but

    it does make it a lot easier than with a single, monolithic application.
  26. Timeouts Idempotent Retries The two most important things in making

    a resilient services are careful control over network timeouts and ensuring that operations are idempotent. This allows you to detect failure quickly, and then simply repeat the failed operation as needed. Some operations can be naturally idempotent, like deleting a record.
  27. post('chpw', nonce: 314) Others need explicit idempotence checks, such as

    checking update times or nonces to avoid race conditions.
  28. Any service can be down Above all else, always have

    a strategy for dealing with any service being down. If it was a critical dependency of your service then perhaps you just send back an error message, but degrade gracefully where possible. As before, better to have the search box disabled than the whole site be down. This is the very essence of composable systems.
  29. Async Messaging Queues When possible, use asynchronous messages instead of

    direct calls. This allows the queue to serve as a buffer between producer and consumer during failures and keeps latency down on the response to the user.
  30. AMQP Kafka Two quick tool recommendations, AMQP and RabbitMQ in

    particular are the gold standard in queueing systems. Kafka is newer but has an impressive feature set and user base.
  31. LXC Jails Zones Docker/Mesos This pairs very nicely with low-overhead

    virtualization systems like Linux's LXC, FreeBSD's Jails, and Solaris' Zones. Docker and Mesos both offer higher-level interfaces to these technologies, though both are complex topics to say the least.
  32. Security Isolation Using these containerization systems allows putting very hard

    boundaries between each service, which helps with a defense-in-depth strategy. A single vulnerable service is less likely to cascade to others.
  33. Immutable Deployment While not a requirement for it, containers also

    play nicely with immutable deployment. This is the idea that once launched, a container is a read-only object. In turn, this allows for powerful techniques like rolling deploys and rapid-response auto-scaling.
  34. Logical Boundaries As your graph of services gets bigger, it

    is common to find clusters of logically-related services that share an internal support service. Just as public/private functions work in a monolithic application, you can use subnet boundaries and other network-level controls to enforce system boundaries with microservices.
  35. Cascade Failures One common issue is cascade failures and overloads.

    This is especially prevalent when caching slow operations, after a deploy the cache will get flooded with requests and may not be able to handle them all. To address this you can build back-pressure in to the system. If a service can't handle incoming requests it can unregister from service discovery or signal things calling it to wait before trying again.
  36. Poor Visibility The move from one application to many can

    impact visibility in to the status of the system and hinder debugging.
  37. Health & Metrics Central Logging Dashboards While important in any

    infrastructure, having solid monitoring and log aggregation is absolutely critical with microservices. Specialized reporters and dashboards like Sentry and Graphite can provide important overviews of system health.
  38. Complex Deployments Similarly deploying many microservices requires a lot more

    coordination than a single codebase. Orchestration tools like Fabric and RunDeck can help with this, though training and documentation are still important, especially for cross-team services.
  39. Minimalist Self-organizing Fault-tolerant Just keep these three goals in mind

    and you'll be well on your way to building better services and better APIs.