分類
發燒車訊

mingw32 exception在sjlj與dwarf差別-反彙編分析

sjlj (setjump/longjump)與dwarf-2為mingw32兩種異常處理模型的實現。sjlj有着開銷,而隨linux發行的mingw32開發庫包都是用sjlj版編譯的,而Qt卻採用dwarf-2版,那麼兩者之間有多少差異,本文就這問題對兩版的異常代碼的反彙編進行分析比較。

我使用mingw-w65-i686-810的sjlj與dwarf-2兩個版本對下面異常代碼編譯。

__attribute__((dllimport)) int dllfunc();
int main()
{
    dllfunc();
    //_create_locale(LC_ALL, "C");
    printf("abc");
    //return 0;

    try
    {
        try
        {
            throw std::exception();
        }
        catch(std::exception&)
      {
            std::rethrow_exception(std::current_exception());
      }
        
    }
    catch(int)
    {
        
    }
    catch(std::exception& e)
    {
        std::cout << e.what() << std::endl;
    }
    catch(...)
    {
        std::cout << "unknown" << std::endl;
    }
    return 0;
}

代碼邏輯:

兩層 try/catch,

1. 裡層 try/catch

1.1 try塊, throw 異常

1.2 catch塊, rethrow

2. 外層 try/catch

2.1 有三catch分支。

 

開刷前,先定義一下。

如果將 try/catch 去除 c++語言特性后,基本就是一種由c++庫還有c++編譯器共同管理的 goto。

throw相當於goto, catch相當於label(一種以類型區分的)。

那麼c++編譯器與c++庫為我們提供了什麼樣的管理呢?

c++編譯器

0. 利用c++支持對象析構進行try塊保護。

1. 將 throw 關鍵字生成彙編 call __cxa_throw,調用 c++庫的函數。

2. 為每個catch塊生成代碼片斷,只能通過jmp跳轉進來。

2.1 開頭 call __cxa_begin_catch。

2.2 結尾 call __cxa_end_catch。

2.3 最後跳出到 try/catch塊邏輯代碼的下條執行指令。

3. 為同一try/catch塊的所有catch塊產生分支控制代碼。

4. 為try塊的析構代碼產生跳轉入口。

5. 為每一層try/catch塊生成 uncaught 代碼塊,調用 _Unwind_Resume。

c++庫:

1. __cxa_throw,馬上_Unwind_RaiseException。跳轉到當前最裏面一層 try/catch的支路控制代碼片斷。

2. _Unwind_Resume,向上繼續展開。

3. std::rethrow_exception,調用 __gcclibcxx_demangle_callback,

3.1 要麼有 catch可達跳回到原來代碼的控制流,直接離開std::rethrow_exception的調用上下文。

3.2 要麼從__gcclibcxx_demangle_callback返回,執行terminate結束進程。

 

sjlj 版的反彙編代碼比 dwarf-2 版的多了50行。

