香港促推電動車用電池回收機制,避免生態汙染

一般的電動車用電池約有7~8 年的使用壽命,但因含有鋰、硫酸等重金屬,若無妥善回收處理,將有環境汙染之虞。香港政府所引入的首批電動車之電池屆臨退役,專家呼籲政府盡速制訂回收辦法,以避免更嚴重的環境破壞。

香港《蘋果日報》報導,香港在豁免電動車首次登記稅後,電動車數量急速從2010年的100輛以下成長到今年8月的6,167輛,其中以Tesla和Volkswagen最受歡迎。以一般電動車用電池約7年的平均壽命來看,香港首批電動車的舊電池已屆臨汰役,但香港目前仍無回收制度;若廢電池隨意丟棄,所造成的重金屬汙染恐怕較傳統柴油車和汽油車更嚴重,完全背離環保的美意。

香港汽車會副會長李耀培表示,車用電池的儲電量若降低到70%,就可能影響電動車行動速度,甚至造成熄火,進而影響行車安全,因此在此階段就會汰役換新。不過,這也意味著汰役的車用電池其實仍有70%的蓄電量,經妥善處理後可進行二次運用。

報導指出,香港過去會將這類廢電池交給有牌照的化學廢物處置設施進行初步處理,之後再出口到海外回收。但李耀培表示,這只是把汙染丟出香港,並沒有解決問題。他呼籲政府應制定長遠的回收機制,或可參考英國、歐洲等國家將汰役的電動車用電池改製為風力、太陽能儲能設備,更能有效利用電池,且也有助環保。

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

【其他文章推薦】

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

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

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

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

※回頭車貨運收費標準

聚甘新

BMW 2017年目標售出10萬輛電動車

德國媒體報導,汽車廠商BMW CEO Harald Krueger 在接受採訪時表示,電動車的時代即將到來,這一市場還有巨大的潛力,BMW 將在2017 年力爭把電動車的銷量提升到10 萬台,目前該公司2015 年電動車和混合動力車的銷量大約為6 萬台。

BMW CEO Harald Krueger 表示,該公司希望能夠在2017 年大幅提升電動車的銷量,至少提升六成,達到年銷量10 萬台。BMW 2016 年電動車和混合動力車的銷量合計大約為6 萬台,自2013 年發售電動車以來,BMW 電動車的雷軍銷量已經超過了10 萬台。

Harald Krueger 認為電動車的時代即將到來,這一市場還有很大的潛力,需求還沒有完全被發掘。

BMW 旗下唯一的純電動車車款i3 上市以來市場反響不如預期,2015 年i3 僅售出了2.5 萬台,為了提升這款產品的競爭力,BMW 將在2016 年的升級中把i3 系列電動車的續航里程提升50%。

在高階汽車市場中,BMW 的產品銷量已經落後於Benz,該公司希望在2025 年前將電動車和混合動力車的銷量提升到15%-25%。

(本文由《》授權提供。照片來源:)

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

聚甘新

電動車有害道路安全?美國規定汽車不能太安靜!

根據外電報導,美國政府正式通過一則汽車行駛新規定,也就是未來一些「安靜的汽車」,如電動汽車或混合動力汽車等,在行駛速度超過時速18.6 英里 (約30 公里)的情況下,必須增加噪音警報裝置,以避免傷及行人、盲人和騎車的路人。

根據美國國家公路交通安全管理局(NHTSA)發布的這則新規定表示,該規定將在2019 年9 月1 日起生效。屆時,包括特斯拉 (TESLA)、日產汽車 (Nisson) 和豐田汽車 (TOYOTA) 等廠商都需要為所有汽車增加噪音裝置。美國政府希望通過此規定,能夠防止每年多達2,400 件的行人受傷事故,並為53 萬輛汽車增設噪音專制。

NHTSA 指出,這項新規每年約會給汽車廠商增加3,900 萬美元的花費。因為,他們要為汽車增設一個外設防水喇叭。但是,能夠透過降低交通事故而每年節省約2.5 億到3.2 億美元的醫療費用。

該規定未來將適用於全美各地區,並且涵蓋路上行駛的電動汽車和混合動力汽車等。另外,包括帶有4 個輪子、總重量低於10,000 磅的車輛,也都必須在向後或向前行駛時,在速度達到每小時30 公里之際發出聲響,以警告周遭的路人與騎車的民眾。不過,NHTSA 也認為,規定汽車喇叭可有可無,因為輪胎和風也可以製造出足夠的噪音聲響,只要該聲響能達到警告的標準即可。

該項新的規定雖然受到全美盲人主組織的歡迎,但是卻讓汽車廠商表示擔憂。因為噪音也許會太大,並且實施起來將可能相當複雜。因為,新規只規定了最低的聲音要求,卻沒有制定必須發出什麼聲音。

(本文由《》授權提供。照片來源:特斯拉)

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

聚甘新

對 JsonConvert 的認識太膚淺了,終於還是遇到了問題

一:背景

1. 講故事

在開始本文之前,真的好想做個問卷調查,到底有多少人和我一樣,對 JsonConvert 的認識只局限在 SerializeObjectDeserializeObject 這兩個方法上(┬_┬), 這樣我也好結伴同行,不再孤單落魄,或許是這兩個方法基本上能夠解決工作中 80% 的場景,對於我來說確實是這樣,但隨着編碼的延續,終究還是會遇到那剩下的 20% ,所以呀。。。

我的場景是這樣的:前段時間寫業務代碼的時候,我有一個自定義的客戶算法類型的Model,這個Model中有這種算法類型下的客戶群以及Report統計信息,還用了 HashSet 記錄了該類型下的 CustomerID集合,為了方便講述,我把Model簡化如下:


    class CustomerAlgorithmModel
    {
        public string DisplayName { get; set; }

        public int CustomerType { get; set; }

        public ReprotModel Report { get; set; }

        public HashSet<int> CustomerIDHash { get; set; }
    }

    class ReprotModel
    {
        public int TotalCustomerCount { get; set; }

        public int TotalTradeCount { get; set; }
    }

那有意思的就來了,我個人是有記日誌的癖好,就想着以後不會出現死無對證的情況,然後就理所當然的使用 JsonConvert.SerializeObject, 這一下就出問題了,日誌送入到了 ElasticSearch ,然後通過 Kibana 查不出來,為啥呢? 看完上面的 Model 我想你也猜到了原因,json體太大了哈,好歹 CustomerIDHash 中也有幾十萬個撒,這一下全導出成json了,這 size 還能小嗎? 要不我寫段代碼看一看。


        static void Main(string[] args)
        {
            var algorithModel = new CustomerAlgorithmModel()
            {
                CustomerType = 1,
                DisplayName = "",
                Report = new ReprotModel()
                {
                    TotalCustomerCount = 1000,
                    TotalTradeCount = 50
                },
                CustomerIDHash = new HashSet<int>(Enumerable.Range(1, 500000))
            };

            var json = JsonConvert.SerializeObject(algorithModel);

            File.WriteAllText("1.txt", json, Encoding.UTF8);

            Console.WriteLine("寫入完成!");
        }

可以看到,僅一個json就 3.3M,這樣的記錄多來幾打后,在 kibana 上一檢索,瀏覽器就卡的要死,其實 CustomerIDHash 這個字段對我來說是可有可無的,就算存下來了也沒啥大用,所以需求就來了,如何屏蔽掉 CustomerIDHash

二:尋求解決方案

1. 使用 JsonIgnore

有問題就網上搜啊,這一搜馬上就有人告訴你可以使用 JsonIgnoreAttribute 忽略特性,加好這個特性後繼續跑一下程序。


    [Newtonsoft.Json.JsonIgnore]
    public HashSet<int> CustomerIDHash { get; set; }

