A few months ago , We started a new project . Our goal is to design a microservice that can handle many concurrent connections . We predict that the application will spend a lot of time waiting for multiple parallel I / O operation . The ideal architecture solution seems to use a non blocking approach . After a brief investigation , We decided to use Spring WebFlux As the main framework . That's because it's based on a non blocking stack , Excellent scalability and flexibility . flow stream The excellent abstraction of form seems really easy to use , Far more than based on Java NIO or Netty Of Traditional non blocking code is much better . That's why we try to be based on Spring WebFlux The reasons for implementing the service . however , Once our development work starts , Trouble comes with it .
If you're used to writing code in traditional order , You may feel a little confused . That's our impression when we started the project . occasionally , Simple things seem difficult . The most surprising thing is , Even experienced Java Developers also have to reinvent the wheel . For them , This is a new programming paradigm , After years of different programming, it's not easy to switch to it . The fact is that , Reaction flow can greatly simplify some tasks , But other tasks seem to be much more complex . Besides , Code in the form of a sequence of operators , for example map or flatMap, It's not as easy to understand as traditional line by line blocking method calls . You can't simply extract any piece of code into a private method to make it more readable . You have to use a sequence of operators on the stream .
My observation is , Responsive flow stream It's less painful for open-minded developers . Although the learning curve is absolutely steep , But I think it's worth the time .
Record context information
One of our technical requirements is a dialogue between traceable Services . relevant ID Pattern It's exactly what we need . In short ： Each incoming message is appended with a unique text value , It's a conversation identifier generated somewhere outside our service . In our case , We have two types of incoming messages ：
To provide traceability , We have to include correlation next to each log line ID, The ID Printed by code execution in the context of a given conversation . If each service participating in the conversation prints one “ The correlation ID”, It's easy to find all the logs related to it . Log aggregators are helpful , Because it collects all the logs in one place , Provides a convenient search function .
The most commonly used logging library implements “ Mapping diagnostic context ”（MDC） function , This function fully meets our needs .
stay Servlet In the world of , We will write a filter to handle each incoming HTTP request . The filter will be in the request handler （ namely Spring controller ） Before starting Correlation ID Put in MDC, And clean up the mapping after the request is processed . It will work perfectly , But remember ,MDC Use thread similarity pattern （ Implemented as a ThreadLocalJava class ） in , Save context information associated with a single thread . This means that it can only be used by a single thread from the beginning of accepting the request to the end of processing the request .
Unfortunately ,Spring WebFlux You can't work like that . A thread is likely to have multiple threads involved in a single request processing . So what should we do ？
Fortunately, ,Reactor The author of the library implements another mechanism for storing context information -Context. It can be used to transfer correlation between stream operators ID. To connect it with a well-known logging library MDC link , We use the solution described here ： https://www.novatec-gmbh.de/en/blog/how-can-the-mdc-context-be-used-in-the-reactive-spring-applications/. If you don't like it Kotlin Language , Please check out Java A similar implementation in ： https://github.com/archie-swif/webflux-mdc
We realized the idea , And it works well ！ We just need to create a custom WebFilter, Can from HTTP Read from request Correlation ID And set it in the context of the response flow . We found a weakness - Because of the implicit call MdcContextLifter, The stack trace is getting longer .
Long stack traces
The advantage of reaction flows is that they form declaration chains , Tell the publisher what happened to each element emitted . This abstraction allows complex logic to be implemented clearly . If the code works as expected , Then everything looks brilliant . You solved an extraordinary problem with just a few lines of code , You are proud of it .
This joy continues until someone sends you stack traces from production . Most of the lines you see are in Reactor.core.publisher（ And include MdcContextLifter class Other lines in the package ） start , If you implement the context logging workaround mentioned in the previous section . It won't tell you anything , Because it's a package that has nothing to do with your code . I would say it doesn't work .
Besides , I remember a very serious problem we had . Users complain that the application occasionally returns errors , But we can't figure out why . There's nothing interesting in the log , Although we know that the application will catch all possible errors and use stacktrace Record the error level .
What's the problem ？ We use Papertrail As a log aggregator . Push the application log to Papertrail The forwarder of limits the maximum size of log entries to 100,000 byte . That sounds more than enough , But it doesn't look like that to us .Stacktrace Bigger , And full of irrelevant information , For example, package reacte.code.publisher The above method call in . If Papertrail The transponder sees a long message , Just skip it . That's why the most important log entries are ignored . So what did we do ？ We have only achieved the goal of Logback An extension of , The extension removes the program line that contains the package , These packages come from react.core.publisher Start , And will stacktrace Trim to 90,000 Characters .
My conclusion is this , Further efforts are needed to achieve similar functions , Especially if you don't want to see irrelevant information and seriously consider reducing the risk of the problem .
We want to provide discovery to our service users RESTful API The ability of .Swagger Is the most famous and mature tool . Unfortunately , When we see Spring WebFlux Without an integration Library , We were surprised again . After some investigation , It finds that the development of integration is still in progress , Open Swagger UI The only way to do that is to use io.springfox：springfox-spring-webflux Snapshot version of . Using unstable libraries in production ready applications sounds terrible , however , On the other hand ,Swagger Not a key component of our services , Enabled only in non production deployment . We've been using the snapshot version for more than six months , There is no stable version yet . It's pathetic .
If you're lucky , And have MongoDB, Then you don't have to worry about . There is an official reactive driver available . because JDBC The blocking property of , So there's no reactive support for relational databases . That's the problem we're facing . The customer told us , We have to MySQL Use in flavor Aurora DB. The worst part is , When we solve all these problems , It's late in the project , So it's too late to go back to non reactors . What we decided to do was to use the official MySQL The driver , Because we're familiar with it , And we can publish the service very quickly . We are aware of all the limitations , Including the lack of ready-made support for transactional Spring The annotations in , For return Mono / Flux Methods . at present , Our services don't use transaction mechanism at all . But we know the potential consequences .
Fortunately, ,R2DBC The project is under active development . One of the goals is to make SQL Database and reaction API In combination with . It's not an official standard , So we suspect that there will be problems when integrating with other libraries , because JDBC It's been known for many years API. Recently released Spring 5.2 Support to use in the background R2DBC stay Reactive Streams Publishers Reactive transaction management on the Internet . It does sound promising , It's definitely worth a try , But we don't have any experience yet .