进程池的应用程序级负载平衡器

时间:2014-08-12 08:45:23

标签: java c++ networking architecture

我们在C ++中使用传统的单片软件,其作用类似于请求 - 回复TCP服务器。该软件是单线程的,可以同时处理一个请求。目前,我们已经修复了这些流程的池,以便并行地为多个客户提供服务。

由于消息量很大,客户端会定期遇到请求处理的严重延迟。目前,我们有一个想法是通过在客户端和工作者之间引入一种代理来解决这个问题:

Proxy

我们希望此代理具有以下功能:

  1. 应用程序级负载平衡:通过检查请求上下文和客户端ID来扩展工作者之间的请求
  2. 控制和监控工作进程的生命周期
  3. 产生额外的工作进程(在不同的PC上)以处理峰值
  4. 实际上我们希望它的行为类似于Java中的ExecutorService,但是使用工作进程而不是线程。目前的想法是基于Jetty或Tomcat服务器在Java中实现此Balancer,内部消息队列和servlet将请求转发给工作进程。

    但我想知道:现有的解决方案(最好是Java)会自动化这个过程吗?实现这样一个代理的最简单方法是什么?

    更新:

    我对请求上下文做了什么 - 好吧,C ++服务器是非常混乱的软件。实际上,每次它接收到不同的上下文时,它会相应地更新内部缓存以匹配该上下文例如,如果您请求该服务器为您提供一些英语数据,那么它会将内部缓存重新加载到英语。如果下一个请求是法语,那么它会再次重新加载缓存。显然,我希望通过更智能地转发请求来最小化缓存重新加载的次数。

    通信协议是自制的(基于TCP / IP),但从中提取上下文部分相对容易。

    当前在客户端实现负载均衡,因此每个客户端都配置为知道所有服务器节点并以循环方式向它们发送请求。这种方法存在一些问题:客户端的复杂连接管理,与多个不了解彼此的客户的错误工作,无法管理节点生命周期。我们无法解决列出的问题重构。

    我们最有可能最终会使用自制的转发解决方案,但我仍然想知道现有产品是否至少用于流程管理?理想情况下,这将是Java应用程序服务器,可以:

    • Spawn子节点(另一个Java进程)
    • 监控他们的生命周期
    • 通过某些协议与他们沟通

    也许这个功能已经在一些现有的应用服务器中实现了?这将大大简化问题!

4 个答案:

答案 0 :(得分:5)

关于流程管理,您可以通过混合Apache Commons Exec库的功能轻松实现您的目标,这可以帮助生成具有Apache Commons Pool库的新工作实例,这将管理正在运行的实例。

实现非常简单,因为commons池将确保您可以使用一个对象,直到它返回池中。如果没有将对象返回到池中,则commons池将为您生成新实例。您可以通过添加监视程序服务(来自apache commons exec)来控制工作程序的生命周期 - 监视程序可以杀死未使用一段时间的实例,或者您也可以使用公共池本身,例如通过调用pool.clearOldest()。您还可以通过调用pool.getNumActive()来查看当前处理的请求数(有多少工作者处于活动状态)。接受GenericKeyedObjectPool的javadoc以查看更多信息。

可以使用在Tomcat服务器上运行的一个简单servlet来完成实现。这个servlet将实例化池,并通过调用pool.borowObject(参数)简单地向池中请求新的worker。在内部参数中,您可以定义工作人员应处理请求的特征(在您的情况下,参数应包括语言)。如果没有这样的工人(例如没有工作人员为法语),游泳池将为您产生新工人。此外,如果有一个工作人员但工作人员当前正在处理另一个请求,则池也将为您生成一个新工作人员(因此您将有两个工作人员处理相同的语言)。当您调用pool.returnObject(parameters,instance)时,Worker将准备好处理新请求。

整个实现花了不到200行代码(完整代码见下文)。代码包括工作进程从外部被杀或将崩溃的情况(请参阅WorkersFactory.activateObject())。

恕我直言:使用Apache Cammel对你来说不是一个好选择,因为它太大的工具而且它被设计成不同消息格式之间的中介总线。您不需要进行转换,也不需要处理不同格式的消息。寻求简单的解决方案。

package com.myapp;

import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteWatchdog;
import org.apache.commons.pool2.BaseKeyedPooledObjectFactory;
import org.apache.commons.pool2.KeyedPooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;

public class BalancingServlet extends javax.servlet.http.HttpServlet {

    private final WorkersPool workersPool;

    public BalancingServlet() {
        workersPool = new WorkersPool(new WorkersFactory());
    }


    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.getWriter().println("Balancing");

        String language = request.getParameter("language");
        String someOtherParam = request.getParameter("other");
        WorkerParameters workerParameters = new WorkerParameters(language, someOtherParam);

        String requestSpecificParam1 = request.getParameter("requestParam1");
        String requestSpecificParam2 = request.getParameter("requestParam2");

        try {
            WorkerInstance workerInstance = workersPool.borrowObject(workerParameters);
            workerInstance.handleRequest(requestSpecificParam1, requestSpecificParam2);
            workersPool.returnObject(workerParameters, workerInstance);

        } catch (Exception e) {
            e.printStackTrace();
        }


    }
}

class WorkerParameters {
    private final String workerLangauge;
    private final String someOtherParam;

    WorkerParameters(String workerLangauge, String someOtherParam) {
        this.workerLangauge = workerLangauge;
        this.someOtherParam = someOtherParam;
    }

    public String getWorkerLangauge() {
        return workerLangauge;
    }

    public String getSomeOtherParam() {
        return someOtherParam;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        WorkerParameters that = (WorkerParameters) o;

        return Objects.equals(this.workerLangauge, that.workerLangauge) && Objects.equals(this.someOtherParam, that.someOtherParam);
    }

