R - doRedis - 覆盖getTask以控制并行foreach循环中的执行顺序

时间:2017-01-16 09:20:47

标签: r foreach parallel-processing r-doredis

问题:我需要控制foreach循环并行处理任务的执行顺序。不幸的是,foreach不支持这一点。

解决方案:使用doRedis使用数据库来保存在foreach循环中执行的所有任务。要控制顺序,我想通过setGetTask覆盖getTask,以根据预先指定的顺序获取任务。虽然我找不到很多关于如何做到这一点的文档。

其他信息:

  1. setGetTask上有一个小段落,redis documentation中有一个例子。

    getTask <- function ( queue , job_id , ...)
    {
    
      key <- sprintf("
      redisEval("local x=redis.call('hkeys',KEYS[1])[1];
                   if x==nil then return nil end;
                   local ans=redis.call('hget',KEYS[1],x);
                   redis.call('hdel',KEYS[1],x);i
                   return ans",key)
    }
    
    setGetTask(getTask)
    

    我认为文档中的代码在语法上是不正确的(缺少imho a&#34;以及结束括号&#34;)&#34;)。我认为这在CRAN上是不可能的,因为文档的代码是在提交时执行的。

  2. 更改getTask函数不会改变任何关于worker获取任务的内容(即使将明显的无意义引入redisEval,如将其更改为redisEval(&#34; dddddddddd((&#34; )

  3. 我从源代码安装软件包之后才能访问setGetTask函数(我从official CRAN package page of version 1.1.1下载了这个函数(这与我直接从CRAN安装没什么区别)

    < / LI>

    数据:要执行的任务的数据框如下所示:

    taskName;taskQueuePosition;parameter1;paramterN
    taskT;1;val1;10
    taskK;2;val2;8
    taskP;3;val3;7
    taskA;4;val4;7
    

    我想使用&#39; taskQueuePosition&#39;为了控制订单,应首先执行数字较小的任务。

    问题:

    1. 是否有人知道我可以通过doRedis或setGetTask获取更多信息的任何来源?
    2. 有人知道我需要如何更改getTask以实现上述目的吗?
    3. 在foreach循环中控制执行顺序的任何其他聪明的想法?最好是在某些时候我可以使用doRedis作为并行后端(由于复杂的技术基础设施原因,改变这意味着处理的重大变化)。
    4. 代码(便于复制):

      以下假设redis-server在本地计算机上启动。

      Redis数据库填充:

      library(doRedis)
      library(foreach)
      
      options('redis:num'=TRUE) # needed for proper execution
      
      REDIS_JOB_QUEUE = "jobs"
      registerDoRedis(REDIS_JOB_QUEUE)
      
      # filling up the data frame
      taskDF = data.frame(taskName=c("taskT","taskK","taskP","taskA"),
                 taskQueuePosition=c(1,2,3,4),
                 parameter1=c("val1","val2","val3","val4"),
                 parameterN=c(10,8,7,7))
      
      foreach(currTask=iter(taskDF, by='row'), 
              .verbose = T
      ) %dopar% {
        print(paste("Executing task: ",currTask$taskName))
        Sys.sleep(currTask$parameterN)
      }
      
      removeQueue(REDIS_JOB_QUEUE)
      

      工人:

      library(doRedis)
      REDIS_JOB_QUEUE = "jobs"
      
      startLocalWorkers(n=1, queue=REDIS_JOB_QUEUE)
      

1 个答案:

答案 0 :(得分:0)

我可以解决问题,现在可以控制任务执行的顺序。

其他信息:

1。文档中似乎存在拼写错误,导致getTask示例无法正常工作。通过考虑包中文件task.R的default_getTask函数的形式,它应该看起来像:

getTaskDefault <- function ( queue , job_id , ...)
{
  key <- sprintf("%s:%s",queue, job_id)
  return(redisEval("local x=redis.call('hkeys',KEYS[1])[1];
                   if x==nil then return nil end;
                   local ans=redis.call('hget',KEYS[1],x);
                   redis.call('set', KEYS[1] .. '.start.' .. x, x);
                   redis.call('hdel',KEYS[1],x);
                   return ans",key))
}

似乎第一个百分号后面的字母在函数的第一行中丢失了。这可以解释括号和引号的数量不均匀。

2) setGetTask对我没有任何影响。当我填充DB时,通过.option设置getTask函数(就像在vignette of the package中描述的那样),它被成功调用。

3) 2)的信息意味着我不需要getTask函数,所以我可以使用CRAN中的包。

-----问题-----

1) doRedis插图描述了如何成功设置自定义getTask。

2和3)当getTask函数中的LUA脚本被修改如下时,任务将以提交方式从数据库中提取。这不是我要求的,但由于时间限制和我(或更好)没有关于LUA脚本的第一个想法的事实,它是一个令人满意的解决方案来控制taskQueuePosition列的提交顺序。

getTaskInOrder <- function ( queue , job_id , ...)
{

  key <- sprintf("%s:%s",queue, job_id)
  return(redisEval("

        local tasks=redis.call('hkeys',KEYS[1]); -- get all tasks

        local x=tasks[1];           -- get first task available task
        if x==nil then              -- if there are no tasks left, stop processing
          return nil 
        end;  

        local xMin = 65535;         -- if we have more tasks than 65535, getting the 
        -- task with the lowest taskID is not guaranteed to be the first one
        local i = 1;
        -- local iMinFound = -1;
        while (x ~= nil) do         -- search the array until there are no tasks left
        -- print('x: ',x)
        local xNum = tonumber(x);
        if(xNum<xMin) then
          xMin = xNum;
          -- iMinFound = i;
        end
        i=i+1;
        -- print('i is now: ',i);
        x=tasks[i];
        end
        -- print('Minimum is task number',xMin,' found at i ', iMinFound)
        x=tostring(xMin)            -- convert it back to a string (maybe it would 
                                    -- be better to keep the original string somewhere, 
                                    -- in case we loose some information whilst converting to number)

        -- print('x is now:',x);
        -- print(KEYS[1] .. '.start.' .. x, x);
        -- print('');
        local ans=redis.call('hget',KEYS[1],x);
        redis.call('set', KEYS[1] .. '.start.' .. x, x);
        redis.call('hdel',KEYS[1],x);
        return ans",key))
}

重要说明: 我注意到,如果任务中止,订单被搞砸,重新提交的任务(即使任务编号保持不变),也会在最初提交的任务之后执行。这对我来说没问题。

------代码(为了便于复制):------

这导致了以下代码示例(在任务数据框中有12个条目,而不是原始的4个):

Redis数据库填充:

library(doRedis)
library(foreach)

options('redis:num'=TRUE) # needed for proper execution

REDIS_JOB_QUEUE = "jobs"

getTaskInOrder <- function ( queue , job_id , ...)
{
  ...like above
}

registerDoRedis(REDIS_JOB_QUEUE)

# filling up the data frame already in order of tasks to be executed
# otherwise the dataframe has to be sorted by taskQueuePosition
taskDF = data.frame(taskName=c("taskA","taskB","taskC","taskD","taskE","taskF","taskG","taskH","taskI","taskJ","taskK","taskL"),
       taskQueuePosition=c(1,2,3,4,5,6,7,8,9,10,11,12),
       parameter1=c("val1","val2","val3","val4","val1","val2","val3","val4","val1","val2","val3","val4"),
       parameterN=c(5,5,5,4,4,4,4,3,3,3,2,2))

foreach(currTask=iter(taskDF, by='row'), 
        .verbose = T,
        .options.redis = list(getTask = getTaskInOrder
) %dopar% {
  print(paste("Executing task: ",currTask$taskName))
  Sys.sleep(currTask$parameterN)
}

removeQueue(REDIS_JOB_QUEUE)

工人:

library(doRedis)
REDIS_JOB_QUEUE = "jobs"

startLocalWorkers(n=1, queue=REDIS_JOB_QUEUE)

另一个注意事项:以防您正在处理长时间工作,请注意a bug in redis 1.1.1(CRAN上的当前版本),这会导致重新提交任务(由于尽管工人们还在为他们工作,但是暂停了。