.Net Core Configuration Etcd數據源

前言

    .Net Core為我們提供了一套強大的Configuration配置系統,使用簡單擴展性強。通過這套配置系統我們可以將Json、Xml、Ini等數據源加載到程序中,也可以自己擴展其他形式的存儲源。今天我們要做的就是通過自定義的方式為其擴展Etcd數據源操作。

何為Etdc

    在使用etcd之前我們先介紹一下Etcd,我相信很多同學都早有耳聞。Etcd是一款高可用、強一致的分佈式KV存儲系統,它內部採用raft協議作為一致性算法,本身也是基於GO語言開發的,最新版本為v3.4.9,具體版本下載地址可參閱官方GitHub地址。相信了解過K8S的同學對這個肯定不陌生,它是K8S的數據管理系統。官方地址為https://etcd.io/。
    在此之前,我相信大家已經了解過很多存儲系統了,Etcd到底能實現了什麼功能呢?其一用於配置中心和服務發現,再者也可以實現分佈式鎖和消息系統。它本身就是基於目錄型存儲,並且內部有一套強大的Watch機制可以監聽針對節點和數據的操作變化,每次對節點的事務操作都會有對於的版本信息。

Etcd VS Zookeeper

通過上面的介紹是不是感覺和Zookeeper有點類似呢,網上有很多很多關於Etcd和Zookeeper的對比文章,大致如下可以得到以下結論

功能 Etcd Zookeeper
分佈式鎖 有(採用節點版本號信息) 有(採用臨時節點和順序臨時節點)
watcher
一致性算法 raft zab
選舉
元數據(metadata)存儲
應用場景 Etcd Zookeeper
發布與訂閱(配置中心) 有(不限次Watch) 有(一次性觸發的,需要重新註冊Watch)
軟負載均衡
命名服務(Naming Service)
服務發現 有(基於租約節點) 有(基於臨時節點)
分佈式通知/協調
集群管理與Master選舉
分佈式鎖
分佈式隊列

說白了就是Zookeeper能幹的活,Etcd也能幹。那既然有了Zookeeper為啥還要選擇Etcd,主要基於以下原因

  • 更輕量級(Etcd基於GO語言開發,Zookeeper基於Java開發)、更易用(開箱即用)
  • 高負載下的穩定讀寫
  • 數據模型的多版本併發控制
  • 穩定的watcher功能,通知訂閱者監聽值的變化(Zookeeper基於數據的監聽是一次性的,每次監聽完成還需重新註冊)
  • 客戶端協議使用GRPC協議,支持語言更廣泛

一言以蔽之,就是不僅實現了Zookeeper的功能,還在很多方面吊打Zookeeper,這麼強大的東西忍不住都要試一試。

在.Net Core中使用Etcd

    在Nuget上可以搜索到很多.Net Core的Etcd客戶端驅動程序,我使用了下載量最多的一個名字叫dotnet-etcd的驅動包,順便找到了它在GayHub上,不好意思手滑打錯了GitHub上的項目地址,大概學習了一下基本的使用方式。其實我們結合Configuration配置這一塊,只需要兩個功能。一個是Get獲取數據,另一個是Watch節點變化(更新數據會用到)。個人認為,前期有目有邊界的學習還是非常重要的。

Configuration擴展Etcd

前面我們講到過自定義擴展Configuration是非常方便的,相信了解過Configuration相關源碼的小夥伴們已經非常熟悉了,大致總結一下分為三步:

  • 編寫IConfigurationBuilder擴展方法,我們這裏叫AddEtcd
  • 編寫實現IConfigurationSource的配置源信息類,我們這裏叫EtcdConfigurationSource
  • 編寫繼承自ConfigurationProvider的ConfigurationSource的配置數據提供類,我們這裏叫EtcdConfigurationProvider

因為微軟已經給我們提供了一部分便利,所以編寫起來還是非常的簡單的。好了,接下來我們開始編寫具體的實現代碼,重點的地方我會在代碼中註釋說明。

首先是定義擴展類EtcdConfigurationExtensions,這個類是針對IConfigurationBuilder的擴展方法,實現如下

public static class EtcdConfigurationExtensions
{
    /// <summary>
    /// AddEtcd擴展方法
    /// </summary>
    /// <param name="serverAddress">Etcd地址</param>
    /// <param name="path">讀取路徑</param>
    /// <returns></returns>
    public static IConfigurationBuilder AddEtcd(this IConfigurationBuilder builder, string serverAddress,string path)
    {
        return AddEtcd(builder, serverAddress:serverAddress, path: path,reloadOnChange: false);
    }

    /// <summary>
    /// AddEtcd擴展方法
    /// </summary>
    /// <param name="serverAddress">Etcd地址</param>
    /// <param name="path">讀取路徑</param>
    /// <param name="reloadOnChange">如果數據發送改變是否刷新</param>
    /// <returns></returns>
    public static IConfigurationBuilder AddEtcd(this IConfigurationBuilder builder, string serverAddress, string path, bool reloadOnChange)
    {
        return AddEtcd(builder,options => {
            options.Address = serverAddress;
            options.Path = path;
            options.ReloadOnChange = reloadOnChange;
        });
    }

    public static IConfigurationBuilder AddEtcd(this IConfigurationBuilder builder, Action<EtcdOptions> options)
    {
        EtcdOptions etcdOptions = new EtcdOptions();
        options.Invoke(etcdOptions);
        return builder.Add(new EtcdConfigurationSource { EtcdOptions = etcdOptions });
    }
}

這裏我還定義了一個EtcdOptions的POCO,用於承載讀取Etcd的配置屬性

public class EtcdOptions
{
    /// <summary>
    /// Etcd地址
    /// </summary>
    public string Address { get; set; }

    /// <summary>
    /// Etcd訪問用戶名
    /// </summary>
    public string UserName { get; set; }

    /// <summary>
    /// Etcd訪問密碼
    /// </summary>
    public string PassWord { get; set; }

    /// <summary>
    /// Etcd讀取路徑
    /// </summary>
    public string Path { get; set; }

    /// <summary>
    /// 數據變更是否刷新讀取
    /// </summary>
    public bool ReloadOnChange { get; set; }
}

接下來我們定義EtcdConfigurationSource,這個類非常簡單就是返回一個配置提供對象

public class EtcdConfigurationSource : IConfigurationSource
{
    public EtcdOptions EtcdOptions { get; set; }

    public IConfigurationProvider Build(IConfigurationBuilder builder)
    {
        return new EtcdConfigurationProvider(EtcdOptions);
    }
}

真正的讀取操作都在EtcdConfigurationProvider里

public class EtcdConfigurationProvider : ConfigurationProvider
{
    private readonly string _path;
    private readonly bool _reloadOnChange;
    private readonly EtcdClient _etcdClient;

    public EtcdConfigurationProvider(EtcdOptions options)
    {
        //實例化EtcdClient
        _etcdClient = new EtcdClient(options.Address,username: options.UserName,password: options.PassWord);
        _path = options.Path;
        _reloadOnChange = options.ReloadOnChange;
    }

    /// <summary>
    /// 重寫加載方法
    /// </summary>
    public override void Load()
    {
        //讀取數據
        LoadData();
        //數據發生變化是否重新加載
        if (_reloadOnChange)
        {
            ReloadData();
        }
    }

    private void LoadData()
    {
        //讀取Etcd里的數據
        string result = _etcdClient.GetValAsync(_path).GetAwaiter().GetResult();
        if (string.IsNullOrEmpty(result))
        {
            return;
        }
        //轉換一下數據結構,這裏我使用的是json格式
        //讀取的數據只要賦值到Data屬性上即可,IConfiguration真正讀取的數據就是存儲到Data的字典數據
        Data = ConvertData(result);
    }

    private IDictionary<string,string> ConvertData(string result)
    {
        byte[] array = Encoding.UTF8.GetBytes(result);
        MemoryStream stream = new MemoryStream(array);
        //JsonConfigurationFileParser是將json數據轉換為Configuration可讀取的結構(複製JsonConfiguration類庫里的)
        return JsonConfigurationFileParser.Parse(stream);
    }

    private void ReloadData()
    {
        WatchRequest request = new WatchRequest()
        {
            CreateRequest = new WatchCreateRequest()
            {
                //需要轉換一個格式,因為etcd v3版本的接口都包含在grpc的定義中
                Key = ByteString.CopyFromUtf8(_path)
            }
        };
        //監聽Etcd節點變化,獲取變更數據,更新配置
        _etcdClient.Watch(request, rsp =>
        {
            if (rsp.Events.Any())
            {
                var @event = rsp.Events[0];
                //需要轉換一個格式,因為etcd v3版本的接口都包含在grpc的定義中
                Data = ConvertData(@event.Kv.Value.ToStringUtf8());
                //需要調用ConfigurationProvider的OnReload方法觸發ConfigurationReloadToken通知
                //這樣才能對使用Configuration的類發送數據變更通知
                //比如IOptionsMonitor就是通過ConfigurationReloadToken通知變更數據的
                OnReload();
            }
        });
    }
}

使用方式如下

builder.AddEtcd("http://127.0.0.1:2379", "service/mydemo", true);

順便給大家推薦一個Etcd可視化管理工具ETCD Manager,以便更好的學習Etcd。
到這裏,基本上就結束了,是不是非常簡單。主要還是Configuration本身的設計思路比較清晰,所以實現起來也不費勁。

總結

    以上代碼都已經上傳了我的GitHub,該倉庫還擴展了其他數據源的讀取比如Consul、Properties文件、Yaml文件的讀取,實現思路也都大致相似,有興趣的同學可以自行查閱。由於主要是講解實現思路,可能許多細節並未做處理還望見諒。如果有疑問或者更好的建議,歡迎評論區交流指導。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

聚甘新

美研究:種植水稻產生溫室氣體被低估 包括中國

摘錄自2018年9月12日新華網報導

美國《國家科學院學報》前(10)日刊載的一項研究顯示,如把一氧化二氮排放考慮在內,水稻種植對全球變暖的影響可能比此前估計水平高出近一倍。

