杭州浩海教育諮詢有限公司張景舒做客《逐夢年代》_網頁設計公司

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

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

在教育國際化背景下,不少家長選擇讓孩子在學習母語之外,學習另一種語言以提高孩子的核心競爭力,英語作為普及率較高的語言之一成為了不少家長的選擇。而隨着家長教育思想的轉變、國家政策的支持,不少家長選擇讓孩子出國留學。在出國前,家長和孩子往往會遇到各式各樣的問題,憑個人能力無法準確拿捏。近日,《逐夢年代》欄目組有幸請到了這樣一位嘉賓,他的企業能夠對想要出國留學的孩子提供很大的幫助,一起來分享他的教育情懷。

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

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

本期訪談嘉賓張景舒,杭州浩海教育諮詢有限公司董事長、浩海聯合創始人,“你好-故事”公益組織的創始人。曾獲得詹姆斯格萊格獎(康奈爾大學工程學院最高獎),康奈爾大學“歐”創業競賽銀牌,麻省理工學院脫口秀第一名, 曾任職於沃帝華等知名量化對沖基金。橫跨文學、工程、商科三個領域,擁有豐富的知識、閱歷儲備。張景舒坦言,自身的留學經歷曾因為不靠譜的留學中介帶來了陰霾,高考的分數只夠申請密歇根大學,在自身的不斷努力之下,成功轉學到康奈爾大學,為了幫助更多有留學夢的師弟師妹,他毅然投入到這份事業中來,為學子們帶來最專業的留學諮詢。

浩海留學是一家專註於美英高端留學的教育諮詢機構。機構由美國哈佛大學、麻省理工學院、康奈爾大學、密歇根大學、牛津大學、劍橋大學等知名學府畢業生創立,旨在為廣大中國赴英美留學生提供高端且專業的留學諮詢服務。不同於傳統留學中介,浩海留學智創的“互聯網+共享顧問”新模式能夠給每一位學生提供同專業名校畢業且奮戰在該領域一線的專業導師,為其量身定製該專業的背景提升規劃,申請指導,文書創作,以及就業指導來幫助申請者以相同的分數獲得更好的錄取結果;同時,公司理念區別於一般機構的“包辦”模式,浩海秉承“全程協同”、“全程透明“的模式,用心讓每個孩子找到自己的價值,挖掘出自己的潛力。

文書作為出國留學的申請材料之一,一直以來處於重要地位,因為它不僅能向錄取院校從各個角度展現申請者的求學動機,學術學習能力,還是唯一可以和招生官直接溝通的機會。正是意識到了文書的重要性,張景舒開始致力於為學生量身定製,提供個性化的教育諮詢服務,幫助學生打造最優質的軟背景,讓他們在這個國際競技舞台上脫穎而出,圓夢名校。

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

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

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

梅三社區舉辦盆栽製作講座_台中搬家公司

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

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

入冬后,花謝凋零,綠恭弘=叶 恭弘枯萎,生活中少了盎然的綠色生機。在漫長的冬季與綠色相伴成為居民們對美好生活的需求。為滿足居民的這一願望,12月11日下午,江蘇省無錫市新吳區梅村街道梅荊三社區居委會邀請園藝師,給轄區盆景愛好者舉辦了一次盆栽製作及管理的技藝講座。

※推薦台中搬家公司優質服務,可到府估價

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

講授中,園藝老師詳細講解了製作盆景的技巧、所需的各種材料的選取,還傳授了盆栽植物的構景、具體的種植和管理方法。隨後,園藝老師把提前準備的製作材料一一分發給大家,指導大家將營養土放入盆中,並根據前低后高的原則,種植好網紋草,將土壓實,鋪上苔蘚和砂石,再插上兩個可愛的小擺件做裝飾。製作中,大家相互提示、相互交流,把對美好生活的嚮往和對綠色大自然的情感傾注於盆景製作中。經過大家的精妙構景和創意,一件件精美的盆栽應運而生。看到自己製作的盆景,大嫂們臉上露出了滿足的笑容,互相欣賞、拍照留念。

講座結束時,大嫂們猶味未盡,表示要到田野里採集宜冬種植的花草製作盆景,在隆冬的家裡享受綠色盎然的“春天”。

(吳建農  糜嘉怡

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

喜看南水潤津沽_網頁設計公司

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

“通水啦!我們喝到江水啦!”11月30日上午,天津武清區丁家酄村,村民張彥忠擰開水龍頭,看着清澈的水嘩嘩流出,話語中洋溢着喜悅。

  “使用南水后,原水水質常規監測大部分指標一直保持在地表水Ⅱ類標準及以上。自來水出廠水、管網水濁度明顯下降,市民飲用水口感顯著提升。”天津水務集團生產技術和信息部副部長季洪德說。

  2014年12月12日,南水北調中線工程正式通水。漢江水自丹江口水庫啟程奔騰北上,到達南水北調中線工程天津幹線終點——曹庄泵站,由此進入天津的千家萬戶。漢江水也成為繼上世紀80年代的引灤入津工程后,天津又一條城市供水生命線。

  5年間,中線工程連續不間斷安全供水,水質穩定達標,累計45億立方米的供水量,相當於一個滿庫容的密雲水庫水量。

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

  2018年起,天津啟動新一輪農村飲水提質增效工程,通過延伸自來水供水範圍、改擴建供水設施、改造老舊管網等措施,以優質自來水替代地下水源,全面提升2061個村、202.2萬人供水質量。目前,南水已覆蓋天津16個行政區中的14個,全市1000多萬居民受益。至明年工程全部完成后,絕大多數農村居民將喝上自來水。

  供水量持續增加,生態補水有底氣。天津南部的北大港濕地是東亞—澳大利亞候鳥遷徙的重要通道和驛站,每年冬季前後到此停歇和棲息的鳥類達上百萬隻。“截至目前總共觀測到鳥類276種,比2015年多了27種。”北大港濕地自然保護區管理中心野保科負責人孫洪義說。自去年10月啟動北大港水庫應急調水以來,北大港水庫累計入庫水量2.95億立方米,水庫蓄水量由干庫狀況增加至1.88億立方米,水面面積由不足30平方公里增加至119平方公里。

  2016年起,天津逐步實現對中心城區重點河道的常態化補水,年均生態補水10億立方米以上,進一步提高了河湖水系連通循環能力。

  截至2018年底,天津深層地下水開採量降至1.5億立方米,預計到2022年,全市地下水超採區基本實現零開採。《2018年天津市水資源公報》显示,2018年,平原淡水區淺層地下水年末存儲量比年初增加0.62億立方米。

  “南水來之不易,天津將落實好最嚴格水資源管理制度,深入推動節水型社會創建,讓清澈甘甜的江水持久造福津門大地。”天津市水務局局長張志頗說。

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

※想知道最厲害的網頁設計公司嚨底家"!

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

看了這篇,我確定你已經徹底搞懂Java的繼承了_台北網頁設計

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

有別於一般網頁架設公司,除了模組化的架站軟體,我們的營業主軸還包含:資料庫程式開發、網站建置、網頁設計、電子商務專案開發、系統整合、APP設計建置、專業網路行銷。

遇到認真的讀者是作者的一種幸運,真的,上一篇接口推送后,有好幾個讀者留言說,“二哥,你有一處內容需要修正,應該是接口中不能有 private 和 protected 修飾的方法。”說實話,看到這樣的留言,我內心是非常欣慰的,因為你投出去的一塊石頭在水面上激起了一串美麗的漣漪。

在 Java 中,一個類可以繼承另外一個類或者實現多個接口,我想這一點,大部分的讀者應該都知道了。還有一點,我不確定大家是否知道,就是一個接口也可以繼承另外一個接口,就像下面這樣:

public interface OneInterface extends Cloneable {
}

這樣做有什麼好處呢?我想有一部分讀者應該已經猜出來了,就是實現了 OneInterface 接口的類,也可以使用 Object.clone() 方法了。

public class TestInterface implements OneInterface {
    public static void main(String[] args) throws CloneNotSupportedException {
        TestInterface c1 = new TestInterface();
        TestInterface c2 = (TestInterface) c1.clone();
    }
}

除此之外,我們還可以在 OneInterface 接口中定義其他一些抽象方法(比如說深拷貝),使該接口擁有 Cloneable 所不具有的功能。

public interface OneInterface extends Cloneable {
    void deepClone();
}

看到了吧?這就是繼承的好處:子接口擁有了父接口的方法,使得子接口具有了父接口相同的行為;同時,子接口還可以在此基礎上自由發揮,添加屬於自己的行為

以上,把“接口”換成“類”,結論同樣成立。讓我們來定義一個普通的父類 Wanger:

public class Wanger {
    int age;
    String name;
    void write() {
        System.out.println("我寫了本《基督山伯爵》");
    }
}

然後,我們再來定義一個子類 Wangxiaoer,使用關鍵字 extends 來繼承父類 Wanger:

public class Wangxiaoer extends Wanger{
    @Override
    void write() {
        System.out.println("我寫了本《茶花女》");
    }
}

我們可以將通用的方法和成員變量放在父類中,達到代碼復用的目的;然後將特殊的方法和成員變量放在子類中,除此之外,子類還可以覆蓋父類的方法(比如write() 方法)。這樣,子類也就煥發出了新的生命力。

Java 只支持單一繼承,這一點,我在上一篇接口的文章中已經提到過了。如果一個類在定義的時候沒有使用 extends 關鍵字,那麼它隱式地繼承了 java.lang.Object 類——在我看來,這恐怕就是 Java 號稱萬物皆對象的真正原因了。

那究竟子類繼承了父類的什麼呢?

子類可以繼承父類的非 private 成員變量,為了驗證這一點,我們來看下面這個示例。

public class Wanger {
    String defaultName;
    private String privateName;
    public String publicName;
    protected String protectedName;
}

父類 Wanger 定義了四種類型的成員變量,缺省的 defaultName、私有的 privateName、共有的 publicName、受保護的 protectedName。

在子類 Wangxiaoer 中定義一個測試方法 testVariable()

可以確認,除了私有的 privateName,其他三種類型的成員變量都可以繼承到。

同理,子類可以繼承父類的非 private 方法,為了驗證這一點,我們來看下面這個示例。

public class Wanger {
    void write() {
    }

    private void privateWrite() {
    }

    public void publicWrite() {
    }

    protected void protectedWrite() {
    }
}

父類 Wanger 定義了四種類型的方法,缺省的 write、私有的 privateWrite()、共有的 publicWrite()、受保護的 protectedWrite()。

在子類 Wangxiaoer 中定義一個 main 方法,並使用 new 關鍵字新建一個子類對象:

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

日本、大陸,發現這些先進的國家已經早就讓電動車優先上路,而且先進國家空氣品質相當好,電動車節能減碳可以減少空污

可以確認,除了私有的 privateWrite(),其他三種類型的方法都可以繼承到。

不過,子類無法繼承父類的構造方法。如果父類的構造方法是帶有參數的,代碼如下所示:

public class Wanger {
    int age;
    String name;

    public Wanger(int age, String name) {
        this.age = age;
        this.name = name;
    }
}

則必須在子類的構造器中顯式地通過 super 關鍵字進行調用,否則編譯器將提示以下錯誤:

修復后的代碼如下所示:

public class Wangxiaoer extends Wanger{
    public Wangxiaoer(int age, String name) {
        super(age, name);
    }
}

is-a 是繼承的一個明顯特徵,就是說子類的對象引用類型可以是一個父類類型。

public class Wangxiaoer extends Wanger{
    public static void main(String[] args) {
        Wanger wangxiaoer = new Wangxiaoer();
    }
}

同理,子接口的實現類的對象引用類型也可以是一個父接口類型。

public interface OneInterface extends Cloneable {
}
public class TestInterface implements OneInterface {
    public static void main(String[] args) {
        Cloneable c1 = new TestInterface();
    }
}

儘管一個類只能繼承一個類,但一個類卻可以實現多個接口,這一點,我在上一篇文章也提到過了。另外,還有一點我也提到了,就是 Java 8 之後,接口中可以定義 default 方法,這很方便,但也帶來了新的問題:

如果一個類實現了多個接口,而這些接口中定義了相同簽名的 default 方法,那麼這個類就要重寫該方法,否則編譯無法通過。

FlyInterface 是一個會飛的接口,裏面有一個簽名為 sleep() 的默認方法:

public interface FlyInterface {
    void fly();
    default void sleep() {
        System.out.println("睡着飛");
    }
}

RunInterface 是一個會跑的接口,裏面也有一個簽名為 sleep() 的默認方法:

public interface RunInterface {
    void run();
    default void sleep() {
        System.out.println("睡着跑");
    }
}

Pig 類實現了 FlyInterface 和 RunInterface 兩個接口,但這時候編譯出錯了。

原本,default 方法就是為實現該接口而不覆蓋該方法的類提供默認實現的,現在,相同方法簽名的 sleep() 方法把編譯器搞懵逼了,只能重寫了。

public class Pig implements FlyInterfaceRunInterface {

    @Override
    public void fly() {
        System.out.println("會飛的豬");
    }

    @Override
    public void sleep() {
        System.out.println("只能重寫了");
    }

    @Override
    public void run() {
        System.out.println("會跑的豬");
    }
}

類雖然不能繼承多個類,但接口卻可以繼承多個接口,這一點,我不知道有沒有觸及到一些讀者的知識盲區。

public interface WalkInterface extends FlyInterface,RunInterface{
    void walk();
}

學到了吧?學到就是賺到。

我是沉默王二,一枚有趣的程序員。如果覺得文章對你有點幫助,請微信搜索「 沉默王二 」第一時間閱讀,回復【666】更有我為你精心準備的 500G 高清教學視頻(已分門別類)。

本文 GitHub 已經收錄,有大廠面試完整考點,歡迎 Star。

原創不易,莫要白票,請你為本文點個贊吧,這將是我寫作更多優質文章的最強動力。

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

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

有別於一般網頁架設公司,除了模組化的架站軟體,我們的營業主軸還包含:資料庫程式開發、網站建置、網頁設計、電子商務專案開發、系統整合、APP設計建置、專業網路行銷。

單例模式,反射破環?_包裝設計

※產品缺大量曝光嗎?你需要的是一流包裝設計!

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

餓漢式

// 餓漢式單例
public class Hungry {
    
    //構造器私有
    private Hungry(){

    }
    // 一上來就把這個類加載了
    private final static  Hungry HUNGRY = new Hungry();

    public static Hungry getInstance(){
        return HUNGRY;
    }
}
// 餓漢式單例
public class Hungry {
    // 這4組數據非常耗內存資源,餓漢式一上來就把所有的內存裏面的東西全部加載進來了,就存在這個空間
    // 但這個空間現在是沒有使用的,可能會造成浪費空間
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];
    private byte[] data3 = new byte[1024*1024];
    private byte[] data4 = new byte[1024*1024];
    
    //構造器私有
    private Hungry(){

    }
    // 一上來就把這個類加載了
    private final static  Hungry HUNGRY = new Hungry();

    public static Hungry getInstance(){
        return HUNGRY;
    }

}

