【原創】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內核機制文章

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

【其他文章推薦】

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

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

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

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

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

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

【其他文章推薦】

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

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

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

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

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

砸數十億歐元!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維修中心

什麼是技術債,為什麼要還技術債?

先說我的結論就是:技術債要還,還不還技術債,決定你所在的公司是不是尊重科學尊重技術,觀點主要有一下三個:

  • 技術債是什麼,對產品和項目有什麼影響
  • 技術債對開發環境和技術氛圍的影響
  • 技術債和技術價值觀

技術棧是什麼,對產品和項目有什麼影響

既然叫技術債,那麼他本質是一種“債”,所以我們先脫離所謂的技術,單獨聊聊什麼是債?債是一個金融上的術語,代表你的負資產,說人話就是代表你欠了別人的錢,在著名美劇《冰與火之歌》裏面的蘭尼斯特家族有一句名言就是:有債必嘗

  1. 那麼生活中有哪些跟債相關的事情呢?我們日常接觸的債有哪些? 花唄,信用卡,透支下個月工資,貸款,高利貸 等等

  2. 債有什麼特點?債本身是一種透支行為,是你犧牲未來滿足自己當下的某種需求,而且所有的債都有一個共同的特點,就是利息,而且跟隨時間~利滾利

  3. 那麼債是怎麼產生的?大家可以想想你什麼時候會用信用卡,用花唄去購物,或者去借高利貸?當你渴望得到某一個東西,但是你本身還不具備購買能力的時候,你會去借債對吧,例如在你財務狀況還很差的情況下,你想買名牌包包,你想買最新款性能最好的蘋果電腦,你想買豪華轎車等等,通過透支未來,來滿足當下的需求,技術債為什麼叫債,就是通過借債,透支系統的擴展性,安全性,來達到快速上線功能目的,借債很容易上癮,為什麼?因為它可以讓你快速滿足慾望(物質,快速變現),嘗到甜頭

  4. 要麼有債要不要還?還債重不重要?:我覺得這其實是看你個人的選擇而已,你借錢也可以不還,可以賴賬,可以能拖一年是一年,甚至也可以忘記你借過的債或者否認它,這可以讓你獲得一些短期利益,讓你嘗到一些甜頭,例如技術上你也可以通過欠債,來快速的實現功能,但是不知道大家是否在意自己的信用和口碑,但在如今的文明社會正在構建就是個人的信用體系,國家徵信中心,支付寶的芝麻信用,微信的支付積分,都是在評價你的個人信用,你的還債的及時和履行契約的能力,最終都是體現在你的個人信用積分上,所以說有債不還也是可以的,這取決與你是否在意你的個人信用和口碑,但如果是一個信用不好的人那麼在一個信用體系如此完善的現代社會裡面是很艱難的,別人不敢跟你做生意,你做什麼時候都必須要先交押金,出行乘坐交通工作,信用好的可以走安全通道,你就必須過安檢和全身掃描,所以在不在債務,還不還債務,其實取決於你想不想做一個講信用的人,做一個用誠信為本去安身立命的人,如果你想做一個誠信為本的人,那麼就要放棄短期利益,把目光放的更加長遠一些,記得我曾經看過一個報道,是講京東創業的故事,京東的企業家劉強東對記者說,如果我們想要賺錢,那麼很簡單,我們有很多捷徑可以走,例如我們不給員工買交社保和五險一金,把大量人員全部轉去外包公司,那麼我們每年營業額馬上就會多十幾個億,可以馬上賺很多的錢,但是他沒有選擇這樣做,這樣通過透支的做事方式確實短期可以獲得一些利益,但是長期來看,你失去的人心,失去了企業的誠信

技術債對開發環境和技術氛圍的影響

產品的迭代就像一個運動員在跑步,汽車在前進,技術債就像運動員消耗的體力,汽車在運行當中所出現的各種問題,沒油,爆胎,熄火等等,還債就是給運動員補水,給汽車加油一樣,是為了可以讓運動員和汽車跑的更遠,不至於因為累積技術債而掛掉,為什麼要重視技術債和細節?因為魔鬼藏在細節當中,再舉幾個和生活息息相關的:

  1. 為什麼我們大樓每天檢修消防和安全設備,為什麼消防要經常做演習?在這些沒有真正產出的事情上耗費精力,難道不能等到真正發生火災發生後去撲滅和搶救嗎 ?
  2. 飛機是在起飛前,為什麼需要做那麼多的安全和檢查措施?確保沒有風險后,然後再執行起飛,難道不能先讓飛機起飛,等到出現問題后再去補救和修復嗎 ?
  3. 為什麼我們提倡每天鍛煉身體,健康飲食?為什麼每年要去醫院體檢?難道不應該等到你的身體已經出現問題,或者發出警報后,你再去看醫生嗎?

說到這裏,技術債的重要性毋庸置疑,重視技術債,就是重視於未然,已最低的成本或者零成本,防止未來的災難發生,還不還技術債很多時候是一種選擇,這些選擇決定了你有沒有預先判斷和解決問題的能力,那麼什麼樣的產品不用還技術債?一次性產品,例如一次性杯子,一次性手套用完就扔掉,所以如果產品長期的可持續的發展,那麼技術債的重要性是毋庸置疑的,對方辯友可能會說我們不是不還技術債,我們只是等做完緊急需求等到空閑時間再還技術債,但是經常做項目的同事應該了解,哪有什麼空閑時間?我們在項目衝刺的時候怎麼可能還會有空閑時間,大部分時間所謂的稍後處理,其實就是不處理,屬於掩耳盜鈴,當技術債被遺忘后就成為項目的定時炸彈埋在那裡了,而且技術債的特性前面也說了,所謂的稍後處理,就是讓它利滾利,拖延時間越長,還債的成本越高,而且人們還債的意願就越低,誰也不敢去碰它,例如,你身體出現問題,你不去看醫院檢查和修復問題,而是一直繼續使用和消耗你的身體,拖到最後實在不能動的,你沒辦法去醫院一查,癌症晚期,那時候神仙也沒救了, 而且技術債不單單是技術債,它就像一個垃圾堆一樣,久而久之不處理,慢慢周圍就會產生更多的垃圾,因此產生的“破窗效應”更加是會對未來的項目環境造成很大的影響,大家也會逐漸喪失維護環境的信心,所以我們在討論技術債的時候不僅僅是討論技術債本身,技術債對團隊追求質量的信心,對大家維護環境整潔的积極性都會造成很大的影響,所以我方觀點是,技術債,有債必嘗,越拖成本越高,最好是在發現的時候馬上處理它,不要讓乾淨的房間出現垃圾堆,只有在乾淨的環境下大家才能持續的高效的去創造,一個需求捏着鼻子做,兩個需求捏着鼻子做,久而久之代碼中就散發出臭味,對於大家的工作體驗和項目質量都會產生巨大的影響,如果連工作都不開心,那還談什麼夢想?沒有良好的技術環境企業就無法吸收和留住高質量的技術人才,人才是現代企業的核心競爭力,沒有人才的企業在瞬息萬變的市場上是難以做出快速反應的

技術債和技術價值觀