先來看dwarf-2的反彙編代碼 

  1  <+0>:    lea    0x4(%esp),%ecx
  2  <+4>:    and    $0xfffffff0,%esp
  3  <+7>:    pushl  -0x4(%ecx)
  4  <+10>:    push   %ebp
  5  <+11>:    mov    %esp,%ebp
  6  <+13>:    push   %esi
  7  <+14>:    push   %ebx
  8  <+15>:    push   %ecx
  9  <+16>:    sub    $0x2c,%esp
 10  <+19>:    call   0x401890 <__main>
 11  <+24>:    mov    0x4071a4,%eax
 12  <+29>:    call   *%eax
 13  <+31>:    movl   $0x404045,(%esp)
 14  <+38>:    call   0x4027c4 <printf>
 15  <+43>:    movl   $0x4,(%esp)
 16  <+50>:    call   0x4017ac <__cxa_allocate_exception>
 17  <+55>:    mov    %eax,%ebx
 18  <+57>:    mov    %ebx,%ecx
 19  <+59>:    call   0x402890 <std::exception::exception()>
 20  <+64>:    movl   $0x4017d4,0x8(%esp)
 21  <+72>:    movl   $0x4042a8,0x4(%esp)
 22  <+80>:    mov    %ebx,(%esp)
 23  <+83>:    call   0x401794 <__cxa_throw>
 24  <+88>:    mov    $0x0,%eax
 25  <+93>:    jmp    0x401723 <main()+355>
 26  <+98>:    mov    %edx,%ecx
 27  <+100>:    cmp    $0x2,%ecx
 28  <+103>:    je     0x40162b <main()+107>
 29  <+105>:    jmp    0x401663 <main()+163>
 30  <+107>:    mov    %eax,(%esp)
 31  <+110>:    call   0x4017a4 <__cxa_begin_catch>
 32  <+115>:    mov    %eax,-0x1c(%ebp)
 33  <+118>:    lea    -0x28(%ebp),%eax
 34  <+121>:    mov    %eax,(%esp)
 35  <+124>:    call   0x4017cc <_ZSt17current_exceptionv>
 36  <+129>:    lea    -0x28(%ebp),%eax
 37  <+132>:    mov    %eax,(%esp)
 38  <+135>:    call   0x4017c4 <_ZSt17rethrow_exceptionNSt15__exception_ptr13exception_ptrE>
 39  <+140>:    mov    %eax,%esi
 40  <+142>:    mov    %edx,%ebx
 41  <+144>:    lea    -0x28(%ebp),%eax
 42  <+147>:    mov    %eax,%ecx
 43  <+149>:    call   0x4017ec <_ZNSt15__exception_ptr13exception_ptrD1Ev>
 44  <+154>:    call   0x40179c <__cxa_end_catch>
 45  <+159>:    mov    %esi,%eax
 46  <+161>:    mov    %ebx,%edx
 47  <+163>:    cmp    $0x1,%edx
 48  <+166>:    je     0x40166f <main()+175>
 49  <+168>:    cmp    $0x2,%edx
 50  <+171>:    je     0x401683 <main()+195>
 51  <+173>:    jmp    0x4016ca <main()+266>
 52  <+175>:    mov    %eax,(%esp)
 53  <+178>:    call   0x4017a4 <__cxa_begin_catch>
 54  <+183>:    mov    (%eax),%eax
 55  <+185>:    mov    %eax,-0x24(%ebp)
 56  <+188>:    call   0x40179c <__cxa_end_catch>
 57  <+193>:    jmp    0x401618 <main()+88>
 58  <+195>:    mov    %eax,(%esp)
 59  <+198>:    call   0x4017a4 <__cxa_begin_catch>
 60  <+203>:    mov    %eax,-0x20(%ebp)
 61  <+206>:    mov    -0x20(%ebp),%eax
 62  <+209>:    mov    (%eax),%eax
 63  <+211>:    add    $0x8,%eax
 64  <+214>:    mov    (%eax),%eax
 65  <+216>:    mov    -0x20(%ebp),%edx
 66  <+219>:    mov    %edx,%ecx
 67  <+221>:    call   *%eax
 68  <+223>:    mov    %eax,0x4(%esp)
 69  <+227>:    movl   $0x6ff07a00,(%esp)
 70  <+234>:    call   0x4017b4 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc>
 71  <+239>:    movl   $0x4017bc,(%esp)
 72  <+246>:    mov    %eax,%ecx
 73  <+248>:    call   0x4017f4 <_ZNSolsEPFRSoS_E>
 74  <+253>:    sub    $0x4,%esp
 75  <+256>:    call   0x40179c <__cxa_end_catch>
 76  <+261>:    jmp    0x401618 <main()+88>
 77  <+266>:    mov    %eax,(%esp)
 78  <+269>:    call   0x4017a4 <__cxa_begin_catch>
 79  <+274>:    movl   $0x404049,0x4(%esp)
 80  <+282>:    movl   $0x6ff07a00,(%esp)
 81  <+289>:    call   0x4017b4 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc>
 82  <+294>:    movl   $0x4017bc,(%esp)
 83  <+301>:    mov    %eax,%ecx
 84  <+303>:    call   0x4017f4 <_ZNSolsEPFRSoS_E>
 85  <+308>:    sub    $0x4,%esp
 86  <+311>:    call   0x40179c <__cxa_end_catch>
 87  <+316>:    jmp    0x401618 <main()+88>
 88  <+321>:    mov    %eax,%ebx
 89  <+323>:    call   0x40179c <__cxa_end_catch>
 90  <+328>:    mov    %ebx,%eax
 91  <+330>:    mov    %eax,(%esp)
 92  <+333>:    call   0x402770 <_Unwind_Resume>
 93  <+338>:    mov    %eax,%ebx
 94  <+340>:    call   0x40179c <__cxa_end_catch>
 95  <+345>:    mov    %ebx,%eax
 96  <+347>:    mov    %eax,(%esp)
 97  <+350>:    call   0x402770 <_Unwind_Resume>
 98  <+355>:    lea    -0xc(%ebp),%esp
 99  <+358>:    pop    %ecx
100  <+359>:    pop    %ebx
101  <+360>:    pop    %esi
102  <+361>:    pop    %ebp
103  <+362>:    lea    -0x4(%ecx),%esp
104  <+365>:    ret    