太好了,終於搞定了,但是靜下心來想一想,總感覺心裏有那麼一點不舒服,為什麼這麼說,一旦你給這個 CustomerIDHash 套上了 JsonIgnore ,這就意味着它在 JsonConvet 的世界中從此消失,也不管是誰在使用這個Model, 但這並不是我的初衷,我的初衷僅僅是為了在記錄日誌的時候踢掉 CustomerIDHash,可千萬不要影響在其他場景下的使用哈,現在這種做法就會給自己,給別人挖坑,埋下了不可預知的bug,我想你應該明白我的意思,還得繼續尋找下一個方案。

2. 使用自定義的 JsonConverter

真的,Newtonsoft 太強大了,我都想寫一個專題好好彌補彌補我的知識盲區,其實在這個場景中不就是想把 HashSet<int> 給屏蔽掉嘛,Newtonsoft 中專門提供了一個針對特定類型的自定義處理類,接下來我就寫一段:


   /// <summary>
   /// 自定義一個 針對 HashSet<int> 的轉換類
   /// </summary>
   public class HashSetConverter : Newtonsoft.Json.JsonConverter<HashSet<int>>
   {
       public override HashSet<int> ReadJson(JsonReader reader, Type objectType, HashSet<int> existingValue, bool hasExistingValue, JsonSerializer serializer)
       {
           return existingValue;
       }

       public override void WriteJson(JsonWriter writer, HashSet<int> value, JsonSerializer serializer)
       {
           writer.WriteNull();
       }
   } 

就是這麼簡單,然後就可以在 SerializeObject 的時候指定下自定義的 HashSetConverter 即可,然後再將程序跑起來看一下。


 var json = JsonConvert.SerializeObject(algorithModel, Formatting.Indented, new HashSetConverter());

從圖中看,貌似也是解決了,但我突然發現自己要鑽牛角尖了,如果我的實體中又來了一個頂級優質客戶群的 TopNCustomerIDHash,但因為這個CustomerID 比較少,我希望在 Json 中能保留下來,然後就是踢掉的那個 CustomerIDHash 我要保留 CustomerIDHash.Length ,哈哈,搞事情哈,那接下來怎麼解決呢?

  • 修改 Model 實體

    class CustomerAlgorithmModel
    {
        public HashSet<int> CustomerIDHash { get; set; }

        // topN 優質客戶群
        public HashSet<int> TopNCustomerIDHash { get; set; }
    }

  • HashSetConverter 增加邏輯鑒別是否為保留字段

        public override void WriteJson(JsonWriter writer, HashSet<int> value, JsonSerializer serializer)
        {
            if (writer.Path == "TopNCustomerIDHash")
            {
                writer.WriteStartArray();

                foreach (var item in value)
                {
                    writer.WriteValue(item);
                }

                writer.WriteEndArray();
            }
            else
            {
                writer.WriteValue(value.Count);
            }
        }

  • 最後給 TopNCustomerIDHash 賦值

            var algorithModel = new CustomerAlgorithmModel()
            {
                CustomerType = 1,
                DisplayName = "",
                Report = new ReprotModel()
                {
                    TotalCustomerCount = 1000,
                    TotalTradeCount = 50
                },
                CustomerIDHash = new HashSet<int>(Enumerable.Range(1, 500000)),
                TopNCustomerIDHash = new HashSet<int>(Enumerable.Range(1, 10)),
            };

三塊都搞定后就可以把程序跑起來了,如下圖:

貌似鑽牛角尖的問題是解決了,既然鑽牛角尖肯定要各種鄙視,比如這裏的 ReportModel 我是不需要的,CustomerType 我也是不需要的,我僅僅需要看一下 DisplayNameTotalCustomerCount 這兩個字段就可以了, 那這個要怎麼解決呢?

3. 使用 匿名類型

確實很多時候記日誌,就是為了跟蹤 Model 中你特別關心的那幾個字段,所以摻雜了多餘的字段確實也是沒必要的,這裏可以用匿名來解決,我就來寫一段代碼:


    var json = JsonConvert.SerializeObject(new
    {
        algorithModel.DisplayName,
        algorithModel.Report.TotalCustomerCount
    }, Formatting.Indented);

三: 總結

雖然阻擊了幾個回合,但同時也發現了 Newtonsoft 中還有特別多的未挖掘功能,真的需要好好研究研究,源碼已下好,接下來準備做個系列來解剖一下,值得一提的是 .Net中已自帶了 System.Text.Json.JsonSerializer 類,目前來看功能還不算太豐富,簡單用用還是可以的,本篇就說到這裏,希望對您有幫助。

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

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

【其他文章推薦】

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

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

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

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

※回頭車貨運收費標準

聚甘新

第 10 篇 評論接口

作者:HelloGitHub-追夢人物

此前我們一直在操作博客文章(Post)資源,並藉此介紹了序列化器(Serializer)、視圖集(Viewset)、路由器(Router)等 django-rest-framework 提供的便利工具,藉助這些工具,就可以非常快速地完成 RESTful API 的開發。

評論(Comment)是另一種資源,我們同樣藉助以上工具來完成對評論資源的接口開發。

首先是設計評論 API 的 URL,根據 RESTful API 的設計規範,評論資源的 URL 設計為:/comments/

對評論資源的操作有獲取某篇文章下的評論列表和創建評論兩種操作,因此相應的 HTTP 請求和動作(action)對應如下:

HTTP請求 Action URL
GET list_comments /posts/:id/comments/
POST create /comments/

文章評論列表 API 使用自定義的 action,放在 /post/ 接口的視圖集下;發表評論接口使用標準的 create action,需要定義單獨的視圖集。

然後需要一個序列化器,用於評論資源的序列化(獲取評論時),反序列化(創建評論時)。有了編寫文章序列化器的基礎,評論序列化器就是依葫蘆畫瓢的事。

comments/serializers.py

from rest_framework import serializers
from .models import Comment


class CommentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Comment
        fields = [
            "name",
            "email",
            "url",
            "text",
            "created_time",
            "post",
        ]
        read_only_fields = [
            "created_time",
        ]
        extra_kwargs = {"post": {"write_only": True}}

注意這裏我們在 Meta 中增加了 read_only_fieldsextra_kwargs 的聲明。

read_only_fields 用於指定只讀字段的列表,由於 created_time 是自動生成的,用於記錄評論發布時間,因此聲明為只讀的,不允許通過接口進行修改。

extra_kwargs 指定傳入每個序列化字段的額外參數,這裏給 post 序列化字段傳入了 write_only 關鍵字參數,這樣就將 post 聲明為只寫的字段,這樣 post 字段的值僅在創建評論時需要。而在返回的資源中,post 字段就不會出現。

首先來實現創建評論的接口,先為評論創建一個視圖集:

comments/views.py

from rest_framework import mixins, viewsets
from .models import Comment
from .serializers import CommentSerializer

class CommentViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
    serializer_class = CommentSerializer

    def get_queryset(self):
        return Comment.objects.all()

視圖集非常的簡單,混入 CreateModelMixin 后,視圖集就實現了標準的 create action。其實 create action 方法的實現也非常簡單,我們來學習一下 CreateModelMixin 的源碼實現。

class CreateModelMixin:
    """
    Create a model instance.
    """
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def perform_create(self, serializer):
        serializer.save()

    def get_success_headers(self, data):
        try:
            return {'Location': str(data[api_settings.URL_FIELD_NAME])}
        except (TypeError, KeyError):
            return {}

核心邏輯在 create 方法:首先取到綁定了用戶提交數據的序列化器,用於反序列化。接着調用 is_valid 方法校驗數據合法性,如果不合法,會直接拋出異常(raise_exception=True)。否則就執行序列化的 save 邏輯將評論數據存入數據庫,最後返迴響應。

接着在 router 里註冊 CommentViewSet 視圖集:

router.register(r"comments", comments.views.CommentViewSet, basename="comment")

進入 API 交互後台,可以看到首頁列出了 comments 接口的 URL,點擊進入 /comments/ 后可以看到一個評論表單,在這裏可以提交評論數據與創建評論的接口進行交互。

接下來實現獲取評論列表的接口。通常情況下,我們都是只獲取某篇博客文章下的評論列表,因此我們的 API 設計成了 /posts/:id/comments/。這個接口具有很強的語義,非常符合 RESTful API 的設計規範。

由於接口位於 /posts/ 空間下,因此我們在 PostViewSet 添加自定義 action 來實現,先來看代碼:

blog/views.py

class PostViewSet(
    mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
):
    # ...
    
    @action(
            methods=["GET"],
            detail=True,
            url_path="comments",
            url_name="comment",
            pagination_class=LimitOffsetPagination,
            serializer_class=CommentSerializer,
    )
    def list_comments(self, request, *args, **kwargs):
        # 根據 URL 傳入的參數值(文章 id)獲取到博客文章記錄
        post = self.get_object()
        # 獲取文章下關聯的全部評論
        queryset = post.comment_set.all().order_by("-created_time")
        # 對評論列表進行分頁,根據 URL 傳入的參數獲取指定頁的評論
        page = self.paginate_queryset(queryset)
        # 序列化評論
        serializer = self.get_serializer(page, many=True)
        # 返回分頁后的評論列表
        return self.get_paginated_response(serializer.data)

action 裝飾器我們在上一篇教程中進行了詳細說明,這裏我們再一次接觸到 action 裝飾器更為深入的用法,可以看到我們除了設置 methodsdetailurl_path 這些參數外,還通過設置 pagination_classserializer_class 來覆蓋原本在 PostViewSet 中設置的這些類屬性的值(例如對於分頁,PostViewSet 默認為我們之前設置的 PageNumberPagination,而這裏我們替換為 LimitOffsetPagination)。

list_comments 方法邏輯非常清晰,註釋中給出了詳細的說明。另外還可以看到我們調用了一些輔助方法,例如 paginate_queryset 對查詢集進行分頁;get_paginated_response 返回分頁后的 HTTP 響應,這些方法其實都是 GenericViewSet 提供的通用輔助方法,源碼也並不複雜,如果不用這些方法,我們自己也可以輕鬆實現,但既然 django-rest-framework 已經為我們寫好了,直接復用就行,具體的實現請大家通過閱讀源碼進行學習。

現在進入 API 交互後台,進入某篇文章的詳細接口,例如訪問 /api/posts/5/,Extra Actions 下拉框中可以看到 List comments 的選項:

點擊 List comments 即可進入這篇文章下的評論列表接口,獲取這篇文章的評論列表資源了:

關注公眾號加入交流群

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

【其他文章推薦】

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

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

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

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

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

聚甘新

Redis系列(五):數據結構List雙向鏈表源碼解析和API實現

1.介紹

Redis中List是通過ListNode構造的雙向鏈表。

特點:

1.雙端:獲取某個結點的前驅和後繼結點都是O(1)

2.無環:表頭的prev指針和表尾的next指針都指向NULL,對鏈表的訪問都是以NULL為終點

3.帶表頭指針和表尾指針:獲取表頭和表尾的複雜度都是O(1)

4.帶鏈表長度計數器:len屬性記錄,獲取鏈表長度O(1)

5.多態:鏈表結點使用void*指針來保存結點的值,並且可以通過鏈表結構的三個函數為結點值設置類型特定函數,所以鏈表可以保存各種不同類型的值

雙向鏈表詳解:https://www.cnblogs.com/vic-tory/p/13140779.html

中文網:http://redis.cn/commands.html#list

2.源碼解析

// listNode 雙端鏈表節點
typedef struct listNode {

    // 前置節點
    struct listNode *prev;

    // 後置節點
    struct listNode *next;

    // 節點的值
    void *value;

} listNode;

 

// list 雙端鏈表
typedef struct list { // 在c語言中,用結構體的方式來模擬對象是一種常見的手法

    // 表頭節點
    listNode *head;

    // 表尾節點
    listNode *tail;

    // 節點值複製函數
    void *(*dup)(void *ptr);

    // 節點值釋放函數
    void(*free)(void *ptr);

    // 節點值對比函數
    int(*match)(void *ptr, void *key);

    // 鏈表所包含的節點數量
    unsigned long len;

} list;

 

/* 作為宏實現的函數 */
//獲取長度
#define listLength(l) ((l)->len)
//獲取頭節點
#define listFirst(l) ((l)->head)
//獲取尾結點
#define listLast(l) ((l)->tail)
//獲取前一個結點
#define listPrevNode(n) ((n)->prev)
//獲取后一個結點
#define listNextNode(n) ((n)->next)
//獲取結點的值 是一個void類型指針
#define listNodeValue(n) ((n)->value)

/* 下面3個函數主要用來設置list結構中3個函數指針,參數m為method的意思 */
#define listSetDupMethod(l,m) ((l)->dup = (m))
#define listSetFreeMethod(l,m) ((l)->free = (m))
#define listSetMatchMethod(l,m) ((l)->match = (m))

/* 下面3個函數主要用來獲取list結構的單個函數指針 */
#define listGetDupMethod(l) ((l)->dup)
#define listGetFree(l) ((l)->free)
#define listGetMatchMethod(l) ((l)->match)

3.API實現

listCreate函數:創建一個不包含任何結點的新鏈表

/*
 * listCreate 創建一個新的鏈表
 *
 * 創建成功返回鏈表,失敗返回 NULL 。
 *
 * T = O(1)
 */
list *listCreate(void)
{
    struct list *list;

    // 分配內存
    if ((list = zmalloc(sizeof(*list))) == NULL)
        return NULL;//內存分配失敗則返回NULL

    // 初始化屬性
    list->head = list->tail = NULL;//空鏈表
    list->len = 0;
    list->dup = NULL;
    list->free = NULL;
    list->match = NULL;

    return list;
}

listAddNodeHead函數:將一個包含給定值的新結點添加到給定鏈表的表頭

/*
 * listAddNodeHead 將一個包含有給定值指針 value 的新節點添加到鏈表的表頭
 *
 * 如果為新節點分配內存出錯,那麼不執行任何動作,僅返回 NULL
 *
 * 如果執行成功,返回傳入的鏈表指針
 *
 * T = O(1)
 */
list *listAddNodeHead(list *list, void *value)
{
    listNode *node;

    // 為節點分配內存
    if ((node = zmalloc(sizeof(*node))) == NULL)
        return NULL;

    // 保存值指針
    node->value = value;

    // 添加節點到空鏈表
    if (list->len == 0) {
        list->head = list->tail = node;
        //該結點的前驅和後繼都為NULL
        node->prev = node->next = NULL;
    }
    else { // 添加節點到非空鏈表
        node->prev = NULL;
        node->next = list->head;
        list->head->prev = node;
        list->head = node;
    }

    // 更新鏈表節點數
    list->len++;

    return list;
}

listAddNodeTail函數:將一個包含給定值的新結點插入到給定鏈表的表尾

/*
 * listAddNodeTail 將一個包含有給定值指針 value 的新節點添加到鏈表的表尾
 *
 * 如果為新節點分配內存出錯,那麼不執行任何動作,僅返回 NULL
 *
 * 如果執行成功,返回傳入的鏈表指針
 *
 * T = O(1)
 */
list *listAddNodeTail(list *list, void *value)
{
    listNode *node;

    // 為新節點分配內存
    if ((node = zmalloc(sizeof(*node))) == NULL)
        return NULL;

    // 保存值指針
    node->value = value;

    // 目標鏈表為空
    if (list->len == 0) {
        list->head = list->tail = node;
        node->prev = node->next = NULL;
    }//目標鏈非空
    else {
        node->prev = list->tail;
        node->next = NULL;
        list->tail->next = node;
        list->tail = node;
    }

    // 更新鏈表節點數
    list->len++;

    return list;
}

