SpringBoot开发案例之整合mail队列篇

    xiaoxiao2021-04-16  219

    前言

    前段时间搞了个SpringBoot开发案例之整合mail发送服务,也是基于目前各项目平台的邮件发送功能做一个抽离和整合。

    问题

    以发送方为例,比如网易的反垃圾邮件政策,过多或者频率过快的发送都会被判定为垃圾邮件,即使发再多的邮件也无济于事。当然如果是自建邮件服务器,也是要考虑发送服务的并发能力。

    以接收方邮件为例,比如腾讯邮箱就有类似说明:如果内容涉嫌大量群发,并且被多数用户投诉为垃圾邮件,也就通过不了收件方的"安检",如下图:

    方案

    为了解决以上实际场景中遇到的问题,使得其更像一个安全高效的邮件服务平台,我们尝试引入了任务队列对邮件发送进行流量削锋、间隔发送以及重复内容检测。

    首先,我们先建一个队列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方法在队列空的时候会阻塞,直到有队列成员被放进来。

    最后给大家补充一个非阻塞队列ConcurrentLinkedQueue,有兴趣的同学可以自行查阅资料。

    分享总结

    如果,你看到你写的代码是一坨屎的时候你就该去优化她了,好好爱护她,未来的你会为昨天的你而感到骄傲的。

    其实,想表达的是,架构优化是无止境的,随着业务的增长以及平台的发展,我们会遇到各种各样的问题。

    邮件服务挂了,队列还没消费完怎么办?邮件服务挂了,我们是否应该做个高可用?邮件服务爆了,我们是否应该做个负载均衡?

    以上问题,你又会怎么解决呢?下一篇为大家带来高可用的邮件服务平台。

    项目案例:码云

    作者: 小柒

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

    分享是快乐的,也见证了个人成长历程。文章大多都是工作经验总结以及平时学习积累,基于自身认知不足之处在所难免,也请大家指正,共同进步。

    本文版权归作者和云栖社区所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 如有问题, 可邮件(345849402@qq.com)咨询。

    相关资源:SpringBoot中整合Mail实现简单邮件发送示例代码

    最新回复(0)