然而,包括中國、美國、印度在內的多數水稻生産國,目前都沒有將與水稻種植有關的一氧化二氮排放計入向聯合國提交的國家溫室氣體排放清單中。

美國環境保護基金組織的研究稱,近年來,水資源短缺導致越來越多的地區採用間歇性淹水法種植水稻,但在分析稻田對氣候的影響時,間歇性淹水稻田釋放的高水平一氧化二氮此前並未被計算在內,導致水稻種植對全球變暖的影響被嚴重低估。

一氧化二氮,又稱氧化亞氮,是重要的長效溫室氣體。在20年到100年時段中,其捕獲大氣熱量的能力是甲烷的數倍。最新研究顯示,間歇性淹水稻田釋放的一氧化二氮水平高達持續性淹水稻田的30到45倍,後者釋放的主要溫室氣體是甲烷。

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

【其他文章推薦】

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

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

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

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

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

聚甘新

加州簽了! 2045年100%乾淨能源目標入法

環境資訊中心外電;姜唯 翻譯;林大利 審校;稿源:ENS

加州州長布朗10日簽署參議院100號法案(SB 100),將「100%乾淨電力」正式設立為法定目標。

繼提早四年達成「2020年減排13%、經濟成長26%」目標後,「SB 100」將使加州的再生能源比例標準更加積極,2025年必須達到50%電力由再生能源供應,2030年要達到60%,2045年實現零碳電網。

加州州長布朗簽署參議院100號法案(SB 100)。圖片來源:Office of the Governor

州長訂定碳中和目標  減碳不涉核電

為了確保全球暖化對策不只侷限於電業(佔加州溫室氣體排放量的16%),布朗還發布了一項行政命令,指示加州要在2045年實現碳中和,之後則要實現淨負排碳。

目前全球有20多個國家和40多個地方政府宣布,最晚要在本世紀中實現碳中和。

為了實現目標,加州將持續減少碳污染,同時增加森林、土壤和其他自然地景的碳儲存和空氣品質及公共衛生改善計畫,尤其是在該州受影響最嚴重的社區。

加州也將陸續實施一系列因應暖化的長期行動。上週,布朗簽署了另一項法案,禁止加州沿海進行新的聯邦海上石油鑽探活動,並宣布加州反對聯邦政府擴大加州公共土地上石油鑽探的計畫。1968年至今,加州都沒有開放沿海地區的新石油和天然氣租賃。

雖然加州政府定義的乾淨能源有三種,州政府在其網站上的指出,加州正在逐步淘汰核電,現階段也並不考慮新建核電廠。

減碳聯盟「Under2 Coalition」 規模跨六大洲、佔全球經濟四成

原本加州計畫於2020年達成減排13%、同時經濟成長26%,結果此目標提早四年達成。從2015到2016年,加州減排量大約等於減少240萬輛汽車上路,省下15億加侖的汽柴油燃料。

此外,布朗幫助建立全美和全球的減碳合作聯盟「Under2 Coalition」。該聯盟起源於加州與德國巴登-符騰堡州之間的合作關係,現在已納入橫跨六大洲的206個各級政府,共代表13億人口和30兆美元的GDP,相當於全球人口的17%和全球經濟的40%。

聯盟成員做出了一些重要承諾,包括減少相當於1990年水準80%至95%的溫室氣體排放,以及2050年加州每人每年碳排少於2公噸。

California Passes 100 Percent Clean Electricity Bill SACRAMENTO, California, September 10, 2018 (ENS)

Putting his stamp of approval on California’s global climate leadership, Governor Jerry Brown today signed Senate Bill 100, which sets a 100 percent clean electricity goal for the state. The governor also issued an executive order establishing a new target to achieve carbon neutrality – both by 2045.

With Governor Brown’s executive order, California establishes the most ambitious carbon neutrality commitment of the more than 20 countries and at least 40 cities, states and provinces planning to go carbon neutral by mid-century or sooner.

SB 100 advances the state’s existing Renewables Portfolio Standard, which establishes how much of the electricity system should be powered from renewable energy resources, to 50 percent by 2025 and 60 percent by 2030.

It also puts California on the bold path to implement a zero-carbon electricity grid by 2045.

To ensure California is combating global warming beyond the electric sector, which represents 16 percent of the state’s greenhouse gas emissions, the governor’s executive order directs the state to achieve carbon neutrality by 2045 and net negative greenhouse gas emissions after that.

The state will reach its goals with continued reductions of carbon pollution and increased carbon sequestration in forests, soils and other natural landscapes and programs focused on improving air quality and public health, especially in California’s most impacted communities.

These actions are the latest in a long series of actions by California to deal with the warming climate. Late last week, Governor Brown also signed legislation to block new federal offshore oil drilling along California’s coast and announced the state’s opposition to the federal government’s plan to expand oil drilling on public lands in California. The entirety of the state’s coast has been off-limits to new oil and gas leases for more than 30 years, and the state has not issued a lease for offshore oil or gas production since 1968.

The state has met its 2020 target four years early, reducing emissions 13 percent while growing the economy 26 percent.

From 2015 to 2016, emissions reductions were roughly equal to taking 2.4 million cars off the road, saving 1.5 billion gallons of gasoline and diesel fuel.

