3 ms·
Non-MicroProfile Helidon uses a reactive interface for most things, which lends itself well to async operations. It a very nice, simple and powerful abstraction
by Two4 3y ago
Non-MicroProfile Helidon uses a reactive interface for most things, which lends itself well to async operations. It a very nice, simple and powerful abstraction that makes async relatively easy. IMO anything webserver or client should be async when virtual threads aren't available, due to Java having the tendency to quickly build up a ton of threads when you use the one-thread-per-request model. That being said, virtual threads does make that model viable at scale, so reactive async probably isn't as necessary.
- strangemonad 3y agoI’ve had no problems scaling jetty to 400-500 qps per node on small thread pools.
- mikmoila 3y agoJetty can be used with virtual threads: https://webtide.com/jetty-12-virtual-threads-support/ https://webtide.com/jetty-12-virtual-threads-support/
- Two4 3y agoAs soon as you start talking thread pools, you're talking about async task dispatching at some level, even if it's abstracted away at the API level. What I'm talking about is using a virtual thread, not event-driven shuttling of stateful objects between threads via task queues, to complete a request from start to finish.
- twic 3y agoReactive APIs are horrible, and precisely what I'm trying to avoid.
- Svenskunganka 3y agoThe main problem I have with reactive programming in Java, at least with Project Reactor which is the only one I've used is the cryptic stack traces. You can regain some information by enabling runtime instrumentation to be used in development, but has too much of a performance hit to be used in production. It doesn't produce much better stack traces either. Trying to read a profiler flamegraph is nigh impossible for reactive code. For the debugger, you need to manually insert breakpoints in each step of the chain, because otherwise you'll just be looking around the reactive library's internal code. The other problems I have is that as soon as you need to do anything blocking (e.g talking to SQLite or RocksDB over JNI), the whole thing falls apart in terms of threading. Doing a HTTP request somewhere in a reactive chain that does some blocking operations - well shit now you're blocking the reactor-http-netty-* threads, which are fixed so you're worse off than with thread-per-request at that point. Or even just using Caffeine cache, or any caching library really, but Caffeine is one of few that prevents async stampede - well shit now you're running on ForkJoin pool during cache misses, but "reactive" threads on cache hits. You can see how this quickly becomes a tangled mess of thread-hopping and avoiding blocking event loops, and it's extremely complicated to get this right with how subscribeOn/publishOn works and how the profiler can't tell you which threads runs what code because the stack traces are filled with garbage. You also opt out of a lot of what Java offers in terms of synchronization. Using the synchronization keyword is a big no-no, the JVM can park the thread while waiting for the lock. Now that thread cannot schedule other tasks just because that one task ran into lock contention, and it's highly likely a fixed pool of threads. You're basically left with CAS, AtomicLong/AtomicBoolean/etc, and that's it. I've even seen BlockHound, project reactor's tool to help you find if you are blocking somewhere, trigger "Blocking call to LockSupport.park()!" during a ConcurrentHashMap lookup. Add to this that most caching libraries are implemented on top of ConcurrentHashMap. Getting async right in reactive code has a huge mental overhead and you need intricate knowledge of the libraries you depend on, the platform you run on and what it is that actually enables async in the kernel, and where it is unavailable. Good example of this; that UUID library you use uses a CSPRNG. Have you set the JVM flag to read it from the non-blocking /dev/urandom rather than the default /dev/random on Linux? I think reactive programming in Java is decent as long as all you're really doing is accepting/performing HTTP requests and talking to a database that has a reactive client connector. In essence, anything that strictly deals with networking, since that's all that can really be made truly async currently, thanks to epoll/kqueue/iocp on linux/mac/windows respectively. Hopefully io_uring will broaden that to file IO as well eventually, but unfortunately that's linux-only for now. Contrast this with Go's scheduler that just takes care of this for you - no need to think about it. Or Rust + Tokio, where it is explicit with tokio::spawn_blocking and offering async counterparts to std Mutex, RWLock, Channels, etc that doesn't block the current thread on lock contention.
- NovaX 3y agonot to distract from your valid points but, when used properly, Caffeine + Reactor can work together really nicely [1]. [1]: https://github.com/ben-manes/caffeine/blob/master/examples/coalescing-bulkloader-reactor/README.md https://github.com/ben-manes/caffeine/blob/master/examples/c...
- geodel 3y agoHelidon 4 is massive refactoring to support Java 21/ virtual thread from grounds up to the point that most reactive stuff is removed and replaced plain sync APIs.