[TOC]java
在各行各业中,批量业务处理都是常规需求,很是常见。它的特色是,离线处理、运行时间长、计算密集。传统的解决方式是,或者使用多线程技术,或者使用数据库计算,好比调用数据库的存储过程技术实现等等。 在以Hadoop为首的分布式计算技术出现后,状况有了很大的变化,MapReduce范式为大规模离线数据处理提供了新的思路,性能获得了很大的提高,也提供了很好的线性扩展解决方案。数据库
多线程或者相似于存储过程这样的技术,共有的缺陷是,扩展性差,性能依赖于单一硬件性能,大幅提高性能困难,没法实现分布式计算。 而以Hadoop为首的大数据处理解决方案,近段时间发展迅速,性能指标也在不断地提高,可是设计的目标,或者说适用的场景,主要仍是在互联网的大规模非结构化数据的分析业务上,虽然也能够用于传统的批量业务处理,可是一方面批量业务处理并不须要那么多的功能,杀鸡用了牛刀;另外一方面,这些新一代的计算平台属于异构系统,须要在具体应用以外单独部署,若是要实现高可用,总体的架构也会变得很是复杂,总体的运维成本也会上升,增长了对应的服务器以后,若是计算很少,资源利用率也会降低,采用这样技术的投入产出比,是须要考虑的。apache
Ignite计算网格实现了分布式的闭包和ExecutorService,同时它还提供了一个轻量级的MapReduce(或ForkJoin)实现。 本文重点讲一下轻量级MapReduce,其它的能够参照相关的手册。数组
ComputeTask
接口是Ignite的简化版内存MapReduce的抽象,它也很是接近于ForkJoin范式。这个接口能够对做业到节点的映射作细粒度的控制以及定制故障转移的策略,若是不须要这些,可使用更简单的分布式闭包实现,代码将会更加精炼。缓存
ComputeTask
定义了要在集群内执行的做业以及这些做业到节点的映射,它还定义了如何处理做业的返回值(Reduce)。全部的IgniteCompute.execute(...)
方法都会在集群上执行给定的任务,应用只须要实现ComputeTask
接口的map(...)
和reduce(...)
方法便可,其中:服务器
map(...)
方法负责将做业实例化而后将它们映射到工做节点,这个过程经过ComputeTaskSplitAdapter
,还能够进一步简化;result(...)
方法在每次做业在集群节点上执行时都会被调用,它接收计算做业返回的结果,以及迄今为止收到的做业结果的列表,该方法会返回一个ComputeJobResultPolicy
的实例,说明下一步要作什么;reduce(...)
方法在Reduce阶段被调用。该方法接收到全部计算结果的一个列表而后返回一个最终的计算结果。定义计算时每次都实现ComputeTask
的全部三个方法并非必须的,经过Ignite提供的适配器,能够进一步简化开发,我着重介绍下ComputeTaskSplitAdapter
,它增长了将做业自动分配给节点的功能。它隐藏了map(...)
方法而后增长了一个新的split(...)
方法,使得开发者只须要提供一个待执行的做业集合便可,这很是适用于批量业务处理。这个适配器对于全部节点都适于执行做业的同质化环境是很是有用的,这样的话映射阶段就能够隐式地完成。多线程
任务触发的全部做业都要实现ComputeJob
接口,这个接口的execute()
方法定义了做业的逻辑而后返回一个做业的结果。闭包
下面这段代码,做为一个简单示例,显示了如何计算一段话中的字母的总数量:架构
IgniteCompute compute = ignite.compute(); // 在集群上执行任务。 int cnt = grid.compute().execute(CharacterCountTask.class, "Hello Grid Enabled World!"); private static class CharacterCountTask extends ComputeTaskSplitAdapter<String, Integer> { // 1. 将收到的字符串拆分为字符串数组 // 2. 为每一个单词建立一个做业 // 3. 将每一个做业发送给工做节点进行处理 @Override public List<ClusterNode> split(List<ClusterNode> subgrid, String arg) { String[] words = arg.split(" "); List<ComputeJob> jobs = new ArrayList<>(words.length); for (final String word : arg.split(" ")) { jobs.add(new ComputeJobAdapter() { @Override public Object execute() { return word.length(); } }); } return jobs; } @Override public Integer reduce(List<ComputeJobResult> results) { int sum = 0; for (ComputeJobResult res : results) sum += res.<Integer>getData(); return sum; } }
是否是很是简单?运维
Ignite支持做业的自动故障转移,当一个节点故障时,做业会被转移到其它可用节点再次执行。故障转移是经过FailoverSpi
实现的,FailoverSpi
负责选择一个新的节点来执行失败的做业。它会检查发生故障的做业以及该做业能够尝试执行的全部可用的网格节点的列表。它会确保该做业不会再次映射到出现故障的同一个节点。故障转移是在ComputeTask.result(...)
方法返回ComputeJobResultPolicy.FAILOVER
策略时触发的。Ignite内置了一些故障转移SPI的实现,开发者也能够进行定制。另外,Ignite保证,只要有一个节点是有效的,做业就不会丢失。
Ignite中的负载平衡是经过LoadBalancingSpi
实现的。它控制全部节点的负载以及确保集群中的每一个节点负载水平均衡。对于同质化环境中的同质化的任务,负载平衡采用的是随机或者循环的策略。然而在不少其它场景中,特别是在一些不均匀的负载下,就须要更复杂的自适应负载平衡策略。Ignite内置了若干中负载平衡实现,好比循环式负载平衡RoundRobinLoadBalancingSpi
以及随机或者加权负载平衡WeightedRandomLoadBalancingSpi
,这部分开发者也能够定制开发,知足个性化需求。
Ignite中,做业是在客户端侧的任务拆分初始化或者闭包执行阶段被映射到集群节点上的,可是一旦做业到达被分配的节点,就会有序地执行。默认状况下,做业会被提交到一个线程池而后随机地执行,若是要对做业执行顺序进行细粒度控制的话,须要启用CollisionSpi
,好比,能够按照FIFO排序或者按照优先级排序。
在企业级批量业务处理中,一般要对数据库进行频繁的更新操做,在分布式计算环境下,将整个任务配置为一个事务显然是不合适的。最佳实践是将每一个做业配置成一个事务,这样若是某个做业失败,只是该做业回滚,其它成功的做业仍是正常提交的,而后故障转移机制会使该失败的做业再次执行,直到成功提交。
Ignite的内存MapReduce实现还支持会话,这个机制能够在任务和做业之间共享一些数据,还支持节点局部状态共享,这个实际上是节点的局部变量,它能够用于任务在不一样的执行过程当中共享状态。还有,经过计算和缓存数据的并置,能够极大地提升性能,它还支持检查点,能够在一个长时间执行的做业中保存一些中间状态,这个机制在重启一个故障节点后,做业能够从保存的检查点载入而后从故障处继续执行。等等,在这里就不一一介绍了。
在以前的关于Ignite的集群部署的文章中我对Ignite的集群特性作了简要的介绍,该文中推荐了一种混合式的集群部署方案,以下图:
在这个架构中,若是可以在应用集群组中进行分布式计算来实现批量业务处理,那么这会是一个很优雅的解决方案,幸运的是,Ignite真的实现了,这个解决方案总体上来说,具备以下的优点: