RequestScoped with bean re-use 基于请求的范围与Bean重用

huangapple go评论77阅读模式
英文:

RequestScoped with bean re-use

问题

我有一个管理对象的类/bean(在这个示例中,EngineManager 包含一个 Engine 对象)。Engine 对象不能被并发使用,它的初始化需要一些时间。然而,可以创建多个 EngineManager 实例,因此也可以创建多个 Engine 实例。

public class EngineManager
{
    private Engine engine;

    @PostConstruct
    public void init()
    {
        this.engine = // ... 执行耗时的初始化操作
    }

    public void doSomethingWithEngine()
    {
        // ...
    }
}

我正在尝试确定用于管理这个对象的类的 CDI 作用域。

  • 我不想将该类设置为单例,因为我可以创建多个实例,而单例会成为瓶颈。
  • 由于并发问题,我不能使用 @ApplicationScoped。
  • 我不想使用 RequestScoped,因为据我了解,这会为每个请求创建一个新实例,而 Engine 对象的昂贵初始化将会产生很大的开销。

因此,我的问题是:是否有一种(CDI)方法:

  1. 使对 EngineManager 类的访问在多线程环境下安全,
  2. 并且可以有多个 EngineManager 类的实例,并对它们进行重用?
英文:

I have a class/bean that manages an object (in this example the EngineManager contains an Engine object). The Engine object cannot be used concurrently and its initialization is a bit time consuming. However it is possible to create multiple instances of the EngineManager and hence multiple Engine instances.

public class EngineManager
{
    private Engine engine;

    @PostConstruct
    public void init()
    {
        this.engine = // ... perform costly initialization
    }

    public void doSomethingWithEngine()
    {
        // ...
    }
}

I'm trying to figure out which CDI scope to use for the class, that manages this object.

  • I don't want to make the class a singleton, since I can create multiple instances of it and a singleton would be a bottleneck.
  • I cannot use @ApplicationScoped due to concurrency issues.
  • I don't want to use RequestScoped, because to my understanding this creates a new instance for every single request and the costly initialization of the Engine object would be a lot of overhead.

So my question is: Is there a (CDI) way to

  1. make the access to the EngineManager class thread-safe and
  2. have multiple instances of the EngineManager class, that are reused?

答案1

得分: 2

简而言之:根据我的了解,没有办法在CDI中严格解决这个问题而不需要额外的努力。以下是一些通用的想法:

这个问题类似于数据库连接池的问题。解决它的一种方法是使用一组Engine实例,供EngineManager(们)选择。

再详细解释一下,如果你使用一个引擎池,只要池保证每个线程都获得不同的EngineEngineManager可以是@ApplicationScoped的。

一个有趣的方面是如何处理Engine实例不可用的情况。抛出异常是最简单的答案,但可能不适合你的用例。阻塞当前线程(可能带有超时)直到有可用的Engine是另一种次优解决方案,因为在高流量下它不会很好地扩展。

如果你的环境允许,你可以考虑一个异步的解决方案,结合使用池。一个ExecutorService(在JEE环境中请参考ManagedExecutorService)用于提交任务;JMS或其他排队机制可能更复杂设置(再次取决于你的环境),但可以提供消息持久性的可靠性(如果服务器在提交工作后但在检索结果之前崩溃,它可以在恢复并重新上线时继续并完成工作)。完全异步需要更多的工作,但如果你的特定用例需要,这可能更合适。

对评论的回应:

  • 对于传统的JEE应用程序,EJBs是处理这种用例的自然方式。你将使用应用服务器提供的设施。(在当今,我的直觉是远离EJBs...只是说说而已)
  • 你正在使用Quarkus(在我看来是个不错的选择)。如果选择使用队列,你将不得不设置一个不同的系统 - 你可以判断是否值得。Quarkus在许多方面支持异步执行(你甚至可以尝试响应式流解决方案)。
  • 我之前不知道提到的omniservices库。它可能适合你的需求,但需要转换为Quarkus扩展,因为Quarkus 目前不支持CDI可移植扩展,很遗憾。
英文:

In short: To my knowledge, there is no way to solve this strictly within CDI without extra effort. Here are some generic thoughts:

This problem is similar to that of the DB connection pool. One way to solve it is with a pool of Engine instances, from which the EngineManager(s) pick.