listInsertNode函數:將一個給定值的新結點插入到給定結點之前或者之後

/*
 * listInsertNode 創建一個包含值 value 的新節點,並將它插入到 old_node 的之前或之後
 *
 * 如果 after 為 0 ,將新節點插入到 old_node 之前。
 * 如果 after 為 1 ,將新節點插入到 old_node 之後。
 *
 * T = O(1)
 */
list *listInsertNode(list *list, listNode *old_node, void *value, int after) {
    listNode *node;

    // 創建新節點
    if ((node = zmalloc(sizeof(*node))) == NULL)
        return NULL;

    // 保存值
    node->value = value;

    // 將新節點添加到給定節點之後
    if (after) {
        node->prev = old_node;
        node->next = old_node->next;
        // 給定節點是原表尾節點
        if (list->tail == old_node) {
            list->tail = node;
        }
    }
    // 將新節點添加到給定節點之前
    else {
        node->next = old_node;
        node->prev = old_node->prev;
        // 給定節點是原表頭節點
        if (list->head == old_node) {
            list->head = node;
        }
    }

    // 更新新節點的前置指針
    if (node->prev != NULL) {
        node->prev->next = node;
    }
    // 更新新節點的後置指針
    if (node->next != NULL) {
        node->next->prev = node;
    }

    // 更新鏈表節點數
    list->len++;

    return list;
}

listDelNode函數:從指定的list中刪除給定的結點

/*
 * listDelNode 從鏈表 list 中刪除給定節點 node
 *
 * 對節點私有值(private value of the node)的釋放工作由調用者進行。該函數一定會成功.
 *
 * T = O(1)
 */
void listDelNode(list *list, listNode *node)
{
    // 調整前置節點的指針
    if (node->prev)
        node->prev->next = node->next;
    else
        list->head = node->next;

    // 調整後置節點的指針
    if (node->next)
        node->next->prev = node->prev;
    else
        list->tail = node->prev;

    // 釋放值
    if (list->free) list->free(node->value);

    // 釋放節點
    zfree(node);

    // 鏈表數減一
    list->len--;
}

listRelease函數:釋放給定鏈表以及鏈表中所有結點

 

/*
 * listRelease 釋放整個鏈表,以及鏈表中所有節點, 這個函數不可能會失敗.
 *
 * T = O(N)
 */
void listRelease(list *list)
{
    unsigned long len;
    listNode *current, *next;

    // 指向頭指針
    current = list->head;
    // 遍歷整個鏈表
    len = list->len;
    while (len--) {
        next = current->next;

        // 如果有設置值釋放函數,那麼調用它
        if (list->free) list->free(current->value);

        // 釋放節點結構
        zfree(current);

        current = next;
    }

    // 釋放鏈表結構
    zfree(list);
}

 

該函數不僅釋放了表結點的內存還釋放了表結構的內存

 listGetIterator函數:為給定鏈表創建一個迭代器

在講這個函數之前,我們應該先看看鏈表迭代器的結構:

// listIter 雙端鏈表迭代器
typedef struct listIter {

    // 當前迭代到的節點
    listNode *next;

    // 迭代的方向
    int direction;

} listIter;

迭起器只有兩個重要的屬性:當前迭代到的結點,迭代的方向

下面再看看鏈表的迭代器創建函數

/*
 * listGetIterator 為給定鏈表創建一個迭代器,
 * 之後每次對這個迭代器調用 listNext 都返回被迭代到的鏈表節點,調用該函數不會失敗
 *
 * direction 參數決定了迭代器的迭代方向:
 *  AL_START_HEAD :從表頭向表尾迭代
 *  AL_START_TAIL :從表尾想表頭迭代
 *
 * T = O(1)
 */
listIter *listGetIterator(list *list, int direction)
{
    // 為迭代器分配內存
    listIter *iter;
    if ((iter = zmalloc(sizeof(*iter))) == NULL) return NULL;

    // 根據迭代方向,設置迭代器的起始節點
    if (direction == AL_START_HEAD)
        iter->next = list->head;
    else
        iter->next = list->tail;

    // 記錄迭代方向
    iter->direction = direction;

    return iter;
}

listReleaseIterator函數:釋放指定的迭代器

/*
 * listReleaseIterator 釋放迭代器
 *
 * T = O(1)
 */
void listReleaseIterator(listIter *iter) {
    zfree(iter);
}

listRewind函數和listRewindTail函數:迭代器重新指向表頭或者表尾的函數

 

/*
 * 將迭代器的方向設置為 AL_START_HEAD,
 * 並將迭代指針重新指向表頭節點。
 *
 * T = O(1)
 */
void listRewind(list *list, listIter *li) {
    li->next = list->head;
    li->direction = AL_START_HEAD;
}

/*
 * 將迭代器的方向設置為 AL_START_TAIL,
 * 並將迭代指針重新指向表尾節點。
 *
 * T = O(1)
 */
void listRewindTail(list *list, listIter *li) {
    li->next = list->tail;
    li->direction = AL_START_TAIL;
}

listNext函數:返回當前迭代器指向的結點

 

/*
 * 返回迭代器當前所指向的節點。
 *
 * 刪除當前節點是允許的,但不能修改鏈表裡的其他節點。
 *
 * 函數要麼返回一個節點,要麼返回 NULL,常見的用法是:
 *
 * iter = listGetIterator(list,<direction>);
 * while ((node = listNext(iter)) != NULL) {
 *     doSomethingWith(listNodeValue(node));
 * }
 *
 * T = O(1)
 */
listNode *listNext(listIter *iter)
{
    listNode *current = iter->next;

    if (current != NULL) {
        // 根據方向選擇下一個節點
        if (iter->direction == AL_START_HEAD)
            // 保存下一個節點,防止當前節點被刪除而造成指針丟失
            iter->next = current->next;
        else
            // 保存下一個節點,防止當前節點被刪除而造成指針丟失
            iter->next = current->prev;
    }

    return current;
}

 

 

 

該函數保持了當前結點的下一個結點,避免了當前結點被刪除而迭代器無法繼續迭代的尷尬情況

 listDup函數:複製整個鏈表,返回副本

 

/*
 * 複製整個鏈表。
 *
 * 複製成功返回輸入鏈表的副本,
 * 如果因為內存不足而造成複製失敗,返回 NULL 。
 *
 * 如果鏈表有設置值複製函數 dup ,那麼對值的複製將使用複製函數進行,
 * 否則,新節點將和舊節點共享同一個指針。
 *
 * 無論複製是成功還是失敗,輸入節點都不會修改。
 *
 * T = O(N)
 */
list *listDup(list *orig)
{
    list *copy;//鏈表副本
    listIter *iter;//鏈表迭代器
    listNode *node;//鏈表結點

    // 創建新的空鏈表
    if ((copy = listCreate()) == NULL)
        return NULL;//創建空的鏈表失敗則返回NULL

    // 設置副本鏈表的節點值處理函數
    copy->dup = orig->dup;
    copy->free = orig->free;
    copy->match = orig->match;

    //獲取輸入鏈表的迭代器
    iter = listGetIterator(orig, AL_START_HEAD);
    
    //遍歷整個輸入鏈表進行複製
    while ((node = listNext(iter)) != NULL) {
        
        //副本結點值
        void *value;

        // 存在複製函數
        if (copy->dup) {
            
            //調用複製函數複製
            value = copy->dup(node->value);
         
            //複製結果為空,說明複製失敗
            if (value == NULL) {
                
                //複製失敗則釋放副本鏈表和迭代器,避免內存泄漏
                listRelease(copy);
                listReleaseIterator(iter);
            
                return NULL;
            }
        }
        //不存在複製函數 則直接指針指向
        else
            value = node->value;

        // 將節點添加到副本鏈表 
        if (listAddNodeTail(copy, value) == NULL) {
                
            //如果不能成功添加,則釋放副本鏈表和迭代器,避免內存泄漏
            listRelease(copy);
            listReleaseIterator(iter);
        
            return NULL;
        }
    }

    // 釋放迭代器
    listReleaseIterator(iter);

    // 返回副本
    return copy;
}

 

