疫情影響 中國第一季新增太陽能和風電裝機驟減

摘錄自2020年5月12日財訊快報報導

據外電報導指出,統計數據顯示,今(2020)年第一季中國新增太陽能發電裝機和新增風電裝機大幅減少,因遏制新型冠狀病毒疫情的努力阻礙了相關項目的建設。

中國國家能源局公佈的數據顯示,第一季新增太陽能發電裝機395萬千瓦,較上年同期減少24%。新增風電裝機大幅減少過半至236萬千瓦。

這項數據反映出疫情對中國這個全球最大的可再生能源市場產生了嚴重衝擊。疫情導致中國整個供應鏈出現原物料和勞動力短缺,並阻礙了正常運營。受疫情影響,中國將提交新太陽能發電項目的截止日期延遲超過一個月。

【其他文章推薦】

※有廠商專門客製化橡膠製品嗎?

※噴霧洗滌塔實際應用案例分享

※票選推薦煮婦最愛手壓封口機,省荷包不犧牲品質

※選購空壓機需注意八大事項 !

※DIY自行施工隔熱紙,簡易教學大公開 !

※選擇好的茶葉罐,有效地保持茶葉的鮮度與風味!

※單螺及雙螺杆泵優劣解析

養殖業轉型漁電共生新型態 綠能光電業前進嘉義縣獵地

摘錄自2020年5月13日聯合報報導

嘉義縣沿海養殖業近年來因魚蝦生產過剩,價格疲軟,加上極端氣候病害育成率低,紛紛將魚塭出租綠能光電業,國內兩大綠能集團、台泥綠能及向陽集團前進嘉縣獵地,找漁民合作,搭建戶外型及室內型「漁電共生」養殖,目前已有一處專區及兩處室內場搭建中,養殖產業轉型改變中。

綠能光電獵地集中義竹鄉及布袋鎮;義竹每公頃年租金5至6萬元,綠能光電業者開出每公頃年租金35至40萬元,雖要綁約20年,但漁民不必投資養殖有租金拿,樂得將魚塭出租綠能業。

義竹養殖區今年有上百公頃不再放養,台泥綠能在義竹及布袋交界處,申請1處廣達77公頃的戶外型「漁電共生專區」,其中4成覆蓋架設太陽能光板,6成做養殖使用。農業處漁業科說,釋放農地作再生能源嚴格審查,除非該土地耕作困難或3年以上廢棄養魚塭,否則不同意釋出使用。

【其他文章推薦】

※示波器鮮為人知的使用技巧?

※各大百貨每波促銷贈品活動,限量知名LOGOL型資料夾,獨家販售中!!

※飲水機選購技巧大公開!!

※空壓機何時可換油? 教你正確觀念與維護 !

※真空封口機該不該買?使用心得分享

※大樓隔熱紙施工分享說明,教你如何善用空間裝潢設計 !

蘇貞昌愛將、環保署長張子敬踢鐵板 全台環團連署下台

摘錄自2020年5月13日聯合報報導

環保署長張子敬被視為行政院長蘇貞昌愛將,又因為擔任署長任內通過桃園鐵路地下化、桃園航空城、淡北道路等多個爭議環評案件,遭質疑強勢主導環評案件造成寒蟬效應。隨著520將至,明天就是內閣總辭,環團昨天(12日)深夜發起全台環團連署,呼籲新內閣不應讓張子敬續任環保署長,至今10多個小時就有24個環團連署支持,希望在5月19日前號召全台100個環團加入。

張子敬在擔任副署長任內最大爭議為2008年被質疑主導蘇花高環評案,當時有許多環團到環保署前抗議,批評張子敬違反行政中立,更因強力主導讓環評委員面臨沈重壓力,甚至形成寒蟬效應,應該立刻下台。12年後的今天,張子敬主掌的環保署也屢次遭外界批評將手伸入環評會議,再度造成環評委員「寒蟬效應」,淡北道路、桃園鐵路地下化、桃園航空城等重大爭議環評案件,都在近期快速通關,只要進入大會的環評案件,無一沒有闖關成功。

環團連署聲明指出,自從張子敬接替李應元擔任環保署長後,卻反其道而行,一路為重大開發案服務,不論是環評、廢棄物、公民參與都呈現停滯甚至倒退,呼籲蔡總統與蘇院長,在今年2020年5月20日上任後的內閣布局,環保署長一職應任命更適合的人選,而張子敬已在其環保署長任期內,證明自己的不適任。全台環保團體呼籲蔡總統蘇院長,勿提名張子敬續任環保署長。

【其他文章推薦】

※專業客製化禮物、贈品設計,辦公用品常見【L夾】搖身一變大受好評!!

※示波器探測執行效能最佳化的8大秘訣

※(全省)堆高機租賃保養一覽表

※什麼規格的隔熱紙能有效提升冷房效果?

※票選推薦煮婦最愛手壓封口機,省荷包不犧牲品質

※如何知道自已的電腦cpu支不支持AVX指令集?

違法章魚籠破壞海洋生態 馬祖掃蕩清除594具

綜合摘錄自2020年5月11日中央社、蘋果日報報導

網友自3月起於「馬祖資訊網」匿名指出馬祖海域章魚籠破壞海洋生態問題。海巡署艦隊分署第十海巡隊出動巡防艇戒護,與連江縣產業發展處委外民間漁船共同查緝違法網具的漁業捕撈行為,於5月初至今共沒收594具章魚籠。

章魚籠對海洋生態的殺傷力,在於很多小魚在未長成之前便遭捕撈,其中最多見的是石狗公、石斑魚、小黑鯛等高經濟魚類。根據漁業法第54條第5款公告,連江縣海域漁業籠具作業禁止使用蟹籠具、章魚籠等漁具進行採捕水產動物。

對於馬祖海域非法章魚籠漁具,產發處表示將持續取締,以維護馬祖海域棲地資源、漁業環境及生態資源,違法施放籠具者將依漁業法第65條第8款,處新台幣3萬元以上,15萬元以下罰鍰。

【其他文章推薦】

※影響示波器測試準確度的五大因素

※無毒橡膠墊片哪裡買的到?

※十大封口機人氣排行榜-烘焙必備幫手!

※哪裡買的到省力省空間,方便攜帶的購物推車?

※買不起高檔茶葉,精緻包裝茶葉罐,也能撐場面!

※(全省)堆高機租賃保養一覽表

※臭氧機推薦

台灣「石斑王國」招牌褪色 養殖重鎮屏東紛換午魚

摘錄自2020年5月12日聯合報報導

高雄與屏東是石斑魚等養殖漁業外銷香港和中國大陸重要產區,業界憂心今年9月ECFA(海峽兩岸經濟合作架構協議)屆滿10年,中國若終止台灣零關稅優惠,我方將失去外銷中國的市場競爭力;前屏東縣養殖漁業發展協會理事長陳登木說,因中國石斑養殖業興起價格下滑,加上對未來的不確定性,面對一連串警訊,逾半養殖戶近年已轉型自救,減縮或棄養石斑,改養午仔魚,台灣「石斑王國」招牌已漸褪色。

屏東縣養殖漁業發展協會理事長李金生說,過去中國對於台灣石斑魚需求量大,因養殖技術外流,中國養殖石斑的產量增加,台灣石斑魚競爭優勢漸失;2009年莫拉克颱風侵台,林邊、佳冬和枋寮地區的魚塭受重創,災過後有人改養肉質細緻、刺少的午仔魚,且以貨櫃冷凍運送,不像石斑需靠活魚運搬船,愈來愈多人跟進,目前屏東占全台午仔魚養殖面積的八成,有後來居上的態勢;疫情期間,石斑魚價格因餐廳需求端銳減下跌,大小適合家庭用的午魚卻賣出每斤140元好價。

【其他文章推薦】

※新北市探針選用參考標準?

※如何知道自已的電腦cpu支不支持AVX指令集?

※如何正確使用飲水機?

※攻戰消費者第一視覺,包裝設計很重要!

※滑鼠墊適用各種文宣活動廣告曝光,專業客製服務

※封口機購物網-不怕你比價,就怕你買貴!

※高溫殺菌機最多可達多少溫度?

[Computer Vision]Harris角點檢測的詳細推導

Harris角點檢測

思想

為什麼要檢測角點呢?因為角點的特徵比較明顯。進行角點檢測的樸素思想是利用圖像梯度,也就是根據圖像強度的變化來尋找角點。如圖所示

這裏舉了個例子,給定一個小的區域(Patch),當這個小區域在不同位置滑動的時候,所呈現出來的一些特性是不同的,根據圖示,有三個方面。

  • Flat,平的地方,在任何方向,梯度都沒什麼變化。
  • Edge,邊的地方,當沿着邊方向的時候,梯度沒什麼變化。
  • Corner,角的地方,沿着任何方向,梯度都有變化。

Error Function

\[E(u,v)=\sum_{x,y}{w(x,y)[I(x+u,y+v)-I(x,y)]^2} \]

  • \(x,y\)是相對於一個小patch來說的,例如一個5*5的區域
  • \((u,v)\)是一個很小的移動量
  • \(w(x,y)\)是windows function,也就是對於每個點的權重,例如想讓中心的點權重高,可以用高斯核,一般就是全1或者高斯。
  • \(I(x,y)\)就代表圖像在\((x,y)\)的強度值。
  • 後面做差其實就是類似求梯度一樣

根據之前的討論,在一個patch里,如果有角點的存在,各個方向的梯度值都很大,於是乎,我們的目標是讓\(E(u,v)\)盡可能的大。
因為\((u,v)\)的值很小,所以我們可以利用二元函數的泰勒展開,來近似計算。
二元函數的泰勒展開,當然扔掉了一些項。

\[f(x+u,y+v) \approx f(x,y)+uf_x(x,y)+vf_y(x,y) \]

那麼我們對Error function中的關鍵部分進行展開

\[\begin{aligned} [I(x+u,y+v)-I(x,y)]^2 &\approx [I(x,y)+uI_x+vI_y-I(x,y)]\\ &=(uI_x+vI_y)^2\\ &=[u,v] \begin{bmatrix} I_x^2 &I_xI_y\\ I_xI_y&I_y^2 \end{bmatrix} \begin{bmatrix} u\\v \end{bmatrix} \end{aligned} \]

所以Error Function可以近似為

\[E(u,v)\approx [u,v]M\begin{bmatrix} u\\v \end{bmatrix} \]

\[M= \sum_{x,y}{w(x,y) \begin{bmatrix} I_x^2 &I_xI_y\\ I_xI_y&I_y^2 \end{bmatrix} } \]

這就涉及到線性代數里的二次型問題了。

簡單的二次型

例如 \(f(x,y) = x^2+y^2\)的可以寫作矩陣的形式

\[[x,y]\begin{bmatrix} 1 & 0\\ 0 & 1 \end{bmatrix} \begin{bmatrix} x\\y \end{bmatrix} \]

由中間這個矩陣來決定這個二次型的形狀,因為我們研究的二次型只有兩個變量,所以可以可視化來理解如下圖所示。對形狀矩陣可以進行特徵分解,分為中間的對角陣(對角線都是特徵值)兩邊是特徵向量。特徵向量代表了橢圓切片的長短軸的方向,而特徵值平方根的倒數代表了軸的長短。至於為什麼分解完會和橢圓對應,線性代數書上會有。

這樣就把Error Function給可視化了,有了幾何含義,更加直觀了。

  • Flat的時候,\((u,v)\)往哪個方向變化都不大,反應在幾何上,應該是一個較為平坦的面
  • Edge的時候,\((u,v)\)往某個方向變化大,反應在幾何上,應該是某個方向翹起。
  • Corner的時候,\((u,v)\)往大部分方向變化都大,反應在幾何上,應該是大部分方向都翹起。

如圖所示

我們可以通過兩個特徵值之間的大小關係,以及他們自身的關係來作為評估的依據。

當兩個特徵值都很大,且差不多時,意味着角點。

角點響應的度量

以上分析了,要兩個特徵值都很大,且同時大,那怎麼來度量?於是乎在最原始的論文里,這樣定義了響應函數,並且對不同的\(\lambda\)有以下的響應圖

\[R = det(M)-k(trace(M))^2\\ det(M) = \lambda_1\lambda_2\\ trace(M) = \lambda_1+\lambda_2 \]

\(k\)一般在是0.04-0.06

如圖所示,黃色的線是等值線,代表\(R\)的值都相同,左上角是\((0,0)\)點,往右下角去\(R\)的值越大,代表角點的響應越高,圖中畫了個綠線,右側的R值基本可以判斷為是角點了。另外還有一些別的響應函數,基本大同小異吧。

算法

所以現在經過以上的分析,總結一下角點檢測的算法步驟。

  1. 計算整個圖像的梯度值\(I_x,I_y\)
  2. 對於每個像素的\(I_{x^2}=I_xI_x,I_{y^2}=I_yI_y,I_{xy}=I_xI_y\)
  3. 計算每一個像素窗口的和,意思就是對於一個像素,定義一個領域例如5*5,就和之前提及的那樣,然後計算這個鄰域裏面所有第二步計算出來的值的和。\(S_{x^2}=G_{\sigma}*I_{x^2},S_{y^2}=G_{\sigma}*I_{y^2},S_{xy}=G_{\sigma}*I_{xy}\)
  4. 對於每個點\((x,y)\),定義矩陣\(\begin{bmatrix}S_{x^2}&S_{xy}\\S_{xy}&S_{y^2}\end{bmatrix}\)
  5. 對於每個點,計算響應值\(R=Det(H)-k(Trace(H))^2\)
  6. 對\(R\)設定閾值,並且計算非極大值抑制(nonmax suppression, NMS),這個的意思應該就是比如5*5的鄰域內有好幾個點通過了閾值的篩選,那麼選擇最大的那個,抑制其他的點。

一些特性

  • Harris角點響應具有旋轉不變性,因為旋轉不會改變特徵值的大小。
  • Harris角點響應對強度變化具有一定的不變性,縮放或者平移。因為經過縮放或者平移,最大值還是最大值,但是閾值可能要改改。
  • Harris角點響應不對尺度有不變性,改變尺度可能會改變檢測的結果。可能在某一尺度下檢測出為角點,而另一尺度檢測出為邊緣。

參考

  • [1]CSE486 PSU http://www.cse.psu.edu/~rtc12/CSE486/
  • [2]16-385 CMU 5http://www.cs.cmu.edu/~16385/

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

ASP.NET Core Blazor WebAssembly 之 .NET JavaScript互調

Blazor WebAssembly可以在瀏覽器上跑C#代碼,但是很多時候顯然還是需要跟JavaScript打交道。比如操作dom,當然跟angular、vue一樣不提倡直接操作dom;比如瀏覽器的後退導航。反之JavaScript也有可能需要調用C#代碼來實現一些功能,畢竟客戶的需求是千變萬化的,有的時候只能通過一些hack的手段來實現。

.NET調用JavaScript函數

使用JSRuntime.InvokeVoidAsync調用無返回值的JavaScript函數

顯然我們的.NET類庫里不會有JavaScript內置的alert方法來显示提示,這裏演示下如何調用JavaScript的alert方法:

<h3>.net call javascript</h3>

<button @onclick="CallJs">
    Call alert
</button>

@inject IJSRuntime jsRuntime
@code {
    private void CallJs()
    {
        jsRuntime.InvokeVoidAsync("alert", "this message from .net runtime .");
    }
}

使用JSRuntime.InvokeVoidAsync調用具有返回值的JavaScript函數

我們在JavaScript環境定義一個加法函數然後.NET這邊調用拿到結果:

    <script>
        function add(a, b) {
            return a + b;
        }
    </script>

注意:JavaScript代碼要放到wwwroot/index.html頁面上里,不能直接放在組件里。

組件代碼:

<h3>.net call javascript</h3>

sum: @sum

<button @onclick="CallJs">
    Call Add
</button>

@inject IJSRuntime jsRuntime
@code {

    private int sum = 0;

    private async void CallJs()
    {
        sum = await jsRuntime.InvokeAsync<int>("add", sum, 2);
        this.StateHasChanged();
    }
}

運行一下:

JavaScript調用.NET方法

JavaScript調用.NET靜態方法

JavaScript調用.NET靜態方法比較簡單,把靜態方法加上[JSInvokable],然後在JavaScript環境使用DotNet對象直接call就行:
定義.NET靜態方法:


    [JSInvokable]
    public static string GetNow()
    {
        return DateTime.Now.ToString();
    }

使用JavaScript調用GetNow:

  $(document).ready(
            setTimeout(() => {
                $('#btn1').on('click', function () {
                    DotNet.invokeMethodAsync('BlazorWasmComponent', 'GetNow')
                        .then(data => {
                            alert(data);
                        });
                })
            }, 10000)
        ); 

由於Blazor渲染UI結束后按鈕才會插入到dom樹上,所以這裏使用一個傻辦法讓綁定事件的JavaScript代碼置后運行。
運行一下:

JavaScript調用組件里的方法

JavaScript調用組件里的方法比較繞,其實還是通過一個靜態方法作為入口,把實例方法綁定一個靜態delegate,然後讓這個靜態方法去執行delegate。
.NET代碼:

<h3>javascript call .net</h3>

<button id="btn1">
   Js call .net
</button>

@inject IJSRuntime jsRuntime
@code {

    [JSInvokable]
    public static string GetNow()
    {
        return Act("");
    }

    public static Func<string, string> Act;

    protected override void OnInitialized()
    {
        Act = GetNowInInstance;
        base.OnInitialized();
    }

    public string GetNowInInstance(string str)
    {
        return DateTime.Now.ToString();
    }
}

JavaScript代碼:

 $(document).ready(
            setTimeout(() => {
                $('#btn1').on('click', function () {
                    DotNet.invokeMethodAsync('BlazorWasmComponent', 'GetNow')
                        .then(data => {
                            alert(data);
                        });
                })
            }, 10000)
        ); 

運行一下:

調用對象的方法

Blazor還可以把.NET對象(引用)直接傳遞到JavaScript運行時來讓JavaScript直接調用.NET對象的方法。

總的來說大概分4步:

  1. 實例化.net對象
  2. DotNetObjectReference.Create方法把.NET對象包裝
  3. 通過JSRuntime調用一個JavaScript方法把第二步生成的對象傳遞到JavaScript運行時
  4. 在JavaScript側通過invokeMethodAsync方法調用.NET對象里的方法

下面演示下把組件整個實例傳遞出去,然後調用裏面的GetNowInInstance方法。

.net代碼:

<h3>javascript call .net</h3>

<button id="btn1">
    Js call .net
</button>
@implements IDisposable
@inject IJSRuntime jsRuntime
@code {
    IDisposable _objRef;
    protected async override Task OnInitializedAsync()
    {
        _objRef = DotNetObjectReference.Create(this);
        await jsRuntime.InvokeAsync<string>(
            "receiveNetObj",
           _objRef);
        base.OnInitialized();
    }

    [JSInvokable]
    public string GetNowInInstance()
    {
        return DateTime.Now.ToString();
    }

    public void Dispose()
    {
        _objRef?.Dispose();
    }
}

注意:把.NET對象傳遞到JavaScript運行時存在內存泄漏的風險,所以組件需要實現IDisposable接口,在Dispose方法內調用objRef的Dispose方法來釋放內存。

JavaScript代碼:

        var _netObj = null;

        function receiveNetObj(obj) {
            _netObj = obj;
        }

        $(document).ready(
            setTimeout(() => {
                $('#btn1').on('click', function () {
                    _netObj.invokeMethodAsync("GetNowInInstance").then(
                        r => alert(r)
                    );
                })
            }, 10000)
        );

運行一下:

總結

使用JSRuntime可以在.NET里調用JavaScript的方法,這些方法必須是全局的,也就是掛載在window對象上的。
在JavaScript里調用.NET方法主要有兩種:

  1. 通過DotNet方式調用.NET的靜態方法
  2. 把.NET對象直接傳遞到JavaScript運行時來調用對象上的方法

相關內容

ASP.NET Core Blazor Webassembly 之 路由
ASP.NET Core Blazor Webassembly 之 數據綁定
ASP.NET Core Blazor Webassembly 之 組件
ASP.NET Core Blazor 初探之 Blazor WebAssembly
ASP.NET Core Blazor 初探之 Blazor Server

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

【其他文章推薦】

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

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

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

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

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

線程池運用不當的一次線上事故

在高併發、異步化等場景,線程池的運用可以說無處不在。線程池從本質上來講,即通過空間換取時間,因為線程的創建和銷毀都是要消耗資源和時間的,對於大量使用線程的場景,使用池化管理可以延遲線程的銷毀,大大提高單個線程的復用能力,進一步提升整體性能。

今天遇到了一個比較典型的線上問題,剛好和線程池有關,另外涉及到死鎖、jstack命令的使用、JDK不同線程池的適合場景等知識點,同時整個調查思路可以借鑒,特此記錄和分享一下。

01 業務背景描述

該線上問題發生在廣告系統的核心扣費服務,首先簡單交代下大致的業務流程,方便理解問題。

綠框部分即扣費服務在廣告召回扣費流程中所處的位置,簡單理解:當用戶點擊一個廣告后,會從C端發起一次實時扣費請求(CPC,按點擊扣費模式),扣費服務則承接了該動作的核心業務邏輯:包括執行反作弊策略、創建扣費記錄、click日誌埋點等。

02 問題現象和業務影響

12月2號晚上11點左右,我們收到了一個線上告警通知:扣費服務的線程池任務隊列大小遠遠超出了設定閾值,而且隊列大小隨着時間推移還在持續變大。詳細告警內容如下:

相應的,我們的廣告指標:點擊數、收入等也出現了非常明顯的下滑,幾乎同時發出了業務告警通知。其中,點擊數指標對應的曲線表現如下:

該線上故障發生在流量高峰期,持續了將近30分鐘后才恢復正常。

03 問題調查和事故解決過程

下面詳細說下整個事故的調查和分析過程。

第1步:收到線程池任務隊列的告警后,我們第一時間查看了扣費服務各個維度的實時數據:包括服務調用量、超時量、錯誤日誌、JVM監控,均未發現異常。

第2步:然後進一步排查了扣費服務依賴的存儲資源(mysql、redis、mq),外部服務,發現了事故期間存在大量的數據庫慢查詢。

上述慢查詢來自於事故期間一個剛上線的大數據抽取任務,從扣費服務的mysql數據庫中大批量併發抽取數據到hive表。因為扣費流程也涉及到寫mysql,猜測這個時候mysql的所有讀寫性能都受到了影響,果然進一步發現insert操作的耗時也遠遠大於正常時期。

第3步:我們猜測數據庫慢查詢影響了扣費流程的性能,從而造成了任務隊列的積壓,所以決定立馬暫定大數據抽取任務。但是很奇怪:停止抽取任務后,數據庫的insert性能恢復到正常水平了,但是阻塞隊列大小仍然還在持續增大,告警並未消失。

第4步:考慮廣告收入還在持續大幅度下跌,進一步分析代碼需要比較長的時間,所以決定立即重啟服務看看有沒有效果。為了保留事故現場,我們保留了一台服務器未做重啟,只是把這台機器從服務管理平台摘掉了,這樣它不會接收到新的扣費請求。

果然重啟服務的殺手鐧很管用,各項業務指標都恢復正常了,告警也沒有再出現。至此,整個線上故障得到解決,持續了大概30分鐘。

04 問題根本原因的分析過程

下面再詳細說下事故根本原因的分析過程。

第1步:第二天上班后,我們猜測那台保留了事故現場的服務器,隊列中積壓的任務應該都被線程池處理掉了,所以嘗試把這台服務器再次掛載上去驗證下我們的猜測,結果和預期完全相反,積壓的任務仍然都在,而且隨着新請求進來,系統告警立刻再次出現了,所以又馬上把這台服務器摘了下來。

第2步:線程池積壓的幾千個任務,經過1個晚上都沒被線程池處理掉,我們猜測應該存在死鎖情況。所以打算通過jstack命令dump線程快照做下詳細分析。

#找到扣費服務的進程號
$ ps aux|grep "adclick"

# 通過進程號dump線程快照,輸出到文件中
$ jstack pid > /tmp/stack.txth

在jstack的日誌文件中,立馬發現了:用於扣費的業務線程池的所有線程都處於waiting狀態,線程全部卡在了截圖中紅框部分對應的代碼行上,這行代碼調用了countDownLatch的await()方法,即等待計數器變為0后釋放共享鎖。

第3步:找到上述異常后,距離找到根本原因就很接近了,我們回到代碼中繼續調查,首先看了下業務代碼中使用了newFixedThreadPool線程池,核心線程數設置為25。針對newFixedThreadPool,JDK文檔的說明如下:

創建一個可重用固定線程數的線程池,以共享的無界隊列方式來運行這些線程。如果在所有線程處於活躍狀態時提交新任務,則在有可用線程之前,新任務將在隊列中等待。

關於newFixedThreadPool,核心包括兩點:

1、最大線程數 = 核心線程數,當所有核心線程都在處理任務時,新進來的任務會提交到任務隊列中等待;

2、使用了無界隊列:提交給線程池的任務隊列是不限制大小的,如果任務被阻塞或者處理變慢,那麼顯然隊列會越來越大。

所以,進一步結論是:核心線程全部死鎖,新進的任務不對湧入無界隊列,導致任務隊列不斷增加。

第4步:到底是什麼原因導致的死鎖,我們再次回到jstack日誌文件中提示的那行代碼做進一步分析。下面是我簡化過後的示例代碼:

/*** 執行扣費任務 */
public Result<Integer> executeDeduct(ChargeInputDTO chargeInput) {  
    ChargeTask chargeTask = new ChargeTask(chargeInput);  
    bizThreadPool.execute(() -> chargeTaskBll.execute(chargeTask ));  
    return Result.success();
}

/*** 扣費任務的具體業務邏輯 */
public class ChargeTaskBll implements Runnable {  
    public void execute(ChargeTask chargeTask) {     
        // 第一步:參數校驗     
        verifyInputParam(chargeTask);     

        // 第二步:執行反作弊子任務     
        executeUserSpam(SpamHelper.userConfigs);     

        // 第三步:執行扣費     
        handlePay(chargeTask);     

        // 其他步驟:點擊埋點等     ...  
    }
}

/*** 執行反作弊子任務 */
public void executeUserSpam(List<SpamUserConfigDO> configs) {  
    if (CollectionUtils.isEmpty(configs)) {     
        return;  
    }  try {    
        CountDownLatch latch = new CountDownLatch(configs.size());    
        for (SpamUserConfigDO config : configs) {      
           UserSpamTask task = new UserSpamTask(config,latch);      
           bizThreadPool.execute(task);    
        }    
        latch.await();  
    } catch (Exception ex) {    
        logger.error("", ex);  
    }
}

通過上述代碼,大家能否發現死鎖是怎麼發生的呢?根本原因在於:一次扣費行為屬於父任務,同時它又包含了多次子任務:子任務用於并行執行反作弊策略,而父任務和子任務使用的是同一個業務線程池。當線程池中全部都是執行中的父任務時,並且所有父任務都存在子任務未執行完,這樣就會發生死鎖。下面通過1張圖再來直觀地看下死鎖的情況:

假設核心線程數是2,目前正在執行扣費父任務1和2。另外,反作弊子任務1執行完了,反作弊子任務2和4都積壓在任務隊列中等待被調度。因為反作弊子任務2和4沒執行完,所以扣費父任務1和2都不可能執行完成,這樣就發生了死鎖,核心線程永遠不可能釋放,從而造成任務隊列不斷增大,直到程序OOM crash。

死鎖原因清楚后,還有個疑問:上述代碼在線上運行很長時間了,為什麼現在才暴露出問題呢?另外跟數據庫慢查詢到底有沒有直接關聯呢?

暫時我們還沒有復現證實,但是可以推斷出:上述代碼一定存在死鎖的概率,尤其在高併發或者任務處理變慢的情況下,概率會大大增加。數據庫慢查詢應該就是導致此次事故出現的導火索。

05 解決方案

弄清楚根本原因后,最簡單的解決方案就是:增加一個新的業務線程池,用來隔離父子任務,現有的線程池只用來處理扣費任務,新的線程池用來處理反作弊任務。這樣就可以徹底避免死鎖的情況了。

06 問題總結

回顧事故的解決過程以及扣費的技術方案,存在以下幾點待繼續優化:

1、使用固定線程數的線程池存在OOM風險,在阿里巴巴Java開發手冊中也明確指出,而且用的詞是『不允許』使用Executors創建線程池。 而是通過ThreadPoolExecutor去創建,這樣讓寫的同學能更加明確線程池的運行規則和核心參數設置,規避資源耗盡的風險。

2、廣告的扣費場景是一個異步過程,通過線程池或者MQ來實現異步化處理都是可選的方案。另外,極個別的點擊請求丟失不扣費從業務上是允許的,但是大批量的請求丟棄不處理且沒有補償方案是不允許的。後續採用有界隊列后,拒絕策略可以考慮發送MQ做重試處理。— 結束 —

– End –

作者簡介:程序員,985碩士,前亞馬遜Java工程師,現58轉轉技術總監。持續分享技術和管理方向的文章。如果感興趣,可微信掃描下面的二維碼關注我的公眾號:『IT人的職場進階』

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

【其他文章推薦】

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

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

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

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

※回頭車貨運收費標準

解Bug之路-記一次存儲故障的排查過程

解Bug之路-記一次存儲故障的排查過程

高可用真是一絲細節都不得馬虎。平時跑的好好的系統,在相應硬件出現故障時就會引發出潛在的Bug。偏偏這些故障在應用層的表現稀奇古怪,很難讓人聯想到是硬件出了問題,特別是偶發性出現的問題更難排查。今天,筆者就給大家帶來一個存儲偶發性故障的排查過程。

Bug現場

我們的積分應用由於量非常大,所以需要進行分庫分表,所以接入了我們的中間件。一直穩定運行,但應用最近確經常偶發連接建立不上的報錯。報錯如下:

GetConnectionTimeOutException

而筆者中間件這邊收到的確是:

NIOReactor - register err java.nio.channels.CloasedChannelException 

這樣的告警。整個Bug現場如下圖所示:

偶發性錯誤

之前出過類似register err這樣的零星報警,最後原因是安全掃描,並沒有對業務造成任何影響。而這一次,類似的報錯造成了業務的大量連接超時。由於封網,線上中間件和應用已經穩定在線上跑了一個多月,代碼層面沒有任何改動!突然出現的這個錯誤感覺是環境出現了某些問題。而且由於線上的應用和中間件都是集群,出問題時候都不是孤立的機器報錯,沒道理所有機器都正好有問題。如下圖所示:

開始排查是否網絡問題

遇到這種連接超時,筆者最自然的想法當然是網絡出了問題。於是找網工進行排查,
在監控裏面發現網絡一直很穩定。而且如果是網絡出現問題,同一網段的應用應該也都會報錯
才對。事實上只有對應的應用和中間件才報錯,其它的應用依舊穩穩噹噹。

又發生了兩次

就在筆者覺得這個偶發性問題可能不會再出現的時候,又開始抖了。而且是一個下午連抖了兩次。臉被打的啪啪的,算了算了,先重啟吧。重啟中間件后,以為能消停一會,沒想到半個小時之內又報了。看來今天不幹掉這個Bug是下不了班了!

開始排查日誌

事實上,筆者一開始就發現中間件有調用後端數據庫慢SQL的現象,由於比較偶發,所以將這個現象發給DBA之後就沒有繼續跟進,DBA也反饋SQL執行沒有任何異常。筆者開始認真分析日誌之後,發現一旦有 中間件的register err 必定會出現中間件調用後端數據庫的sql read timeout的報錯。
但這兩個報錯完全不是在一個線程裏面的,一個是處理前端的Reactor線程,一個是處理後端SQL的Worker線程,如下圖所示:

這兩個線程是互相獨立的,代碼中並沒有發現任何機制能讓這兩個線程互相影響。難道真是這些機器本身網絡出了問題?前端APP失敗,後端調用DB超時,怎麼看都像網絡的問題!

進一步進行排查

既然有DB(數據庫)超時,筆者就先看看調用哪個DB超時吧,畢竟後面有一堆DB。筆者突然發現,和之前的慢SQL一樣,都是調用第二個數據庫超時,而DBA那邊卻說SQL執行沒有任何異常,

筆者感覺明顯SQL執行有問題,只不過DBA是採樣而且將採樣耗時平均的,偶爾的幾筆耗時並不會在整體SQL的耗時裏面有所體現。

