Ignite计算网格第二部分

8.负载平衡

8.1.概述

Ignite中负载平衡是通过LoadBalancingSpi实现的,它控制所有节点的负载以及确保集群中的每个节点负载水平均衡。对于同质化环境中的同质化任务,负载平衡采用的是随机或者轮询的策略。不过在很多其它场景中,特别是在一些不均匀的负载下,就需要更复杂的自适应负载平衡策略。

LoadBalancingSpi采用“前”负载技术,即在将其发送到集群之前就对作业在某个节点的执行进行了调度。

数据并置

注意,当作业还没有与数据并置或者还没有在哪个节点上执行的倾向时,负载平衡就已经触发了。如果使用了计算和数据的并置,那么数据的并置优先于负载平衡。

 

8.2.轮询式负载平衡

RoundRobinLoadBalancingSpi以轮询的方式在节点间迭代,然后选择下一个连续的节点。轮询式负载平衡支持两种操作模式:每任务以及全局,全局模式为默认模式。

每任务模式

如果配置成每任务模式,当任务开始执行时实现会随机地选择一个节点,然后会顺序地迭代拓扑中所有的节点,对于任务拆分的大小等同于节点的数量时,这个模式保证所有的节点都会参与任务的执行。

全局模式

如果配置成全局模式,对于所有的任务都会维护一个节点的单一连续队列然后每次都会从队列中选择一个节点。这个模式中(不像每任务模式),当多个任务并发执行时,即使任务的拆分大小等同于节点的数量,同一个任务的某些作业仍然可能被赋予同一个节点。

RoundRobinLoadBalancingSpi spi = new RoundRobinLoadBalancingSpi();

// Configure SPI to use per-task mode (this is default behavior).
spi.setPerTask(true);

IgniteConfiguration cfg = new IgniteConfiguration();

// Override default load balancing SPI.
cfg.setLoadBalancingSpi(spi);

// Start Ignite node.
Ignition.start(cfg);

8.3.随机和加权负载平衡

WeightedRandomLoadBalancingSpi会为作业的执行随机选择一个节点。也可以选择为节点赋予权值,这样有更高权重的节点最终会使将作业分配给它的机会更多。所有节点的权重默认值都是10。

WeightedRandomLoadBalancingSpi spi= new WeightedRandomLoadBalancingSpi();

// Configure SPI to used weighted random load balancing.
spi.setUseWeights(true);

// Set weight for the local node.
spi.setNodeWeight(10);

IgniteConfiguration cfg = new IgniteConfiguration();

// Override default load balancing SPI.
cfg.setLoadBalancingSpi(spi);

// Start Ignite node.
Ignition.start(cfg);

8.4.磨洋工(与节点运行效率相关的负载)

通常集群由很多计算机组成,这就可能存在配置不均衡的情况,这时开启JobStealingCollisionSpi就会有助于避免作业聚集在过载的节点,或者远离低利用率的节点。

JobStealingCollisionSpi支持作业从高负载节点到低负载节点的移动,当部分作业完成得很快,而其它的作业还在高负载节点中排队时,这个SPI就会非常有用。这种情况下,等待作业就会被移动到低负载的节点。

JobStealingCollisionSpi采用的是后负载技术,它可以在任务已经被调度在节点A执行后重新分配到节点B。

下面是配置JobStealingCollisionSpi的示例:

JobStealingCollisionSpi spi = new JobStealingCollisionSpi();

 // Configure number of waiting jobs
 // in the queue for job stealing.
 spi.setWaitJobsThreshold(10);

 // Configure message expire time (in milliseconds).
 spi.setMessageExpireTime(1000);

 // Configure stealing attempts number.
 spi.setMaximumStealingAttempts(10);

 // Configure number of active jobs that are allowed to execute
 // in parallel. This number should usually be equal to the number
 // of threads in the pool (default is 100).
 spi.setActiveJobsThreshold(50);

 // Enable stealing.
 spi.setStealingEnabled(true);

 // Set stealing attribute to steal from/to nodes that have it.
 spi.setStealingAttributes(Collections.singletonMap("node.segment", "foobar"));

 // Enable `JobStealingFailoverSpi`
 JobStealingFailoverSpi failoverSpi = new JobStealingFailoverSpi();

 IgniteConfiguration cfg = new IgniteConfiguration();

 // Override default Collision SPI.
 cfg.setCollisionSpi(spi);

 cfg.setFailoverSpi(failoverSpi);