In addition, Governor Brown has helped establish and expand coalitions of partners across the nation and globe committed to curbing carbon pollution.

The Under2 Coalition, which originated from a partnership between California and the German state of Baden-Württemberg, now includes 206 jurisdictions on six continents that collectively represent 1.3 billion people and $30 trillion in GDP – equivalent to 17 percent of the global population and 40 percent of the global economy.

Members of the Under2 Coalition have made a number of key commitments, including reducing greenhouse gas emissions equivalent to 80 to 95 percent below 1990 levels or to less than two annual metric tons for each person in California by 2050.

※ 全文及圖片詳見:

作者

如果有一件事是重要的,如果能為孩子實現一個願望,那就是人類與大自然和諧共存。

於特有生物研究保育中心服務,小鳥和棲地是主要的研究對象。是龜毛的讀者,認為龜毛是探索世界的美德。

延伸閱讀

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

聚甘新

Chevrolet Bolt平價電動車投產、續航力近400公里

美聯社5日報導,通用汽車(General Motors)已開始在美國密西根州奧萊恩(Orion)鎮生產Chevrolet Bolt。這台續航力達238英里(383公里)的電動車建議零售價37,495美元起(註:最多可取得7,500美元的聯邦折抵稅額、扣除後入手價相當於29,995美元),今年底以前將於加州、奧勒岡州開賣,明年將擴及全美其他地區。根據凱利藍皮書(Kelley Blue Book)的統計,美國新車平均價格為34,000美元。特斯拉(Tesla Motors Inc.)200英里續航力、35,000美元(註:扣除聯邦折抵稅額前)Model 3電動車預計自2017年下半年開始交車。

IHS Markit汽車業分析師Stephanie Brinley指出,Bolt續航力遠高於美國平均來回通勤距離(40英里),但有時人們回家後可能忘了或沒有足夠時間進行充電,這是電動車主得多加費心的地方。美國目前使用中的電動車數量約23.5萬台,IHS預估Bolt開賣第一年銷售量逼近3萬台。美國去年電動車銷售量為10萬台,IHS預估年度銷售量將在2020年升至30萬台、2025年挑戰40萬台。

華爾街日報報導,通用汽車2015年10月宣布與LG電子結盟、後者將成為Chevrolet Bolt電動車的主要關鍵零件供應商。

英國金融時報8月報導,國際能源署(IEA)首席經濟學家Laszlo Varro指出,電動車目前對商品(原油)市場的影響力大約就像是10年前的太陽能一樣。他說,太陽能現在已是一個規模達數百億美元的市場、擁有龐大的影響力。Varro提到,電動車需達5千萬至1億台的規模、才能取代相當於100萬桶的石油日消費量。

IEA數據顯示,2009年全球40個國家合計僅有不到6千台的電動車、去年已升至120萬台。麥格理集團全球能源策略師Vikas Dwivedi指出,沙烏地阿拉伯對電動車的長期發展存有戒心,這可能就是它為何宣布將讓沙烏地阿拉伯國家石油公司(Saudi Aramco)初次公開發行(IPO)的原因之一。

CNNMoney去年底報導,石油輸出國組織(OPEC)發表的年度「世界石油展望(World Oil Outlook)」報告顯示,2040年高達94%的使用中車輛仍將是依靠石油燃料。OPEC報告顯示,除非出現重大技術性突破,否則在可預見的未來電動車將難以大幅取得市佔率。油國組織預估2040年僅有1%的車輛銷售量是來自純電動車款。

能源情報署(EIA)公布,截至2016年10月28日為止當週美國商用汽油庫存報2.238億桶、較去年同期高出3.9%。EIA公布的數據顯示,美國一般汽油每加侖零售均價10月31日報2.230美元,較一年前高出0.006美元。

(照片來源:。本文內容由 授權提供)

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

【其他文章推薦】

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

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

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

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

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

聚甘新

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/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

聚甘新

第 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 即可進入這篇文章下的評論列表接口,獲取這篇文章的評論列表資源了:

關注公眾號加入交流群

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

【其他文章推薦】

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

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

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

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

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

聚甘新

一起玩轉微服務(5)——分層架構

領域驅動設計DDD(Domain Driven Design)提出了從業務設計到代碼實現一致性的要求,不再對分析模型和實現模型進行區分。也就是說從代碼的結構中我們可以直接理解業務的設計,命名得當的話,非程序人員也可以“讀”代碼。這與微服務設計中的約定優於配置不謀而合,如果你熟悉英文,那麼直接根據包名和類名就可以直接解讀出程序開發者所構建的業務的大概意圖。

領域模型包含一些明確定義的類型:

  • 實體是一個對象,它有固定的身份,具有明確定義的”連續性線索”或生命周期。通常列舉的示例是一個 Person(一個實體)。大多數系統都需要唯一地跟蹤一個 Person,無論姓名、地址或其他屬性是否更改。
  • l值對象沒有明確定義的身份,而僅由它們的屬性定義。它們通常不可變,所以兩個相等的值對象始終保持相等。地址可以是與 Person 關聯的值對象。
  • l集合是一個相關對象集群,這些對象被看作一個整體。它擁有一個特定實體作為它的根,並定義了明確的封裝邊界。它不只是一個列表。
  • l服務用於表示不是實體或值對象的自然部分的操作或活動。