不重視技術債就是不重視技術,不尊重科學發展,不能客觀的認識和理解技術的複雜性和軟件工程帶來的價值和意義,我們國家近幾年就因為不重視技術吃了不少虧,比如去年的中興通訊公司被制裁,因為沒有自己的技術,芯片被斷供製裁后卻毫無還手之力,國產目前的大多手機廠商看似繁榮,但手機行業的 8,9 成利潤被都被掌握技術的蘋果公司賺走,打開現在的智能手機裏面你會看到,美國的芯片和谷歌的安卓操作系統,日本的鏡頭和相機模組,三星的屏幕,還要在微薄的利潤上繳納高通的芯片稅,實際上國內大多廠商做的都是代加工和組裝的臟活累活,沒有技術的公司,就會受制於人,不僅賺不到錢,而且公司的命運都是由掌握核心技術的公司決定,再比如一個近期的新聞,哈工大的建模軟件被斷供等等例子,不勝枚舉,那麼技術有多重要?我們就用華為來舉例,華為為什麼是一家值得尊重的科技公司,因為他打破了中國自從第二次工業革命以來,但是因為長期技術落後長期受制於人的客觀事實,中國以前的代號叫做世界工廠,只適合做一些勞動密集型產業,但華為讓中國企業在先進的技術領域,同樣是被美國制裁,為什麼華為活的比中興好很多?因為華為重視技術,從海思芯片到5G 再到操作系統,自己擁有產業供應鏈,有自己的的核心技術,才能掌握自己的命運,而且在取得商業上的成功后,也得到的大家的尊重,相同還有最近處於風口的台灣的芯片製造廠商台積電公司,全球唯二掌握 7納米芯片製造技術的芯片公司,因為自主的核心技術在擁有可以在國際上和英特爾平起平坐資本,綜上所述,不重視技術雖然也可以生存,但是重視技術,掌握核心技術,才能走的更遠,我們都知道技術的目的是要體現商業價值,但前提是要擁有核心技術才配擁有商業價值,沒有技術壁壘的企業和人隨時都可能被人替換,而且幾乎沒有什麼成本,重視技術公司才能發展的更遠,才不會受制於人,才能成為頭部玩家,收割行業90%的利潤,才有可能成為一家偉大並且受人尊重的公司,不然你去想想蘋果公司為什麼不放棄技術,微軟和谷歌為什麼不放棄技術,英特爾和高通為什麼不放棄技術,技術很重要,可以讓個人和企業提升競爭力,不容易被淘汰,對於國家和社會,二次工業革命以來,技術改變了我們的生產效率,從而改變我們社會的運行方式,技術幫助解決了困擾我們幾千年的《馬爾薩斯陷阱》,我們國家經歷過近代史的幾百年技術落後的屈辱后,更加的尤為重視技術,我們在1960 年代大家都吃不飽的情況下我們就研發出自己的原子彈,我們國家級的戰略目標《中國製造 2025》就包含的“芯片,人工智能,區塊鏈,機器人,新能源”等等高精尖產業,目的就是讓我們脫離低端製造業,脫離勞動密集型產業,因為沒有技術含量的重複性的勞動工作未來都將被機器和 人工智能 取代,在未來很難被取代就是人類特有的豐富的想象力和創造力。

最後我想再引用 一個真實的故事,是來源於 NASA 的著名文章《為什麼要探索太空?》,文章的背景是來源於 1970年,贊比亞修女 Mary Jucunda 給 NASA 科學家 Ernst Stuhlinger 博士寫了一封信,信中,Mary Jucunda 修女問道:目前地球上還有這麼多小孩子吃不上飯,他怎麼能捨得為遠在火星的項目花費數十億美元。Ernst Stuhlinger 在回信中寫到一個真實的故事如下:

那是在400年前,德國某小鎮里有一位伯爵。他是個心地善良的人,他將自己收入的一大部分捐給了鎮子上的窮人。這十分令人欽佩,因為中世紀時窮人很多,而且那時經常爆發席捲全國的瘟疫。一天,伯爵碰到了一個奇怪的人,他家中有一個工作台和一個小實驗室,他白天賣力工作,每天晚上的幾小時的時間專心進行研究。他把小玻璃片研磨成鏡片,然後把研磨好的鏡片裝到鏡筒里,用此來觀察細小的物件。伯爵被這個前所未見的可以把東西放大觀察的小發明迷住了。他邀請這個怪人住到了他的城堡里,作為伯爵的門客,此後他可以專心投入所有的時間來研究這些光學器件。然而,鎮子上的人得知伯爵在這麼一個怪人和他那些無用的玩意兒上花費金錢之後,都很生氣。“我們還在受瘟疫的苦,”他們抱怨道,“而他卻為那個閑人和他沒用的愛好亂花錢!”伯爵聽到后不為所動。“我會盡可能地接濟大家,”他表示,“但我會繼續資助這個人和他的工作,我確信終有一天會有回報。”果不其然,他的工作(以及同時期其他人的努力)贏來了豐厚的回報:顯微鏡。顯微鏡的發明給醫學帶來了前所未有的發展,由此展開的研究及其成果,消除了世界上大部分地區肆虐的瘟疫和其他一些傳染性疾病。伯爵為支持這項研究發明所花費的金錢,其最終結果大大減輕了人類所遭受的苦難,這回報遠遠超過單純將這些錢用來救濟那些遭受瘟疫的人。