如果複製失敗則要注意釋放副本鏈表和迭代器,避免內存泄漏

 listSearchKey函數:查找list中值和key匹配的結點

 

/*
 * 查找鏈表 list 中值和 key 匹配的節點。
 *
 * 對比操作由鏈表的 match 函數負責進行,
 * 如果沒有設置 match 函數,
 * 那麼直接通過對比值的指針來決定是否匹配。
 *
 * 如果匹配成功,那麼第一個匹配的節點會被返回。
 * 如果沒有匹配任何節點,那麼返回 NULL 。
 *
 * T = O(N)
 */
listNode *listSearchKey(list *list, void *key)
{
    listIter *iter;//鏈表迭代器
    listNode *node;//鏈表結點

    //獲得鏈表迭代器
    iter = listGetIterator(list, AL_START_HEAD);

    //遍歷整個鏈表查詢
    while ((node = listNext(iter)) != NULL) {

        //存在比較函數
        if (list->match) {

            //利用比較函數進行比較
            if (list->match(node->value, key)) {

                //返回目標結點之前釋放迭代器空間,避免內存泄漏
                listReleaseIterator(iter);

                return node;
            }
        }
        //不存在比較函數
        else {
            //直接比較
            if (key == node->value) {

                //返回目標結點之前釋放迭代器空間,避免內存泄漏
                listReleaseIterator(iter);
                // 找到
                return node;
            }
        }
    }

    //返回目標結點之前釋放迭代器空間,避免內存泄漏
    listReleaseIterator(iter);

    // 未找到
    return NULL;
}

listIndex函數:返回鏈表在給定索引上的值

 

/*
 * 返回鏈表在給定索引上的值。
 *
 * 索引以 0 為起始,也可以是負數, -1 表示鏈表最後一個節點,諸如此類。
 *
 * 如果索引超出範圍(out of range),返回 NULL 。
 *
 * T = O(N)
 */
listNode *listIndex(list *list, long index) {
    
    listNode *n;//鏈表結點

    
    /* n不用設置成NULL的原因:
    如果索引超出範圍,
    那肯定是找到表頭或者表尾沒有找到,
    表頭的前驅和表尾的後繼都是NULL,
    所以這裏n不用設置為NULL,直接設置也可以*/
    
    // 如果索引為負數,從表尾開始查找
    if (index < 0) {
        
        //變成正數,方便索引
        index = (-index) - 1;
    
        //從尾部開始找
        n = list->tail;
        
        //尋找 因為從尾部開始找,所以是前驅
        while (index-- && n) n = n->prev;
        
    }
    
    // 如果索引為正數,從表頭開始查找
    else {
        
        //從頭部開始找
        n = list->head;
    
        //尋找 因為從頭部開始找,所以是後繼
        while (index-- && n) n = n->next;
    }

    return n;
}

listRotate函數:取出鏈表的表尾結點放到表頭,成為新的表頭結點

/*
 * 取出鏈表的表尾節點,並將它移動到表頭,成為新的表頭節點。
 *
 * T = O(1)
 */
void listRotate(list *list) {
    
    //表尾結點
    listNode *tail = list->tail;

    //如果鏈表中只有一個元素,那麼表頭就是表尾,可以直接返回
    if (listLength(list) <= 1) return;

    // 重新設置表尾節點
    list->tail = tail->prev;
    list->tail->next = NULL;

    // 插入到表頭
    list->head->prev = tail;
    tail->prev = NULL;
    tail->next = list->head;
    list->head = tail;
}

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

【其他文章推薦】

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

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

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

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

※回頭車貨運收費標準

聚甘新

跨雲廠商部署 k3s 集群

原文鏈接:https://fuckcloudnative.io/posts/deploy-k3s-cross-public-cloud/

最近一兩年各大雲服務商都出了各種福利活動,很多小夥伴薅了一波又一波羊毛,比如騰訊雲 1C2G 95/年 真香系列,華為雲和阿里雲也都有類似的活動,薅個兩三台就能搭建一個 Kubernetes 集群。但是跨雲服務商搭建 Kubernetes 集群並不像我們想象中的那麼容易,首先就是原生的 Kubernetes 組件本身對資源的消耗量很大,而雲服務器的資源非常有限,經不起這麼大傢伙的折騰,對此我們可以選擇使用輕量級 Kubernetes 發行版:k3s

k3s 將安裝 Kubernetes 所需的一切打包進僅有 60MB 大小的二進制文件中,並且完全實現了 Kubernetes API。為了減少運行 Kubernetes 所需的內存,k3s 刪除了很多不必要的驅動程序,並用附加組件對其進行替換。由於它只需要極低的資源就可以運行,因此它能夠在任何 512MB 內存以上的設備上運行集群。

其實 k3s 的安裝非常簡單,分分鐘就能搞定,但對於公有雲來說,還是有很多坑的,比如內網不通、公網 IP 不在服務器上該咋辦?本文就為你一一解決這些難題,讓天下的雲羊毛都成為 k3s 的後宮!

1. 下載二進制文件

首先來解決第一個難題:k3s 二進制文件的下載。國內下載 GitHub 速度基本都是以幾個 kb 為單位,不忍直視,如果下載內容都是代碼,有很多辦法可以解決,比如通過碼雲中轉啊、直接通過 CDN 下載啊,什麼?你不知道可以通過 CDN 下載?好吧沒關係,現在我告訴你了:https://cdn.con.sh/。

但是上面的 CDN 並不能下載 release 里的內容,要想下載 release 里的內容,可以使用這個網站:https://toolwa.com/github/。打開網站,輸入 release 裏面的文件下載鏈接,點擊起飛即可加速下載。

當然,如果你會魔法上網的話,上面的所有花里胡哨的方法都可以無視,直接下載就好啦(本文選擇使用版本 v1.17.6+k3s1):

$ wget https://github.com/rancher/k3s/releases/download/v1.17.6+k3s1/k3s -O /usr/local/bin/k3s
$ chmod +x /usr/local/bin/k3s

需要在所有節點中下載上述二進制文件。

2. 升級內核

k3s 的默認網絡插件是 flannel,默認模式是 vxlan 模式,建議使用 wireguard 模式,原因不解釋了,不知道 wireguard 是啥的自己去搜一下。

wireguard 對內核的要求比較高,而 CentOS 7.x 的默認內核是不滿足要求的,需要升級內核(如果你的操作系統是 CentOS 7.x 的話)。步驟如下:

① 載入公鑰

$ rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org

② 升級安裝 elrepo

$ rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm

③ 載入 elrepo-kernel 元數據

$ yum --disablerepo=\* --enablerepo=elrepo-kernel repolist

④ 安裝最新版本的內核

$ yum --disablerepo=\* --enablerepo=elrepo-kernel install  kernel-ml.x86_64  -y

⑤ 刪除舊版本工具包

$ yum remove kernel-tools-libs.x86_64 kernel-tools.x86_64  -y

⑥ 安裝新版本工具包

$ yum --disablerepo=\* --enablerepo=elrepo-kernel install kernel-ml-tools kernel-ml-devel kernel-ml-headers -y

⑦ 查看內核插入順序

$ grep "^menuentry" /boot/grub2/grub.cfg | cut -d "'" -f2

CentOS Linux (3.10.0-1127.10.1.el7.x86_64) 7 (Core)
CentOS Linux (5.7.2-1.el7.elrepo.x86_64) 7 (Core)
CentOS Linux (0-rescue-96820b9851c24560b5f942f2496b9aeb) 7 (Core)

默認新內核是從頭插入,默認啟動順序也是從 0 開始。

