標籤: 貨運

  • .Net Core微服務入門全紀錄(三)——Consul-服務註冊與發現(下)

    .Net Core微服務入門全紀錄(三)——Consul-服務註冊與發現(下)

    前言

    上一篇【.Net Core微服務入門全紀錄(二)——Consul-服務註冊與發現(上)】已經成功將我們的服務註冊到Consul中,接下來就該客戶端通過Consul去做服務發現了。

    服務發現

    • 同樣Nuget安裝一下Consul:

    • 改造一下業務系統的代碼:

    ServiceHelper.cs:

        public class ServiceHelper : IServiceHelper
        {
            private readonly IConfiguration _configuration;
    
            public ServiceHelper(IConfiguration configuration)
            {
                _configuration = configuration;
            }
    
            public async Task<string> GetOrder()
            {
                //string[] serviceUrls = { "http://localhost:9060", "http://localhost:9061", "http://localhost:9062" };//訂單服務的地址,可以放在配置文件或者數據庫等等...
    
                var consulClient = new ConsulClient(c =>
                {
                    //consul地址
                    c.Address = new Uri(_configuration["ConsulSetting:ConsulAddress"]);
                });
    
                //consulClient.Catalog.Services().Result.Response;
                //consulClient.Agent.Services().Result.Response;
                var services = consulClient.Health.Service("OrderService", null, true, null).Result.Response;//健康的服務
    
                string[] serviceUrls = services.Select(p => $"http://{p.Service.Address + ":" + p.Service.Port}").ToArray();//訂單服務地址列表
    
                if (!serviceUrls.Any())
                {
                    return await Task.FromResult("【訂單服務】服務列表為空");
                }
    
                //每次隨機訪問一個服務實例
                var Client = new RestClient(serviceUrls[new Random().Next(0, serviceUrls.Length)]);
                var request = new RestRequest("/orders", Method.GET);
    
                var response = await Client.ExecuteAsync(request);
                return response.Content;
            }
    
            public async Task<string> GetProduct()
            {
                //string[] serviceUrls = { "http://localhost:9050", "http://localhost:9051", "http://localhost:9052" };//產品服務的地址,可以放在配置文件或者數據庫等等...
    
                var consulClient = new ConsulClient(c =>
                {
                    //consul地址
                    c.Address = new Uri(_configuration["ConsulSetting:ConsulAddress"]);
                });
    
                //consulClient.Catalog.Services().Result.Response;
                //consulClient.Agent.Services().Result.Response;
                var services = consulClient.Health.Service("ProductService", null, true, null).Result.Response;//健康的服務
    
                string[] serviceUrls = services.Select(p => $"http://{p.Service.Address + ":" + p.Service.Port}").ToArray();//產品服務地址列表
    
                if (!serviceUrls.Any())
                {
                    return await Task.FromResult("【產品服務】服務列表為空");
                }
    
                //每次隨機訪問一個服務實例
                var Client = new RestClient(serviceUrls[new Random().Next(0, serviceUrls.Length)]);
                var request = new RestRequest("/products", Method.GET);
    
                var response = await Client.ExecuteAsync(request);
                return response.Content;
            }
        }
    

    appsettings.json:

    {
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft": "Warning",
          "Microsoft.Hosting.Lifetime": "Information"
        }
      },
      "AllowedHosts": "*",
      "ConsulSetting": {
        "ConsulAddress": "http://localhost:8500"
      }
    }
    

    OK,以上代碼就完成了服務列表的獲取。

    瀏覽器測試一下:

    隨便停止2個服務:

    繼續訪問:

    這時候停止的服務地址就獲取不到了,客戶端依然正常運行。

    這時候解決了服務的發現,新的問題又來了…

    • 客戶端每次要調用服務,都先去Consul獲取一下地址,這不僅浪費資源,還增加了請求的響應時間,這顯然讓人無法接受。

    那麼怎麼保證不要每次請求都去Consul獲取地址,同時又要拿到可用的地址列表呢?
    Consul提供的解決方案:——Blocking Queries (阻塞的請求)。詳情請見官網:https://www.consul.io/api-docs/features/blocking

    Blocking Queries

    這是什麼意思呢,簡單來說就是當客戶端請求Consul獲取地址列表時,需要攜帶一個版本號信息,Consul會比較這個客戶端版本號是否和Consul服務端的版本號一致,如果一致,則Consul會阻塞這個請求,直到Consul中的服務列表發生變化,或者到達阻塞時間上限;如果版本號不一致,則立即返回。這個阻塞時間默認是5分鐘,支持自定義。
    那麼我們另外啟動一個線程去干這件事情,就不會影響每次的用戶請求了。這樣既保證了客戶端服務列表的準確性,又節約了客戶端請求服務列表的次數。

    • 繼續改造代碼:
      IServiceHelper增加一個獲取服務列表的接口方法:
        public interface IServiceHelper
        {
            /// <summary>
            /// 獲取產品數據
            /// </summary>
            /// <returns></returns>
            Task<string> GetProduct();
    
            /// <summary>
            /// 獲取訂單數據
            /// </summary>
            /// <returns></returns>
            Task<string> GetOrder();
    
            /// <summary>
            /// 獲取服務列表
            /// </summary>
            void GetServices();
        }
    

    ServiceHelper實現接口:

        public class ServiceHelper : IServiceHelper
        {
            private readonly IConfiguration _configuration;
            private readonly ConsulClient _consulClient;
            private ConcurrentBag<string> _orderServiceUrls;
            private ConcurrentBag<string> _productServiceUrls;
    
            public ServiceHelper(IConfiguration configuration)
            {
                _configuration = configuration;
                _consulClient = new ConsulClient(c =>
                {
                    //consul地址
                    c.Address = new Uri(_configuration["ConsulSetting:ConsulAddress"]);
                });
            }
    
            public async Task<string> GetOrder()
            {
                if (_productServiceUrls == null)
                    return await Task.FromResult("【訂單服務】正在初始化服務列表...");
    
                //每次隨機訪問一個服務實例
                var Client = new RestClient(_orderServiceUrls.ElementAt(new Random().Next(0, _orderServiceUrls.Count())));
                var request = new RestRequest("/orders", Method.GET);
    
                var response = await Client.ExecuteAsync(request);
                return response.Content;
            }
    
            public async Task<string> GetProduct()
            {
                if(_productServiceUrls == null)
                    return await Task.FromResult("【產品服務】正在初始化服務列表...");
    
                //每次隨機訪問一個服務實例
                var Client = new RestClient(_productServiceUrls.ElementAt(new Random().Next(0, _productServiceUrls.Count())));
                var request = new RestRequest("/products", Method.GET);
    
                var response = await Client.ExecuteAsync(request);
                return response.Content;
            }
    
            public void GetServices()
            {
                var serviceNames = new string[] { "OrderService", "ProductService" };
                Array.ForEach(serviceNames, p =>
                {
                    Task.Run(() =>
                    {
                        //WaitTime默認為5分鐘
                        var queryOptions = new QueryOptions { WaitTime = TimeSpan.FromMinutes(10) };
                        while (true)
                        {
                            GetServices(queryOptions, p);
                        }
                    });
                });
            }
            private void GetServices(QueryOptions queryOptions, string serviceName)
            {
                var res = _consulClient.Health.Service(serviceName, null, true, queryOptions).Result;
                
                //控制台打印一下獲取服務列表的響應時間等信息
                Console.WriteLine($"{DateTime.Now}獲取{serviceName}:queryOptions.WaitIndex:{queryOptions.WaitIndex}  LastIndex:{res.LastIndex}");
    
                //版本號不一致 說明服務列表發生了變化
                if (queryOptions.WaitIndex != res.LastIndex)
                {
                    queryOptions.WaitIndex = res.LastIndex;
    
                    //服務地址列表
                    var serviceUrls = res.Response.Select(p => $"http://{p.Service.Address + ":" + p.Service.Port}").ToArray();
    
                    if (serviceName == "OrderService")
                        _orderServiceUrls = new ConcurrentBag<string>(serviceUrls);
                    else if (serviceName == "ProductService")
                        _productServiceUrls = new ConcurrentBag<string>(serviceUrls);
                }
            }
        }
    

    Startup的Configure方法中調用一下獲取服務列表:

            public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceHelper serviceHelper)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                else
                {
                    app.UseExceptionHandler("/Home/Error");
                }
                app.UseStaticFiles();
    
                app.UseRouting();
    
                app.UseAuthorization();
    
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapControllerRoute(
                        name: "default",
                        pattern: "{controller=Home}/{action=Index}/{id?}");
                });
    
                //程序啟動時 獲取服務列表
                serviceHelper.GetServices();
            }
    

    代碼完成,運行測試:

    現在不用每次先請求服務列表了,是不是流暢多了?

    看一下控制台打印:

    這時候如果服務列表沒有發生變化的話,獲取服務列表的請求會一直阻塞到我們設置的10分鐘。

    隨便停止2個服務:

    這時候可以看到,數據被立馬返回了。

    繼續訪問客戶端網站,同樣流暢。
    (gif圖傳的有點問題。。。)

    至此,我們就通過Consul完成了服務的註冊與發現。
    接下來又引發新的思考。。。

    1. 每個客戶端系統都去維護這一堆服務地址,合理嗎?
    2. 服務的ip端口直接暴露給所有客戶端,安全嗎?
    3. 這種模式下怎麼做到客戶端的統一管理呢?

    代碼放在:https://github.com/xiajingren/NetCoreMicroserviceDemo

    未完待續…

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

    【其他文章推薦】

    ※超省錢租車方案

    ※別再煩惱如何寫文案,掌握八大原則!

    ※回頭車貨運收費標準

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

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

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

  • 加州野火每5秒燒1英畝 吞噬酒莊數萬人撤離

    摘錄自2020年9月28日中央社報導

    美國加州野火在強風助長下,每5秒鐘延燒約1英畝的土地,蔓延到世界知名的葡萄酒之鄉,納帕(Napa)與索諾馬(Sonoma)山谷今天有數以萬計的民眾被迫逃離家園。

    根據美國國家海洋暨大氣總署(NOAA)衛星影像,昨天清晨約4時從納帕山谷爆發的「玻璃之火」(Glass Fire),昨晚延燒了2500英畝的土地,到了今早擴大到1萬1000英畝,相當於每5秒鐘燒掉約1英畝(約0.4公頃)。

    法新社報導,加州森林防火廳(Cal Fire)說,加州野火把天空染成橘紅色,在悶熱的熱浪侵襲之下,火勢以「危險的速度」蔓延,且沒有一處獲得控制,沿途燒毀數座葡萄園與建築物。

    官員說,當局已下令近3萬4000名居民疏散,並要求約1萬4000人準備立即撤離,因為「迅速蔓延的火勢」延燒到乾燥的植被以及難以進入的山區。

    氣候變遷
    國際新聞
    美國
    加州
    野火
    森林野火

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

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

    ※台北網頁設計公司全省服務真心推薦

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

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

  • 沙烏地首間狗狗咖啡廳開張 愛犬人士好去處

    摘錄自2020年9月29日中央社報導

    沙烏地阿拉伯沿海城市霍巴(Khobar)在6月新開一間寵物咖啡廳The Barking Lot,這是非常保守的沙國境內首家狗狗友善咖啡廳。在伊斯蘭世界,狗被視為不潔的動物,沙烏地阿拉伯的公共場所通常禁止犬類出沒。

    沙國曾禁止民眾在街上遛寵物,但這項禁令普遍被人民忽視,民眾在街上遛寵物的景象越來越常見,好幾個城市的動物收容所如雨後春筍般出現。沙烏地王儲穆罕默德.沙爾曼(Crown Prince Mohammed bin Salman)推動現代化改革,領養流浪動物也變得越來越普遍。

    The Barking Lot老闆阿邁德(Dalal Ahmed)告訴法新社:「我之前帶著狗狗來到沙烏地阿拉伯,但被禁止跟牠一起在海灘上散步。」、「我非常難過,因此決定開一家咖啡廳幫助有養狗的人,甚至是那些沒有養狗的人。」

    生物多樣性
    國際新聞
    沙烏地阿拉伯
    同伴動物
    流浪動物

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

    ※別再煩惱如何寫文案,掌握八大原則!

    ※超省錢租車方案

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

  • 巴西廢除保護紅樹林法規 環團怒轟犯罪

    摘錄自2020年9月29日自由時報報導

    巴西波索納洛(Jair Bolsonaro)政府28日廢除了保護紅樹林和其他脆弱沿海生態系統的法規,將使這類土地得以開發,環保團體警告此舉將造成災難性影響,直言這是危害社會的「罪行」。

    綜合外媒報導,28日巴西的國家環境委員會會議決議撤銷一系列環保法規,其中,2002年創設、保護巴西許多熱帶紅樹林和大西洋沿岸沙丘灌木叢的「永久保護區」被廢除。

    環保人士警告,放寬法規將使這類土地得以開發,可能對其生態系統造成災難性影響。巴西非政府組織「搶救大西洋叢林」(SOS Mata Atlantica)負責人曼托瓦尼(Mario Mantovani)說,「這些地區已經受到房地產開發帶來的巨大壓力」。

    外媒指出,4000平方公尺紅樹林可吸收的二氧化碳量,與同等面積亞馬遜雨林吸收的二氧化碳量幾乎相同。

    土地利用
    國際新聞
    巴西
    紅樹林
    大西洋

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

    【其他文章推薦】

    ※超省錢租車方案

    ※別再煩惱如何寫文案,掌握八大原則!

    ※回頭車貨運收費標準

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

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

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

  • 面試必問:分佈式鎖實現之zk(Zookeeper)

    面試必問:分佈式鎖實現之zk(Zookeeper)

    點贊再看,養成習慣,微信搜索【三太子敖丙】關注這個互聯網苟且偷生的工具人。

    本文 GitHub https://github.com/JavaFamily 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。

    前言

    鎖我想不需要我過多的去說,大家都知道是怎麼一回事了吧?

    在多線程環境下,由於上下文的切換,數據可能出現不一致的情況或者數據被污染,我們需要保證數據安全,所以想到了加鎖。

    所謂的加鎖機制呢,就是當一個線程訪問該類的某個數據時,進行保護,其他線程不能進行訪問,直到該線程讀取完,其他線程才可使用。

    還記得我之前說過Redis在分佈式的情況下,需要對存在併發競爭的數據進行加鎖,老公們十分費解,Redis是單線程的嘛?為啥還要加鎖呢?

    看來老公們還是年輕啊,你說的不需要加鎖的情況是這樣的:

    單個服務去訪問Redis的時候,確實因為Redis本身單線程的原因是不用考慮線程安全的,但是,現在有哪個公司還是單機的呀?肯定都是分佈式集群了嘛。

    老公們你看下這樣的場景是不是就有問題了:

    你們經常不是說秒殺嘛,拿到庫存判斷,那老婆告訴你分佈式情況就是會出問題的。

    我們為了減少DB的壓力,把庫存預熱到了KV,現在KV的庫存是1。

    1. 服務A去Redis查詢到庫存發現是1,那說明我能搶到這個商品對不對,那我就準備減一了,但是還沒減。
    2. 同時服務B也去拿發現也是1,那我也搶到了呀,那我也減。
    3. C同理。
    4. 等所有的服務都判斷完了,你發現誒,怎麼變成-2了,超賣了呀,這下完了。

    老公們是不是發現問題了,這就需要分佈式鎖的介入了,我會分三個章節去分別介紹分佈式鎖的三種實現方式(Zookeeper,Redis,MySQL),說出他們的優缺點,以及一般大廠的實踐場景。

    正文

    一個騷里騷氣的面試官啥也沒拿的就走了進來,你一看,這不是你老婆嘛,你正準備叫他的時候,發現他一臉嚴肅,死鬼還裝嚴肅,肯定會給我放水的吧。

    B站搜:三太子敖丙

    咳咳,我們啥也不說了,開始今天的面試吧。

    正常線程進程同步的機制有哪些?

    • 互斥:互斥的機制,保證同一時間只有一個線程可以操作共享資源 synchronized,Lock等。
    • 臨界值:讓多線程串行話去訪問資源
    • 事件通知:通過事件的通知去保證大家都有序訪問共享資源
    • 信號量:多個任務同時訪問,同時限制數量,比如發令槍CDL,Semaphore等

    那分佈式鎖你了解過有哪些么?

    分佈式鎖實現主要以Zookeeper(以下簡稱zk)、Redis、MySQL這三種為主。

    那先跟我聊一下zk吧,你能說一下他常見的使用場景么?

    他主要的應用場景有以下幾個:

    • 服務註冊與訂閱(共用節點)
    • 分佈式通知(監聽znode)
    • 服務命名(znode特性)
    • 數據訂閱、發布(watcher)
    • 分佈式鎖(臨時節點)

    zk是啥?

    他是個數據庫,文件存儲系統,並且有監聽通知機制(觀察者模式)

    存文件系統,他存了什麼?

    節點

    zk的節點類型有4大類

    • 持久化節點(zk斷開節點還在)

    • 持久化順序編號目錄節點

    • 臨時目錄節點(客戶端斷開後節點就刪除了)

    • 臨時目錄編號目錄節點

    節點名稱都是唯一的。

    節點怎麼創建?

    我特么,這樣問的么?可是我面試只看了分佈式鎖,我得好好想想!!!

    還好我之前在自己的服務器搭建了一個zk的集群,我剛好跟大家回憶一波。

    create /test laogong // 創建永久節點 

    那臨時節點呢?

    create -e /test laogong // 創建臨時節點

    臨時節點就創建成功了,如果我斷開這次鏈接,這個節點自然就消失了,這是我的一個zk管理工具,目錄可能清晰點。

    如何創建順序節點呢?

    create -s /test // 創建順序節點

    臨時順序節點呢?

    我想聰明的老公都會搶答了

    create -e -s /test  // 創建臨時順序節點

    我退出后,重新連接,發現剛才創建的所有臨時節點都沒了。

    開篇演示這麼多呢,我就是想給大家看到的zk大概的一個操作流程和數據結構,中間涉及的搭建以及其他的技能我就不說了,我們重點聊一下他在分佈式鎖中的實現。

    zk就是基於節點去實現各種分佈式鎖的。

    就拿開頭的場景來說,zk應該怎麼去保證分佈式情況下的線程安全呢?併發競爭他是怎麼控制的呢?

    為了模擬併發競爭這樣一個情況,我寫了點偽代碼,大家可以先看看

    我定義了一個庫存inventory值為1,還用到了一個CountDownLatch發令槍,等10個線程都就緒了一起去扣減庫存。

    是不是就像10台機器一起去拿到庫存,然後扣減庫存了?

    所有機器一起去拿,發現都是1,那大家都認為是自己搶到了,都做了減一的操作,但是等所有人都執行完,再去set值的時候,發現其實已經超賣了,我打印出來給大家看看。

    是吧,這還不是超賣一個兩個的問題,超賣7個都有,代碼裏面明明判斷了庫存大於0才去減的,怎麼回事開頭我說明了。

    那怎麼解決這個問題?

    sync,lock也只能保證你當前機器線程安全,這樣分佈式訪問還是有問題。

    上面跟大家提到的zk的節點就可以解決這個問題。

    zk節點有個唯一的特性,就是我們創建過這個節點了,你再創建zk是會報錯的,那我們就利用一下他的唯一性去實現一下。

    怎麼實現呢?

    上面不是10個線程嘛?

    我們全部去創建,創建成功的第一個返回true他就可以繼續下面的扣減庫存操作,後續的節點訪問就會全部報錯,扣減失敗,我們把它們丟一個隊列去排隊。

    那怎麼釋放鎖呢?

    刪除節點咯,刪了再通知其他的人過來加鎖,依次類推。

    我們實現一下,zk加鎖的場景。

    是不是,只有第一個線程能扣減成功,其他的都失敗了。

    但是你發現問題沒有,你加了鎖了,你得釋放啊,你不釋放後面的報錯了就不重試了。

    那簡單,刪除鎖就釋放掉了,Lock在finally裏面unLock,現在我們在finally刪除節點。

    加鎖我們知道創建節點就夠了,但是你得實現一個阻塞的效果呀,那咋搞?

    死循環,遞歸不斷去嘗試,直到成功,一個偽裝的阻塞效果。

    怎麼知道前面的老哥刪除節點了嗯?

    監聽節點的刪除事件

    但是你發現你這樣做的問題沒?

    是的,會出現死鎖。

    第一個仔加鎖成功了,在執行代碼的時候,機器宕機了,那節點是不是就不能刪除了?

    你要故作沉思,自問自答,時而看看遠方,時而看看面試官,假裝自己什麼都不知道。

    哦我想起來了,創建臨時節點就好了,客戶端連接一斷開,別的就可以監聽到節點的變化了。

    嗯還不錯,那你發現還有別的問題沒?

    好像這種監聽機制也不好。

    怎麼個不好呢?

    你們可以看到,監聽,是所有服務都去監聽一個節點的,節點的釋放也會通知所有的服務器,如果是900個服務器呢?

    這對服務器是很大的一個挑戰,一個釋放的消息,就好像一個牧羊犬進入了羊群,大家都四散而開,隨時可能幹掉機器,會佔用服務資源,網絡帶寬等等。

    這就是羊群效應。

    那怎麼解決這個問題?

    繼續故作沉思,啊啊啊,好難,我的腦袋。。。。

    你TM別裝了好不好?

    好的,臨時順序節點,可以順利解決這個問題。

    怎麼實現老公你先別往下看,先自己想想。

    之前說了全部監聽一個節點問題很大,那我們就監聽我們的前一個節點,因為是順序的,很容易找到自己的前後。

    和之前監聽一個永久節點的區別就在於,這裏每個節點只監聽了自己的前一個節點,釋放當然也是一個個釋放下去,就不會出現羊群效應了。

    以上所有代碼我都會開源到我的https://github.com/AobingJava/Thanos其實上面的還有瑕疵,大家可以去拉下來改一下提交pr,我會看合適的會通過的。

    你說了這麼多,挺不錯的,你能說說ZK在分佈式鎖中實踐的一些缺點么?

    Zk性能上可能並沒有緩存服務那麼高。

    因為每次在創建鎖和釋放鎖的過程中,都要動態創建、銷毀瞬時節點來實現鎖功能。

    ZK中創建和刪除節點只能通過Leader服務器來執行,然後將數據同步到所有的Follower機器上。(這裏涉及zk集群的知識,我就不展開了,以後zk章節跟老公們細聊)

    還有么?

    使用Zookeeper也有可能帶來併發問題,只是並不常見而已。

    由於網絡抖動,客戶端可ZK集群的session連接斷了,那麼zk以為客戶端掛了,就會刪除臨時節點,這時候其他客戶端就可以獲取到分佈式鎖了。

    就可能產生併發問題了,這個問題不常見是因為zk有重試機制,一旦zk集群檢測不到客戶端的心跳,就會重試,Curator客戶端支持多種重試策略。

    多次重試之後還不行的話才會刪除臨時節點。

    Tip:所以,選擇一個合適的重試策略也比較重要,要在鎖的粒度和併發之間找一個平衡。

    有更好的實現么?

    基於Redis的分佈式鎖

    能跟我聊聊么?

    我看看了手上的表,老公,今天天色不早了,你全問完了,我怎麼多水幾篇文章呢?

    行確實很晚了,那你回家去把家務幹了吧?

    我????

    =

    總結

    zk通過臨時節點,解決掉了死鎖的問題,一旦客戶端獲取到鎖之後突然掛掉(Session連接斷開),那麼這個臨時節點就會自動刪除掉,其他客戶端自動獲取鎖。

    zk通過節點排隊監聽的機制,也實現了阻塞的原理,其實就是個遞歸在那無限等待最小節點釋放的過程。

    我上面沒實現鎖的可重入,但是也很好實現,可以帶上線程信息就可以了,或者機器信息這樣的唯一標識,獲取的時候判斷一下。

    zk的集群也是高可用的,只要半數以上的或者,就可以對外提供服務了。

    這周會寫完Redis和數據庫的分佈式鎖的,老公們等好。

    我是敖丙,一個在互聯網苟且偷生的工具人。

    最好的關係是互相成就老公們的「三連」就是丙丙創作的最大動力,我們下期見!

    注:如果本篇博客有任何錯誤和建議,歡迎老公們留言,老公你快說句話啊

    文章持續更新,可以微信搜索「 三太子敖丙 」第一時間閱讀,回復【資料】【面試】【簡歷】有我準備的一線大廠面試資料和簡歷模板,本文 GitHub https://github.com/JavaFamily 已經收錄,有大廠面試完整考點,歡迎Star。

    你知道的越多,你不知道的越多

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

    【其他文章推薦】

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

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

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

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

    ※回頭車貨運收費標準

  • 分析ThreadLocal的弱引用與內存泄漏問題-Java8,利用線性探測法解決hash衝突

    分析ThreadLocal的弱引用與內存泄漏問題-Java8,利用線性探測法解決hash衝突

    目錄

    一.介紹

    二.問題提出

      2.1內存原理圖

      2.2幾個問題

    三.回答問題

      3.1為什麼會出現內存泄漏

      3.2若Entry使用弱引用

      3.3弱引用配合自動回收

    四.總結  

     

     

     

    一.介紹

      之前使用ThreadLocal的時候,就聽過ThreadLocal怎麼怎麼的可能會出現內存泄漏,具體原因也沒去深究,就是一種不清不楚的狀態。最近在看JDK的源碼,其中就包含ThreadLocal,在對ThreadLocal的使用場景介紹以及源碼的分析后,對於ThreadLocal中可能存在的內存泄漏問題也搞清楚了,所以這裏專門寫一篇博客分析一下。

      在分析內存泄漏之前,先了解2個概念,就是內存泄漏和內存溢出:

      內存溢出(memory overflow):是指不能申請到足夠的內存進行使用,就會發生內存溢出,比如出現的OOM(Out Of Memory)

      內存泄漏(memory lack):內存泄露是指在程序中已經動態分配的堆內存由於某種原因未釋放或者無法釋放(已經沒有用處了,但是沒有釋放),造成系統內存的浪費,這種現象叫“內存泄露”。

      當內存泄露到達一定規模后,造成系統能申請的內存較少,甚至無法申請內存,最終導致內存溢出,所以內存泄露是導致內存溢出的一個原因。

     

    二.問題提出

    2.1內存原理圖

      下圖是程序運行中的內存分布圖,簡要介紹一下這種圖:當前線程有一個threadLocals屬性(ThreadLocalMap屬性),該map的底層是數組,每個數組元素時Entry類型,Entry類型的key是ThreadLocal類型(也就是創建的ThreadLocal對象),而value是則是ThreadLocal.set()方法設置的value。

      

      需要注意的是ThreadLocalMap的Entry,繼承自弱引用,定義如下,關於Java的引用介紹,可以參考:Java-強引用、軟引用、弱引用、虛引用

    /**
     * ThreadLocalMap中存放的元素類型,繼承了弱引用類
     */
    static class Entry extends WeakReference<ThreadLocal<?>> {
        // key對應的value,注意key是ThreadLocal類型
        Object value;
    
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    

     

    2.2問題提出

      在看了上面ThreadLocal和ThreadLocalMap相關的內存分佈以及關聯后,提出這樣幾個問題:

      1.ThreadLocal為什麼會出現內存溢出?

      2.Entry的key為什麼要用弱引用?

      3.使用弱引用是否就能解決內存溢出?

      為了回答上面這3個問題,我寫了一段代碼,後面根據這段代碼進行分析:

    public void step1() {
        // some action
        
        step2();
        step3();
        
        // other action
    }
    
    // 在stepX中都會創建threadLocal對象
    public void step2() {
        ThreadLocal<String> tl = new ThreadLocal<>();
        tl.set("this is value");
    }
    public void step3() {
        ThreadLocal<Integer> tl = new ThreadLocal<>();
        tl.set(99);
    }
    

      在step1中會調用step2和step3,step2和step3都會創建ThreadLocal對象,當step2和step3執行完畢后,其中的棧內存中ThreadLocal引用就會被清除。

     

    三.回答問題

     

      

      現在針對這個圖,一步一步的分析問題,中途會得出一些臨時的結論,但是最終的結論才是正確的

     

    3.1為什麼會出現內存泄露

      現在有2點假設,本小節的分析都是基於這兩個假設之上的:

      1.Entry的key使用強引用,key對ThreadLocal對象使用強引用,也就是上面圖中連線5是強引用(key強引用ThreadLocal對象);

      2.ThreadLocalMap中不會對過期的Entry進行清理。

      上面代碼中,如果ThreadLocalMap的key使用強引用,那麼即使棧內存的ThreadLocal引用被清除,但是堆中的ThreadLocal對象卻並不會被清除,這是因為ThreadLocalMap中Entry的key對ThreadLocal對象是強引用。

      如果當前線程不結束,那麼堆中的ThreadLocal對象將會一直存在,對應的內存就不會被回收,與之關聯的Entry也不會被回收(Entry對應的value也不會被回收),當這種情況出現數量比較多的時候,未釋放的內存就會上升,就可能出現內存泄漏的問題。

      上面的結論是暫時的,有前提假設!!!最終結論還需要看後面分析。

     

    3.2若Entry使用弱引用

      

      仍舊有1個假設,就是ThreadLocalMap中不會對過期的Entry進行清理,陳舊的Entry是指Entry的key為null。

      按照源碼,Entry繼承弱引用,其Key對ThreadLocal是弱引用,也就是上圖中連線5是弱引用,連線6仍為強引用。

      同樣以上面代碼為例,step2和step3創建了ThreadLocal對象,step2和step3執行完后,棧中的ThreadLocal引用被清除了;由於堆內存中ThreadLocalMap的Entry key弱引用ThreadLocal對象,根據垃圾收集器對弱引用對象的處理:

    當垃圾收集器工作時,無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象。

      此時堆中ThreadLocal對象會被gc回收(因為現在沒有對ThreadLocal的強引用,只有一個弱引用ThreadLocal對象),Entry的key為null,但是value不為null,且value也是強引用(連線6),所以Entry仍舊不能回收,只能釋放ThreadLocal的內存,仍舊可能導致內存泄漏

      在沒有自動清理陳舊Entry的前提下,即使Entry使用弱引用,仍可能出現內存泄漏。

     

    3.3弱引用配合自動回收

      通過3.2的分析,其實只要陳舊的Entry能自動被回收,就能解決內存泄漏的問題,其實JDK就是這麼做的。

      如果看過源碼,就知道,ThreadLocalMap底層使用數組來保存元素,使用“線性探測法”來解決hash衝突,關於線性探測法的介紹可以查看:利用線性探測法解決hash衝突

      在每次調用ThreadLocal類的get、set、remove這些方法的時候,內部其實都是對ThreadLocalMap進行操作,對應ThreadLocalMap的get、set、remove操作。

      重點來了!重點來了!重點來了!

      ThreadLocalMap的每次get、set、remove,都會清理過期的Entry,下面以get操作解釋,其他操作也是一個意思,大致如下:

      1.ThreadLocalMap底層用數組保存元素,當get一個Entry時,根據key的hash值(非hashCode)計算出該Entry應該出在什麼位置;

      2.計算出的位置可能會有衝突,比如預期位置是position=5,但是position=5的位置已經有其他Entry了;

      3.出現衝突后,會使用線性探測法,找position=6位置上的Entry是否匹配(匹配是指hash相同),如果匹配,則返回position=6的Entry。

      4.在這個過程中,如果position=5位置上的Entry已經是陳舊的Entry(Entry的key為null),此時position=5的key就應該被清理;

      5.光清理position=5的Entry還不夠,為了保證線性探測法的規則,需要判斷數組中的其他元素是否需要調整位置(如果需要,則調整位置),在這個過程中,也會進行清理陳舊Entry的操作。

      上面這5個步驟就保證了每次get都會清理數組中(map)的陳舊Entry,清理一個陳舊的Entry,就是下面這三行代碼:

    Entry.value = null; // 將Entry的value設為null
    table[index] = null;// 將數組中該Entry的位置設置null
    size--;	// map的size減一
    

      對於ThreadLocal的set、remove也類似這個原理。

      有了自動回收陳舊Entry的操作,需要注意的是,在這個時候,key使用弱引用就是至關重要的一點!!!

      因為key使用弱引用后,當弱引用的ThreadLocal對象被會回收后,該key的引用為null,則該Entry在下一次get、set、remove的時候就才會被清理,從未避免內存泄漏的問題。

      

    四.總結

      在上面的分析中,看到ThreadLocal基本不會出現內存泄漏的問題了,因為ThreadLocalMap中會在get、set、remove的時候清理陳舊的Entry,與Entry的key使用弱引用密不可分。

      當然我們也可以在代碼中手動調用ThreadLocal的remove方法進行清除map中key為該threadLocal對象的Entry,同時清理過期的Entry。

      

     

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

    【其他文章推薦】

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

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

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

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

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

    ※超省錢租車方案

  • .Net 對於PDF生成以及各種轉換的操作

    .Net 對於PDF生成以及各種轉換的操作

    前段時間公司的產品,要做一個新功能,簽章(就是把需要的數據整理成PDF很標準的文件,然後在蓋上我們在服務器上面的章)

    然後我就在百度上找了找,發現搞PDF的類庫很少,要麼就要錢,要麼就有水印,破解版的沒找到。

    先講一講我是怎麼生成PDF的

    1、生成PDF

      這裏用到了 Spire.Pdf    這個類庫可以在NuGet裏面搜索到,上面帶個小紅標的就是免費版本。  

      當然也可以去他們的官網,上面還有文檔(https://www.e-iceblue.cn/Introduce/Spire-PDF-NET.html)。

      代碼(這是我自己寫的一個測試的表格)

      

            public static void abc()
            {
                //創建PDF文檔
                Spire.Pdf.PdfDocument doc = new Spire.Pdf.PdfDocument();
    
                //添加一頁
                PdfPageBase page = doc.Pages.Add();
        //設置字體
                PdfTrueTypeFont font = new PdfTrueTypeFont(new System.Drawing.Font("Microsoft Yahei", 20f), true);
                PdfTrueTypeFont font1 = new PdfTrueTypeFont(new System.Drawing.Font("Microsoft Yahei", 11f), true);
    
                //創建一個PdfGrid對象
                PdfGrid grid = new PdfGrid();
    
            //這一段的內容是在表格只玩显示一些數據 根據坐標定位 第一個是內容,第二個是字體,第三個是顏色,第四第五是坐標
                page.Canvas.DrawString("XXXXXXXX管理中心回單",
                        font,
                        new PdfSolidBrush(System.Drawing.Color.Black), 130, 10);
                page.Canvas.DrawString("編號:31231",
                        font1,
                        new PdfSolidBrush(System.Drawing.Color.Black), 380, 60);
                page.Canvas.DrawString("經辦人:XXXX",
                        font1,
                        new PdfSolidBrush(System.Drawing.Color.Black), 60, 250);
                page.Canvas.DrawString("打印日期:2020/06/15",
                        font1,
                        new PdfSolidBrush(System.Drawing.Color.Black), 380, 250);
                //設置單元格邊距
                grid.Style.CellPadding = new PdfPaddings(1, 1, 4, 4);
    
                //設置表格默認字體
                grid.Style.Font = new PdfTrueTypeFont(new System.Drawing.Font("Microsoft Yahei", 12f), true);
    
                //添加4行4列
                PdfGridRow row1 = grid.Rows.Add();
                PdfGridRow row2 = grid.Rows.Add();
                PdfGridRow row3 = grid.Rows.Add();
                PdfGridRow row4 = grid.Rows.Add();
                PdfGridRow row5 = grid.Rows.Add();
                PdfGridRow row6 = grid.Rows.Add();
                grid.Columns.Add(4);
    
                //設置列寬
                foreach (PdfGridColumn col in grid.Columns)
                {
                    col.Width = 120f;
                }
    
                //寫入數據 第一行第一個格式的值,第一行第二個格子的值 
                row1.Cells[0].Value = "收款單位";
                row1.Cells[1].Value = "{DW}";
                row2.Cells[0].Value = "收款單位";
                row2.Cells[1].Value = "{DW}";
                row3.Cells[0].Value = "匯款時間";
                row3.Cells[1].Value = "2016/06/02";
                row3.Cells[2].Value = "金額小寫";
                row3.Cells[3].Value = "¥231";
                row4.Cells[0].Value = "金額合計大寫";
                row4.Cells[1].Value = "大蘇打實打實";
                row5.Cells[0].Value = "用途:" +
                    "付XXXX2020年04月至2020年04月";
                row6.Cells[0].Value = "提示:回單可重複打印,請勿重複XXX";
    
                //row5.Cells[0].Height = (float)20;
                //水平和垂直合併單元格 從那個格子開始合併幾個(包含當前格子)
                row1.Cells[1].ColumnSpan = 3;
                row2.Cells[1].ColumnSpan = 3;
                row4.Cells[1].ColumnSpan = 3;
                row5.Cells[0].ColumnSpan = 2;
                row5.Cells[2].ColumnSpan = 2;
                row6.Cells[0].ColumnSpan = 2;
                row6.Cells[2].ColumnSpan = 2;
            //這個是垂直合併,但是我之前合併的沒有效果
                row5.Cells[1].RowSpan = 2;
    
                //設置單元格內文字對齊方式
                row1.Cells[0].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
                row1.Cells[1].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
                row2.Cells[0].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
                row2.Cells[1].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
                row3.Cells[0].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
                row3.Cells[1].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
                row3.Cells[2].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
                row3.Cells[3].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
                row4.Cells[0].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
                row4.Cells[1].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
                row5.Cells[0].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
                row6.Cells[0].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
                //設置單元格背景顏色
                //row1.Cells[0].Style.BackgroundBrush = PdfBrushes.Gray;
                //row3.Cells[3].Style.BackgroundBrush = PdfBrushes.Green;
                //row4.Cells[3].Style.BackgroundBrush = PdfBrushes.MediumVioletRed;
    
                //設置邊框顏色、粗細
                PdfBorders borders = new PdfBorders();
                borders.All = new PdfPen(System.Drawing.Color.Black, 0.1f);
                foreach (PdfGridRow pgr in grid.Rows)
                {
                    foreach (PdfGridCell pgc in pgr.Cells)
                    {
                        pgc.Style.Borders = borders;
                    }
                }
                //保存到文檔
                //在指定為繪入表格
                grid.Draw(page, new PointF(30, 80));
                doc.SaveToFile(@"路徑");
           //這句是在瀏覽器重打開這個PDF  
                System.Diagnostics.Process.Start(@"路徑");
            }

     

      保存我們看一下

      

     

     

    2、之後就是關於PDF一些轉換的操作了(PDF轉base64,轉圖片之類的,我把代碼貼到下面)

     

     

     

    • 這個用到了iTextSharp類庫,也可以在Nuget下載到

     

    /// <summary>
            /// 圖片轉pdf
            /// </summary>
            /// <param name="jpgfile"></param>
            /// <param name="pdf"></param>
            public static bool ConvertJPG2PDF(string jpgfile, string pdf)
            {
                try
                {
                    var document = new iTextSharp.text.Document(iTextSharp.text.PageSize.A4, 25, 25, 25, 25);
                    using (var stream = new FileStream(pdf, FileMode.Create, FileAccess.Write, FileShare.None))
                    {
                        PdfWriter.GetInstance(document, stream);
                        document.Open();
                        using (var imageStream = new FileStream(jpgfile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                        {
                            var image = iTextSharp.text.Image.GetInstance(imageStream);
                            if (image.Height > iTextSharp.text.PageSize.A4.Height - 25)
                            {
                                image.ScaleToFit(iTextSharp.text.PageSize.A4.Width - 25, iTextSharp.text.PageSize.A4.Height - 25);
                            }
                            else if (image.Width > iTextSharp.text.PageSize.A4.Width - 25)
                            {
                                image.ScaleToFit(iTextSharp.text.PageSize.A4.Width - 25, iTextSharp.text.PageSize.A4.Height - 25);
                            }
                            image.Alignment = iTextSharp.text.Image.ALIGN_MIDDLE;
                            document.NewPage();
                            document.Add(image);
                        }
                        document.Close();
                    }
    
                    return true;
    
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }
    • 這個用的是(O2S.Components.PDFRender4NET)
    /// <summary>
            /// pdf轉img
            /// </summary>
            /// <param name="path">pdf位置</param>
            /// <param name="path2">img位置</param>
            public static void Pdf2Img(string path, string path2)
            {
                PDFFile pdfFile = PDFFile.Open(path);
                //實例化一個PdfDocument類對象,並加載PDF文檔
                using (Spire.Pdf.PdfDocument doc = new Spire.Pdf.PdfDocument())
                {
                    doc.LoadFromFile(path);
                    //調用方法SaveAsImage()將PDF第一頁保存為Bmp格式
                    System.Drawing.Image bmp = doc.SaveAsImage(0);
                    // //調用另一個SaveAsImage()方法,並將指定頁面保存保存為Emf、Png            
                    System.Drawing.Image emf = doc.SaveAsImage(0, Spire.Pdf.Graphics.PdfImageType.Bitmap);
                    System.Drawing.Image zoomImg = new Bitmap((int)(emf.Size.Width * 2), (int)(emf.Size.Height * 2));
                    using (Graphics g = Graphics.FromImage(zoomImg))
                    {
                        g.ScaleTransform(2.0f, 2.0f);
                        g.DrawImage(emf, new System.Drawing.Rectangle(new System.Drawing.Point(0, 0), emf.Size), new System.Drawing.Rectangle(new System.Drawing.Point(0, 0), emf.Size), GraphicsUnit.Pixel);
                        zoomImg.Save(path2, ImageFormat.Jpeg);
                        
                        zoomImg.Dispose();
                        emf.Dispose();
                        bmp.Dispose();
                    }
                    doc.Close();
                    doc.Dispose();
                }
                
               
            }
    • 這個和上面用的也是同一個(我覺得這個比較好用一些)

     

    /// <summary>
                    /// 將PDF文檔轉換為圖片的方法
                    /// </summary>
                    /// <param name="pdfInputPath">PDF文件路徑</param>
                    /// <param name="imageOutputPath">圖片輸出路徑</param>
                    /// <param name="imageName">生成圖片的名字</param>
                    /// <param name="startPageNum">從PDF文檔的第幾頁開始轉換</param>
                    /// <param name="endPageNum">從PDF文檔的第幾頁開始停止轉換</param>
                    /// <param name="imageFormat">設置所需圖片格式</param>
                    /// <param name="definition">設置圖片的清晰度,数字越大越清晰</param>
            public static void ConvertPDF2Image(string pdfInputPath, string imageOutputPath,
          string imageName, int startPageNum, int endPageNum, ImageFormat imageFormat, Definition definition)
            {
                PDFFile pdfFile = null;
                FileStream fs = null;
                try
                {
                    fs = new FileStream(pdfInputPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
                    pdfFile = PDFFile.Open(fs);
    
                    if (startPageNum <= 0)
                    {
                        startPageNum = 1;
                    }
                    if (endPageNum > pdfFile.PageCount)
                    {
                        endPageNum = pdfFile.PageCount;
                    }
                    if (startPageNum > endPageNum)
                    {
                        int tempPageNum = startPageNum;
                        startPageNum = endPageNum;
                        endPageNum = startPageNum;
                    }
                    string path = imageOutputPath + imageName + "1" + "." + imageFormat.ToString();
                    Logger.WriteLogs("PDFIMG:" + path);
                    using (Bitmap pageImage = pdfFile.GetPageImage(0, 56 * (int)definition))
                    {
                        if (!File.Exists(path))
                        {
                            using (FileStream f = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite))
                            {
                                pageImage.Save(f, ImageFormat.Jpeg);
                                pageImage.Dispose();
                            }
                        }
                    }
                    fs.Flush();
                }
                catch (Exception ex)
                {
                    throw new Exception(ex.Message);
                }
                finally
                {
                    if (pdfFile != null)
                    {
                        pdfFile.Dispose();
                    }
                    else if (fs != null)
                    {
                        fs.Close();
                        fs.Dispose();
                    }
                }
            }
    
        public enum Definition
        {
            One = 1, Two = 2, Three = 3, Four = 4, Five = 5, Six = 6, Seven = 7, Eight = 8, Nine = 9, Ten = 10
        }
    • PDF轉Base64
    • /// <summary>
              /// pdf轉base64
              /// </summary>
              /// <param name="filePath"></param>
              /// <returns></returns>
              public static string PdfWord_To_Base64(string filePath)
              {
                  try
                  {
                      if (!string.IsNullOrWhiteSpace(filePath.Trim()))
                      {
                          
                              FileStream fileStream = new FileStream(filePath, FileMode.OpenOrCreate);
                              byte[] bt = new byte[fileStream.Length];
                              fileStream.Read(bt, 0, bt.Length);
                              fileStream.Close();
                              fileStream.Dispose();
                              return Convert.ToBase64String(bt);
                      }
                      else
                      {
                          return "請輸入正確的路徑";
                      }
      
                  }
                  catch (Exception ex)
                  {
                      throw ex;
                  }
              }

      我主要也就用到這些,之後在發現,我在往上加  

      鏈接: https://pan.baidu.com/s/16xEpLBJ3-8fGjNPyvHUSmA

      提取碼: p9qi

      這個是O2S.Components.PDFRender4NET的文件,我看評論里有問的,之前忘記發了,網上有的是收費的,Nuget裏面帶後綴的我沒用過。

     

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

    【其他文章推薦】

    ※超省錢租車方案

    ※別再煩惱如何寫文案,掌握八大原則!

    ※回頭車貨運收費標準

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

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

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

  • 跑得不夠快,配置不夠高,但TA的魅力依然擋不住

    跑得不夠快,配置不夠高,但TA的魅力依然擋不住

    聽瑪莎拉蒂堅持與寶華韋健合作隨車打造的音響系統可以帶給你在聽覺上殿堂級的享受,甚至可以調整側重地方,比如往後排照顧,就可以向後排調節,能輕易分出前排後排的區別。最神奇的是,經過無數次調校之後,發動機艙傳來的聲浪並不會影響音響效果,甚至會相互加成,帶給你獨特的聽覺享受。

    瑪莎拉蒂,一個最喜愛的超豪華品牌,沒有之一。在有的人眼裡,瑪莎拉蒂是個跑車裡的異類,沒有讓人驚艷的百公里加速成績,沒有讓人眼前一亮的高科技配置,更沒有跑車具有的戰鬥姿態,但是,可以告訴你,瑪莎拉蒂具有的是其他品牌所沒有的一種浪漫,把豪華、運動、享受都融合在一起的意式浪漫。

    在這次北京車展專門跑去瑪莎拉蒂展台個性化配置專區感受一番這種浪漫情懷,並通過看、聽、聞、觸、味全方位體驗。



    看瑪莎拉蒂全系車型就是一種享受,造型不僅獨特個性,且在不經意間會發現瑪莎拉蒂融入車裡的品牌造型,比如,你會發現在輪轂的形狀就是圍繞瑪莎拉蒂的標誌(海神三叉戟)而設計。並且全系車型都接受定製,從車身顏色、車漆種類、輪轂、制動卡鉗到內飾材料、多功能方向盤、豪華還是運動座椅、座椅面料等等,無不體現屬於自己的個性訂製。



    瑪莎拉蒂堅持與寶華韋健合作隨車打造的音響系統可以帶給你在聽覺上殿堂級的享受,甚至可以調整側重地方,比如往後排照顧,就可以向後排調節,能輕易分出前排後排的區別。最神奇的是,經過無數次調校之後,發動機艙傳來的聲浪並不會影響音響效果,甚至會相互加成,帶給你獨特的聽覺享受。



    意大利頂級皮革帶給車內獨特的芳香,讓駕駛者更覺舒適,展台專門聘請的咖啡師調出香濃的意式咖啡,帶來極致的享受。



    意大利頂級皮革以及精緻的手工縫製,無一不在體現瑪莎拉蒂在工藝方面的水平以及造車的誠意。



    最後,在瑪莎拉蒂個性化配置專區還提供了頂級廚師烹飪的意大利美食,開啟味覺享受的同時也結束了本次瑪莎拉蒂的體驗之旅。

    瑪莎拉蒂,一個百年來始終堅持做自己的個性品牌,這是喜歡它的原因,有人說,瑪莎拉蒂是一個“全靠浪(聲浪)”的品牌,這個說法對也不對,不可否認,瑪莎拉蒂在聲浪方面所下的功夫確實無人能敵,但是其在豪華體驗上也是讓人不得不翹起個大拇指說“Good”!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

    ※回頭車貨運收費標準

  • 同是2.0T+7速雙離合,為何那麼多人放棄大眾買貴4萬的豪車?

    同是2.0T+7速雙離合,為何那麼多人放棄大眾買貴4萬的豪車?

    我曾經從一位一汽的裝配工口中聽過這樣一句話:大眾的車底盤焊縫要求不能伸進指甲就合格,但是。奧迪的要求是不能伸進頭髮。上面我們說到了兩車的造車成本以及測試種類,那就不免出現一個更加專業名詞——NVH,直白一點說就是車輛的胎噪,風噪和發動機噪音。

    在如今這個車水馬龍的城市中,滿大街都是車,而車一多,咱們買車選車就自然變得更加困難了。而且中國人買車需要的可不單單隻是性價比,更需要的是面子,並且一定要大。

    特別是當BBA這種豪華品牌不斷的將車型價格下探之後,吸引了更多的中國人可以花普通品牌的價錢買到掛着“豪華品牌商標”的汽車,這也是為什麼如今奔馳、寶馬、奧迪這些車會滿大街跑的原因。

    當然,相比起豪華品牌的紙醉金迷,普通品牌的優勢更多在於極高性價比。不過不可否認的是,豪華品牌的品牌溢價能力,的確是普通品牌無法比擬的。

    並且豪華車除了可以給人帶來身份的象徵,還帶來了更極佳的駕乘感受,畢竟“豪華”兩字已經說明了一切。如今各類豪華品牌的入門車型售價,儼然已經可以跟普通品牌針鋒相對了。

    那在面對預算有限的情況下,到底是咬咬牙弄台豪華品牌,享受一下豪車帶來的優越感呢,還是輕鬆的拿下普通品牌,得到其極具性價比配置的同時還能多買幾台蘋果XXX呢?

    如果有一天,你開着一輛頂配大眾高爾夫GTI去相親,對方父母只會認為你混得一般,但是當你開着一輛掛這“四個環”的丐版奧迪A3去相親,人家則會認為,這家孩子不錯,年紀輕輕就開得起奧迪了。

    一輛頂配的GTI比起低配的奧迪A3,怎麼著也得貴上好幾萬吧,但是為什麼人人都會認為奧迪更高級呢。

    奧迪嘛,在國人心目中一直存在着“官車”的形象,而大眾則是普羅大眾都消費得起的存在。而且再怎麼說你也只是大眾,我可是奧迪啊,世界公認的三大豪華品牌之一,買車看的不正是牌子,面子嘛!

    不過很多朋友都認為,A3其實就是換標高爾夫,而且那個標還不便宜。那這次咱們就以高爾夫GTI以及奧迪A3兩廂頂配來說話。到底用幾台蘋果XXX的錢去買一個四環標值不值得?

    目前兩車在廣州的裸車價基本是:奧迪A3頂配23萬左右,高爾夫GTI19萬左右,也就是四萬左右的差價,雖然不同地區情況不同,不過差價相信基本也是相差無幾。

    相信有了解過這兩款車的朋友都知道,兩者同為一汽旗下合資公司的產品、同是MQB平台、同一工廠生產、相同的EA888發動機、相同的變速箱。聽起來兩者的差異性極小,但是細分之後你就知道還是有區別的。

    相同的EA888發動機,在奧迪A3頂配身上只能輸出190匹馬力,但是在GTI身上則能爆發出220馬力。兩者都匹配了7速雙離合變速器,不過在大眾身上叫DSG,在奧迪身上喊S tronic而已。

    從平台上看,兩者均出於MQB橫置模塊化平台,這就不難讓人認為兩者的底盤變現以及操控性上是不是一致呢?事實卻並非如此。就好比一台計算機,平台就如同一個主板,再裝配不同的顯卡,聲卡,內存,CpU,不同搭配所得出的效果自然也是截然不同的。

    另外,雖然奧迪有着品牌溢價,但相對應的供應商也會與大眾有所區別,零配件的成本也會相應的提高,並且在裝配工藝上奧迪也高於大眾。。單單從用漆方面就可以看得出這點,奧迪A3大部分採用的均為高成本、高品質的珠光漆。

    還有奧迪需要做的測試種類,無論是整車測試還是零部件試驗的嚴苛程度,複雜性,精密度,都遠遠高於大眾。我曾經從一位一汽的裝配工口中聽過這樣一句話:大眾的車底盤焊縫要求不能伸進指甲就合格,但是………奧迪的要求是不能伸進頭髮!!!

    上面我們說到了兩車的造車成本以及測試種類,那就不免出現一個更加專業名詞——NVH,直白一點說就是車輛的胎噪,風噪和發動機噪音。從各大測評機構的測試結果都不難看出,奧迪A3對NVH的把控要比高爾夫好,另一方面也表明了,豪華品牌在駕乘感受上下的功夫要比大眾這種普通品牌更多。

    至於外觀,咱們就略過不說了,反正蘿蔔青菜各有所愛,奧迪自帶四環光環,高爾夫GTI則滿身情懷。內飾的話,GTI也不能說完敗,只能說那萬年不變的布局真的讓人又愛又恨,而A3的內飾那才真的稱得上人人喜愛,既精緻又簡潔還顯高級感,而且細看內飾的做工用料,無論是視覺的衝擊力還是觸感的細膩度上,相信什麼叫奧迪,什麼叫大眾自然一目瞭然了。

    不過在配置的差異上,奧迪A3比起GTI似乎真的閹割太多了,要知道那可是四萬塊的差價啊。此外,奧迪選裝配置的價錢也非常感人,一套真皮座椅就小一萬了,估計想選裝到GTI這麼豐富配置的時候你就得多掏幾百張毛爺爺了,不得不說奧迪這生意還真好賺!

    估計看到這裏,大家都不難得出一個結論:奧迪A3當然不是換標高爾夫那麼簡單。

    儘管GTI的馬力、配置都比A3更優越,但是從用料做工、質感品控、駕乘感受方面來看,A3比起GTI則是更勝一籌。終究是豪華品牌嘛,還是會有自己的擔當,哪怕是十幾萬起步的入門車型,他們都會用心去對待。所以與其說多花了幾萬塊錢要裝逼買個豪華商標,倒不如老老實實的承認“一分錢一分貨”這個道理。

    再舉個例子,如果奧迪A3賣着高爾夫的價錢,我估計你也不大敢買吧?又或者說,奧迪如果把A3的價格降低到高爾夫這個層次,我相信一定會大大衝擊高爾夫的銷量,而且奧迪作為豪華品牌的形象也會因此大打折扣。在汽車界,類似的例子比比皆是,所以單單拿平台來說事,似乎就有點無稽之談了。

    至於遇到類似的問題該怎麼去選擇,那就只能看個人需求以及兜里毛爺爺的多與少啦。反正有錢的話,那必須選豪華品牌,去酒吧把鑰匙往桌子一拍,嘿嘿,那效應老司機都懂都懂!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

    ※別再煩惱如何寫文案,掌握八大原則!

    ※超省錢租車方案

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

  • 玩車的人都在捧的本田發動機,真的這麼神?

    玩車的人都在捧的本田發動機,真的這麼神?

    然而,這種熟悉的“爆tec”感覺沒有出現在最新的謳歌TLX-L上。謳歌是本田的豪華品牌,一切的用料,技術,做工等都用上高標準。就連本田被人詬病的隔音謳歌也能做得安靜。被拉高轉的本田發動機“爆tec”時的感覺聲音和動力都有着明顯變化,讓人為之興奮,但開着謳歌TLX-L卻沒有這種感覺。

    本田,一個無人不知無人不曉的一個汽車品牌。尤其是在廣東地區,更是本田粉活躍的地方,大街上跑的10輛車中,有4輛或5輛是本田車型。

    買本田車,除了買質量可靠性外,更多的是為了“買發動機送車”,而坊間也流傳着本田金句VTEC is the best。可想而知,本田發動機技術含量和民望有多高。

    本田發動機的看家技術VTEC和i-VTEC技術外,在近年也推出VTEC Turbo這渦輪組合技術。

    那本田技術牛在哪?主要除了有可變氣門正時外,還有氣門升程這一技術。氣門升程技術並不是很多發動機有,除了寶馬和英菲尼迪VQ37系列外,還真是寥寥無幾。

    開過本田的人未必知道什麼叫“爆tec”,但拉過高轉的人就知道。當發動機轉速拉到5000轉左右的時候,聲音突變,發動機也感覺顫抖一下。這就是“爆tec”給人的感覺。

    然而,這種熟悉的“爆tec”感覺沒有出現在最新的謳歌TLX-L上。謳歌是本田的豪華品牌,一切的用料,技術,做工等都用上高標準。就連本田被人詬病的隔音謳歌也能做得安靜。

    被拉高轉的本田發動機“爆tec”時的感覺聲音和動力都有着明顯變化,讓人為之興奮,但開着謳歌TLX-L卻沒有這種感覺。難道就因為高端車型而被“技術”隔絕?

    其實這種“爆tec”的感覺在自主品牌的渦輪車上有着“更好的體現”。只不過我們吐槽它而已,這一技術就是渦輪遲滯。體驗過大部分自主品牌渦輪車,輕踩油門不去,踩重一點像是被踢出去的感覺。這種感覺比“爆tec”還爽快。

    總結:高轉“爆tec”還是低轉渦輪介入明顯,都使得發動機動力輸出沒那麼“線性”。只是拉高轉的時候,亢奮的聲音和動力的凸顯使得“爆tec”的感覺被神化。渦輪介入明顯,其實就是“爆tec”時動力凸顯的一部分,只是沒有亢奮的聲音罷了。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※超省錢租車方案

    ※別再煩惱如何寫文案,掌握八大原則!

    ※回頭車貨運收費標準

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

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

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