綜上所述,重視技術債就是重視技術,重視技術就是重視細節和未來,魔鬼存在細節當中,細節決定成敗

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

程序員過關斬將–分佈式系統消息異常該何去何從

異步處理模型

一旦談到分佈式,微服務等這些具有很高逼格的代名詞,總能讓你在面試中脫穎而出,不是因為這些詞的英文翻譯的好,而是現代互聯網乃至企業級開發確實在分佈式,微服務等模式下取得了良好的架構效果。無論是微服務,還是之前的SOA,總是離不開異步處理模型,小到程序中IO的處理,大到系統間的消息交互,處處都有異步的身影。

談到系統之間的消息異步處理,就不能不談消息隊列(MQ),目前業界比較流行的MQ類型請自行百度腦補。但是需要提醒一下,MQ只是實現數據異步處理的一種解決方案,沒有MQ能不能實現異步處理呢?當然能,最簡單粗暴的莫過於採用數據庫方式,消息生產者直接把數據插入數據庫,消費者採用讀取數據庫的方式來獲取數據,所以MQ並不等於異步處理哦。

異步消息處理最大程度上解耦了各個系統,為每個系統獨立擴展提供了更大空間,但是異步消息處理也同時面臨着一些挑戰,比如:消息管道性能,消息管道的高可用等,其中最貼近業務層的可能非“數據異常處理”莫屬,基本上可認為這是數據處理模型的最末端,數據流向的尾部,但往往卻是業務中比較重要的部分。

如果一條異步消息作為一個分佈式事務中的一環,那還設計到消息處理結果的反饋,分佈式協調器會根據消息結果的成功與否來決定的事務的結果。

單就異步消息消費端來講,根據不同的業務場景就有以下幾種異常處理方案

直接忽略

這是所有異常數據處理方案中最粗暴,同時也是最簡單的一種:發生異常的時候,直接忽略,什麼都不做。

面對異常不採取任何措施,乍一聽這可能是個很糟糕的方案,但是在實際業務中,這可能是完全可以接受的。如果因為錯誤導致的損失很小,甚至可以忽略,但是建立一套錯誤糾正機制的成本遠遠高於忽略異常,這種場景下選擇直接忽略往往是一種更優的方案。而且當錯誤糾正機制設計到需要人工介入操作的時候,代價會更高,而且還會引入影響其他業務的可能,更為可怕的是如果錯誤糾正機制本身出現問題,那代價更是…..

舉個很簡單的栗子:像一些登錄日誌的統計操作,如果處理某個人登錄的數據出現異常,往往會選擇直接忽略。因為統計這種業務本身就帶有數據的容錯機制,100000和100001在統計需求看來沒有什麼區別。

重試

當直接忽略的方案不可行的時候,你可能需要重試的操作。如果在重試的情況下有足夠高的成功率,那重試就是合理的選擇。重試雖然可以改正間接性的錯誤,但是它對那些違反業務規則,違反數據模型的數據無能為力。

在最理想的情況下,如果重試操作是冪等性的,什麼叫冪等性能(自己去百度吧)?事情就會簡單很多,重試操作可以放心大膽的去實施。但是在重試操作經過一段時間或者一定次數之後還未成功的話,多數情況下可能需要有一定的後續策略,比如:重試10次之後如果還是失敗,則放棄。

補償

這個策略是分佈式事務中經常用到的,與其說是補償,不如說是回滾操作。特別是在程序接收到數據會有一系列的操作的情景下,補償操作類似於事務回滾的概念,讓系統回到發生這一系列操作之前的狀態。這種補償的機制非常適合於那種有“事務”需求的場景。

