八張圖徹底了解JDK8 GC調優秘籍-附PDF下載

目錄

  • 簡介
  • 分代垃圾回收器的內存結構
  • JDK8中可用的GC
  • 打印GC信息
  • 內存調整參數
  • Thread配置
  • 通用GC參數
  • CMS GC
  • G1參數
  • 總結

簡介

JVM的參數有很多很多,根據我的統計JDK8中JVM的參數總共有1853個,正式的參數也有680個。

這麼多參數帶給我們的是對JVM的細粒度的控制,但是並不是所有的參數都需要我們自己去調節的,我們需要關注的是一些最常用的,對性能影響比較大的GC參數即可。

為了更好的讓大家理解JDK8中 GC的調優的秘籍,這裏特意準備了八張圖。在本文的最後,還附帶了一個總結的PDF all in one文檔,大家把PDF下載回去,遇到問題就看兩眼,不美嗎?

分代垃圾回收器的內存結構

為了更好的提升GC的效率,現代的JVM都是採用的分代垃圾回收的策略(ZGC不是)。

java運行時內存可以分為JVM內存和非JVM內存。

JVM內存又可以分為堆內存和非堆內存。

堆內存大家都很熟悉了,YoungGen中的Eden,Survivor和OldGen。

非堆內存中存儲的有thread Stack,Code Cache, NIO Direct Buffers,Metaspace等。

注意這裏的Metaspace元空間是方法區在JDK8的實現,它是在本地內存中分配的。

JDK8中可用的GC

JDK8中到底有哪些可以使用的GC呢?

這裏我們以HotSpot JVM為例,總共可以使用4大GC方式:

其中對於ParallelGC和CMS GC又可以對年輕代和老年代分別設置GC方式。

大家看到上圖可能有一個疑問,Parallel scavenge和Parallel有什麼區別呢?

其實這兩個GC的算法是類似的,Parallel Scavenge收集器也經常稱為“吞吐量優先”收集器,Parallel Scavenge收集器提供了兩個參數用於精確控制吞吐量; -XX:MaxGCPauseMillis:控制最大垃圾收集停頓時間; -XX:GCTimeRatio:設置吞吐量大小。

同時Parallel Scavenge收集器能夠配合自適應調節策略,把內存管理的調優任務交給虛擬機去完成。

JDK8中默認開啟的是ParallelGC。

打印GC信息

如果想研究和理解GC的內部信息,GC信息打印是少不了的:

上圖提供了一些非常有用的GC日誌的控制參數。

內存調整參數

JVM分為Heap區和非Heap區,各個區又有更細的劃分,下面就是調整各個區域大小的參數:

Thread配置

TLAB大家還記得嗎?TLAB的全稱是Thread-Local Allocation Buffers。TLAB是在Eden區間分配的一個一個的連續空間。然後將這些連續的空間分配個各個線程使用。

因為每一個線程都有自己的獨立空間,所以這裏不涉及到同步的概念。

上圖就是TLAB的參數。

通用GC參數

雖然JDK8的GC這麼多,但是他們有一些通用的GC參數:

這裏講解一下Young space tenuring,怎麼翻譯我不是很清楚,這個主要就是指Young space中的對象經過多少次GC之後會被提升到Old space中。

CMS GC

CMS全稱是Concurrent mark sweep。是一個非常非常複雜的GC。

複雜到什麼程度呢?光光是CMS調優的參數都有一百多個!

下圖是常用的CMS的參數。

CMS這裏就不多講了,因為在JDK9之後,CMS就已經被廢棄了。

主要原因是CMS太過複雜,如果要向下兼容需要巨大的工作量,然後就直接被廢棄了。

在JDK9之後,默認的GC是G1。

G1參數

G1收集器是分代的和region化的,也就是整個堆內存被分為一系列大小相等的region。在啟動時,JVM設置region的大小,根據堆大小的不同,region的大小可以在1MB到32MB之間變動,region的數量最多不超過2048個。Eden區、Survivor區、老年代是這些region的邏輯集合,它們並不是連續的。

G1中的垃圾收集過程:年輕代收集和混合收集交替進行,背後有全局的併發標記周期在進行。當老年代分區佔用的空間達到或超過初始閾值,就會觸發併發標記周期。

下圖是G1的調優參數:

總結

上面總共8副圖,我把他們做成了一個PDF,預覽界面大概是這樣子的:

大家可以通過下面的鏈接直接下載PDF版本:

JDK8GC-cheatsheet.pdf

如果遇到問題可以直接拿過來參考。這種東西英文名字應該叫JDK8 GC cheatsheet,翻譯成中文應該就是JDK8 GC調優秘籍!

本文作者:flydean程序那些事

本文鏈接:http://www.flydean.com/jdk8-gc-cheatsheet/

本文來源:flydean的博客

歡迎關注我的公眾號:程序那些事,更多精彩等着您!

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

【其他文章推薦】

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

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

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

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

Newtonsoft 六個超簡單又實用的特性,值得一試 【下篇】

一:講故事

上一篇介紹的 6 個特性從園子里的反饋來看效果不錯,那這一篇就再帶來 6 個特性同大家一起欣賞。

二:特性分析

1. 像弱類型語言一樣解析 json

大家都知道弱類型的語言有很多,如: nodejs,python,php,它們有一個的地方就是處理json,不需要像 強類型語言 那樣還要給它配一個類,什麼意思呢? 就拿下面的 json 說事。


{
  "DisplayName": "新一代算法模型",
  "CustomerType": 1,
  "Report": {
    "TotalCustomerCount": 1000,
    "TotalTradeCount": 50
  },
  "CustomerIDHash": [1,2,3,4,5]
}