領域模型在實現時可大可小,在業務的早期,在系統比較小的情況下,它有可能是一個類。當系統做大了以後,它可能是個庫。再做更大一點的時候,它可能是一個服務,給不同的應用去調用。

要將領域元素轉換為服務,可按照以下一般準則來完成此操作:

  • 使用值對象的表示作為參數和返回值,將集合和實體轉換為獨立的微服務。
  • 將領域服務(未附加到集合或實體的服務)與獨立的微服務相匹配。
  • 每個微服務應處理一個完整的業務功能。

領域模型又可以分為失血、貧血和充血3種。

  • 失血模型:基於數據庫的領域設計方式就是典型的失血模型,只關注數據的增刪改查。
  • 貧血模型:就是在domain object包含了不依賴於持久化的領域邏輯,而那些依賴持久化的領域邏輯被分離到server層。
  • 充血模型:充血模型跟貧血模型差不多,不同的是如何劃分業務邏輯,就是說,約大部分業務應該放到domain object裏面,而service應該是很薄的一層。

設計原則之分層架構

同一公司使用統一應用分層,以減少開發維護學習成本。應用分層這件事情看起來很簡單,但每個程序員都有自己的一套,哪怕是初學者,所以想實施起來並非那麼容易。

最早接觸分層架構的應該是我們最熟悉的MVC(Model-View-Controller)架構,將應用分成了模型、視圖和控制層,可以說引導了絕大多數開發者,而我們現在的應用中非常多的包括框架,架構設計都使用此模式。這后又演化出了MVP(Model-View-Presenter)和MVVM(Model-View-ViewModel)。這些可以說都是隨着技術的不斷髮展,為了應對不同場景所演化出來的模型。而微服務的每個架構都可以再細分成領域模型,下面看一下經典的領域模型架構。

它包括了Domain,Service Layer和Repositories。核心實體(Entity)和值對象(Value Object)應該在Domain層,定義的領域服務(Domain Service)在Service Layer,而針對實體和值對象的存儲和查詢邏輯都應該在Repositories層。值得注意的是,不要把Entity的屬性和行為分離到Domain和Service兩層中去實現,即所謂的貧血模型,事實證明這樣的實現方式會造成很大的維護問題。基於這種設計,工程的結構可以構造為:

– MicroService-Sample/src/

    domain

    gateways

    interface

    repositories

    services

當然,在微服務的架構中,每個微服務不必嚴格遵照這樣的規定,切忌死搬硬套,最重要的是理解,在不同的業務場合,架構的設計可以適當的做調整,畢竟適合的架構一定要具有靈活性。

分層的原則包括:

  • 文件夾分層法

應用分層採用文件夾方式的優點是可大可小、簡單易用、統一規範,可以包括 5 個項目,也可以包括 50 個項目,以滿足所有業務應用的多種不同場景。

  • 調用規約

在開發過程中,需要遵循分層架構的約束,禁止跨層次的調用。

  • 下層為上層服務

以用戶為中心,以目標為導向。上層(業務邏輯層)需要什麼,下層(數據訪問層)提供什麼,而不是下層(數據訪問層)有什麼,就向上層(業務邏輯層)提供什麼。

  • 實體層規約

Entity是數據表對象,不是數據訪問層對象;DTO 是網絡傳輸對象,不是表現層對象;BO 是內存計算邏輯對象,不是業務邏輯層對象,不是只能給業務邏輯層使用 。如果僅限定在本層訪問,則導致單個應用內大量沒有價值的對象轉換。以用戶為中心來設計實體類,可以減少無價值重複對象和無用轉換。

  • U 型訪問

下行時表現層是 Input,業務邏輯層是 Process,數據訪問層是 Output。上行時數據訪問層是 Input,業務邏輯層是 Process,  表現層就 Output。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

聚甘新

六、線程池(一)

線程池

通過建立池可以有效的利用系統資源,節約系統性能。Java 中的線程池就是一種非常好的實現,從 JDK1.5 開始 Java 提供了一個線程工廠 Executors 用來生成線程池,通過 Executors 可以方便的生成不同類型的線程池。

線程池的優點

  • 降低資源消耗。線程的開啟和銷毀會消耗資源,通過重複利用已創建的線程降低線程創建和銷毀造成的消耗。
  • 提高響應速度。當任務到達時,任務可以不需要的等到線程創建就能立即執行。
  • 提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。

常見的線程池

  • CachedThreadPool:可緩存的線程池,該線程池中沒有核心線程,非核心線程的數量為 Integer.max_value,就是無限大,當有需要時創建線程來執行任務,沒有需要時回收線程,適用於耗時少,任務量大的情況。
  • SecudleThreadPool:周期性執行任務的線程池,按照某種特定的計劃執行線程中的任務,有核心線程,但也有非核心線程,非核心線程的大小也為無限大。適用於執行周期性的任務。
  • SingleThreadPool:只有一條線程來執行任務,適用於有順序的任務的應用場景。
  • FixedThreadPool:定長的線程池,有核心線程,核心線程的即為最大的線程數量,沒有非核心線程
  • Executors.newFixedThreadPool()、Executors.newSingleThreadExecutor() 和 Executors.newCachedThreadPool() 等方法的底層都是通過 ThreadPoolExecutor 實現的。

ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        // maximumPoolSize 必須大於 0,且必須大於 corePoolSize
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

