Gearman入门及使用(JAVA调用)

前言
因为新公司用了这个框架做任务分发,所以我才有机会知道并学习!那么Gearman到底是个什么样子的框架或系统呢?它有哪些作用、优缺点呢?
Gearman是一个用来把工作委派给其他机器、分布式的调用更适合做某项工作的机器、并发的做某项工作在多个调用间做负载均衡、或用来在调用其它语言的函数的系统。可用于sso 分发连接,但有弊端就是占用系统资源较多,例如CPU、内存。(来源于百度百科)
简单来说,Gearman就是一个分布式计算调度框架或系统,它的主要职责就是做工作分发,所以既然是用来做分布式计算,那自然资源占用会多一些,毕竟不是用来搞小事情的~
正题
如下图,Gearman的工作方式基本可以这样理解,当然既然是计算,那肯定会有结果返回,Client端在任务提交完成之后,那么就可以等待计算结果返回,然后进行汇总。为了简单便于理解,我这里只画出了一个Client,实际生产中Client和Worker肯定都是很多个。

image.png

我们大致了解了Gearman的工作方式,那现在着手编程,试试效果。
环境准备
Linux:Centos 6.10
Gearman:1.1.18
Language:Java
Jar包:gearman-java 0.6
安装好Gearman后,我们用命令启动它,进入Gearman的sbin目录下,启动命令:./gearmand --pid-file=/tmp/gearman.pid --daemon --log-file=/home/log/gearman/gearman.log。(Linux安装源码软件,先configure、再make、最后make install,关于一些安装参数,可以查一下configure的参数,基本上就没啥问题了,缺库的话对应着安装即可)
如下图,服务已经正常启动,默认端口是4730。

image.png

第一个程序

public class EchoApplication {
    private GearmanClient client;

    public EchoApplication() {
        GearmanJobServerConnection connection = new GearmanNIOJobServerConnection(Constant.GEARMAN_HOST, Constant.GEARMAN_PORT); // 创建Gearman服务connection
        client = new GearmanClientImpl();
        client.addJobServer(connection);
    }
    public String echo(String input) throws IOException {
        byte[] data = input.getBytes(); // 发送数据
        byte[] reply = ((GearmanClientImpl) client).echo(data); // 调用Gearman
        return new String(reply, "UTF-8");
    }
    public static void main(String... args) {
        EchoApplication app = new EchoApplication();
        String reply = null;
        try {
            reply = app.echo("hello");
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("reply is ===> " + reply);
    }
}

很简单的一个使用方法,首先在构造函数中初始化连接,然后主函数中调用echo。
如果运行后能够得到如下结果,那么表示Gearman调用成功!

image.png

Client、Worker实现
首先,我们清楚Client的职责,就是做工作拆分(根据实际业务逻辑)以及提交,在Gearman系统中,我们只关注工作提交即可。由于Gearman系统中,不要求Client和Worker的实现语言一直,毕竟分布式计算,所以Client和Worker可以使用不同语言实现,为了方便,我这里统一使用Java。
既然是提交工作,那么定义一个工作是必不可少的部分,如下,我就定义了一个很简答的工作(INFO日志输出内容太多,不方便看结果)。

public class ReadDataJob extends AbstractGearmanFunction {
    private Logger logger = LoggerFactory.getLogger(ReadDataJob.class);

    @Override
    public GearmanJobResult executeFunction() {
        DataInfo data = SerializationUtils.deserialize((byte[]) this.data);
        logger.warn("this thread ID is [{}], i will do [{}]", Thread.currentThread().getId(), data.toString());
        return new GearmanJobResultImpl(this.jobHandle, true, SerializationUtils.serialize(data.toString()), new byte[0], new byte[0], 0, 0);
    }
}

定义工作需要继承Gearman-Java中的AbstractGearmanFunction,实现executeFunction()方法,然后客户端提交的工作的参数,存放在data字段中。我这里就是简单的打印了提交过来的数据,然后作为返回数据返回。
有了工作,我们实现Client,那Client的做提交动作,在实际生产中,肯定是不断会提交工作,所以我们就简单的实现一个循环来模拟。

public class ClientRunner implements Runnable {
    private Logger logger = LoggerFactory.getLogger(ClientRunner.class);

    private GearmanClient client;