這個 json 如果想灌到 C# 中處理,你就得給它定義一個適配的類,就如 初篇 的客戶算法模型類,所以這裏就有了一個需求,能不能不定義類也可以自由解析上面這串 json 呢??? 哈哈,當然是可以的, 反序列化成 Dictionary 即可,就拿提取 Report.TotalCustomerCountCustomerIDHash 這兩個字段演示一下。


        static void Main(string[] args)
        {
            var json = @"{
                           'DisplayName': '新一代算法模型',
                           'CustomerType': 1,
                           'Report': {
                             'TotalCustomerCount': 1000,
                             'TotalTradeCount': 50
                           },
                           'CustomerIDHash': [1,2,3,4,5]
                         }";

            var dict = JsonConvert.DeserializeObject<Dictionary<object, object>>(json);

            var report = dict["Report"] as JObject;
            var totalCustomerCount = report["TotalCustomerCount"];

            Console.WriteLine($"totalCustomerCount={totalCustomerCount}");

            var arr = dict["CustomerIDHash"] as JArray;
            var list = arr.Select(m => m.Value<int>()).ToList();

            Console.WriteLine($"list={string.Join(",", list)}");
        }

2. 如何讓json中的枚舉保持更易讀的字符串型

這句話是什麼意思呢? 默認情況下, SerializeObject 會將 Model 中的 Enum 變成數值型,大家都知道數值型語義性是非常差的,如下代碼所示:


    static void Main(string[] args)
    {
        var model = new ThreadModel() { ThreadStateEnum = System.Threading.ThreadState.Running };

        var json = JsonConvert.SerializeObject(model);

        Console.WriteLine(json);
    }

    class ThreadModel
    {
        public System.Threading.ThreadState ThreadStateEnum { get; set; }
    }

對吧,確實語義特別差,那能不能直接生成 Running 這種字符串形式呢? 當然可以了。。。改造如下:


  var json = JsonConvert.SerializeObject(model, new StringEnumConverter());

這裏可能就有人鑽牛角尖了,能不能部分指定讓枚舉生成 string,其他的生成 int ,沒關係,這也難不倒我,哪裡使用就用 JsonConverter 標記哪裡。。。


        static void Main(string[] args)
        {
            var model = new ThreadModel()
            {
                ThreadStateEnum = System.Threading.ThreadState.Running,
                TaskStatusEnum = TaskStatus.RanToCompletion
            };

            var json = JsonConvert.SerializeObject(model);

            Console.WriteLine(json);
        }

        class ThreadModel
        {
            public System.Threading.ThreadState ThreadStateEnum { get; set; }

            [JsonConverter(typeof(StringEnumConverter))]
            public TaskStatus TaskStatusEnum { get; set; }
        }        

3. 格式化 json 中的時間類型

在 model 轉化成 json 的過程中,總少不了 時間類型,為了讓時間類型 可讀性更高,通常會 格式化為 YYYY年/MM月/dd日 ,那如何實現呢? 很簡單撒,在 JsonConvert 中也是一個 枚舉 幫你搞定。。。


        static void Main(string[] args)
        {
            var json = JsonConvert.SerializeObject(new Order()
            {
                OrderTitle = "女裝大佬",
                Created = DateTime.Now
            }, new JsonSerializerSettings
            {
                DateFormatString = "yyyy年/MM月/dd日",
            });

            Console.WriteLine(json);
        }
        public class Order
        {
            public string OrderTitle { get; set; }
            public DateTime Created { get; set; }
        }   

對了,我記得很早的時候,C# 自帶了一個 JavaScriptSerializer, 也是用來進行 model 轉 json的,但是它會將 datetime 轉成 時間戳,而不是時間字符串形式,如果你因為特殊原因想通過 JsonConvert 將時間生成時間戳的話,也是可以的, 用 DateFormatHandling.MicrosoftDateFormat 枚舉指定一下即可,如下:

4. 對一些常用設置進行全局化

在之前所有演示的特性技巧中都是在 JsonConvert 上指定的,也就是說 100 個 JsonConvert 我就要指定 100 次,那有沒有類似一次指定,整個進程通用呢? 這麼強大的 Newtonsoft 早就支持啦, 就拿上面的 Order 舉例:


        JsonConvert.DefaultSettings = () =>
        {
            var settings = new JsonSerializerSettings
            {
                Formatting = Formatting.Indented
            };
            return settings;
        };

        var order = new Order() { OrderTitle = "女裝大佬", Created = DateTime.Now };

        var json1 = JsonConvert.SerializeObject(order);
        var json2 = JsonConvert.SerializeObject(order);

        Console.WriteLine(json1);
        Console.WriteLine(json2);

可以看到,Formatting.Indented 對兩串 json 都生效了。

5. 如何保證 json 到 model 的嚴謹性 及提取 json 未知字段

有時候我們有這樣的需求,一旦 json 中出現 model 未知的字段,有兩種選擇: 要麼報錯,要麼提取出未知字段,在 Newtonsoft 中默認的情況是忽略,場景大家可以自己找哈。

  • 未知字段報錯

        static void Main(string[] args)
        {
            var json = "{'OrderTitle':'女裝大佬', 'Created':'2020/6/23','Memo':'訂單備註'}";

            var order = JsonConvert.DeserializeObject<Order>(json, new JsonSerializerSettings
            {
                MissingMemberHandling = MissingMemberHandling.Error
            });

            Console.WriteLine(order);
        }

        public class Order
        {
            public string OrderTitle { get; set; }
            public DateTime Created { get; set; }
            public override string ToString()
            {
                return $"OrderTitle={OrderTitle}, Created={Created}";
            }
        }        

  • 提取未知字段

我依稀的記得 WCF 在這種場景下也是使用一個 ExtenstionDataObject 來存儲客戶端傳過來的未知字段,有可能是客戶端的 model 已更新,server端還是舊版本,通常在 json 序列化中也會遇到這種情況,這裏只要使用 JsonExtensionData 特性就可以幫你搞定,在 OnDeserialized 這種AOP方法中進行攔截,如下代碼:


    static void Main(string[] args)
    {
        var json = "{'OrderTitle':'女裝大佬', 'Created':'2020/6/23','Memo':'訂單備註'}";

        var order = JsonConvert.DeserializeObject<Order>(json);

        Console.WriteLine(order);
    }

    public class Order
    {
        public string OrderTitle { get; set; }

        public DateTime Created { get; set; }

        [JsonExtensionData]
        private IDictionary<string, JToken> _additionalData;

        public Order()
        {
            _additionalData = new Dictionary<string, JToken>();
        }

        [OnDeserialized]
        private void OnDeserialized(StreamingContext context)
        {
            var dict = _additionalData;
        }

        public override string ToString()
        {
            return $"OrderTitle={OrderTitle}, Created={Created}";
        }
    }        

