天天看点

「Serverless云开发72变」Serverless邮件推送系统实现

我的场景选择和思考

场景选择

某WEB网站在用户注册成功后,会发一封欢迎邮件,通过函数计算把邮件内容定制成模板,每次触发,每次执行都是幂等无状态。因为做过对应业务,对阿里的对应的文档比较熟悉,且有部分实现代码。所以就是选择了这个业务场景。

思考

第一次选择函数计算去做对应的业务场景实现,个人在场景选择上比较谨慎。由于之前做过SpringCloud项目对阿里云邮件服务的整合,并完成了注册后邮件推送的代码逻辑实现,所以我就在思考把传统代码实现搬到云上使用函数计算来实现的可行性。

在原来的SpringCloud项目实现中,我主要使用了Redis MySQL 阿里云SDK集成等服务。首先就要考虑的就是阿里云函数计算JavaSDK对阿里云自己的SDK以及其他的第三方SDK的支持程度。我先找宁中大佬询问了阿里云函数计算 Java SDK对MySQL的支持后(可行的),就坚定了可以将原有代码设计转移到云上的决心。

系统设计与代码实现

系统设计

首先我设计的这个邮件推送系统主要包括以下的职能:

  1. 完成注册结果的推送
  2. 完成邮件校验码的发送
  3. 完成订阅者的定期邮件推送

对于一般的邮件系统来说,上述三条应该包含了大多数的业务场景,因此我主要围绕上述三点进行论述。由于系统用的是函数计算,且对于邮件系统来说,业务逻辑其实并不是过分的复杂,所以尽量轻量化的去做代码实现,减少重消耗的服务的引入。

我在做技术选择的时候(做SpringCloud的邮件服务的时候),参考了多家的邮件服务,为什么选择阿里云呢,其实是接口文档给的比较充足且具备可视化及统计服务。考虑到系统的稳定性,所以采用了阿里云的邮件推送服务。然后为了考虑扩展性我采用了上传HTML模板然后在用代码对预留关键字进行修改的形式去做代码的自定义处理。其实主要的也就是这两块 具体的分析我将在服务依赖中一一讲解。

服务依赖

该系统为Maven项目所以对依赖的管理较为方便 ,我主要使用的几个组件

  1. 阿里云邮件服务SDK
  2. Redis
  3. Mybatis
  4. FastJson
  5. Junit
  6. slf4j

使用阿里云邮件SDK的原因我已经在上面做了阐述,下面我主要讲一下Redis MySQL的使用。先说一下MySQL,有人会问不就是发送一个邮件吗,写几个模板丢到项目代码里面代码指一下不就好了。这样说也没什么问题,确实是可以这样实现。但是存在一个问题,假如模板格式发生了变动,那么整个代码也就需要改动。这样的话对于扩展性来说就显得相当不利了,还有就是SDK中的配置很多,每种邮件都会有自己不同的参数配置 ,假若如此,每个都在代码中申明其实问题和上面一样,对于修改与维护相当不利。所以我将其抽取出来 做成了两个类 1、SmtpConfig(存储配置) 2、SmtpTemplate(存储模板)。这两个类的代码实现,可以在我后面贴的码云链接里面找到。在这两个类的设计中我采用了充血模式,将部分对象封装业务以及对应的核心数据处理业务放到了实体类当中。大家有兴趣的可以在代码中查看一下,希望能提出宝贵的意见。

还有人会说你说使用MySQL是为了代码的维护性,那Redis是怎么一回事呢。这也和你之前说的轻量化也不符合啊。回答这个问题我们还要回到业务上面,对于邮件推送和验证码发送场景,邮件的频繁发送合理吗。显然不合理,连续的随意发送和有可能会使你的发送地址被邮件接收方标记成垃圾邮件。还有就是验证码发送场景,其实不只是验证码发送出去就了结的,其实还包含了校验的逻辑,所以使用Redis的原因其实大家就了解了。

其实还有一个原因:做缓存 从上面可知 我的模板是放在MySQL里面的以一个OSS链接的形式做的 ,那么每次调用的时候就需要请求OSS链接拿到对应的模板然后在进行解析。然后再替换对应的预设字段进行发送。从日常使用中大家都知道字符串替换的速度其实很快但是,文件的解析则需要根绝网络的速度来决定,并且将文件读成一个String串其实也很麻烦。所以我在第一次读取该模板后将原先的读取好的子串信息放入到Redis中 在第二次调用同一模板时则直接从Redis里面取数据,假如三天之后这个模板还是没有再被调用,则说明该模板并不常有,比较偏,所以就从Redis自动删除(预设时间3天)这样的话执行损耗就可以从300ms降低到100ms左右。

解耦思维

在代码发送前,其实需要做一些状态检查和一些条件拦截的操作。假如光使用Ifelse做简单的代码实现,则代码可读性和代码扩展性就会降低。我之前读过阿里大佬张建飞(大飞哥)的书,对其写的Cola框架有过一定的了解,所以我使用了其中的责任链模式以及其对异常处理的一些实现。

 首先看一下责任链设计

/**
 * @author mac_zyj
 *  一分钟内不能重复发送短信规则
 */

public class EmailOneMinNotRepeatFilter implements Filter<Map<String,Object>> {

    private final Logger logger = LoggerFactory.getLogger(EmailOneMinNotRepeatFilter.class);

    private static final String SMS_ERROR_MESSAGE="请不要一分钟内重复发送";

    private RedisUtils redisService =new RedisUtils();