我們的主要代碼邏輯只有20-30條指令

 

 當 throw時,__cxa_throw函數是不會返回的, 如同goto最後是跳轉到他處,若被本層catch處理完才會跳轉回來<+88>。

然後看c++編譯器為我們生成的異常代碼 。

 

 

 

 

 

 對於沒有發生異常時,代碼執行路徑基本不會去涉及到異常代碼支路,開銷幾近為0,只是代碼量增大。

下面來看 sjlj 版的彙編代碼,

  1 function main():
  2  <+0>:    lea    0x4(%esp),%ecx
  3  <+4>:    and    $0xfffffff0,%esp
  4  <+7>:    pushl  -0x4(%ecx)
  5  <+10>:    push   %ebp
  6  <+11>:    mov    %esp,%ebp
  7  <+13>:    push   %edi
  8  <+14>:    push   %esi
  9  <+15>:    push   %ebx
 10  <+16>:    push   %ecx
 11  <+17>:    sub    $0x68,%esp
 12  <+20>:    movl   $0x4017ac,-0x44(%ebp)
 13  <+27>:    movl   $0x402958,-0x40(%ebp)
 14  <+34>:    lea    -0x3c(%ebp),%eax
 15  <+37>:    lea    -0x18(%ebp),%ebx
 16  <+40>:    mov    %ebx,(%eax)
 17  <+42>:    mov    $0x4015b4,%edx
 18  <+47>:    mov    %edx,0x4(%eax)
 19  <+50>:    mov    %esp,0x8(%eax)
 20  <+53>:    lea    -0x5c(%ebp),%eax
 21  <+56>:    mov    %eax,(%esp)
 22  <+59>:    call   0x402790 <_Unwind_SjLj_Register>
 23  <+64>:    call   0x4018b0 <__main>
 24  <+69>:    mov    0x406194,%eax
 25  <+74>:    movl   $0xffffffff,-0x58(%ebp)
 26  <+81>:    call   *%eax
 27  <+83>:    movl   $0x404001,(%esp)
 28  <+90>:    call   0x4027e4 <printf>
 29  <+95>:    movl   $0x4,(%esp)
 30  <+102>:    call   0x4017cc <__cxa_allocate_exception>
 31  <+107>:    mov    %eax,-0x60(%ebp)
 32  <+110>:    mov    %eax,%ecx
 33  <+112>:    call   0x4028b0 <std::exception::exception()>
 34  <+117>:    movl   $0x4017f4,0x8(%esp)
 35  <+125>:    movl   $0x404264,0x4(%esp)
 36  <+133>:    mov    -0x60(%ebp),%eax
 37  <+136>:    mov    %eax,(%esp)
 38  <+139>:    movl   $0x1,-0x58(%ebp)
 39  <+146>:    call   0x4017b4 <__cxa_throw>
 40  <+151>:    mov    $0x0,%eax
 41  <+156>:    mov    %eax,-0x60(%ebp)
 42  <+159>:    jmp    0x401733 <main()+547>
 43  <+164>:    lea    0x18(%ebp),%ebp
 44  <+167>:    mov    -0x54(%ebp),%edx
 45  <+170>:    mov    -0x50(%ebp),%ecx
 46  <+173>:    mov    -0x58(%ebp),%eax
 47  <+176>:    test   %eax,%eax
 48  <+178>:    je     0x4015e6 <main()+214>
 49  <+180>:    sub    $0x1,%eax
 50  <+183>:    test   %eax,%eax
 51  <+185>:    je     0x40161b <main()+267>
 52  <+187>:    sub    $0x1,%eax
 53  <+190>:    test   %eax,%eax
 54  <+192>:    je     0x4016f8 <main()+488>
 55  <+198>:    sub    $0x1,%eax
 56  <+201>:    test   %eax,%eax
 57  <+203>:    je     0x401712 <main()+514>
 58  <+209>:    sub    $0x1,%eax
 59  <+212>:    ud2    
 60  <+214>:    mov    %edx,%eax
 61  <+216>:    mov    %ecx,%edx
 62  <+218>:    mov    %edx,%ecx
 63  <+220>:    cmp    $0x2,%ecx
 64  <+223>:    je     0x4015f3 <main()+227>
 65  <+225>:    jmp    0x401642 <main()+306>
 66  <+227>:    mov    %eax,(%esp)
 67  <+230>:    call   0x4017c4 <__cxa_begin_catch>
 68  <+235>:    mov    %eax,-0x1c(%ebp)
 69  <+238>:    lea    -0x28(%ebp),%eax
 70  <+241>:    mov    %eax,(%esp)
 71  <+244>:    call   0x4017ec <_ZSt17current_exceptionv>
 72  <+249>:    lea    -0x28(%ebp),%eax
 73  <+252>:    mov    %eax,(%esp)
 74  <+255>:    movl   $0x2,-0x58(%ebp)
 75  <+262>:    call   0x4017e4 <_ZSt17rethrow_exceptionNSt15__exception_ptr13exception_ptrE>
 76  <+267>:    mov    %edx,-0x60(%ebp)
 77  <+270>:    mov    %ecx,-0x64(%ebp)
 78  <+273>:    lea    -0x28(%ebp),%eax
 79  <+276>:    mov    %eax,%ecx
 80  <+278>:    call   0x40180c <_ZNSt15__exception_ptr13exception_ptrD1Ev>
 81  <+283>:    mov    -0x60(%ebp),%eax
 82  <+286>:    mov    %eax,-0x60(%ebp)
 83  <+289>:    mov    -0x64(%ebp),%esi
 84  <+292>:    mov    %esi,-0x64(%ebp)
 85  <+295>:    call   0x4017bc <__cxa_end_catch>
 86  <+300>:    mov    -0x60(%ebp),%eax
 87  <+303>:    mov    -0x64(%ebp),%edx
 88  <+306>:    cmp    $0x1,%edx
 89  <+309>:    je     0x40164e <main()+318>
 90  <+311>:    cmp    $0x2,%edx
 91  <+314>:    je     0x401665 <main()+341>
 92  <+316>:    jmp    0x4016b3 <main()+419>
 93  <+318>:    mov    %eax,(%esp)
 94  <+321>:    call   0x4017c4 <__cxa_begin_catch>
 95  <+326>:    mov    (%eax),%eax
 96  <+328>:    mov    %eax,-0x20(%ebp)
 97  <+331>:    call   0x4017bc <__cxa_end_catch>
 98  <+336>:    jmp    0x4015a7 <main()+151>
 99  <+341>:    mov    %eax,(%esp)
100  <+344>:    call   0x4017c4 <__cxa_begin_catch>
101  <+349>:    mov    %eax,-0x24(%ebp)
102  <+352>:    mov    -0x24(%ebp),%eax
103  <+355>:    mov    (%eax),%eax
104  <+357>:    add    $0x8,%eax
105  <+360>:    mov    (%eax),%eax
106  <+362>:    mov    -0x24(%ebp),%edx
107  <+365>:    mov    %edx,%ecx
108  <+367>:    call   *%eax
109  <+369>:    mov    %eax,0x4(%esp)
110  <+373>:    movl   $0x6ff29a00,(%esp)
111  <+380>:    movl   $0x3,-0x58(%ebp)
112  <+387>:    call   0x4017d4 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc>
113  <+392>:    movl   $0x4017dc,(%esp)
114  <+399>:    mov    %eax,%ecx
115  <+401>:    call   0x401814 <_ZNSolsEPFRSoS_E>
116  <+406>:    sub    $0x4,%esp
117  <+409>:    call   0x4017bc <__cxa_end_catch>
118  <+414>:    jmp    0x4015a7 <main()+151>
119  <+419>:    mov    %eax,(%esp)
120  <+422>:    call   0x4017c4 <__cxa_begin_catch>
121  <+427>:    movl   $0x404005,0x4(%esp)
122  <+435>:    movl   $0x6ff29a00,(%esp)
123  <+442>:    movl   $0x4,-0x58(%ebp)
124  <+449>:    call   0x4017d4 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc>
125  <+454>:    movl   $0x4017dc,(%esp)
126  <+461>:    mov    %eax,%ecx
127  <+463>:    call   0x401814 <_ZNSolsEPFRSoS_E>
128  <+468>:    sub    $0x4,%esp
129  <+471>:    movl   $0xffffffff,-0x58(%ebp)
130  <+478>:    call   0x4017bc <__cxa_end_catch>
131  <+483>:    jmp    0x4015a7 <main()+151>
132  <+488>:    mov    %edx,-0x60(%ebp)
133  <+491>:    call   0x4017bc <__cxa_end_catch>
134  <+496>:    mov    -0x60(%ebp),%eax
135  <+499>:    mov    %eax,(%esp)
136  <+502>:    movl   $0xffffffff,-0x58(%ebp)
137  <+509>:    call   0x402788 <_Unwind_SjLj_Resume>
138  <+514>:    mov    %edx,-0x60(%ebp)
139  <+517>:    movl   $0x0,-0x58(%ebp)
140  <+524>:    call   0x4017bc <__cxa_end_catch>
141  <+529>:    mov    -0x60(%ebp),%eax
142  <+532>:    mov    %eax,(%esp)
143  <+535>:    movl   $0xffffffff,-0x58(%ebp)
144  <+542>:    call   0x402788 <_Unwind_SjLj_Resume>
145  <+547>:    lea    -0x5c(%ebp),%eax
146  <+550>:    mov    %eax,(%esp)
147  <+553>:    call   0x402780 <_Unwind_SjLj_Unregister>
148  <+558>:    mov    -0x60(%ebp),%eax
149  <+561>:    lea    -0x10(%ebp),%esp
150  <+564>:    pop    %ecx
151  <+565>:    pop    %ebx
152  <+566>:    pop    %esi
153  <+567>:    pop    %edi
154  <+568>:    pop    %ebp
155  <+569>:    lea    -0x4(%ecx),%esp
156  <+572>:    ret    