參數介紹:

  • corePoolSize

    • 線程池的核心線程數。在沒有設置 allowCoreThreadTimeOut 為 true 的情況下,核心線程會在線程池中一直存活,即使處於閑置狀態。
    • 如果設置為 0,則表示在沒有任何任務時,銷毀線程池;如果大於 0,即使沒有任務時也會保證線程池的線程數量等於此值。
    • 但需要注意,此值如果設置的比較小,則會頻繁的創建和銷毀線程,如果設置的比較大,則會浪費系統資源,所以需要根據自己的實際業務來調整此值。
  • maximumPoolSize

    • 線程池所能容納的最大線程數。當活動線程(核心線程+非核心線程)達到這個數值后,後續任務將會根據 RejectedExecutionHandler 來進行拒絕策略處理。
    • 官方規定此值必須大於 0,也必須大於等於 corePoolSize,此值只有在任務比較多,且不能存放在任務隊列時,才會用到。
  • keepAliveTime

    • 非核心線程閑置時的超時時長。超過該時長,非核心線程就會被回收。
    • 若線程池通過 allowCoreThreadTimeOut() 方法設置 allowCoreThreadTimeOut 屬性為 true,則該時長同樣會作用於核心線程,AsyncTask 配置的線程池就是這樣設置的。
  • unit

    • keepAliveTime 時長對應的單位。
  • workQueue

    • 表示線程池執行的任務隊列,當線程池的所有線程都在處理任務時,如果來了新任務就會緩存到此任務隊列中排隊等待執行。
    • 是一個阻塞隊列 BlockingQueue,雖然它是 Queue 的子接口,但是它的主要作用並不是容器,而是作為線程同步的工具,他有一個特徵,當生產者試圖向 BlockingQueue 放入(put)元素,如果隊列已滿,則該線程被阻塞;當消費者試圖從 BlockingQueue 取出(take)元素,如果隊列已空,則該線程被阻塞。
  • ThreadFactory

    • 線程的創建工廠,功能很簡單,就是為線程池提供創建新線程的功能。
    • 也可以自定義一個線程工廠,通過實現 ThreadFactory 接口來完成,這樣就可以自定義線程的名稱或線程執行的優先級了。
    • 通常在創建線程池時不指定此參數,它會使用默認的線程創建工廠的方法來創建線程,源代碼如下:
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        // Executors.defaultThreadFactory() 為默認的線程創建工廠
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
    public static ThreadFactory defaultThreadFactory() {
        return new DefaultThreadFactory();
    }
    // 默認的線程創建工廠,需要實現 ThreadFactory 接口
    static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;
    
        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }
        // 創建線程
        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon()) 
                t.setDaemon(false); // 創建一個非守護線程
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY); // 線程優先級設置為默認值
            return t;
        }
    }
    
  • RejectedExecutionHandler

    • 表示指定線程池的拒絕策略,當線程池的任務已經在緩存隊列 workQueue 中存儲滿了之後,並且不能創建新的線程來執行此任務時,就會用到此拒絕策略.
    • 它屬於一種限流保護的機制,這裡有四種任務拒絕類型:
      1. AbortPolicy: 不執行新任務,直接拋出異常,提示線程池已滿,涉及到該異常的任務也不會被執行,線程池默認的拒絕策略就是該策略。
      2. DisCardPolicy: 不執行新任務,也不拋出異常,即忽略此任務;
      3. DisCardOldSetPolicy: 將消息隊列中的第一個任務(即等待時間最久的任務)替換為當前新進來的任務執行,忽略最早的任務(最先加入隊列的任務);
      4. CallerRunsPolicy: 把任務交給當前線程來執行;
    /**
     * 線程池的拒絕策略
     */
    @Test
    public void test1() {
        // 創建線程池 核心線程為1,最大線程為3,任務隊列大小為2
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(1, 3, 10,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(2),
                new ThreadPoolExecutor.AbortPolicy() // 添加 AbortPolicy 拒絕策略
        );
    
    
        for (int i = 0; i < 6; i++) {
            poolExecutor.execute(() -> {
                System.out.println(Thread.currentThread().getName());
            });
        }
        
    }
    
    • 自定義線程池拒絕策略
    /**
     * 自定義線程池的拒絕策略
     * 實現接口 RejectedExecutionHandler
     */
    @Test
    public void test2() {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 3, 10,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(2),
                new RejectedExecutionHandler() {
    
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        // 業務處理方法
                        System.out.println("執行自定義拒絕策略");
                    }
                }
        );
    
        for (int i = 0; i < 6; i++) {
            executor.execute(() -> {
                System.out.println(Thread.currentThread().getName());
            });
        }
    
    }
    