⑧ 查看當前實際啟動順序

$ grub2-editenv list

saved_entry=CentOS Linux (3.10.0-1127.10.1.el7.x86_64) 7 (Core)

⑨ 設置默認啟動

$ grub2-set-default 'CentOS Linux (5.7.2-1.el7.elrepo.x86_64) 7 (Core)'

最後重啟檢查:

$ reboot
$ uname -r

注意:集群中的所有節點都需要升級內核。

3. 安裝 wireguard

內核升級了之後,就可以安裝 wireguard 了,也很簡單,步驟如下:

$ yum install epel-release https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm
$ yum install yum-plugin-elrepo
$ yum install kmod-wireguard wireguard-tools

注意:集群中的所有節點都需要安裝。

4. 部署控制平面

下面就可以在控制節點上啟動控制平面的組件了,這裏我們選擇手動部署,這樣比較方便修改參數。先創建一個 Service Unit 文件:

$ cat > /etc/systemd/system/k3s.service <<EOF
[Unit]
Description=Lightweight Kubernetes
Documentation=https://k3s.io
Wants=network-online.target

[Install]
WantedBy=multi-user.target

[Service]
Type=notify
EnvironmentFile=/etc/systemd/system/k3s.service.env
KillMode=process
Delegate=yes
# Having non-zero Limit*s causes performance problems due to accounting overhead
# in the kernel. We recommend using cgroups to do container-local accounting.
LimitNOFILE=1048576
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
TimeoutStartSec=0
Restart=always
RestartSec=5s
ExecStartPre=-/sbin/modprobe br_netfilter
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/local/bin/k3s \
    server \
    --tls-san <public_ip> \
    --node-ip <public_ip> \
    --node-external-ip <public_ip> \
    --no-deploy servicelb \
    --flannel-backend wireguard \
    --kube-proxy-arg "proxy-mode=ipvs" "masquerade-all=true" \
    --kube-proxy-arg "metrics-bind-address=0.0.0.0"
EOF
  • <public_ip> 替換成控制節點的公網 IP。
  • flannel 使用 wireguard 協議來跨主機通信。
  • kube-proxy 使用 ipvs 模式。

啟動 k3s 控制平面並設置開機自啟:

$ systemctl enable k3s --now

查看集群組件健康狀況:

$ kubectl get cs

NAME                 STATUS    MESSAGE   ERROR
scheduler            Healthy   ok
controller-manager   Healthy   ok

這裏的輸出沒有 etcd,因為 k3s 的默認數據存儲是 Sqlite,對於小型數據庫十分友好。Kubernetes 控制平面中發生的更改更多是與頻繁更新部署、調度 Pod 等有關,因此對於幾個節點的小型集群而言,數據庫不會造成太大負載,能省下不少資源,真香!

5. 加入計算節點

部署好控制平面之後,就可以加入計算節點了。首先在計算節點上創建 Service Unit 文件:

$ cat > /etc/systemd/system/k3s-agent.service <<EOF
[Unit]
Description=Lightweight Kubernetes
Documentation=https://k3s.io
Wants=network-online.target

[Install]
WantedBy=multi-user.target

[Service]
Type=exec
EnvironmentFile=/etc/systemd/system/k3s-agent.service.env
KillMode=process
Delegate=yes
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
TimeoutStartSec=0
Restart=always
RestartSec=5s
ExecStartPre=-/sbin/modprobe br_netfilter
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/local/bin/k3s agent \
    --node-external-ip <public_ip> \
    --node-ip <public_ip> \
    --kube-proxy-arg "proxy-mode=ipvs" "masquerade-all=true" \
    --kube-proxy-arg "metrics-bind-address=0.0.0.0"
EOF

環境變量文件 /etc/systemd/system/k3s-agent.service.env 中需要加入兩個環境變量:

  • K3S_URL : API Server 的 URL,一般格式為:https://<master_ip>:6443。其中 <master_ip> 是控制節點的公網 IP。
  • K3S_TOKEN : 加入集群所需的 token,可以在控制節點上查看 /var/lib/rancher/k3s/server/node-token 文件。

/etc/systemd/system/k3s-agent.service.env 內容如下:

K3S_URL=https://<master_ip>:6443
K3S_TOKEN=xxxxxxxx

啟動 k3s-agent 並設置開啟自啟:

$ systemctl enable k3s-agent --now

查看節點狀態:

$ kubectl get node

NAME         STATUS   ROLES    AGE     VERSION
blog-k3s01   Ready    master   3d6h    v1.17.6+k3s1
blog-k3s02   Ready    <none>   3d3h    v1.17.6+k3s1

6. 內網不互通的解決辦法

這裡會遇到一個問題,不同節點的 flannel 使用的是內網 IP 來進行通信,而我們的雲服務器是內網不互通的,而且公網 IP 也不在服務器上。可以看一下 node 的 annotations

$ kubectl get node blog-k3s02 -o yaml

apiVersion: v1
kind: Node
metadata:
  annotations:
    flannel.alpha.coreos.com/backend-data: '"xxxxx"'
    flannel.alpha.coreos.com/backend-type: extension
    flannel.alpha.coreos.com/kube-subnet-manager: "true"
    flannel.alpha.coreos.com/public-ip: 192.168.0.11
    ...

可以看到 flannel 給節點打的註解中的節點 IP 是內網 IP。要想讓 flannel 使用公網 IP 進行通信,需要額外添加一個註解 public-ip-overwrite,然後 flannel 會基於這個 IP 配置網絡。按照官方文檔的說法,如果你的 node 設置了 ExternalIP,flannel 會自動給 node 添加一個註解 public-ip-overwrite,但我不知道該如何給 node 設置 ExternalIP,乾脆就直接手動加註解吧:

$ kubectl annotate nodes <master> flannel.alpha.coreos.com/public-ip-overwrite=<master_pub_ip>
$ kubectl annotate nodes <node> flannel.alpha.coreos.com/public-ip-overwrite=<node_pub_ip>

加了註解之後,flannel 的 public-ip 就會被修改為公網 IP。然後在各個節點上重啟各自的 k3s 服務,查看 wireguard 連接狀況:

$ wg show flannel.1

interface: flannel.1
  public key: ONDgJCwxxxxxxxJvdWpoOKTxQA=
  private key: (hidden)
  listening port: 51820
  
peer: MKKaanTxxxxxxxV8VpcHq4CSRISshw=
  endpoint: <pub_ip>:51820
  allowed ips: 10.42.4.0/24
  latest handshake: 26 seconds ago
  transfer: 133.17 KiB received, 387.44 KiB sent
  persistent keepalive: every 25 seconds

可以看到通信端點被改成了公網 IP,大功告成!

7. metrics-server 問題解決

還有一個問題就是 metrics-server 無法獲取 cpu、內存等利用率核心指標。需要修改 metrics-server 的 manifests,使用以下命令在線編輯 metrics-server 的 manifests:

$ kubectl -n kube-system edit deploy metrics-server

然後加入以下執行參數后保存退出:

      -command:
        - /metrics-server
        - --kubelet-preferred-address-types=ExternalIP
        - --kubelet-insecure-tls

這樣就可以讓 metrics-server 使用公網 IP 來和 node 通信了。修改成功后就可以看到核心指標了:

$ kubectl top nodes
NAME         CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
blog-k3s01   193m         9%     886Mi           22%
blog-k3s02   41m          2%     1292Mi          32%

$ kubectl top pod -n kube-system
NAME                                      CPU(cores)   MEMORY(bytes)
coredns-848b6cc76f-zq576                  8m           14Mi
local-path-provisioner-58fb86bdfd-bzdfl   2m           9Mi
metrics-server-bdfc79c97-djmzk            1m           12Mi

到這裏跨雲服務商部署 k3s 基本上就大功告成了,下一篇文章將會教你如何打通家裡到雲上 k3s 的網絡,讓你家中所有設備都可以直接訪問 Pod IP、svc IP,甚至可以直接訪問 svc 域名,敬請期待。