餓漢式單例可能會造成浪費空間,所以想要用的時候再去創建這個對象,平時就先放在這個地方,於是就出現了懶漢式!

懶漢式

// 懶漢式單例
public class LazyMan {
	
   // 構造器私有
    private LazyMan(){
       
    }

    private  static LazyMan lazyMan;

   
    public static LazyMan getInstance(){
       
        if (lazyMan==null){
            lazyMan = new LazyMan(); 
        }
        return lazyMan;
    }
}

它是有問題的,單線程下確實單例ok,多線程併發就會出現問題!

測試

// 懶漢式單例
public class LazyMan {
	
   // 構造器私有
    private LazyMan(){
       System.out.println(Thread.currentThread().getName()+":: ok");
    }

    private  static LazyMan lazyMan;

   
    public static LazyMan getInstance(){
       
        if (lazyMan==null){
            lazyMan = new LazyMan(); 
        }
        return lazyMan;
    }
    
    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                LazyMan.getInstance();
            }).start();
        }
    }
}

發現單例有問題,每次結果可能都不一樣!

解決

// 懶漢式單例
public class LazyMan {


    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+":: ok");
    }

    private  static LazyMan lazyMan;

    // 雙重檢測鎖模式的 懶漢式單例 DCL懶漢式
    public static LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan = new LazyMan(); 
                }
            }
        }
        return lazyMan;
    }

     public static void main(String[] args) {

         for (int i = 0; i < 10 ; i++) {
             new Thread(()->{
                LazyMan.getInstance();
             }).start();
         }
     }
}

但在極端情況下還是可能出現問題

經歷三個步驟:

1、 分配內存空間

2、 執行構造方法,初始化對象

3、 把這個對象指向這個空間

有可能會發生指令重排的操作!

比如,期望它執行 123 ,但是它真實可能執行132,比如第一個A線程過來執行了132,先分配空間再吧這個空間佔用了,佔用之後再去執行構造方法,如果現在突然來了個B線程,由於A已經指向這個空間了,它會以為這個 lazyMan 不等於 null ,直接return ,此時lazyMan還沒有完成構造,所以必須避免這個問題!

必須加上volatile

// 懶漢式單例
public class LazyMan {


    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+":: ok");
    }
	// 避免指令重排
    private volatile static LazyMan lazyMan;

    // 雙重檢測鎖模式的 懶漢式單例 DCL懶漢式
    public static LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan = new LazyMan(); 
                }
            }
        }
        return lazyMan;
    }

     public static void main(String[] args) {

         for (int i = 0; i < 10 ; i++) {
             new Thread(()->{
                LazyMan.getInstance();
             }).start();
         }
     }
}

靜態內部類

// 靜態內部類
public class Holder {
    private Holder(){

    }
    public static Holder getInstance(){
        return InnerClass.HOLDER;
    }
    public static  class InnerClass{
        private static final Holder HOLDER = new Holder();
    }
}

也是單例模式的一種,不安全!

單例不安全 反射

// 懶漢式單例
public class LazyMan {


    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+":: ok");
    }

    private volatile static LazyMan lazyMan;

    // 雙重檢測鎖模式的 懶漢式單例 DCL懶漢式
    public static LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan = new LazyMan(); //不是一個原子性操作
                }
            }
        }
        return lazyMan;
    }


    //反射
     public static void main(String[] args) throws Exception {
         LazyMan instance1 = LazyMan.getInstance();
         Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
         declaredConstructor.setAccessible(true); // 無視了私有的構造器
         // 通過反射創建對象
         LazyMan instance2 = declaredConstructor.newInstance();

         System.out.println(instance1);
         System.out.println(instance2);
     }
}

結論:反射可以破壞這種單例

解決

// 懶漢式單例
public class LazyMan {

    private LazyMan(){
        synchronized (LazyMan.class){
            if (lazyMan!=null){
                throw new RuntimeException("不要試圖使用反射破環 異常");
            }
        }
        System.out.println(Thread.currentThread().getName()+":: ok");
    }

    private volatile static LazyMan lazyMan;

    // 雙重檢測鎖模式的 懶漢式單例 DCL懶漢式
    public static LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan = new LazyMan(); //不是一個原子性操作
                }
            }
        }
        return lazyMan;
    }

    //反射
     public static void main(String[] args) throws Exception {
         LazyMan instance1 = LazyMan.getInstance();
         Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
         declaredConstructor.setAccessible(true); // 無視了私有的構造器
         // 通過反射創建對象
         LazyMan instance2 = declaredConstructor.newInstance();

         System.out.println(instance1);
         System.out.println(instance2);
     }
}

但是如果都用反射創建對象的情況下,還是會破環單例!

測試

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

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

解決

// 懶漢式單例
public class LazyMan {
      // 標誌位
    private static boolean abc = false;

    private LazyMan(){
        synchronized (LazyMan.class){
            if (abc==false){
                abc=true;
            }else {
                throw new RuntimeException("不要試圖使用反射破環 異常");
            }
          
        }
        System.out.println(Thread.currentThread().getName()+":: ok");
    }
    private volatile static LazyMan lazyMan;

    // 雙重檢測鎖模式的 懶漢式單例 DCL懶漢式
    public static LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan = new LazyMan(); //不是一個原子性操作
                }
            }
        }
        return lazyMan;
    }

    //反射
    public static void main(String[] args) throws Exception {
        //LazyMan instance1 = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true); // 無視了私有的構造器
        // 通過反射創建對象
        LazyMan instance2 = declaredConstructor.newInstance();
        LazyMan instance1 = declaredConstructor.newInstance();

        System.out.println(instance1);
        System.out.println(instance2);
    }
}

但是如果被人知道 abc這個變量,也可以破環!

單例又被破環了!

看一下源碼

它說不能使用反射破環枚舉,枚舉是jdk1.5出現的,自帶單例模式!

測試,寫一個枚舉類

