閱讀341 返回首頁    go 阿裏雲 go 技術社區[雲棲]


SpringBoot開發案例之整合mail隊列篇

_

前言

前段時間搞了個SpringBoot開發案例之整合mail發送服務,也是基於目前各項目平台的郵件發送功能做一個抽離和整合。

問題

以發送方為例,比如網易的反垃圾郵件政策,過多或者頻率過快的發送都會被判定為垃圾郵件,即使發再多的郵件也無濟於事。當然如果是自建郵件服務器,也是要考慮發送服務的並發能力。

以接收方郵件為例,比如騰訊郵箱就有類似說明:如果內容涉嫌大量群發,並且被多數用戶投訴為垃圾郵件,也就通過不了收件方的"安檢",如下圖:

maileror

方案

為了解決以上實際場景中遇到的問題,使得其更像一個安全高效的郵件服務平台,我們嚐試引入了任務隊列對郵件發送進行流量削鋒、間隔發送以及重複內容檢測。

首先,我們先建一個隊列MailQueue:

/**
 * 郵件隊列
 * 創建者 科幫網
 * 創建時間 2017年8月4日
 *
 */
public class MailQueue {
     //隊列大小
    static final int QUEUE_MAX_SIZE   = 1000;

    static BlockingQueue<Email> blockingQueue = new LinkedBlockingQueue<Email>(QUEUE_MAX_SIZE);

    /**
     * 私有的默認構造子,保證外界無法直接實例化
     */
    private MailQueue(){};
    /**
     * 類級的內部類,也就是靜態的成員式內部類,該內部類的實例與外部類的實例
     * 沒有綁定關係,而且隻有被調用到才會裝載,從而實現了延遲加載
     */
    private static class SingletonHolder{
        /**
         * 靜態初始化器,由JVM來保證線程安全
         */
        private  static MailQueue queue = new MailQueue();
    }
    //單例隊列
    public static MailQueue getMailQueue(){
        return SingletonHolder.queue;
    }
    //生產入隊
    public  void  produce(Email mail) throws InterruptedException {
        blockingQueue.put(mail);
    }
    //消費出隊
    public  Email consume() throws InterruptedException {
        return blockingQueue.take();
    }
    // 獲取隊列大小
    public int size() {
        return blockingQueue.size();
    }
}

如文章開頭圖片所描述,這裏我們還需要建一個消費線程池ConsumeMailQueue:

/**
 * 消費隊列
 * 創建者 科幫網
 * 創建時間 2017年8月4日
 */
@Component
public class ConsumeMailQueue {
    private static final Logger logger = LoggerFactory.getLogger(ConsumeMailQueue.class);
    @Autowired
    IMailService mailService;

    @PostConstruct
    public void startThread() {
        ExecutorService e = Executors.newFixedThreadPool(2);// 兩個大小的固定線程池
        e.submit(new PollMail(mailService));
        e.submit(new PollMail(mailService));
    }

    class PollMail implements Runnable {
        IMailService mailService;

        public PollMail(IMailService mailService) {
            this.mailService = mailService;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    Email mail = MailQueue.getMailQueue().consume();
                    if (mail != null) {
                        logger.info("剩餘郵件總數:{}",MailQueue.getMailQueue().size());
                        //可以設置延時 以及重複校驗等等操作
                        mailService.send(mail);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    @PreDestroy
    public void stopThread() {
        logger.info("destroy");
    }
}

改造service:
部分接口:

 /**
      * 隊列
      * @Author  科幫網
      * @param mail
      * @throws Exception  void
      * @Date   2017年8月4日
      * 更新日誌
      * 2017年8月4日  科幫網 首次創建
      *
      */
     public void sendQueue(Email mail) throws Exception;

部分實現:

    @Override
    public void sendQueue(Email mail) throws Exception {
        MailQueue.getMailQueue().produce(mail);
    }

隊列說明

以上代碼,大家可以看到我們有使用到了linkedblockingqueue來實現郵件發送隊列。

LinkedBlockingQueue作為一個阻塞隊列是線程安全的,同時具有先進先出等特性,是作為生產者消費者的首選。

LinkedBlockingQueue可以指定容量,也可以不指定,不指定的話,默認最大是Integer.MAX_VALUE。

其中主要用到put和take方法,put方法在隊列滿的時候會阻塞直到有隊列成員被消費,take方法在隊列空的時候會阻塞,直到有隊列成員被放進來。

qj6Yi4M_png

最後給大家補充一個非阻塞隊列ConcurrentLinkedQueue,有興趣的同學可以自行查閱資料。

分享總結

如果,你看到你寫的代碼是一坨屎的時候你就該去優化她了,好好愛護她,未來的你會為昨天的你而感到驕傲的。

其實,想表達的是,架構優化是無止境的,隨著業務的增長以及平台的發展,我們會遇到各種各樣的問題。

  • 郵件服務掛了,隊列還沒消費完怎麼辦?
  • 郵件服務掛了,我們是否應該做個高可用?
  • 郵件服務爆了,我們是否應該做個負載均衡?

以上問題,你又會怎麼解決呢?下一篇為大家帶來高可用的郵件服務平台。

項目案例:碼雲

作者: 小柒

出處: https://blog.52itstyle.com

分享是快樂的,也見證了個人成長曆程。文章大多都是工作經驗總結以及平時學習積累,基於自身認知不足之處在所難免,也請大家指正,共同進步。

本文版權歸作者和雲棲社區所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出, 如有問題, 可郵件(345849402@qq.com)谘詢。

最後更新:2017-08-13 22:29:20

  上一篇:go  如何構建一個加密貨幣交換應用程序將要花費多少錢
  下一篇:go  嵌入式ARM11處理器特點分析