你的業務中有哪些“事務”的需求場景呢?歡迎在留言中體現,另外再稍微提一下,每周送架構書籍的活動仍然在進行哦,歡迎關注

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

【其他文章推薦】

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

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

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

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

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

恕我直言你可能真的不會java第6篇:Stream性能差?不要人云亦云

一、粉絲的反饋

問:stream比for循環慢5倍,用這個是為了啥?
答:互聯網是一個新聞泛濫的時代,三人成虎,以假亂真的事情時候發生。作為一個技術開發者,要自己去動手去做,不要人云亦云。

的確,這位粉絲說的這篇文章我也看過,我就不貼地址了,也沒必要給他帶流量。怎麼說呢?就是一個不懂得測試的、不入流開發工程師做的性能測試,給出了一個危言聳聽的結論。

二、所有性能測試結論都是片面的

性能測試是必要的,但針對性能測試的結果,永遠要持懷疑態度。為什麼這麼說?

  • 性能測試脫離業務場景就是片面的性能測試。你能覆蓋所有的業務場景么?
  • 性能測試脫離硬件環境就是片面的性能測試。你能覆蓋所有的硬件環境么?
  • 性能測試脫離開發人員的知識面就是片面的性能測試。你能覆蓋各種開發人員奇奇怪怪的代碼么?

所以,我從來不相信網上的任何性能測試的文章。凡是我自己的從事的業務場景,我都要在接近生產環境的機器上自己測試一遍。 所有性能測試結論都是片面的,只有你生產環境下的運行結果才是真的。

三、動手測試Stream的性能

3.1.環境

windows10 、16G內存、i7-7700HQ 2.8HZ 、64位操作系統、JDK 1.8.0_171

3.2.測試用例與測試結論

我們在上一節,已經講過:

  • 針對不同的數據結構,Stream流的執行效率是不一樣的
  • 針對不同的數據源,Stream流的執行效率也是不一樣的

所以記住筆者的話:所有性能測試結論都是片面的,你要自己動手做,相信你自己的代碼和你的環境下的測試!我的測試結果僅僅代表我自己的測試用例和測試數據結構!

3.2.1.測試用例一

測試用例:5億個int隨機數,求最小值
測試結論(測試代碼見後文):

  • 使用普通for循環,執行效率是Stream串行流的2倍。也就是說普通for循環性能更好。
  • Stream并行流計算是普通for循環執行效率的4-5倍。
  • Stream并行流計算 > 普通for循環 > Stream串行流計算

3.2.測試用例二

測試用例:長度為10的1000000隨機字符串,求最小值
測試結論(測試代碼見後文):

  • 普通for循環執行效率與Stream串行流不相上下
  • Stream并行流的執行效率遠高於普通for循環
  • Stream并行流計算 > 普通for循環 = Stream串行流計算

3.3.測試用例三

測試用例:10個用戶,每人200個訂單。按用戶統計訂單的總價。
測試結論(測試代碼見後文):

  • Stream并行流的執行效率遠高於普通for循環
  • Stream串行流的執行效率大於等於普通for循環
  • Stream并行流計算 > Stream串行流計算 >= 普通for循環

四、最終測試結論

  • 對於簡單的数字(list-Int)遍歷,普通for循環效率的確比Stream串行流執行效率高(1.5-2.5倍)。但是Stream流可以利用并行執行的方式發揮CPU的多核優勢,因此并行流計算執行效率高於for循環。
  • 對於list-Object類型的數據遍歷,普通for循環和Stream串行流比也沒有任何優勢可言,更不用提Stream并行流計算。

雖然在不同的場景、不同的數據結構、不同的硬件環境下。Stream流與for循環性能測試結果差異較大,甚至發生逆轉。但是總體上而言

  • Stream并行流計算 >> 普通for循環 ~= Stream串行流計算 (之所以用兩個大於號,你細品)
  • 數據容量越大,Stream流的執行效率越高。
  • Stream并行流計算通常能夠比較好的利用CPU的多核優勢。CPU核心越多,Stream并行流計算效率越高。

