1

我們遇到問題。我們的客戶抱怨他們在收件箱中收到重複的電子郵件。在同一時間內,有些日​​子最多有5到6次完全相同的電子郵件。我們不明白爲什麼。代碼已被重寫至少一次,但問題仍然存在。防止從Google App Engine發送重複的每日報告電子郵件

我會試着解釋這...但它是一個有點複雜:我們想送我們的用戶每日報告包含使用情況統計信息O(

每天晚上(凌晨),所以我們有一個cron工作:

<cron> 
    <url>/redacted/report/url</url> 
    <description>Send out daily reports to active subscribers</description> 
    <schedule>every 2 hours</schedule> 
</cron> 

cron作業到達這個servlet get方法:

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 
    AccountFilter filter = AccountFilter.forWebSafeName(req.getParameter("filter")); 
    createTasks(filter, null); 
} 

它調用createTasks方法用一個空指針:

private void createTasks(AccountFilter accountFilter, String cursor) { 
    try { 
     PagedResults<Account> pagedAccounts = accountRepository.getAccounts(accountFilter.getFilter(), 50, cursor); 
     createTaskBatch(pagedAccounts); 

     // If there are still more results in cursor, then send cursor back to this servlet's doPost method so we don't hit the request time limit 
     if (pagedAccounts.getCursor() != null) { 
      getQueue(QUEUE_NAME).add(withUrl(WORKER_URL).param(CURSOR_KEY, pagedAccounts.getCursor()).param(FILTER_KEY, accountFilter.getWebSafeName())); 
     } 
    } catch(Exception ex) { 
     logger.log(Level.WARNING, "Problem creating daily report task batch for filter " + accountFilter.getWebSafeName(), ex); 
    } 
} 

它抓取50個帳戶並對它們進行迭代,爲當前應發送的電子郵件創建新的排隊作業。有代碼可以明確地檢查上次報告發送的時間戳,並在創建新排隊的任務之前更新時間戳。這應該寧可不發送報告,而不是發送重複的一面:

private void createTaskBatch(PagedResults<Account> pagedAccounts) { 
    // GAE datastore query might return duplicate results?! 
    List<Account> list = pagedAccounts.getResults(); 
    Set<Account> noDuplicates = new HashSet<>(list); 
    int dups = list.size() - noDuplicates.size(); 
    if (dups > 0){ 
     logger.warning ("Accounts paged results contained " + dups + " duplicates!"); 
    } 
    for (Account account : noDuplicates) { 
     try { 
      if (lastReportSentOver12HoursAgo(account)) { 
       List<Parent> parents = parentRepository.getVerifiedParentsForAccount(account.getId()); 
       if (eitherParentSubscribed(parents)) { 
        List<AccountUser> users = accountUserRepository.listUsers(account.getId()); 
        List<Device> devices = getUserDevices(account, users); 
        if (!devices.isEmpty()) { 
         DateTimeZone tz = getMostCommonTimezone(devices); 
         if (null == tz){ 
          logger.warning("No timezone found for account: " + account.getId()); 
         } 
         else{ 
          // Send early in the morning as the report contains the previous day's stats 
          if (now(tz).getHourOfDay() < 7) { 
           // mark sent now because queue might not be processed for a while 
           // and the next cursor set might contain some of the same accounts 
           accountRepository.markReportSent(account.getId(), now()); 
           getQueue(QUEUE_NAME).add(withUrl(DailyReportServlet.WORKER_URL).param(DailyReportServlet.ACCOUNT_ID, account.getId()).param(DailyReportServlet.COMMON_TIMEZONE, tz.getID())); 
          } 
         } 
        } 
       } 
      } 
     } catch(Exception ex) { 
      logger.log(Level.WARNING, "Problem creating daily report task for " + account.getId(), ex); 
     } 
    } 
} 

該servlet POST方法需要照顧的通過遊標方法處理結果的跟進網頁:

public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { 
    AccountFilter accountFilter = AccountFilter.forWebSafeName(req.getParameter(FILTER_KEY)); 
    logger.log(Level.INFO, "doPost hit from task queue with filter " + accountFilter.getWebSafeName()); 
    String cursor = req.getParameter(CURSOR_KEY); 
    createTasks(accountFilter, cursor); 
} 

有是處理每個報表任務的另一個servlet,它只是創建com.sendgrid.SendGrid類中的電子郵件內容和調用發送。

Datastore最終的一致性似乎是可能的候選者,但應該在幾秒鐘內解決,我不明白這將如何解決客戶抱怨的數量和一些客戶看到的重複數量。

幫助!有任何想法嗎?我們在某個地方愚蠢嗎?

修訂

爲了清楚......電子郵件發送任務隊列在這個方法中確實捕獲異常並報告他們回到我們結束了。我們沒有看到重複的情況例外:

private void sendReport(Account account, DateTimeZone tz) throws IOException, EntityNotFoundException { 
    try { 
      boolean sent = false; 
      Map<String, Object> root = buildEmailData(account, tz); 
      for (Parent parent : parentRepository.getVerifiedParentsForAccount(account.getId())) { 
       if (parent.getEmailPreferences().isSubscribedReports()) { 
        emailBuilder.send(account, parent, root, "report", EmailSender.NOTIFICATION); 
        sent = true; 
       } 
      } 
      if (sent){ 
       accountRepository.markReportSent(account.getId(), now()); 
      } 
    } catch (Exception ex) { 
     String message = "Problem building report email for account " + account.getId(); 
     logger.log(Level.WARNING, message, ex);; 
     new TeamNotificationEvent(message + " : exception: " + ex.getMessage()).fire(); 
     throw new IOException(message, ex); 
    } 
} 

更新2之後增加額外調試日誌

我看到兩個職位在同一時間,以相同的任務隊列相同的遊標:

09:35:08.397 2015年4月30日200 0 B 3.78s/WS /通知/每日報告任務創造者 0.1.0.2 - - [30 /月/ 2015:01:35 :08 -0700]「POST/ws/notification/daily-report-task-creator HTTP/1.1」200 0「http://screentimelabs.appspot.com/ws/notification/daily-report-task-creator 「」AppEngine-Google; (+ http://code.google.com/appengine)「」screentimelabs.appspot.com「ms = 3782 cpu_ms = 662 queue_name = dailyReports task_name = 8168414365365326983 instance = 00c61b117c33a909790f0d1882657e04f40b2c7e app_engine_release = 1.9.20 09:35:04.618 com.screentime.service.taskqueue.reports。DailyReportTaskCreatorServlet createTasks:createTasks要求濾波器:ACTIVE與光標:E-ABAIICO2oQc35zY3JlZW50aW1lbGFic3InCxIHQWNjb3VudCIaamFybW8ua2Fya2thaW5lbkBnbWFpbC5jb20MiAIAFA

09:35:08.432 2015年4月30日200 0 B 8.84s/WS /通知/每日報告任務創造者「POST/ws/notification/daily-report-task-creator HTTP/1.1」200 0「http://screentimelabs.appspot.com/ws/notification/daily-report-task-creator」「AppEngine-Google;(()/ - [30/Apr/2015:01:35:08 -0700] + http://code.google.com/appengine)「」screentimelabs.appspot.com「ms = 8837 cpu_ms = 1348 queue_name = dailyReports task_name = 50170612326424582061 instance = 00c61b117c2bffe8de313e96fea8aeb813f4b20f app_engine_release = 1.9.20 trace_id = 7e5c0348382e66cf4e2c6ba400529fb7 09:34:59.608 com.screentime.service.taskqueue.reports.DailyReportTaskCreatorServlet createTasks:createTasks要求濾波器:ACTIVE與光標:E-ABAIICO2oQc35zY3JlZW50aW1lbGFic3InCxIHQWNjb3VudCIaamFybW8ua2Fya2thaW5lbkBnbWFpbC5jb20MiAIAFA

在搜索1個特定帳戶ID我看到這些請求:

09:35:08.397 2015年4月30日200 0 B 3.78s/WS /通知/每日報告任務創造者

09:35:08.432 2015年4月30日200 0 B 8.84s/ws/notification/daily-report-task-creator

09:35:08.443 2015年4月30日200 0 B 6.73s/WS /通知/每日報告任務創造者

09:35:10.541 2015年4月30日200 0 B 4.03s/WS /通知/每日報告任務創造者

09:35:10.690 2015年4月30日200 0 B 11.09s/WS /通知/每日報告任務創造者

9點35: 13.678 2015-04-30 200 0 B 862ms/ws/notification/daily-report-worker

09:35:13.829 2015-04-30 500 0 B 1.21s/ws/notification/daily-report-worker

09:35:14.677 2015年4月30日200 0 B 1.56s/WS /通知/每日報告工

09:35:14.961 2015年4月30日200 0 B 346ms/WS /通知/ daily-report-worker

一些重複的遊標值。

+0

添加日誌記錄,以便下次發生這種情況時,您知道它是如何發生的。 –

+0

說起來容易做起來難 - 有超過10,000個賬戶,並且沒有簡單的方法來檢測它何時發生......如果我能夠檢測到它發生的時間,我可以修復它:0) – TiGz

+1

將您的日誌導出到bigquery(trivial wth appengine)並找到它有 –

回答

0

我會猜測,因爲我沒有看到任務隊列的代碼。它很可能是您沒有在任務隊列中正確處理錯誤。如果任務隊列發生錯誤,gae將重新排隊。因此,如果某些電子郵件已發送,該任務仍會再次運行。您需要一種方法來記住您已在任務隊列中處理的內容,以便重試不會重新處理這些內容。

+0

我已經添加了關於此的更新 - 謝謝 – TiGz

+0

除非您認爲我們需要捕獲Throwable以及Exception? – TiGz

+0

我看到您的更新。 1)您首先發送電子郵件,然後標記發送,以便在兩者之間的失敗將再次發送,也許應該是另一種方式。2)我其實不使用java,但我相信在java中沒有辦法捕捉「全部」,而是需要捕獲每個可能的異常。 –

相關問題