只能靠日誌分析了

既然找不到什麼頭緒,那麼只能從日誌入手,好好分析推理了。REACTOR線程和Worker線程同時報錯,但兩者並無特殊的關聯,說明可能是同一個原因引起的兩種不同現象。筆者在線上報錯日誌裏面進行細細搜索,發現在大量的

NIOReactor-1-RW register err java.nio.channels.CloasedChannelException

日誌中會摻雜着這個報錯:

NIOReactor-1-RW Socket Read timed out
	at XXXXXX . doCommit
	at XXXXXX Socket read timedout

這一看就發現了端倪,Reactor作為一個IO線程,怎麼會有數據庫調用呢?於是翻了翻源碼,原來,我們的中間件在處理commit/rollback這樣的操作時候還是在Reactor線程進行的!很明顯Reactor線程卡主是由於commit慢了!筆者立馬反應過來,而這個commit慢也正是導致了regsiter err以及客戶端無法創建連接的元兇。如下面所示:

由於app1的commit特別慢而卡住了reactor1線程,從而落在reactor1線程上的握手操作都會超時!如下圖所示:

為什麼之前的模擬宕機測試發現不了這一點

因為模擬宕機的時候,在事務開始的第一條SQL就會報錯,而執行SQL都是在Worker線程裏面,
所以並不會觸發reactor線程中commit超時這種現象,所以測試的時候就遺漏了這一點。

為什麼commit會變慢?

系統一直跑的好好的,為什麼突然commit就變慢了呢,而且筆者發現,這個commit變慢所關聯的DB正好也是出現慢SQL的那個DB。於是筆者立馬就去找了DBA,由於我們應用層和數據庫層都沒有commit時間的監控(因為一般都很快,很少出現慢的現象)。DBA在數據庫打的日誌裏面進行了統計,發現確實變慢了,而且變慢的時間和我們應用報錯的時間相符合!
順藤摸瓜,我們又聯繫了SA,發現其中和存儲相關的HBA卡有報錯!如下圖所示:

報錯時間都是一致的!

緊急修復方案

由於是HBA卡報錯了,屬於硬件故障,而硬件故障並不是很快就能進行修復的。所以DBA做了一次緊急的主從切換,進而避免這一問題。

一身冷汗

之前就有慢sql慢慢變多,而後突然數據庫存儲hba卡宕機導致業務不可用的情況。
而這一次到最後主從切換前為止,報錯越來越頻繁,感覺再過一段時間,HBA卡過段時間就完全不可用,重蹈之前的覆轍了!

中間件修復

我們在中間件層面將commit和rollback操作挪到Worker裏面。這樣,commit如果卡住就不再會引起創建連接失敗這種應用報錯了。

總結

由於軟件層面其實是比較信任硬件的,所以在硬件出問題時,就會產生很多詭異的現象,而且和硬件最終的原因在表面上完全產生不了關聯。只有通過抽絲剝繭,慢慢的去探尋現象的本質才會解決最終的問題。要做到高可用真的是要小心評估各種細節,才能讓系統更加健壯!

公眾號

關注筆者公眾號,獲取更多乾貨文章:

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

UniRx精講(一):UniRx簡介&定時功能實現

1.UniRx 簡介

UniRx 是一個 Unity3D 的編程框架。它專註於解決時間上異步的邏輯,使得異步邏輯的實現更加簡潔和優雅。

簡潔優雅如何體現?

比如,實現一個“只處理第一次鼠標點擊事件”這個功能,使用 UniRx 實現如下:

Observable.EveryUpdate()
			.Where(_ => Input.GetMouseButtonUp(0))
			.First()
			.Subscribe(_ => { // do something   });

代碼做的事情很簡單:

  1. 開啟一個 Update 的事件監聽。
  2. 每次 Update 事件被調用時,進行鼠標是否抬起的判斷。
  3. 如果判斷通過,則進行計數,並且只獲取第一次點擊事件。
  4. 訂閱/處理事件。

如果在 Unity 中,使用傳統的方式實現如上功能,首先要創建一個成員變量來記錄點擊次數/是否點擊過,然後在腳本中創建一個 Update 方法來監聽鼠標抬起的事件。

如果在 Update 方法中,除了實現鼠標事件監聽這個功能之外,還要實現其他的功能。那麼 Update 里就會充斥着大量的狀態判斷等邏輯。代碼非常不容易閱讀。

而 UniRx 提供了一種編程思維,使得平時一些比較難以實現的異步邏輯(比如以上這種),使用 UniRx 輕鬆搞定,並且不失代碼的可讀性。

當然 UniRx 的強大不僅僅如此。

它還可以:

  • 優雅實現 MVP(MVC)架構模式。
  • 對 UGUI/Unity API 提供了增強,很多需要寫大量代碼的 UI 邏輯,使用 UniRx 優雅實現。
  • 輕鬆完成非常複雜的異步任務處理。
  • ……

最最重要的是,它可以提高我們的編碼效率,同時還給我們的大腦提供一個強有力的編程模型。而 UniRx 本身的源碼非常值得研究學習,就連大名鼎鼎的 uFrame 框架,在 1.6 版本之後,使用 UniRx 進行了大幅重構,其事件/數據綁定層使用 UniRx 強力驅動。

筆者的 QFramework 也同樣引入了 UniRx,有一大批的框架用戶都是因為支持了 UniRx 慕名而來。

為什麼要用 UniRx ?

UniRx 就是 Unity 版本的 Reactive Extensions,Reactive Extensions 中文意思是:響應式擴展,響應式指的是觀察者和定時器,擴展指的是 LINQ 的操作符。Reactive Extensions 以擅長處理時間上異步的邏輯、以及極簡的 API 風格而聞名。

我們都知道,遊戲很多的系統都是在時間上異步的,所以 Unity 開發者所需要實現的異步邏輯是非常多的。這也是為什麼 Unity 官方在引擎層實現了 Coroutine(協程)這樣的概念。

在遊戲中,像動畫的播放、聲音的播放、網絡請求、資源加載/卸載、Tween 動畫、場景過渡等都是在時間上異步的邏輯。甚至是遊戲循環(Every Update、OnCollisionEnter 等)、傳感器數據(Kinect、Leap Motion、VR Input 等)都是時間上異步的邏輯(事件)。

當我們在項目中實現以上的邏輯的時候,往往使用的方式是用大量的回調實現,最終隨着項目的擴張會導致傳說中的”回調地獄”。

相對較好的方法則是使用消息/事件進行實現,結果導致“消息滿天飛”,導致代碼非常難以閱讀。

以上的任務使用 Coroutine 也是非常不錯的,但是 Coroutine 在 Unity 使用的時候,需要定義一個方法。寫起來是非常面向過程的。當邏輯稍微複雜一點,就很容易造成 Coroutine 嵌套 Coroutine,代碼是非常不容易閱讀的(強耦合)。

而 UniRx 的出現剛好解決了這個問題,它介於回調和事件之間。

它有事件的概念,只不過它的事件是像流水一樣流過來,而我們要做的則是簡單地對這些事件進行組織、變換、過濾、合併就可以得到我們想要的結果了。

它也用掉了回調,只不過它的回調是在事件經過組織之後,只需要調用一次就可以進行事件處理了。

它的原理和 Coroutine (迭代器模式)、LINQ 非常相似,但是比 Coroutine 強大得多。

UniRx 將時間上異步的事件轉化為響應式的事件序列,通過 LINQ操作可以很簡單地組合起來。

為什麼要用 UniRx? 答案就是遊戲本身有大量的在時間上異步的邏輯,而 UniRx 恰好擅長處理這類邏輯,使用 UniRx 可以節省我們的時間,同時讓代碼更簡潔易讀。

Rx 只是一套標準,其他的語言也有實現,如果在 Unity 中熟悉了這套標準,那麼在未來,大家在做別的語言的項目的時候,很容易獲得 Rx 的能力。

專欄內容:

  1. 實踐並講解開發中最常用的 UniRx API。
  2. 對 UniRx 進行一個全方面的簡介。
  3. 在每個階段結束后就會結合所學的知識進行項目實踐。
  4. UniRx 操作符大全。
  5. UniRx 源碼閱讀。
  6. UniRx 背後的設計原理及設計模式學習。
  7. LINQ、Coroutine 底層原理剖析。
  8. BindingsRx、uFrame 源碼閱讀。

OK,讓我們從下一篇開始,感受一下 UniRx 的魅力吧。

知識地圖

2.定時功能實現

在 Unity 開發中,延時是我們經常要實現的功能,這個功能對於有點經驗的開發者來說不難。

最常見的實現方式如下:

using UnityEngine;

public class CommonDelayExample : MonoBehaviour
{
	private float mStartTime;

	void Start()
	{
		mStartTime = Time.time;
	}

	void Update()
	{
		if (Time.time - mStartTime > 5)
		{
			DoSomething();
			// 避免再次執行
			mStartTime = float.MaxValue;
		}
	}

	void DoSomething()
	{
		Debug.Log("DoSomething");
	}
}

這是很多初學者剛入門時候的實現方式,而初學者們隨着深入學習後來接觸到了 Coroutine(協程),使用 Coroutine 實現定時功能會更容易,而且也是更好的選擇,實現如下:

using System;
using System.Collections;
using UnityEngine;

public class CoroutineDelayExample : MonoBehaviour
{
	void Start()
	{
		StartCoroutine(Timer(5, DoSomething));
	}

	IEnumerator Timer(float seconds, Action callback)
	{
		yield return new WaitForSeconds(seconds);
		callback();
	}

	void DoSomething()
	{
		Debug.Log("DoSomething");
	}
}

協程已經把代碼精簡了很多,不過接下來有更厲害的,使用 UniRx。

代碼如下:

Observable.Timer(TimeSpan.FromSeconds(5)).Subscribe(_ => { /* do something */ });

當然以上代碼是沒有和 MonoBehaviour 進行生命周期綁定的,也就是說當 MonoBehaviour 銷毀了之後,這個定時邏輯可能還在執行,這樣就會有造成空指針異常的風險。

要解決也很簡單,代碼如下:

Observable.Timer(TimeSpan.FromSeconds(5))
		.Subscribe(_ => { /* do something */ })
		.AddTo(this);

只要加上一個 AddTo(this) 就可以了。
這樣,當 this(MonoBehaviour) Destroy 的時候,這個延時邏輯也會銷毀掉,從而避免造成空指針異常。

三行代碼,寫下來大約 20 秒的時間,就搞定了一個實現起來比較麻煩的邏輯。

今天的內容就這些。

知識地圖

更多內容

  • QFramework 地址:https://github.com/liangxiegame/QFramework
  • QQ 交流群:623597263
  • 涼鞋的主頁:https://liangxiegame.com/zhuanlan
  • 關注公眾號:liangxiegame 獲取第一時間更新通知及更多的免費內容。

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

【其他文章推薦】

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

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

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

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

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

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