【asp.net core 系列】13 Identity 身份驗證入門

0. 前言

通過前兩篇我們實現了如何在Service層如何訪問數據,以及如何運用簡單的加密算法對數據加密。這一篇我們將探索如何實現asp.net core的身份驗證。

1. 身份驗證

asp.net core的身份驗證有 JwtBearer和Cookie兩種常見的模式,在這一篇我們將啟用Cookie作為身份信息的保存。那麼,我們如何啟用呢?

在Startup.cs 的ConfigureServices(IServiceCollection services) 方法里添加如下:

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
                {
                    Configuration.Bind("CookieSettings",options);
                });

此時可以啟動一個權限驗證,當用戶訪問需要驗證的頁面或接口時,如果沒有登錄,則會自動跳轉到:

https://localhost:5001/Account/Login?ReturnUrl=XXXX

其中ReturnUrl指向來源頁。

1.1 設置驗證

當我們在Startup類里設置啟用了身份驗證后,並不是訪問所有接口都會被跳轉到登錄頁面。那麼如何設置訪問的路徑需要身份驗證呢?asp.net core為我們提供了一個特性類:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class AuthorizeAttribute : Attribute, IAuthorizeData
{
    public string Policy { get; set; }
    public string Roles { get; set; }
    public string AuthenticationSchemes { get; set; }
}

可以看的出,這個特性類允許設置在類、方法上,可以設置多個,允許子類繼承父類的特性。所以可以在控制器上設置[Authorize],當在控制器上設置以後訪問控制器里所有的Action都會要求驗證身份;也可以單獨設置在Action上,表示該Action需要驗證身份,控制器里的其他方法不需要驗證。

1.2 設置忽略

我們在開發過程中,會遇到這樣的一組鏈接或者頁面:請求地址同屬於一個控制器下,但其中某個地址可以不用用戶登錄就可以訪問。通常我們為了減少重複代碼以及復用性等方面的考慮,會直接在控制器上設置身份驗證要求,而不是在控制器里所有的Action上添加驗證要求。

那麼,我們如何放開其中的某個請求,可以允許它不用身份驗證。

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class AllowAnonymousAttribute : Attribute, IAllowAnonymous
{
}

仔細觀察,可以看得出這個特性可以設置在類、方法上,不允許多次設置,允許子類繼承父類的特性。

這個特性的使用沒啥可說的,不過需要注意的是,不要與AuthorizeAttribute一起使用。雖然編譯上沒啥問題,但實際上會對程序員的邏輯照成一定程度的誤導。

2.保存身份

有身份驗證,就必然需要保存身份。當我們從數據庫中或者其他的三方服務中獲取到用戶信息后,我們需要將用戶信息保存起來,而不是每次都向用戶或者服務提供方索求信息。

在asp.net core中,Controller類里有一個屬性:

public HttpContext HttpContext { get; }

HttpContext 提供了一個擴展方法,可以用來保存用戶信息:

public static Task SignInAsync(this HttpContext context, ClaimsPrincipal principal);

暫時忽略這個方法的返回類型,它接受了一個ClaimsPrincipal類型的參數。我們來看下這個類的基本情況吧:

public class ClaimsPrincipal : IPrincipal
{

    public ClaimsPrincipal();
    public ClaimsPrincipal(IEnumerable<ClaimsIdentity> identities);
    public ClaimsPrincipal(BinaryReader reader);
    public ClaimsPrincipal(IIdentity identity);
    public ClaimsPrincipal(IPrincipal principal);
    
    public static ClaimsPrincipal Current { get; }
    public static Func<ClaimsPrincipal> ClaimsPrincipalSelector { get; set; }
    public static Func<IEnumerable<ClaimsIdentity>, ClaimsIdentity> PrimaryIdentitySelector { get; set; }
    public virtual IIdentity Identity { get; }
    public virtual IEnumerable<ClaimsIdentity> Identities { get; }
    public virtual IEnumerable<Claim> Claims { get; }
    public virtual void AddIdentities(IEnumerable<ClaimsIdentity> identities);
    public virtual void AddIdentity(ClaimsIdentity identity);
    public virtual ClaimsPrincipal Clone();
    public virtual IEnumerable<Claim> FindAll(Predicate<Claim> match);
    public virtual IEnumerable<Claim> FindAll(string type);
    public virtual Claim FindFirst(string type);
    public virtual Claim FindFirst(Predicate<Claim> match);
    public virtual bool HasClaim(Predicate<Claim> match);
    public virtual bool HasClaim(string type, string value);
    public virtual bool IsInRole(string role);
    public virtual void WriteTo(BinaryWriter writer);
}

方法和屬性有點多,那麼我們重點關注一下構造函數以及可以AddXXX開頭的方法。

這裡有一個竅門,對於一個陌生的類來說,構造函數對於類本身是個很重要的特徵,我們可以通過構造函數分析出這個類需要哪些基礎數據。

所以,通過簡單的分析,我們需要繼續了解這兩個類:

public class ClaimsIdentity : IIdentity
{
    public ClaimsIdentity();
    public ClaimsIdentity(string authenticationType);
    public ClaimsIdentity(IIdentity identity);
    public ClaimsIdentity(IEnumerable<Claim> claims);
    public ClaimsIdentity(IEnumerable<Claim> claims, string authenticationType);
    public ClaimsIdentity(IIdentity identity, IEnumerable<Claim> claims);
    public ClaimsIdentity(string authenticationType, string nameType, string roleType);
    public ClaimsIdentity(IEnumerable<Claim> claims, string authenticationType, string nameType, string roleType);
    public ClaimsIdentity(IIdentity identity, IEnumerable<Claim> claims, string authenticationType, string nameType, string roleType);
    
}

public class Claim
{
    public Claim(BinaryReader reader);
    public Claim(BinaryReader reader, ClaimsIdentity subject);

    public Claim(string type, string value);
    public Claim(string type, string value, string valueType);
    public Claim(string type, string value, string valueType, string issuer);

    public Claim(string type, string value, string valueType, string issuer, string originalIssuer);
    public Claim(string type, string value, string valueType, string issuer, string originalIssuer, ClaimsIdentity subject);
    protected Claim(Claim other);
    protected Claim(Claim other, ClaimsIdentity subject);
    public string Type { get; }
    public ClaimsIdentity Subject { get; }
    public IDictionary<string, string> Properties { get; }
    public string OriginalIssuer { get; }
    public string Issuer { get; }
    public string ValueType { get; }
    public string Value { get; }
    protected virtual byte[] CustomSerializationData { get; }
    public virtual Claim Clone();
    public virtual Claim Clone(ClaimsIdentity identity);
    public override string ToString();
    public virtual void WriteTo(BinaryWriter writer);
    protected virtual void WriteTo(BinaryWriter writer, byte[] userData);
}

所以,看到這裏就會發現,我們可以通過以下方式保存信息:

List<Claim> claims = null;
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);

HttpContext.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme,new ClaimsPrincipal(identity));

這時候,數據就可以保存在Cookie里了,那麼如何在控制器中獲取到數據呢:

public ClaimsPrincipal User { get; }

在控制器中,提供了這樣一個屬性,當然如果想要正確獲取到值的話,需要在 Startup.cs類中的添加如下配置:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // ……省略其他配置
    app.UseAuthorization();
    app.UseAuthentication();
    // ……省略其他配置
}

3. 總結

在這一篇中,簡單介紹了asp.net core的identity,下一篇將從實際上帶領大家設置不一樣的identity以及Authorize驗證。

更多內容煩請關注我的博客《高先生小屋》

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

【其他文章推薦】

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

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

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

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

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

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

Mysql和Redis數據同步策略

目錄

  • 為什麼對緩存只刪除不更新
  • 先更新數據庫還是先刪除緩存?
  • Cache Aside Pattern
  • Double-Delete
  • Read/Write Through Pattern
  • Write Behind
  • 設置緩存過期時間
  • 總結

為什麼對緩存只刪除不更新

不更新緩存是防止併發更新導致的數據不一致。
所以為了降低數據不一致的概率,不應該更新緩存,而是直接將其刪除,
然後等待下次發生cache miss時再把數據庫中的數據同步到緩存。

先更新數據庫還是先刪除緩存?

有兩個選擇:
1. 先刪除緩存,再更新數據庫
2. 先更新數據庫,再刪除緩存

如果先刪除緩存,有一個明顯的邏輯錯誤:考慮兩個併發操作,線程A刪除緩存后,線程B讀該數據時會發生Cache Miss,然後從數據庫中讀出該數據並同步到緩存中,此時線程A更新了數據庫。
結果導致,緩存中是老數據,數據庫中是新數據,並且之後的讀操作都會直接讀取緩存中的臟數據。(直到key過期被刪除或者被LRU策略踢出)
如果數據庫更新成功后,再刪除緩存,就不會有上面這個問題。
可能是由於數據庫優先,第二種方式也被稱為Cache Aside Pattern。

Cache Aside Pattern

cache aside在絕大多數情況下能做到數據一致性,但是在極端情況仍然存在問題。

  • 首先更新數據庫(A)和刪除緩存(B)不是原子操作,任何在A之後B之前的讀操作,都會讀到redis中的舊數據。
    正常情況下操作緩存的速度會很快,通常是毫秒級,臟數據存在的時間極端。
    但是,對超高併發的應用可能會在意這幾毫秒。
  • 更新完數據庫后,線程意外被kill掉(真的很不幸),由於沒有刪除緩存,緩存中的臟數據會一直存在。
  • 線程A讀數據時cache miss,從Mysql中查詢到數據,還沒來得及同步到redis中,
    此時線程B更新了數據庫並把Redis中的舊值刪除。隨後,線程A把之前查到的數據同步到了Redis。
    顯然,此時redis中的是臟數據。
    通常數據庫讀操作比寫操作快很多,所以除非線程A在同步redis前意外卡住了,否則發生上述情況的概率極低。

雖然以上情況都有可能發生,但是發生的概率相比“先刪除緩存再更新數據庫”會低很多。

Double-Delete

前面我們講到先刪除緩存(A)、后更新數據庫(B)的方案有明顯的錯誤,任何發生在A操作和B操作之間的併發讀都會造成數據的最終不一致。
Double-Delete是一種比較笨拙的修補方案,執行過程如下:

1.delete redis cache
2.update database
3.sleep(500ms)
3.delete redis cache

也很好理解,在睡眠500ms后嘗試再次刪除緩存中的臟數據,它通過兩次刪除來盡可能做到數據的最終一致。
其實,Double-Delete在數據一致性上比Cache Aside更靠譜,但是它的代價是昂貴的,
即使,把睡眠時間縮短到100ms,對耗時敏感的應用也不會考慮這種方案。

Read/Write Through Pattern

cache aside是我們自己的應用程序維護兩個數據存儲系統,而Read/Write Through Pattern是把同步數據的問題交給緩存系統了,應用程序不需要關心。
Read Through是指發生cache miss時,緩存系統自動去數據庫加載數據。
Write Through是指如果cache miss,直接更新數據庫,然後返回,如果cache hit,則更新緩存后,由緩存系統自動同步到數據庫。
以Redis為例,通常我們不會把數據庫的數據全部緩存到redis,而是採用一定的數據精簡或壓縮策略,以節省緩存空間。
就是說,讓緩存系統設計出通用的緩存方案不太現實,不過根據自己的業務定製一個在項目內部通用的中間件是可行的。

Write Behind

Write Behind方案在更新數據時,只更新緩存,不更新數據庫。而是由另外一個服務異步的把數據更新到數據庫。
邏輯上,和Linux中的write back很類似。這個設計的好處是,I/O操作很快,因為是純內存操作。
但是由於異步寫庫,可能要犧牲一些數據一致性,譬如突然宕機會丟失所有未寫入數據庫的內存數據。

阿里巴巴的Canal中間件是一種相反的設計,它先更新mysql,然後通過binlog把數據自動同步到redis。
這種方案會全量同步數據到redis,不適合只緩存熱點數據的應用。

設置緩存過期時間

無論採用哪種策略都應該設置緩存的過期時間。
過期時間設置太長,臟數據存在的時間就越長;
過期時間設置太短,大量的cache miss會讓查詢直接進入數據庫。
所以,需要根據業務場景考慮過期時間。

總結

以上沒有哪種方案是完美的,都無法做到強一致性。
我們總要在性能和數據準確性之間做出妥協。
Cache Aside Pattern適用於絕大多數的場景。

https://www.pixelstech.net/article/1562504974-Consistency-between-Redis-Cache-and-SQL-Database
https://coolshell.cn/articles/17416.html
為什麼不更新緩存,而是直接刪除

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

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

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

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

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

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

※回頭車貨運收費標準

現代化編程模式(1)-快

感謝大家購買我的書

首先十分感謝大家購買我的書,在Tommy和Julisa購買我的書的時候,我承諾會開Sharing給大家。我開Sharing的目標就是讓像Tommy和Julisa這樣的基礎比較低的人都能夠學會編程。所以我就以Tommy和Julisa為目標制定sharing課程,不再按照書籍章節順序一章一章的講。

現代化編程模式核心 – 快

我這本書最值得學習的地方不是.NET,所以這節sharing我故意不用.NET來演示,而是採用JavaScript來演示。

我這本書最值得學習的地方也不是併發。

我這本書最值得學習的地方是“現代化的編程模式”。也就是如下標黃處

 

 

 

現代化編程模式核心只有一個字,就是 – 快!

為了显示出快,我故意挑選了最慢的JavaScript來做這次sharing的演示語言!

現代化編程模式適用於所有現代化的編程語言,包括.NET/(C#和F#),Java,Python,JavaScript,Scala,Clojure,Swift。所以無論是我書中的C#和F#,還是我這次sharing用的JavaScript,都是通用的。最典型的就是本書第6章所講的Rx,它就有多個編程語言的版本:Java,JavaScript,.NET,Scala,Clojure,Swift。以下截圖取自Rx的官網 http://reactivex.io/

 

 

 

快主要來自兩個方面:

  • 程序運行速度快。
  • 編程速度快。

程序運行速度快

從大家的反饋來看,大家不太關注這點,所以我這次sharing先不講,以後再講。

編程速度快

所有技術的進步趨勢都是越來越容易,門檻越來越低,編程也同樣如此!

現在我回首十九年前(2001年)我用C語言寫出第一個程序的時候,當時的編程門檻是相當的高。當時的Turbo C 2.0還是跑在Dos上的,與今天相比真是天壤之別。編程門檻越來越低,編程工具環境越來越完善。這些基礎設施的完善決定了我們編程速度會越來越快。所以大家不需要再對編程帶有恐懼感,覺得編程難!現在編程的難度已經是你讀書時代的幾十分之一了!如果你掌握的是現代化的編程模式而不再是傳統古老的編程模式的話。

在這次sharing我只講大家最容易理解的幾點:

  • 調試診斷快
  • 不用動腦跟着感覺走導致行動快
  • 不用等待他人協作導致自身行動快

調試診斷快

在傳統的編程模式中,我們是這樣編程的:

  1. 啪啪啪敲一堆鍵盤之後,程序寫完了。
  2. 跑起來試一下,oh! No! 出錯了!(耗時幾分到十幾分鐘)
  3. 這時候就加斷點調試,先進一點的就不加斷點,而是看log來診斷。(至少耗時一分鐘起)

大家可以看到,第二步和第三步耗時都是以分鐘為單位的。如果unlucky,第三步可能要按小時甚至天為單位。為了加深大家的印象,sharing的時候我會視時間多少而現場演示一遍這個痛苦的傳統編程模式。

但是現代化的編程模式是以秒為單位的,比以分為單位的傳統編程模式快了一個數量級!!!

Sharing的時候我以JavaScript為編程語言,Visual Studio Code為編程工具,框架選karma + jasmine + AngularJS(我故意沒選最近的Angular而是選AngularJS,也是為了證明現代化編程模式是通用的)來演示現代化編程模式是如何以秒為單位進行調試診斷的。

現代化編程模式以秒為單位進行調試診斷的要點在於:

  1. 每Save一次就會把所有BDD Test cases在一兩秒內跑完!
  2. 基於上一點,也就是說,與傳統的編程模式相比,我馬上就能知道結果,而不是要等幾分鐘。
  3. 基於上一點,就可以每改一點代碼,就馬上Save,同時就馬上知道結果。如果此時發現BDD Test Case變紅了,馬上就知道剛才改的哪一處代碼有錯誤。馬上就能知道原因,馬上就能夠Fix!這裏的每改一點代碼可以理解成:每改一行代碼,每改一個字符,等等。
  4. 因為每次修改代碼的粒度是如此之小,小到每一行和每一個字符,可謂:一步一個腳印。並且每次改動都能保證程序是能BDD Test Case跑通過的,自然也就不需要加斷點調試和看log來診斷了。
  5. 然而人總是會犯錯的,程序出錯時,現代化編程模式不需要通過加斷點調試和看log診斷這種這麼古老的方式,而是通過不斷的加BDD Test Case和縮小Scope來定位問題發生的地方以及去解決。

這部分實操內容也是這次Sharing的大頭。

不用動腦跟着感覺走導致行動快

傳統的編程模式需要程序員去動腦,去思考程序代碼的流程,所以需要寫if/else/switch/for/foreach等語句。然而動腦是很容易累的,同時又是很耗時間的,所以傳統的編程模式自然是快不起來的!

“動腦是不可能動腦的啦,做生意又不會,只能跟着感覺走寫寫代碼才能生活這樣子”

竊-格瓦拉

現代化編程模式改變了思路,不再去思考程序代碼的流程:

 

傳統的編程模式

現代化編程模式

程序員所要做的

需要動腦思考如何做,需要明確的指出每一步該怎麼做。

只需要告訴計算機你的目標即可!

相關編程語句和庫

if/else/switch/for/foreach

LINQ,Reactive Extensions, Promises (不應該再出現if/else/switch/for/foreach)

專業術語

命令式編程

聲明式編程

 

 

 

補充多一句:聲明式編程語言通常用作解決人工智能和約束滿足問題。哈哈,看到這裏大家都懂的啦。

從上面的表格對比中可以看出,“只需要告訴計算機你的目標”的現代化編程模式明顯就比“還需要明確的指出每一步該怎麼做。”的傳統編程模式節省腦子很多!!!

大家可以看看 https://baike.baidu.com/item/%E5%A3%B0%E6%98%8E%E5%BC%8F%E7%BC%96%E7%A8%8B/9939512 來預習一下

 

 

 

不用等待他人協作導致自身行動快

自從進入移動互聯網時代之後,因為客戶端設備的多樣化,前後端分離已經成了大趨勢。同時也帶來了一個大問題:前後端程序員協作的問題!

接着前面“調試診斷快”一節中所share的內容,傳統的編程模式對前後端程序員聯調的依賴性比較高。而現代化編程模式因為可以通過mock的方式來模擬絕大部分後端的響應,前端程序員不再需要等待後端程序員的工作了,從而節約了這部分時間,導致自身在項目進度中更快更有保障。

在這次sharing中我所使用的Karma(BDD框架)和Jasmine(包含了httpbackend mock庫的unit testing框架)在其他編程語言中都有對應的框架和庫,再次體現了現代化編程模式是在各種編程語言中是通用的!!!

總結和動手口訣

在這次sharing之前,我請了兩位老夥計参觀了一下,都得到了好評,希望參加的有經驗的程序員也同樣會一聲驚嘆!

當然,非程序員還是不可能只通過這一次sharing就馬上學會編程的,所以對於你們來說,這次sharing的目的應該是:你能夠識別和分辨出哪些才是編程里正確的道路,從而找到先進的編程資料和避免誤讀落後的編程資料。關於這點,我總結了口訣如下:

  1. 快是王道!
    凡是要一分鐘以上才能看到編程運行結果的都是落後的編程模式,現代化模式是一兩秒就能看到運行結果的。凡是要通過debug和看log這麼耗時的行為,我們都要思考是否通過BDD來減少時間。
  2. 節省腦力是王道!
    現代化編程模式已經可以實現了只需要告訴計算機你的目標即可!如果還需要費腦子去想if/else/switch/for/foreach。那你就落後了。
  3. 能夠獨立完成任務,不依賴別人是王道!
    Mock+BDD已經可以讓你不需要過多等待和依賴其他程序員的工作了。

大家下周sharing見!

風險提示:

我的blog文章和我所翻譯和所寫的書籍不一樣:

    • 沒有像書籍一樣經過三審三校。所以不像書籍一樣嚴謹和全面。
    • 都有當時特定的閱讀對象,然而每篇blog的閱讀對象都不一樣,這點和書籍十分不一樣。所以如果你讀起來覺得怪怪的,那很有可能是你與該篇blog閱讀對象差異很大。
    • 所有文章全部不構成任何投資建議。如因採納這些文章而進行投資所造成的虧損,我不負任何責任。

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

【其他文章推薦】

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

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

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

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

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

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

【原創】Linux中斷子系統(四)-Workqueue

背景

  • Read the fucking source code! –By 魯迅
  • A picture is worth a thousand words. –By 高爾基

說明:

  1. Kernel版本:4.14
  2. ARM64處理器,Contex-A53,雙核
  3. 使用工具:Source Insight 3.5, Visio

1. 概述

  • Workqueue工作隊列是利用內核線程來異步執行工作任務的通用機制;
  • Workqueue工作隊列可以用作中斷處理的Bottom-half機制,利用進程上下文來執行中斷處理中耗時的任務,因此它允許睡眠,而SoftirqTasklet在處理任務時不能睡眠;

來一張概述圖:

  • 在中斷處理過程中,或者其他子系統中,調用workqueue的調度或入隊接口后,通過建立好的鏈接關係圖逐級找到合適的worker,最終完成工作任務的執行;

2. 數據結構

2.1 總覽

此處應有圖:

  • 先看看關鍵的數據結構:
    1. work_struct:工作隊列調度的最小單位,work item
    2. workqueue_struct:工作隊列,work item都掛入到工作隊列中;
    3. workerwork item的處理者,每個worker對應一個內核線程;
    4. worker_poolworker池(內核線程池),是一個共享資源池,提供不同的worker來對work item進行處理;
    5. pool_workqueue:充當橋樑紐帶的作用,用於連接workqueueworker_pool,建立鏈接關係;

下邊看看細節吧:

2.2 work

struct work_struct用來描述work,初始化一個work並添加到工作隊列后,將會將其傳遞到合適的內核線程來進行處理,它是用於調度的最小單位。

關鍵字段描述如下:

struct work_struct {
	atomic_long_t data;     //低比特存放狀態位,高比特存放worker_pool的ID或者pool_workqueue的指針
	struct list_head entry; //用於添加到其他隊列上
	work_func_t func;       //工作任務的處理函數,在內核線程中回調
#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
#endif
};

圖片說明下data字段:

2.3 workqueue

  • 內核中工作隊列分為兩種:

    1. bound:綁定處理器的工作隊列,每個worker創建的內核線程綁定到特定的CPU上運行;
    2. unbound:不綁定處理器的工作隊列,創建的時候需要指定WQ_UNBOUND標誌,內核線程可以在處理器間遷移;
  • 內核默認創建了一些工作隊列(用戶也可以創建):

    1. system_mq:如果work item執行時間較短,使用本隊列,調用schedule[_delayed]_work[_on]()接口就是添加到本隊列中;
    2. system_highpri_mq:高優先級工作隊列,以nice值-20來運行;
    3. system_long_wq:如果work item執行時間較長,使用本隊列;
    4. system_unbound_wq:該工作隊列的內核線程不綁定到特定的處理器上;
    5. system_freezable_wq:該工作隊列用於在Suspend時可凍結的work item
    6. system_power_efficient_wq:該工作隊列用於節能目的而選擇犧牲性能的work item
    7. system_freezable_power_efficient_wq:該工作隊列用於節能或Suspend時可凍結目的的work item

struct workqueue_struct關鍵字段介紹如下:

struct workqueue_struct {
	struct list_head	pwqs;		/* WR: all pwqs of this wq */   //所有的pool_workqueue都添加到本鏈表中
	struct list_head	list;		/* PR: list of all workqueues */    //用於將工作隊列添加到全局鏈表workqueues中

	struct list_head	maydays;	/* MD: pwqs requesting rescue */    //rescue狀態下的pool_workqueue添加到本鏈表中
	struct worker		*rescuer;	/* I: rescue worker */  //rescuer內核線程,用於處理內存緊張時創建工作線程失敗的情況

	struct pool_workqueue	*dfl_pwq;	/* PW: only for unbound wqs */

	char			name[WQ_NAME_LEN]; /* I: workqueue name */

	/* hot fields used during command issue, aligned to cacheline */
	unsigned int		flags ____cacheline_aligned; /* WQ: WQ_* flags */
	struct pool_workqueue __percpu *cpu_pwqs; /* I: per-cpu pwqs */     //Per-CPU都創建pool_workqueue
	struct pool_workqueue __rcu *numa_pwq_tbl[]; /* PWR: unbound pwqs indexed by node */    //Per-Node創建pool_workqueue
    ...
};

2.4 worker

  • 每個worker對應一個內核線程,用於對work item的處理;
  • worker根據工作狀態,可以添加到worker_pool的空閑鏈表或忙碌列表中;
  • worker處於空閑狀態時並接收到工作處理請求,將喚醒內核線程來處理;
  • 內核線程是在每個worker_pool中由一個初始的空閑工作線程創建的,並根據需要動態創建和銷毀;

關鍵字段描述如下:

struct worker {
	/* on idle list while idle, on busy hash table while busy */
	union {
		struct list_head	entry;	/* L: while idle */     //用於添加到worker_pool的空閑鏈表中
		struct hlist_node	hentry;	/* L: while busy */ //用於添加到worker_pool的忙碌列表中
	};

	struct work_struct	*current_work;	/* L: work being processed */   //當前正在處理的work
	work_func_t		current_func;	/* L: current_work's fn */                  //當前正在執行的work回調函數
	struct pool_workqueue	*current_pwq; /* L: current_work's pwq */   //指向當前work所屬的pool_workqueue

	struct list_head	scheduled;	/* L: scheduled works */    //所有被調度執行的work都將添加到該鏈表中

	/* 64 bytes boundary on 64bit, 32 on 32bit */

	struct task_struct	*task;		/* I: worker task */    //指向內核線程
	struct worker_pool	*pool;		/* I: the associated pool */    //該worker所屬的worker_pool
						/* L: for rescuers */
	struct list_head	node;		/* A: anchored at pool->workers */  //添加到worker_pool->workers鏈表中
						/* A: runs through worker->node */
    ...
};

2.5 worker_pool

  • worker_pool是一個資源池,管理多個worker,也就是管理多個內核線程;
  • 針對綁定類型的工作隊列,worker_pool是Per-CPU創建,每個CPU都有兩個worker_pool,對應不同的優先級,nice值分別為0和-20;
  • 針對非綁定類型的工作隊列,worker_pool創建後會添加到unbound_pool_hash哈希表中;
  • worker_pool管理一個空閑鏈表和一個忙碌列表,其中忙碌列表由哈希管理;

關鍵字段描述如下:

struct worker_pool {
	spinlock_t		lock;		/* the pool lock */
	int			cpu;		/* I: the associated cpu */     //綁定到CPU的workqueue,代表CPU ID
	int			node;		/* I: the associated node ID */ //非綁定類型的workqueue,代表內存Node ID
	int			id;		/* I: pool ID */
	unsigned int		flags;		/* X: flags */

	unsigned long		watchdog_ts;	/* L: watchdog timestamp */

	struct list_head	worklist;	/* L: list of pending works */  //pending狀態的work添加到本鏈表
	int			nr_workers;	/* L: total number of workers */    //worker的數量

	/* nr_idle includes the ones off idle_list for rebinding */
	int			nr_idle;	/* L: currently idle ones */

	struct list_head	idle_list;	/* X: list of idle workers */   //處於IDLE狀態的worker添加到本鏈表
	struct timer_list	idle_timer;	/* L: worker idle timeout */
	struct timer_list	mayday_timer;	/* L: SOS timer for workers */

	/* a workers is either on busy_hash or idle_list, or the manager */
	DECLARE_HASHTABLE(busy_hash, BUSY_WORKER_HASH_ORDER);   //工作狀態的worker添加到本哈希表中
						/* L: hash of busy workers */

	/* see manage_workers() for details on the two manager mutexes */
	struct worker		*manager;	/* L: purely informational */
	struct mutex		attach_mutex;	/* attach/detach exclusion */
	struct list_head	workers;	/* A: attached workers */   //worker_pool管理的worker添加到本鏈表中
	struct completion	*detach_completion; /* all workers detached */

	struct ida		worker_ida;	/* worker IDs for task name */

	struct workqueue_attrs	*attrs;		/* I: worker attributes */
	struct hlist_node	hash_node;	/* PL: unbound_pool_hash node */    //用於添加到unbound_pool_hash中
    ...
} ____cacheline_aligned_in_smp;

2.6 pool_workqueue

  • pool_workqueue充當紐帶的作用,用於將workqueueworker_pool關聯起來;

關鍵字段描述如下:

struct pool_workqueue {
	struct worker_pool	*pool;		/* I: the associated pool */    //指向worker_pool
	struct workqueue_struct *wq;		/* I: the owning workqueue */   //指向所屬的workqueue

	int			nr_active;	/* L: nr of active works */     //活躍的work數量
	int			max_active;	/* L: max active works */   //活躍的最大work數量
	struct list_head	delayed_works;	/* L: delayed works */      //延遲執行的work掛入本鏈表
	struct list_head	pwqs_node;	/* WR: node on wq->pwqs */      //用於添加到workqueue鏈表中
	struct list_head	mayday_node;	/* MD: node on wq->maydays */   //用於添加到workqueue鏈表中
    ...
} __aligned(1 << WORK_STRUCT_FLAG_BITS);

2.7 小結

再來張圖,首尾呼應一下:

3. 流程分析

3.1 workqueue子系統初始化

  • workqueue子系統的初始化分成兩步來完成的:workqueue_init_earlyworkqueue_init

3.1.1 workqueue_init_early

  • workqueue子系統早期初始化函數完成的主要工作包括:
    1. 創建pool_workqueue的SLAB緩存,用於動態分配struct pool_workqueue結構;
    2. 為每個CPU都分配兩個worker_pool,其中的nice值分別為0和HIGHPRI_NICE_LEVEL,並且為每個worker_poolworker_pool_idr中分配一個ID號;
    3. 為unbound工作隊列創建默認屬性,struct workqueue_attrs屬性,主要描述內核線程的nice值,以及cpumask值,分別針對優先級以及允許在哪些CPU上執行;
    4. 為系統默認創建幾個工作隊列,這幾個工作隊列的描述在上文的數據結構部分提及過,不再贅述;

從圖中可以看出創建工作隊列的接口為:alloc_workqueue,如下圖:

  • alloc_workqueue完成的主要工作包括:
    1. 首先當然是要分配一個struct workqueue_struct的數據結構,並且對該結構中的字段進行初始化操作;
    2. 前文提到過workqueue最終需要和worker_pool關聯起來,而這個紐帶就是pool_workqueuealloc_and_link_pwqs函數就是完成這個功能:1)如果工作隊列是綁定到CPU上的,則為每個CPU都分配pool_workqueue並且初始化,通過link_pwq將工作隊列與pool_workqueue建立連接;2)如果工作隊列不綁定到CPU上,則按內存節點(NUMA,參考之前內存管理的文章)來分配pool_workqueue,調用get_unbound_pool來實現,它會根據wq屬性先去查找,如果沒有找到相同的就創建一個新的pool_workqueue,並且添加到unbound_pool_hash哈希表中,最後也會調用link_pwq來建立連接;
    3. 創建工作隊列時,如果設置了WQ_MEM_RECLAIM標誌,則會新建rescuer worker,對應rescuer_thread內核線程。當內存緊張時,新創建worker可能會失敗,這時候由rescuer來處理這種情況;
    4. 最終將新建好的工作隊列添加到全局鏈表workqueues中;