6. 開啟 JsonConvert 詳細日誌功能

有時候在查閱源碼的時候開啟日誌功能更加有利於理解源碼的內部運作,所以這也是一個非常實用的功能,看看如何配置吧。


        static void Main(string[] args)
        {
            var json = "{'OrderTitle':'女裝大佬', 'Created':'2020/6/23','Memo':'訂單備註'}";

            MemoryTraceWriter traceWriter = new MemoryTraceWriter();

            var account = JsonConvert.DeserializeObject<Order>(json, new JsonSerializerSettings
            {
                TraceWriter = traceWriter
            });

            Console.WriteLine(traceWriter.ToString());
        }

        public class Order
        {
            public string OrderTitle { get; set; }

            public DateTime Created { get; set; }

            public override string ToString()
            {
                return $"OrderTitle={OrderTitle}, Created={Created}";
            }
        }

三:總結

嘿嘿,這篇 6 個特性就算說完了, 結合上一篇一共 12 個特性,是不是非常簡單且實用,後面準備給大家帶來一些源碼解讀吧! 希望本篇對您有幫助,謝謝!

如您有更多問題與我互動,掃描下方進來吧~

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

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

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

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

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

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

GitHub 熱點速覽 Vol.25:距離優雅編程你差個它

作者:HelloGitHub-小魚乾

摘要:如何優雅地誇一個程序員呢?vscode-rainbow-fart 作為一個彩虹屁的項目,深得程序員心,能在你編程時瘋狂稱讚你的除了你自己,還有它。除了鼓勵之外,Super Linte 是官方出品的旨在保證代碼和文檔一致性的工具,有了它,你可以更優雅地進行編程。說完優雅編程,來說下優雅使用 k8s,那就不得不提 Lens,一個專業管理 k8s 工具。

以下內容摘錄自微博@HelloGitHub 的 GitHub Trending,選項標準:新發布 | 實用 | 有趣,根據項目 release 時間分類,發布時間不超過 7 day 的項目會標註 New,無該標誌則說明項目 release 超過一周。由於本文篇幅有限,還有部分項目未能在本文展示,望周知

  • 本文目錄
      1. 本周特推
      • 1.1 GitHub 官方出品:super-linter
      • 1.2 彩虹屁 VSCode 插件:vscode-rainbow-fart
      1. GitHub Trending 周榜
      • 2.1 Python 實用編程:practical-python
      • 2.2 有碼變高清:pulse
      • 2.3 刷題模版:algorithm-pattern
      • 2.4 一分鐘一個小 case:python-small-examples
      • 2.5 專業管理 k8s:lens
      • 2.6 益智遊戲:shapez
      • 2.7 數據科學:GoPlus
      1. 本周 GitHub Trending #量化投資# 主題的主力軍
      • 3.1 量化交易框架:vnpy
      • 3.2 量化交易組件:easytrader
      • 3.3 30 天掌握量化交易:stock
      1. 推薦閱讀

1. 本周特推

1.1 GitHub 官方出品:super-linter

本周 star 增長數:3100+

GitHub Super Linter 是由 GitHub Services DevOps 工程團隊開源的提供給 Action 調用的存儲庫,目的是保持我們文檔和代碼的一致性,同時提升整個公司之間的交流和協作的效率。特性包括:

  • 防止將損壞的代碼上傳到主分支;
  • 幫助建立多種語言的編碼最佳實踐;
  • 制訂代碼布局和格式的指南;
  • 自動化流程以幫助簡化代碼審查;

GitHub 地址→https://github.com/github/super-linter/

1.2 彩虹屁 VSCode 插件:vscode-rainbow-fart

本周 star 增長數:1800+

Newvscode-rainbow-fart 是一個彩虹屁 VSCode 插件,在你編程時瘋狂稱讚你,可以根據代碼關鍵字播放貼近代碼意義的真人語音,誇你寫代碼牛逼。

GitHub 地址→https://github.com/SaekiRaku/vscode-rainbow-fart

2. GitHub Trending 周榜

2.1 Python 實用編程:practical-python

本周 star 增長數:1850+

practical-python 是一個從事 Python 編程近三十年的工程師出的 Python 核心課程,它需要你 3、4 天的學習時間,大約 25-35 小時的時間,包括 130 多個項目實踐。

GitHub 地址→https://github.com/dabeaz-course/practical-python

2.2 有碼變高清:pulse

本周 star 增長數:1500+

Newpulse 是一個可以將馬賽克圖片百年變成高清圖的工具,近日由杜克大學(Duke University)研究團隊開發了。作為一款 AI 修圖黑科技 PULSE,可以解決所有低像素煩惱。據說它能夠將圖像原始分辨率放大 64 倍,任何渣畫質都可以秒變高清、逼真圖像,甚至被打了馬賽克的人臉圖像,毛孔、皺紋,頭髮也都能被清晰還原。

GitHub 地址→https://github.com/adamian98/pulse

2.3 刷題模版:algorithm-pattern

本周 star 增長數:2800+

Newalgorithm-pattern 是項目作者找工作時,從 0 開始刷 LeetCode 的心得記錄,通過各種刷題文章、專欄、視頻等總結的一套自己的刷題模板。

GitHub 地址→https://github.com/greyireland/algorithm-pattern

2.4 一分鐘一個小 case:python-small-examples

本周 star 增長數:10900+

python-small-examples 是一個告別枯燥,60 秒學會一個 Python 小例子的項目,目前庫已有 223 個實用的小例子 。

GitHub 地址→https://github.com/jackzhenguo/python-small-examples

2.5 專業管理 k8s:lens

本周 star 增長數:800+

Len 是一個開源、免費可用的 IDE,可方便管理 Kubernetes 的工具。

GitHub 地址→https://github.com/lensapp/lens

2.6 益智遊戲:shapez.io

本周 star 增長數:600+

shapez.io 是一個受 Factorio 啟發的搭建遊戲。你要做的事情就是簡單地通過切割,旋轉,合併和繪製形狀的零件來產生形狀。