Elaborating a bit, and if you use an engine pool, the EngineManager can be @ApplicationScoped, as long as the pool guarantees that each thread gets a different Engine.

An interesting aspect of this is how do you deal with unavailability of Engine instances. Throwing an exception is the simplest answer, but might not be appropriate for you use case. Blocking the current thread (probably with a timeout) until an Engine is available is another sub-optimal solution because it will not scale well under traffic.

If your environment allows, you may want to consider an asynchronous solution, in combination with the pool. An ExecutorService (see ManagedExecutorService in JEE environments) where you submit tasks; JMS or other queuing mechanism might be more complex to setup (again depending on your environment) but can offer reliability in the form of message persistence (if the server crashes after you submit your work but before retrieving the result, it can resume and complete the work when it comes back online). Going full async requires more effort, but might be more appropriate if your specific use case justifies it.

Reactions to the comments:

  • EJBs are the natural way for such use cases in traditional JEE applications. You will be using the facilities provided by the application server. (My instinct is to stay away of EJBs in the present day... just saying)
  • You are on Quarkus (good IMO). If you go for a queue, you will have to setup a different system - you can judge if it is worth it. Quarkus supports asynchronous execution in many ways (and you may even want to try the reactive streams solutions).
  • I was not aware of the omniservices library mentioned. It may suit your needs, but requires conversion to a Quarkus extension, as Quarkus does not support CDI portable extensions at this time, sadly.

答案2

得分: 1

Nikos的回答很好,所以这里只是稍微扩展一下。

确实没有现成的解决方案来解决这个问题。从我理解的情况来看,主要问题在于Engine对象及其共享。您希望能够持有n个实例,并在mEngineManager实例之间分配它们。

请注意,如果您使用@InjectEngine注入到EngineManager中,引擎将绑定到管理器的生命周期。因此,如果您希望动态地进行交换(例如,一个管理器为不同的调用使用不同的引擎),那么您还必须使用动态解析(Instance<T>)。基于此,您的EngineManager可以是依赖范围或应用程序范围。

我可以想到两种处理共享Engine实例并拥有多个实例的方法:

  1. 创建一个bean,其中包含一个@Dependent范围的Engine生产者。现在,每次注入Engine时都会调用此生产者,您可以控制它返回什么。该bean可以保存一组引擎,有时如果引擎空闲,则返回现有引擎,有时可能创建新的引擎。线程安全性由您自己负责!

  2. 定义适合您需求的自定义作用域。这需要一些专业知识和对Quarkus特定API的使用,因为在CDI中,您通常会使用扩展,但在Quarkus中无法这样做。例如,在Weld SE中,您有@ThreadScoped,这可能是您可以在Quarkus中重新实现为自定义作用域并且在需要每个线程基础上拥有一个Engine时使用的内容。但是,请注意,自定义作用域确实可以实现几乎任何功能,这只是一个示例。

英文:

Nikos answer is good, so this one is just to expand it a bit.
There is indeed no ready-to-use solution for this problem. From what I understood, the main issue here is the Engine object and it's sharing. You want to be able to hold n instances and distribute them between m EngineManager instances.

Note that if you use @Inject to get Engine to EngineManager the engine is bound to the manager for the lifecycle of the manager. So if you want to swap it dynamically (e.g. one manager using different engines for different invocations), then you would have to also use dynamic resolution (Instance&lt;T&gt;). Based on this your EngineManager can be either dependent or application scoped.

I can think of two ways to go about the Engine instance being shares and having multiple instances.

  1. Create a bean that holds a @Dependent scope producer for Engine. Now this producer gets called for every injection of Engine and you can control what it returns. The bean can hold a collection of Engines and sometimes it gives you an existing one if they are free, sometimes it could create new one. Thread safety is up to you though!

  2. Define your own custom scope that will fit your needs. This requires some expertise and usage of Quarkus specific APIs as in CDI you would normally use extensions but you cannot do that in Quarkus. For instance in Weld SE, you have @ThreadScoped which might be something that you could re-implement as custom scope in Quarkus and use in case you want an Engine to be on a per-thread basis. However, custom scoped can really do next to anything, this is just an example.

huangapple
  • 本文由 发表于 2020年4月9日 22:38:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/61123691.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定