必要的配置

注意org.apache.ignite.spi.failover.jobstealing.JobStealingFailoverSpiIgniteConfiguration.getMetricsUpdateFrequency()都要开启,这样这个SPI才能正常工作,JobStealingCollisionSpi的其它配置参数都是可选的。

9.检查点

#9.1.概述

检查点提供了保存一个作业中间状态的能力,它有助于一个长期运行的作业保存一些中间状态以防节点故障。重启一个故障节点后,一个作业会从保存的检查点载入然后从故障处继续执行。要开启作业检查点状态,实现java.io.Serializable接口即可。

检查点功能可以通过GridTaskSession接口的如下方法启用:

  • ComputeTaskSession.loadCheckpoint(String)
  • ComputeTaskSession.removeCheckpoint(String)
  • ComputeTaskSession.saveCheckpoint(String, Object)

@ComputeTaskSessionFullSupport注解

注意检查点因为性能的原因默认是禁用的,要启用它需要在任务或者闭包类上加注@ComputeTaskSessionFullSupport注解。

9.2.主节点故障保护

检查点的一个重要使用场景是避免“主”节点(发起任务的节点)的故障。当主节点故障时,Ignite不知道将作业的执行结果发送给谁,这样结果就会被丢弃。

这种情况下要恢复,可以先将作业的最终执行结果保存为一个检查点然后在”主”节点故障时有一个逻辑来重新运行整个任务。这时任务的重新运行会非常快因为所有的作业都可以从已保存的检查点启动。

9.3.设置检查点

每个计算任务都可以通过调用ComputeTaskSession.saveCheckpoint(...)方法定期地保存检查点。

如果作业确实保存了检查点,那么当它开始执行时,应该检查检查点是否可用然后从最后保存的检查点处开始执行。

IgniteCompute compute = ignite.compute();

compute.run(new CheckpointsRunnable());

/**
 * Note that this class is annotated with @ComputeTaskSessionFullSupport
 * annotation to enable checkpointing.
    启用分布式任务会话
 */
@ComputeTaskSessionFullSupport
private static class CheckpointsRunnable implements IgniteCallable {
  // Task session (injected on closure instantiation).
  //分布式任务会话
  @TaskSessionResource
  private ComputeTaskSession ses;

  @Override
  public Object call() throws GridException {
    // Try to retrieve step1 result.
    Object res1 = ses.loadCheckpoint("STEP1");

    if (res1 == null) {
      res1 = computeStep1(); // Do some computation.

      // Save step1 result.
      ses.saveCheckpoint("STEP1", res1);
    }

    // Try to retrieve step2 result.
    Object res2 = ses.loadCheckpoint("STEP2");

    if (res2 == null) {
      res2 = computeStep2(res1); // Do some computation.

      // Save step2 result.
      ses.saveCheckpoint("STEP2", res2);
    }

    ...
  }
}

9.5.文件系统检查点配置

下面的配置参数可用于配置SharedFsCheckpointSpi:

settter方法 描述 默认值
setDirectoryPaths(Collection) 设置检查点要保存的共享文件夹的目录路径。这个路径既可以是绝对的也可以是相对于IGNITE_HOME环境变量或者系统参数指定的路径 IGNITE_HOME/work/cp/sharedfs
IgniteConfiguration cfg = new IgniteConfiguration();

SharedFsCheckpointSpi checkpointSpi = new SharedFsCheckpointSpi();

// List of checkpoint directories where all files are stored.
Collection dirPaths = new ArrayList();

dirPaths.add("/my/directory/path");
dirPaths.add("/other/directory/path");

// Override default directory path.
checkpointSpi.setDirectoryPaths(dirPaths);

// Override default checkpoint SPI.
cfg.setCheckpointSpi(checkpointSpi);

// Starts Ignite node.
Ignition.start(cfg);

9.6.缓存检查点配置

CacheCheckpointSpi对于检查点SPI来说是一个基于缓存的实现,检查点数据会存储于Ignite数据网格中的一个预定义缓存中。

下面的配置参数可用于配置CacheCheckpointSpi:

setter方法 描述 默认值
setCacheName(String) 设置用于保存检查点的缓存名 checkpoints

 

9.7.数据库检查点配置

JdbcCheckpointSpi通过数据库来保存检查点。所有的检查点都会保存在数据库表中然后对于集群中的所有节点都是可用的。注意每个节点必须访问数据库。一个作业状态可以在一个节点保存然后在另一个节点载入(例如一个作业在节点故障后被另一个节点取代)。

下面的配置参数可用于配置JdbcCheckpointSpi,(所有的都是可选的)

setter方法 描述 默认值
setDataSource(DataSource) 设置用于数据库访问的数据源
setCheckpointTableName(String) 设置检查点表名 CHECKPOINTS
setKeyFieldName(String) 设置检查点键字段名 NAME
setKeyFieldType(String) 设置检查点键字段类型,字段应该有相应的SQL字符串类型(比如VARCHAR VARCHAR(256)
setValueFieldName(String) 设置检查点值字段名 VALUE
setValueFieldType(String) 设置检查点值字段类型,注意,字段需要有相应的SQL BLOB类型,默认值是BLOB,但不是所有数据库都兼容,比如,如果使用HSQL DB,那么类型应该为longvarbinary BLOB
setExpireDateFieldName(String) 设置检查点过期时间字段名 EXPIRE_DATE
setExpireDateFieldType(String) 设置检查点过期时间字段类型,字段应该有相应的DATETIME类型 DATETIME
setNumberOfRetries(int) 任何数据库错误时的重试次数 2
setUser(String) 设置检查点数据库用户名,注意只有同时设置了用户名和密码时,认证才会执行
setPwd(String) 设置检查点数据库密码

 

9.8.Amazon S3 检查点配置

S3CheckpointSpi使用S3存储来保存检查点,但是前提是要有一个S3 bucket(桶),具体请参见http://aws.amazon.com/。

下面的参数可用于配置S3CheckpointSpi:

setter方法 描述 默认值
setAwsCredentials(AWSCredentials) 设置要使用的AWS凭证来保存检查点 无,但必须提供
setClientConfiguration(Client) 设置AWS客户端配置
setBucketNameSuffix(String) 设置bucket名字后缀 default-bucket

10.作业调度

#10.1.概述

Ignite中,作业是在客户端侧的任务拆分初始化或者闭包执行阶段被映射到集群节点上的。不过一旦作业到达被分配的节点,就需要有序地执行。作业默认是被提交到一个线程池然后随机地执行,如果要对作业执行顺序进行细粒度控制,需要启用CollisionSpi

#10.2.FIFO排序

FifoQueueCollisionSpi可以使一定数量的作业无中断地以先入先出的顺序执行,所有其它的作业都会被放入一个等待列表,直到轮到它。

并行作业的数量是由parallelJobsNumber配置参数控制的,默认值为2.

一次一个

注意如果将parallelJobsNumber设置为1,可以保证所有作业同时只会执行一个,这样就没有任何两个作业并发执行。

FifoQueueCollisionSpi colSpi = new FifoQueueCollisionSpi();

// Execute jobs sequentially, one at a time,
// by setting parallel job number to 1.
colSpi.setParallelJobsNumber(1);

IgniteConfiguration cfg = new IgniteConfiguration();

// Override default collision SPI.
cfg.setCollisionSpi(colSpi);

// Start Ignite node.
Ignition.start(cfg);

10.3.优先级排序

PriorityQueueCollisionSpi可以为每个作业设置一个优先级,因此高优先级的作业会比低优先级的作业先执行。

任务优先级

任务优先级是通过任务会话中的grid.task.priority属性设置的,如果任务没有被赋予优先级,那么会使用默认值0。

下面是一个如何设置任务优先级的示例:

public class MyUrgentTask extends ComputeTaskSplitAdapter {
  // Auto-injected task session.
  @TaskSessionResource
  private ComputeTaskSession taskSes = null;

  @Override
  protected Collection split(int gridSize, Object arg) {
    ...
    // Set high task priority.
    taskSes.setAttribute("grid.task.priority", 10);

    List jobs = new ArrayList<>(gridSize);

    for (int i = 1; i <= gridSize; i++) {
      jobs.add(new ComputeJobAdapter() {
        ...
      });
    }
    ...

    // These jobs will be executed with higher priority.
    return jobs;
  }
}

配置

和FIFO排序一样,并行执行作业的数量是由parallelJobsNumber配置参数控制的。

PriorityQueueCollisionSpi colSpi = new PriorityQueueCollisionSpi();

// Change the parallel job number if needed.
// Default is number of cores times 2.
colSpi.setParallelJobsNumber(5);

IgniteConfiguration cfg = new IgniteConfiguration();

// Override default collision SPI.
cfg.setCollisionSpi(colSpi);

// Start Ignite node.
Ignition.start(cfg);

11.任务部署

除了对等类加载之外,Ignite还有一个部署机制,它负责在运行时从不同的源中部署任务和类。

#11.1.DeploymentSpi

部署的功能是通过DeploymentSpi接口提供的。

类加载器负责加载任务类(或者其它的类),它可以通过调用register(ClassLoader, Class)方法或者SPI自己来直接部署。比如通过异步地扫描一些文件夹来加载新的任务。当系统调用了findResource(String)方法时,SPI必须返回一个与给定的类相对应的类加载器。每次一个类加载器获得(再次)部署或者释放,SPI必须调用DeploymentListener.onUnregistered(ClassLoader)方法。

如果启用了对等类加载,因为通常只会在一个网格节点上部署类加载器,一旦一个任务在集群上开始执行,所有其它的节点都会从发起任务的节点自动加载所有的任务类。对等类加载也支持热部署。每次任务发生变化或者在一个节点上重新部署,所有其它的节点都会侦测到然后重新部署这个任务。注意对等类加载只在任务为非本地部署时才生效,否则本地部署总是具有优先权。

Ignite直接支持如下的DeploymentSpi实现:

  • UriDeploymentSpi
  • LocalDeploymentSpi

注意

SPI的方法不要直接使用。SPI提供了子系统的内部视图,由Ignite内部使用,需要访问这个SPI的某个实现的场景很少。可以通过Ignite.configuration()方法来获得这个SPI的一个实例,来检查它的配置属性或者调用其它的非SPI方法。再次注意从获得的实例中调用该接口的方法可能导致无法预期的行为,因此Ignite明确不会为此提供支持。

#11.2.UriDeploymentSpi

这是DeploymentSpi的一个实现,它可以从不同的资源部署任务,比如文件系统文件夹、email和FTP。在集群中有不同的方式来部署任务以及每个部署方法都会依赖于所选的源协议。这个SPI可以配置与一系列的URI一起工作,每个URI都包括有关协议/传输加上配置参数比如凭证、扫描频率以及其它的所有数据。

当SPI建立一个与URI的连接时,为了防止扫描过程中的任何变化它会下载待部署的单元到一个临时目录,通过方法setTemporaryDirectoryPath(String)可以为下载的部署单元设置自定义的临时文件夹。SPI会在该路径下创建一个和本地节点ID名字同名的文件夹。

SPI会跟踪每个给定URI的所有变化。这意味着如果任何文件发生变化或者被删除,SPI会重新部署或者删除相应的任务。注意第一个调用findResource(String)是阻塞的,直到SPI至少一次完成了对所有URI的扫描。

下面是一些支持的可部署单元的类型:

  • GAR文件
  • 带有未解压GAR文件结构的本地磁盘文件夹
  • 只包含已编译的Java类的本地磁盘文件夹

GAR文件

GAR文件是一个可部署的单元,GAR文件基于ZLIB压缩格式,类似简单JAR文件,它的结构类似于WAR包,GAR文件使用.gar扩展名。

GAR文件结构(以.gar结尾的文件或者目录):

META-INF/
        |
         - ignite.xml
         - ...
lib/
   |
    -some-lib.jar
    - ...
xyz.class
...
  • META-INF:包含任务描述的ignite.xml文件的入口,任务描述XML格式文件的作用是指定所有要部署的任务。这个文件是标准的Spring格式XML定义文件,META-INF/也可以包含其它所有的JAR格式指定的文件。
  • lib/:包含所有库依赖的入口。
  • 编译过的java类必须放在GAR文件的根目录中。

当然没有描述文件GAR文件也可以部署。如果没有描述文件,SPI会扫描包里的所有类然后实例化其中实现ComputeTask接口的,不过这样所有的任务类必须有一个公开的无参数的构造函数。为了方便,创建任务时也可以使用ComputeTaskAdapter适配器。

所有下载的GAR文件在META-INF文件夹都要有默认待认证的数字签名,然后只有在签名有效时才会被部署。

示例

下面的实例演示了可用的SPI是如何部署的,不同的协议也可以一起使用。

File协议:

// The example expects that you have a GAR file in
// `home/username/ignite/work/my_deployment/file` folder
// which contains `myproject.HelloWorldTask` class.

IgniteConfiguration cfg = new IgniteConfiguration();

UriDeploymentSpi deploymentSpi = new UriDeploymentSpi();

deploymentSpi.setUriList(Arrays.asList("file:///home/username/ignite/work/my_deployment/file"));

cfg.setDeploymentSpi(deploymentSpi);

try(Ignite ignite = Ignition.start(cfg)) {
    ignite.compute().execute("myproject.HelloWorldTask", "my args");
}

HTTP协议:

// The example expects that you have a HTMP under
// 'www.mysite.com:110/ignite/deployment'page which contains a link
// on GAR file which contains `myproject.HelloWorldTask` class.

IgniteConfiguration cfg = new IgniteConfiguration();

UriDeploymentSpi deploymentSpi = new UriDeploymentSpi();

deploymentSpi.setUriList(Arrays.asList("http://username:password;[email protected]:110/ignite/deployment"));

cfg.setDeploymentSpi(deploymentSpi);

try(Ignite ignite = Ignition.start(cfg)) {
    ignite.compute().execute("myproject.HelloWorldTask", "my args");
}

XML配置


  ...
  
    
     
      
        
          http://www.site.com/tasks
          file://freq=20000@localhost/c:/Program files/gg-deployment
        
      
    
  

配置

属性 描述 可选 默认值
uriList SPI扫描新任务的URI列表 file://${IGNITE_HOME}/work/deployment/file,注意如果要使用默认文件夹,IGNITE_HOME必须设置。
scanners 用于部署资源的UriDeploymentScanner实现的数组 UriDeploymentFileScannerUriDeploymentHttpScanner
temporaryDirectoryPath 要扫描的GAR文件和目录要拷贝的目标临时目录路径 java.io.tmpdir系统属性值
encodeUri 控制URI中路径部分编码的标志 true

协议

SPI直接支持如下协议:

  • file:// - File协议
  • http:// - HTTP协议
  • https:// - 安全HTTP协议

自定义协议

如果需要可以增加其它的协议的支持,可以通过实现UriDeploymentScanner接口然后通过setScanners(UriDeploymentScanner... scanners)方法将实现加入SPI来做到这一点。

除了SPI配置参数之外,对于选择的URI所有必要的配置参数都应该在URI中定义。不同的协议有不同的配置参数,下面分别做了描述,参数是以分号分割的。

File

对于这个协议,SPI会在文件系统中扫描URI指定的文件夹然后从URI定义的源文件夹中下载任何的GAR文件或者目录中的以.gar结尾的文件。

支持如下的参数:

参数 描述 可选 默认值
freq 扫描频率,以毫秒为单位 5000ms

File URI示例

下面的示例会在本地每2000毫秒扫描c:/Program files/ignite/deployment文件夹。注意如果path有空格,setEncodeUri(boolean)参数必须设置为true(这也是默认的行为)。

file://freq=2000@localhost/c:/Program files/ignite/deployment

HTTP/HTTPS

URI部署扫描器会试图读取指向的HTML文件的DOM然后解析出所有的标签的href属性 - 这会成为要部署的URL集合(到GAR文件):每个'A'链接都应该是指向GAR文件的URL。很重要的是只有HTTP扫描器会使用URLConnection.getLastModified()方法来检查自从重新部署之前对每个GAR文件的最后一次迭代以来是否有任何变化。

支持如下的参数:

参数 描述 可选 默认值
freq 扫描频率,以毫秒为单位 300000ms

HTTP URI示例

下面的示例会下载www.mysite.com/ignite/deployment页面,解析它然后每10000毫秒使用username:password进行认证,对从页面的所有a元素的href属性指定的所有GAR文件进行下载和部署。

http://username:password;[email protected]:110/ignite/deployment

11.3.LocalDeploymentSpi

本地部署SPI只是通过register(ClassLoader, Class)方法实现了在本地节点中的虚拟机进行部署,这个SPI不需要配置。 显式地配置LocalDeploymentSpi是没有意义的,因为它是默认的以及没有配置参数。

12.基于Cron的调度

12.1.概述

RunnableCallable的实例可以使用IgniteScheduler.scheduleLocal()方法和Cron语法,在本地节点进行调度用于周期性的执行。

注意

要使用相关的调度功能,项目中需要引入ignite-schedule模块。

下面的示例会在所有的可用节点上触发周期性的发送缓存指标报告。

ignite.compute().broadcast(new IgniteCallable() {
    @IgniteInstanceResource
    Ignite ignite;

    @Override
    public Object call() throws Exception {
        ignite.scheduler().scheduleLocal(new Runnable() {
            @Override public void run() {
                sendReportWithCacheMetrics(cache.metrics());
            }
        }, "0 0   *");
        return null;
    }
});

Cron简称

在当前的实现中,Cron简称(@hourly, @daily, @weekly...)还不支持,并且最小的调度时间单元是一分钟。

12.2.SchedulerFuture

IgniteScheduler.scheduleLocal()方法返回SchedulerFuture,它有一组有用的方法来监控调度的执行以及获得结果,通过它也可以取消周期性的执行。

SchedulerFuture fut = ignite.scheduler().scheduleLocal(new Runnable() {
    @Override public void run() {
        ...
    }
}, "0 0 * * *");

System.out.println("The task will be next executed on " + new Date(fut.nextExecutionTime()));

fut.get(); // Wait for next execution to finish.

fut.cancel(); // Cancel periodic execution.

12.3.语法扩展

Ignite引入了一个Cron语法的扩展,它可以指定一个初始化的延迟(秒)和运行的次数。这两个可选的数值用大括号括起来,用逗号分割,放在Cron规范之前。下面的示例中指定了每分钟执行五次,并且有一个初始2秒钟的延迟。

{2, 5} * * * * *

13.持续映射

13.1.概述

传统的MapReduce范式中,有一个明确且有限的作业集,在Map阶段会处理它们并且整个计算运行期间都不会改变。但是如果有一个作业流会怎么样呢?这时仍然可以使用Ignite的持续映射能力执行MapReduce。通过持续映射,当计算仍然在运行时作业可以动态地生成,新生成的作业同样会被工作节点处理,并且Reducer和通常的MapReduce一样会收到结果。

13.2.运行持续的映射任务

如果在任务中要使用持续映射,需要在一个任务实例中注入TaskContinuousMapperResource资源:

@TaskContinuousMapperResource
private TaskContinuousMapper mapper;

之后,新的作业可以异步地生成,并且使用TaskContinuousMapper实例的send()方法加入当前正在运行的计算中:

mapper.send(new ComputeJobAdapter() {
    @Override public Object execute() {
        System.out.println("I'm a continuously-mapped job!");

        return null;
    }
});

对于持续映射,有几个约束需要了解:

  • 如果最初的ComputeTask.map()方法返回null,那么在返回之前通过持续映射器要至少发送一个作业;
  • ComputeTask.result()方法返回REDUCE策略之后持续映射器无法使用;
  • 如果ComputeTask.result()方法返回WAIT策略,并且所有的作业都已经结束,那么任务会进入REDUCE阶段并且持续映射器也不再使用。

在其它方面,计算逻辑和正常的MapReduce一样,详情可以参照14.2章节。

13.3.示例

下面的示例是基于网站包含的图片对其进行分析。Web搜索引擎会扫描站点上的所有图片,然后Ignite实现的MapReduce引擎会基于一些图片分析算法确定每个图片的种类(“含义”)。然后图片分析的结果会归约确定站点的种类,这个示例会很好地演示持续映射,因为Web搜索是一个持续的过程,其中MapReduce可以并行地执行,并且分析到来的新的Web搜索结果(新的图片文件)。这会节省大量的时间,而传统的MapReduce,首先需要等待所有的Web搜索结果(一个完整的图片集),然后将它们映射到节点,分析,归约结果。

假定有一个来自一些扫描站点图片的库的Crawler接口,CrawlerListener接口用于获得异步的后台扫描结果,然后一个Image接口表示一个单个图片文件(类名故意缩短以简化阅读):

 

interface Crawler {
    ...
    public Image findNext();

    public void findNextAsync(CrawlerListener listener);

    ...
}

interface CrawlerListener {
    public void onImage(Crawler c, Image img) throws Exception;
}

interface Image {
    ...

    public byte[] getBytes();

    ...
}

假定还有一个实现了ComputeJob的ImageAnalysisJob,它的逻辑是分析图片:

class ImageAnalysisJob implements ComputeJob, Externalizable {
    ...

    public ImageAnalysisJob(byte[] imgBytes) {
        ...
    }

    @Nullable @Override public Object execute() throws IgniteException {
        // Image analysis logic (returns some information
        // about the image content: category, etc.).
        ...
    }
}

上面的都准备好后,下面是如何运行Web搜索然后使用持续映射进行并行地分析:

enum SiteCategory {
    ...
}

// Instantiate a Web search engine for a particular site.
Crawler crawler = CrawlerFactory.newCrawler(siteUrl);

// Execute a continuously-mapped task.
SiteCategory result = ignite.compute().execute(new ComputeTaskAdapter() {
    // Interface for continuous mapping (injected on task instantiation).
    @TaskContinuousMapperResource
    private TaskContinuousMapper mapper;

    // Map step.
    @Nullable @Override
    public Map map(List nodes, @Nullable Crawler c) throws IgniteException {
        assert c != null;

        // Find a first image synchronously to submit an initial job.
        Image img = c.findNext();

        if (img == null)
            throw new IgniteException("No images found on the site.");

        // Submit an initial job.
        mapper.send(new ImageAnalysisJob(img.getBytes()));

        // Now start asynchronous background Web search and
        // submit new jobs as search results come. This call
        // will return immediately.
        c.findNextAsync(new CrawlerListener() {
            @Override public void onImage(Crawler c, Image img) throws Exception {
                if (img != null) {
                    // Submit a new job to analyse image file.
                    mapper.send(new ImageAnalysisJob(img.getBytes()));

                    // Move on with search.
                    c.findNextAsync(this);
                }
            }
        });

        // Initial job was submitted, so we can return
        // empty mapping.
        return null;
    }

    // Reduce step.
    @Nullable @Override public SiteCategory reduce(List results) throws IgniteException {
        // At this point Web search is finished and all image
        // files are analysed. Here we execute some logic for
        // determining site category based on image content
        // information.
        return defineSiteCategory(results);
    }
}, crawler);

 

在上面这个示例中,为了简化,假定图片分析作业会比在站点上搜索下一张图片花费更长的时间。换句话说,新的Web搜索结果的到来会快于分析完图片文件。在现实生活中,这不一定是真的,并且可以轻易地获得一个情况,就是过早地完成了分析,因为当前所有的作业都已经完成而新的搜索结果还没有到达(由于网络延迟或者其它的原因)。这时,Ignite会切换至归约阶段,因此持续映射就不再可能了。

要避免这样的情况,需要考虑提交一个特别的作业,它只是完成接收一些消息,或者使用可用的分布式同步化基本类型之一,比如CountDownLatch,这个做法可以确保在Web搜索结束之前归约阶段不会启动。

14.启用基于AOP的网格

通过面向切面编程,可以隐式地修改任何方法的行为(比如,记录日志或者开启一个方法级的事务等)。在Ignite中,可以将方法加入运行在网格中的一个闭包,这和在一个方法上加注@Gridify注解一样简单。

@Gridify
public void sayHello() {
    System.out.println("Hello!");
}

可以使用下面的AOP库:AspectJJBoss或者Spring AOP,只需要在主节点(发起执行的节点)上正确配置这些框架中的任何一个。仅仅上述方法的一个调用就会产生集群范围的一次任务执行,它会在拓扑内的所有工作节点上输出"Hello!"。

14.1.Gridify注解

@Gridify注解可以用于任何方法,它可以有如下的参数,所有参数都是可选的:

属性名称 类型 描述 默认值
taskClass Class 自定义任务类 GridifyDefaultTask.class
taskName String 要启动的自定义任务名 “”
timeout long 任务执行超时时间,0为没有超时限制 0
interceptor Class 过滤网格化方法的拦截器 GridifyInterceptor.class
gridName String 要使用的网格名 “”

通常,如果调用了一个网格化的方法,会发生如下事情:

  • 通过gridName指定的网格会用于执行(如果未指定网格名,默认会使用没有名字的网格);
  • 如果指定了,会使用一个拦截器检查方法是否应该启用网格化,如果拦截器返回false,一个方法会像正常的一样被调用,而不会被网格化;
  • 通过有效的方法参数,this对象(如果方法为非静态),会创建并且执行一个网格任务;
  • 这个网格任务的返回值会通过网格化的方法返回。

默认的行为

如果使用了没有参数的@Gridify注解,会隐式地使用如下的默认的行为:

  • 会创建一个名为GridifyDefaultTask的任务类,它会生成一个GridifyJobAdapter作业类,然后使用一个默认的负载平衡器选择一个工作节点;
  • 远程节点的一个作业会通过内置的参数调用一个方法,使用反序列化的这个对象(如果方法为静态则为null),然后返回这个方法的结果作为作业的结果;
  • 远程节点上作业的结果会作为调用端任务的结果;
  • 任务的结果会返回,作为网格化方法的返回值。

自定义任务

对于一个网格化的方法也可以自定义任务,用于特定的网格化逻辑。下面的示例会广播网格化的方法到所有有效的工作节点上,然后忽略reduce阶段(意味着这个任务没有返回值):

// Custom task class.
class GridifyBroadcastMethodTask extends GridifyTaskAdapter {
    @Nullable @Override
    public Map map(List subgrid, @Nullable GridifyArgument arg) throws IgniteException {
        Map ret = new HashMap<>(subgrid.size());

        // Broadcast method to all nodes.
        for (ClusterNode node : subgrid)
            ret.put(new GridifyJobAdapter(arg), node);

        return ret;
    }

    @Nullable @Override
    public Void reduce(List list) throws IgniteException {
        return null; // No-op.
    }
}

public class GridifyHelloWorldTaskExample {
  ...
  // Gridified method.
  @Gridify(taskClass = GridifyBroadcastMethodTask.class)
  public static void sayHello(String arg) {
      System.out.println("Hello, " + arg + '!');
  }
  ...
}

14.2.配置AOP

Ignite支持三个AOP框架:

  • AspectJ
  • JBoss AOP
  • Spring AOP

这个章节会描述每个框架的详细配置,假定IGNITE_HOME是Ignite的安装目录。

这些细节只适用于逻辑主节点(初始化该次执行的节点),剩下的都应该是逻辑工作节点。

AspectJ

要启用AspectJ字节码织入,主节点的JVM需要通过如下方式进行配置:

  • 启动时需要添加-javaagent:IGNITE_HOME/libs/aspectjweaver-1.8.9.jar参数;
  • 类路径需要包含IGNITE_HOME/config/aop/aspectj文件夹。

JBoss AOP

要启用JBoss的字节码织入,主节点的JVM需要有如下的配置:

  • 启动时需要有如下参数:
  • -javaagent:[指向jboss-aop-jdk50-4.x.x.jar的路径]
  • -Djboss.aop.class.path=[ignite.jar的路径]
  • -Djboss.aop.exclude=org,com
  • -Djboss.aop.include=[应用的包名]
  • 还要在类路径中包含如下的jar文件:
  • javassist-3.x.x.jar
  • jboss-aop-jdk50-4.x.x.jar
  • jboss-aspect-library-jdk50-4.x.x.jar
  • jboss-common-4.x.x.jar
  • trove-1.0.2.jar

JBoss依赖

Ignite不带有JBoss,因此必要的库需要单独下载(如果已经安装了JBoss,这些都是标准的)。

Spring AOP

Spring AOP框架基于动态代理实现,对于在线织入不需要任何特定的运行时参数,所有的织入都是按需的,对于有加注了@Gridify注解的方法的对象会通过调用GridifySpringEnhancer.enhance()方法执行。

注意这个织入的方法是非常不方便的,推荐使用AspectJ或者JBoss AOP代替。当代码增强是不想要的并且无法使用时,Spring AOP适用于这样的场景,它也可以用于对织入什么进行细粒度的控制。

 

 

 

 

你可能感兴趣的:(Ignite学习)