GitHub 地址→https://github.com/tobspr/shapez.io

2.7 數據科學:GoPlus

本周 star 增長數:1800+

NewGoPlus 是數據科學的 Go+ 語言。

GitHub 地址→https://github.com/qiniu/goplus

3. 本周 GitHub Trending #投資量化#主題的主力軍

在本期主題模塊,小魚乾這裏選取了 3 個和量化相關的小工具,希望能增加你的收入,養肥你的錢包。

3.1 量化交易框架:vnpy

vn.py 是一套基於 Python 的開源量化交易系統開發框架。

GitHub 地址→https://github.com/vnpy/vnpy

3.2 量化交易組件:easytrader

easytrader 是一個提供同花順客戶端/國金/華泰客戶端/雪球的基金、股票自動程序化交易以及自動打新,支持跟蹤 joinquant /ricequant 模擬交易和實盤雪球組合的量化交易組件。特性:

  • 進行自動的程序化股票交易
  • 支持跟蹤 joinquant, ricequant 的模擬交易
  • 支持跟蹤雪球組合調倉
  • 支持通用的同花順客戶端模擬操作
  • 實現自動登錄
  • 支持通過 webserver 遠程操作客戶端
  • 支持命令行調用,方便其他語言適配
  • 基於 Python 3.6, Win。注: Linux 僅支持雪球

GitHub 地址→https://github.com/shidenggui/easytrader

3.3 30 天掌握量化交易:stock

stock 是作者作為業餘投機者(韭菜)一枚,自學量化交易,把經歷寫成代碼推送到 GitHub 的項目。

GitHub 地址→https://github.com/Rockyzsu/stock

推薦閱讀

  • GitHub 熱點速覽 Vol.24:程序員自我增值,優雅賺零花錢
  • GitHub 熱點速覽 Vol.23:前後端最佳實踐
  • GitHub 熱點速覽 Vol.22:如何打造超級技術棧

以上為 2020 年第 23 個工作周的 GitHub Trending 如果你 Pick 其他好玩、實用的 GitHub 項目,記得來 HelloGitHub issue 區和我們分享下喲

HelloGitHub 交流群現已全面開放,添加微信號:HelloGitHub 為好友入群,可同前端、Java、Go 等各界大佬談笑風生、切磋技術~

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

【其他文章推薦】

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

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

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

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

Netty源碼分析之自定義編解碼器

在日常的網絡開發當中,協議解析都是必須的工作內容,Netty中雖然內置了基於長度、分隔符的編解碼器,但在大部分場景中我們使用的都是自定義協議,所以Netty提供了  MessageToByteEncoder<I>  與  ByteToMessageDecoder  兩個抽象類,通過繼承重寫其中的encode與decode方法實現私有協議的編解碼。這篇文章我們就對Netty中的自定義編解碼器進行實踐與分析。

一、編解碼器的使用

下面是MessageToByteEncoder與ByteToMessageDecoder使用的簡單示例,其中不涉及具體的協議編解碼。

創建一個sever端服務

EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        final CodecHandler codecHandler = new CodecHandler();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100)
                    .handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline p = ch.pipeline();
                            if (sslCtx != null) {
                                p.addLast(sslCtx.newHandler(ch.alloc()));
                            }
                            //添加編解碼handler
                            p.addLast(new MessagePacketDecoder(),new MessagePacketEncoder());
                            //添加自定義handler
                            p.addLast(codecHandler);
                        }
                    });

            // Start the server.
            ChannelFuture f = b.bind(PORT).sync();

繼承MessageToByteEncoder並重寫encode方法,實現編碼功能

public class MessagePacketEncoder extends MessageToByteEncoder<byte[]> {

    @Override
    protected void encode(ChannelHandlerContext ctx, byte[] bytes, ByteBuf out) throws Exception {
        //進行具體的編碼處理 這裏對字節數組進行打印
        System.out.println("編碼器收到數據:"+BytesUtils.toHexString(bytes));
        //寫入並傳送數據
        out.writeBytes(bytes);
    }
}

繼承ByteToMessageDecoder 並重寫decode方法,實現解碼功能

public class MessagePacketDecoder extends ByteToMessageDecoder {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out){
        try {
            if (buffer.readableBytes() > 0) {
                // 待處理的消息包
                byte[] bytesReady = new byte[buffer.readableBytes()];
                buffer.readBytes(bytesReady);
                //進行具體的解碼處理
                System.out.println("解碼器收到數據:"+ByteUtils.toHexString(bytesReady));
                //這裏不做過多處理直接把收到的消息放入鏈表中,並向後傳遞
                out.add(bytesReady);
            
            }
        }catch(Exception ex) {
            
        }

    }

}

實現自定義的消息處理handler,到這裏其實你拿到的已經是編解碼后的數據

public class CodecHandler extends ChannelInboundHandlerAdapter{
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        System.out.println("CodecHandler收到數據:"+ByteUtils.toHexString((byte[])msg));
        byte[] sendBytes = new byte[] {0x7E,0x01,0x02,0x7e};
        ctx.write(sendBytes);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}

運行一個客戶端模擬發送字節0x01,0x02,看一下輸出的執行結果

解碼器收到數據:0102
CodecHandler收到數據:0102
編碼器收到數據:7E01027E

 根據輸出的結果可以看到消息的入站與出站會按照pipeline中自定義的順序傳遞,同時通過重寫encode與decode方法實現我們需要的具體協議編解碼操作。

二、源碼分析

 通過上面的例子可以看到MessageToByteEncoder<I>與ByteToMessageDecoder分別繼承了ChannelInboundHandlerAdapter與ChannelOutboundHandlerAdapter,所以它們也是channelHandler的具體實現,並在創建sever時被添加到pipeline中, 同時為了方便我們使用,netty在這兩個抽象類中內置與封裝了一些其操作;消息的出站和入站會分別觸發write與channelRead事件方法,所以上面例子中我們重寫的encode與decode方法,也都是在父類的write與channelRead方法中被調用,下面我們就別從這兩個方法入手,對整個編解碼的流程進行梳理與分析。

1、MessageToByteEncoder