下面的分析只列出不同的地方 

 上圖的註釋有誤沒有勘誤過,lea是不訪問內存,通常代替add指令做加法,應該是6條指令要訪問內存。

支路控制代碼:

 

 

 

 

 可以看出,支路選路控制指令多而且複雜,還有就是跳轉多。

最後是函數結束前。

 

 

 

 可以看出在 sjlj 版本中,即使代碼不發生異常,函數在進入與離開時都要為登記維護付出一此成本,當涉及異常代碼時,支路選路控制更加複雜更多跳轉。這裡有一個成本比例,你的函數邏輯簡單,上面的開銷比重就越大,如果是頻繁調用的輕量函數就要考慮不用exception這樣的error handle。

還有就是當發生異常時,需要交給c++庫去管理,不同異常處理模型的實現,有着不同的開銷,本文並沒有涉及到。只是單純從c++庫以外的代碼進行分析,也足夠看出他們之間有着一定的差別。

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

分類
發燒車訊

使用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網頁設計為架站首選

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

※回頭車貨運收費標準

分類
發燒車訊

新能源汽車需求井噴 助推鋰材料超預期大漲

據中國汽車工業協會最新發佈數據,今年前6個月我國新能源汽車產量達到7.6萬輛,這一產量同比增幅達到2.5倍。   然而,新能源整車產量快速增長的同時,配套動力電池的產量卻出現缺口。“現在動力電池基本上只要能造出來,銷售出去的問題不大。”乘用車市場資訊聯席會秘書長崔東樹向記者表示,這不僅大大制約了新能源汽車產能的釋放,同時也影響了動力電池技術的進步,“大家都忙著造,很難有人沉下心來做研發。”   實際上,據專家介紹,新能源汽車電池在生產上的技術門檻並不高,這直接導致的是動力電池產能處於快速擴張當中。然而,大批量技術含量較低電池企業的投產,則可能讓國內電池產能由短缺轉向過剩。業內人士預計,隨著產能的快速實現,電池產業可能將在2016年下半年迎來洗牌。  
研發上與日韓有較大差距   “國內電池企業在自動化和研發能力上都與日韓企業有較大差距。”華霆動力技術有限公司的一位負責人向記者介紹,目前日韓企業在生產成本和技術上都整體領先於國內動力電池企業。   一位電池技術專家告訴記者,現階段國內動力電池企業的生產成本大約是2元每瓦時,按照容量為25千瓦時的動力電池計算,成本大約在5萬元左右。   這樣的成本明顯高於LG、三星等韓國動力電池生產企業。據介紹,韓企的成本已降至1.8元每瓦時以下,這意味著同樣是25千瓦時的動力電池,其成本將會低於4.5萬元。   不僅如此,國內電池企業的能量密度也低於日韓企業。上述電池技術專家介紹,國內較好的動力電池模組的能量密度在130瓦時每千克,而松下等日本企業生產動力電池模組的能量密度則能超過200瓦時每千克,LG、三星等韓國企業所生產動力電池也能達到180瓦時每千克左右。   這意味著,國內電池企業生產容量25千瓦時的電池重量將超過190千克,而同樣容量的電池,韓企生產出來的重量為140千克左右,部分日企則能達到125千克。這對於新能源整車的輕量化影響不小。   “目前,在動力電池領域,松下領先LG和三星12~18個月,而LG和三星則領先國內企業12~18個月。”國內某動力電池企業的負責人向記者坦言,“國內電池企業的自動化程度不高,研發和製造水準都趕不上。”   乘聯會資料顯示,國內新能源整車企業除比亞迪擁有自己的配套電池廠外,大多數都通過外採的方式解決電池問題。  
明年底行業恐面臨洗牌   “現在國內電池企業的狀態普遍很浮躁。”上述電池企業負責人向記者表示,由於新能源車企對配套電池的需求持續旺盛,電池企業對產能投入的熱情已大於對研發和技術的追求。 隨著近年來新能源汽車產銷量高速增長,汽車電池產量的缺口也逐步展現出來。這激發了配套電池企業的投產熱情。   一位動力電池公司負責人向記者介紹,僅LG、三星、力神和CATL四家動力電池企業,明年將投產的產能就高達10吉瓦時以上,而每吉瓦時的電池產能可以滿足大約4萬輛新能源汽車的需要,也就是說,僅上述四家動力電池企業的產能就可以滿足40萬輛新能源車的裝配需要。“動力電池的技術門檻並不高。”一位充電設施企業的負責人告訴記者,目前動力電池的核心技術已相對成熟,因此企業實現投產並不難,這造成很多實力並不強大的電池企業紛紛上專案。   不過,“國內主流的12家新能源整車企業的採購,基本上都來自5家主要的動力電池廠家。”一位電池企業負責人向記者表示,隨著具備技術優勢的大電池企業產能的跟上,在技術和成本上都不具備優勢的小企業將很難生存,因此他預測“2016年底電池企業將面臨洗牌”。   乘聯會的資料顯示,目前電池企業CATL主要供應北汽、廣汽、長安和宇通等新能源車企;天津力神主要供應江淮、康迪、廣汽和宇通等;國軒高科則供應康迪、江淮、金龍、安凱和申沃等車企;萬向億能則供應上汽、奇瑞、廣汽、青年等車企。   文章來源:中財網

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