// enum 本身就是一個class類
public enum EnumSingle {

    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

查看它的源碼

試圖破環!

// enum 本身就是一個class類
public enum EnumSingle {

    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}
class Test{
    public static void main(String[] args) throws Exception {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

它竟然說我現在的這個枚舉類中沒有空參構造器!

然後就去源碼里分析!

找到這個class文件!利用javap反編譯一下!

發現這個也显示有一個空參構造,證明這個也不對,用第三方的工具查看!

利用它再吧class文件生成java文件!

打開這個java文件

證明是idea和源碼騙了我!

再次嘗試破環!

// enum 本身就是一個class類
public enum EnumSingle {

    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}
class Test{
    public static void main(String[] args) throws Exception {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

結論:反射無法破環枚舉類!

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

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

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

(數據科學學習手札85)Python+Kepler.gl輕鬆製作酷炫路徑動畫_台中搬家

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

本文示例代碼、數據已上傳至我的Github倉庫https://github.com/CNFeffery/DataScienceStudyNotes

1 簡介

  Kepler.gl相信很多人都聽說過,作為Uber幾年前開源的交互式地理信息可視化工具,kepler.gl依託WebGL強大的圖形渲染能力,可以在瀏覽器端以多種形式輕鬆展示大規模數據集。

圖1

  更令人興奮地是Kepler.gl在去年推出了基於Python的接口庫keplergl,結合jupyter notebook/jupyter lab的相關拓展插件,使得我們可以通過編寫Python程序配合Kepler.gl更靈活地製作各種可視化作品。

圖2

  而隨着近期keplergl的更新,更多的新特性得以同步到其Python生態中,本文就將針對其中的路徑動畫的製作方法進行介紹。

2 基於keplergl的路徑動畫

  我們要製作的路徑動畫圖主要用於表現特定路徑上流的運動,譬如圖3的例子來自Kepler.gl官方示例:

圖3

  而在官方的說明中描述了要繪製路徑動畫需要輸入的數據格式:

圖4

  這是一個典型的GeoJSON格式LineString要素,特別的是其"coordinates"鍵對應的值不同於常規的[經度, 緯度]格式,而是代表着[經度, 緯度, 高度, 時間戳],其中高度非必要,可以設置為0,而時間戳則聲明了軌跡動畫在該時間點會到達的該點位置,即線要素上連續的點位置+時間戳定義了軌跡動畫的運動模式,下面我們分步驟來實現。

2.1 構造數據與初始化html

  這裏我們以重慶市渝中區的OSM路網為演示示例數據,首先我們需要利用json模塊來讀取本地重慶市渝中區_osm路網_道路.geojson數據:

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

from keplergl import KeplerGl
import json
import time

with open('geometry/重慶市渝中區_osm路網_道路.geojson') as g:
    raw_roads = json.load(g)

  隨便打印出其中包含的某個線要素:

圖5

  可以看到,這時線要素內部包含的點還是[經度, 緯度]的格式,接下來我們為其虛構上時間戳信息,為了保證整個路網可視化的協調一致,將所有線要素的時間跨度固定在一個小時之內,保證每段路上從頭到尾的軌跡動畫都保持一致:

start_time = time.mktime(time.strptime('2020-05-29 20:00:00', "%Y-%m-%d %H:%M:%S"))

for i in range(raw_roads['features'].__len__()):
    for j in range(raw_roads['features'][i]['geometry']['coordinates'].__len__()):
        
        shift_time = int((j / raw_roads['features'][i]['geometry']['coordinates'].__len__())*3600) # 更新當前對應的時間戳
        
        raw_roads['features'][i]['geometry']['coordinates'][j] \
            .extend([0, # 高度設置為0
                     int(start_time) + shift_time])

  接着再打印其中一個線要素:

圖6

  可以發現每個點元素都追加上高度0以及對應的時間戳(注意這裏的時間戳必須為整數否則之後輸入keplergl會報錯),接下來的過程就非常簡單,首先確保你已經安裝了keplergl以及對應插件,譬如我所使用的jupyter lab,在確保nodejs被安裝的前提下,使用jupyter labextension install @jupyter-widgets/jupyterlab-manager keplergl-jupyter安裝拓展插件(jupyter notebook可參考https://github.com/keplergl/kepler.gl/tree/master/bindings/kepler.gl-jupyter#installation),以及使用pip install keplergl來安裝keplergl庫,一切準備就緒直接運行如下代碼:

from keplergl import KeplerGl
# 生成KeplerGl對象s
map1 = KeplerGl(height=400, 
                data={'flow': raw_roads}) # data以圖層名為鍵,對應的矢量數據為值
map1
map1.save_to_html(file_name='渝中區.html') # 導出到本地可編輯html文件

圖7

  這一步的目的是初始化已嵌入目標數據的html文件,接下來你就可以關閉jupyter lab,在工作目錄下找到已經導出的html文件直接打開,接下來的工作將在瀏覽器里進行。

2.2 交互式創作

  接下來的過程則不涉及任何代碼,我們將以點擊按鈕的方式改變我們可視化作品的外觀,由於整個過程細節較多,因此將整個過程錄製為視頻:

  Kepler.gl中的交互式參數調整非常簡單,只要你隨便試一試就可以掌握其用法,不必拘泥於我上面的參數設置,發揮自己的創意,調出更美觀的軌跡動畫結果。

  以上就是本文的全部內容,如有疑問或建議歡迎到評論區與我交流~

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

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

數據分析 | 基於智能標籤,精準管理數據_網頁設計

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

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

本文源碼:GitHub·點這裏 || GitEE·點這裏

一、場景分析

1、場景案例

互聯網行業的朋友一定了解或者聽說過下列幾個場景:

阿里:千人千面,意思不同用戶使用阿里相關的產品感覺是不一樣的,例如支付寶首頁的推薦內容,和其他相關推薦流信息是完全不同的。

騰訊:社交廣告,不同用戶的朋友圈或者其他媒體場景下的廣告信息是不同的,會基於用戶特徵推薦。

頭條:信息價值,根據用戶瀏覽信息,分析用戶相關喜好,針對分析結果推薦相關的信息流,越關注某類內容,獲取相關的信息越多。

如上幾種場景的邏輯就是:基於不斷分析用戶的行為,生成用戶的特徵畫像,然後再基於用戶標籤,定製化的推薦相關內容。

2、基本概念

通過上面的場景,衍生出來兩個概念:

用戶畫像

用戶畫像,作為一種勾畫目標用戶、聯繫用戶訴求與設計方向的有效工具,把該用戶相關聯的數據的可視化的展現,就形成了用戶畫像。用戶畫像在各領域得到了廣泛的應用,最初是在電商領域得到應用的,在大數據時代背景下,用戶信息充斥在網絡中,將用戶的每個具體信息抽象成標籤,利用這些標籤將用戶形象具體化,從而為用戶提供有針對性的服務。

標籤數據

標籤在生活中非常常見,比如商品標籤,個人標籤,行業標籤,例如提到996就想到程序員,提到程序員就想到格子衫。

標籤是把分散的多方數據進行整合納入統一的技術平台,並對這些數據進行標準化和細分,進行結構化存儲和更新管理,讓業務線可以把這些細分結果推向現有的互動營銷環境里的平台,產生價值,這些數據稱為標籤數據,也就是常說的標籤庫。數據標籤的概念也是在最近幾年大數據的發展中不斷火熱起來的。

標籤價值

  • 精細運營的基礎,有效提高流量精準和效率。
  • 幫助產品快速定位需求人群,進行精準營銷;
  • 能幫助客戶更快切入到市場周期中;
  • 深入的預測分析客戶並作出及時反應;
  • 基於標籤的開發智能推薦系統;
  • 基於某類用戶的分析,洞察行業特徵;

標籤的核心價值,或者說最常用的場景:實時智能推薦,精準化数字營銷。

二、數據標籤

1、標籤劃分

屬性標籤

屬性標籤是變化最小的,例如用戶實名認證之後,基於身份信息獲取相關:性別,生日,出生年月,年齡,等相關標籤。變動頻率小,且最具有精準性。

行為標籤

行為標籤就是用戶通過在產品上的一系列操作,基於行為日誌分析得出:例如購買能力、消費愛好、季節性消費標籤等。在信息流的APP上,通過相關瀏覽行為,不斷推薦用戶感興趣的內容就是基於該邏輯。

規則標籤

根據業務場景需求,配置指定規則,基於規則生成分析結果,例如:

  • 近7天活躍用戶:近7天,每天都登錄的用戶作為規則生成;
  • 丟失用戶:六個月內沒有任何操作,可以發放高額優惠劵;
  • 潛在用戶:使用或產生瀏覽數據,但是未發生任何交易行為;

這類標籤可以基於動態的規則配置,經過計算和分析,生成描述結果,也就是規則標籤。

擬合標籤

擬合類的標籤最具有複雜性,通過用戶上述幾種標籤,智能組合分析,給的預測值,例如:未婚、瀏覽相關婚禮內容,通過分析預測用戶將要舉辦婚禮,得到一個擬合結果:預測將要結婚。這個預測邏輯也可以反向執行,用戶購買嬰兒用品:預測已婚已育。

這就是數據時代常說的一句話:用戶在某個應用上一通操作之後,算法分析的結果可能比用戶對自己的描述還要真實。

2、標籤加工流程

數據採集

數據採集的渠道相對較多,比如同一APP內的各種業務線:購物、支付、理財、外賣、信息瀏覽等等。通過數據通道傳輸到統一的數據聚合平台。有了這些海量日誌數據的支撐,才具有數據分析的基礎條件。不管是數據智能,深度學習,算法等都是建立在海量數據的基礎條件上,這樣才能獲取具有價值的分析結果。

數據加工

結合如上業務,通過對海量數據的加工,分析和提取,獲取相對精準的用戶標籤,這裏還有關鍵的一步,就是對已有的用戶標籤進行不斷的驗證和修復,尤其是規則類和擬合類的相關標籤。

標籤庫

網頁設計最專業,超強功能平台可客製化

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

通過標籤庫,管理複雜的標籤結果,除了複雜的標籤,和基於時間線的標籤變,標籤數據到這裏,已經具有相當大的價值,可以圍繞標籤庫開放一些收費服務,例如常見的,用戶在某電商APP瀏覽某些商品,可以在某信息流平台看到商品推薦。大數據時代就是這麼令人感覺智能和窒息。

標籤業務

數據走了一大圈轉換成標籤,自然還是要回歸到業務層面,通過對標籤數據的用戶的分析,可以進行精準營銷,和智能推薦等相關操作,電商應用中可以提高成交量,信息流中可以更好的吸引用戶。

應用層

把上述業務開發成服務,集成到具有的應用層面,不斷提升應用服務的質量,不斷的吸引用戶,提供服務。當然用戶的數據不斷在應用層面產生,在轉到數據採集服務中,最終形成完整的閉環流程。

3、應用案例

從流程和業務層面描述都是簡單的,到開發層面都會變得複雜和不好處理,這可能就是產品和開發之間的隔閡。

標籤的數據類型

不同標籤的分析結果需要用不同的數據類型描述,在標籤體系中,常用描述標籤的數據類型如下:枚舉、數值、日期、布爾、文本類型。不同的類型需要不一樣的分析流程。

商品和標籤

這裏提供一個基礎案例,用商品的標籤來分析商品,例如通過商品產地,價格,狀態等條件,來查詢產品庫有多少符合條件的商品。

數據表設計

主要分四張表:標籤分類,標籤庫,標籤值,標籤數據。

CREATE TABLE `tc_tag_catalog` (
	`id` INT (11) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
	`catalog_name` VARCHAR (50) NOT NULL DEFAULT '' COMMENT '名稱',
	`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
	`update_time` datetime DEFAULT NULL COMMENT '更新時間',
	`state` INT (1) DEFAULT '1' COMMENT '狀態1啟用,2禁用',
	PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '標籤層級目錄';

CREATE TABLE `tc_tag_cloud` (
	`id` INT (11) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
	`catalog_id` INT (11) NOT NULL COMMENT '目錄ID',
	`tag_name` VARCHAR (100) DEFAULT '' COMMENT '標籤名稱',
	`tag_code` INT (11) DEFAULT NULL COMMENT '標籤編碼',
	`bind_column` VARCHAR (100) DEFAULT '' COMMENT '綁定數據列',
	`data_type` INT (2) NOT NULL COMMENT '1枚舉,2數值,3日期,4布爾,5值類型',
	`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
	`update_time` datetime DEFAULT NULL COMMENT '更新時間',
	`remark` VARCHAR (150) DEFAULT NULL COMMENT '備註',
	`state` INT (1) DEFAULT '1' COMMENT '狀態1啟用,2禁用',
	PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '標籤雲';

CREATE TABLE `tc_tag_data_enum` (
	`tag_code` INT (11) NOT NULL COMMENT '標籤編碼',
	`data_value` VARCHAR (150) NOT NULL COMMENT '枚舉值',
	`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
	KEY `tag_code_index` (`tag_code`) USING BTREE
) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '標籤枚舉值';

CREATE TABLE `tc_tag_data_set` (
	`id` INT (11) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
	`product_name` VARCHAR (100) DEFAULT '' COMMENT '商品名稱',
	`unit_price` DECIMAL (10, 2) DEFAULT '0.00' COMMENT '單價',
	`is_shelves` INT (1) DEFAULT '1' COMMENT '是否上架:1否,2是',
	`origin_place` VARCHAR (100) DEFAULT '' COMMENT '產地',
	`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
	PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '標籤數據集';

模擬入參接口

這裏的參數應該是基於需求,動態選取,進行組織到一起:

例如圖片中這裏給定的標籤值列表,稱為枚舉值。

@RestController
public class AnalyzeController {

    @Resource
    private TagDataSetService tagDataSetService ;

    @GetMapping("/analyze")
    public String analyze (){
        List<TagParam> tagParamList = new ArrayList<>() ;

        TagParam tagParam1 = new TagParam(1,"產地","origin_place") ;
        List<String> valueList1 = new ArrayList<>() ;
        valueList1.add("深圳");
        valueList1.add("廣東");
        tagParam1.setValueList(valueList1);
        tagParamList.add(tagParam1) ;

        TagParam tagParam2 = new TagParam(2,"價格","unit_price") ;
        List<String> valueList2 = new ArrayList<>() ;
        valueList2.add("1999");
        tagParam2.setValueList(valueList2);
        tagParamList.add(tagParam2) ;

        TagParam tagParam3 = new TagParam(3,"生產日期","create_time") ;
        List<String> valueList3 = new ArrayList<>() ;
        valueList3.add("2020-05-01 13:43:54");
        tagParam3.setValueList(valueList3);
        tagParamList.add(tagParam3) ;

        TagParam tagParam4 = new TagParam(4,"是否上架","is_shelves") ;
        List<String> valueList4 = new ArrayList<>() ;
        valueList4.add("1");
        tagParam4.setValueList(valueList4);
        tagParamList.add(tagParam4) ;

        TagParam tagParam5 = new TagParam(5,"產品名稱","product_name") ;
        List<String> valueList5 = new ArrayList<>() ;
        valueList5.add("智能");
        tagParam5.setValueList(valueList5);
        tagParamList.add(tagParam5) ;

        Integer count = tagDataSetService.analyze(tagParamList) ;

        return "Result:" + count ;
    }
}

參數解析查詢

通過對參數的解析,最終形成查詢的SQL語句,獲取精準的結果數據。

@Service
public class TagDataSetServiceImpl extends ServiceImpl<TagDataSetMapper, TagDataSet> implements TagDataSetService {

    @Resource
    private TagDataSetMapper tagDataSetMapper ;

    @Override
    public Integer analyze(List<TagParam> tagParamList) {
        StringBuffer querySQL = new StringBuffer() ;
        for (TagParam tagParam:tagParamList){
            querySQL.append(" AND ") ;
            querySQL.append(tagParam.getBindColumn()) ;
            // 1枚舉,2數值,3日期,4布爾,5值類型
            List<String> valueList = tagParam.getValueList();
            switch (tagParam.getDataType()){
                case 1:
                    querySQL.append(" IN (") ;
                    for (int i = 0 ; i < valueList.size() ;i++){
                        if (i != valueList.size()-1){
                            querySQL.append("'").append(valueList.get(i)).append("',");
                        } else {
                            querySQL.append("'").append(valueList.get(i)).append("'");
                        }
                    }
                    querySQL.append(" )") ;
                    break;
                case 2:
                    querySQL.append("=").append(tagParam.getValueList().get(0)) ;
                    break;
                case 3:
                    querySQL.append(">='").append(tagParam.getValueList().get(0)).append("'") ;
                    break;
                case 4:
                    querySQL.append("=").append(tagParam.getValueList().get(0)) ;
                    break;
                case 5:
                    querySQL.append(" LIKE '%").append(tagParam.getValueList().get(0)).append("%'") ;
                    break;
                default:
                    break;
            }
        }
        /* 最終執行的 SQL
            SELECT COUNT(*) FROM tc_tag_data_set
            WHERE 1 = 1
            AND origin_place IN ('深圳', '廣東')
            AND unit_price = 1999
            AND create_time >= '2020-05-01 13:43:54'
            AND is_shelves = 1
            AND product_name LIKE '%智能%'
         */
        String whereCondition = String.valueOf(querySQL);
        return tagDataSetMapper.analyze(whereCondition);
    }
}

可能有人會說這不就是個查詢流程嗎?如果有這樣的疑問,把上述案例換成用戶查詢,標籤數據的價值會更直觀。

三、智能畫像

1、基本概念

用戶畫像

作為一種勾畫目標用戶、聯繫用戶訴求與設計方向的有效工具,用戶畫像在各領域得到了廣泛的應用。最初是在電商領域得到應用的,在大數據時代背景下,用戶信息充斥在網絡中,將用戶的每個具體信息抽象成標籤,利用這些標籤將用戶形象具體化,從而為用戶提供有針對性的服務。

行業畫像

通過行業屬性標籤,行業下用戶標籤的綜合分析,生成行業分析報告,提供極有價值的導向,這是最近兩年極其熱門的應用。

畫像補全

通過不斷分析用戶數據,豐富標籤庫,使用戶的畫像更加豐富立體。

2、畫像報告

通過標籤數據的分析,生成一份分析報告,報告內容包含豐富的用戶標籤統計數據。

例如:90后畫像報告

這個報告,互聯網用戶一定或多或少都看到過。主要是一些標籤統計,共性標籤展示,或者哪些群體對90后三觀影響最大,收入來源,學歷等各種分析解讀。

四、源代碼地址

GitHub·地址
https://github.com/cicadasmile/data-manage-parent
GitEE·地址
https://gitee.com/cicadasmile/data-manage-parent

推薦閱讀:《架構設計系列》,蘿蔔青菜,各有所需

序號 標題
01 架構設計:單服務.集群.分佈式,基本區別和聯繫
02 架構設計:分佈式業務系統中,全局ID生成策略
03 架構設計:分佈式系統調度,Zookeeper集群化管理
04 架構設計:接口冪等性原則,防重複提交Token管理
05 架構設計:緩存管理模式,監控和內存回收策略

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

※推薦評價好的iphone維修中心

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(二)_貨運

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

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

系列文章

  1. 基於 abp vNext 和 .NET Core 開發博客項目 – 使用 abp cli 搭建項目
  2. 基於 abp vNext 和 .NET Core 開發博客項目 – 給項目瘦身,讓它跑起來
  3. 基於 abp vNext 和 .NET Core 開發博客項目 – 完善與美化,Swagger登場
  4. 基於 abp vNext 和 .NET Core 開發博客項目 – 數據訪問和代碼優先
  5. 基於 abp vNext 和 .NET Core 開發博客項目 – 自定義倉儲之增刪改查
  6. 基於 abp vNext 和 .NET Core 開發博客項目 – 統一規範API,包裝返回模型
  7. 基於 abp vNext 和 .NET Core 開發博客項目 – 再說Swagger,分組、描述、小綠鎖
  8. 基於 abp vNext 和 .NET Core 開發博客項目 – 接入GitHub,用JWT保護你的API
  9. 基於 abp vNext 和 .NET Core 開發博客項目 – 異常處理和日誌記錄
  10. 基於 abp vNext 和 .NET Core 開發博客項目 – 使用Redis緩存數據
  11. 基於 abp vNext 和 .NET Core 開發博客項目 – 集成Hangfire實現定時任務處理
  12. 基於 abp vNext 和 .NET Core 開發博客項目 – 用AutoMapper搞定對象映射
  13. 基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(一)
  14. 基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(二)
  15. 基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(三)
  16. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(一)
  17. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(二)
  18. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(三)
  19. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(四)
  20. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(五)
  21. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(一)
  22. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(二)
  23. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(三)
  24. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(四)
  25. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(五)
  26. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(六)
  27. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(七)
  28. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(八)
  29. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(九)
  30. 基於 abp vNext 和 .NET Core 開發博客項目 – 終結篇之發布項目

上一篇(https://www.cnblogs.com/meowv/p/12971041.html)使用HtmlAgilityPack抓取壁紙數據成功將圖片存入數據庫,本篇繼續來完成一個全網各大平台的熱點新聞數據的抓取。

同樣的,可以先預覽一下我個人博客中的成品:https://meowv.com/hot ,和抓取壁紙的套路一樣,大同小異。

本次要抓取的源有18個,分別是博客園、V2EX、SegmentFault、掘金、微信熱門、豆瓣精選、IT之家、36氪、百度貼吧、百度熱搜、微博熱搜、知乎熱榜、知乎日報、網易新聞、GitHub、抖音熱點、抖音視頻、抖音正能量。

還是將數據存入數據庫,按部就班先將實體類和自定義倉儲創建好,實體取名HotNews。貼一下代碼:

//HotNews.cs
using System;
using Volo.Abp.Domain.Entities;

namespace Meowv.Blog.Domain.HotNews
{
    public class HotNews : Entity<Guid>
    {
        /// <summary>
        /// 標題
        /// </summary>
        public string Title { get; set; }

        /// <summary>
        /// 鏈接
        /// </summary>
        public string Url { get; set; }

        /// <summary>
        /// SourceId
        /// </summary>
        public int SourceId { get; set; }

        /// <summary>
        /// 創建時間
        /// </summary>
        public DateTime CreateTime { get; set; }
    }
}

剩下的大家自己完成,最終數據庫生成一張空的數據表,meowv_hotnews 。

然後還是將我們各大平台放到一個枚舉類HotNewsEnum.cs中。

//HotNewsEnum.cs
using System.ComponentModel;

namespace Meowv.Blog.Domain.Shared.Enum
{
    public enum HotNewsEnum
    {
        [Description("博客園")]
        cnblogs = 1,

        [Description("V2EX")]
        v2ex = 2,

        [Description("SegmentFault")]
        segmentfault = 3,

        [Description("掘金")]
        juejin = 4,

        [Description("微信熱門")]
        weixin = 5,

        [Description("豆瓣精選")]
        douban = 6,

        [Description("IT之家")]
        ithome = 7,

        [Description("36氪")]
        kr36 = 8,

        [Description("百度貼吧")]
        tieba = 9,

        [Description("百度熱搜")]
        baidu = 10,

        [Description("微博熱搜")]
        weibo = 11,

        [Description("知乎熱榜")]
        zhihu = 12,

        [Description("知乎日報")]
        zhihudaily = 13,

        [Description("網易新聞")]
        news163 = 14,

        [Description("GitHub")]
        github = 15,

        [Description("抖音熱點")]
        douyin_hot = 16,

        [Description("抖音視頻")]
        douyin_video = 17,

        [Description("抖音正能量")]
        douyin_positive = 18
    }
}

和上一篇抓取壁紙一樣,做一些準備工作。

.Application.Contracts層添加HotNewsJobItem<T>,在.BackgroundJobs層添加HotNewsJob用來處理爬蟲邏輯,用構造函數方式注入倉儲IHotNewsRepository

//HotNewsJobItem.cs
using Meowv.Blog.Domain.Shared.Enum;

namespace Meowv.Blog.Application.Contracts.HotNews
{
    public class HotNewsJobItem<T>
    {
        /// <summary>
        /// <see cref="Result"/>
        /// </summary>
        public T Result { get; set; }

        /// <summary>
        /// 來源
        /// </summary>
        public HotNewsEnum Source { get; set; }
    }
}
//HotNewsJob.CS
using Meowv.Blog.Domain.HotNews.Repositories;
using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace Meowv.Blog.BackgroundJobs.Jobs.HotNews
{
    public class HotNewsJob : IBackgroundJob
    {
        private readonly IHttpClientFactory _httpClient;
        private readonly IHotNewsRepository _hotNewsRepository;

        public HotNewsJob(IHttpClientFactory httpClient,
                          IHotNewsRepository hotNewsRepository)
        {
            _httpClient = httpClient;
            _hotNewsRepository = hotNewsRepository;
        }

        public async Task ExecuteAsync()
        {
            throw new NotImplementedException();
        }
    }
}

接下來明確數據源地址,因為以上數據源有的返回是HTML,有的直接返回JSON數據。為了方便調用,我這裏還注入了IHttpClientFactory

整理好的待抓取數據源列表是這樣的。

...
var hotnewsUrls = new List<HotNewsJobItem<string>>
{
    new HotNewsJobItem<string> { Result = "https://www.cnblogs.com", Source = HotNewsEnum.cnblogs },
    new HotNewsJobItem<string> { Result = "https://www.v2ex.com/?tab=hot", Source = HotNewsEnum.v2ex },
    new HotNewsJobItem<string> { Result = "https://segmentfault.com/hottest", Source = HotNewsEnum.segmentfault },
    new HotNewsJobItem<string> { Result = "https://web-api.juejin.im/query", Source = HotNewsEnum.juejin },
    new HotNewsJobItem<string> { Result = "https://weixin.sogou.com", Source = HotNewsEnum.weixin },
    new HotNewsJobItem<string> { Result = "https://www.douban.com/group/explore", Source = HotNewsEnum.douban },
    new HotNewsJobItem<string> { Result = "https://www.ithome.com", Source = HotNewsEnum.ithome },
    new HotNewsJobItem<string> { Result = "https://36kr.com/newsflashes", Source = HotNewsEnum.kr36 },
    new HotNewsJobItem<string> { Result = "http://tieba.baidu.com/hottopic/browse/topicList", Source = HotNewsEnum.tieba },
    new HotNewsJobItem<string> { Result = "http://top.baidu.com/buzz?b=341", Source = HotNewsEnum.baidu },
    new HotNewsJobItem<string> { Result = "https://s.weibo.com/top/summary/summary", Source = HotNewsEnum.weibo },
    new HotNewsJobItem<string> { Result = "https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total?limit=50&desktop=true", Source = HotNewsEnum.zhihu },
    new HotNewsJobItem<string> { Result = "https://daily.zhihu.com", Source = HotNewsEnum.zhihudaily },
    new HotNewsJobItem<string> { Result = "http://news.163.com/special/0001386F/rank_whole.html", Source = HotNewsEnum.news163 },
    new HotNewsJobItem<string> { Result = "https://github.com/trending", Source = HotNewsEnum.github },
    new HotNewsJobItem<string> { Result = "https://www.iesdouyin.com/web/api/v2/hotsearch/billboard/word", Source = HotNewsEnum.douyin_hot },
    new HotNewsJobItem<string> { Result = "https://www.iesdouyin.com/web/api/v2/hotsearch/billboard/aweme", Source = HotNewsEnum.douyin_video },
    new HotNewsJobItem<string> { Result = "https://www.iesdouyin.com/web/api/v2/hotsearch/billboard/aweme/?type=positive", Source = HotNewsEnum.douyin_positive },
};
...

其中有幾個比較特殊的,掘金、百度熱搜、網易新聞。

掘金需要發送Post請求,返回的是JSON數據,並且需要指定特有的請求頭和請求數據,所以使用IHttpClientFactory創建了HttpClient對象。

百度熱搜、網易新聞兩個老大哥玩套路,網頁編碼是GB2312的,所以要專門為其指定編碼方式,不然取到的數據都是亂碼。

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

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

...
var web = new HtmlWeb();
var list_task = new List<Task<HotNewsJobItem<object>>>();

hotnewsUrls.ForEach(item =>
{
    var task = Task.Run(async () =>
    {
        var obj = new object();

        if (item.Source == HotNewsEnum.juejin)
        {
            using var client = _httpClient.CreateClient();
            client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.14 Safari/537.36 Edg/83.0.478.13");
            client.DefaultRequestHeaders.Add("X-Agent", "Juejin/Web");
            var data = "{\"extensions\":{\"query\":{ \"id\":\"21207e9ddb1de777adeaca7a2fb38030\"}},\"operationName\":\"\",\"query\":\"\",\"variables\":{ \"first\":20,\"after\":\"\",\"order\":\"THREE_DAYS_HOTTEST\"}}";
            var buffer = data.SerializeUtf8();
            var byteContent = new ByteArrayContent(buffer);
            byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");

            var httpResponse = await client.PostAsync(item.Result, byteContent);
            obj = await httpResponse.Content.ReadAsStringAsync();
        }
        else
        {
            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
            obj = await web.LoadFromWebAsync(item.Result, (item.Source == HotNewsEnum.baidu || item.Source == HotNewsEnum.news163) ? Encoding.GetEncoding("GB2312") : Encoding.UTF8);
        }

        return new HotNewsJobItem<object>
        {
            Result = obj,
            Source = item.Source
        };
    });
    list_task.Add(task);
});
Task.WaitAll(list_task.ToArray());

循環 hotnewsUrls ,可以看到HotNewsJobItem我們返回的是object類型,因為有JSON又有HtmlDocument對象。所以這裏為了能夠統一接收,就是用了object。

針對掘金做了單獨處理,使用HttpClient發送Post請求,返回JSON字符串數據。

針對百度熱搜和網易新聞,使用Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);註冊編碼提供程序,然後在web.LoadFromWebAsync(...)加載網頁數據的時候指定網頁編碼,我使用了一個三元表達式來處理。

完成上面這一步,就可以循環 list_task,使用XPath語法,或者解析JSON數據,去拿到數據了。

...
var hotNews = new List<HotNews>();
foreach (var list in list_task)
{
    var item = await list;
    var sourceId = (int)item.Source;

    ...

    if (hotNews.Any())
    {
        await _hotNewsRepository.DeleteAsync(x => true);
        await _hotNewsRepository.BulkInsertAsync(hotNews);
    }
}

這個爬蟲同樣很簡單,只要拿到標題和鏈接即可,所以主要目標是尋找到頁面上的a標籤列表。這個我覺得也沒必要一個個去分析了,直接上代碼。

// 博客園
 if (item.Source == HotNewsEnum.cnblogs)
 {
     var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//div[@class='post_item_body']/h3/a").ToList();
     nodes.ForEach(x =>
     {
         hotNews.Add(new HotNews
         {
             Title = x.InnerText,
             Url = x.GetAttributeValue("href", ""),
             SourceId = sourceId,
             CreateTime = DateTime.Now
         });
     });
 }
// V2EX
if (item.Source == HotNewsEnum.v2ex)
{
    var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//span[@class='item_title']/a").ToList();
    nodes.ForEach(x =>
    {
        hotNews.Add(new HotNews
        {
            Title = x.InnerText,
            Url = $"https://www.v2ex.com{x.GetAttributeValue("href", "")}",
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    });
}
 // SegmentFault
 if (item.Source == HotNewsEnum.segmentfault)
 {
     var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//div[@class='news__item-info clearfix']/a").Where(x => x.InnerText.IsNotNullOrEmpty()).ToList();
     nodes.ForEach(x =>
     {
         hotNews.Add(new HotNews
         {
             Title = x.SelectSingleNode(".//h4").InnerText,
             Url = $"https://segmentfault.com{x.GetAttributeValue("href", "")}",
             SourceId = sourceId,
             CreateTime = DateTime.Now
         });
     });
 }
// 掘金
if (item.Source == HotNewsEnum.juejin)
{
    var obj = JObject.Parse((string)item.Result);
    var nodes = obj["data"]["articleFeed"]["items"]["edges"];
    foreach (var node in nodes)
    {
        hotNews.Add(new HotNews
        {
            Title = node["node"]["title"].ToString(),
            Url = node["node"]["originalUrl"].ToString(),
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    }
}
// 微信熱門
if (item.Source == HotNewsEnum.weixin)
{
    var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//ul[@class='news-list']/li/div[@class='txt-box']/h3/a").ToList();
    nodes.ForEach(x =>
    {
        hotNews.Add(new HotNews
        {
            Title = x.InnerText,
            Url = x.GetAttributeValue("href", ""),
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    });
}
// 豆瓣精選
if (item.Source == HotNewsEnum.douban)
{
    var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//div[@class='channel-item']/div[@class='bd']/h3/a").ToList();
    nodes.ForEach(x =>
    {
        hotNews.Add(new HotNews
        {
            Title = x.InnerText,
            Url = x.GetAttributeValue("href", ""),
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    });
}
// IT之家
if (item.Source == HotNewsEnum.ithome)
{
    var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//div[@class='lst lst-2 hot-list']/div[1]/ul/li/a").ToList();
    nodes.ForEach(x =>
    {
        hotNews.Add(new HotNews
        {
            Title = x.InnerText,
            Url = x.GetAttributeValue("href", ""),
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    });
}
// 36氪
if (item.Source == HotNewsEnum.kr36)
{
    var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//div[@class='hotlist-main']/div[@class='hotlist-item-toptwo']/a[2]|//div[@class='hotlist-main']/div[@class='hotlist-item-other clearfloat']/div[@class='hotlist-item-other-info']/a").ToList();
    nodes.ForEach(x =>
    {
        hotNews.Add(new HotNews
        {
            Title = x.InnerText,
            Url = $"https://36kr.com{x.GetAttributeValue("href", "")}",
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    });
}
// 百度貼吧
if (item.Source == HotNewsEnum.tieba)
{
    var obj = JObject.Parse(((HtmlDocument)item.Result).ParsedText);
    var nodes = obj["data"]["bang_topic"]["topic_list"];
    foreach (var node in nodes)
    {
        hotNews.Add(new HotNews
        {
            Title = node["topic_name"].ToString(),
            Url = node["topic_url"].ToString().Replace("amp;", ""),
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    }
}
// 百度熱搜
if (item.Source == HotNewsEnum.baidu)
{
    var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//table[@class='list-table']//tr/td[@class='keyword']/a[@class='list-title']").ToList();
    nodes.ForEach(x =>
    {
        hotNews.Add(new HotNews
        {
            Title = x.InnerText,
            Url = x.GetAttributeValue("href", ""),
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    });
}
// 微博熱搜
if (item.Source == HotNewsEnum.weibo)
{
    var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//table/tbody/tr/td[2]/a").ToList();
    nodes.ForEach(x =>
    {
        hotNews.Add(new HotNews
        {
            Title = x.InnerText,
            Url = $"https://s.weibo.com{x.GetAttributeValue("href", "").Replace("#", "%23")}",
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    });
}
// 知乎熱榜
if (item.Source == HotNewsEnum.zhihu)
{
    var obj = JObject.Parse(((HtmlDocument)item.Result).ParsedText);
    var nodes = obj["data"];
    foreach (var node in nodes)
    {
        hotNews.Add(new HotNews
        {
            Title = node["target"]["title"].ToString(),
            Url = $"https://www.zhihu.com/question/{node["target"]["id"]}",
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    }
}
// 知乎日報
if (item.Source == HotNewsEnum.zhihudaily)
{
    var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//div[@class='box']/a").ToList();
    nodes.ForEach(x =>
    {
        hotNews.Add(new HotNews
        {
            Title = x.InnerText,
            Url = $"https://daily.zhihu.com{x.GetAttributeValue("href", "")}",
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    });
}
// 網易新聞
if (item.Source == HotNewsEnum.news163)
{
    var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//div[@class='area-half left']/div[@class='tabBox']/div[@class='tabContents active']/table//tr/td[1]/a").ToList();
    nodes.ForEach(x =>
    {
        hotNews.Add(new HotNews
        {
            Title = x.InnerText,
            Url = x.GetAttributeValue("href", ""),
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    });
}
// GitHub
if (item.Source == HotNewsEnum.github)
{
    var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//article[@class='Box-row']/h1/a").ToList();
    nodes.ForEach(x =>
    {
        hotNews.Add(new HotNews
        {
            Title = x.InnerText.Trim().Replace("\n", "").Replace(" ", ""),
            Url = $"https://github.com{x.GetAttributeValue("href", "")}",
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    });
}
// 抖音熱點
if (item.Source == HotNewsEnum.douyin_hot)
{
    var obj = JObject.Parse(((HtmlDocument)item.Result).ParsedText);
    var nodes = obj["word_list"];
    foreach (var node in nodes)
    {
        hotNews.Add(new HotNews
        {
            Title = node["word"].ToString(),
            Url = $"#{node["hot_value"]}",
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    }
}
// 抖音視頻 & 抖音正能量
if (item.Source == HotNewsEnum.douyin_video || item.Source == HotNewsEnum.douyin_positive)
{
    var obj = JObject.Parse(((HtmlDocument)item.Result).ParsedText);
    var nodes = obj["aweme_list"];
    foreach (var node in nodes)
    {
        hotNews.Add(new HotNews
        {
            Title = node["aweme_info"]["desc"].ToString(),
            Url = node["aweme_info"]["share_url"].ToString(),
            SourceId = sourceId,
            CreateTime = DateTime.Now
        });
    }
}

item.Result轉換成指定類型,最終拿到數據后,我們先刪除所有數據后再批量插入。

然後新建擴展方法UseHotNewsJob(),在模塊類中調用。

//MeowvBlogBackgroundJobsExtensions.cs
...
        /// <summary>
        /// 每日熱點數據抓取
        /// </summary>
        /// <param name="context"></param>
        public static void UseHotNewsJob(this IServiceProvider service)
        {
            var job = service.GetService<HotNewsJob>();

            RecurringJob.AddOrUpdate("每日熱點數據抓取", () => job.ExecuteAsync(), CronType.Hour(1, 2));
        }
...

指定定時任務為每2小時運行一次。

...
        public override void OnApplicationInitialization(ApplicationInitializationContext context)
        {
            ...
            var service = context.ServiceProvider;
            ...
            service.UseHotNewsJob();
        }

編譯運行,此時周期性作業就會出現我們的定時任務了。

默認時間沒到是不會執行的,我們手動執行等待一會看看效果。

執行完成后,成功將所有熱點數據保存在數據庫中,說明我們的爬蟲已經搞定了,並且Hangfire會按照給定的規則去循環執行,你學會了嗎?

開源地址:https://github.com/Meowv/Blog/tree/blog_tutorial

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

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

分佈式事務_網頁設計公司

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

 

  
眾所周知,數據庫能實現本地事務,也就是在同一個數據庫中,你可以允許一組操作要麼全都正確執行,要麼全都不執行。這裏特彆強調了本地事務,也就是目前的數據庫只能支持同一個數據庫中的事務。但現在的系統往往採用微服務架構,業務系統擁有獨立的數據庫,因此就出現了跨多個數據庫的事務需求,這種事務即為“分佈式事務”。那麼在目前數據庫不支持跨庫事務的情況下,我們應該如何實現分佈式事務呢?本文首先會為大家梳理分佈式事務的基本概念和理論基礎,然後介紹幾種目前常用的分佈式事務解決方案。廢話不多說,那就開始吧~

1 什麼是事務?

  事務由一組操作構成,我們希望這組操作能夠全部正確執行,如果這一組操作中的任意一個步驟發生錯誤,那麼就需要回滾之前已經完成的操作。也就是同一個事務中的所有操作,要麼全都正確執行,要麼全都不要執行。

2 事務的四大特性 ACID

  說到事務,就不得不提一下事務著名的四大特性。

  • 原子性

      原子性要求,事務是一個不可分割的執行單元,事務中的所有操作要麼全都執行,要麼全都不執行。

  • 一致性

      一致性要求,事務在開始前和結束后,數據庫的完整性約束沒有被破壞。

  • 隔離性

      事務的執行是相互獨立的,它們不會相互干擾,一個事務不會看到另一個正在運行過程中的事務的數據。

  • 持久性

      持久性要求,一個事務完成之後,事務的執行結果必須是持久化保存的。即使數據庫發生崩潰,在數據庫恢復後事務提交的結果仍然不會丟失

 

注意:事務只能保證數據庫的高可靠性,即數據庫本身發生問題后,事務提交后的數據仍然能恢復;而如果不是數據庫本身的故障,如硬盤損壞了,那麼事務提交的數據可能就丟失了。這屬於『高可用性』的範疇。因此,事務只能保證數據庫的『高可靠性』,而『高可用性』需要整個系統共同配合實現。

3 事務的隔離級別

  這裏擴展一下,對事務的隔離性做一個詳細的解釋。

  在事務的四大特性ACID中,要求的隔離性是一種嚴格意義上的隔離,也就是多個事務是串行執行的,彼此之間不會受到任何干擾。這確實能夠完全保證數據的安全性,但在實際業務系統中,這種方式性能不高。因此,數據庫定義了四種隔離級別,隔離級別和數據庫的性能是呈反比的,隔離級別越低,數據庫性能越高,而隔離級別越高,數據庫性能越差。

3.1 事務併發執行會出現的問題

  我們先來看一下在不同的隔離級別下,數據庫可能會出現的問題:

    1. 更新丟失

      當有兩個併發執行的事務,更新同一行數據,那麼有可能一個事務會把另一個事務的更新覆蓋掉。 當數據庫沒有加任何鎖操作的情況下會發生。

    2. 臟讀

      一個事務讀到另一個尚未提交的事務中的數據。 該數據可能會被回滾從而失效。 如果第一個事務拿着失效的數據去處理那就發生錯誤了。

    3. 不可重複讀

      不可重複度的含義:一個事務對同一行數據讀了兩次,卻得到了不同的結果。它具體分為如下兩種情況:

      •   虛讀:在事務1兩次讀取同一記錄的過程中,事務2對該記錄進行了修改,從而事務1第二次讀到了不一樣的記錄。
      •   幻讀:事務1在兩次查詢的過程中,事務2對該表進行了插入、刪除操作,從而事務1第二次查詢的結果發生了變化。

不可重複讀 與 臟讀 的區別? 臟讀讀到的是尚未提交的數據,而不可重複讀讀到的是已經提交的數據,只不過在兩次讀的過程中數據被另一個事務改過了。

3.2 數據庫的四種隔離級別

  數據庫一共有如下四種隔離級別:

  1. Read uncommitted 讀未提交

    在該級別下,一個事務對一行數據修改的過程中,不允許另一個事務對該行數據進行修改,但允許另一個事務對該行數據讀。 因此本級別下,不會出現更新丟失,但會出現臟讀、不可重複讀。

  2. Read committed 讀提交

    在該級別下,未提交的寫事務不允許其他事務訪問該行,因此不會出現臟讀;但是讀取數據的事務允許其他事務的訪問該行數據,因此會出現不可重複讀的情況。

  3. Repeatable read 重複讀

    在該級別下,讀事務禁止寫事務,但允許讀事務,因此不會出現同一事務兩次讀到不同的數據的情況(不可重複讀),且寫事務禁止其他一切事務。

  4. Serializable 序列化

    該級別要求所有事務都必須串行執行,因此能避免一切因併發引起的問題,但效率很低。

  隔離級別越高,越能保證數據的完整性和一致性,但是對併發性能的影響也越大。對於多數應用程序,可以優先考慮把數據庫系統的隔離級別設為Read Committed。它能夠避免臟讀取,而且具有較好的併發性能。儘管它會導致不可重複讀、幻讀和第二類丟失更新這些併發問題,在可能出現這類問題的個別場合,可以由應用程序採用悲觀鎖或樂觀鎖來控制。

4 什麼是分佈式事務?

  到此為止,所介紹的事務都是基於單數據庫的本地事務,目前的數據庫僅支持單庫事務,並不支持跨庫事務。而隨着微服務架構的普及,一個大型業務系統往往由若干個子系統構成,這些子系統又擁有各自獨立的數據庫。往往一個業務流程需要由多個子系統共同完成,而且這些操作可能需要在一個事務中完成。在微服務系統中,這些業務場景是普遍存在的。此時,我們就需要在數據庫之上通過某種手段,實現支持跨數據庫的事務支持,這也就是大家常說的“分佈式事務”。

這裏舉一個分佈式事務的典型例子——用戶下單過程。 當我們的系統採用了微服務架構后,一個電商系統往往被拆分成如下幾個子系統:商品系統、訂單系統、支付系統、積分系統等。整個下單的過程如下:

  1. 用戶通過商品系統瀏覽商品,他看中了某一項商品,便點擊下單
  2. 此時訂單系統會生成一條訂單
  3. 訂單創建成功后,支付系統提供支付功能
  4. 當支付完成后,由積分系統為該用戶增加積分

上述步驟2、3、4需要在一個事務中完成。對於傳統單體應用而言,實現事務非常簡單,只需將這三個步驟放在一個方法A中,再用Spring的@Transactional註解標識該方法即可。Spring通過數據庫的事務支持,保證這些步驟要麼全都執行完成,要麼全都不執行。但在這個微服務架構中,這三個步驟涉及三個系統,涉及三個數據庫,此時我們必須在數據庫和應用系統之間,通過某項黑科技,實現分佈式事務的支持。

5 CAP理論

CAP理論說的是:在一個分佈式系統中,最多只能滿足C、A、P中的兩個需求。

CAP的含義:

  • C:Consistency 一致性

    同一數據的多個副本是否實時相同。

  • A:Availability 可用性

    可用性:一定時間內 & 系統返回一個明確的結果 則稱為該系統可用。

  • P:Partition tolerance 分區容錯性

    將同一服務分佈在多個系統中,從而保證某一個系統宕機,仍然有其他系統提供相同的服務。

  CAP理論告訴我們,在分佈式系統中,C、A、P三個條件中我們最多只能選擇兩個。那麼問題來了,究竟選擇哪兩個條件較為合適呢?

對於一個業務系統來說,可用性和分區容錯性是必須要滿足的兩個條件,並且這兩者是相輔相成的。業務系統之所以使用分佈式系統,主要原因有兩個:

  • 提升整體性能

    當業務量猛增,單個服務器已經無法滿足我們的業務需求的時候,就需要使用分佈式系統,使用多個節點提供相同的功能,從而整體上提升系統的性能,這就是使用分佈式系統的第一個原因。

  • 實現分區容錯性

    單一節點 或 多個節點處於相同的網絡環境下,那麼會存在一定的風險,萬一該機房斷電、該地區發生自然災害,那麼業務系統就全面癱瘓了。為了防止這一問題,採用分佈式系統,將多個子系統分佈在不同的地域、不同的機房中,從而保證系統高可用性。

  這說明分區容錯性是分佈式系統的根本,如果分區容錯性不能滿足,那使用分佈式系統將失去意義。

  此外,可用性對業務系統也尤為重要。在大談用戶體驗的今天,如果業務系統時常出現“系統異常”、響應時間過長等情況,這使得用戶對系統的好感度大打折扣,在互聯網行業競爭激烈的今天,相同領域的競爭者不甚枚舉,系統的間歇性不可用會立馬導致用戶流向競爭對手。因此,我們只能通過犧牲一致性來換取系統的可用性分區容錯性。這也就是下面要介紹的BASE理論。

6 BASE理論

  CAP理論告訴我們一個悲慘但不得不接受的事實——我們只能在C、A、P中選擇兩個條件。而對於業務系統而言,我們往往選擇犧牲一致性來換取系統的可用性和分區容錯性。不過這裏要指出的是,所謂的“犧牲一致性”並不是完全放棄數據一致性,而是犧牲強一致性換取弱一致性。下面來介紹下BASE理論。

  • BA:Basic Available 基本可用S:Soft State:柔性狀態 同一數據的不同副本的狀態,可以不需要實時一致。
    • 整個系統在某些不可抗力的情況下,仍然能夠保證“可用性”,即一定時間內仍然能夠返回一個明確的結果。只不過“基本可用”和“高可用”的區別是:
      • “一定時間”可以適當延長 當舉行大促時,響應時間可以適當延長
      • 給部分用戶返回一個降級頁面 給部分用戶直接返回一個降級頁面,從而緩解服務器壓力。但要注意,返回降級頁面仍然是返回明確結果。
  • E:Eventual Consisstency:最終一致性 同一數據的不同副本的狀態,可以不需要實時一致,但一定要保證經過一定時間后仍然是一致的。

7 酸鹼平衡

  ACID能夠保證事務的強一致性,即數據是實時一致的。這在本地事務中是沒有問題的,在分佈式事務中,強一致性會極大影響分佈式系統的性能,因此分佈式系統中遵循BASE理論即可。但分佈式系統的不同業務場景對一致性的要求也不同。如交易場景下,就要求強一致性,此時就需要遵循ACID理論,而在註冊成功后發送短信驗證碼等場景下,並不需要實時一致,因此遵循BASE理論即可。因此要根據具體業務場景,在ACID和BASE之間尋求平衡。

8 分佈式事務協議

  下面介紹幾種實現分佈式事務的協議。

8.1 兩階段提交協議 2PC

分佈式系統的一個難點是如何保證架構下多個節點在進行事務性操作的時候保持一致性。為實現這個目的,二階段提交算法的成立基於以下假設:

  • 該分佈式系統中,存在一個節點作為協調者(Coordinator),其他節點作為參与者(Cohorts)。且節點之間可以進行網絡通信。
  • 所有節點都採用預寫式日誌,且日誌被寫入后即被保持在可靠的存儲設備上,即使節點損壞不會導致日誌數據的消失。
  • 所有節點不會永久性損壞,即使損壞后仍然可以恢復。

1. 第一階段(投票階段):

  1. 協調者節點向所有參与者節點詢問是否可以執行提交操作(vote),並開始等待各參与者節點的響應。
  2. 參与者節點執行詢問發起為止的所有事務操作,並將Undo信息和Redo信息寫入日誌。(注意:若成功這裏其實每個參与者已經執行了事務操作)
  3. 各參与者節點響應協調者節點發起的詢問。如果參与者節點的事務操作實際執行成功,則它返回一個”同意”消息;如果參与者節點的事務操作實際執行失敗,則它返回一個”中止”消息。

2. 第二階段(提交執行階段):

  當協調者節點從所有參与者節點獲得的相應消息都為”同意”時:

  1. 協調者節點向所有參与者節點發出”正式提交(commit)”的請求。
  2. 參与者節點正式完成操作,並釋放在整個事務期間內佔用的資源。
  3. 參与者節點向協調者節點發送”完成”消息。
  4. 協調者節點受到所有參与者節點反饋的”完成”消息后,完成事務。

  如果任一參与者節點在第一階段返回的響應消息為”中止”,或者 協調者節點在第一階段的詢問超時之前無法獲取所有參与者節點的響應消息時:

  1. 協調者節點向所有參与者節點發出”回滾操作(rollback)”的請求。
  2. 參与者節點利用之前寫入的Undo信息執行回滾,並釋放在整個事務期間內佔用的資源。
  3. 參与者節點向協調者節點發送”回滾完成”消息。
  4. 協調者節點受到所有參与者節點反饋的”回滾完成”消息后,取消事務。

  不管最後結果如何,第二階段都會結束當前事務。

  二階段提交看起來確實能夠提供原子性的操作,但是不幸的事,二階段提交還是有幾個缺點的:

  1. 執行過程中,所有參与節點都是事務阻塞型的。當參与者佔有公共資源時,其他第三方節點訪問公共資源不得不處於阻塞狀態。
  2. 參与者發生故障。協調者需要給每個參与者額外指定超時機制,超時后整個事務失敗。(沒有多少容錯機制)
  3. 協調者發生故障。參与者會一直阻塞下去。需要額外的備機進行容錯。(這個可以依賴後面要講的Paxos協議實現HA)
  4. 二階段無法解決的問題:協調者再發出commit消息之後宕機,而唯一接收到這條消息的參与者同時也宕機了。那麼即使協調者通過選舉協議產生了新的協調者,這條事務的狀態也是不確定的,沒人知道事務是否被已經提交。

為此,Dale Skeen和Michael Stonebraker在“A Formal Model of Crash Recovery in a Distributed System”中提出了三階段提交協議(3PC)。

8.2 三階段提交協議 3PC

  與兩階段提交不同的是,三階段提交有兩個改動點。

  • 引入超時機制。同時在協調者和參与者中都引入超時機制。
  • 在第一階段和第二階段中插入一個準備階段。保證了在最後提交階段之前各參与節點的狀態是一致的。

  也就是說,除了引入超時機制之外,3PC把2PC的準備階段再次一分為二,這樣三階段提交就有CanCommit、PreCommit、DoCommit三個階段。

1. CanCommit階段

  3PC的CanCommit階段其實和2PC的準備階段很像。協調者向參与者發送commit請求,參与者如果可以提交就返回Yes響應,否則返回No響應。

  1. 事務詢問

    協調者向參与者發送CanCommit請求。詢問是否可以執行事務提交操作。然後開始等待參与者的響應。

  2. 響應反饋

    參与者接到CanCommit請求之後,正常情況下,如果其自身認為可以順利執行事務,則返回Yes響應,並進入預備狀態。否則反饋No

2. PreCommit階段

  協調者根據參与者的反應情況來決定是否可以記性事務的PreCommit操作。根據響應情況,有以下兩種可能。 假如協調者從所有的參与者獲得的反饋都是Yes響應,那麼就會執行事務的預執行。

  1. 發送預提交請求

    協調者向參与者發送PreCommit請求,並進入Prepared階段。

  2. 事務預提交

    參与者接收到PreCommit請求后,會執行事務操作,並將undo和redo信息記錄到事務日誌中。

  3. 響應反饋

    如果參与者成功的執行了事務操作,則返回ACK響應,同時開始等待最終指令。

假如有任何一個參与者向協調者發送了No響應,或者等待超時之後,協調者都沒有接到參与者的響應,那麼就執行事務的中斷。

  1. 發送中斷請求

    協調者向所有參与者發送abort請求。

  2. 中斷事務

    參与者收到來自協調者的abort請求之後(或超時之後,仍未收到協調者的請求),執行事務的中斷。

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

3. doCommit階段 該階段進行真正的事務提交,也可以分為以下兩種情況。

  該階段進行真正的事務提交,也可以分為以下兩種情況。

3.1 執行提交

  1. 發送提交請求

    協調接收到參与者發送的ACK響應,那麼他將從預提交狀態進入到提交狀態。並向所有參与者發送doCommit請求。

  2. 事務提交

    參与者接收到doCommit請求之後,執行正式的事務提交。並在完成事務提交之後釋放所有事務資源。

  3. 響應反饋

    事務提交完之後,向協調者發送Ack響應。

  4. 完成事務

    協調者接收到所有參与者的ack響應之後,完成事務。

3.2 中斷事務 協調者沒有接收到參与者發送的ACK響應(可能是接受者發送的不是ACK響應,也可能響應超時),那麼就會執行中斷事務。

  1. 發送中斷請求

    協調者向所有參与者發送abort請求

  2. 事務回滾

    參与者接收到abort請求之後,利用其在階段二記錄的undo信息來執行事務的回滾操作,並在完成回滾之後釋放所有的事務資源。

  3. 反饋結果

    參与者完成事務回滾之後,向協調者發送ACK消息

  4. 中斷事務

    協調者接收到參与者反饋的ACK消息之後,執行事務的中斷。

9 分佈式事務的解決方案

  分佈式事務的解決方案有如下幾種:

  • 全局消息
  • 基於可靠消息服務的分佈式事務
  • TCC
  • 最大努力通知

9.1 方案1:全局事務(DTP模型)

  全局事務基於DTP模型實現。DTP是由X/Open組織提出的一種分佈式事務模型——X/Open Distributed Transaction Processing Reference Model。它規定了要實現分佈式事務,需要三種角色:

  • AP:Application 應用系統

    它就是我們開發的業務系統,在我們開發的過程中,可以使用資源管理器提供的事務接口來實現分佈式事務。

  • TM:Transaction Manager 事務管理器

    • 分佈式事務的實現由事務管理器來完成,它會提供分佈式事務的操作接口供我們的業務系統調用。這些接口稱為TX接口。
    • 事務管理器還管理着所有的資源管理器,通過它們提供的XA接口來同一調度這些資源管理器,以實現分佈式事務。
    • DTP只是一套實現分佈式事務的規範,並沒有定義具體如何實現分佈式事務,TM可以採用2PC、3PC、Paxos等協議實現分佈式事務。
  • RM:Resource Manager 資源管理器

    • 能夠提供數據服務的對象都可以是資源管理器,比如:數據庫、消息中間件、緩存等。大部分場景下,數據庫即為分佈式事務中的資源管理器。
    • 資源管理器能夠提供單數據庫的事務能力,它們通過XA接口,將本數據庫的提交、回滾等能力提供給事務管理器調用,以幫助事務管理器實現分佈式的事務管理。
    • XA是DTP模型定義的接口,用於向事務管理器提供該資源管理器(該數據庫)的提交、回滾等能力。
    • DTP只是一套實現分佈式事務的規範,RM具體的實現是由數據庫廠商來完成的。
  1. 有沒有基於DTP模型的分佈式事務中間件?
  1. DTP模型有啥優缺點?

9.2 方案2:基於可靠消息服務的分佈式事務

  這種實現分佈式事務的方式需要通過消息中間件來實現。假設有A和B兩個系統,分別可以處理任務A和任務B。此時系統A中存在一個業務流程,需要將任務A和任務B在同一個事務中處理。下面來介紹基於消息中間件來實現這種分佈式事務。

 

 

  • 在系統A處理任務A前,首先向消息中間件發送一條消息
  • 消息中間件收到后將該條消息持久化,但並不投遞。此時下游系統B仍然不知道該條消息的存在。
  • 消息中間件持久化成功后,便向系統A返回一個確認應答;
  • 系統A收到確認應答后,則可以開始處理任務A;
  • 任務A處理完成后,向消息中間件發送Commit請求。該請求發送完成后,對系統A而言,該事務的處理過程就結束了,此時它可以處理別的任務了。 但commit消息可能會在傳輸途中丟失,從而消息中間件並不會向系統B投遞這條消息,從而系統就會出現不一致性。這個問題由消息中間件的事務回查機制完成,下文會介紹。
  • 消息中間件收到Commit指令后,便向系統B投遞該消息,從而觸發任務B的執行;
  • 當任務B執行完成后,系統B向消息中間件返回一個確認應答,告訴消息中間件該消息已經成功消費,此時,這個分佈式事務完成。

上述過程可以得出如下幾個結論:

  1. 消息中間件扮演者分佈式事務協調者的角色。
  2. 系統A完成任務A后,到任務B執行完成之間,會存在一定的時間差。在這個時間差內,整個系統處於數據不一致的狀態,但這短暫的不一致性是可以接受的,因為經過短暫的時間后,系統又可以保持數據一致性,滿足BASE理論。

上述過程中,如果任務A處理失敗,那麼需要進入回滾流程,如下圖所示:

 

  • 若系統A在處理任務A時失敗,那麼就會向消息中間件發送Rollback請求。和發送Commit請求一樣,系統A發完之後便可以認為回滾已經完成,它便可以去做其他的事情。

  • 消息中間件收到回滾請求后,直接將該消息丟棄,而不投遞給系統B,從而不會觸發系統B的任務B。

此時系統又處於一致性狀態,因為任務A和任務B都沒有執行。

  上面所介紹的Commit和Rollback都屬於理想情況,但在實際系統中,Commit和Rollback指令都有可能在傳輸途中丟失。那麼當出現這種情況的時候,消息中間件是如何保證數據一致性呢?——答案就是超時詢問機制。

 

 

  系統A除了實現正常的業務流程外,還需提供一個事務詢問的接口,供消息中間件調用。當消息中間件收到一條事務型消息后便開始計時,如果到了超時時間也沒收到系統A發來的Commit或Rollback指令的話,就會主動調用系統A提供的事務詢問接口詢問該系統目前的狀態。該接口會返回三種結果:

  • 提交

    若獲得的狀態是“提交”,則將該消息投遞給系統B。

  • 回滾

    若獲得的狀態是“回滾”,則直接將條消息丟棄。

  • 處理中

    若獲得的狀態是“處理中”,則繼續等待。

消息中間件的超時詢問機制能夠防止上游系統因在傳輸過程中丟失Commit/Rollback指令而導致的系統不一致情況,而且能降低上游系統的阻塞時間,上游系統只要發出Commit/Rollback指令后便可以處理其他任務,無需等待確認應答。而Commit/Rollback指令丟失的情況通過超時詢問機制來彌補,這樣大大降低上游系統的阻塞時間,提升系統的併發度。

  下面來說一說消息投遞過程的可靠性保證。 當上游系統執行完任務並向消息中間件提交了Commit指令后,便可以處理其他任務了,此時它可以認為事務已經完成,接下來消息中間件**一定會保證消息被下游系統成功消費掉!**那麼這是怎麼做到的呢?這由消息中間件的投遞流程來保證。

  消息中間件向下游系統投遞完消息后便進入阻塞等待狀態,下游系統便立即進行任務的處理,任務處理完成后便向消息中間件返回應答。消息中間件收到確認應答后便認為該事務處理完畢!

  如果消息在投遞過程中丟失,或消息的確認應答在返回途中丟失,那麼消息中間件在等待確認應答超時之後就會重新投遞,直到下游消費者返回消費成功響應為止。當然,一般消息中間件可以設置消息重試的次數和時間間隔,比如:當第一次投遞失敗后,每隔五分鐘重試一次,一共重試3次。如果重試3次之後仍然投遞失敗,那麼這條消息就需要人工干預。

 

 

 

有的同學可能要問:消息投遞失敗後為什麼不回滾消息,而是不斷嘗試重新投遞?

這就涉及到整套分佈式事務系統的實現成本問題。 我們知道,當系統A將向消息中間件發送Commit指令后,它便去做別的事情了。如果此時消息投遞失敗,需要回滾的話,就需要讓系統A事先提供回滾接口,這無疑增加了額外的開發成本,業務系統的複雜度也將提高。對於一個業務系統的設計目標是,在保證性能的前提下,最大限度地降低系統複雜度,從而能夠降低系統的運維成本。

不知大家是否發現,上游系統A向消息中間件提交Commit/Rollback消息採用的是異步方式,也就是當上游系統提交完消息后便可以去做別的事情,接下來提交、回滾就完全交給消息中間件來完成,並且完全信任消息中間件,認為它一定能正確地完成事務的提交或回滾。然而,消息中間件向下游系統投遞消息的過程是同步的。也就是消息中間件將消息投遞給下游系統后,它會阻塞等待,等下游系統成功處理完任務返回確認應答后才取消阻塞等待。為什麼這兩者在設計上是不一致的呢?

  首先,上游系統和消息中間件之間採用異步通信是為了提高系統併發度。業務系統直接和用戶打交道,用戶體驗尤為重要,因此這種異步通信方式能夠極大程度地降低用戶等待時間。此外,異步通信相對於同步通信而言,沒有了長時間的阻塞等待,因此系統的併發性也大大增加。但異步通信可能會引起Commit/Rollback指令丟失的問題,這就由消息中間件的超時詢問機制來彌補。

  那麼,消息中間件和下游系統之間為什麼要採用同步通信呢?

  異步能提升系統性能,但隨之會增加系統複雜度;而同步雖然降低系統併發度,但實現成本較低。因此,在對併發度要求不是很高的情況下,或者服務器資源較為充裕的情況下,我們可以選擇同步來降低系統的複雜度。 我們知道,消息中間件是一個獨立於業務系統的第三方中間件,它不和任何業務系統產生直接的耦合,它也不和用戶產生直接的關聯,它一般部署在獨立的服務器集群上,具有良好的可擴展性,所以不必太過於擔心它的性能,如果處理速度無法滿足我們的要求,可以增加機器來解決。而且,即使消息中間件處理速度有一定的延遲那也是可以接受的,因為前面所介紹的BASE理論就告訴我們了,我們追求的是最終一致性,而非實時一致性,因此消息中間件產生的時延導致事務短暫的不一致是可以接受的。

9.3 方案3:最大努力通知(定期校對)

  最大努力通知也被稱為定期校對,其實在方案二中已經包含,這裏再單獨介紹,主要是為了知識體系的完整性。這種方案也需要消息中間件的參与,其過程如下:

 

 

  • 上游系統在完成任務后,向消息中間件同步地發送一條消息,確保消息中間件成功持久化這條消息,然後上游系統可以去做別的事情了;
  • 消息中間件收到消息后負責將該消息同步投遞給相應的下游系統,並觸發下游系統的任務執行;
  • 當下游系統處理成功后,向消息中間件反饋確認應答,消息中間件便可以將該條消息刪除,從而該事務完成。

上面是一個理想化的過程,但在實際場景中,往往會出現如下幾種意外情況:

  1. 消息中間件向下游系統投遞消息失敗
  2. 上游系統向消息中間件發送消息失敗

  對於第一種情況,消息中間件具有重試機制,我們可以在消息中間件中設置消息的重試次數和重試時間間隔,對於網絡不穩定導致的消息投遞失敗的情況,往往重試幾次后消息便可以成功投遞,如果超過了重試的上限仍然投遞失敗,那麼消息中間件不再投遞該消息,而是記錄在失敗消息表中,消息中間件需要提供失敗消息的查詢接口,下游系統會定期查詢失敗消息,並將其消費,這就是所謂的“定期校對”。

如果重複投遞和定期校對都不能解決問題,往往是因為下游系統出現了嚴重的錯誤,此時就需要人工干預。

  對於第二種情況,需要在上游系統中建立消息重發機制。可以在上游系統建立一張本地消息表,並將 任務處理過程向本地消息表中插入消息 這兩個步驟放在一個本地事務中完成。如果向本地消息表插入消息失敗,那麼就會觸發回滾,之前的任務處理結果就會被取消。如果這量步都執行成功,那麼該本地事務就完成了。接下來會有一個專門的消息發送者不斷地發送本地消息表中的消息,如果發送失敗它會返回重試。當然,也要給消息發送者設置重試的上限,一般而言,達到重試上限仍然發送失敗,那就意味着消息中間件出現嚴重的問題,此時也只有人工干預才能解決問題。

  對於不支持事務型消息的消息中間件,如果要實現分佈式事務的話,就可以採用這種方式。它能夠通過重試機制+定期校對實現分佈式事務,但相比於第二種方案,它達到數據一致性的周期較長,而且還需要在上游系統中實現消息重試發布機制,以確保消息成功發布給消息中間件,這無疑增加了業務系統的開發成本,使得業務系統不夠純粹,並且這些額外的業務邏輯無疑會佔用業務系統的硬件資源,從而影響性能。

因此,盡量選擇支持事務型消息的消息中間件來實現分佈式事務,如RocketMQ。

9.4 方案4:TCC(兩階段型、補償型)

  TCC即為Try Confirm Cancel,它屬於補償型分佈式事務。顧名思義,TCC實現分佈式事務一共有三個步驟:

  • Try:嘗試待執行的業務
    • 這個過程並未執行業務,只是完成所有業務的一致性檢查,並預留好執行所需的全部資源
  • Confirm:執行業務
    • 這個過程真正開始執行業務,由於Try階段已經完成了一致性檢查,因此本過程直接執行,而不做任何檢查。並且在執行的過程中,會使用到Try階段預留的業務資源。
  • Cancel:取消執行的業務
    • 若業務執行失敗,則進入Cancel階段,它會釋放所有佔用的業務資源,並回滾Confirm階段執行的操作。

下面以一個轉賬的例子來解釋下TCC實現分佈式事務的過程。

假設用戶A用他的賬戶餘額給用戶B發一個100元的紅包,並且餘額系統和紅包系統是兩個獨立的系統。

  • Try

    • 創建一條轉賬流水,並將流水的狀態設為交易中
    • 將用戶A的賬戶中扣除100元(預留業務資源)
    • Try成功之後,便進入Confirm階段
    • Try過程發生任何異常,均進入Cancel階段
  • Confirm

    • 向B用戶的紅包賬戶中增加100元
    • 將流水的狀態設為交易已完成
    • Confirm過程發生任何異常,均進入Cancel階段
    • Confirm過程執行成功,則該事務結束
  • Cancel

    • 將用戶A的賬戶增加100元
    • 將流水的狀態設為交易失敗

  在傳統事務機制中,業務邏輯的執行和事務的處理,是在不同的階段由不同的部件來完成的:業務邏輯部分訪問資源實現數據存儲,其處理是由業務系統負責;事務處理部分通過協調資源管理器以實現事務管理,其處理由事務管理器來負責。二者沒有太多交互的地方,所以,傳統事務管理器的事務處理邏輯,僅需要着眼於事務完成(commit/rollback)階段,而不必關注業務執行階段。

TCC全局事務必須基於RM本地事務來實現全局事務

  TCC服務是由Try/Confirm/Cancel業務構成的, 其Try/Confirm/Cancel業務在執行時,會訪問資源管理器(Resource Manager,下文簡稱RM)來存取數據。這些存取操作,必須要參与RM本地事務,以使其更改的數據要麼都commit,要麼都rollback。

這一點不難理解,考慮一下如下場景:

 

 

  假設圖中的服務B沒有基於RM本地事務(以RDBS為例,可通過設置auto-commit為true來模擬),那麼一旦[B:Try]操作中途執行失敗,TCC事務框架後續決定回滾全局事務時,該[B:Cancel]則需要判斷[B:Try]中哪些操作已經寫到DB、哪些操作還沒有寫到DB:假設[B:Try]業務有5個寫庫操作,[B:Cancel]業務則需要逐個判斷這5個操作是否生效,並將生效的操作執行反向操作。

  不幸的是,由於[B:Cancel]業務也有n(0<=n<=5)個反向的寫庫操作,此時一旦[B:Cancel]也中途出錯,則後續的[B:Cancel]執行任務更加繁重。因為,相比第一次[B:Cancel]操作,後續的[B:Cancel]操作還需要判斷先前的[B:Cancel]操作的n(0<=n<=5)個寫庫中哪幾個已經執行、哪幾個還沒有執行,這就涉及到了冪等性問題。而對冪等性的保障,又很可能還需要涉及額外的寫庫操作,該寫庫操作又會因為沒有RM本地事務的支持而存在類似問題。。。可想而知,如果不基於RM本地事務,TCC事務框架是無法有效的管理TCC全局事務的。

反之,基於RM本地事務的TCC事務,這種情況則會很容易處理:[B:Try]操作中途執行失敗,TCC事務框架將其參与RM本地事務直接rollback即可。後續TCC事務框架決定回滾全局事務時,在知道“[B:Try]操作涉及的RM本地事務已經rollback”的情況下,根本無需執行[B:Cancel]操作。

換句話說,基於RM本地事務實現TCC事務框架時,一個TCC型服務的cancel業務要麼執行,要麼不執行,不需要考慮部分執行的情況。

TCC事務框架應該提供Confirm/Cancel服務的冪等性保障

  一般認為,服務的冪等性,是指針對同一個服務的多次(n>1)請求和對它的單次(n=1)請求,二者具有相同的副作用。

在TCC事務模型中,Confirm/Cancel業務可能會被重複調用,其原因很多。比如,全局事務在提交/回滾時會調用各TCC服務的Confirm/Cancel業務邏輯。執行這些Confirm/Cancel業務時,可能會出現如網絡中斷的故障而使得全局事務不能完成。因此,故障恢復機制後續仍然會重新提交/回滾這些未完成的全局事務,這樣就會再次調用參与該全局事務的各TCC服務的Confirm/Cancel業務邏輯。

  既然Confirm/Cancel業務可能會被多次調用,就需要保障其冪等性。 那麼,應該由TCC事務框架來提供冪等性保障?還是應該由業務系統自行來保障冪等性呢? 個人認為,應該是由TCC事務框架來提供冪等性保障。如果僅僅只是極個別服務存在這個問題的話,那麼由業務系統來負責也是可以的;然而,這是一類公共問題,毫無疑問,所有TCC服務的Confirm/Cancel業務存在冪等性問題。TCC服務的公共問題應該由TCC事務框架來解決;而且,考慮一下由業務系統來負責冪等性需要考慮的問題,就會發現,這無疑增大了業務系統的複雜度。


作者:大閑人柴毛毛


鏈接:https://juejin.im/post/5ab0d1a3f265da23731448e0


來源:掘金


著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

提車時不做好這些重要事情 出了門馬上就後悔到哭!_網頁設計

網頁設計最專業,超強功能平台可客製化

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

雖然里程表可以調,但也可以稍微看一下,也許就給你發現出問題了。出廠日期這是為了檢查是否是庫存車,如果不是太久,三四個月左右吧,還可以接受,如果時間太長,建議還是和4S店談談吧。發動機艙檢查是夠乾淨,或者油漬滲漏等等,如果有那必然是頻繁使用,也許是試駕車。

提車,是一件很興奮的事,但是很多人過於興奮,導致在提車的時候忘記檢查車輛,開回家之後發現不對勁的地方,或者有刮痕什麼的,而此時回去找4S店,只會認為是你自己弄傷,而進行維修,此時就會很無奈,所以在提車的時候別太興奮,下面這些流程必須得做。

檢查外觀

看看有沒有掛上,漆面的色澤均不均衡,或者有沒有凹陷的地方,

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

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

凹陷的話反光和正常平面的反光是不一樣的。

輪胎

同上,檢查輪轂有沒有刮傷,輪胎有沒有裂痕,輪胎上的胎毛是否還算完整。

內飾,功能

稍微試用一下一些功能,因為現在比較操作系統都放在了中控屏上,用一下看下有無問題。內飾面板之類有沒有完全接合,車窗,天窗開關是否正常。

里程表,燈光

所有燈光都常識打開關閉一次或幾次。雖然里程表可以調,但也可以稍微看一下,也許就給你發現出問題了。

出廠日期

這是為了檢查是否是庫存車,如果不是太久,三四個月左右吧,還可以接受,如果時間太長,建議還是和4S店談談吧。

發動機艙

檢查是夠乾淨,或者油漬滲漏等等,如果有那必然是頻繁使用,也許是試駕車。聽一聽發動機的運作聲是否正常。

試駕一下

稍微跑動一下,看下轉向之類的是否有異常。

尾語:畢竟花了大價錢買的,都不想出門就有問題,花了錢還不省心,所以最好“交貨”之前,檢查一遍,避免日後的麻煩。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

※推薦評價好的iphone維修中心

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