編碼需要操作的是出站數據,所以在MessageToByteEncoder的write方法中會調用我們重寫的encode具體實現, 把我們內部定義的消息實體編碼為最終要發送的字節流數據發送出去。

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        ByteBuf buf = null;
        try {
            if (acceptOutboundMessage(msg)) {//判斷傳入的msg與你定義的類型是否一致
                @SuppressWarnings("unchecked")
                I cast = (I) msg;//轉為你定義的消息類型
                buf = allocateBuffer(ctx, cast, preferDirect);//包裝成一個ByteBuf
                try {
                    encode(ctx, cast, buf);//傳入聲明的ByteBuf,執行具體編碼操作
                } finally {
                    /**
                     * 如果你定義的類型就是ByteBuf 這裏可以幫助你釋放資源,不需要在自己釋放
                     * 如果你定義的消息類型中包含ByteBuf,這裡是沒有作用,需要你自己主動釋放
                     */
                    ReferenceCountUtil.release(cast);//釋放你傳入的資源
                }

                //發送buf
                if (buf.isReadable()) {
                    ctx.write(buf, promise);
                } else {
                    buf.release();
                    ctx.write(Unpooled.EMPTY_BUFFER, promise);
                }
                buf = null;
            } else {
                //類型不一致的話,就直接發送不再執行encode方法,所以這裏要注意如果你傳遞的消息與泛型類型不一致,其實是不會執行的
                ctx.write(msg, promise);
            }
        } catch (EncoderException e) {
            throw e;
        } catch (Throwable e) {
            throw new EncoderException(e);
        } finally {
            if (buf != null) {
                buf.release();//釋放資源
            }
        }
    }

 MessageToByteEncoder的write方法要實現的功能還是比較簡單的,就是把你傳入的數據類型進行轉換和發送;這裡有兩點需要注意:

  • 一般情況下,需要通過重寫encode方法把定義的泛型類型轉換為ByteBuf類型, write方法內部自動幫你執行傳遞或發送操作;
  • 代碼中雖然有通過ReferenceCountUtil.release(cast)釋放你定義的類型資源,但如果定義的消息類中包含ByteBuf對象,仍需要主動釋放該對象資源;

2、ByteToMessageDecoder

從命名上就可以看出ByteToMessageDecoder解碼器的作用是把字節流數據編碼轉換為我們需要的數據格式

作為入站事件,解碼操作的入口自然是channelRead方法

 @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof ByteBuf) {//如果消息是bytebuff
            CodecOutputList out = CodecOutputList.newInstance();//實例化一個鏈表
            try {
                ByteBuf data = (ByteBuf) msg;
                first = cumulation == null;
                if (first) {
                    cumulation = data;
                } else {
                    cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data);
                }
                callDecode(ctx, cumulation, out);//開始解碼
            } catch (DecoderException e) {
                throw e;
            } catch (Exception e) {
                throw new DecoderException(e);
            } finally {
                if (cumulation != null && !cumulation.isReadable()) {//不為空且沒有可讀數據,釋放資源
                    numReads = 0;
                    cumulation.release();
                    cumulation = null;
                } else if (++ numReads >= discardAfterReads) {
                    // We did enough reads already try to discard some bytes so we not risk to see a OOME.
                    // See https://github.com/netty/netty/issues/4275
                    numReads = 0;
                    discardSomeReadBytes();
                }

                int size = out.size();
                decodeWasNull = !out.insertSinceRecycled();
                fireChannelRead(ctx, out, size);//向下傳遞消息
                out.recycle();
            }
        } else {
            ctx.fireChannelRead(msg);
        }
    }

callDecode方法內部通過while循環的方式對ByteBuf數據進行解碼,直到其中沒有可讀數據 

    protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
        try {
            while (in.isReadable()) {//判斷ByteBuf是還有可讀數據
                int outSize = out.size();//獲取記錄鏈表大小

                if (outSize > 0) {//判斷鏈表中是否已經有數據
                    fireChannelRead(ctx, out, outSize);//如果有數據繼續向下傳遞
                    out.clear();//清空鏈表

                    // Check if this handler was removed before continuing with decoding.
                    // If it was removed, it is not safe to continue to operate on the buffer.
                    //
                    // See:
                    // - https://github.com/netty/netty/issues/4635
                    if (ctx.isRemoved()) {
                        break;
                    }
                    outSize = 0;
                }

                int oldInputLength = in.readableBytes();
                decodeRemovalReentryProtection(ctx, in, out);//開始調用decode方法

                // Check if this handler was removed before continuing the loop.
                // If it was removed, it is not safe to continue to operate on the buffer.
                //
                // See https://github.com/netty/netty/issues/1664
                if (ctx.isRemoved()) {
                    break;
                }

                //這裏如果鏈表為空且bytebuf沒有可讀數據,就跳出循環
                if (outSize == out.size()) {
                    if (oldInputLength == in.readableBytes()) {
                        break;
                    } else {//有可讀數據繼續讀取
                        continue;
                    }
                }

                if (oldInputLength == in.readableBytes()) {//beytebuf沒有讀取,但卻進行了解碼
                    throw new DecoderException(
                            StringUtil.simpleClassName(getClass()) +
                                    ".decode() did not read anything but decoded a message.");
                }

                if (isSingleDecode()) {//是否設置了每條入站數據只解碼一次,默認false
                    break;
                }
            }
        } catch (DecoderException e) {
            throw e;
        } catch (Exception cause) {
            throw new DecoderException(cause);
        }
    }