分類
發燒車訊

元晶維持獲利 第一季每股純益 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網頁設計為架站首選

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

※回頭車貨運收費標準

分類
發燒車訊

澳洲新南威爾斯省 無尾熊2050年恐滅絕

摘錄自2018年09月08日蘋果日報澳洲報導

澳洲動物保護生物學家泰勒(Martin Taylor)周五(7日)提出一份報告,指出新南威爾士省如果繼續目前的土地清理工作,至2050年左右,當地的無尾熊恐面臨滅絕。

《澳洲新聞網》報導,這份報告由澳洲世界自然基金會(WFF)與自然保護委員會(NCC)發布,分析新南威爾士省北部衛星圖像,評估土地清理工作對瀕危物種的影響。泰勒指出,如果清理速度沒有減緩,將會對當地野生動物造成嚴重後果。「我們看到無尾熊的棲地正以驚人的速度消失,每個人都在告訴我們,無尾熊的數量正在下降。如果速度不變,本世紀中葉,新南威爾士省將沒有野生無尾熊。」泰勒的研究發現,自2016年至今,完全或部分被清理的土地面積幾乎增加了2倍,從2845公頃增加到8194公頃。

然而新南威爾士省政府對這項警告提出反駁,當地環境廳長辦公室聲明指控,澳洲世界自然基金會(WFF)與自然保護委員會(NCC)正在散布恐慌。並強調「政府承諾會為無尾熊提供更多自然棲地,並改善道路殺手問題,也已投入4500萬澳幣的大量經費。」政府也強調「無尾熊的數量事實上高於預期。」

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

分類
發燒車訊

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

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

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

【其他文章推薦】

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

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

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

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

※回頭車貨運收費標準

分類
發燒車訊

山葉 YAMAHA 與 Gogoro 㩗手合作!將用 Gogoro 電池交換結構在台推新車型

台灣知名電動機車品牌 Gogoro 將與日本著名機車品牌 YAMAHA 共同合作研發機車,此共同合作的車種將於 2019 年夏天發售,由台灣山葉機車發售,新車型將採用 Gogoro 的充換電結構與網路,Gogoro 這次與老牌機車廠合作無疑給其換電系統打了劑強心針,甚至有可能藉由 YAMAHA 之助在世界各地推動其系統,這個合作勢必也將對光陽(Kymco)與尚未選擇何種充換電系統的三陽(SYM)帶來更多壓力。

根據兩公司發出的資料顯示,該合作計畫的內容是有關電動機車的產品開發、委託製造以及電池交換系統的使用,雙方預計於今年內簽署正式合約。屆時將以 Gogoro 市面銷售的車款為基礎,進行 YAMAHA 品牌的電動機車設計,並委由 Gogoro 生產。所完成的車輛則交由台灣山葉機車的銷售通路進行銷售,預計 2019 年夏季將推出第一個車款。

同時,兩家公司的事業合作夥伴「住友商事株式會社」,在促成雙方合作上也扮演了重要的角色。