stream比for循環慢5倍?也許吧,單核CPU、串行Stream的int類型數據遍歷?我沒試過這種場景,但是我知道這不是應用系統的核心場景。看了十幾篇測試博文,和我的測試結果。我的結論是: 在大多數的核心業務場景下及常用數據結構下,Stream的執行效率比for循環更高。 畢竟我們的業務中通常是實實在在的實體對象,沒事誰總對List<Int>類型進行遍歷?誰的生產服務器是單核?。

五、測試代碼

<dependency>
    <groupId>com.github.houbb</groupId>
    <artifactId>junitperf</artifactId>
    <version>2.0.0</version>
</dependency>

測試用例一:

import com.github.houbb.junitperf.core.annotation.JunitPerfConfig;
import com.github.houbb.junitperf.core.report.impl.HtmlReporter;
import org.junit.jupiter.api.BeforeAll;

import java.util.Arrays;
import java.util.Random;

public class StreamIntTest {

    public static int[] arr;

    @BeforeAll
    public static void init() {
        arr = new int[500000000];  //5億個隨機Int
        randomInt(arr);
    }

    @JunitPerfConfig( warmUp = 1000, reporter = {HtmlReporter.class})
    public void testIntFor() {
        minIntFor(arr);
    }

    @JunitPerfConfig( warmUp = 1000, reporter = {HtmlReporter.class})
    public void testIntParallelStream() {
        minIntParallelStream(arr);
    }

    @JunitPerfConfig( warmUp = 1000, reporter = {HtmlReporter.class})
    public void testIntStream() {
        minIntStream(arr);
    }

    private int minIntStream(int[] arr) {
        return Arrays.stream(arr).min().getAsInt();
    }

    private int minIntParallelStream(int[] arr) {
        return Arrays.stream(arr).parallel().min().getAsInt();
    }

    private int minIntFor(int[] arr) {
        int min = Integer.MAX_VALUE;
        for (int anArr : arr) {
            if (anArr < min) {
                min = anArr;
            }
        }
        return min;
    }

    private static void randomInt(int[] arr) {
        Random r = new Random();
        for (int i = 0; i < arr.length; i++) {
            arr[i] = r.nextInt();
        }
    }
}

測試用例二:

import com.github.houbb.junitperf.core.annotation.JunitPerfConfig;
import com.github.houbb.junitperf.core.report.impl.HtmlReporter;
import org.junit.jupiter.api.BeforeAll;

import java.util.ArrayList;
import java.util.Random;

public class StreamStringTest {

    public static ArrayList<String> list;

    @BeforeAll
    public static void init() {
        list = randomStringList(1000000);
    }

    @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
    public void testMinStringForLoop(){
        String minStr = null;
        boolean first = true;
        for(String str : list){
            if(first){
                first = false;
                minStr = str;
            }
            if(minStr.compareTo(str)>0){
                minStr = str;
            }
        }
    }

    @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
    public void textMinStringStream(){
        list.stream().min(String::compareTo).get();
    }

    @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
    public void testMinStringParallelStream(){
        list.stream().parallel().min(String::compareTo).get();
    }

    private static ArrayList<String> randomStringList(int listLength){
        ArrayList<String> list = new ArrayList<>(listLength);
        Random rand = new Random();
        int strLength = 10;
        StringBuilder buf = new StringBuilder(strLength);
        for(int i=0; i<listLength; i++){
            buf.delete(0, buf.length());
            for(int j=0; j<strLength; j++){
                buf.append((char)('a'+ rand.nextInt(26)));
            }
            list.add(buf.toString());
        }
        return list;
    }
}

測試用例三:

import com.github.houbb.junitperf.core.annotation.JunitPerfConfig;
import com.github.houbb.junitperf.core.report.impl.HtmlReporter;
import org.junit.jupiter.api.BeforeAll;

import java.util.*;
import java.util.stream.Collectors;

public class StreamObjectTest {

    public static List<Order> orders;

    @BeforeAll
    public static void init() {
        orders = Order.genOrders(10);
    }

    @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
    public void testSumOrderForLoop(){
        Map<String, Double> map = new HashMap<>();
        for(Order od : orders){
            String userName = od.getUserName();
            Double v; 
            if((v=map.get(userName)) != null){
                map.put(userName, v+od.getPrice());
            }else{
                map.put(userName, od.getPrice());
            }
        }

    }

    @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
    public void testSumOrderStream(){
        orders.stream().collect(
                Collectors.groupingBy(Order::getUserName, 
                        Collectors.summingDouble(Order::getPrice)));
    }

    @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
    public void testSumOrderParallelStream(){
        orders.parallelStream().collect(
                Collectors.groupingBy(Order::getUserName, 
                        Collectors.summingDouble(Order::getPrice)));
    }
}


class Order{
    private String userName;
    private double price;
    private long timestamp;
    public Order(String userName, double price, long timestamp) {
        this.userName = userName;
        this.price = price;
        this.timestamp = timestamp;
    }
    public String getUserName() {
        return userName;
    }
    public double getPrice() {
        return price;
    }
    public long getTimestamp() {
        return timestamp;
    }

    public static List<Order> genOrders(int listLength){
        ArrayList<Order> list = new ArrayList<>(listLength);
        Random rand = new Random();
        int users = listLength/200;// 200 orders per user
        users = users==0 ? listLength : users;
        ArrayList<String> userNames = new ArrayList<>(users);
        for(int i=0; i<users; i++){
            userNames.add(UUID.randomUUID().toString());
        }
        for(int i=0; i<listLength; i++){
            double price = rand.nextInt(1000);
            String userName = userNames.get(rand.nextInt(users));
            list.add(new Order(userName, price, System.nanoTime()));
        }
        return list;
    }
    @Override
    public String toString(){
        return userName + "::" + price;
    }
}

歡迎關注我的博客,裏面有很多精品合集

  • 本文轉載註明出處(必須帶連接,不能只轉文字):字母哥博客。

覺得對您有幫助的話,幫我點贊、分享!您的支持是我不竭的創作動力! 。另外,筆者最近一段時間輸出了如下的精品內容,期待您的關注。

  • 《手摸手教你學Spring Boot2.0》
  • 《Spring Security-JWT-OAuth2一本通》
  • 《實戰前後端分離RBAC權限管理系統》
  • 《實戰SpringCloud微服務從青銅到王者》
  • 《VUE深入淺出系列》

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

【其他文章推薦】

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

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

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

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

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

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

蒲公英 · JELLY技術周刊 Vol.12 尤雨溪新作 Vite, 你會支持么?

「蒲公英」期刊,每周更新,我們專註於挖掘「基礎技術工程化跨端框架技術圖形編程服務端開發桌面開發人工智能」等多個大方向的業界熱點,並加以專業的解讀;不僅如此,我們還精選凹凸技術文章,向大家呈現團隊內的研究技術方向。

抬頭仰望,蒲公英的種子會生根發芽,如夏花絢爛;格物致知,我們登高遠眺、滄海拾遺,以求積硅步而至千里。

登高遠眺

天高地迥,覺宇宙之無窮

前端框架

Vue3 Composition API 提案

Vue3 其中一個重量級的特性就是 Composition API,它能幫助我們更好地組織代碼。本網頁是 Composition API 的草案,詳細介紹了 Composition API 的設計動機、設計細節、具體 API 用法等。文章篇幅較長,推薦找一個悠閑的周末,泡上咖啡,帶上耳機,細細品讀一番。

工具測評: React Hook Form VS Formik

使用 React 構建表單是一件痛苦的事情,官方推薦了 Formik。本文對使用 Formik 和 React Hook Form 構建表單進行了比較,得出 React Hook Form 比 Formik 更易用、更高效的結論。如果你正巧在這方面有困惑,可實踐嘗試體驗。

Quark-h5 — 從零開始的可視化編輯器

想必你一定使用微場景生成工具製作過炫酷的 h5 頁面,除了感嘆其神奇之處有沒有想過其實現方式呢?本文從零開始實現一個 H5 編輯器項目完整設計思路和主要實現步驟,並開源前後端代碼。有需要的小夥伴可以按照該教程從零實現自己的H5編輯器。