3.1.2 workqueue_init

workqueue子系統第二階段的初始化:

  • 主要完成的工作是給之前創建好的worker_pool,添加一個初始的worker
  • create_worker函數中,創建的內核線程名字為kworker/XX:YY或者kworker/uXX:YY,其中XX表示worker_pool的編號,YY表示worker的編號,u表示unbound

workqueue子系統初始化完成后,基本就已經將數據結構的關聯建立好了,當有work來進行調度的時候,就可以進行處理了。

3.2 work調度

3.2.1 schedule_work

schedule_work接口為例進行分析:

  • schedule_work默認是將work添加到系統的system_work工作隊列中;

  • queue_work_on接口中的操作判斷要添加work的標誌位,如果已經置位了WORK_STRUCT_PENDING_BIT,表明已經添加到了隊列中等待執行了,否則,需要調用__queue_work來進行添加。注意了,這個操作是在關中斷的情況下進行的,因為工作隊列使用WORK_STRUCT_PENDING_BIT位來同步work的插入和刪除操作,設置了這個比特后,然後才能執行work,這個過程可能被中斷或搶佔打斷;

  • workqueue的標誌位設置了__WQ_DRAINING,表明工作隊列正在銷毀,所有的work都要處理完,此時不允許再將work添加到隊列中,有一種特殊情況:銷毀過程中,執行work時又觸發了新的work,也就是所謂的chained work

  • 判斷workqueue的類型,如果是bound類型,根據CPU來獲取pool_workqueue,如果是unbound類型,通過node號來獲取pool_workqueue

  • get_work_pool獲取上一次執行workworker_pool,如果本次執行的worker_pool與上次執行的worker_pool不一致,且通過find_worker_executing_work判斷work正在某個worker_pool中的worker中執行,考慮到緩存熱度,放到該worker執行是更合理的選擇,進而根據該worker獲取到pool_workqueue

  • 判斷pool_workqueue活躍的work數量,少於最大限值則將work加入到pool->worklist中,否則加入到pwq->delayed_works鏈表中,如果__need_more_worker判斷沒有worker在執行,則喚醒worker內核線程執行;

  • 總結:

    1. schedule_work完成的工作是將work添加到對應的鏈表中,而在添加的過程中,首先是需要確定pool_workqueue
    2. pool_workqueue對應一個worker_pool,因此確定了pool_workqueue也就確定了worker_pool,進而可以將work添加到工作鏈表中;
    3. pool_workqueue的確定分為三種情況:1)bound類型的工作隊列,直接根據CPU號獲取;2)unbound類型的工作隊列,根據node號獲取,針對unbound類型工作隊列,pool_workqueue的釋放是異步執行的,需要判斷refcnt的計數值,因此在獲取pool_workqueue時可能要多次retry;3)根據緩存熱度,優先選擇正在被執行的worker_pool

3.2.2 worker_thread

work添加到工作隊列后,最終的執行在worker_thread函數中:

  • 在創建worker時,創建內核線程,執行函數為worker_thread

  • worker_thread在開始執行時,設置標誌位PF_WQ_WORKER,調度器在進行調度處理時會對task進行判斷,針對workerqueue worker有特殊處理;

  • worker對應的內核線程,在沒有處理work的時候是睡眠狀態,當被喚醒的時候,跳轉到woke_up開始執行;

  • woke_up之後,如果此時worker是需要銷毀的,那就進行清理工作並返回。否則,離開IDLE狀態,並進入recheck模塊執行;

  • recheck部分,首先判斷是否需要更多的worker來處理,如果沒有任務處理,跳轉到sleep地方進行睡眠。有任務需要處理時,會判斷是否有空閑內核線程以及是否需要動態創建,再清除掉worker的標誌位,然後遍歷工作鏈表,對鏈表中的每個節點調用process_one_worker來處理;

  • sleep部分比較好理解,沒有任務處理時,worker進入空閑狀態,並將當前的內核線程設置成睡眠狀態,讓出CPU;

  • 總結:

    1. 管理worker_pool的內核線程池時,如果有PENDING狀態的work,並且發現沒有正在運行的工作線程(worker_pool->nr_running == 0),喚醒空閑狀態的內核線程,或者動態創建內核線程;
    2. 如果work已經在同一個worker_pool的其他worker中執行,不再對該work進行處理;

work的執行函數為process_one_worker

  • work可能在同一個CPU上不同的worker中運行,直接退出;
  • 調用worker->current_func(),完成最終work的回調函數執行;

3.3 worker動態管理

3.3.1 worker狀態機變換

  • worker_pool通過nr_running字段來在不同的狀態機之間進行切換;
  • worker_pool中有work需要處理時,需要至少保證有一個運行狀態的worker,當nr_running大於1時,將多餘的worker進入IDLE狀態,沒有work需要處理時,所有的worker都會進入IDLE狀態;
  • 執行work時,如果回調函數阻塞運行,那麼會讓worker進入睡眠狀態,此時調度器會進行判斷是否需要喚醒另一個worker
  • IDLE狀態的worker都存放在idle_list鏈表中,如果空閑時間超過了300秒,則會將其進行銷毀;
  1. Running->Suspend
  • worker進入睡眠狀態時,如果該worker_pool沒有其他的worker處於運行狀態,那麼是需要喚醒一個空閑的worker來維持併發處理的能力;
  1. Suspend->Running
  • 睡眠狀態可以通過wake_up_worker來進行喚醒處理,最終判斷如果該worker不在運行狀態,則增加worker_poolnr_running值;

3.3.2 worker的動態添加和刪除

  1. 動態刪除
  • worker_pool初始化時,註冊了timer的回調函數,用於定時對空閑鏈表上的worker進行處理,如果worker太多,且空閑時間太長,超過了5分鐘,那麼就直接進行銷毀處理了;
  1. 動態添加
  • 內核線程執行worker_thread函數時,如果沒有空閑的worker,會調用manage_workers接口來創建更多的worker來處理工作;

參考

Documentation/core-api/workqueue.rst
http://kernel.meizu.com/linux-workqueue.html

洗洗睡了,收工!

歡迎關注公眾號,不定期分享Linux內核機制文章

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

【其他文章推薦】

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

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

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

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

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

【String註解驅動開發】你了解@PostConstruct註解和@PreDestroy註解嗎?

寫在前面

在之前的文章中,我們介紹了如何使用@Bean註解指定初始化和銷毀的方法,小夥伴們可以參見《【Spring註解驅動開發】如何使用@Bean註解指定初始化和銷毀的方法?看這一篇就夠了!!》,也介紹了使用InitializingBean和DisposableBean來處理bean的初始化和銷毀,小夥伴們可以參見《【Spring註解驅動開發】Spring中的InitializingBean和DisposableBean,你真的了解嗎?》。除此之外,在JDK中也提供了兩個註解能夠在bean加載到Spring容器之後執行和在bean銷毀之前執行,今天,我們就一起來看看這兩個註解的用法。

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

@PostConstruct註解

@PostConstruct註解好多人以為是Spring提供的。其實是Java自己的註解。我們來看下@PostConstruct註解的源碼,如下所示。

package javax.annotation;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PostConstruct {
}

從源碼可以看出,@PostConstruct註解是Java中的註解,並不是Spring提供的註解。

@PostConstruct註解被用來修飾一個非靜態的void()方法。被@PostConstruct修飾的方法會在服務器加載Servlet的時候運行,並且只會被服務器執行一次。PostConstruct在構造函數之後執行,init()方法之前執行。

通常我們會是在Spring框架中使用到@PostConstruct註解,該註解的方法在整個Bean初始化中的執行順序:

Constructor(構造方法) -> @Autowired(依賴注入) -> @PostConstruct(註釋的方法)。

@PreDestroy註解

@PreDestroy註解同樣是Java提供的,看下源碼,如下所示。

package javax.annotation;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PreDestroy {
}

被@PreDestroy修飾的方法會在服務器卸載Servlet的時候運行,並且只會被服務器調用一次,類似於Servlet的destroy()方法。被@PreDestroy修飾的方法會在destroy()方法之後運行,在Servlet被徹底卸載之前。執行順序如下所示。

調用destroy()方法->@PreDestroy->destroy()方法->bean銷毀。

總結:@PostConstruct,@PreDestroy是Java規範JSR-250引入的註解,定義了對象的創建和銷毀工作,同一期規範中還有註解@Resource,Spring也支持了這些註解。

案例程序

對@PostConstruct註解和@PreDestroy註解有了簡單的了解之後,接下來,我們就寫一個簡單的程序來加深對這兩個註解的理解。

我們創建一個Cat類,如下所示。

package io.mykit.spring.plugins.register.bean;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

/**
 * @author binghe
 * @version 1.0.0
 * @description 測試@PostConstruct註解和@PreDestroy註解
 */
public class Cat {

    public Cat(){
        System.out.println("Cat類的構造方法...");
    }

    public void init(){
        System.out.println("Cat的init()方法...");
    }

    @PostConstruct
    public void postConstruct(){
        System.out.println("Cat的postConstruct()方法...");
    }

    @PreDestroy
    public void preDestroy(){
        System.out.println("Cat的preDestroy()方法...");
    }

    public void destroy(){
        System.out.println("Cat的destroy()方法...");
    }
}

可以看到,在Cat類中,我們提供了構造方法,init()方法、destroy()方法,使用 @PostConstruct註解標註的postConstruct()方法和只用@PreDestroy註解標註的preDestroy()方法。接下來,我們在AnimalConfig類中使用@Bean註解將Cat類註冊到Spring容器中,如下所示。

@Bean(initMethod = "init", destroyMethod = "destroy")
public Cat cat(){
    return new Cat();
}

接下來,在BeanLifeCircleTest類中新建testBeanLifeCircle04()方法進行測試,如下所示。

@Test
public void testBeanLifeCircle04(){
    //創建IOC容器
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnimalConfig.class);
    //關閉IOC容器
    context.close();
}

運行BeanLifeCircleTest類中的testBeanLifeCircle04()方法,輸出的結果信息如下所示。

Cat類的構造方法...
Cat的postConstruct()方法...
Cat的init()方法...
Cat的preDestroy()方法...
Cat的destroy()方法...

從輸出的結果信息中,可以看出執行的順序是: 構造方法 -> @PostConstruct -> init()方法 -> @PreDestroy -> destroy()方法。

好了,咱們今天就聊到這兒吧!別忘了給個在看和轉發,讓更多的人看到,一起學習一起進步!!

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

寫在最後

如果覺得文章對你有點幫助,請微信搜索並關注「 冰河技術 」微信公眾號,跟冰河學習Spring註解驅動開發。公眾號回復“spring註解”關鍵字,領取Spring註解驅動開發核心知識圖,讓Spring註解驅動開發不再迷茫。

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

【其他文章推薦】

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

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

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

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

※超省錢租車方案

Redis SDS 深入一點,看到更多!

1、什麼是SDS?

Redis 自定的字符串存儲結構,關於redis,你需要了解的幾點!中我們對此有過簡要說明。

Redis 底層是用C語言編寫的,可是在字符存儲上,並未使用C原生的String類型,而是定義了自己的字符串結構 Simple Dynamic Stirng,簡稱SDS。

SDS基本結構如下:

struct sdshdr {
int len; // 記錄buf數組中已使用字節的數量,等於SDS所保存字符串的長度
int free; // 記錄buf數組中未使用字節的數量
char buf[];// 字節數組,用於保存字符串
};

例如,字符串“Redis”存儲示意圖為:

圖1

當前存儲字符串長度為5,未使用長度為0,字節數組存儲的字符為“Redis\0”。

這裏需要注意的是:內部數據數組存儲字符串形式符合C語言要求,以‘\0’結尾。且len字符串長度不包含結尾標識符‘\0’。

buf[]的這種遵循C語言形式的存儲,使得Redis可以直接使用C語言的相關字符串函數進行SDS對象的操作。

二、SDS的優勢

1、O(1)時間複雜度獲取字符串長度

SDS內部維護着一個字符串長度的len變量,可以直接讀取,時間複雜度為O(1)。

對於傳統的C字符串:字符+“\0”,想要獲取字符長度,則需要遍歷整個字符串,直到遇到結束字符,時間複雜度為O(n)。

2、緩衝區溢出規避

所謂緩衝區溢出即所需要的內存超出了實際的內存。因此對於C字符串來說,要特別注意內存分配,回收使用問題。

比如,向一個現有字符串內添加特定字符時,需要保證當前已經分配了這足夠的內存。 

圖2

與C不同的是,SDS的空間預分配策略可以避免緩衝區溢出發生,

當需要對SDS進行操作時,首先會檢查當前空間是否滿足需求,不足則擴展當前分配空間。內存檢查相對於C變成了內部預置操作。

3、減少內存重分配次數

上面我們講到過,C字符操作前都需要進行內存的分配操作,同時,操作完成后,也需要進行相應的內存回收操作。一次操作至少涉及一次內存分配操作。

大家都知道內存的重分配是一個比較複雜且需精細控制的過程,耗時耗資源。針對此弊端,Redis 在SDS內存配置策略上採用了空間預分配+惰性刪除相結合的策略。

a)空間預分配:

空間預分配用於優化SDS字符擴展操作。

所謂預分配,也即是說在一次擴展操作中,擴展的空間大小會大於實際需要的空間大小。
如下,圖1執行圖2操作后SDS變更為:

圖3 

預分配空間的大小基於以下規則計算:

SDS len<1M:分配len長度空間作為預分配空間;

SDS len>=1M:分配1M空間作為預分配空間;

這樣,在下次進行字符操作的時候,如果所需要的空間小於當前SDS free空間,則可以直接行操作,而不需要再執行內存擴展,重分配操作。

SDS的預分配機制使得一次擴展操作所需的內存重分配次數變為<=1。

b)惰性刪除機制

所謂惰性刪除,即調整刪除SDS中部分數據時,不會立刻執行內存重分配,而是會保留空出來內存,並更新內部free屬性。以備將來有字符擴展需求,可以直接使用。

當然,Redis也提供了主動釋放未使用內存的方法。

如下,刪除“ent”之後的SDS結構:

 圖4

SDS的內存分配機制,尤其對於以寫為主的應用場景,能夠提供更加優異的性能表現。

3、二進制安全

C字符串由於特殊的編碼要求只能保存文本數據。

SDS相關的功能方法會以二進制的形式來操作SDS存儲的數據,沒有任何中間操作,存儲最原始的數據,因此不會有字符層面的因素影響。

SDS可以保存任何源的二進制數據,字符、圖片、文件或者序列化的對象等等。

 

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

比亞迪拉丁美洲第一座電動車工廠誕生 投27.1億定位巴西

比亞迪股份有限公司將投資2億巴西雷亞爾(約合新臺幣27.1億元)在巴西投資建廠。這家工廠將落戶聖保羅州坎皮納市(Campinas),是比亞迪在拉丁美洲的第一座工廠。   比亞迪巴西工廠的產品是電動公車、電池和太陽能板。今年1月,比亞迪曾表示其年電動公車最大產能將達到4000輛。其生產的電動公車可能在巴西和拉美地區銷售。

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

【其他文章推薦】

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

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

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

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

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

2014第三屆混合動力技術國際峰會 會後報告

6月26日,以“技術驅動混合動力市場化”為主題的2014第三屆混合動力技術國際峰會開幕。本屆峰會由Merisis Consulting舉辦,超過150位來自政府部門,科研機構,整車,一級和二級的核心零部件供應商的高層代表們齊聚一堂,共議混合動力汽車的技術路線和發展。

乘用車市場方面,目前的市場熱度雖然中等,可以預見在不久以後將會迎來良好的發展勢頭。四部委聯合發佈的《關於繼續開展新能源汽車推廣應用工作的通知》中明確了PHEV的補貼政策,上海廣州北京各地政府也相繼推出了對於PHEV和燃料電池汽車的大力度補貼條例,這將在未來1~2年內對HEV的銷量和市場接受度產生一定的推動力度。而技術相較成熟的商用車市場,大力度的政策補貼,也驅使整車廠們在不斷追求規模的基礎上追求更低成本,最優化的技術。

2014第三屆混合動力技術國際峰會為汽車業界關注混合動力和新能源汽車發展的同仁們提供了一手資訊,説明他們更好的判斷行業未來走向,學習借鑒前沿的經驗與技術,並更好的推動混合動力汽車的研發和市場化進程。

在兩天全程的演講中,嘉賓們從各自領域為在場觀眾分享了行業內前瞻資訊和專業知識。以上總結部分讓筆者印象深刻並值得行業人士深入思考的觀點。