decodeRemovalReentryProtection方法內部會調用我們重寫的decode解碼實現

    final void decodeRemovalReentryProtection(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
            throws Exception {
        decodeState = STATE_CALLING_CHILD_DECODE;//標記狀態
        try {
            decode(ctx, in, out);//調用我們重寫的decode解碼實現
        } finally {
            boolean removePending = decodeState == STATE_HANDLER_REMOVED_PENDING;
            decodeState = STATE_INIT;
            if (removePending) {//這裏判斷標記,防止handlerRemoved事件與解碼操作衝突
                handlerRemoved(ctx);
            }
        }
    }

channelRead方法中接受到數據經過一系列邏輯處理,最終會調用我們重寫的decode方法實現具體的解碼功能;在decode方法中我們只需要ByteBuf類型的數據解析為我們需要的數據格式直接放入 List<Object> out鏈表中即可,ByteToMessageDecoder會自動幫你向下傳遞消息。

三、總結

通過上面的講解,我們可以對Netty中內置自定義編解碼器MessageToByteEncoder與ByteToMessageDecoder有一定的了解,其實它們本質上是Netty封裝的一組專門用於自定義編解碼的channelHandler實現類。在實際開發當中基於這兩個抽象類的實現非常具有實用性,所以在這裏稍作分析, 其中如有不足與不正確的地方還望指出與海涵。

 

關注微信公眾號,查看更多技術文章。

 

 

轉載說明:未經授權不得轉載,授權后務必註明來源(註明:來源於公眾號:架構空間, 作者:大凡)

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

【其他文章推薦】

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

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

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

※幫你省時又省力,新北清潔一流服務好口碑

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

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

「從零單排canal 03」 canal源碼分析大綱

在前面兩篇中,我們從基本概念理解了canal是一個什麼項目,能應用於什麼場景,然後通過一個demo體驗,有了基本的體感和認識。

從這一篇開始,我們將從源碼入手,深入學習canal的實現方式。了解canal相關功能的實現方式,其中有很多機制是非常值得深入了解的,從代碼實現角度去學習實時數據訂閱與同步的實現與核心技術點。當然,如果要在生產中使用這個開源項目,了解源碼更是必不可少,是解決問題和新特性定製的前提條件。

本文使用的版本是1.1.4,這也是筆者寫這篇博客時的最新穩定版。

1.準備工作

下載源碼

git clone https://github.com/alibaba/canal.git

切換到1.1.4這個tag

git checkout canal-1.1.4

 

或者可以關注我的源碼註釋版本(正在不斷更新中)
https://github.com/saigu/JavaKnowledgeGraph/tree/master/code_reading/canal

2.canal項目模塊介紹

canal項目是基於maven構建的,將不同的功能模塊劃分了不同的子模塊。

我們可以簡單執行可執行模塊deployer,也可以將模塊通過maven依賴的方式,將你需要的子模塊引入到你自己的項目中進行使用開發。

 

簡單介紹下核心模塊的功能:

  • deployer模塊:獨立部署模塊,用於canal-server的獨立啟動,包括本地配置解析、拉取遠程配置、啟動canal-server。
  • server模塊:canal-server的實現邏輯,一個canal-server一般是一個jvm進程。重點關注兩種canal-server的實現方式,內嵌型的canalServerEmbed和獨立使用的canalServerWithNetty。新版本中新增了直接對接mq的canal-server實現。
  • instance模塊:具體實時訂閱任務是由一個個instance組成的,每個canal-server中可以同時運行多個instance。instance由parser、sink、store三個重點模塊組成。
  • parser模塊:數據源接入,模擬slave協議和master進行交互,協議解析。parser模塊依賴於dbsync、driver模塊。
  • sink模塊:將parser抓取到的數據,進行過濾,加工,然後發送到store模塊進行存儲。核心接口為CanalEventSink。
  • store模塊:數據存儲模塊,類似內存模式到消息隊列,本質上是一個RingBuffer。核心接口為CanalEventStore。
  • meta模塊:增量訂閱&消費信息管理器,核心接口為CanalMetaManager,主要用於記錄canal消費到的mysql binlog的位置
  • client模塊:項目最早的消費客戶端,通過將client模塊引入自己的項目中,然後直接消費canal-server獲取的數據。
  • client-adapter模塊:1.1.x后新出的模塊,可以獨立部署為canal-server的消費服務端,是一個springboot項目。通過SPI機制,能夠加載不同plugins,將消費信息投遞到ES\hbase\rdb等下游。
  • admin模塊:1.1.x新出的模塊,可以獨立部署為canal-server的控制台,配置canal-server、instance相關配置,非常好用。

3.模塊關聯

那這些模塊之間是如何組織、如何關聯的呢?

我們從整體到局部來看一下。

整體架構關聯,包括admin模塊、server模塊、client-adapter模塊

 

1)server模塊是服務端核心模塊,用來拉取binlog的實時變更,然後投遞到客戶端。

2)server可以通過配置,選擇投遞到MQ,或者是啟動一個netty,讓客戶端來拉取。

3)client-adapter就是一個獨立部署到服務,可以直接拉取canal-server的消息(或者拉取mq的消息),轉發到對應RDS/Redis/HBase,當然,你也可以自己實現一個轉發到redis的adapter

4)admin模塊是管理控制台,可以調度canal-server組成一個個集群實現instance的高可用、可以更改server、instance的配置信息。

Canal-server模塊局部關係,包括deployer模塊、server模塊、instance模塊、parser模塊、sink模塊、store模塊、meta模塊、client模塊。

 

1)deployer模塊是一個啟動模塊,可以啟動canal-server。

2)一個server是一個獨立應用,是一個jvm進程,裏面可以有多個instance對象。

3)instance內包括了parser、sink、store、meta

4)parser負責獲取binlog變更,然後sink將parser獲取的binlog變更轉換為event,存入store。

5)meta是元信息管理器

6)client模塊可以內嵌入你的應用,用來消費canal-server的消息事件。

基本上核心模塊的關係就是這樣了,後續會按照模塊的維度進行源碼分析,敬請期待。

 

都看到最後了,原創不易,點個關注,點個贊吧~

文章持續更新,可以微信搜索「阿丸筆記 」第一時間閱讀,回復關鍵字【學習】有我準備的一線大廠面試資料。

知識碎片重新梳理,構建Java知識圖譜: github.com/saigu/JavaK…(歷史文章查閱非常方便)

 

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

【其他文章推薦】

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

自動化測試實戰技巧:「用例失敗重試機制」實現方案分享

1. 背景說明

在開展自動化測試工作時,經常會由於一些外在原因(如網絡中斷、返回超時)導致自動化測試用例運行失敗,而這些失敗並不是用例本身驗證或被測程序存在Bug而引起的,更可氣的是這些失敗場景有可能還是偶發的,為了保證測試用例運行的穩定性驗證有效性,我們需要一種針對失敗用例重試的運行機制。