    @Override
    public void doFilter(Map<String, Object> contextMap, FilterInvoker nextFilter) {
        String emailAddress= (String) contextMap.get("emailAddress");
        String repeatCheckKey=emailAddress+ SmtpServiceConstants.REPEAT_CHECK;
        if(null!=redisService.get(repeatCheckKey)){
            logger.warn("SmsException with error code,error message");
            throw new BizException(SMS_ERROR_MESSAGE);
        }
        nextFilter.invoke(contextMap);
    }
}      

上面的代码主要是做的重复发送的校验,若不存在重复情况则进行下一个条件校验若存在重复情况则进行异常抛出。返回错误信息。其余的情况也如此:例如 一定时间内过多的发送 同Ip发送次数过多等 都可以在后面进行追加具体使用如下

private final FilterChain filterChain= FilterChainFactory.buildFilterChain(
            EmailOneMinNotRepeatFilter.class,
            EmailSendTooMuchFilter.class
    );
 @Override
    public void sendEmailCode(String toAddress, String code)   {
        //先执行拦截操作
        Map<String,Object> content=new HashMap<>(16);
        content.put("emailAddress",toAddress);
        filterChain.doFilter(content);
        ...........
}      

项目不足与展望

 在上述文字和代码实现中 ,我完成了部分代码的实现(只完成了验证码发送场景),还有一部分由于时间原因并没有来得及进行编码,所以也有一些遗憾。还有就是对于数据库存储邮件模板的问题,是不是最优解也有待商榷。在后续的时间里,我会在码云上完善上述案例争取覆盖我之前说的其余的 没有实现的场景,并且优化现有的代码,希望大家多多帮助。提出宝贵的意见。

代码实现

我把代码上传到了码云上面

https://gitee.com/zhuyongjie1212/serverless-email

后续计划逐步完善上面所说的不足,并不断更新。也请有兴趣的大佬提出宝贵的意见,大家互相进步。

小结

  在阿里云 云开发平台的这次活动中,通过对具体的业务场景进行分析。对代码的设计与实现使我对Serverless云开发有了更深的认识与理解,特别是对于Java函数计算的使用,使我了解到了对于Java这种老牌开发语言在处理传统业务场景之外的使用,对我扩充技术栈具有一定的帮助。

  另外,感谢阿里云云开发平台给了我这个参与活动的机会,使我有机会和大家分享我对Serverless云开发的理解和具体的使用。大家可以访问云开发平台的官网(

http://workbench.aliyun.com/

)。那里有更多的Serverless上云的实践,云开发平台是一个可以满足开发者、研发团队完全基于「云+浏览器」就能完成日常开发工作的环境,它的设计理念是使自己成为团队大协同中的一环,它会跟阿里云诸多研发能力和工具进行集成,籍由更强大的阿里研发生态,为用户提供更大的协同研发可能,比如您在使用云开发平台的时候,可以根据您的需要,主动选择去开通使用项目管理、需求管理、文档管理等其他服务。

同时,为了帮助用户提供一个无缝应用阿里云服务的环境,云开发平台会跟阿里云的诸多云产品进行集成,随时为用户的使用而准备;您可以在云开发平台创建基于各种场景解决方案的应用,并为每个应用选用不同的云服务,这些云服务会开通在您的阿里云主账号之下,您主动开通的各种云资源会按照您的使用,正常地计量计费。

云开发平台鼓励所有的场景解决方案尽可能多的基于阿里云的 Serverless 类型产品去提供服务。Serverless 类型的产品都具有实时弹性以及按量付费的特征,这可以帮助到商业化研发团队,以尽可能低的成本去实现自己的商业价值。

背景知识

什么是函数计算

  函数计算是事件驱动的全托管计算服务。使用函数计算,您无需采购与管理服务器等基础设施,只需编写并上传代码。函数计算为您准备好计算资源,弹性地、可靠地运行任务,并提供日志查询、性能监控和报警等功能。

  借助函数计算,您可以快速构建任何类型的应用和服务,并且只需为任务实际消耗的资源付费。

Java函数计算

  函数计算支持Java8运行环境。Java语言由于需要编译后才可以在JVM虚拟机中运行。和Python、Node.js这类脚本型语言不同,Java语言有以下限制:

  不支持上传代码:仅支持上传已经开发完成、编译打包后的ZIP包或JAR包。函数计算不提供Java的编译能力。

  不支持在线编辑:由于不支持上传代码,所以不支持在线编辑代码,仅能看到通过页面上传或OSS上传两种方法提交代码。

事件函数接口

  您在使用Java编程时,必须要实现函数计算提供的接口类,对于事件入口函数目前有两个预定义接口可以选择。这两个预定义接口分别是:

StreamRequestHandler

  以流的方式接受调用输入event和返回执行结果,您需要从输入流中读取调用函数时的输入,处理完成后把函数执行结果写入到输出流中来返回。

PojoRequestHandler

  通过泛型的方式,您可以自定义输入和输出的类型,但是输入和输出的类型必须是POJO类型。

常见业务场景

  1. IoT应用:设备端通过函数计算来订阅天气信息和空气质量,设备和设备之间无依赖,执行过程中无需记录状态,获取到第三方数据即可返回。
  2. WEB应用:某WEB网站在用户注册成功后,会发一封欢迎邮件,通过函数计算把邮件内容定制成模板,每次触发,每次执行都是幂等无状态。
  3. 图片处理:基于OSS的事件触发,当用户上传的图片转入到某Bucket中后,自动触发函数岁图片进行可定制化处理
  4. 音频转换文字处理:当用户通过语音来发出某些指令的时候,可以通过函数计算来触发阿里云的ET公开API获取到音频转换成文字的方式。

还没有使用过Serverless云开发?

现在花3分钟体验新手任务即领10元阿里云无门槛代金券。

「Serverless云开发72变」Serverless邮件推送系统实现

本文参加Serverless云开发的有奖征文活动,已经获得作者授权