使用Apache commons email發送郵件

今天研究了一下怎麼用java代碼發送郵件,用的是Apache的commons-email包。

據說這個包是對javamail進行了封裝,簡化了操作。 這裏講一下具體用法吧

 

一.首先你需要有郵箱賬號和一個授權碼。

需要進入到QQ郵箱或者是網易郵箱裏面去獲取。在郵箱的設置->賬戶裏面,開啟如下服務,就能得到一個授權碼,這個授權碼要好好保管。有了這兩個東西就能夠通過第三方客戶端發送郵件了。

 

二.導入commons-email的maven依賴。

我用的是1.4,也可以去maven倉庫網站(https://mvnrepository.com)上面找別的版本。

<dependency>
     <groupId>org.apache.commons</groupId>
     <artifactId>commons-email</artifactId>
     <version>1.4</version>
 </dependency>

三.然後就可以寫發送郵件的代碼了。

我在網上找了幾個案例,如下。

 1.發送簡單文本郵件。這是最簡單也是最常用的。

    /**
     * @describe 發送內容為簡單文本的郵件
     * @throws EmailException
     */
    public static void sendSimpleTextEmail() throws EmailException {
         Email email = new SimpleEmail();
         //設置主機名,QQ郵箱是"smtp.qq.com",網易郵箱是"smtp.163.com"
         email.setHostName("smtp.163.com");
         // 用戶名和密碼為郵箱的賬號和授權碼(不需要進行base64編碼)
         email.setAuthenticator(new DefaultAuthenticator("myemailaddress@163.com", "myshouquanma"));
         //設置SSL連接,這樣寫就對了
         email.setSSLOnConnect(true);
         //設置來源,就是發送方的郵箱地址
         email.setFrom("myemailaddress@163.com");
         //設置主題,可以不設置
         email.setSubject("java發送郵件");
         //設置信息,就是內容,這個必須要有
         email.setMsg("這是測試郵件 ... :-)");
         //接收人郵箱地址
         email.addTo("receiveeraddress@qq.com");
         email.send();
    }

 

2.發送包含附件的郵件(附件為本地資源),這裏用到了一個EmailAttachment對象,也就是附件的意思

    /**
     * @describe 發送包含附件的郵件(附件為本地資源)
     * @throws EmailException
     */
    public static void sendEmailsWithAttachments() throws EmailException {
        // 創建一個attachment(附件)對象
        EmailAttachment attachment = new EmailAttachment();
        //設置上傳附件的地址
        attachment.setPath("C:\\Users\\Administrator\\Pictures\\Saved Pictures\\conti.png");
        attachment.setDisposition(EmailAttachment.ATTACHMENT);
        //這個描述可以隨便寫
        attachment.setDescription("Picture of conti");
        //這個名稱要注意和文件格式一致,這將是接收人下載下來的文件名稱
        attachment.setName("conti.png");

        //因為要上傳附件,所以用MultiPartEmail()方法創建一個email對象,固定步驟都是一樣的
        MultiPartEmail email = new MultiPartEmail();
        email.setHostName("smtp.163.com");
        email.setAuthenticator(new DefaultAuthenticator("myemailaddress@163.com", "myshouquanma"));
        email.setSSLOnConnect(true);
        email.addTo("receiveemail@qq.com", "Conti Zhang");
        email.setFrom("myemailaddress@163.com", "Me");
        email.setSubject("圖片");
        email.setMsg("這是發送給你的圖片");
        //將附件添加到郵件
        email.attach(attachment);

        email.send();
    }

 

3.發送包含附近的郵件(附件為在線資源),這個與上傳本地附件稍有區別,注意一下就行

   /**
     * @describe 發送包含附件的郵件(附件為在線資源)
     * @throws EmailException
     * @throws MalformedURLException
     */
    public static void sendEmailsWithOnlineAttachments() throws EmailException, MalformedURLException {
        EmailAttachment attachment = new EmailAttachment();
        //設置在線資源路徑,和上傳本地附件的唯一區別
        attachment.setURL(new URL("http://www.apache.org/images/asf_logo_wide.gif"));
        attachment.setDisposition(EmailAttachment.ATTACHMENT);
        attachment.setDescription("Apache logo");
        attachment.setName("Apache logo.gif");

        MultiPartEmail email = new MultiPartEmail();
        email.setHostName("smtp.163.com");
        email.setAuthenticator(new DefaultAuthenticator("myemailaddress@163.com", "myshouquanma"));
        email.setSSLOnConnect(true);
        email.addTo("receiveemail@qq.com", "Conti Zhang");
        email.setFrom("myemailaddress@163.com", "Me");
        email.setSubject("The logo");
        email.setMsg("Here is Apache's logo");
        email.attach(attachment);
        email.send();
    }

 

4.發送內容為HTML格式的郵件,有些郵件直接打開就是一個HTML頁面。雖然不一定用到,可以了解一下

   /**
     * @describe 發送內容為HTML格式的郵件
     * @throws EmailException
     * @throws MalformedURLException
     */
    public static void sendHTMLFormattedEmail() throws EmailException, MalformedURLException {
        // 這裏需要使用HtmlEmail創建一個email對象
        HtmlEmail email = new HtmlEmail();
        email.setHostName("smtp.163.com");
        email.setAuthenticator(new DefaultAuthenticator("myemailaddresss@163.com", "myshouquanma"));
        email.addTo("receiveemail@qq.com", "Conti Zhang");
        email.setFrom("myemailaddress@163.com", "Me");
        email.setSubject("Test email with inline image");

        // 嵌入圖像並獲取內容id,雖然案例這樣寫,但我感覺直接在html內容裏面寫圖片網絡地址也可以
        URL url = new URL("http://www.apache.org/images/asf_logo_wide.gif");
        String cid = email.embed(url, "Apache logo");

        // 設置html內容
        email.setHtmlMsg("<html>The apache logo - <img src=\"cid:" + cid + "\"></html>");

        // 設置替代內容,如果不支持html
        email.setTextMsg("你的郵件客戶端不支持html郵件");
        email.send();
    }

 

5.發送內容為HTML格式的郵件(嵌入圖片更方便)

這裏用到了DataSourceFileResolver對象,和DataSourceUrlResolver對象,前者可以解析本地文件路徑,後者可以解析網絡路徑

具體用法如下

    /**
     * @describe 發送內容為HTML格式的郵件(嵌入圖片更方便)
     * @throws MalformedURLException
     * @throws EmailException
     */
    public static void sendHTMLFormattedEmailWithEmbeddedImages() throws MalformedURLException, EmailException {
        //html郵件模板
String htmlEmailTemplate = "<img src=\"http://www.conti.com/images/1.jpg\">"; DataSourceResolver[] dataSourceResolvers =new DataSourceResolver[]{new DataSourceFileResolver(),new DataSourceUrlResolver(new URL("http://"))}; email.setDataSourceResolver(new DataSourceCompositeResolver(dataSourceResolvers)); email.setHostName("smtp.qq.com"); email.setAuthenticator(new DefaultAuthenticator("myemailaddress@qq.com", "myshouquanma")); email.addTo("receiveemail@qq.com", "Conti Zhang"); email.setFrom("myemailaddress@qq.com", "Me"); email.setSubject("Test email with inline image"); email.setHtmlMsg(htmlEmailTemplate); email.setTextMsg("你的郵件客戶端不支持html郵件"); email.send(); }

此種方式可能會報錯,會被郵箱認為是有害郵件不接收而導致發送失敗。解決方法就是,網易郵箱不行就換QQ郵箱,我就是這樣做的

 

好了,就這麼多,歡迎討論!

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

VSCode + WSL 2 + Ruby環境搭建詳解

vscode配置ruby開發環境

vscode近年來發展迅速,幾乎在3年之間就搶佔了原來vim、sublime text的很多份額,猶記得在2015-2016年的時候,ruby推薦的開發環境基本上都是vim和sublime text,然而,隨着vscode的發展,vscode下ruby的開發體驗已經非常不錯。現在基本上使用win 10 wsl2 + vscode + windows terminal的體驗已經不遜於mac + vim (sublime) + item 2的體驗了

總體步驟

使用win10專業版配置ruby開發環境大致分為以下幾步:

  1. 開啟win10 wsl功能
  2. 升級wsl2
  3. 安裝ubuntu
  4. 安裝ruby(rvm)
  5. 安裝vscode
  6. 安裝vscode wsl擴展
  7. 安裝vscode ruby相關擴展

經過以上7步就可以開始愉悅的ruby開發了,再開始之前,可以先看個效果圖。

1. 開啟win10 wsl功能

ruby對Linux和Mac比較友好,在windows下很多第三方庫要配合mingw或msys2才能安裝,不過好在windows 10提供了Linux子系統,在win10 2004版本中wsl也升級到了wsl2,速度更快,功能更完善。

要使用wsl2需要先在控制面板中開啟wsl功能:

  • 適用於Linux的Windows子系統
  • 虛擬機平台

2. 升級wsl2

目前wsl2還需要安裝一個內核升級包,具體可參考微軟說明:

  • wsl2安裝說明
  • wsl2 update包

更新包安裝完成后,輸入命令

wsl --set-default-version 2

3. 安裝Ubuntu

在微軟應用商店安裝Ubuntu,當前Ubuntu版本為20.04 LTS

安裝完成以後,配置Ubuntu默認為wsl2

# 查看
wsl --list --verbose

# 設置
wsl --set-version Ubuntu 2

4. 安裝ruby

在Linux下安裝ruby有多種方法,比較主流的方法是RVM,不過為了簡單起見,我直接通過ubuntu的apt工具進行了安裝。

關於RVM的安裝可參考如下網站:

  • RVM官網
  • RVM實用指南

通過APT安裝,輸入下列命令即可

sudo apt install ruby ruby-dev ri ruby-bundle

安裝完成以後需要配置gem國內鏡像,參考如下網址:

  • gem中文鏡像

輸入下列命令

# 設置gem source
gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/

# 查看gem source
gem sources -l

# 設置bundle
bundle config mirror.https://rubygems.org https://gems.ruby-china.com

5. 安裝vscode

vscode直接在官網下載安裝即可,這裏我選擇了System Installer

  • vscode官網下載頁面

6. 安裝vscode wsl擴展

vscode安裝完成以後,可以在plugin中找到Remote – WSL擴展,點擊安裝即可

7. 安裝vscode ruby相關擴展

直接在plugin中搜索ruby在wsl中安裝下列五個擴展即可

  • Peng Lv/Ruby
  • Castwide/Ruby Solargraph(Language Server)
  • misogi/ruby-rubocop(Lint)
  • Simple Ruby ERB
  • endwise

其中,ruby solargraphrubocop除了安裝擴展,還需要通過gem安裝第三方包

sudo gem install rubocop
sudo gem install solargraph

重新加載vscode-wsl就可以愉快的使用ruby language進行開發了

vscode使用

在使用上基本只要require了相應的庫,就solargraph就會對require的庫中涉及的類和模塊進行提示,非常方便。唯一有問題的地方就是require的時候沒有提示,這可能就需要自己記一下庫的名稱,不過相比於原來已經好太多了,應該說在可以接受的範圍內。

1. 如果安裝了新的第三方庫會提示嗎?

如果安裝了sinatra這樣的庫,vscode-ruby如何給出提示呢?只需要Ctrl + Shift + P,選擇solargraph: build new gem documention即可

2. rubocop如何使用?

rubocop是一個Ruby Lint工具,可以進行Ruby代碼風格檢查,並能夠自動修復,只需要Ctrl + Shift + P,選擇Ruby: autocorrect by rubocop即可

3. 常用類型註釋

ruby是動態強類型語言,由於不需要指定函數返回值類型,這導致IDE無法自動推斷一些變量的類型。目前Python、PHP、TypeScript都在不斷的強化類型以方便IDE進行靜態檢查。IDE只有在知道類型的情況下才能準確地進行智能提示。

在ruby 2當中,我們可以通過類型註釋的方式增強IDE推斷能力。常見的類型註釋可參考YARD項目

下面代碼給出了一些示例。

require 'socket'

server = TCPServer.new 2000
loop do
  # 代碼塊參數類型註釋
  # @param {TCPSocket} client
  Thread.start(server.accept) do |client|
    client.puts 'hello !'
    client.puts "Time is #{Time.now}"
    client.close
  end
end

server = TCPServer.new 2000
loop do
  # 變量註釋
  # @type {TCPSocket} client
  client = server.accept
end

# 函數參數和返回值註釋,數組類型
# @param {Array(Integer)} nums
# @param {Integer} target
# @return {Array(Integer)}
def two_sum(nums, target)
  hash_nums = {}
  result = []
  nums.each_with_index do |num, index|
    hash_nums[num] = index
  end

  nums.each_with_index do |num, index|
    another = target - num
    if hash_nums[another] && hash_nums[another] != index
      result.push(index, hash_nums[another])
      break
    end
  end

  result
end

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

元晶維持獲利 第一季每股純益 0.11 元

今年第 1 季太陽能市況依然不佳,元晶在產能去瓶頸化,固定成本有效降低,營運仍維持獲利狀態,稅後淨利 3,643 萬元,每股純益 0.11 元。   元晶近年積極拓展高效產品市場,去年雖因美國雙反調查影響市場需求降溫,產品價格下滑,獲利縮水,不過,元晶去年仍有獲利,稅後淨利 1.7 億元,每股純益 0.56 元。   元晶目前太陽能電池年產能 720MW 規模,預計異質接面 (HiT) 高效太陽能電池技術達量產水準後,展開新一波擴產計畫,初步規劃 2017 年產能將倍增至 1,500MW 規模。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

力挺能源轉型 全球最大開發銀行擬2020撤資化石燃料

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

菲律賓巴丹群島群島 規模5以上地震連三起

摘錄自2019年7月27日新唐人亞太台、自由時報報導

27日清晨,菲律賓最北邊的巴丹群島接連發生三起規模五以上的地震,根據美國地質調查所數據,三起地震的規模分別為5.4、5.9和5.7,震源深度僅9.1公里,屬於極淺層地震;菲律賓地震局表示第二次地震規模為6.4。當地許多建築物倒塌,現全島因電力線的損壞處於停電狀態,目前估計已造成5人死亡,9人輕重傷。

當局沒有發布海嘯警報。目前已知至少8人死亡,60人受傷。事發之後,民眾住家內的物品散亂一地。還有居民從瓦礫堆當中搜救生還者。

巴丹群島位於台灣和呂宋島之間,菲律賓當局也派遣醫療救難隊前往巴丹群島。



菲律賓最北方巴丹群(Batanes)今日清晨發生連續兩次規模5以上的地震。(推特 )

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

低噪音無環境污染,全球首輛商業氫燃料列車駛入德國

氫燃料電池目前已應用在汽車、公車甚至是列車中,為備受看好的化石燃料替代品,各國也紛紛將氫燃料交通運輸納入未來藍圖,像是全球首輛商業運行氫燃料列車已在德國漢諾威上路,除了為氫燃料列車立下新里程碑,也讓德國離減碳目標更進一步。

該氫燃料列車名為 CORADIA iLINT,由法商阿爾斯通(Alstom)一手開發,於 2016年首次亮相德國柏林 InnoTrans 貿易展,2017 年 3 月更在德國完成首航測試,漢諾威當地鐵路公司 LNVG 於在同年 9 月斥資 8,100 萬歐元訂購 14 輛 CORADIA iLINT。

目前德國漢諾威民眾已經可在下薩克森邦(Lower Saxony)鐵路系統乘坐世界第一批、共 2 輛氫燃料列車,預計 14 輛 CORADIA iLINT 將於 2021 年全面上路,並行駛於庫克斯港、不萊梅港、布雷梅爾弗爾德、布克斯特胡德之間的鐵路線。

CORADIA iLINT 可乘載 300 人,最高時速 140 公里,續航距離與柴油列車相同為 1,000 公里,每組氫燃料槽重達 94 公斤,而雖然氫燃料列車成本較高,但行駛途中只會排出水,沒有排放廢氣與二氧化碳問題,預估可在 10 年左右達成損益兩平。

圖片來源:

該氫燃料列車外觀跟傳統列車相差無幾,如果不是車體外觀滿滿的 H 與 O 化學鍵符號,乍看之下就是一般的藍色列車,但其實其中暗藏玄機,每節車輛頂部都設有氫燃料箱與氧燃料箱,透過燃料電池來產生電力,最後再由車底部的鋰離子電池驅動火車,多餘電力也可儲存起來增加能源使用效率。

該列車還搭載智慧電源管理系統,燃料電池會在列車行駛時穩定供應電力,而列車煞車或是停止時,會立刻停止燃料電池轉換,可節省氫氣消耗量。阿爾斯通開發人員 Jens Sprotte 表示,與傳統柴油火車相比,CORADIA iLINT 噪音降低 60%,乘客只會聽到車輪與風的聲音。

目前德國約有 40% 鐵路未實現電氣化,若是可將 4,000 輛柴油列車全部汰換成氫燃料電池列車,除了能減少 45% 排碳量,還可以免除鐵路電氣化所需的電纜、配電成本。

不過氫燃料列車也有許多挑戰待克服,由於氫燃料技術尚未成熟,能量轉換率還不高、最高時速只能達到每小時 140 公里,且氫氣製程仍無法擺脫化石燃料,純氫氣需要加工才可獲得,阿爾斯通公司表示,計劃之後使用下薩克森邦龐大風力發電系統的電力來製造所需的氫燃料。

德國推行節能減碳與再生能源不遺餘力,計劃在 2030 將再生能源發電比例提升至 65%,而阿爾斯通也有其雄心勃勃目標,望可在在未來 5-20 年間汰換掉德國所有的柴油列車,讓德國鐵路朝零碳邁進。

(首圖來源:。文/DaisyChuang)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

抗暖化不遺餘力,丹麥 2030 年將禁售汽柴油車

丹麥首相拉斯穆森(Lars Løkke Rasmussen)日前向國會提案,計劃在 2030 年禁售汽柴油車,並同時達成 100 萬輛電動車與油電混合車販售目標,盼可藉此響應巴黎協議,進一步展現緩解氣候變遷的決心。

拉斯穆森指出,「這是一個具有挑戰性的目標,但這正是我們需要嘗試的原因。」,丹麥將在短短 12 年內禁止銷售新汽柴油車,而在 17 年內新販售車輛也都得是電動車與其他零排放車款。顯示混合動力車也有可能在 2035 年逐步停止販售。

丹麥可說是全球能源轉型先鋒,將於2020年把碳排放量降到1990年的66%,更設定在 2030 年將再生能源發電佔比提升至 55%,2050 年達成發電、交通、供暖製冷全再生能源供應。為了達路上零排放,丹麥也在電動車充電站、重工業氫氣與天然氣設備撥出 7,000 萬丹麥克朗(約 3.3 億新台幣)補助。

不過該提案尚未獲得國會批准,詳細內容則會在下週公布,若要讓國會通過新法案,丹麥首先得面臨是否要調整電動車補貼政策等難題。

為解決空氣污染與提升電動車購買率,各國通常會以補貼或是減稅等措施提供購買誘因,像是丹麥原先透過免除最高達 180% 進口稅來刺激電動車買氣,只不過由於受到傳統車廠抗議,丹麥於 2016 年調整補貼政策,此舉導致該國電動車購買量雪崩式下滑。

以電動車銷售為例,2015 年有高達 4,762 輛電動車登記銷售,但政策公布後,該數據在短短幾年內下降到 1,428,最後在跌至 1,000 以下、僅售出 913 輛,丹麥 2017年僅賣出 500 輛電動車,對此拉斯穆森在 2018 年 5 月也表示,由於電動車銷售量急遽下滑,政府已考慮是否調整電動車補貼政策,也不排除提高獎勵金額。

在節能減碳與環保世界趨勢下,目前各國紛紛設立禁售汽柴油車時間表,其中由挪威率先吹響號角,於 2016 年設定 2025 年禁售汽柴油車,之後德國、愛爾蘭、印度與荷蘭等國也計劃在 2030 年達標,法國、英國、台灣則預計在 2040 年達成路上零排放,希望可藉由禁售燃油車規範達成改善空氣污染、減緩全球暖化等目的。

彭博能源財經(BNEF)也預估,2020~2030 年電動車價格將可與傳統汽車相當,2030 年全球電動車銷售量有望突破 3,000 萬輛。

(首圖來源: CC BY-SA 2.0。文/DaisyChuang)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

MySQL多版本併發控制機制(MVCC)-源碼淺析

MySQL多版本併發控制機制(MVCC)-源碼淺析

前言

作為一個數據庫愛好者,自己動手寫過簡單的SQL解析器以及存儲引擎,但感覺還是不夠過癮。<<事務處理-概念與技術>>誠然講的非常透徹,但只能提綱挈領,不能讓你玩轉某個真正的數據庫。感謝cmake,能夠讓我在mac上用xcode去debug MySQL,從而能去領略它的各種實現細節。
筆者一直對數據庫的隔離性很好奇,此篇博客就是我debug MySQL過程中的偶有所得。
(注:本文的MySQL採用的是MySQL-5.6.35版本)

MVCC(多版本併發控制機制)

隔離性也可以被稱作併發控制、可串行化等。談到併發控制首先想到的就是鎖,MySQL通過使用兩階段鎖的方式實現了更新的可串行化,同時為了加速查詢性能,採用了MVCC(Multi Version Concurrency Control)的機制,使得不用鎖也可以獲取一致性的版本。

Repeatable Read

MySQL的通過MVCC以及(Next-Key Lock)實現了可重複讀(Repeatable Read),其思想(MVCC)就是記錄數據的版本變遷,通過精巧的選擇不同數據的版本從而能夠對用戶呈現一致的結果。如下圖所示:

上圖中,(A=50|B=50)的初始版本為1。
1.事務t1在select A時候看到的版本為1,即A=50
2.事務t2對A和B的修改將版本升級為2,即A=0,B=100
3.事務t1再此select B的時候看到的版本還是1, 即B=50
這樣就隔離了版本的影響,A+B始終為100。

Read Commit

而如果不通過版本控制機制,而是讀到最近提交的結果的話,則隔離級別是read commit,如下圖所示:

在這種情況下,就需要使用鎖機制(例如select for update)將此A,B記錄鎖住,從而獲得正確的一致結果,如下圖所示:

MVCC的優勢

當我們要對一些數據做一些只讀操作來檢查一致性,例如檢查賬務是否對齊的操作時候,並不希望加上對性能損耗很大的鎖。這時候MVCC的一致性版本就有很大的優勢了。

MVCC(實現機制)

本節就開始談談MVCC的實現機制,注意MVCC僅僅在純select時有效(不包括select for update,lock in share mode等加鎖操作,以及update\insert等)。

select運行棧

首先我們追蹤一下一條普通的查詢sql在mysql源碼中的運行過程,sql為(select * from test);

其運行棧為:

handle_one_connection  MySQL的網絡模型是one request one thread
 |-do_handle_one_connection
	|-do_command
		|-dispatch_command
			|-mysql_parse	解析SQL
				|-mysql_execute_command
					|-execute_sqlcom_select	執行select語句
						|-handle_select
							...一堆parse join 等的操作,當前並不關心
							|-*tab->read_record.read_record 讀取記錄

由於mysql默認隔離級別是repeatable_read(RR),所以read_record重載為
rr_sequential(當前我們並不關心select通過index掃描出row之後再通過condition過濾的過程)。繼續追蹤:

read_record
 |-rr_sequential
	|-ha_rnd_next
		|-ha_innobase::rnd_next 這邊就已經到了innodb引擎了
			|-general_fetch
				|-row_search_for_mysql
					|-lock_clust_rec_cons_read_sees 這邊就是判斷並選擇版本的地方

讓我們看下該函數內部:

bool lock_clust_rec_cons_read_sees(const rec_t* rec /*由innodb掃描出來的一行*/,....){
	...
	// 從當前掃描的行中獲取其最後修改的版本trx_id(事務id)
	trx_id = row_get_rec_trx_id(rec, index, offsets);
	// 通過參數(一致性快照視圖和事務id)決定看到的行快照
	return(read_view_sees_trx_id(view, trx_id));
}

read_view的創建過程

我們先關注一致性視圖的創建過程,我們先看下read_view結構:

struct read_view_t{
	// 由於是逆序排列,所以low/up有所顛倒
	// 能看到當前行版本的高水位標識,>= low_limit_id皆不能看見
	trx_id_t	low_limit_id;
	// 能看到當前行版本的低水位標識,< up_limit_id皆能看見
	trx_id_t	up_limit_id;
	// 當前活躍事務(即未提交的事務)的數量
	ulint		n_trx_ids;
	// 以逆序排列的當前獲取活躍事務id的數組
	// 其up_limit_id<tx_id<low_limit_id
	trx_id_t*	trx_ids;	
	// 創建當前視圖的事務id
	trx_id_t	creator_trx_id;
	// 事務系統中的一致性視圖鏈表
	UT_LIST_NODE_T(read_view_t) view_list;
};

然後通過debug,發現創建read_view結構也是在上述的rr_sequential中操作的,繼續跟蹤調用棧:

rr_sequential
 |-ha_rnd_next
 	|-rnd_next
 		|-index_first 在start_of_scan為true時候走當前分支index_first
 			|-index_read
 				|-row_search_for_mysql
 					|-trx_assign_read_view

我們看下row_search_for_mysql里的一個分支:

row_search_for_mysql:
// 這邊只有select不加鎖模式的時候才會創建一致性視圖
else if (prebuilt->select_lock_type == LOCK_NONE) {		// 創建一致性視圖
		trx_assign_read_view(trx);
		prebuilt->sql_stat_start = FALSE;
}

上面的註釋就是select for update(in share model)不會走MVCC的原因。讓我們進一步分析trx_assign_read_view函數:

trx_assign_read_view
 |-read_view_open_now
 	|-read_view_open_now_low

好了,終於到了創建read_view的主要階段,主要過程如下圖所示:

代碼過程為:

static read_view_t* read_view_open_now_low(trx_id_t	cr_trx_id,mem_heap_t*	heap)
{
	read_view_t*	view;
	// 當前事務系統中max_trx_id(即尚未被分配的trx_id)設置為low_limit_no
	view->low_limit_no = trx_sys->max_trx_id;
	view->low_limit_id = view->low_limit_no;
	// CreateView構造函數,會將非當前事務和已經在內存中提交的事務給剔除,即判斷條件為
	// trx->id != m_view->creator_trx_id&& !trx_state_eq(trx, TRX_STATE_COMMITTED_IN_MEMORY)的
	// 才加入當前視圖列表
	ut_list_map(trx_sys->rw_trx_list, &trx_t::trx_list, CreateView(view));
	if (view->n_trx_ids > 0) {
		// 將當前事務系統中的最小id設置為up_limit_id,因為是逆序排列
		view->up_limit_id = view->trx_ids[view->n_trx_ids - 1];
	} else {
		// 如果當前沒有非當前事務之外的活躍事務,則設置為low_limit_id
		view->up_limit_id = view->low_limit_id;
	}
	// 忽略purge事務,purge時,當前事務id是0
	if (cr_trx_id > 0) {
		read_view_add(view);
	}
	// 返回一致性視圖
	return(view);
}

行版本可見性:

由上面的lock_clust_rec_cons_read_sees可知,行版本可見性由read_view_sees_trx_id函數判斷:

/*********************************************************************//**
Checks if a read view sees the specified transaction.
@return	true if sees */
UNIV_INLINE
bool
read_view_sees_trx_id(
/*==================*/
	const read_view_t*	view,	/*!< in: read view */
	trx_id_t		trx_id)	/*!< in: trx id */
{
	if (trx_id < view->up_limit_id) {

		return(true);
	} else if (trx_id >= view->low_limit_id) {

		return(false);
	} else {
		ulint	lower = 0;
		ulint	upper = view->n_trx_ids - 1;

		ut_a(view->n_trx_ids > 0);

		do {
			ulint		mid	= (lower + upper) >> 1;
			trx_id_t	mid_id	= view->trx_ids[mid];

			if (mid_id == trx_id) {
				return(FALSE);
			} else if (mid_id < trx_id) {
				if (mid > 0) {
					upper = mid - 1;
				} else {
					break;
				}
			} else {
				lower = mid + 1;
			}
		} while (lower <= upper);
	}

	return(true);
}

其實上述函數就是一個二分法,read_view其實保存的是當前活躍事務的所有事務id,如果當前行版本對應修改的事務id不在當前活躍事務裏面的話,就返回true,表示當前版本可見,否則就是不可見,如下圖所示。

接上述lock_clust_rec_cons_read_sees的返回:

if (UNIV_LIKELY(srv_force_recovery < 5)
			    && !lock_clust_rec_cons_read_sees(
				    rec, index, offsets, trx->read_view)){
	// 當前處理的是當前版本不可見的情況
	// 通過undolog來返回到一致的可見版本
	err = row_sel_build_prev_vers_for_mysql(
					trx->read_view, clust_index,
					prebuilt, rec, &offsets, &heap,
					&old_vers, &mtr);			    
} else{
	// 可見,然後返回
}

undolog搜索可見版本的過程

我們現在考察一下row_sel_build_prev_vers_for_mysql函數:

row_sel_build_prev_vers_for_mysql
 |-row_vers_build_for_consistent_read

主要是調用了row_ver_build_for_consistent_read方法返回可見版本:

dberr_t row_vers_build_for_consistent_read(...)
{
	......
	for(;;){
		err = trx_undo_prev_version_build(rec, mtr,version,index,*offsets, heap,&prev_version);
		......
		trx_id = row_get_rec_trx_id(prev_version, index, *offsets);
		// 如果當前row版本符合一致性視圖,則返回
		if (read_view_sees_trx_id(view, trx_id)) {
			......
			break;
		}
		// 如果當前row版本不符合,則繼續回溯上一個版本(回到for循環的地方)
		version = prev_version;
	}
	......
}

整個過程如下圖所示:

至於undolog怎麼恢復出對應版本的row記錄就又是一個複雜的過程了,由於篇幅原因,在此略過不表。

read_view創建時機再討論

在創建一致性視圖的row_search_for_mysql的代碼中

// 只有非鎖模式的select才創建一致性視圖
else if (prebuilt->select_lock_type == LOCK_NONE) {		// 創建一致性視圖
		trx_assign_read_view(trx);
		prebuilt->sql_stat_start = FALSE;
}

trx_assign_read_view中由這麼一段代碼

// 一致性視圖在一個事務只創建一次
if (!trx->read_view) {
		trx->read_view = read_view_open_now(
			trx->id, trx->global_read_view_heap);
		trx->global_read_view = trx->read_view;
	}

所以綜合這兩段代碼,即在一個事務中,只有第一次運行select(不加鎖)的時候才會創建一致性視圖,如下圖所示:

筆者構造了此種場景模擬過,確實如此。

MVCC和鎖的同時作用導致的一些現象

MySQL是通過MVCC和二階段鎖(2PL)來兼顧性能和一致性的,但是由於MySQL僅僅在select時候才創建一致性視圖,而在update等加鎖操作的時候並不做如此操作,所以就會產生一些詭異的現象。如下圖所示:

如果理解了update不走一致性視圖(read_view),而select走一致性視圖(read_view),就可以很好解釋這個現象。
如下圖所示:

總結

MySQL為了兼顧性能和ACID使用了大量複雜的機制,2PL(兩階段鎖)和MVCC就是其實現的典型。幸好可以通過xcode等IDE進行方便的debug,這樣就可以非常精確加便捷的追蹤其各種機制的實現。希望這篇文章能夠幫助到喜歡研究MySQL源碼的讀者們。

公眾號

關注筆者公眾號,獲取更多乾貨文章:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

一起玩轉微服務(3)——微服務架構設計模式

一、聚合器微服務設計模式

這是一種最常見也最簡單的設計模式,效果如下圖所示。
聚合器調用多個服務實現應用程序所需的功能。它可以是一個簡單的Web頁面,將檢索到的數據進行處理展示。它也可以是一個更高層次的組合微服務,對檢索到的數據增加業務邏輯後進一步發布成一個新的微服務,這符合DRY原則。另外,每個服務都有自己的緩存和數據庫。如果聚合器是一個組合服務,那麼它也有自己的緩存和數據庫。

二、代理微服務設計模式

這是聚合模式的一個變種,如下圖所示。
在這種情況下,客戶端並不聚合數據,但會根據業務需求的差別調用不同的微服務。代理可以僅僅委派請求,也可以進行數據轉換工作。每個微服務都有自己獨立的緩存和數據庫系統,彼此獨立。

三、鏈式微服務設計模式

這種模式在接收到請求後會產生一個經過合併的響應,如下圖所示:
在這種情況下,服務A接收到請求後會與服務B進行通信,類似地,服務B會同服務C進行通信。所有服務都使用同步消息傳遞。在整個鏈式調用完成之前,客戶端會一直阻塞。
因此,服務調用鏈不宜過長,以免客戶端長時間等待。

四、分支微服務設計模式

這種模式是聚合器模式的擴展,允許同時調用兩個微服務鏈,如下圖所示:
每個調用鏈分別調用自己的服務。當某個調用出現問題時,互相之間不會造成影響。

五、數據共享微服務設計模式

自治是微服務的設計原則之一,就是說微服務是全棧式服務。但在重構現有的“單體應用(monolithic application)”時,SQL數據庫反規範化可能會導致數據重複和不一致。
因此,在單體應用到微服務架構的過渡階段,可以使用這種設計模式,如下圖所示:
在這種情況下,部分微服務可能會共享緩存和數據庫存儲。不過,這隻有在兩個服務之間存在強耦合關係時才可以。對於基於微服務的新建應用程序而言,這是一種反模式。
 

六、異步消息傳遞微服務設計模式

雖然REST設計模式非常流行,但它是同步的,會造成阻塞。因此部分基於微服務的架構可能會選擇使用消息隊列代替REST請求/響應,如下圖所示:
各個服務之間通過異步的消息隊列進行交互,當服務出現問題時,不會造成阻塞,隊列會幫助緩存消息,直到消費服務開始工作。

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

考量經濟 梅克爾反對歐盟拉高減碳目標

摘錄自2018年8月27日中央社報導

德國總理梅克爾今天(27日)表示,世界各地極端天候頻傳,充分證明氣候變遷已是事實。不過她反對為保護氣候拉高減少溫室氣體排放門檻,指此舉沒太大意義。

路透社報導,北半球今夏的炙熱高溫,引發外界憂心氣候變遷的現象正加速發生,歐洲數十個國家都呼籲,減少溫室氣體排放量的速度,應要比原訂目標更快。

歐洲聯盟(EU)執委會主管氣候行動與能源事務執委卡尼特(Miguel Arias Canete)呼籲將2030年之前溫室氣體減量目標,從40%提高至45%。

但梅克爾(Angela Merkel)表示,加速減少有害的二氧化碳排放量恐適得其反,況且歐洲各國此際對達成原訂目標都很吃力,再要拉高門檻沒有道理。

她說:「我對這些新提議尤其不怎麼高興。我認為應先堅守既定目標。我不認為一直設定新目標有任何意義。」

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準