今天給大家分享的主題:自動化測試工作中,用例腳本失敗重試機制的實現方式。

結合自動化測試框架來講,用例運行失敗重試機制,通常有三種形式來實現:

  • 藉助依賴框架自身是否有用例失敗重試運行機制。

  • 從用例腳本自身邏輯處入手,實現失敗運行重試。(適用於被特殊處理過的用例邏輯)

  • 從擴展框架源碼,自定義失敗重試運行機制。(通常適合於所有失敗用例)

接下來,我們以Robot Framework框架為例,以具體的實戰示例項目介紹如何實現用例失敗重試機制。

2. 示例項目環境搭建

為了便於演示,重新創建一套新的虛擬隔離環境,用於搭建Robot Framework框架,操作步驟如下。

1、創建虛擬環境robotframework_env

python3 -m venv robotframework_env

2、激活虛擬環境

cd robotframework_env
source bin/activate

3、在虛擬環境中,安裝robotframework、robotframework-ride庫(安裝最新即可)。

pip install robotframework
pip install robotframework-ride

如下圖所示:

4、 輸入命令:bin ./ride.py啟動RIDE,如下圖所示。

PS: 其它三方庫演示項目中,暫不需要,讀者可根據實際需求,自行安裝。

3. 創建實戰示例項目

1、 創建trainning演示項目,並在項目下,創建失敗重試機制實戰目錄,並依次創建測試套件、測試用例,示例結構如下:

2、 編寫測試用例,測試用例邏輯如下:

*** Settings ***
Library           Collections

*** Test Cases ***
Class_01_隨機取數,模擬隨機出現失敗場景
    @{list}=    create list    1    2    3
    ${random_num}=    Evaluate    random.choice(${list})    random
    log    ${random_num}
    should be true    ${random_num}==2
  • 在測試用例中,先通過create list關鍵字創建了一個名稱為${list}的列表變量,並依次存入1、2、3三個元素。
  • 再通過Evaluate萬能關鍵字,結合random.chocie方法,從${list}列表中隨機取出一個整型元素,保存到名稱為${random_num}變量中。
  • 最後,通過should be true關鍵字,斷言${random_num}變量等於2,由於第二步的隨機取值,會讓${random_num}變量值具有隨機性(可能等於2,也可能是1或3),從而實現模擬一條隨機失敗的用例場景。

運行成功結果:

運行失敗結果:

4. 用例失敗重試機制實現

Robot Framework 官方並沒有提供類似retry等參數來配置失敗用例重執行。僅僅提供了--rerunfailed參數對基於結果文件output.xml來選擇重新執行失敗的用例。

4.1 基於RF框架自身的重試機制

1、 以第3節中新建的示例項目為例,為了便於演示,以命令行來操作,在命令行中輸入執行用例命令,並且將輸出文件保存到original.xml文件中。

robot --output original.xml .

2、 重新運行測試用例,並將第二次運行的結果文件輸出保存到rerun.xml文件中。

robot --output rerun.xml --rerunfailed original.xml .

3、合併兩次運行的結果輸出文件。

rebot --merge original.xml rerun.xml

在Robot Framework中除了有--rerunfailed參數針對失敗的測試用例外,也有針對測試套件的--rerunfailedsuites,參數詳細說明如下:

-R --rerunfailed output  Select failed tests from an earlier output file to be
                          re-executed. Equivalent to selecting same tests
                          individually using --test option.

------------------------------------------------
 -S --rerunfailedsuites output  Select failed suite from an earlier output file
                          to be re-executed. New in RF 3.0.1.

  • -R--rerunfailed參數非常有用,它的作用是從output file中選擇失敗的用例重跑。但是有個問題,如果上一次運行時用例全部成功,此時加上-R參數再去運行用例時會報錯: failed: All tests passed ,這導致我沒辦法在jenkins job中使用這個參數。

  • -S--rerunfailedsuites參數和-R參數的作用類似,它的作用是從output file中選擇失敗的用例套件重跑。

4.2 基於用例腳本邏輯重試機制

第二種方法,我們介紹,如何基於用例腳本邏輯特殊改造,實現用例失敗后的重試機制。

基於用例邏輯增加重試機制,核心實現思路:基於RF內置變量${TEST_STATUS}獲取用例運行結果,再結合Teardown運行改造后的關鍵字邏輯即可。

操作如下:

1、對示例1中的Class_01 測試用例進行改造,抽取用例邏輯部分,存放到單獨的關鍵字下,名稱如測試用例關鍵字

*** Keywords ***
測試用例關鍵字
    @{list}=    create list    1    2    2
    ${random_num}=    Evaluate    random.choice(${list})    random
    log    ${random_num}
    should be true    ${random_num}==2

2、 添加關鍵字用例重試機制,增加用例重試機制的處理邏輯:

*** Keywords ***
用例重試機制
    [Arguments]    ${times}
    ${status}=    set variable    ${TEST STATUS}
    FOR    ${index}    IN RANGE    ${times}
        log    第${index+1}運行結果: ${status}
        Exit For loop if    '${status}'=='PASS' or '${status}'=='True'
        log    第${index+1}次重試運行
        ${status}=    Run keyword And Return Status    測試用例關鍵字
    END

用例重試機制關鍵字中,先通過${TEST STATUS}內置變量,獲取用例執行結果,並且接收變量${times}用於控制重試次數,如果用例執行狀態等於PASS則直接退出重試,否則調用Run keyword And Return Status關鍵字繼續運行測試用例。

3、為了便於演示,增加一條名稱為Class_02測試用例,內容如下:

Class_02_隨機取數,模擬隨機出現失敗場景
    測試用例關鍵字
    [Teardown]    run keyword    用例重試機制    5

到此, 我們已經在用例邏輯層面實現了用例失敗重試機制了。

PS: 針對用例邏輯層面實現重試機制,也可以採用關鍵字: Wait Until Keyword Succeeds,讀者可根據自身需求進行改造,本文的用例重試機制並不是唯一的方法。

4.3 基於框架源碼實現重試機制