YAMAHA 自 1966 年起投入台灣機車市場,目前以台灣山葉機車所生產的機車為主,年間販賣 29 萬台(2017 年實績)。同時也製造、銷售電動機車 E-VINO,並輸出到日本。以開發機能來說,YAMAHA 在台灣設有 YAMAHA Motor R&D Taiwan,主要是負責台灣市場的速克達機車開發。此次和 Gogoro 的合作案,不只是想要在台灣市場擴充其包含燃油車種在內的產品種類,在電動車的部分,也想透過運用 Gogoro 所擁有的電池交換站網路,提高消費者使用上的便利性。

Gogoro 自 2015 年起投入台灣機車市場,以製造自有品牌的智慧雙輪和可方便交換電池的電池交換站,開展全新的電動機車商業模式。Gogoro 能源網路目前在台灣已設置超過 750 個電池交換站,預計 2019 年初將超過 1,000 站。並且,自產品上市以來,已達到 1,700 萬次以上的電池交換次數,透過此次的合作,Gogoro 可望擴大電動機車的產能。

YAMAHA 發動機株式會社 MC 事業本部長木下拓也表示:「此次和台灣 Gogoro 的合作,不只增加顧客對移動工具的選擇,同時透過共用先進的電池交換系統,將對新的移動工具服務和創造市場帶來挑戰。」

Gogoro 創辦人暨執行長陸學森表示:「Gogoro 為推動能源開放平台的創新品牌,並運用能源網路的建置來推進大型智慧城市的轉型。這次我們非常榮幸能與 YAMAHA 合作,向實現能源願景的目標邁出重要的一步。」

(合作媒體:。首圖來源:)

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

分類
發燒車訊

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

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

該氫燃料列車名為 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網頁設計為架站首選

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

※回頭車貨運收費標準

分類
發燒車訊

分佈式事務

分佈式事務

分佈式環境下的事務

要了解分佈式事務,首先要了解分佈式環境

分佈式

如一網站,訪問一個服務A(查詢自己用戶信息), 提供服務A的服務器分別有A1(上海)A2(廣州) A3(新加坡)

同一個服務分佈在三個區域的服務器上,這就是分佈式。你可以訪問 上海的服務器,廣州的或者新加坡的,但是

三個服務器之前通信是有延遲的,所以數據同步需要一定時間

分佈式中的問題

舉例一個分佈式場景

如果用戶Y個人信息 名字為 “南柯一夢” Y改為 “南柯夢”, 同一時間,Y用戶好友查看Y的名字,好友查詢的結果是”南柯一夢” 還是 “南柯夢” 這是分佈式系統常見的問題(數據修改發生在上海,訪問發生在新加坡)。

CAP原則

CAP原則又稱CAP定理,指的是在一個分佈式系統中,一致性(Consistency)、可用性(Availability)、分區容錯性(Partition tolerance)。CAP 原則指的是,這三個要素最多只能同時實現兩點,不可能三者兼顧。

參考文章
An Illustrated Proof of the CAP Theorem
CAP 定理的含義

以為網絡有延遲和不可測故障,因此分佈式系統是保持服務穩定的常用手段,但是分佈式因為服務機器分佈在不同地點,因此也會有分佈式的特點問題。

分佈式中 一致性 可用性 容災性 是三個指標

Consistency

數據一致性,分佈式環境下,不同地點的服務器,數據庫數據同步一致。

Availability

服務可用性,分佈式環境下,調用服務,都可用

Partition tolerance

分區容錯性,容災能力

分佈式環境多台服務器運行,其中一部分機器故障了,整個系統仍然可以正常運行提供服務

CAP不能同時滿足

必須滿足P

首先分佈式環境,系統需要穩定運行,一台服務器意外斷電,不應該影響系統整體功能正常,另一台或多台服務器還能穩定提供服務,所以分區容錯是必須要滿足。

滿足C

數據一致性,所指的是同一個服務所在不同服務器的數據是同步的。如上改名字的場景 南柯一夢 改為 南柯夢 (在上海的數據庫被修改) 那麼系統要做到滿足數據一致性,必須馬上同步廣州和新加坡的數據庫,這樣才能滿足廣州或者新加坡的訪問者獲得的結果也一致是 “南柯夢” 而不是”南柯一夢”

滿足A

服務可用性,指任何時候訪問服務,都返回結果

A與C是衝突的,上海服務器南柯一夢改為南柯夢后,為了服務可用,此時間訪問新加坡和廣州的服務器,返回的結果應該是南柯一夢(任何時候服務都返回結果) 但是嚴格上講,數據是錯誤的,因為用戶已經改了名字,改為南柯夢,但是數據在上海的才是正確的。