國家發改委耿志成老師在報告中明確國家主要扶持的發展方向有三方面:電動車(包括PHEV),燃料電池汽車,HEV電池控制系統。那麼汽車工業尤其是新能源汽車的未來是靠市場還是靠政策,這一圍繞人們心中許久的問題也許仍然有待時日來解答。

真鋰研究聶昕老師未對目前中國電池行業發展做過於資料性的分析,而是提出未來電池技術的瓶頸將不再是問題。從今年正極材料已經發展到五氟級,以及液體電解質發展到固態電解質,性能大幅度提升等方面可以預見未來無限可能性。同時,也認為目前電池領域存在的主要將圍繞BMS以及一致性問題展開。

第一天上午的小組討論環節中上汽集團商用車技術中心蘭志波總工分享了他個人的獨到觀點,他認為目前HEV存在的成本問題還是基於用戶需求挖掘不夠,用戶並非一味的追求低價格,真正有特點的產品仍然能夠打動消費者。汽車不論技術如何發展,消費者還是講駕駛體驗。

來自吉孚動力的Sven Steinwascher先生分享了混合動力變速箱方面的最新技術挑戰,從客戶接受度到換擋品質,以及變速箱的整合可能性。在演講中他提出了變速箱混合的概念並對其動力總成的佈局進行了詳盡分析。從而最終達到了良好的駕駛性能,經濟型和低成本。

法國Energies Nouvelles的Sébastien MAGAND先生為我們分享了混合動力傳動技術方面的海外領先經驗。IFPEN的傳動系統有11個模式,低於50kg的輕量化設計,有低於300mm長度的無離合器版本,以及模組化的電動汽車整合及比率。優勢在於大量節油,設計輕巧,卓越駕駛性能以及成本最優化。

第二天下午商用車專場中來自五洲龍的全頌華先生細緻深入的為大家講解了公司技術的發展,關鍵零部件的發展以及車配套設施的發展。公司緊跟國家政策,參與了2009年商業示範運行項目並利用2011年世界大學生運動會實現了真正的市場化和批量化。

一位演講者曾經這麼說到,在混合動力越走越看不到前途的過程中,松正的技術突然讓我感覺又有了希望。天津松正的李喬博士在本次論壇上圍繞第IV代插電式深混公車動力系統關鍵技術及應用展開的詳細的解析。觀點很鮮明:技術應以使用者為導向,使用者關心價格又希望避免插電的繁瑣過程。松正的技術可以同時滿足客戶的這兩點需求。

宇通客車主管新能源產品的胡俊傑經理提出混合動力才是未來公交的發展方向。配置可更改、功能可定制的動力系統技術平臺,大力自主研發電機電控電源技術、整車和發動機節能技術和輕量化技術。作為新能源客車推廣應用的領頭企業,他認為制約推廣的瓶頸在於電源和充電技術兩方面,電池的成本和壽命,充電標準、設施、模式包括技術開發。

在聆聽了插電式、混合動力之後,東風揚子江客車雷洪鈞開始了他純電動客車的精彩報告。他認為電動車,混合動力,插電式三者存在都有它的意義,細分市場和技術方向。混合動力中“混合”二字的定義有待深究,混合的普遍解釋是2種以上能源作為動力並存。混合僅僅是油電混合,氣電混合或者也應將燃料汽車加入其中。如果新能源汽車的最終目標是節能減排,那麼他的觀點是減排肯定排第一位,然後才是節能。而我們所談論的重混,中混&微混根本是同一條技術路線,只是發展程度和時間問題。

衷心感謝中國公交資訊網、電動汽車網、高工鋰電網、新華信汽車網等十餘家媒體合作夥伴對論壇進行的全程的報導。我們將在來年呈現更精彩豐富的會議內容,也期待行業內合作夥伴的參與和支持。

更多混合動力行業最新動態、趨勢、資料、人脈資訊,可關注搜索微信服務號“merisis-asia”,掃描以下二維碼關注我們的微信互動平臺

網站:

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

【其他文章推薦】

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

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

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

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

※超省錢租車方案

砸數十億歐元!BMW加碼購三星SDI電池 衝電動車市場

BMW15日表示,計劃為i3電動都市車、插電式混合動力的i8跑車與未來數年新推出的混合動力車款擴大採購SDI電池。三星SDI發表聲明說,與BMW的供應協議價值「數十億歐元」,但未提確切金額。   BMW正推出i8與i3車款來對抗電動車大廠Tesla搶攻豪華車領域,上述交易讓BMW能確保汽車電池的供應來源。對三星SDI來說,BMW的訂單有助擴大汽車電池業務。

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

那些年,我們用過的服務器軟件

引言

看過這部電影的人都老了。。。

很多人都知道我是一名後端開發底層小碼農,平時打交道最多的就是服務器,而關於服務器,又有一堆名詞,看起來好像是一個意思,仔細想想又好像不對。

不信?

先放三個名詞「Web 服務器」、「HTTP 服務器」、「應用服務器」。這三種服務器有誰能現在立刻馬上區分開么。

反正我是區分不開。這個問題要是扔到一堆程序員中間,指不定還能引發一場菜雞爭奪戰。

雖然我不會,不會可以度娘啊,希望經常看我文章同學可以有我這種敢於承認自己菜的精神,沒啥好丟人的,不會可以學,學完了下次就會了,總比不會裝會死鴨子嘴硬,下次還不會要強。

經過我一翻度娘 + 整理后,基本上這三個名詞解釋有了:

「Web 服務器」它一般指的是網站服務器,可以向瀏覽器( PC 端或者移動端)等 Web 客戶端提供服務,供請求數據或者下載數據。

而由於 Web 服務器主要支持的協議就是 HTTP 或者 HTTPS ,所以通常情況下 Web 服務器和 HTTP 服務器是等同的,這兩種服務器之間是可以畫上等號的。

而應用服務器是一個很大的概念,微軟對它的定義是「我們把應用程序服務器定義為:作為服務器執行共享業務應用程序的底層的系統軟件。 就像文件服務器為很多用戶提供文件一樣,應用程序服務器讓多個用戶可以同時使用應用程序(通常是客戶創建的應用程序)」。

講的通俗一點就是一種特定應用的承載容器,一般來講,它需要有運行時環境的支持,比如說在 Java 領域,比較常用的應用服務器 Tomcat ,它就必須要 Java 的環境支持。

站在我的角度上,一名 Java 底層碼農從業人員的角度上來看,「Web 服務器」、「HTTP 服務器」、「應用服務器」之間的界限是非常模糊的,因為同樣一個 Tomcat 服務器,我叫它「Web 服務器」或者「HTTP 服務器」沒有問題,叫它「應用服務器」也沒有半毛錢問題。

常見的「Web 服務器」有 Nginx 、 Apache 、 IIS (這個做 .Net 的同學應該不陌生),常見的「應用服務器」軟件包括 WebLogic、JBoss,前者更輕量級,後者更重量級。

靜態服務和動態服務

接下來科普另一個概念:「靜態服務」和「動態服務」。

「靜態服務」返回的是一些靜態資源,比如圖片、HTML 、 CSS 、 JavaScript 等資源,這些靜態資源有一個顯著的特點是在我們的電腦上,只要路徑寫對了,可以在瀏覽器裏面直接訪問。

比如我在電腦上新建一個文件,把後綴改成 html ,裏面使用 html 隨便寫點內容:

<html>
    <h1>Hello World!</h1>
</html>

靜態服務就是每個人訪問,得到的內容都是一樣的,而動態服務就比較牛逼了,能做到每個人訪問,得到的內容都是不一樣的。

最直接的例子就是經常用的淘寶京東這些網站,登錄以後,訪問自己賬號的個人中心,肯定每個人得到的結果都是不一樣的結果。

還有就是我的博客,最開始我的博客是使用 Hexo 搭建的靜態博客,託管在騰訊雲的文件服務上,後來開了一台雲服務器,就換成了使用 WordPress 構建的動態博客。

Nginx

Nginx 是一款輕量級的 Web 服務器 / 反向代理服務器及电子郵件( IMAP / POP3 )代理服務器。

不查還真不知道,原來 Nginx 還提供了 IMAP / POP3 / SMTP 服務,設置過郵箱客戶端的同學對這三個名詞應該不陌生。

關於 Nginx ,比較令人遺憾的一件事是,它的作者伊戈爾·賽索耶夫進了監獄。

