天天看點

「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雲開發的有獎征文活動,已經獲得作者授權