Kubernetes 1.18.2 1.17.5 1.16.9 1.15.12離線安裝包發布地址http://store.lameleg.com ,歡迎體驗。 使用了最新的sealos v3.3.6版本。 作了主機名解析配置優化,lvscare 掛載/lib/module解決開機啟動ipvs加載問題, 修復lvscare社區netlink與3.10內核不兼容問題,sealos生成百年證書等特性。更多特性 https://github.com/fanux/sealos 。歡迎掃描下方的二維碼加入釘釘群 ,釘釘群已經集成sealos的機器人實時可以看到sealos的動態。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

聚甘新

Flutter學習筆記(35)–通知Notification,Flutter學習筆記(35)–通知Notification

如需轉載,請註明出處:Flutter學習筆記(35)–通知Notification

通知的NotificationListener和我們之前寫的事件的Listener一樣,都是功能性的組件,而且也都是從子節點順着widget樹向上冒泡,不同的是,事件的Listener不可以被終止,但是通知的NotificationListener是可以被終止的。

是否終止根據NotificationListener的返回值來決定。

說一下我個人的理解:

通知Notification的發送是通過disPatch進行分發的,就好像Android裏面的事件分發,當NotificationListener監聽到了通知事件,這時候會走到其onNotification回調中,根據回調中的返回值類型(true還是false)來決定是否還繼續向父親節點發送通知。

返回true就是繼續分發,返回false就是終止分發,返回false就意味着上層節點的NotificationListener就不會接收到通知事件了。

舉個例子就是:

兩層NotificationListener嵌套,子節點的NotificationListener返回true,那麼父親節點的NotificationListener可以接收到通知事件,反之如果返回false,那麼父親節點的NotificationListener就不會接收到通知事件了。

下面看一下demo示例:

demo就是簡單的發送通知,監聽到通知事件后改變text的內容。

1.創建一個事件通知類,要繼承Notification,它其實就是一個數據載體,在裏面定義通知數據的類型和內容。

import 'package:flutter/material.dart';

class MyNotification extends Notification{
  String notificationStr;

  MyNotification(this.notificationStr);
}

2.NotificationListener的使用和通知事件的分發

import 'package:flutter/material.dart';
import 'package:study_app/util/MyNotification.dart';

class NotificationDemo extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _NotificationDemoState();
  }
}

class _NotificationDemoState extends State {
  String _notificationData = 'default_data';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'NotificationDemo',
      home: new Scaffold(
          appBar: AppBar(
            title: Text('NotificationDemo'),
          ),
          body: NotificationListener<MyNotification>(
            onNotification: (notification) {
              setState(() {
                _notificationData = notification.notificationStr;
              });
              return true;
            },
            child: Column(
              children: <Widget>[
                Text(_notificationData),
                Builder(
                  builder: (context) {
                    return Container(
                      width: double.infinity,
                      child: RaisedButton(
                          child: Text('發送通知'),
                          onPressed: () {
                            MyNotification('notification_data')
                                .dispatch(context);
                          }),
                    );
                  },
                )
              ],
            ),
          )),
    );
  }
}

在看書的時候,作者強調了一種錯誤的寫法,如下圖註釋的部分:

原因是通知在分發的時候,需要一個context參數,這個參數指的是Notification監聽的子widget的context,如果按照註釋部分的寫法的話,context是根widget的,這樣會導致監聽不到子widget了。

所以需要我們通過Builder構建出我們子widget的context,這裏需要特別注意一下。

最後看一下效果截圖:

   

以上!有任何疑問歡迎留言!

 

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

聚甘新

Jmeter系列(21)- 詳解 HTTP Request

如果你想從頭學習Jmeter,可以看看這個系列的文章哦

https://www.cnblogs.com/poloyy/category/1746599.html

 

HTTP Request 介紹

用來發送 HTTP、HTTPS 協議請求

 

HTTP Request 界面

字段名 作用
名稱 不多介紹啦,建議自定義一個識別度高的名稱
註釋 對於測試沒有任何影響,僅記錄作用
協議

http或https,大小寫不敏感

默認:http

服務器名稱或IP
  • 服務器 host 或者 ip,不包括協議
  • 比如:www.baidu.com、192.168.196.128
端口號 目標服務器的端口號,默認:80
方法 發送 http 請求的方法
路徑
  • 目標請求的 URL 路徑
  • 不包括協議、host、ip、端口
內容編碼 請求的編碼方式,默認:iso8859
自動重定向
  • 發出的請求的響應碼是3**,會自動跳轉到新目標頁面
  • 只記錄最終頁面的返回結果
跟隨重定向
  • 和自動重定向唯一不同的是:
  • 會記錄重定向過程中的的所有請求的響應結果
使用 KeepAlive
  • jmeter 和目標服務器之間使用 Keep-Alive 方式進行 HTTP 通信
  • 真正做性能測試強烈建議不勾選
對POST使用multipart/form-data post 請求需要上傳文件時勾選
與瀏覽器兼容的頭
  • 當勾選 multipart/form-data 時,勾選此項
  • http請求頭中的 Content-Type 和Content-Transfer-Encoding 被忽略
  • 而只發送 Content-Disposition 部分

 

Parameters 講解

字段 描述
Name 參數名
Value 參數值
URL Encode?
  • 是否要 URL 編碼?
  • 重點:如果參數值包含了中文、特殊字符(非数字字母以外),最好勾上,當然全都勾上最穩妥
Content-Type
  • 參數值的資源類型
  • 默認:text/plain
Include Equals?
  • 當你的參數值為空的時候,可以選擇不包含=,默認勾選
  • 如果參數值不為空,則不可以取消勾選

 

什麼是 URL 編碼

  • URL 編碼解碼又叫百分號編碼,是統一資源定位(URL)的編碼方式
  • URL 地址(常說網址)規定了数字,字母可以直接使用,另外一批作為特殊用戶字符也可以直接用( / , : @  等),剩下的其它所有字符必須通過 %xx 編碼處理
  • 編碼方法很簡單,在該字符ascii碼的的16進制字符前面加%,如空格字符,ascii碼是32,對應16進制是20,那麼 urlencode 編碼結果是 %20 

 

URL 編碼的栗子

直接在網上搜在線 URL 編解碼

 

include equals 的栗子

參數值為空,且勾選 Include equals

 

參數值為空,但不勾選  Include equals

 

其實說的就是等於號而已,一般也不會傳空值,即使傳了也會帶上=

 

Body Data 講解

  • 沒啥好說的,傳 json 字符串就行了,注意下格式,後面再講具體栗子
  • 不過倒有個重點:如果 Parameters 有參數列表的話,是無法切換到 Body Data 的哦

 

Files Upload 講解

字段 描述
File Path 文件的本地路徑
Parameter Name 參數名
MIME Type 資源媒體類型

 

常見資源媒體類型

類型 文件後綴 格式
超文本標記語言文本 .html text/html
普通文本 .txt text/plain
XML 文件 .xml text/xml
PNG 圖片 .png image/png
GIF .gif image/gif
JPEG 圖片 .jpeg、jpg  image/jpeg

 

類型 文件後綴 格式
表單中進行文件上傳   multipart/form-data
表單默認提交數據的格式   application/x-www-form-urlencoded
XML 數據格式   application/xml
JSON 數據格式   application/json
PDF 文件 .pdf application/pdf
RTF 文本 .rtf application/rtf
GZIP 文件  .gz application/x-gzip
TAR 文件 .tar application/x-tar
AVI 文件 .avi  video/x-msvideo
MPEG 文件 .mpg、.mpeg video/mpeg

 

不同的content-type在jmeter中如何輸入參數

前提

