在正确处理之前,App Engine Pull Queue任务会消失

时间:2014-02-21 18:11:48

标签: java google-app-engine

更新4 - 为了清晰起见重新提出问题

我正在使用Pull Queues来提供发送推送通知的后端工作人员任务。我可以看到前端实例在日志中排队任务。但是,该任务仅偶尔由后端处理。我没有看到任务在处理和从队列中删除之前为什么会消失的迹象。

这可能是相关的:我在尝试从队列中租用任务时看到异常高的TransientFailureException个数 - 尽管在尝试之间休眠。

我的开发服务器上的所有内容都正常工作(早期版本已在生产中使用),但生产不再正常。起初我以为这是证书问题。但是,有时会在后端首次启动时发送通知。

当我在队列上调用leaseTasks时,除了TransientFailureException之外没有任何迹象表明发生了错误。此外,我的日志似乎需要很长时间才能显示出来。

我可以根据需要提供更多信息和代码段。

感谢您的帮助。

更新1:

该应用程序使用10个拉取队列。它通常会使用2但队列标记仍然被认为是实验性的。它们以标准方式声明:

<queue>
    <name>gcm-henchdist</name>
    <mode>pull</mode>
</queue>

租赁任务功能是:

public boolean processBatchOfTasks()
{
    List< TaskHandle > tasks = attemptLeaseTasks();

    if( null == tasks || tasks.isEmpty() )
    {
        return false;
    }

    processLeasedTasks( tasks );
    return true;
}

private List< TaskHandle > attemptLeaseTasks()
{
    for( int attemptNnum = 1; !LifecycleManager.getInstance().isShuttingDown(); ++attemptNnum )
    {
        try
        {
            return m_taskQueue.leaseTasks( m_numLeaseTimeUnits, m_leaseTimeUnit, m_maxTasksPerLease );
        } catch( TransientFailureException exc )
        {
            LOG.warn( "TransientFailureException when leasing tasks from queue '{}'", m_taskQueue.getQueueName(), exc );
            ApiProxy.flushLogs();
        } catch( ApiDeadlineExceededException exc )
        {
            LOG.warn( "ApiDeadlineExceededException when when leasing tasks from queue '{}'",
                    m_taskQueue.getQueueName(), exc );
            ApiProxy.flushLogs();
        }

        if( !backOff( attemptNnum ) )
        {
            LOG.warn( "Failed to lease tasks." );
            break;
        }
    }

    return Collections.emptyList();
}

其中租约变量为30,TimeUnit.MINUTES,分别为100

通过以下方式查询processBatchOfTasks函数:

private void startPollingForClient( EClientType clientType )
{
    InterimApnsCertificateConfig config = InterimApnsCertificateConfigMgr.getConfig( clientType );
    Queue notificationQueue = QueueFactory.getQueue( config.getQueueId().getName() );

    ApplePushNotificationWorker worker = new ApplePushNotificationWorker(
            notificationQueue,
            m_messageConverter.getObjectMapper(),
            config.getCertificateBytes(),
            config.getPassword(),
            config.isProduction() );

    LOG.info( "Started worker for {} polling queue {}", clientType, notificationQueue.getQueueName() );

    while ( !LifecycleManager.getInstance().isShuttingDown() )
    {
        boolean tasksProcessed = worker.processBatchOfTasks();
        ApiProxy.flushLogs();

        if ( !tasksProcessed )
        {
            // Wait before trying to lease tasks again.
            try
            {
                //LOG.info( "Going to sleep" );
                Thread.sleep( MILLISECONDS_TO_WAIT_WHEN_NO_TASKS_LEASED );
                //LOG.info( "Waking up" );
            } catch ( InterruptedException exc )
            {
                LOG.info( "Polling loop interrupted. Terminating loop.", exc );
                return;
            }
        }
    }

    LOG.info( "Instance is shutting down" );
}

并通过以下方式创建线程:

Thread thread = ThreadManager.createBackgroundThread( new Runnable()
{
    @Override
    public void run()
    {
        startPollingForClient( clientType );
    }
} );

thread.start();

GCM通知以类似的方式处理。

更新2

以下是退避功能。我在日志中验证了(包括GAE和我自己的时间戳)睡眠正在递增

private boolean backOff( int attemptNo )
{
    // Exponential back off between 2 seconds and 64 seconds with jitter
    // 0..1000 ms.
    attemptNo = Math.min( 6, attemptNo );
    int backOffTimeInSeconds = 1 << attemptNo;
    long backOffTimeInMilliseconds = backOffTimeInSeconds * 1000 + (int)( Math.random() * 1000 );

    LOG.info( "Backing off for {} milliseconds from queue '{}'", backOffTimeInMilliseconds, m_taskQueue.getQueueName() );
    ApiProxy.flushLogs();

    try
    {
        Thread.sleep( backOffTimeInMilliseconds );

    } catch( InterruptedException e )
    {
        return false;
    }

    LOG.info( "Waking up from {} milliseconds sleep for queue '{}'", backOffTimeInMilliseconds, m_taskQueue.getQueueName() );
    ApiProxy.flushLogs();

    return true;
}

更新3

任务将添加到前端实例上的事务中的队列中:

if( null != queueType )
{
    String deviceName;
    int numDevices = deviceList.size();
    for ( int iDevice = 0; iDevice < numDevices; ++iDevice )
    {
        deviceName = deviceList.get( iDevice ).getName();
        LOG.info( "Queueing Your-Turn notification for user: {} device: {} queue: {}", user.getId(), deviceName, queueType.getName() );
        Queue queue = QueueFactory.getQueue( queueType.getName() );

        queue.addAsync( TaskOptions.Builder.withMethod( TaskOptions.Method.PULL )
                .param( "alertLocKey", "NOTIF_YOUR_TURN" ).param( "device", deviceName ) );
    }
}

我知道事务成功,因为数据库正确更新。

在日志中,我看到“排队你的转弯通知...”条目,但我看到后端日志中没有显示任何内容。

在管理面板中,我看到任务队列API调用递增1以及任务队列存储任务计数递增1.但是,写入的队列在队列中的任务和最后一分钟租用中都显示为零字段。

2 个答案:

答案 0 :(得分:0)

TransientFailureException JavaDoc表示“如果再次尝试,请求的操作可能会成功”(因为失败是暂时的)。因此,当抛出此异常时,您的代码应循环回并重复leaseTasks调用。此外,AppEngine不必重做请求本身,因为它通过例外通知您应该这样做。

遗憾的是你重复方法名称leaseTasks作为你自己的一个,因为现在我不清楚当我提到leaseTasks时我指的是哪一个。仍然,将内部调用包含在while循环中的m_taskQueue.leaseTasks和另一个try块中以仅捕获TransientFailureException。仅当未抛出该异常时,才使用标志结束while循环。

这是足够的解释,还是需要完整的源代码列表?

答案 1 :(得分:0)

看起来罪魁祸首可能是我在排队任务时调用addAsync而不只是调用add。

我取代了电话,事情似乎一直在努力。我想知道为什么这会产生影响,并在找到原因时更新答案。