除了上述兩種方法,最後一種方法是基於框架層面進行改造,增加全局重試機制,

通過改寫Robot Framework源代碼增加--retry選項,實現test級別的失敗用例自動再執行,比如用例失敗后,會重新運行N次,直至成功or 耗盡重試次數,生成的日誌和報告文件中只會體現最後一次執行的結果。

類似如下命令格式:

robot --retry 3 trainning

具體實現:

1、修改文件 : robotframework_env/lib/python3.7/site-packages/robot/run.py,在USAGE變量里添加retry參數。

 -F --extension value     Parse only files with this extension when executing
                          a directory. Has no effect when running individual
                          files or when using resource files. If more than one
                          extension is needed, separate them with a colon.
                          Examples: `--extension txt`, `--extension robot:txt`
                          New in RF 3.0.1. Starting from RF 3.2 only `*.robot`
                          files are parsed by default.
 -N --name name           Set the name of the top level suite. By default the
                          name is created based on the executed file or
                          directory.
 -H --retry retry     Set the retry times if test failed.

2、在run.py文件,RobotFramework類增加make方法,並在開始之前導入庫from xml.dom import minidom

def make(self, outxml):
        xmldoc = minidom.parse(outxml)
        suiteElementList = xmldoc.getElementsByTagName('suite')
        mySuite = []
        for suiteElement in suiteElementList:
            if suiteElement.childNodes is not None:
                for element in suiteElement.childNodes:
                    if element.nodeName == 'test':
                        mySuite.append(suiteElement)
                        break
        for suite in mySuite:
            testElements = {}
            for element in suite.childNodes:
                if element.nodeName == 'test':
                    name = element.getAttribute('name')
                    if testElements.get(name) == None:
                        testElements.update({name: [element]})
                    else:
                        testElements.get(name).append(element)
            for n, el in testElements.items():
                for i in el[0:-1]:
                    textElement = i.nextSibling
                    suite.removeChild(i)
                    suite.removeChild(textElement)
        savefile = open(outxml, 'w')
        root = xmldoc.documentElement
        root.writexml(savefile)
        savefile.close()

3、RobotFramework類的main方法,加入紅色內容 self.make(settings.output)

4、 打開robot/conf/setting.py文件,修改_cli_opts字典,增加'Retry': ('retry', 3),,如下所示:

5、打開robot/model/itemlist.py文件,修改visit方法:

    def visit(self, visitor):
        for item in self:
            if self.__module__ == 'robot.model.testcase' and hasattr(visitor, "_context"):
                testStatus = ''
                for i in range(0, int(visitor._settings._opts['Retry'])):
                    if testStatus != 'PASS':
                        if item.name in visitor._executed_tests:
                            visitor._executed_tests.pop(item.name)
                        item.visit(visitor)
                        testStatus = visitor._context.variables['${PREV_TEST_STATUS}']
                    else:
                        break
            else:
                item.visit(visitor)

6、做完如上配置之後,我們來驗證一下參數是否配置成功了,輸入robot —help查看一下配置參數項。

7、 輸入如下命令,結合Class_01用例,驗證用例失敗重試機制:

robot --test Class_01_隨機取數,模擬隨機出現失敗場景 --retry 3 .

如果測試用例運行結果為PASS,運行一次即正常結束,如果用例運行失敗,則會重試3次執行。

5. 小結

本文以Robot Framework框架為例,介紹了在自動化測試過程中,如何實現用例腳本失敗重試機制,並且分享了三類實現思路:

  • 藉助依賴框架自身是否有用例失敗重試運行機制。

  • 從用例腳本自身邏輯處入手,實現失敗運行重試。(適用於被特殊處理過的用例邏輯)

  • 從擴展框架源碼,自定義失敗重試運行機制。(通常適合於所有失敗用例)

認真品味本文的讀者,會發現,雖然本文內容是以Robot Framework框架為例,但其實任何自動化測試框架,要實現測試用例腳本重試機制,都繞不開本文所提到的三類實現方式思路。學會變通、靈活運用才是王道

希望對大家在實施自動化測試工作當中有所幫助或啟發!如果覺得有用,不用以身相許,關注一下就行

原文傳送門: 原文閱讀

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

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

※超省錢租車方案

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

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

德國傳統電廠熱浪中掙扎 整體供電仍穩定

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

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

【其他文章推薦】

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

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

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

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

用樹的高度來建築  弗班社區找回古城區的感覺

環境資訊中心特約記者 陳文姿報導

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

【其他文章推薦】

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

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

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

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

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

危及蜜蜂存亡 加國漸禁2款類尼古丁農藥

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

加拿大政府今天(16日)表示,將逐步限用和水生昆蟲及蜜蜂死亡有關連的2款類尼古丁農藥。環保人士認為這是一大勝利,但對販售殺蟲劑的企業而言卻是最新挫敗。

加拿大衛生署(Health Canada)病蟲害管制局(PMRA)表示,未來三到五年之間將逐步禁止在戶外使用賽速安(Thiamethoxam)及可尼丁(Clothianidin)。

加拿大衛生署這項措施將有90天諮詢期,最後在2019年底做出決定。

加拿大衛生署也計畫在今年底之前做出最終決定,是否逐步禁止第3種的類尼古丁農藥,拜耳生產的益達胺(imidacloprid)。

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

【其他文章推薦】

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

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

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

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

擔心成為新的傾倒場 泰國將禁止電子垃圾進口

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

環境部一名官員今天(16日)說,泰國將在6個月內禁止進口432種廢棄電子用品。中國今年禁止高科技廢棄物進口以來,泰國成為最新呼應的國家。

泰國下達禁令數週前,區域鄰國越南表示,將停止核發新執照給廢棄物進口,並將取締非法運送廢紙、塑膠和金屬。

泰國環境部一名高級官員今天告訴路透社,禁令涵蓋432種電子廢棄物,從電路板到老舊電視機和收音機零件等等,將在6個月內生效。

他並說,禁令是環境部長素拉薩(Surasak Kanchanarat)昨天主持會議時做成決定。

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

【其他文章推薦】

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

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

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

※幫你省時又省力,新北清潔一流服務好口碑

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

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