    public ClientRunner() {
        GearmanJobServerConnection connection = new GearmanNIOJobServerConnection(Constant.GEARMAN_HOST, Constant.GEARMAN_PORT);
        this.client = new GearmanClientImpl();
        client.addJobServer(connection);
    }
    public void run() {
        this.start();
    }
    private void start() {
        while (true) {
            try {
                String function = ReadDataJob.class.getCanonicalName();
                DataInfo data = new DataInfo(UUID.randomUUID().toString(), "log", "/home/log/");
                // 创建一个工作,包含名字、数据(参数)、工作ID
                GearmanJob job = GearmanJobImpl.createJob(function, SerializationUtils.serialize(data), data.getDataId());
                this.client.submit(job);
                GearmanJobResult result = job.get();
                byte[] resData = result.getResults();
                String res = SerializationUtils.deserialize(resData);
                logger.warn("reply is [{}]", res);
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
}

以上就是一个简单的Client,首先在构造函数中初始化Gearman连接,然后在循环中一直提交工作。GearmanJob需要有functionName(工作名称),数据(参数),以及ID。submit工作之后,GearmanJob对象可以等待其返回,这个实现模式是不是很像线程池提交Task得到Future对象,然后用Future对象等待获取执行结果呢~。我在这里也仅仅是简单打印返回结果。
最后我们要实现Worker了,也就是实际帮我们完成工作的地方。

public class WorkerRunner implements Runnable {
    // 所有定义好的工作
    private List> functions;
    private GearmanWorker worker;

    public WorkerRunner(List> functions) {
        // Gearman connection
        GearmanJobServerConnection connection = new GearmanNIOJobServerConnection(Constant.GEARMAN_HOST, Constant.GEARMAN_PORT);
        // 初始化worker
        this.worker = new GearmanWorkerImpl();
        this.worker.addServer(connection);
        this.functions = new ArrayList>();
        this.functions.addAll(functions);
    }
    public void run() {
        this.start();
    }
    private void start() {
        // 将所有定义好的工作,注册到worker中
        for (Class function : functions) {
            worker.registerFunction(function);
        }
        // 启动worker
        worker.work();
    }
}

以上就是Worker的简单实现,首先在构造函数中初始化好Gearman连接,以及Worker对象,然后我们在start函数中把所有定义的工作都注册到Worker中,这里需要注意,查看Worker的源码可以发现,Worker中是把工作的名字和工作一一对应起来的;最后启动Worker,等待接受工作。
OK,我们实现了所有必要组件,那么试试运行我们的代码。为了方便观察其工作结果,所以我针对Client和Worker分别写了启动类。
Client启动

public class ClientApplication {
    private ExecutorService client = Executors.newSingleThreadExecutor();

    public static void main(String... args) {
        ClientApplication app = new ClientApplication();
        ClientRunner clientRunner = new ClientRunner();
        app.client.submit(clientRunner);
    }
}

Worker启动

public class WorkerApplication {
    private ExecutorService workers = Executors.newFixedThreadPool(2);

    public static void main(String... args) {
        WorkerApplication app = new WorkerApplication();
        List> functions = new ArrayList<>();
        functions.add(ReadDataJob.class);
        WorkerRunner worker1 = new WorkerRunner(functions);
        WorkerRunner worker2 = new WorkerRunner(functions);
        app.workers.submit(worker1);
        app.workers.submit(worker2);
    }
}

针对worker,我启动了两个,我们可以看看是不是随机选取的Worker。
Worker输出

image.png

Client输出

image.png

可见两个Worker都有被分派到工作。
OK,到此我们基本对Gearman的使用入门了,后面如果要继续研究Gearman,可能需要关注它的一些关键参数、特性,以及使用场景,毕竟工具会用和拥得正确、拥得好还是有一定差距的~。
最后再插一句,关于Gearman的数据传输协议,它直接使用了字节流传输,那么业界序列化的框架很多,所以我们在开发的时候,很有必要选择一种通用的(多种语言都支持的),千万不要被数据的序列化方式所局限;所以在生产中,我上面的序列化方式并不可取,毕竟这仅仅适用于Java;然后推荐一下序列化方式,比如protocol buffer,Thrift这些,当然这些使用需要一点点学习成本,如果不愿意耗费,而且系统对于序列化性能要求不高,那么最土的方式,转成Json,然后json字符串序列化成byte数组。

如果有不正确的地方,请帮忙指正,谢谢!
github: https://github.com/zhangveiqiang/gearman-learn

你可能感兴趣的:(Gearman入门及使用(JAVA调用))