線程池工作原理

線程池的工作流程要從它的執行方法 execute() 說起,源碼如下:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    // 當前工作的線程數小於核心線程數
    if (workerCountOf(c) < corePoolSize) {
        // 創建新的線程執行此任務
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 檢查線程池是否處於運行狀態,如果是則把任務添加到隊列
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // 再出檢查線程池是否處於運行狀態,防止在第一次校驗通過後線程池關閉
        // 如果是非運行狀態,則將剛加入隊列的任務移除
        if (! isRunning(recheck) && remove(command))
            reject(command);
        // 如果線程池的線程數為 0 時(當 corePoolSize 設置為 0 時會發生)
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false); // 新建線程執行任務
    }
    // 核心線程都在忙且隊列都已爆滿,嘗試新啟動一個線程執行失敗
    else if (!addWorker(command, false)) 
        // 執行拒絕策略
        reject(command);
}

execute() VS submit()

  • execute() 和 submit() 都是用來執行線程池任務的,它們最主要的區別是,submit() 方法可以接收線程池執行的返回值,而 execute() 不能接收返回值。
  • sumbit 之所以可以接收返回值,是因為參數中可以傳遞:Callable task,而通過 callable 創建的線程任務有返回值並且可以拋出異常。
/**
 * execute VS sumbin
 * execute 提交任務沒有返回值
 * submit 提交任務有返回值
 */
@Test
public void test3() throws ExecutionException, InterruptedException {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 10, 10, TimeUnit.SECONDS, new LinkedBlockingDeque<>(20));
    // execute
    executor.execute(new Runnable() {
        @Override
        public void run() {
            System.out.println("Hello, execute");
        }
    });

    // submit 使用
    Future<String> future = executor.submit(new Callable<String>() {
        @Override
        public String call() throws Exception {
            System.out.println("Hello, submit");
            return "submit success";
        }
    });
    System.out.println(future.get());
}
  • 它們的另一個區別是 execute() 方法屬於 Executor 接口的方法,而 submit() 方法則是屬於 ExecutorService 接口的方法。

線程池的使用:

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author xiandongxie
 */
public class ThreadPool {

    //參數初始化 返回Java虛擬機可用的處理器數量
//    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CPU_COUNT = 2;
    //核心線程數量大小
    private static final int corePoolSize = Math.max(2, Math.min(CPU_COUNT - 1, 4));
    //線程池最大容納線程數
    private static final int maximumPoolSize = CPU_COUNT * 2 + 1;
    //線程空閑后的存活時長
    private static final int keepAliveTime = 30;

    //任務過多后,存儲任務的一個阻塞隊列
    BlockingQueue<Runnable> workQueue = new SynchronousQueue<>();

    //線程的創建工廠
    ThreadFactory threadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) {
            return new Thread(r, "AdvacnedAsyncTask #" + mCount.getAndIncrement());
        }
    };

    //線程池任務滿載后採取的任務拒絕策略: 不執行新任務,直接拋出異常,提示線程池已滿
    RejectedExecutionHandler rejectHandler = new ThreadPoolExecutor.AbortPolicy();

    //線程池對象,創建線程
    ThreadPoolExecutor mExecute = new ThreadPoolExecutor(
            corePoolSize,
            maximumPoolSize,
            keepAliveTime,
            TimeUnit.SECONDS,
            workQueue,
            threadFactory,
            rejectHandler
    );

    public static void main(String[] args) {
        System.out.println("main start ..... \nCPU_COUNT = " + CPU_COUNT + "\tcorePoolSize=" + corePoolSize + "\tmaximumPoolSize=" + maximumPoolSize);
        
        ThreadPool threadPool = new ThreadPool();
        ThreadPoolExecutor execute = threadPool.mExecute;
        // 預啟動所有核心線程
        execute.prestartAllCoreThreads();

        for (int i = 0; i < 5; i++) {
            execute.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "\tstart..." + System.currentTimeMillis());
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "\tend..." + System.currentTimeMillis());
                }
            });
        }
        execute.shutdown();
        
        System.out.println("main end .....");
    }
}

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

【其他文章推薦】

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

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

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

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

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

聚甘新

跨雲廠商部署 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的動態。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

聚甘新

這樣用技術,程序猿更幸福

作為久經職場而又富有責任心的猿外,每天上班的第一件事,便是連上生產服務器,top free ps 一頓命令熱身猛如虎,然後匯總出業務服務的狀態、系統指標等,看到一切正常,心裏才算踏實。

不知道有多少盡職盡責的朋友們,每天都重複的做着如此机械而又簡單的事情。說句實話,其實和机械狗沒啥區別。本着做事認真、追求極致的態度,那我們為什麼不能打造一款這樣的机械狗呢?

關注過猿外的朋友們都應該知道,在之前的文章中,猿外曾經提到過,守護服務的監控應用——看門狗。不錯,要打造的机械狗,就是看門狗。

接下來就給大家簡單聊一聊,看門狗的背景來源以及實現思路,希望能給正在尋找守護服務監控解決方案的朋友們,一點實現思路。下面的內容十分燒腦,請大家坐穩扶好。