圖形編程

初探虛幻引擎5

5月底, 遊戲公司Epic揭開了虛幻5引擎神秘的面紗,此次更新包含 Nanite虛擬微多邊形 和 全新的動態全局光照Lumen 兩大核心技術,然後展示了該引擎運行在PS5上實時渲染效果,其逼真的光照和媲美電影的細節震驚了整個行業。

工程化

Vite — 入門到實戰

Vite 是 Vue 技術生態新推出的開發工具,針對 Vue 應用的無打包開發服務器,開發者無需藉助 webpack 等打包工具,即可直接在瀏覽器中預覽 Vue 項目。Vite 的原理與本技術周刊之前介紹過的 snowpack 有着異曲同工之處,Vite 本身也表示一部分靈感來自 snowpack 項目。本文從 0 開始一步一步實現了一個簡易版本的 Vite 來講解 Vite 的技術原理,讀過本文之後,再去閱讀 Vite 的項目源碼,相信會有不小的收穫。

人工智能

杜克大學出品 AI 黑科技 PULSE 算法,讓你的照片有碼變高清

近日杜克大學開源了新型超分辨率圖像算法 PULSE,可將16×16像素的低分辨率人像放大到1024×1024像素的高分辨率。

工具推介

手把手教你快速搭建專屬的 StoryBook

Storybook是一個輔助UI控件開發的工具。通過story創建獨立的控件,讓每個控件開發都有一個獨立的開發調試環境。 Storybook的運行不依賴於項目,開發人員不用擔心由於開發環境、依賴問題導致不能開發控件。Storybook支持的框架覆蓋主流的框架(React、Vue、Angular)。 由於使用React作為技術棧,本文將介紹使用react的項目如何配置Storybook環境。

滄海拾遺

滄海拾遺,積跬步以至千里

ELF – 靈活可擴展的 HTML5 構建工具

前端工程化的問題由來已久,除了尤老師正在努力的方向,還出現過很多優秀的小工具幫助我們解決各個方面的問題,ELF 就是其中一種解放我們重複勞動的構建工具之一。

用 Git 鈎子進行簡單自動部署

除了這些工具,工程化中也還是有很多小問題,可以用很多方法去解決,自動化部署就是其中之一,如果你還不懂的如何利用 Git Hook 完成自動化部署的方法,趕緊補起這一課吧,未來正在向你招手~

歡迎關注凹凸實驗室博客:aotu.io

或者關注凹凸實驗室公眾號(AOTULabs),不定時推送文章:

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

【其他文章推薦】

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

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

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

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

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

【故障公告】阿里雲 RDS 實例 CPU 100% 故障引發全站無法正常訪問

非常抱歉,今天凌晨 3:20~8:30 左右,我們使用的阿里雲 RDS 實例 SQL Server 2016 標準版突然出現 CPU 100% 故障,造成全站無法正常訪問,由此給您帶來巨大的麻煩,請您諒解。

問題很奇怪,故障期間是數據庫服務器負載極低的時間段。從阿里雲 RDS 控制台 CloudDBA 看,故障期間下面的一個 SQL 語句大量執行,並且極其消耗 CPU 。

開始我們以為是這個 SQL 語句引發的故障,但排查下來這個 SQL 語句本身並沒有性能問題,而且已經使用了至少6個月。

最終恢復正常是通過 RDS 的2次主備切換,當發現故障后,我們立即進行主備切換,但切換后 CPU 依然 100% ,然後我們排查 SQL 語句的問題,排查未果,然後又進行一次主備切換,才恢復正常。

事後分析后發現應該是第一次主備切換沒有成功完成,阿里雲 RDS 控制台查看不到主備切換日誌,但2次切換,只有第2次收到郵件通知,由此可以推斷。

您的雲數據庫RDS實例:xxx(名稱:enable or disable task fetching while rds2slb transgfer.)任務觸發切換完畢,請檢查程序連接是否正常,建議設置自動重連機制以避免切換影響。

問題的原因有待進一個分析,再次抱歉由此給您帶來的麻煩。

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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