因為是需要真實接口進行測試的,這裏提供兩種方案

  • 自己用 Flask 框架開發了本地的接口進行測試, 如果有需要的同學進群領取哦:870155189
  • 或者進入 http://open.yesapi.cn/?r=user/registration&from=wx_837493986,直接註冊個賬號,弄個免費會員,有在線免費的接口提供測試哦

 

application/x-www-form-urlencoded 的栗子

備註:也是表單提交最常見的栗子

 

Parameters 方式傳參

 

總結

  • 最終表單的參數列表會拼接到 URL 中,所以如果包含了中文、特殊字符就要勾選編碼?
  • 這裏不可以通過 Body Data 傳遞參數哦,會無法識別到參數,已實踐過(即使加了 HTTP請求頭也不行),乖乖用 Parameters 的方式傳參

 

content-type:application/json 的栗子

Body Data 方式傳參

 

添加 HTTP請求頭

 

請求體

 

請求頭

 

結論

重點就是添加 HTTP請求頭,指明 Content-type 是 json 格式

 

content-type:multipart/form-data

重點:用於 post 請求,需要文件上傳的場景;記住不是 get 請求

 

請求參數列表

如果選了 get 方法的話,文件參數是不會生效哦

 

文件參數

 

請求體

 

重點

  • 如果添加了 HTTP請求頭,請務必不要添加 content-type : multipart/form-data 
  • 如果加了的話:那麼所有的請求參數都會被當成文件以二進制形式傳輸,我們 parameters 里的文本格式參數就不會被識別,接口會提示參數為空

 

HTTP Request Advance

說實話我還沒用過這部分的內容,不過還是得了解下每個配置項是什麼意思哦

 

Client implemention 和 Timeouts

字段 描述
implementation 發送http請求的方式,可選項為 java、HttpClient4(默認)
Connect 連接超時時間,單位毫秒
Respones 響應等待超時時間,單位毫秒

 

Embedded Resources from HTML Files

  • 從HTML文件獲取所有內含的資源
  • jmeter 在發出的  HTTP請求獲得響應的 HTML文件內容后,對 HTML進行解析並獲取HTML中包含的所有資源(圖片、flash等)
字段 描述
Retrieve All Embedded Resources 發送http請求的方式,可選項為 java、HttpClient4(默認)
Parallel downloadds. Number

是否使用自設資源處。啟用后可以設置資源池大小,默認為6

URLs must match URL 匹配過濾,填寫此項則只會下載與此內容項匹配的 url 的資源

 

Source address

只用於 HTTP協議且 implemention = HttpClient4 時

字段 描述
IP/Hostname IP /主機名以使用特定的IP地址或(本地)主機名
Device 選擇設備以選擇該接口的第一個可用地址,該設備可以是IPv4或IPv6
Device IPv4 選擇IPv4設備來選擇名稱設備的IPv4地址(如eth0, lo, em0)
Device IPv6 選擇IPv6設備來選擇名稱設備的IPv6地址(如eth0, lo, em0)

 

Proxy Server

代理服務器

字段 描述
Server Name or IP 代理服務器的名稱或者IP地址
Port Number 代理服務器的端口號
Username 代理服務器的用戶名
Password 代理服務器的密碼

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

【其他文章推薦】

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

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

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

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

※回頭車貨運收費標準

聚甘新

測試人員遇到Android APP崩潰和無響應手足無措?,基於Python的Appium環境搭建合集

這2天,在測APP兼容性時,遇到APP奔潰閃退的情況。將問題反饋給開發后,開發自己調試后,沒有復現。由於又是遠程,base地不在一塊,我總不能把手機寄過去吧,那也太費事了。

所以就想到,提供明確的報錯日誌,讓開發定位問題,豈不是就很方便了,也解決了遠程的問題。

那如何抓取到Crash日誌呢,我又沒開發調試工具,也不可能在短時間內搭建一套開發環境。尋思答案后,最終得到了完美解決,且聽細細道來。

了解Crash

我們先來簡單了解下Crash:Crash,就是崩潰。anr(Application Not Responding — 程序無響應)是Crash的一種。程序正常運行中,可能會出現未捕獲到的異常,這就會造成崩潰。

常見Crash異常

NullPointerException  空指針

ClassCastException  類型轉換異常

IndexOutOfBoundsException  下標越界異常

ActivityNotFoundException Activity  未找到異常

IllegalStateException  非法狀態異常

ArrayIndexOutOfBoundsException  數組越界異常

SecurityException  安全異常

NoSuchMethodException  方法未找到異常

SQLException  操作數據庫異常

抓取奔潰和無響應日誌

對於開發人員來說,抓取日誌是很方便的,但對於測試人員來說,就不是太方便了。大多都是直接dos窗口下執行adb命令來抓取日誌,而每次都敲命令也是很麻煩。

所以通過adb程序與bat命令組合使用來抓取日誌,就要方便很多了,短短几秒鐘,可以輕鬆搞定日誌的抓取,期不期待。

環境準備

安裝JDK和ADB,這個安裝很簡單,可參見以前的博文,基於Python的Appium環境搭建合集。

bat文件製作

環境準備好后,就來寫bat文件了。

捕獲Crash異常的bat文件命令

命令參考如下,製作成logcat.bat文件,logcat.bat文件可以放置任意位置。注意:如果adb沒有配置到環境變量中,則需要將logcat.bat文件放到adb對應文件夾中。

@ECHO OFF
for /f "tokens=2 delims==" %%a in ('wmic OS Get localdatetime /value') do set "dt=%%a"

SET timeStamp=%dt:~0,4%-%dt:~4,2%-%dt:~6,2%_%dt:~8,2%-%dt:~10,2%-%dt:~12,2%
SET mutID=_mut

@ECHO ON
adb logcat -v time > .\"%mutID%_%timeStamp%_logcat.log"

pause

上述命令實現原理:該工具的原理是bat文件調用adb工具,將手機運行日誌拉到本地,並將實時日誌也記錄到本地。

當手機需要重現Crash、或者某一段時間內已經發生過Crash,點擊我們製作的bat文件,logcat文件中的命令會將手機的logcat日誌拉下來並實時記錄,直到你關閉cmd窗口。

然後在拉下來的txt中尋找FATAL關鍵字,附近上下文即為Crash日誌。

捕獲ANR異常的bat文件命令

anr:全稱為Application Not Responding,意思為程序無響應。

命令參考如下,製作成anr.bat文件,anr.bat文件可以放置任意位置。注意:如果adb沒有配置到環境變量中,則需要將anr.bat文件放到adb對應文件夾中。

@ECHO OFF
for /f "tokens=2 delims==" %%a in ('wmic OS Get localdatetime /value') do set "dt=%%a"
SET timeStamp=%dt:~0,4%-%dt:~4,2%-%dt:~6,2%_%dt:~8,2%-%dt:~10,2%-%dt:~12,2%
SET mutID=_mut
@ECHO ON
adb pull data/anr/traces.txt traces_%timeStamp%.txt

實現原理與捕獲Crash異常是一樣的,只是該命令是針對發生anr的情況。

具體實踐

捕獲Crash異常具體用法:

①將android手機連接電腦,開啟開發者模式並允許usb調試;

②運行logcat.bat文件

③如果手機程序已經發生過crash,10秒后關閉cmd窗口;如果是想重現crash,則在手機端重現后即可關閉cmd窗口;

④在logcat.bat的同級目錄下會生成一份log文件,從文件中搜查FATAL關鍵字,便可找到崩潰代碼。

查看報錯日誌,報錯如下所示:

如上所示截圖,就是測試過程中,發生奔潰的日誌了,將日誌貼在bug里,既方便開發排查問題,又節約協作時間。

使用優點

使用bat文件捕獲日誌,有如下幾個有點:手機無需root;無需開發環境支持;方便保存、查找日誌;操作簡單。

以上就是捕獲報錯日誌的操作步驟了,希望對有需要的博友有所幫助。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

聚甘新