Nginx 的特點是佔有內存少,併發能力強,在同類型的網頁服務器中表現較好,國內比較有名的公司,比如說百度、京東、新浪、網易、騰訊、淘寶等都在使用。

Nginx 現在用途最多的應該是作為反向代理服務器在使用,因為它的特性穩定、佔用系統資源少、併發能力強,一般都直接放在直面用戶的最外層應對用戶流量。

用戶的訪問請求先落到 Nginx 上,由 Nginx 進行代理轉發,負載均衡到後續的 Tomcat 應用服務器上,盡可能的提升系統的穩定性。

至於 Nginx 如何複雜均衡到後面的應用服務器上,這就是另一個問題了, Nginx 有很多種的負載均衡方案,這裏我就不展開介紹了。

Nginx 是一個典型的靜態服務,把圖片等內容放在 Nginx 上,可以通過固定的鏈接直接訪問。

不過現在通過 Lua 的加持,我們也可以在 Nginx 做一些動態服務才能做的事情,這就是大名鼎鼎的 OpenResty 。

至於 Nginx 安裝或者是 OpenResty 的安裝以及簡單的使用,大家可以訪問各自的官網查看,我就不演示介紹了(反正都不難)。

Tomcat

在我的碼農生涯中,使用最多的莫過於 Tomcat ,沒有之一。

Tomcat 啟動成功的話,訪問它的首頁,正常情況下是能看到一隻貓的,雖然這隻貓長得實在是有點抽象,但人家確實是只貓。

Tomcat 是 Apache 軟件基金會( Apache Software Foundation )的 Jakarta 項目中的一個核心項目,由 Apache 、 Sun 和其他一些公司及個人共同開發而成。

Tomcat 服務器是一個免費的開放源代碼的 Web 應用服務器,這也是為什麼它可以風靡全球的重要原因。

由於這個項目有了 Sun 公司的參与和支持,所以 Tomcat 一般都會支持最新版的 Servlet 和 JSP 規範。這也是為什麼 Java 初學者接觸到的第一個 Web 服務總會是 Tomcat 。

但是 Tomcat 並未支持 EJB 和 JMS ,所以說 Tomcat 是一款輕量級的 Web 容器。

GlassFish

Sun 公司為 Java 提供了商業兼容的 Web 容器: Glassfish ,不過說實話,我沒怎麼用過這個容器,這個 Web 容器僅存在於我上大學的時候的課本上以及課後大作業上。

GlassFish 達到產品級質量,可免費用於開發、部署和重新分發。開發者可以免費獲得源代碼,還可以對代碼進行更改。

Glassfish 既是 EJB 容器也是 WEB 容器,它支持最新版的 Java EE 標準。

而剛才前面介紹的 Tomcat 則僅僅只是一個 Web 容器。

Jboss

Jboss 是一個基於 Java EE 的開放源代碼的應用服務器。 JBoss 代碼遵循 LGPL 許可,可以在任何商業應用中免費使用。

Jboss 和上面的 Glassfish 一樣,同樣是企業級的 Web 容器,並且在 2004 年 6 月, JBoss 公司宣布, JBoss 應用服務器通過了 Sun 公司的 J2EE 認證后,一直在緊跟最新的 J2EE 規範,而且在某些技術領域引領 J2EE 規範的開發。

因此,無論在商業領域,還是在開源社區, JBoss 成為了第一個通過 J2EE 1.4 認證的主流應用服務器。 JBoss 應用服務器已經真正發展成企業級應用服務器。

之後好景不長,在 2006 年, JBoss 被 Red Hat 以三億五千萬美金併購。

之後在 2019 年,也就是去年, Red Hat 為 JBoss Application Server 換了一個新的名字,即: WildFly 。

因為 JBoss 本身是開源免費的,而 Red Hat 的企業產品 JBoss EAP 是一個收費產品,Red Hat 為了使這兩個產品差異化,避免用戶混淆,而進行更名。

JBoss 版本:

  • 社區版:JBoss AS(Application Server) -> WildFly
  • 企業版:JBoss EAP(Enterprise Application Server)

JBoss 核心服務不包括支持 servlet / JSP 的 WEB 容器,一般與 Tomcat 綁定使用, JBoss 的 Web 容器使用的是 Tomcat 。

Apache

如果不是 IT 行業,如果說起來 Apache ,是不是大多數人想到的是這個東西:

不過顯然我要說的不是這個,而是這個小羽毛:

說起來慚愧,我在剛入門的很長一段時間中,一直以為 Apache 就是 Tomcat , 傻傻分不清楚。

後來接觸到 PHP 以後,才知道他們倆完全不同, Logo 就不同(這不是廢話)。

Apache 一般是指 Apache HTTP Server,是 Apache 軟件基金會(和 Tomcat 同屬一家基金會,並且 Apache 服務和 Apache 基金會名字都一樣,新人能分清才見鬼了)下的一個網頁服務器軟件。

由於其跨平台和安全性,被廣泛使用,是最流行的 Web 服務器軟件之一。它快速、可靠並且可通過簡單的 API 擴展。

我現在的博客站使用的就是 Apache 的服務,當時搞 WordPress 的時候着實坑了我一把,完全沒想到一個 PHP 環境這麼難搞,後來在網上找問題的搜索的時候才知道有 LAMP 這麼個東西。

  • Linux,操作系統。
  • Apache,網頁服務器。
  • MySQL,數據庫管理系統(或者數據庫服務器)。
  • PHP 、 Perl 或 Python,腳本語言。

不過還可以使用 Nginx 替換 Apache ,這個新的組合叫 LNMP 。

Jetty

Jetty 和 Tomcat 有很多相似之處,比如說可以為 JSP 和 Servlet 提供運行時環境。Jetty 是 Java 語言編寫的,它的 API 以一組 JAR 包的形式發布。

相比較 Tomcat 而言, Jetty 更加的輕量級,因為 Tomcat 除了遵循 Servlet 規範以外,自身還擴展了大量 Java EE 特性以滿足企業級應用的需求。

但對於大量普通的互聯網應用而言,並不需要用到 Tomcat 其他高級特性,所以在這種情況下,使用 Tomcat 是很浪費資源的。

而這時換成 Jetty ,每個應用服務器省下那幾 MB 內存,對於大的分佈式環境則是節省大量資源。

Jetty 可以同時處理大量鏈接並且長時間的保持這些鏈接,例如,一些 Web 聊天應用非常適合用 Jetty 服務器。

Jetty 的架構比較簡單,它有一個基本數據模型,這個數據模型就是 Handler,所有可以被擴展的組件都可以作為一個 Handler,添加到 Server 中,Jetty 就是幫我們管理這些 Handler 的。

Resin

最後一個放出來的是 Resin ,不知道有多少人聽說過這個 Web 容器。

Resin 是 CAUCHO 公司的產品,也是一個 Web 容器,對 Servlet / JSP 提供了良好的支持,性能也比較優良, Resin 自身採用 JAVA 語言開發。

基於百度百科的介紹是說 Resin 是一個非常流行的 Web 容器。

請恕我直言,這個容器真的這麼流行么,如果我不是因為維護一個公司的老系統,還真不知道還有這麼個 Web 容器。

可能 Resin 流行的年代比較久遠了,從我入行以後就不流行了。

其他

再說幾個只聽過沒接觸過的容器:

  • Undertow: JFinal 框架的默認容器切換成了 Undertow 。
  • WebLogic: 甲骨文出品,這個我沒接觸過,不過聽朋友講用這個大多數都是銀行,據說買一買挺貴的。
  • WebSphere: IBM 出品,這個和上面那個 WebLogic 一樣,只聽說過銀行在用。

JFinal 是基於 Java 語言的極速 WEB + ORM 框架,其核心設計目標是開發迅速、代碼量少、學習簡單、功能強大、輕量級、易擴展、Restful。用於一些小項目還是很合適的。

WebLogic 和 WebSphere 一看出品方都挺 NB 的,據說還有後台界面可以操作使用,發布程序都是點鼠標完成的。

不過現在發布程序大多數都開始用 Jenkins 了,其實也方便了很多,包括很多公司可能都上線了 DevOps 系統,程序發布只會越來越簡單。

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

【其他文章推薦】

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

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

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

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

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

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