標籤: 潭子電動車

  • 【故障公告】數據庫服務器 CPU 近 100% 引發的故障(源於 .NET Core 3.0 的一個 bug),雲計算之路-阿里雲上:數據庫連接數過萬的真相,從阿里雲RDS到微軟.NET Core

    【故障公告】數據庫服務器 CPU 近 100% 引發的故障(源於 .NET Core 3.0 的一個 bug),雲計算之路-阿里雲上:數據庫連接數過萬的真相,從阿里雲RDS到微軟.NET Core

    非常抱歉,這次故障給您帶來麻煩了,請您諒解。

    今天早上 10:54 左右,我們所使用的數據庫服務(阿里雲 RDS 實例 SQL Server 2016 標準版)CPU 突然飆升至 90% 以上,應用日誌中出現大量數據庫查詢超時的錯誤。

    Microsoft.Data.SqlClient.SqlException (0x80131904): Execution Timeout Expired.  The timeout period elapsed prior to completion of the operation or the server is not responding.
     ---> System.ComponentModel.Win32Exception (258): Unknown error 258

    我們收到告警通知並確認問題后,在 11:06 啟動了阿里雲 RDS 的主備切換, 11:08 完成切換,數據庫 CPU 恢復正常。但是關鍵時候 docker swarm 總是雪上加霜,在數據庫恢復正常后,部署博客站點的 docker swarm 集群有一個節點出現異常情況,部分請求會出現 50x 錯誤,將這個異常節點退出集群並啟動新的節點后在 11:15 左右才恢復正常。

    通過阿里雲 RDS 控制台的 CloudDBA 發現了 CPU 近 100% 期間執行次數異常多的 SQL 語句。

    SELECT TOP @__p_1 [b].[TagName] AS [Name], [b].[TagID] AS [Id], [b].[UseCount], [b].[BlogId]
    FROM [blog_Tag] [b]
    WHERE [b].[BlogId] = @__blogId_0
        AND @__blogId_0 IS NOT NULL
        AND [b].[UseCount] > ?
    ORDER BY [b].[UseCount] DESC

    上面的 SQL 語句是 EF Core 3.0 生成的,其中加粗的  IS NOT NULL  就是 EF Core 3.0 的一個臭名還沒昭著的 bug —— 生成 SQL 語句時會生成額外的  IS NOT NULL  查詢條件。

    誰也沒想到(連微軟自己也沒想到)這個看似無傷大雅的多此一舉卻存在致命隱患 —— 在某些情況下會讓整個數據庫服務器 CPU 持續 100% (或者近 100%)。一開始遇到這個問題時,我們也沒想到,還因此錯怪了阿里雲(),後來在阿里雲數據庫專家分析了我們遇到的問題后才發現原來罪魁禍首是 EF Core 生成的多餘的 “IS NOT NULL” ,它會在某些情況下會造成 SQL Server 緩存了性能極其低下(很耗CPU)的執行計劃,然後後續的查詢都走這個執行計劃,CPU 就會居高不下。這個錯誤的執行計劃有雙重殺傷力,一邊巨耗數據庫 CPU ,一邊造成對應的查詢無法正常完成從而查詢結果不能被緩存到 memcached ,於是針對這個執行計劃的查詢就越多,雪崩效應就發生了。唯一的解決方法就是清除這個錯誤的執行計劃緩存,主備切換或者重啟服務器只是清除執行計劃緩存的一種簡單粗暴的方法。

    在我們開始遇到這個問題,就已經有人在 github 上了這個問題:

    Yeah this needs to be fixed asap. We just deployed code that uses 3.0 and had to immediately revert to 2.2 because simple queries blew up our SQL Azure CPU usage. Went from under 50% to 100% and stayed there until we rolled back.

    但當時沒有引起微軟的足夠重視,在我們知道錯怪了阿里雲實際是微軟的問題之後,我們向微軟 .NET 團隊反饋了這個問題,這次得到了微軟的重視,很快就修復了,但是是通過 .NET Core 3.0 Preview 版發布的,我們在非生產環境下驗證了  IS NOT NULL 的確修復了,由於是 Preview 版,再加上 .NET Core 3.1 正式版年底前會發布,所以我們沒有在生產環境中更新這個修復,只是將上次出現問題的複雜 SQL 語句改為用 Dapper 調用存儲過程。後來阿里雲數據庫專家進一步對我們的數據庫進行分析,連平時數據庫 CPU 的毛刺(偶爾跑高的波動)都與  IS NOT NULL  有關。

    這就是這次故障的背景,在我們等待 .NET Core 3.1 正式版修復這個 bug 的過程中又被坑了一次,與上次不同的是這次出現問題的 SQL 語句非常簡單,而且只有一個 “IS NOT NULL” ,由此可見這個坑的殺傷力。

    這個坑足以載入 .NET Core 的史冊,另一個讓我們記憶猶新的那次也讓我們錯怪阿里雲的 .NET Core 坑是正式版的 .NET Core 中 SqlClient 竟然漏寫了 Dispose ,詳見 。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

    ※帶您來看台北網站建置台北網頁設計,各種案例分享

  • 中國電池行業發展全球領先,亞太電池展海外佈局加速

    羅蘭貝格汽車行業中心與德國著名汽車研究機構亞琛汽車工程技術有限公司共同發佈的《2017年第一季度全球電動汽車發展指數》報告(以下簡稱“報告”),對全球七大主要汽車國家電動汽車競爭格局在技術、行業、市場三項指標上進行了詳細分析。

     

    就“行業”指標而言,中國已經確立了領先地位,原因在於中國市場持續快速增長,且超過90%的鋰電池都在本土生產。與中國相比,日本在電動汽車產量和全球電池生產份額這兩方面都處於不利地位,排名滑落至全球第三。美國則攀升至第二位。

     

    從全球電池生產份額來看,中國電池製造商已經處於領先地位。由此可見,在電池製造領域,中國的優勢日益明顯。許多國際企業負責人紛紛表示看好未來中國電池市場,希望借助展會這一平臺進入中國市場,在中國千億元級電池市場中“分一杯羹”。作為全球領先的電池採購交易平臺,亞太電池展吸引了大批國際企業前來參展,目前已有大量海外優質採購商分別通過網站後臺、郵件、社交平臺等進行了參觀登記。

     

     

    除此之外,主辦方還收到了德國汽車協會、美國能源協會、美國汽車零部件協會、巴基斯坦汽車零部件協會(54人觀展團)、印度觀展團(50余人觀展團)等重量級觀展團參觀申請。國際優質採購商的參與有利於幫助國內參展企業拓展海外消費市場,助推我國電池產業的可持續發展,做大做強國內品牌。相信在亞太電池展這一全球領先的電池與儲能行業採購交易平臺的支援下,中國電池與儲能行業國際化的步伐將繼續提速,在全球範圍內掀起“動力風暴”。

     

    作為世界級的動力電池與儲能行業交易盛會,亞太電池展自全面啟動以來,得到了國內外主流媒體的高度關注,多家行業媒體進行宣傳報導,迅速建立起亞太電池展在國際的品牌知名度、美譽度,有效對接目標客戶及潛在買家,也有效幫助國內參展商在全球範圍的品牌傳播。

    專業協會鼎力支持

     

    TURKEY ELECTRIC & HYBRID VEHICLES ASSOCIATION是國際能源機構(IEA)框架下的一個國際成員組織,致力於混合動力和燃料電池汽車的資訊傳播,通過banner、新聞等報導方式使亞太電池展的宣傳效果如虎添翼。

     

    有理由相信,亞太電池展很快將給業界帶來一場電池與儲能產業的航母級盛宴。作為立志於打造國際頂級電池行業採購易與技術交流平臺的亞太電池展,組委會將繼續增大海外買家邀約力度,後期將著重歐美、俄羅斯、中東、中亞以及東南亞等地區優質買家團的組織,以切實説明到國內企業拓展國際市場。

     

     

     

     

    想查詢更多展會訊息,請登陸大會官網:http://www.battery-expo.com/ 瞭解。

     

    預訂展位:+86-20-32373488

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

    【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

    ※帶您來看台北網站建置台北網頁設計,各種案例分享

  • 上海新能源汽車展8月23舉行 氫燃料電池汽車成亮點

    上海新能源汽車展8月23舉行 氫燃料電池汽車成亮點

     目前,未來汽車的三大發展方向包括:新能源汽車、自動駕駛汽車和飛行汽車。飛行汽車估計在我們這代人的時間裡是實現不了了,但是新能源汽車及自動駕駛汽車卻是觸手可及,是當前最熱門的一個話題。

     

    氫燃料電池開始被重視

    新能源汽車之間的競爭,其實主要在純電動汽車和混合動力汽車之間展開,而被稱為傳統汽車“完美替代者”的氫燃料汽車,一直以來都顯得特別低調。直到今年,氫燃料電池動力汽車終於重新進入人們的視野。

     

    “十三五”規劃中明確提到:“要推進燃料電池汽車產業化”;前不久科技部部長萬鋼表示:“在未來車用能源中,氫燃料與電力將並存互補,共同支撐新能源汽車產業發展”。國外方面:英國政府4月份宣佈將投入2300萬英鎊來完善氫燃料電池汽車的基礎設施;5月,日本11家企業簽署諒解備忘錄,計畫在日本共建加氫站,推進日本政府此前發佈的《氫能與燃料電池戰略路線圖》。

     

     氫能具有高效率、來源豐富、用途廣泛的優勢,可以在3分鐘-5分鐘內給電池灌滿燃料,被視為是“未來能源”。與傳統動力汽車及純電動車相比,氫燃料電池汽車動力更可持續,能效更高,續航能力更強,且可實現零碳排放,被國際公認為“終極新能源汽車解決方案”。

     

    除了“十三五”規劃中提到2020年實現燃料電池車批量生產和規模化示範應用外,在新一輪的新能源汽車補貼政策中,儘管純電動和插電式混合動力的補貼有所下降,但是氫燃料電池的補貼方案並沒有調整,依然延續至2020年不退坡的政策。

     

    氫燃料汽車異軍突起

    目前宇通、福田、中植、五洲龍等大型車企均有技術儲備,並開發出了成品車型。6月16-18日,在2017深圳國際新能源汽車產業博覽會上,五洲龍全球首發了“F1未來”(Future)系列8.5米氫燃料電池通勤車。該車採用了30KW氫燃料電池系統+磷酸鐵鋰電池(功率型)的混合動力技術路線,一次加氫只需5分鐘,可以續航430KM,成為了展會上的一大熱點。

     

     

    另外,工信部發佈的《新能源汽車推薦車型目錄》中也不乏氫燃料電池車型的身影。無論是國家的政策還是車企的動作,種種跡象都表明,氫燃料汽車即將迎來發展的高潮。目前國內已有14家企業正緊鑼密鼓的斥鉅資佈局燃料電池車領域。近期,就連東旭光電這樣的玻璃基板廠商也斥資1億元參股億華通,積極佈局氫燃料電池業務,足見行業風口之大。

     

    由充電設施線上網、廣東省充電設施協會、廣東省新能源汽車產業協會和振威展覽股份共同主辦,中國土木工程學會城市公共交通學會協辦的2017上海國際新能源汽車產業博覽會將於8月23-25日在上海新國際博覽中心舉行,多家氫燃料電池企業和氫燃料汽車企業將亮相,將有力推動氫燃料電池汽車的市場化應用。

     

    此外,比亞迪、申龍客車、珠海銀隆、上汽集團、上饒客車、中植新能源、中通、江淮、吉利、眾泰、知豆、南京金龍、成功汽車、新吉奧集團、瑞馳新能源、福汽新龍馬等新能源汽車企業,以及精進電動、英威騰、東風電機、力神、沃特瑪、國軒高科、地上鐵、特來電、科陸、巴斯巴、萬馬專纜、奧美格、瑞可達等核心三電及零部件知名企業將亮相本次展會,展出最新款產品和前沿技術。

     

    前有“三元鋰電”跟“磷酸鐵鋰”的技術路線之爭,現有純電動汽車與氫燃料電池汽車市場前景的博弈。本次展會的舉辦對於新能源汽車純電動和燃料電池動力產品具有積極的展示作用。促進各界人士對純電動及燃料電池動力產品的認識及瞭解。

     

    參觀預登記,請點擊:

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

    【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

    ※帶您來看台北網站建置台北網頁設計,各種案例分享

  • 繼法國之後,英國政府宣布2040 年禁售汽柴油車

    繼法國之後,英國政府宣布2040 年禁售汽柴油車

    汽柴油車真的要走入歷史了嗎?繼法國宣布2040 年禁售汽柴油車後,英國政府為解決空氣污染問題,準備從30 億英鎊抗空汙的資金中提撥2.55 億英鎊協助委員會加快地方措施,以應對柴油車輛的污染,終極目標也將在2040 年前禁售汽柴油車。

    英國獨立報(The Independent) 報導,空氣污染與英國每年約4 萬人過早死亡有關,運輸也佔溫室氣體排放量的很大一部分。英國政府先前推出的抗空汙計畫版本被環保人士反對,認為力道太弱,無法達到歐盟的排放標準,英國最高法院要求7 月31 日前英國政府必須制定新的計畫,以降低有害二氧化氮的排放量,而就在法院規定的截止日前,英國政府宣布2040 年汽柴油車禁令。

    英國追隨法國腳步頒布禁售令,顯示向電動車轉型的速度正在加快,BMW 宣布計畫推出Mini 電動車版,將在英國牛津進行組裝,Volvo 也宣布清潔能源車計畫。

    英國政府還將討論柴油車報廢計劃的執行細節。英國環保倡議者認為,這項計劃應包括政府資助且強制性的清潔空氣區,對進入高空氣污染地區的高污染車輛收取費用。對清潔空氣區設立收費制度被視為是最有效打擊二氧化氮污染的政策,而柴油車是排量二氧化氮的禍首。

    但是英國政府對此有疑慮,認為這是懲罰柴油車駕駛,畢竟原本認為柴油車的碳排量比汽車少,因此鼓勵消費者購買。英國政府傾向改裝巴士等交通工具,降低排放量,或改變道路佈局,甚至改變速度和重新編排交通號誌等功能,使交通流量更加順暢,減少污染。

    英國政府發言人表示,不該責怪柴油車駕駛,為了幫助他們改用清潔車,政府會討論針對性的報廢計劃,支持受本地計劃影響的駕駛。但英國在野黨不滿意這種溫和的作風,呼籲柴油車禁售令應該提前至2025 年,並提出廢止計劃幫駕駛轉換成更環保的車輛。

    印度2030 年實現全電動車目標

    除歐洲國家之外,受嚴重空汙困擾的印度動作也非常積極,印度政府計畫2030 年實現全電動車目標,而印度政府此舉並不只是為了抗空汙,還可減少燃料進口費用。印度重工業部和印度國家研究院正在製定促進電動汽車發展政策,主要是朝降低成本提高價格誘因做起,現在印度對混合動力車與電動車提供補貼,初期也會補貼業者度過轉型期,為2030 年禁售汽柴油車鋪路。

    未來3 年印度將大規模佈建充電基礎設施與電池交換計畫,目前只有印度只有電動車廠  Mahindra Electric 在印度提供全電動車,最近Anand Mahindra 執行長在社群媒體上邀請Tesla 到印度設置商店,Tesla 將在2017 年底之前進入印度市場,開放印度消費者訂購Model 3,明年可能開始展店。

    除了電動汽車,混合動力汽車市場處於更好的市場位置,豐田、Volvo 和BMW 等幾家製造商在印度提供混合動力車或插電式混合動力車。特斯拉和日產也宣布進在印度市場堆出Model 3和Leaf。若印度政府的計劃順利進行,印度將成為全球電動車品牌的一級戰場。

    (合作媒體:。圖片出處:public domain CC0)

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

    【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

    ※帶您來看台北網站建置台北網頁設計,各種案例分享

  • Freemarker + xml 實現Java導出word

    Freemarker + xml 實現Java導出word

    前言

    最近做了一個調查問卷導出的功能,需求是將維護的題目,答案,導出成word,參考了幾種方案之後,選擇功能強大的freemarker+固定格式之後的wordxml實現導出功能。導出word的代碼是可以直接復用的,於是在此貼出,並進行總結,方便大家拿走。

    實現過程概覽

    先在word上,調整好自己想要的樣子。然後存為xml文件。保存為freemarker模板,以ftl後綴結尾。將需要替換的變量使用freemarker的語法進行替換。最終將數據準備好,和模板進行渲染,生成文件並返回給瀏覽器流。

    詳細的實現過程

    準備好word的樣式

    我們新建一個word,我們應該使用Microsoft office,如果使用wps可能會造成樣式有些不兼容。在新建的office中,設置好我們的表格樣式。我們的調查問卷涉及到四種類型,單選,多選,填空,簡答。我們做出四種類型的示例。

    樣式沒有問題后,我們選擇另存為word xml 2003版本。將會生成一個xml文件。

    格式化xml,並用freemarker語法替換xml

    我們可以先下載一個工具 firstobject xml editor,這個可以幫助我們查看xml,同時方便我們定位我們需要改的位置。
    複製過去之後,按f8可以將其進行格式化,左側是標籤,右側是內容,我們只需要關注w:body即可。

    像右側的調查問卷這個就是個標題,我們實際渲染的時候應該將其進行替換,比如我們的程序數據map中,有title屬性,我們想要這裏展示,我們就使用語法${title}即可。

    freemarker的具體語法,可以參考freemarker的問題,在這裏我給出幾個簡單的例子。
    比如我們將所有的數據放置在dataList中,所以我們需要判斷,dataList是不是空,是空,我們不應該進行下面的邏輯,不是空,我們應該先循環題目是必須的,答案是需要根據類型進行再次循環的。語法參考文檔,這裏不再贅述。

    程序端引入freemarker

    <dependency>
        <groupId>org.freemarker</groupId>
        <artifactId>freemarker</artifactId>
    </dependency>

    將我們的flt文件放在resources下的templates下。

    後端代碼實現

    此代碼可以復用,在此貼出

    public class WordUtils {
    
        private static Configuration configuration = null;
        private static final String templateFolder = WordUtils.class.getClassLoader().getResource("").getPath()+"/templates/word";
        static {
            configuration = new Configuration();
            configuration.setDefaultEncoding("utf-8");
            try {
                configuration.setDirectoryForTemplateLoading(new File(templateFolder));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         *  @Description:導出word,傳入request,response,map就是值,title是導出問卷名,ftl是你要使用的模板名
         */
        public static void exportWord(HttpServletRequest request, HttpServletResponse response, Map map, String title, String ftlFile) throws Exception {
            Template freemarkerTemplate = configuration.getTemplate(ftlFile);
            File file = null;
            InputStream fin = null;
            ServletOutputStream out = null;
            try {
                file = createDocFile(map,freemarkerTemplate);
                fin = new FileInputStream(file);
                String fileName = title + ".doc";
                response.setCharacterEncoding("utf-8");
                response.setContentType("application/msword");
                response.setHeader("Content-Disposition", "attachment;filename="
                 +fileName);
                out = response.getOutputStream();
                byte[] buffer = new byte[512];  
                int bytesToRead = -1;
                while((bytesToRead = fin.read(buffer)) != -1) {
                    out.write(buffer, 0, bytesToRead);
                }
            }finally {
                if(fin != null) fin.close();
                if(out != null) out.close();
                if(file != null) file.delete(); 
            }
        }
    
        /**
         *  @Description:創建doc文件
         */
        private static File createDocFile(Map<?, ?> dataMap, Template template) {
            File file = new File("init.doc");
            try {
                Writer writer = new OutputStreamWriter(new FileOutputStream(file), "utf-8");
                template.process(dataMap, writer);
                writer.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return file;
        }
    
    }

    有了工具類后,我們準備好我們的map數據。map裏面的數據大家可以自行定義。然後調用utils中的導出方法即可。

    WordUtils.exportWord(request, response, dataMap, "word", "demo.ftl");

    結語

    至此已經結束了,十分的好用,有疑問的話,可以評論交流。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

    ※帶您來看台北網站建置台北網頁設計,各種案例分享

  • 利用Python學習線性代數 — 1.1 線性方程組

    利用Python學習線性代數 — 1.1 線性方程組

    利用Python學習線性代數 — 1.1 線性方程組

    系列,

    本節實現的主要功能函數,在源碼文件中,後續章節將作為基本功能調用。

    線性方程

    線性方程組由一個或多個線性方程組成,如
    \[ \begin{array}\\ x_1 – 2 x_2 &= -1\\ -x_1 + 3 x_2 &= 3 \end{array} \]

    求包含兩個變量兩個線性方程的方程組的解,等價於求兩條直線的交點。
    這裏可以畫出書圖1-1和1-2的線性方程組的圖形。
    通過改變線性方程的參數,觀察圖形,體會兩個方程對應直線平行、相交、重合三種可能。

    那麼,怎麼畫二元線性方程的直線呢?

    方法是這樣的:
    假如方程是 \(a x_1 + b x_2 = c\) 的形式,可以寫成 \(x_2 = (c – a x_1) / b\)
    在以 \(x_1\)\(x_2\)為兩個軸的直角坐標系中,\(x_1\)取一組值,如 \((-3, -2.9, -2.8, \dots, 2.9, 3.0)\)
    計算相應的 \(x_2\),然後把所有點 \((x_1, x_2)\) 連起來成為一條線。
    \(b\)\(0\) 時, 則在\(x_1 = c / a\)處畫一條垂直線。

    # 引入Numpy和 Matplotlib庫
    import numpy as np
    from matplotlib import pyplot as plt

    Matplotlib 是Python中使用較多的可視化庫,這裏只用到了它的一些基本功能。

    def draw_line(a, b, c, start=-4, 
                  stop=5, step=0.01):
        """根據線性方程參數繪製一條直線"""
        # 如果b為0,則畫一條垂線
        if np.isclose(b, 0):
            plt.vlines(start, stop, c / a)
        else: # 否則畫 y = (c - a*x) / b
            xs = np.arange(start, stop, step)
            plt.plot(xs, (c - a*xs)/b)
    # 1.1 圖1-1
    draw_line(1, -2, -1)
    draw_line(-1, 3, 3)

    def draw_lines(augmented, start=-4, 
                  stop=5, step=0.01):
        """給定增廣矩陣,畫兩條線."""
        plt.figure()
        for equation in augmented:
            draw_line(*equation, start, stop, step)
        plt.show()
    # Fig. 1-1
    # 增廣矩陣用二維數組表示 
    # [[1, -2, -1], [-1, 3, 3]]
    # 這些数字對應圖1-1對應方程的各項係數
    draw_lines([[1, -2, -1], [-1, 3, 3]])

    # Fig. 1-2
    draw_lines([[1, -2, -2], [-1, 2, 3]])
    # Fig. 1-3
    draw_lines([[1, -2, -1], [-1, 2, 1]])

    • 建議:改變這些係數,觀察直線,體會兩條直線相交、平行和重合的情況

    例如

    draw_lines([[1, -2, -2], [-1, 2, 9]])

    如果對Numpy比較熟悉,則可以採用更簡潔的方式實現上述繪圖功能。
    在計算多條直線方程時,可以利用向量編程的方式,用更少的代碼實現。

    def draw_lines(augmented, start=-4, 
                   stop=5, step=0.01):
        """Draw lines represented by augmented matrix on 2-d plane."""
        am = np.asarray(augmented)
        xs = np.arange(start, stop, step).reshape([1, -1])
        # 同時計算兩條直線的y值
        ys = (am[:, [-1]] - am[:, [1]]*xs) / am[:, [0]]
        for y in ys:
            plt.plot(xs[0], y)
        plt.show()

    矩陣記號

    矩陣是一個數表,在程序中通常用二維數組表示,例如

    # 嵌套列表表示矩陣
    matrix = [[1, -2, 1, 0],
              [0, 2, -8, 8],
              [5, 0, -5, 10]]
    matrix
    [[1, -2, 1, 0], [0, 2, -8, 8], [5, 0, -5, 10]]

    實際工程和研究實踐中,往往會採用一些專門的數值計算庫,簡化和加速計算。
    Numpy庫是Python中數值計算的常用庫。
    在Numpy中,多維數組類型稱為ndarray,可以理解為n dimensional array。
    例如

    # Numpy ndarray 表示矩陣
    matrix = np.array([[1, -2, 1, 0],
                        [0, 2, -8, 8],
                        [5, 0, -5, 10]])
    matrix
    array([[ 1, -2,  1,  0],
           [ 0,  2, -8,  8],
           [ 5,  0, -5, 10]])

    解線性方程組

    本節解線性方程組的方法是 高斯消元法,利用了三種基本行變換。

    1. 把某個方程換成它與另一個方程的倍數的和;
    2. 交換兩個方程的位置;
    3. 某個方程的所有項乘以一個非零項。

    假設線性方程的增廣矩陣是\(A\),其第\(i\)\(j\)列的元素是\(a_{ij}\)
    消元法的基本步驟是:

    • 增廣矩陣中有 \(n\) 行,該方法的每一步處理一行。
      1. 在第\(i\)步,該方法處理第\(i\)
        • \(a_{ii}\)為0,則在剩餘行 \(\{j| j \in (i, n]\}\)中選擇絕對值最大的行\(a_{ij}\)
          • \(a_{ij}\)為0,返回第1步。
          • 否則利用變換2,交換\(A\)的第\(i\)\(j\)行。
      2. 利用行變換3,第\(i\)行所有元素除以\(a_{ii}\),使第 \(i\) 個方程的第 \(i\)個 係數為1
      3. 利用行變換1,\(i\)之後的行減去第\(i\)行的倍數,使這些行的第 \(i\) 列為0

    為了理解這些步驟的實現,這裏先按書中的例1一步步計算和展示,然後再總結成完整的函數。
    例1的增廣矩陣是

    \[ \left[ \begin{array} &1 & -2 & 1 & 0\\ 0 & 2 & -8 & 8\\ 5 & 0 & -5 & 10 \end{array} \right] \]

    # 增廣矩陣
    A = np.array([[1, -2, 1, 0],
                  [0, 2, -8, 8],
                  [5, 0, -5, 10]])
    # 行號從0開始,處理第0行
    i = 0
    # 利用變換3,將第i行的 a_ii 轉成1。這裏a_00已經是1,所不用動
    # 然後利用變換1,把第1行第0列,第2行第0列都減成0。
    # 這裏僅需考慮i列之後的元素,因為i列之前的元素已經是0
    #   即第1行減去第0行的0倍
    #   而第2行減去第0行的5倍
    A[i+1:, i:] = A[i+1:, i:] - A[i+1:, [i]] * A[i, i:]
    A
    array([[  1,  -2,   1,   0],
           [  0,   2,  -8,   8],
           [  0,  10, -10,  10]])
    i = 1
    # 利用變換3,將第i行的 a_ii 轉成1。
    A[i] = A[i] / A[i, i]
    A
    array([[  1,  -2,   1,   0],
           [  0,   1,  -4,   4],
           [  0,  10, -10,  10]])
    # 然後利用變換1,把第2行第i列減成0。
    A[i+1:, i:] = A[i+1:, i:] - A[i+1:, [i]] * A[i, i:]
    A
    array([[  1,  -2,   1,   0],
           [  0,   1,  -4,   4],
           [  0,   0,  30, -30]])
    i = 2
    # 利用變換3,將第i行的 a_ii 轉成1。
    A[i] = A[i] / A[i, i]
    A
    array([[ 1, -2,  1,  0],
           [ 0,  1, -4,  4],
           [ 0,  0,  1, -1]])

    消元法的前向過程就結束了,我們可以總結成一個函數

    def eliminate_forward(augmented): 
        """
        消元法的前向過程.
        
        返回行階梯形,以及先導元素的坐標(主元位置)
        """
        A = np.asarray(augmented, dtype=np.float64)
        # row number of the last row
        pivots = []
        i, j = 0, 0
        while i < A.shape[0] and j < A.shape[1]:
            A[i] = A[i] / A[i, j]
            if (i + 1) < A.shape[0]: # 除最後一行外
                A[i+1:, j:] = A[i+1:, j:] - A[i+1:, [j]] * A[i, j:]
            pivots.append((i, j))
            i += 1
            j += 1
        return A, pivots

    這裡有兩個細節值得注意

    1. 先導元素 \(a_{ij}\),不一定是在主對角線位置,即 \(i\) 不一定等於\(j\).
    2. 最後一行只需要用變換3把先導元素轉為1,沒有剩餘行需要轉換
    # 測試一個增廣矩陣,例1
    A = np.array([[1, -2, 1, 0],
                  [0, 2, -8, 8],
                  [5, 0, -5, 10]])
    A, pivots = eliminate_forward(A)
    print(A)
    print(pivots)
    [[ 1. -2.  1.  0.]
     [ 0.  1. -4.  4.]
     [ 0.  0.  1. -1.]]
    [(0, 0), (1, 1), (2, 2)]

    消元法的後向過程則更簡單一些,對於每一個主元(這裏就是前面的\(a_{ii}\)),將其所在的列都用變換1,使其它行對應的列為0.

    for i, j in reversed(pivots):
        A[:i, j:] = A[:i, j:] - A[[i], j:] * A[:i, [j]] 
    A
    array([[ 1.,  0.,  0.,  1.],
           [ 0.,  1.,  0.,  0.],
           [ 0.,  0.,  1., -1.]])
    def eliminate_backward(simplified, pivots):
        """消元法的後向過程."""
        A = np.asarray(simplified)
        for i, j in reversed(pivots):
            A[:i, j:] = A[:i, j:] - A[[i], j:] * A[:i, [j]] 
        return A

    至此,結合 eliminate_forward 和eliminate_backward函數,可以解形如例1的線性方程。

    然而,存在如例3的線性方程,在eliminate_forward算法進行的某一步,主元為0,需要利用變換2交換兩行。
    交換行時,可以選擇剩餘行中,選擇當前主元列不為0的任意行,與當前行交換。
    這裏每次都採用剩餘行中,當前主元列絕對值最大的行。
    補上行交換的前向過程函數如下

    def eliminate_forward(augmented): 
        """消元法的前向過程"""
        A = np.asarray(augmented, dtype=np.float64)
        # row number of the last row
        pivots = []
        i, j = 0, 0
        while i < A.shape[0] and j < A.shape[1]:
            # if pivot is zero, exchange rows
            if np.isclose(A[i, j], 0):
                if (i + 1) < A.shape[0]:
                    max_k = i + 1 + np.argmax(np.abs(A[i+1:, i]))
                if (i + 1) >= A.shape[0] or np.isclose(A[max_k, i], 0):
                    j += 1
                    continue
                A[[i, max_k]] = A[[max_k, i]]
            A[i] = A[i] / A[i, j]
            if (i + 1) < A.shape[0]:
                A[i+1:, j:] = A[i+1:, j:] - A[i+1:, [j]] * A[i, j:]
            pivots.append((i, j))
            i += 1
            j += 1
        return A, pivots

    行交換時,有一種特殊情況,即剩餘所有行的主元列都沒有非零元素
    這種情況下,在當前列的右側尋找不為零的列,作為新的主元列。

    # 用例3測試eliminate_forward
    aug = [[0, 1, -4, 8],
           [2, -3, 2, 1],
           [4, -8, 12, 1]]
    echelon, pivots = eliminate_forward(aug)
    print(echelon)
    print(pivots)
    [[ 1.   -2.    3.    0.25]
     [ 0.    1.   -4.    0.5 ]
     [ 0.    0.    0.    1.  ]]
    [(0, 0), (1, 1), (2, 3)]

    例3化簡的結果與書上略有不同,由行交換策略不同引起,也說明同一個矩陣可能由多個階梯形。

    結合上述的前向和後向過程,即可以給出一個完整的消元法實現

    def eliminate(augmented):
        """
        利用消元法前向和後向步驟,化簡線性方程組.
        
        如果是矛盾方程組,則僅輸出前向化簡結果,並打印提示
        否則輸出簡化后的方程組,並輸出最後一列
        """
        print(np.asarray(augmented))
        A, pivots = eliminate_forward(augmented)
        print(" The echelon form is\n", A)
        print(" The pivots are: ", pivots)
        pivot_cols = {p[1] for p in pivots}
        simplified = eliminate_backward(A, pivots)
        if (A.shape[1]-1) in pivot_cols:
            print(" There is controdictory.\n", simplified)
        elif len(pivots) == (A.shape[1] -1):
            print(" Solution: ", simplified[:, -1])
            is_correct = solution_check(np.asarray(augmented), 
                                simplified[:, -1])
            print(" Is the solution correct? ", is_correct)
        else:
            print(" There are free variables.\n", simplified)
        print("-"*30)
    eliminate(aug)
    [[ 0  1 -4  8]
     [ 2 -3  2  1]
     [ 4 -8 12  1]]
     The echelon form is
     [[ 1.   -2.    3.    0.25]
     [ 0.    1.   -4.    0.5 ]
     [ 0.    0.    0.    1.  ]]
     The pivots are:  [(0, 0), (1, 1), (2, 3)]
     There is controdictory.
     [[ 1.  0. -5.  0.]
     [ 0.  1. -4.  0.]
     [ 0.  0.  0.  1.]]
    ------------------------------

    利用 Sympy 驗證消元法實現的正確性

    Python的符號計算庫Sympy,有化簡矩陣為行最簡型的方法,可以用來檢驗本節實現的代碼是否正確。

    # 導入 sympy的 Matrix模塊
    from sympy import Matrix
    Matrix(aug).rref(simplify=True)
    # 返回的是行最簡型和主元列的位置
    (Matrix([
     [1, 0, -5, 0],
     [0, 1, -4, 0],
     [0, 0,  0, 1]]), (0, 1, 3))
    echelon, pivots = eliminate_forward(aug)
    simplified = eliminate_backward(echelon, pivots)
    print(simplified, pivots)
    # 輸出與上述rref一致
    [[ 1.  0. -5.  0.]
     [ 0.  1. -4.  0.]
     [ 0.  0.  0.  1.]] [(0, 0), (1, 1), (2, 3)]

    綜合前向和後向步驟,並結果的正確性

    綜合前向和後向消元,就可以得到完整的消元法過程。
    消元結束,如果沒有矛盾(最後一列不是主元列),基本變量數與未知數個數一致,則有唯一解,可以驗證解是否正確。
    驗證的方法是將解與係數矩陣相乘,檢查與原方程的b列一致。

    def solution_check(augmented, solution):
        # 係數矩陣與解相乘
        b = augmented[:, :-1] @ solution.reshape([-1, 1])
        b = b.reshape([-1])
        # 檢查乘積向量與b列一致
        return all(np.isclose(b - augmented[:, -1], np.zeros(len(b))))
    def eliminate(augmented):
        from sympy import Matrix
        print(np.asarray(augmented))
        A, pivots = eliminate_forward(augmented)
        print(" The echelon form is\n", A)
        print(" The pivots are: ", pivots)
        pivot_cols = {p[1] for p in pivots}
        simplified = eliminate_backward(A, pivots)
        if (A.shape[1]-1) in pivot_cols: # 最後一列是主元列
            print(" There is controdictory.\n", simplified)
        elif len(pivots) == (A.shape[1] -1): # 唯一解
            is_correct = solution_check(np.asarray(augmented), 
                                simplified[:, -1])
            print(" Is the solution correct? ", is_correct)
            print(" Solution: \n", simplified)
        else: # 有自由變量
            print(" There are free variables.\n", simplified)
        print("-"*30)
        print("對比Sympy的rref結果")
        print(Matrix(augmented).rref(simplify=True))
        print("-"*30)

    測試書中的例子

    aug_1_1_1 = [[1, -2, 1, 0], 
                 [0, 2, -8, 8], 
                 [5, 0, -5, 10]]
    eliminate(aug_1_1_1)
    # 1.1 example 3
    aug_1_1_3 = [[0, 1, -4, 8],
                 [2, -3, 2, 1],
                 [4, -8, 12, 1]]
    eliminate(aug_1_1_3)
    eliminate([[1, -6, 4, 0, -1],
               [0, 2, -7, 0, 4],
               [0, 0, 1, 2, -3],
               [0, 0, 3, 1, 6]])
    eliminate([[0, -3, -6, 4, 9],
               [-1, -2, -1, 3, 1],
               [-2, -3, 0, 3, -1],
               [1, 4, 5, -9, -7]])
    
    eliminate([[0, 3, -6, 6, 4, -5],
               [3, -7, 8, -5, 8, 9],
               [3, -9, 12, -9, 6, 15]])
    [[ 1 -2  1  0]
     [ 0  2 -8  8]
     [ 5  0 -5 10]]
     The echelon form is
     [[ 1. -2.  1.  0.]
     [ 0.  1. -4.  4.]
     [ 0.  0.  1. -1.]]
     The pivots are:  [(0, 0), (1, 1), (2, 2)]
     Is the solution correct?  True
     Solution: 
     [[ 1.  0.  0.  1.]
     [ 0.  1.  0.  0.]
     [ 0.  0.  1. -1.]]
    ------------------------------
    對比Sympy的rref結果
    (Matrix([
    [1, 0, 0,  1],
    [0, 1, 0,  0],
    [0, 0, 1, -1]]), (0, 1, 2))
    ------------------------------
    [[ 0  1 -4  8]
     [ 2 -3  2  1]
     [ 4 -8 12  1]]
     The echelon form is
     [[ 1.   -2.    3.    0.25]
     [ 0.    1.   -4.    0.5 ]
     [ 0.    0.    0.    1.  ]]
     The pivots are:  [(0, 0), (1, 1), (2, 3)]
     There is controdictory.
     [[ 1.  0. -5.  0.]
     [ 0.  1. -4.  0.]
     [ 0.  0.  0.  1.]]
    ------------------------------
    對比Sympy的rref結果
    (Matrix([
    [1, 0, -5, 0],
    [0, 1, -4, 0],
    [0, 0,  0, 1]]), (0, 1, 3))
    ------------------------------
    [[ 1 -6  4  0 -1]
     [ 0  2 -7  0  4]
     [ 0  0  1  2 -3]
     [ 0  0  3  1  6]]
     The echelon form is
     [[ 1.  -6.   4.   0.  -1. ]
     [ 0.   1.  -3.5  0.   2. ]
     [ 0.   0.   1.   2.  -3. ]
     [-0.  -0.  -0.   1.  -3. ]]
     The pivots are:  [(0, 0), (1, 1), (2, 2), (3, 3)]
     Is the solution correct?  True
     Solution: 
     [[ 1.   0.   0.   0.  62. ]
     [ 0.   1.   0.   0.  12.5]
     [ 0.   0.   1.   0.   3. ]
     [-0.  -0.  -0.   1.  -3. ]]
    ------------------------------
    對比Sympy的rref結果
    (Matrix([
    [1, 0, 0, 0,   62],
    [0, 1, 0, 0, 25/2],
    [0, 0, 1, 0,    3],
    [0, 0, 0, 1,   -3]]), (0, 1, 2, 3))
    ------------------------------
    [[ 0 -3 -6  4  9]
     [-1 -2 -1  3  1]
     [-2 -3  0  3 -1]
     [ 1  4  5 -9 -7]]
     The echelon form is
     [[ 1.   1.5 -0.  -1.5  0.5]
     [-0.   1.   2.  -3.  -3. ]
     [-0.  -0.  -0.   1.  -0. ]
     [ 0.   0.   0.   0.   0. ]]
     The pivots are:  [(0, 0), (1, 1), (2, 3)]
     There are free variables.
     [[ 1.  0. -3.  0.  5.]
     [-0.  1.  2.  0. -3.]
     [-0. -0. -0.  1. -0.]
     [ 0.  0.  0.  0.  0.]]
    ------------------------------
    對比Sympy的rref結果
    (Matrix([
    [1, 0, -3, 0,  5],
    [0, 1,  2, 0, -3],
    [0, 0,  0, 1,  0],
    [0, 0,  0, 0,  0]]), (0, 1, 3))
    ------------------------------
    [[ 0  3 -6  6  4 -5]
     [ 3 -7  8 -5  8  9]
     [ 3 -9 12 -9  6 15]]
     The echelon form is
     [[ 1.         -2.33333333  2.66666667 -1.66666667  2.66666667  3.        ]
     [ 0.          1.         -2.          2.          1.33333333 -1.66666667]
     [ 0.          0.          0.          0.          1.          4.        ]]
     The pivots are:  [(0, 0), (1, 1), (2, 4)]
     There are free variables.
     [[  1.   0.  -2.   3.   0. -24.]
     [  0.   1.  -2.   2.   0.  -7.]
     [  0.   0.   0.   0.   1.   4.]]
    ------------------------------
    對比Sympy的rref結果
    (Matrix([
    [1, 0, -2, 3, 0, -24],
    [0, 1, -2, 2, 0,  -7],
    [0, 0,  0, 0, 1,   4]]), (0, 1, 4))
    ------------------------------

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

    ※帶您來看台北網站建置台北網頁設計,各種案例分享

  • .NET高級特性-Emit(1)

    .NET高級特性-Emit(1)

      在這個大數據/雲計算/人工智能研發普及的時代,Python的崛起以及Javascript的前後端的侵略,程序員與企業似乎越來越青睞動態語言所帶來的便捷性與高效性,即使靜態語言在性能,錯誤檢查等方面的優於靜態語言。對於.NETer來說,.NET做為一門靜態語言,我們不僅要打好.NET的基本功,如基本類型/語法/底層原理/錯誤檢查等知識,也要深入理解.NET的一些高級特性,來為你的工作減輕負擔和提高代碼質量。

      ok,咱們今天開始聊一聊.NET中的Emit。

    一、什麼是Emit?

      Emit含義為發出、產生的含義,這是.NET中的一組類庫,命名空間為System.Reflection.Emit,幾乎所有的.NET版本(Framework/Mono/NetCore)都支持Emit,可以實現用C#代碼生成代碼的類庫

    二、Emit的本質

      我們知道.NET可以由各種語言進行編寫,比如VB,C++等,當然絕大部分程序員進行.NET開發都是使用C#語言進行的,這些語言都會被各自的語言解釋器解釋為IL語言並執行,而Emit類庫的作用就是用這些語言來編寫生成IL語言,並交給CLR(公共語言運行時)進行執行。

      我們先來看看IL語言長什麼樣子:

      (1) 首先我們創建一個Hello,World程序

        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("Hello World!");
            }
        }

      (2) 將程序編譯成dll文件,我們可以看到在開發目錄下生成了bin文件夾

      

      (3) 向下尋找,我們可以看到dll文件已經生成,筆者使用netcore3進行開發,故路徑為bin/Debug/netcoreapp3.0

      

      (4) 這時候,我們就要祭出我們的il查看神器了,ildasm工具

      

      如何找到這個工具?打開開始菜單,找到Visual Studio文件夾,打開Developer Command Prompt,在打開的命令行中鍵入ildasm回車即可,筆者使用vs2019進行演示,其它vs版本操作方法均一致

      

     

     

     

     

     

     

       (5) 在dasm菜單欄選擇文件->打開,選擇剛剛生成的dll文件

      

     

     

       (6) 即可查看生成il代碼

      

     

      有了ildasm的輔助,我們就能夠更好的了解IL語言以及如何編寫IL語言,此外,Visual Studio中還有許多插件支持查看il代碼,比如JetBrains出品的Resharper插件等,如果覺得筆者方式較為麻煩可以使用以上插件查看il代碼

    三、理解IL代碼

      在上一章節中,我們理解了Emit的本質其實就是用C#來編寫IL代碼,既然要編寫IL代碼,那麼我們首先要理解IL代碼是如何進行工作的,IL代碼是如何完成C#當中的順序/選擇/循環結構的,是如何實現類的定義/字段的定義/屬性的定義/方法的定義的。

      IL代碼是一種近似於指令式的代碼語言,與彙編語言比較相近,所以習慣於寫高級語言的.NETer來說比較難以理解

      讓我們來看看Hello,World程序的IL代碼:

    IL_0000:  nop
    IL_0001:  ldstr      "Hello World!"
    IL_0006:  call       void [System.Console]System.Console::WriteLine(string)
    IL_000b:  nop
    IL_000c:  ret

      我們可以把IL代碼看成棧的運行

      第一條指令,nop表示不做任何事情,表示代碼不做任何事情

      第二條指令,ldstr表示將字符串放入棧中,字符串的值為“Hello,World!”

      第三條指令,call表示調用方法,參數為調用方法的方法信息,並把返回的結構壓入棧中,使用的參數為之前已經入棧的“Hello World!”,以此類推,如果方法有n個參數,那麼他就會調取棧中n個數據,並返回一個結果放回棧中

      第四條指令,nop表示不做任何事情

      第五條指令,ret表示將棧中頂部的數據返回,如果方法定義為void,則無返回值

      關於Hello,world程序IL的理解就說到這裏,更多的指令含義讀者可以參考微軟官方文檔,筆者之後也會繼續對Emit進行講解和Emit的應用

    四、用Emit類庫編寫IL代碼

      既然IL代碼咱們理解的差不多了,咱們就開始嘗試用C#來寫IL代碼了,有了IL代碼的參考,咱們也可以依葫蘆畫瓢的把代碼寫出來了

      (1) 引入Emit命名空間

    using System.Reflection.Emit;

      (2) 首先我們定義一個Main方法,入參無,返回類型void

    //定義方法名,返回類型,輸入類型
    var method = new DynamicMethod("Main", null, Type.EmptyTypes);

      (3) 生成IL代碼

    //生成IL代碼
    var ilGenerator = method.GetILGenerator();
    ilGenerator.Emit(OpCodes.Nop);
    ilGenerator.Emit(OpCodes.Ldstr,"Hello World!");
    ilGenerator.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) })); //尋找Console的WriteLine方法
    ilGenerator.Emit(OpCodes.Nop);
    ilGenerator.Emit(OpCodes.Ret);

      (4) 創建委託並調用

    //創建委託
    var helloWorldMethod = method.CreateDelegate(typeof(Action)) as Action;
    helloWorldMethod.Invoke();

      (5)運行,即輸出Hello World!

    五、小結

      Emit的本質是使用高級語言生成IL代碼,進而進行調用的的一組類庫,依賴Emit我們可以實現用代碼生成代碼的操作,即編程語言的自舉,可以有效彌補靜態語言的靈活性的缺失。

      Emit的性能非常好,除了第一次構建IL代碼所需要時間外,之後只要將操作緩存在計算機內存中,速度與手寫代碼相差無幾

      有許多著名.NET類庫均依賴於Emit:

      (.NET JSON操作庫)Json.NET/Newtonsoft.Json:

      (輕量ORM)Dapper:

      (ObjectToObjectMapper)EmitMapper:

      (AOP庫)Castle.DynamicProxy:

      學習Emit:

      .NET官方文檔:

      .NET API瀏覽器:

      之後作者將繼續講解.NET Emit的相關內容和應用,感謝閱讀

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

    ※帶您來看台北網站建置台北網頁設計,各種案例分享

  • 源碼分析RocketMQ消息軌跡

    源碼分析RocketMQ消息軌跡

    目錄

    本文沿着的思路,從如下3個方面對其源碼進行解讀:

    1. 發送消息軌跡
    2. 消息軌跡格式
    3. 存儲消息軌跡數據

    @(本節目錄)

    1、發送消息軌跡流程

    首先我們來看一下在消息發送端如何啟用消息軌跡,示例代碼如下:

    public class TraceProducer {
        public static void main(String[] args) throws MQClientException, InterruptedException {
            DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName",true);      // @1
            producer.setNamesrvAddr("127.0.0.1:9876");
            producer.start();
            for (int i = 0; i < 10; i++)
                try {
                    {
                        Message msg = new Message("TopicTest",
                            "TagA",
                            "OrderID188",
                            "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));
                        SendResult sendResult = producer.send(msg);
                        System.out.printf("%s%n", sendResult);
                    }
    
                } catch (Exception e) {
                    e.printStackTrace();
                }
            producer.shutdown();
        }
    }

    從上述代碼可以看出其關鍵點是在創建DefaultMQProducer時指定開啟消息軌跡跟蹤。我們不妨瀏覽一下DefaultMQProducer與啟用消息軌跡相關的構造函數:

    public DefaultMQProducer(final String producerGroup, boolean enableMsgTrace)
    public DefaultMQProducer(final String producerGroup, boolean enableMsgTrace, final String customizedTraceTopic)

    參數如下:

    • String producerGroup
      生產者所屬組名。
    • boolean enableMsgTrace
      是否開啟跟蹤消息軌跡,默認為false。
    • String customizedTraceTopic
      如果開啟消息軌跡跟蹤,用來存儲消息軌跡數據所屬的主題名稱,默認為:RMQ_SYS_TRACE_TOPIC。

    1.1 DefaultMQProducer構造函數

    public DefaultMQProducer(final String producerGroup, RPCHook rpcHook, boolean enableMsgTrace,final String customizedTraceTopic) {      // @1
        this.producerGroup = producerGroup;
        defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook);
        //if client open the message trace feature
        if (enableMsgTrace) {                                                                                                                                                                                            // @2
            try {
                AsyncTraceDispatcher dispatcher = new AsyncTraceDispatcher(customizedTraceTopic, rpcHook);                                                         
                dispatcher.setHostProducer(this.getDefaultMQProducerImpl());
                traceDispatcher = dispatcher;
                this.getDefaultMQProducerImpl().registerSendMessageHook(
                    new SendMessageTraceHookImpl(traceDispatcher));                                                                                                                             // @3
            } catch (Throwable e) {
                log.error("system mqtrace hook init failed ,maybe can't send msg trace data");
            }
        }
    }

    代碼@1:首先介紹一下其局部變量。

    • String producerGroup
      生產者所屬組。
    • RPCHook rpcHook
      生產者發送鈎子函數。
    • boolean enableMsgTrace
      是否開啟消息軌跡跟蹤。
    • String customizedTraceTopic
      定製用於存儲消息軌跡的數據。

    代碼@2:用來構建AsyncTraceDispatcher,看其名:異步轉發消息軌跡數據,稍後重點關注。

    代碼@3:構建SendMessageTraceHookImpl對象,並使用AsyncTraceDispatcher用來異步轉發。

    1.2 SendMessageTraceHookImpl鈎子函數

    1.2.1 SendMessageTraceHookImpl類圖

    1. SendMessageHook
      消息發送鈎子函數,用於在消息發送之前、發送之後執行一定的業務邏輯,是記錄消息軌跡的最佳擴展點。
    2. TraceDispatcher
      消息軌跡轉發處理器,其默認實現類AsyncTraceDispatcher,異步實現消息軌跡數據的發送。下面對其屬性做一個簡單的介紹:
      • int queueSize
        異步轉發,隊列長度,默認為2048,當前版本不能修改。
      • int batchSize
        批量消息條數,消息軌跡一次消息發送請求包含的數據條數,默認為100,當前版本不能修改。
      • int maxMsgSize
        消息軌跡一次發送的最大消息大小,默認為128K,當前版本不能修改。
      • DefaultMQProducer traceProducer
        用來發送消息軌跡的消息發送者。
      • ThreadPoolExecutor traceExecuter
        線程池,用來異步執行消息發送。
      • AtomicLong discardCount
        記錄丟棄的消息個數。
      • Thread worker
        woker線程,主要負責從追加隊列中獲取一批待發送的消息軌跡數據,提交到線程池中執行。
      • ArrayBlockingQueue< TraceContext> traceContextQueue
        消息軌跡TraceContext隊列,用來存放待發送到服務端的消息。
      • ArrayBlockingQueue< Runnable> appenderQueue
        線程池內部隊列,默認長度1024。
      • DefaultMQPushConsumerImpl hostConsumer
        消費者信息,記錄消息消費時的軌跡信息。
      • String traceTopicName
        用於跟蹤消息軌跡的topic名稱。

    1.2.2 源碼分析SendMessageTraceHookImpl

    1.2.2.1 sendMessageBefore
    public void sendMessageBefore(SendMessageContext context) { 
        //if it is message trace data,then it doesn't recorded
        if (context == null || context.getMessage().getTopic().startsWith(((AsyncTraceDispatcher) localDispatcher).getTraceTopicName())) {   // @1
            return;
        }
        //build the context content of TuxeTraceContext
        TraceContext tuxeContext = new TraceContext();
        tuxeContext.setTraceBeans(new ArrayList<TraceBean>(1));
        context.setMqTraceContext(tuxeContext);
        tuxeContext.setTraceType(TraceType.Pub);
        tuxeContext.setGroupName(context.getProducerGroup());                                                                                                                       // @2
        //build the data bean object of message trace
        TraceBean traceBean = new TraceBean();                                                                                                                                                // @3
        traceBean.setTopic(context.getMessage().getTopic());
        traceBean.setTags(context.getMessage().getTags());
        traceBean.setKeys(context.getMessage().getKeys());
        traceBean.setStoreHost(context.getBrokerAddr());
        traceBean.setBodyLength(context.getMessage().getBody().length);
        traceBean.setMsgType(context.getMsgType());
        tuxeContext.getTraceBeans().add(traceBean);
    }

    代碼@1:如果topic主題為消息軌跡的Topic,直接返回。

    代碼@2:在消息發送上下文中,設置用來跟蹤消息軌跡的上下環境,裏面主要包含一個TraceBean集合、追蹤類型(TraceType.Pub)與生產者所屬的組。

    代碼@3:構建一條跟蹤消息,用TraceBean來表示,記錄原消息的topic、tags、keys、發送到broker地址、消息體長度等消息。

    從上文看出,sendMessageBefore主要的用途就是在消息發送的時候,先準備一部分消息跟蹤日誌,存儲在發送上下文環境中,此時並不會發送消息軌跡數據。

    1.2.2.2 sendMessageAfter
    public void sendMessageAfter(SendMessageContext context) {
        //if it is message trace data,then it doesn't recorded
        if (context == null || context.getMessage().getTopic().startsWith(((AsyncTraceDispatcher) localDispatcher).getTraceTopicName())     // @1
            || context.getMqTraceContext() == null) {
            return;
        }
        if (context.getSendResult() == null) {
            return;
        }
    
        if (context.getSendResult().getRegionId() == null
            || !context.getSendResult().isTraceOn()) {
            // if switch is false,skip it
            return;
        }
    
        TraceContext tuxeContext = (TraceContext) context.getMqTraceContext();
        TraceBean traceBean = tuxeContext.getTraceBeans().get(0);                                                                                                // @2
        int costTime = (int) ((System.currentTimeMillis() - tuxeContext.getTimeStamp()) / tuxeContext.getTraceBeans().size());     // @3
        tuxeContext.setCostTime(costTime);                                                                                                                                      // @4
        if (context.getSendResult().getSendStatus().equals(SendStatus.SEND_OK)) {                                                                    
            tuxeContext.setSuccess(true);
        } else {
            tuxeContext.setSuccess(false);
        }
        tuxeContext.setRegionId(context.getSendResult().getRegionId());                                                                                      
        traceBean.setMsgId(context.getSendResult().getMsgId());
        traceBean.setOffsetMsgId(context.getSendResult().getOffsetMsgId());
        traceBean.setStoreTime(tuxeContext.getTimeStamp() + costTime / 2);
        localDispatcher.append(tuxeContext);                                                                                                                                   // @5
    }

    代碼@1:如果topic主題為消息軌跡的Topic,直接返回。

    代碼@2:從MqTraceContext中獲取跟蹤的TraceBean,雖然設計成List結構體,但在消息發送場景,這裏的數據永遠只有一條,及時是批量發送也不例外。

    代碼@3:獲取消息發送到收到響應結果的耗時。

    代碼@4:設置costTime(耗時)、success(是否發送成功)、regionId(發送到broker所在的分區)、msgId(消息ID,全局唯一)、offsetMsgId(消息物理偏移量,如果是批量消息,則是最後一條消息的物理偏移量)、storeTime,這裏使用的是(客戶端發送時間 + 二分之一的耗時)來表示消息的存儲時間,這裡是一個估值。

    代碼@5:將需要跟蹤的信息通過TraceDispatcher轉發到Broker服務器。其代碼如下:

    public boolean append(final Object ctx) {
        boolean result = traceContextQueue.offer((TraceContext) ctx);
        if (!result) {
            log.info("buffer full" + discardCount.incrementAndGet() + " ,context is " + ctx);
        }
        return result;
    }

    這裏一個非常關鍵的點是offer方法的使用,當隊列無法容納新的元素時會立即返回false,並不會阻塞。

    接下來將目光轉向TraceDispatcher的實現。

    1.3 TraceDispatcher實現原理

    TraceDispatcher,用於客戶端消息軌跡數據轉發到Broker,其默認實現類:AsyncTraceDispatcher。

    1.3.1 TraceDispatcher構造函數

    public AsyncTraceDispatcher(String traceTopicName, RPCHook rpcHook) throws MQClientException {    
        // queueSize is greater than or equal to the n power of 2 of value
        this.queueSize = 2048;
        this.batchSize = 100;
        this.maxMsgSize = 128000;                                        
        this.discardCount = new AtomicLong(0L);         
        this.traceContextQueue = new ArrayBlockingQueue<TraceContext>(1024);
        this.appenderQueue = new ArrayBlockingQueue<Runnable>(queueSize);
        if (!UtilAll.isBlank(traceTopicName)) {
            this.traceTopicName = traceTopicName;
        } else {
            this.traceTopicName = MixAll.RMQ_SYS_TRACE_TOPIC;
        }                   // @1
        this.traceExecuter = new ThreadPoolExecutor(// :
            10, //
            20, //
            1000 * 60, //
            TimeUnit.MILLISECONDS, //
            this.appenderQueue, //
            new ThreadFactoryImpl("MQTraceSendThread_"));
        traceProducer = getAndCreateTraceProducer(rpcHook);      // @2
    }

    代碼@1:初始化核心屬性,該版本這些值都是“固化”的,用戶無法修改。

    • queueSize
      隊列長度,默認為2048,異步線程池能夠積壓的消息軌跡數量。
    • batchSize
      一次向Broker批量發送的消息條數,默認為100.
    • maxMsgSize
      向Broker彙報消息軌跡時,消息體的總大小不能超過該值,默認為128k。
    • discardCount
      整個運行過程中,丟棄的消息軌跡數據,這裏要說明一點的是,如果消息TPS發送過大,異步轉發線程處理不過來時,會主動丟棄消息軌跡數據。
    • traceContextQueue
      traceContext積壓隊列,客戶端(消息發送、消息消費者)在收到處理結果后,將消息軌跡提交到噶隊列中,則會立即返回。
    • appenderQueue
      提交到Broker線程池中隊列。
    • traceTopicName
      用於接收消息軌跡的Topic,默認為RMQ_SYS_TRANS_HALF_TOPIC。
    • traceExecuter
      用於發送到Broker服務的異步線程池,核心線程數默認為10,最大線程池為20,隊列堆積長度2048,線程名稱:MQTraceSendThread_。、
    • traceProducer
      發送消息軌跡的Producer。

    代碼@2:調用getAndCreateTraceProducer方法創建用於發送消息軌跡的Producer(消息發送者),下面詳細介紹一下其實現。

    1.3.2 getAndCreateTraceProducer詳解

    private DefaultMQProducer getAndCreateTraceProducer(RPCHook rpcHook) {
            DefaultMQProducer traceProducerInstance = this.traceProducer;
            if (traceProducerInstance == null) {  //@1
                traceProducerInstance = new DefaultMQProducer(rpcHook);
                traceProducerInstance.setProducerGroup(TraceConstants.GROUP_NAME);
                traceProducerInstance.setSendMsgTimeout(5000);
                traceProducerInstance.setVipChannelEnabled(false);
                // The max size of message is 128K
                traceProducerInstance.setMaxMessageSize(maxMsgSize - 10 * 1000);
            }
            return traceProducerInstance;
        }

    代碼@1:如果還未建立發送者,則創建用於發送消息軌跡的消息發送者,其GroupName為:_INNER_TRACE_PRODUCER,消息發送超時時間5s,最大允許發送消息大小118K。

    1.3.3 start

    public void start(String nameSrvAddr) throws MQClientException {
        if (isStarted.compareAndSet(false, true)) {     // @1
            traceProducer.setNamesrvAddr(nameSrvAddr);
            traceProducer.setInstanceName(TRACE_INSTANCE_NAME + "_" + nameSrvAddr);
            traceProducer.start();
        }
        this.worker = new Thread(new AsyncRunnable(), "MQ-AsyncTraceDispatcher-Thread-" + dispatcherId);   // @2
        this.worker.setDaemon(true);
        this.worker.start();                                                                                   
        this.registerShutDownHook();
    }

    開始啟動,其調用的時機為啟動DefaultMQProducer時,如果啟用跟蹤消息軌跡,則調用之。

    代碼@1:如果用於發送消息軌跡的發送者沒有啟動,則設置nameserver地址,並啟動着。

    代碼@2:啟動一個線程,用於執行AsyncRunnable任務,接下來將重點介紹。

    1.3.4 AsyncRunnable

    class AsyncRunnable implements Runnable {
             private boolean stopped;
        public void run() {
            while (!stopped) {
                List<TraceContext> contexts = new ArrayList<TraceContext>(batchSize);     // @1
                for (int i = 0; i < batchSize; i++) {
                    TraceContext context = null;
                    try {
                        //get trace data element from blocking Queue — traceContextQueue
                        context = traceContextQueue.poll(5, TimeUnit.MILLISECONDS);        // @2
                    } catch (InterruptedException e) {
                    }
                    if (context != null) {
                        contexts.add(context);
                    } else {
                        break;
                    }
                }
                if (contexts.size() > 0) {                                                                               :
                    AsyncAppenderRequest request = new AsyncAppenderRequest(contexts);  // @3
                    traceExecuter.submit(request);                                                               
                } else if (AsyncTraceDispatcher.this.stopped) {
                    this.stopped = true;
                }
            }
        }
    }

    代碼@1:構建待提交消息跟蹤Bean,每次最多發送batchSize,默認為100條。

    代碼@2:從traceContextQueue中取出一個待提交的TraceContext,設置超時時間為5s,即如何該隊列中沒有待提交的TraceContext,則最多等待5s。

    代碼@3:向線程池中提交任務AsyncAppenderRequest。

    1.3.5 AsyncAppenderRequest#sendTraceData

    public void sendTraceData(List<TraceContext> contextList) {
        Map<String, List<TraceTransferBean>> transBeanMap = new HashMap<String, List<TraceTransferBean>>();
        for (TraceContext context : contextList) {        //@1
            if (context.getTraceBeans().isEmpty()) {
                continue;
            }
            // Topic value corresponding to original message entity content
            String topic = context.getTraceBeans().get(0).getTopic();     // @2
            // Use  original message entity's topic as key
            String key = topic;
            List<TraceTransferBean> transBeanList = transBeanMap.get(key);
            if (transBeanList == null) {
                transBeanList = new ArrayList<TraceTransferBean>();
                transBeanMap.put(key, transBeanList);
            }
            TraceTransferBean traceData = TraceDataEncoder.encoderFromContextBean(context);    // @3
            transBeanList.add(traceData);
        }
        for (Map.Entry<String, List<TraceTransferBean>> entry : transBeanMap.entrySet()) {       // @4
            flushData(entry.getValue());
        }
    }

    代碼@1:遍歷收集的消息軌跡數據。

    代碼@2:獲取存儲消息軌跡的Topic。

    代碼@3:對TraceContext進行編碼,這裡是消息軌跡的傳輸數據,稍後對其詳細看一下,了解其上傳的格式。

    代碼@4:將編碼后的數據發送到Broker服務器。

    1.3.6 TraceDataEncoder#encoderFromContextBean

    根據消息軌跡跟蹤類型,其格式會有一些不一樣,下面分別來介紹其合適。

    1.3.6.1 PUB(消息發送)
    case Pub: {
        TraceBean bean = ctx.getTraceBeans().get(0);
        //append the content of context and traceBean to transferBean's TransData
        sb.append(ctx.getTraceType()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(ctx.getTimeStamp()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(ctx.getRegionId()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(ctx.getGroupName()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(bean.getTopic()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(bean.getMsgId()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(bean.getTags()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(bean.getKeys()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(bean.getStoreHost()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(bean.getBodyLength()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(ctx.getCostTime()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(bean.getMsgType().ordinal()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(bean.getOffsetMsgId()).append(TraceConstants.CONTENT_SPLITOR)//
         .append(ctx.isSuccess()).append(TraceConstants.FIELD_SPLITOR);
    }

    消息軌跡數據的協議使用字符串拼接,字段的分隔符號為1,整個數據以2結尾,感覺這個設計還是有點“不可思議”,為什麼不直接使用json協議呢?

    1.3.6.2 SubBefore(消息消費之前)
    for (TraceBean bean : ctx.getTraceBeans()) {
        sb.append(ctx.getTraceType()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(ctx.getTimeStamp()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(ctx.getRegionId()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(ctx.getGroupName()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(ctx.getRequestId()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(bean.getMsgId()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(bean.getRetryTimes()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(bean.getKeys()).append(TraceConstants.FIELD_SPLITOR);//
        }
    }

    軌跡就是按照上述順序拼接而成,各個字段使用1分隔,每一條記錄使用2結尾。

    1.3.2.3 SubAfter(消息消費后)
    case SubAfter: {
        for (TraceBean bean : ctx.getTraceBeans()) {
            sb.append(ctx.getTraceType()).append(TraceConstants.CONTENT_SPLITOR)//
              .append(ctx.getRequestId()).append(TraceConstants.CONTENT_SPLITOR)//
              .append(bean.getMsgId()).append(TraceConstants.CONTENT_SPLITOR)//
              .append(ctx.getCostTime()).append(TraceConstants.CONTENT_SPLITOR)//
              .append(ctx.isSuccess()).append(TraceConstants.CONTENT_SPLITOR)//
              .append(bean.getKeys()).append(TraceConstants.CONTENT_SPLITOR)//
              .append(ctx.getContextCode()).append(TraceConstants.FIELD_SPLITOR);
            }
        }
    }

    格式編碼一樣,就不重複多說。

    經過上面的源碼跟蹤,消息發送端的消息軌跡跟蹤流程、消息軌跡數據編碼協議就清晰了,接下來我們使用一張序列圖來結束本部分的講解。

    其實行文至此,只關注了消息發送的消息軌跡跟蹤,消息消費的軌跡跟蹤又是如何呢?其實現原理其實是一樣的,就是在消息消費前後執行特定的鈎子函數,其實現類為ConsumeMessageTraceHookImpl,由於其實現與消息發送的思路類似,故就不詳細介紹了。

    2、 消息軌跡數據如何存儲

    其實從上面的分析,我們已經得知,RocketMQ的消息軌跡數據存儲在到Broker上,那消息軌跡的主題名如何指定?其路由信息又怎麼分配才好呢?是每台Broker上都創建還是只在其中某台上創建呢?RocketMQ支持系統默認與自定義消息軌跡的主題。

    2.1 使用系統默認的主題名稱

    RocketMQ默認的消息軌跡主題為:RMQ_SYS_TRACE_TOPIC,那該Topic需要手工創建嗎?其路由信息呢?

    {
        if (this.brokerController.getBrokerConfig().isTraceTopicEnable()) {    // @1
            String topic = this.brokerController.getBrokerConfig().getMsgTraceTopicName();
            TopicConfig topicConfig = new TopicConfig(topic);
            this.systemTopicList.add(topic);
            topicConfig.setReadQueueNums(1);                                              // @2
            topicConfig.setWriteQueueNums(1);
            this.topicConfigTable.put(topicConfig.getTopicName(), topicConfig);
        }
    }

    上述代碼出自TopicConfigManager的構造函數,在Broker啟動的時候會創建topicConfigManager對象,用來管理topic的路由信息。

    代碼@1:如果Broker開啟了消息軌跡跟蹤(traceTopicEnable=true)時,會自動創建默認消息軌跡的topic路由信息,注意其讀寫隊列數為1。

    2.2 用戶自定義消息軌跡主題

    在創建消息發送者、消息消費者時,可以显示的指定消息軌跡的Topic,例如:

    public DefaultMQProducer(final String producerGroup, RPCHook rpcHook, boolean enableMsgTrace,final String customizedTraceTopic)
    
    public DefaultMQPushConsumer(final String consumerGroup, RPCHook rpcHook,
            AllocateMessageQueueStrategy allocateMessageQueueStrategy, boolean enableMsgTrace, final String customizedTraceTopic)

    通過customizedTraceTopic來指定消息軌跡Topic。

    溫馨提示:通常在生產環境上,將不會開啟自動創建主題,故需要RocketMQ運維管理人員提前創建好Topic。

    好了,本文就介紹到這裏了,本文詳細介紹了RocktMQ消息軌跡的實現原理,下一篇,我們將進入到多副本的學習中。

    作者介紹:
    丁威,《RocketMQ技術內幕》作者,RocketMQ 社區佈道師,公眾號: 維護者,目前已陸續發表源碼分析Java集合、Java 併發包(JUC)、Netty、Mycat、Dubbo、RocketMQ、Mybatis等源碼專欄。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

    ※帶您來看台北網站建置台北網頁設計,各種案例分享

  • Nissan新型電動車Leaf續航增4成、可自駕和自動停車

    Nissan新型電動車Leaf續航增4成、可自駕和自動停車

    日產汽車(Nissan)6日發表了進行全面改良的電動車「Leaf」新型車款,並預計於10月2日在日本開賣,美國、加拿大、歐洲將在2018年1月開始交車。首款Leaf於2010年末開賣以來、迄今的累計銷售量約28萬台。

    新型Leaf採用新研發的鋰離子電池,電池容量為40KWh(舊型車款有24KWh和30KWh兩種),續航距離達400km、較舊型車款提升約四成,進行一般充電時約八小時可充飽電、但用急速充電時充飽80%電力約需40分鐘(舊型車款為30分鐘)。新型Leaf售價約315萬-399萬日圓(舊型車款約280萬至456萬日圓)。

    新型Leaf搭載能夠在高速公路單一車道上行駛的自動駕駛技術「ProPILOT」以及自動停車功能「ProPILOT Parking」,另外也採用能減輕駕駛負擔的「e-Pedal」功能。

    「e-Pedal」可讓駕駛單靠油門踏板執行前進、加速、減速、停止等動作。只要駕駛採下踏板就會加速,而若鬆開踏板就會減速、完全放開踏板車輛就會停止。

    路透社報導,日產為電動車的先驅者,不過近來特斯拉(Tesla)等新興業者抬頭、競爭激化,而日產期望藉由性能提升的新型Leaf展開攻勢。新型Leaf全球年銷售量目標為9萬台,且之後日產計畫在2018年推出電池容量/馬達輸出升級,續航距離更長的高階車款。

    以美國基準的續航距離來看,新款Leaf為150英里(約240km),遜於競爭對手特斯拉Model 3的220英里和通用(General Motors)BOLT的238英里。

    特斯拉Model 3的售價為3.5萬美元,截至7月28日開始在美國市場交車為止,其訂單量超過50萬台。

    日產會長Carlos Ghosn於6月28日舉行的股東會上表示,「日產在電動車界居領導位置。日產電動車累計銷售量超過60萬台、為美國特斯拉兩倍」。

    (本文內容由授權使用。圖片出處:)

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

    【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

    ※帶您來看台北網站建置台北網頁設計,各種案例分享

  • Yelp的“嚇人”更新說明:訓練神經網絡刪除全部數據

    Yelp的“嚇人”更新說明:訓練神經網絡刪除全部數據

      騰訊《一線》 紀振宇 1 月 16 日發自硅谷

      本地服務信息點評網站 Yelp 最近發生了一次可能釀成嚴重後果的運營事故:全部數據遭到刪除,幸而最終得以恢復。

      15 日,Yelp 在其移動應用的最新更新說明中稱:“我們向所有本周使用 app 過程中出現問題的用戶致歉。我們訓練了一個神經網絡用於去除應用中的所有程序缺陷,但它把所有數據都刪除了,我們只好恢復了所有數據。“

      在更新說明中公布這一事件的同時,Yelp 還不忘幽默一把,稱,“公平地說,至少短暫時間內(指所有數據被刪除后和數據恢復之前),我們是 100% 沒有 Bug 的。“

      根據 Yelp 近期應用更新說明显示, Yelp 應用最近的更新重點為去除各種漏洞、程序缺陷和改善應用功能,或許是由於程序故障排查已經成為一個常規工作,因而 Yelp 計劃訓練一套神經網絡來進行程序故障的自動排查,但卻意外發生了刪除所有數據的事故。

      Yelp 成立於 2004 年,由兩名原 Paypal 的員工創辦,被認為是本地信息服務點評的鼻祖,於 2012 年上市,目前市值約為 30 億美元。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

    ※帶您來看台北網站建置台北網頁設計,各種案例分享