    @Override
    public int hashCode() {
        return Objects.hash(workerLangauge, someOtherParam);
    }
}

class WorkerInstance {
    private final Thread thread;
    private WorkerParameters workerParameters;

    public WorkerInstance(final WorkerParameters workerParameters) {
        this.workerParameters = workerParameters;

        // launch the process here   
        System.out.println("Spawing worker for language: " + workerParameters.getWorkerLangauge());

        // use commons Exec to spawn your process using command line here

        // something like


        thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    String line = "C:/Windows/notepad.exe" ;
                    final CommandLine cmdLine = CommandLine.parse(line);

                    final DefaultExecutor executor = new DefaultExecutor();
                    executor.setExitValue(0);
//                    ExecuteWatchdog watchdog = new ExecuteWatchdog(60000); // if you want to kill process running too long
//                    executor.setWatchdog(watchdog);

                    int exitValue = executor.execute(cmdLine);
                    System.out.println("process finished with exit code: " + exitValue);
                } catch (IOException e) {
                    throw new RuntimeException("Problem while executing application for language: " + workerParameters.getWorkerLangauge(), e);
                }


            }
        });

        thread.start();


        System.out.println("Process spawned for language: " + workerParameters.getWorkerLangauge());


    }

    public void handleRequest(String someRequestParam1, String someRequestParam2) {
        System.out.println("Handling request for extra params: " + someRequestParam1 + ", " + someRequestParam2);

        // communicate with your application using parameters here

        // communcate via tcp or whatever protovol you want using extra parameters: someRequestParam1, someRequestParam2


    }

    public boolean isRunning() {
        return thread.isAlive();
    }


}

class WorkersFactory extends BaseKeyedPooledObjectFactory<WorkerParameters, WorkerInstance> {

    @Override
    public WorkerInstance create(WorkerParameters parameters) throws Exception {
        return new WorkerInstance(parameters);
    }

    @Override
    public PooledObject<WorkerInstance> wrap(WorkerInstance worker) {
        return new DefaultPooledObject<WorkerInstance>(worker);
    }

    @Override
    public void activateObject(WorkerParameters worker, PooledObject<WorkerInstance> p)
            throws Exception {
        System.out.println("Activating worker for lang: " + worker.getWorkerLangauge());

        if  (! p.getObject().isRunning()) {
            System.out.println("Worker for lang: " + worker.getWorkerLangauge() + " stopped working, needs to respawn it");
            throw new RuntimeException("Worker for lang: " + worker.getWorkerLangauge() + " stopped working, needs to respawn it");
        }
    }

    @Override
    public void passivateObject(WorkerParameters worker, PooledObject<WorkerInstance> p)
            throws Exception {
        System.out.println("Passivating worker for lang: " + worker.getWorkerLangauge());
    }

}

class WorkersPool extends GenericKeyedObjectPool<WorkerParameters, WorkerInstance> {

    public WorkersPool(KeyedPooledObjectFactory<WorkerParameters, WorkerInstance> factory) {
        super(factory);
    }
}

答案 1 :(得分:1)

看起来您正在寻找一个消息传递系统。 Apache Camel有许多组件可以集成不同的协议并添加自定义处理逻辑(使用XML或Java API)。 Apache Camel实现了很多(Enterprise Integration Patterns)。

它与Apache MINA集成,这也是一个很好的起点。

目前尚不清楚如何在其他计算机上动态启动新实例。我认为您至少需要在这些机器上运行一些代理,您可以请求启动新服务器。

答案 2 :(得分:1)

当您坚持使用TCP / IP协议并且想要基于内容的路由时 - 您必须自己做一些事情。

我认为您可以获得一些现成的集成平台,并为您的协议和处理程序编写适配器以进行路由。

根据我的经验:Apache Camel - 还有很长的路要走。

作为设计的第一步,我将采取:

  1. Camel作为集成总线
  2. Apache Active MQ作为消息代理(JMS)
  3. MysqlPostgre SQLHSQLDB作为数据库(基于负载和尺寸要求)
  4. 服务器的TCP / IP协议的设计适配器从客户端获取连接并将数据发送到JMS
  5. 设计路由组件从JMS接收消息,并根据以下内容将其转发到所需的队列中:服务器上下文,负载,队列长度。
  6. 设计端点组件从JMS获取消息并发送到真实服务器,获取响应并将其发送到JMS。
  7. Camel服务器的作用是构建进程中所有步骤的工作流程: 它可以从JMS获取消息并调用java方法,从调用中获取返回数据并推送到JMS等。所以你不需要自己创建它。

    JMS的作用 - 在每种类型的几个节点之间平衡工作负载:适配器,路由器和终结点。

    开放式问题仍然存在:

    1. 在服务器上监控和自动启动节点池 - 这个问题值得单独讨论。

答案 3 :(得分:0)

不确定要检查请求上下文和客户端ID以及它如何影响路由。请问内容格式是什么?如果它不是文本而是二进制,那么事情会变得更加困难。

您希望对流程进行何种控制和监控?

虽然您将此与Java Executor服务进行比较,但是将问题空间从线程扩展到进程是可以的,但是当您谈论多台PC时它会呈指数级增长。现在我们在谈论集群。

您需要通过TCP / IP对此专有请求/响应进行自定义负载平衡。现在,如果我没有过度工程,您可能还需要开始考虑节点故障策略并添加更多节点以进一步扩展系统。

处理请求映射到池化进程的当前组件是什么?是否可以重构此组件以使其可配置,以便将请求路由到池化进程或配置节点集中的某个节点?