滿足數據一致性必須犧牲服務可用性 或者相反

要達到數據一致性的要求,必須在上海服務器修改數據的同時,同步廣州和新加坡的數據庫,並且在數據同步完成之前,訪問廣州和新加坡的數據庫中這條數據需要等待,返回同步后的結果(一致性)。

失去了服務可用性(這裏服務是等待數據同步完成才返回結果,而不是立刻返回)

因此CAP 要麼 滿足AP (分區服務可用)要麼 CP (分區數據一致)

分佈式中事務

商品購買中的事務

以商品購買生成訂單為例子

網絡上用戶A 購買 一雙鞋子 價格50 付款後生成消費訂單

事務中包含子的服務

這裏簡單設為三個服務,他們是事務相關的

1.商品信息服務

提供商品信息等服務

鞋子 顏色 價格 庫存數量等信息 這裏設 價格price為 50 庫存數 num 9

2.商家賬號收款服務

提供金額收入信息等服務

用戶購買鞋子,需要付款50元到商家賬號

3.用戶消費訂單服務

提供購買消費憑證信息等服務

首先分析用戶購買鞋子,三個服務分別要做什麼

@1 鞋子庫存減1

@2 商家賬號金額增加50

@3 生成 用戶購買鞋子的訂單記錄, 包括數量金額等信息

事務特性

原子性

@1 @2 @3 要麼同時發生,要麼都不發生

一致性

鞋子庫存減少1,收入增加50

隔離性

鞋子庫存減1,後續用戶最多只能購買(9-1=8)雙鞋子

持久性

動作執行成功后,訂單生效,收入新增50生效,庫存減1生效

上述三個服務他們可以在不同的地點,不同機器上部署的,並很常見。

保證數據正確

開啟事務

確定要執行的服務,每個服務的數據庫事務開啟

執行業務

調用庫存減1,轉賬,生成訂單等子服務

提交

業務執行過程中沒有意外,各子服務的數據庫提交事務,生效數據修改

回退

回退,如果服務調用出現了差錯,或者某個子服務執行失敗,可以通過回滾所有數據庫達到數據正確。

補償

某些情況下,某個子服務執行失敗,但是不影響整體業務,也可以提交事務,後續補償機制將失敗的子服務重新執行。

補償機制

個人認為就商品購買而言,補償機制多數情況可以使用且實用。(對強一致要求沒那麼高的情況下)

@1 庫存減1

@2 收入增加50

@ 3生成訂單記錄

如果這次執行的動作, 只有@3失敗,@1 @2成功 說明金額交易,商品庫存業務都沒問題,只是訂單記錄失敗,這是可以提交事務的,訂單錯誤可以生成一條記錄(攜帶商品,金額等信息),發送到MQ消息隊列(或者其他設計)通過消息隊列通知訂單相關服務,補償重新執行生成訂單,達到最終一致性。

分佈式事務控制問題

不同服務在不同區運行

不管是從安全性,穩定性,還是服務粒度細化方便維護等多因素考慮,都是很有必要讓不同的服務分開在不同服務區運行。

單體數據庫的事務不被支持,購買商品到生成訂單所有操作加起來算一個事務,涉及的數據在不同一服務(不同的數據庫),並且同一個服務可能運行在多台服務器上。

數據庫開啟事務針對的是單台服務器,多個服務多個數據庫,並不支持數據庫的事務,需要額外設計處理數據一致性問題(或者最終一致性)

同一個服務運行在多個區

不同服務不在一個服務器,同樣的,分佈式為穩定性可用而生,因此,一個服務大多有在多個區的服務器上運行,開啟事務的時候,如何保證事務開啟提交等事務相關命令每次發送到同一個區的同一個服務器,也是一定要考慮的問題。

分佈式事務處理方式

如上所述分佈式服務代表多個數據庫,不支持數據庫的事務,

如何保證事務中涉及的數據庫數據修改都提交生效或者都回滾。

建立控制中心

控制中心在執行業務時,統一發送開始事務的命令給三個服務,返回狀態

狀態沒問題執行數據修改,

都沒問題就發送給三個服務,提交事務,否在回滾事務

消息機制事務

MQ消息隊列,達到控制事務正確目的,項目中kafka聽的比較多,可在高併發環境下穩定運行,可以通過消息機制發送事務處理結果到子服務,子服務收到消息,通過分析消息內容,做出對應的操作,達到事務一致性或者最終一致性等目的
思考圖:

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

分類
發燒車訊

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網頁設計為架站首選

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

※回頭車貨運收費標準