1. 什麼是看門狗?

在由單片機構成的微型計算機系統中,由於單片機的工作,常常會受到來自外界電磁場的干擾,造成程序的跑飛,而陷入死循環,程序的正常運行被打斷,由單片機控制的系統無法繼續工作,會造成整個系統的陷入停滯狀態,發生不可預料的後果,所以出於對單片機運行狀態進行實時監測的考慮,便產生了一種專門用於監測單片機程序運行狀態的芯片,稱“看門狗”——摘自“百度百科”。

相信有不少朋友,看到上面一段非人話的描述,就想直接刪了此篇熊文,溜之大吉。大家,心莫慌,猿外再舉個貼近生活的栗子解釋一下。

猿外家養了一條 dog ,經常陪孩子玩,孩子時不時的撒一把狗糧給 dog;
如果孩子睡着,長時間未撒狗糧,dog 就主動把孩子叫醒;
繼續讓孩子撒狗糧,然後 dog 吃到孩子撒的狗糧,就高興的狂吠;
如果 dog 跑走了,孩子也不撒狗糧了,孩子也就可以睡覺了。

剝開栗子,內行看門道,外行看熱鬧,把上面內容翻譯一遍:

原話:猿外家養了一條 dog,經常陪孩子玩,孩子時不時的撒一把狗糧給 dog;
翻譯:存在兩個進程:子進程(應用服務)、父進程(守護進程),子進程和父進程兩個進程間一直保持心跳通訊;

原話:如果孩子睡着,長時間未撒狗糧, dog 就主動把孩子叫醒;
翻譯:子進程與父進程心跳通訊中斷,父進程則負責啟動子進程,完成子進程的服務守護;

原話:繼續讓孩子撒狗糧,然後 dog 吃到孩子撒的狗糧,就高興的狂吠;
翻譯:子進程與父進程實時通訊,父進程實時監控子進程的狀態,並實時進行報警通知;

原話:如果 dog 跑走了,孩子也不撒狗糧了,孩子也就可以睡覺了。
翻譯:父進程down了,子進程與父進程的通訊也就無法保持了,子進程也一併退出。

不知道朋友們通過上面的栗子剖析,對看門狗了解了多少呢?為了更清晰的給你們說清楚,猿外再給大家上個一目瞭然的圖吧。

如上圖所示,猿外主要把看門狗定位為:集服務守護、指標採集、日誌歸集、自動化報警於一體的監控系統。說白了,有了看門狗,媽媽再也不用擔心我的應用服務出問題了。

洋洋洒洒鋪墊這麼多,那到底該如何實現呢?

2. 如何實現?

猿外先給大家畫個腦圖,主要分五步走,希望朋友們跟着猿外的腳步,莫掉隊。

相信絕大部分朋友通過猿外的腦圖,已經對看門狗應用了解個八九不離十。猿外再稍微闡述一下:

實現方式:看門狗應用在實現過程中也走了一些彎路。剛開始技術選型的時候,考慮到看門狗應該對應用零侵入、低耦合,於是採用javaagent植入方式進行實現,但是萬萬沒想到的是開發、調試過程比較麻煩,一遇到問題,小夥伴就排查半天,在第一版上線后,猿外迅速帶領小夥伴,採用插件化方式進行第二版迭代。

服務守護:採用 J2SE 1.5 java.lang 包中新添加的 ProcessBuilder 類來完成子進程的啟動、停止、重啟(完成子服務的守護功能)。

指標採集:通過 JMX 方式連接子服務,獲取子服務的內存佔用、CPU 使用、線程數等指標。

日誌歸集:主要站在 flume 的肩膀上,集成到項目中並進行二次開發,存儲採用 elasticsearch。

報警通知:主要提供郵箱通知、QQ 通知、微信實時通知。

其中每個實現細節猿外就不再進行深入展開了,感興趣的朋友歡迎關注微信公眾號四猿外(si-yuanwai),後台留言進行交流。

羅里吧嗦,寫了這麼多。羅馬並非一日建成的,實現途中踩過的坑、吃過的苦頭,剎那間全部湧上心頭。不過看門狗應用,已經在生產上推廣使用,自上線以來還廣受好評,猿外現在每天來的第一件事,由原來的系統巡檢變成了沖咖啡,幸福指數陡然上升。

最後,猿外想說的是:簡單的事重複做,你就是專家;重複的事用心做,你就是贏家。沒錯,這話說的沒毛病,不過成為專家之餘,大家不妨稍做創新,看看有沒有可以用的技術輪子,說不定會改變机械的工作方式,提升工作效率,提高幸福指數呢。

歡迎大家關注我的公眾號:四猿外。
我準備了一些純手打的高質量PDF,有好友贊助的也有我自己的,大家可以免費領取:
深入淺出Java多線程、HTTP超全匯總、Java基礎核心總結、程序員必知的硬核知識大全、簡歷面試談薪的超全乾貨。
別看數量不多,但篇篇都是乾貨,看完的都說很肝。

領取方式:掃碼關注后,在公眾號後台回復:666

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

【其他文章推薦】

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

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

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

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

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

聚甘新