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

Practical non-blocking microservices in Java 8 - JDD

Practical non-blocking microservices in Java 8 - JDD

Talk at #JDDKrakow2016 with @MichalBalinski about non-blocking microservices

Oleksandr Goldobin

October 10, 2016
Tweet

Other Decks in Technology

Transcript

  1. Cloud of microservices for secure IoT gateway backing service core

    service gateway gateway core service backing service
  2. The demand and characteristics of our domain Crowded IoT environment

    Slow, long lived connections Big amount of concurrent connections Scalability and resilience Rather I/O intensive than CPU intensive
  3. OTA Gateway – Use Case OTA (Over-the-Air) Gateway TSM Trusted

    Service Manager Security Module DB HTTP 1. submit scripts
  4. OTA Gateway – Use Case OTA (Over-the-Air) Gateway TSM Trusted

    Service Manager Security Module DB HTTP 1. submit scripts HTTP 2. encrypt scripts
  5. OTA Gateway – Use Case OTA (Over-the-Air) Gateway TSM Trusted

    Service Manager Security Module DB HTTP 1. submit scripts HTTP 2. encrypt scripts TCP 3. store scripts
  6. OTA Gateway – Use Case OTA (Over-the-Air) Gateway TSM Trusted

    Service Manager Security Module DB HTTP 1. submit scripts HTTP 2. encrypt scripts TCP 4. submition response 3. store scripts
  7. OTA Gateway – Use Case OTA (Over-the-Air) Gateway TSM Trusted

    Service Manager Security Module DB HTTP 1. submit scripts HTTP 2. encrypt scripts TCP 4. submition response HTTP 5. poll scripts 3. store scripts
  8. OTA Gateway – Use Case OTA (Over-the-Air) Gateway TSM Trusted

    Service Manager Security Module DB HTTP 1. submit scripts HTTP 2. encrypt scripts TCP 3. store scripts 4. submition response HTTP 5. poll scripts 6. search scripts
  9. OTA Gateway – Use Case OTA (Over-the-Air) Gateway TSM Trusted

    Service Manager Security Module DB HTTP 1. submit scripts HTTP 2. encrypt scripts TCP 3. store scripts 4. submition response HTTP 5. poll scripts 6. search scripts 7. response with scripts
  10. OTA Gateway – Use Case OTA (Over-the-Air) Gateway TSM Trusted

    Service Manager Security Module DB HTTP HTTP HTTP TCP
  11. OTA Gateway – Use Case OTA (Over-the-Air) Gateway TSM Trusted

    Service Manager Security Module DB Log Storage HTTP HTTP HTTP TCP File I/O Monitoring System HTTP
  12. OTA Gateway Blocking - technologies 17 TSM DB Log Storage

    HTTP TCP STDOUT OTA Gateway JAX-RS Logback appender Security Module JAX-RS Jedis JAX-RS Client
  13. OTA Gateway – Blocking - test OTA (Over-the-Air) Gateway send

    2 scripts per request Security Module DB Log Storage HTTP HTTP HTTP TCP File I/O emulated latency 200 ms expected latency < 450 ms verified with throughput over 7k req/s
  14. Blocking – 1k threads OTA (Over-the-Air) Gateway send 2 scripts

    per request Security Module DB Log Storage HTTP HTTP HTTP TCP File I/O emulated latency 200 ms max 1000 connections max 1000 threads expected latency < 450 ms
  15. The drawbacks of classic synchronous I/O One thread per connection

    Threads waiting instead of running Context switches Resource wasting (~1 MB per thread - 64bit)
  16. OTA Gateway Non-blocking - technologies 23 TSM DB Log Storage

    HTTP TCP STDOUT OTA Gateway JAX-RS 2.0 Logback async appender Async Http Client 2 (Netty) Security Module JAX-RS 2.0 Lettuce (Netty)
  17. Non-blocking – 16 threads OTA (Over-the-Air) Gateway send 2 scripts

    per request Security Module DB Log Storage HTTP HTTP HTTP TCP File I/O emulated latency 200 ms no limit for connections max 16 threads expected latency < 450 ms
  18. OTA Gateway Blocking – sequence diagram OTA Gateway TSM Security

    Module DB Logs loop 1. submit scripts 2. encrypt script 3a. store script 4. submition response 3b. count scripts
  19. OTA Gateway Non-blocking – sequence diagram OTA Gateway TSM Security

    Module DB Logs loop 1. submit scripts 2. encrypt script 3a. store script 4. submition response 3b. count scripts
  20. OTA Non-blocking – reality OTA Gateway TSM Security Module DB

    Logs „looped processing chain” 1. submit scripts 4. submition response 3b. count scripts HTTP Server 2. encrypt script 3a. store script Logging DB Client Security Client
  21. Code. Bird view 31 10 October 2016 package org.demo.ota.blocking.rest; @Path("se")

    public class ScriptSubmissionResource extends Application { private static final Logger log = LoggerFactory.getLogger(ScriptSubmissionResource.class); private static final ResourceMetrics METRICS = new ResourceMetrics("ota_submission"); private SecureModuleClient secureModuleClient = SecureModuleClient.instance(); private ScriptStorageClient scriptStorageClient = ScriptStorageClient.instance(); @POST @Path("/{seId}/scripts") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.TEXT_PLAIN) public long submitScripts(@PathParam("seId") String seId, List<Script> scripts) { MDC.put("flow", "submission"); MDC.put("se", seId); return METRICS.instrument(() -> { log.debug("Processing {} scripts submission", scripts.size(), seId); for (int i = 0; i < scripts.size(); i++) { final Script script = scripts.get(i); log.debug("Encrypting {} script", i); final String encryptedPayload = secureModuleClient.encrypt(seId, script.getPayload()); script.setPayload(encryptedPayload); log.debug("Storing encrypted script {}", i); scriptStorageClient.storeScript(seId, script); } long numberOfScripts = scriptStorageClient.numberOfScriptsForSe(seId); log.debug("Request processed", seId); return numberOfScripts; }); } @Override public Set<Object> getSingletons() { return Collections.singleton(this); } } package org.demo.ota.nonblocking.rest; @Path("se") public class ScriptSubmissionResource extends Application { private static final Logger log = LoggerFactory.getLogger(ScriptSubmissionResource.class); private static final ResourceMetrics METRICS = new ResourceMetrics("ota_submission"); private SecureModuleClient secureModuleClient = SecureModuleClient.instance(); private ScriptStorageClient scriptStorageClient = ScriptStorageClient.instance(); @POST @Path("/{seId}/scripts") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.TEXT_PLAIN) public void submitScripts( @PathParam("seId") String seId, List<Script> scripts, @Suspended final AsyncResponse asyncResponse ) { final DiagnosticContext diagnosticContext = new DiagnosticContext("submission", seId); METRICS.instrumentStage(() -> { log.debug("{} Processing {} scripts submission", diagnosticContext, scripts.size()); return encryptAndStoreAllScripts(diagnosticContext, seId, scripts) .thenCompose( ignore -> scriptStorageClient.numberOfScriptsForSe(seId) ); }) .whenComplete((numberOfScripts, e) -> { if (e != null) { asyncResponse.resume(e); } else { log.debug("{} Request processed", diagnosticContext); asyncResponse.resume(numberOfScripts); } }); } private CompletionStage<Void> encryptAndStoreAllScripts( final DiagnosticContext diagnosticContext, final String seId, final List<Script> scripts ) { CompletionStage<Void> stage = null; // <- non final field, potential concurrent access bug! for (int i = 0; i < scripts.size(); i++) { final int scriptIndex = i; final Script script = scripts.get(scriptIndex); if (stage == null) { stage = encryptAndStoreSingleScript(diagnosticContext, seId, scriptIndex, script); } else { stage = stage.thenCompose(ignore -> encryptAndStoreSingleScript(diagnosticContext, seId, scriptIndex, script)); } } return stage; } private CompletionStage<Void> encryptAndStoreSingleScript( final DiagnosticContext diagnosticContext, final String seId, final int scriptIndex, final Script script ) { log.debug("{} Encrypting script {}", diagnosticContext, scriptIndex); return secureModuleClient .encrypt(seId, script.getPayload()) .thenCompose( encryptedPayload -> { log.debug("{} Storing encrypted script {}", diagnosticContext, scriptIndex); return scriptStorageClient.storeScript(seId, new Script(encryptedPayload)); } ); } @Override public Set<Object> getSingletons() { return new HashSet<>(Collections.singletonList(this)); } }
  22. Code. Blocking. Submission 1 10 October 2016 32 @POST @Path("/{seId}/scripts")

    @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.TEXT_PLAIN) public long submitScripts(@PathParam("seId") String seId, List<Script> scripts) { MDC.put("flow", "submission"); // ß Setting diagnostic context MDC.put("se", seId); return METRICS.instrument(() -> { // ß Instrumenting with metrics //...
  23. Code. Blocking. Submission 2 10 October 2016 33 log.debug("Processing {}

    scripts submission", scripts.size(), seId); for (int i = 0; i < scripts.size(); i++) { ß Cycle through the scripts final Script script = scripts.get(i); log.debug("Encrypting {} script", i); final String encryptedPayload = secureModuleClient .encrypt(seId, script.getPayload()); ß Encrypting the script script.setPayload(encryptedPayload); log.debug("Storing encrypted script {}", i); scriptStorageClient.storeScript(seId, script); ß Saving the script into DB } long numberOfScripts = scriptStorageClient.numberOfScriptsForSe(seId); ß Getting current number of scripts in DB log.debug("Request processed", seId); return numberOfScripts;
  24. Code. Non-blocking. Submission 1 10 October 2016 34 @POST @Path("/{seId}/scripts")

    @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.TEXT_PLAIN) public void submitScripts( @PathParam("seId") String seId, List<Script> scripts, @Suspended final AsyncResponse asyncResponse ) { final DiagnosticContext diagnosticContext = new DiagnosticContext("submission", seId); ß Creating diagnostic context METRICS.instrumentStage(() -> { ß Instrumenting with metrics
  25. Code. Non-blocking. Submission 1 10 October 2016 35 @POST @Path("/{seId}/scripts")

    @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.TEXT_PLAIN) public void submitScripts( @PathParam("seId") String seId, List<Script> scripts, @Suspended final AsyncResponse asyncResponse ) { final DiagnosticContext diagnosticContext = new DiagnosticContext("submission", seId); ß Creating diagnostic context METRICS.instrumentStage(() -> { ß Instrumenting with metrics
  26. Code. Non-blocking. Submission 2 10 October 2016 36 METRICS.instrumentStage(() ->

    { log.debug( "{} Processing {} scripts submission", diagnosticContext, scripts.size()); return encryptAndStoreAllScripts(diagnosticContext, seId, scripts) .thenCompose( ignore -> scriptStorageClient.numberOfScriptsForSe(seId) ); }) .whenComplete((numberOfScripts, e) -> { if (e != null) { asyncResponse.resume(e); } else { log.debug("{} Request processed", diagnosticContext); asyncResponse.resume(numberOfScripts); } });
  27. Code. Non-blocking. Submission 3 10 October 2016 37 private CompletionStage<Void>

    encryptAndStoreAllScripts( final DiagnosticContext diagnosticContext, final String seId, final List<Script> scripts ) { CompletionStage<Void> stage = null; // <- non final field, potential // concurrent access bug! for (int i = 0; i < scripts.size(); i++) { ß Cycle through the scripts final int scriptIndex = i; final Script script = scripts.get(scriptIndex); if (stage == null) { stage = encryptAndStoreSingleScript( diagnosticContext, seId, scriptIndex, script); } else { stage = stage.thenCompose(ignore -> encryptAndStoreSingleScript( diagnosticContext, seId, scriptIndex, script)); } } return stage; }
  28. Code. Non-blocking. Submission 4 10 October 2016 38 private CompletionStage<Void>

    encryptAndStoreSingleScript( final DiagnosticContext diagnosticContext, final String seId, final int scriptIndex, final Script script ) { log.debug("{} Encrypting script {}", diagnosticContext, scriptIndex); return secureModuleClient .encrypt(seId, script.getPayload()) ß Encrypting the script .thenCompose( encryptedPayload -> { log.debug( "{} Storing encrypted script {}", diagnosticContext, scriptIndex); return scriptStorageClient.storeScript( ß Saving the script into seId, the DB new Script(encryptedPayload)); } ); }
  29. Code. Blocking. Integration 10 October 2016 39 private final JedisPool

    pool; public void storeScript(String seId, Script script) { try (Jedis jedis = pool.getResource()) { jedis.rpush(seId, script.getPayload()); } } public long numberOfScriptsForSe(String seId) { try (Jedis jedis = pool.getResource()) { return jedis.llen(seId); } } public Optional<Script> nextScript(String seId) { try (Jedis jedis = pool.getResource()) { return Optional.ofNullable(jedis.lpop(seId)).map(Script::new); } } public String encrypt(String keyDiversifier, String payload) { // ... }
  30. Code. Non-Blocking. Integration 10 October 2016 40 private final RedisAsyncCommands<String,

    String> commands; public CompletionStage<Void> storeScript(String seId, Script script) { return commands .rpush(seId, script.getPayload()) .thenApply(ignore -> null); } public CompletionStage<Long> numberOfScriptsForSe(String seId) { return commands.llen(seId); } public CompletionStage<Optional<String>> nextScript(String seId) { return commands.lpop(seId).thenApply(Optional::ofNullable); } public CompletionStage<String> encrypt(String keyDiversifier, String payload) { // ... }
  31. Diagnostics in non-blocking systems No clear stack traces, need for

    good logs Name your threads properly MDC becomes useless (thread locals) Explicitly pass debug context to trace flows Use metrics Be prepared for debugging non-obvious errors
  32. Lessons learned, part 1 Vanila Java 8 for NIO µ-services

    Netty best for custom protocols in NIO Unit tests should be synchronous Load/stress testing is a must Make bulkheading and plan your resources
  33. Conclusion Non-blocking processing can really save your resources (== money)

    But! Weight all pros and cons and use non-blocking processing only if you really need it.
  34. Thank you. Michał Baliński [email protected] Oleksandr Goldobin [email protected] goldobin @goldobin

    balonus @MichalBalinski Readings: • https://github.com/balonus/blocking-vs-nonblocking-demo • C10k Problem, C10M Problem, Asynchronous I/O • Boost application performance using asynchronous I/O (M. Tim Jones, 2006) • Zuul 2 : The Netflix Journey to Asynchronous, Non-Blocking Systems (Netflix, 2016) • Thousands of Threads and Blocking I/O: The Old Way to Write Java Servers Is New Again (and Way Better) (Paul Tyma, 2008) • Why Non-Blocking? (Bozhidar Bozhanov, 2011)