高併發與大數據解決方案概述


概述

隨着業務的不斷豐富,高併發和海量數據的處理日益成爲影響系統性能的重要問題。下面將提供一些針對併發問題和海量數據處理的解決方案。

海量數據的解決方案:

  • 緩存
  • 頁面靜態化
  • 數據庫優化
  • 分離活躍數據
  • 批量讀取和延遲修改
  • 讀寫分離
  • 分佈式數據庫
  • NoSQL和Hadoop

高併發的解決方案:

  • 應用和靜態資源分離
  • 頁面緩存
  • 集羣與分佈式
  • 反向代理
  • CDN
  • 底層的優化

海量數據的解決方案

緩存

數據量大最直接的解決方案就是使用緩存,緩存就是將從數據庫獲取的結果暫時保存起來,在下次使用的時候無需從數據庫獲取,這樣可以大大降低數據庫的壓力。

緩存的使用方式有兩種,一是通過程序直接保存到內存中,二是使用緩存框架。
程序直接操作緩存主要使用Map,尤其是CurrentHashMap。
常用的緩存框架有Ehcache、Memcache、Redis,等等。

緩存的適用情況
緩存主要用於數據變化不是很頻繁的情況,如果緩存數據的實時性要求較高,那麼會造成緩存數據與真實數據不一致的情況。


頁面靜態化

頁面靜態化是將程序最後生成的頁面保存起來,頁面靜態化後就不需要再次調用生成頁面了。
這樣不僅不需要再查詢數據庫了,連程序處理都省了。

  • 模板技術
    頁面靜態化可以在程序中使用模板技術生成。常用的模板引擎有:FreeMarker,Thymeleaf,Velocity,等等。

  • 緩存服務器
    可以使用緩存服務器,在應用服務器的上一層緩存生成的頁面,可以使用代理緩存服務器Squid,Nginx也提供了相應的功能。


數據庫優化

數據庫優化可以在不增加硬件成本的前提下提高處理效率,常用的數據庫優化方法有:表結構優化、SQL語句優化、分區和分表、索引優化、使用存儲過程代替直接操作等,另外有時候合理使用冗餘也能獲得非常好的效果。

表結構優化

表結構優化是數據庫中最基礎也是最重要的,如果表結構優化得不合理,就可能導致嚴重的性能問題,具體怎麼設計更合理也沒有固定不變的準則,需要根據實際情況具體處理。

表結構優化建議
1. > 數據類型選擇
數據庫操作中最爲耗時的操作就是IO處理,大部分數據庫操作90%以上的時間都花在了IO讀寫上面。所以儘可能減少IO 讀寫量,可以在很大程度上提高數據庫操作的性能。
我們無法改變數據庫中需要存儲的數據,但是我們可以在這些數據的存儲方式方面花一些心思。下面的這些關於字段類型的優化建議主要適用於記錄條數較多,數據量較大的場景,因爲精細化的數據類型設置可能帶來維護成本的提高,過度優化也可能會帶來其他的問題:
- > (1)數字類型:
非萬不得已不要使用DOUBLE,不僅僅只是存儲長度的問題,同時還會存在精確性的問題。同樣,固定精度的小數,也不建議使用DECIMAL,建議乘以固定倍數轉換成整數存儲,可以大大節省存儲空間,且不會帶來任何附加維護成本。對於整數的存儲,在數據量較大的情況下,建議區分開 TINYINT / INT / BIGINT 的選擇,因爲三者所佔用的存儲空間也有很大的差別,能確定不會使用負數的字段,建議添加unsigned定義。當然,如果數據量較小的數據庫,也可以不用嚴格區分三個整數類型。
- > (2)字符類型:
- > 非萬不得已不要使用 TEXT 數據類型,其處理方式決定了他的性能要低於char或者是varchar類型的處理。定長字段,建議使用 CHAR 類型,不定長字段儘量使用 VARCHAR,且僅僅設定適當的最大長度,而不是非常隨意的給一個很大的最大長度限定,因爲不同的長度範圍,MySQL也會有不一樣的存儲處理。
- > (3)時間類型:
儘量使用TIMESTAMP類型,因爲其存儲空間只需要 DATETIME 類型的一半。對於只需要精確到某一天的數據類型,建議使用DATE類型,因爲他的存儲空間只需要3個字節,比TIMESTAMP還少。不建議通過INT類型類存儲一個unix timestamp 的值,因爲這太不直觀,會給維護帶來不必要的麻煩,同時還不會帶來任何好處。
- > 4.ENUM & SET:
對於狀態字段,可以嘗試使用 ENUM 來存放,因爲可以極大的降低存儲空間,而且即使需要增加新的類型,只要增加於末尾,修改結構也不需要重建表數據。如果是存放可預先定義的屬性數據呢?可以嘗試使用SET類型,即使存在多種屬性,同樣可以遊刃有餘,同時還可以節省不小的存儲空間。

SET是一個字符串對象,可以有零或多個值,其值來自表創建時規定的允許的一列值。指定包括多個SET成員的SET列值時各成員之間用逗號(‘,’)間隔開。所以SET成員值本身不能包含逗號。
SET最多可以有64個不同的成員。當創建表時,SET成員值的尾部空格將自動被刪除。當檢索時,保存在SET列的值使用列定義中所使用的大小寫來顯示。請注意可以爲SET列分配字符集和校對規則。對於二進制或大小寫敏感的校對規則,當爲列分配值時應考慮大小寫。
MySQL用數字保存SET值,所保存值的低階位對應第1個SET成員。如果在數值上下文中檢索一個SET值,檢索值的位置對應組成列值的SET成員。例如,你可以這樣從一個SET列檢索數值值:
mysql> SELECT set_col+0 FROM tbl_name;
使用實例:[sql]

CREATE TABLE `TestSet` (  
`Id` int(4) NOT NULL AUTO_INCREMENT,  
`set1` set('ABC','1111','2222','XXX') DEFAULT NULL,  
PRIMARY KEY (`Id`)  
)ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;  
INSERT INTO `testset` VALUES ('1','a');  
INSERT INTO `testset` VALUES ('2', 'ABC');
INSERT INTO `testset` VALUES ('3', 'ABCD');
#ABC可以存入,a和ABCD無法存入set1字段。

ENUM是一個字符串對象,其值來自表創建時在列規定中顯式枚舉的一列值。
在某些情況下,ENUM值也可以爲空字符串(”)或NULL。如果你將一個非法值插入ENUM(也就是說,允許的值列之外的字符串),將插入空字符串以作爲特殊錯誤值。該字符串與“普通”空字符串不同,該字符串有數值值0。如果將ENUM列聲明爲允許NULL,NULL值則爲該列的一個有效值,並且默認值爲NULL。如果ENUM列被聲明爲NOT NULL,其默認值爲允許的值列的第1個元素。每個枚舉值有一個索引,來自列規定的允許的值列中的值從1開始編號。空字符串錯誤值的索引值是0。 NULL值的索引是NULL。這說明你可以使用下面的SELECT語句來找出分配了非法ENUM值的行
· mysql> SELECT * FROM tbl_name WHERE enum_col=0;
枚舉最多可以有65,535個元素。

CREATE TABLE `TestEnum` (  
`Id` INT(4) NOT NULL AUTO_INCREMENT,  
`enum` ENUM('ABC','1111','2222','XXX') DEFAULT NULL,  
PRIMARY KEY (`Id`)  
) ENGINE=INNODB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;  
INSERT INTO `TestEnum` VALUES ('1','a');  
INSERT INTO `TestEnum` VALUES ('2', 'ABC');
INSERT INTO `TestEnum` VALUES ('3', 'ABCD');
  • (5)BLOB類型:
    強烈反對在數據庫中存放 BLOB 類型數據,雖然數據庫提供了這樣的功能,但這不是他所擅長的,我們更應該讓合適的工具做他擅長的事情,才能將其發揮到極致。
    BLOB (binary large object),二進制大對象,是一個可以存儲二進制文件的容器。在計算機中,BLOB常常是數據庫中用來存儲二進制文件的字段類型。BLOB是一個大文件,典型的BLOB是一張圖片或一個聲音文件,由於它們的尺寸,必須使用特殊的方式來處理(例如:上傳、下載或者存放到一個數據庫)。

    1. 字符編碼
      字符集直接決定了數據在MySQL中的存儲編碼方式,由於同樣的內容使用不同字符集表示所佔用的空間大小會有較大的差異,所以通過使用合適的字符集,可以幫助我們儘可能減少數據量,進而減少IO操作次數。
      1.純拉丁字符能表示的內容,沒必要選擇 latin1 之外的其他字符編碼,因爲這會節省大量的存儲空間
      2.如果我們可以確定不需要存放多種語言,就沒必要非得使用UTF8或者其他UNICODE字符類型,這回造成大量的存儲空間浪費
      3.MySQL的數據類型可以精確到字段,所以當我們需要大型數據庫中存放多字節數據的時候,可以通過對不同表不同字段使用不同的數據類型來較大程度減小數據存儲量,進而降低 IO 操作次數並提高緩存命中率

    2. 適當拆分
      有些時候,我們可能會希望將一個完整的對象對應於一張數據庫表,這對於應用程序開發來說是很有好的,但是有些時候可能會在性能上帶來較大的問題。當我們的表中存在類似於 TEXT 或者是很大的VARCHAR類型的大字段的時候,如果我們大部分訪問這張表的時候都不需要這個字段,我們就該義無反顧的將其拆分到另外的獨立表中,以減少常用數據所佔用的存儲空間。這樣做的一個明顯好處就是每個數據塊中可以存儲的數據條數可以大大增加,既減少物理IO次數,也能大大提高內存中的緩存命中率。

    3. 適度冗餘
      爲什麼我們要冗餘?這不是增加了每條數據的大小,減少了每個數據塊可存放記錄條數嗎?
      確實,這樣做是會增大每條記錄的大小,降低每條記錄中可存放數據的條數,但是在有些場景下我們仍然還是不得不這樣做:
      被頻繁引用且只能通過 Join 2張(或者更多)大表的方式才能得到的獨立小字段
      這樣的場景由於每次Join僅僅只是爲了取得某個小字段的值,Join到的記錄又大,會造成大量不必要的 IO,完全可以通過空間換取時間的方式來優化。不過,冗餘的同時需要確保數據的一致性不會遭到破壞,確保更新的同時冗餘字段也被更新

    4. 儘量使用NOT NULL

NULL 類型比較特殊,SQL難優化。
雖然 MySQL NULL類型和 Oracle 的NULL 有差異,會進入索引中,但如果是一個組合索引,那麼這個NULL 類型的字段會極大影響整個索引的效率。此外,NULL 在索引中的處理也是特殊的,也會佔用額外的存放空間。
很多人覺得 NULL會節省一些空間,所以儘量讓NULL來達到節省IO的目的,但是大部分時候這會適得其反,雖然空間上可能確實有一定節省,倒是帶來了很多其他的優化問題,不但沒有將IO量省下來,反而加大了SQL的IO量。所以儘量確保DEFAULT值不是 NULL,也是一個很好的表結構設計優化習慣。

SQL語句優化

SQL語句優化也是非常重要的,基礎的SQL優化是語法層面的優化,不過更重要的是處理邏輯的優化,這也需要根據實際情況具體處理,而且要和索引緩存等配合使用。
不過SQL優化有一個通用的做法就是,首先要將涉及大數據的業務的SQL語句執行時間詳細記錄下來,其次通過仔細分析日誌(同一條語句對不同條件的執行時間也可能不同,這點也需要仔細分析)找出需要優化的語句和其中的問題,然後再有的放矢地優化,而不是不分重點對每條語句都花同樣的時間和精力優化。

優化舉例:

  1. 對查詢進行優化,要儘量避免全表掃描,首先應考慮在 where 及 order by 涉及的列上建立索引。

  2. 應儘量避免在 where 子句中對字段進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如:
    select id from t where num is null
    最好不要給數據庫留NULL,儘可能的使用NOT NULL填充數據庫.
    備註、描述、評論之類的可以設置爲NULL,其他的,最好不要使用NULL。
    不要以爲NULL不需要空間,比如:char(100)型,在字段建立時,空間就固定了,不管是否插入值(NULL也包含在內),都是佔用100個字符的空間的,如果是varchar這樣的變長字段,null不佔用空間。
    可以在num上設置默認值0,確保表中num列沒有null值,然後這樣查詢:
    select id from t where num = 0

  3. 應儘量避免在 where 子句中使用 != 或 <> 操作符,否則將引擎放棄使用索引而進行全表掃描。

  4. 應儘量避免在 where 子句中使用 or 來連接條件,如果一個字段有索引,一個字段沒有索引,將導致引擎放棄使用索引而進行全表掃描。
    如:
    SELECT * FROM b2buser u WHERE u.userId = ‘1’ OR u.username=’test’;
    可以這樣查詢:
    SELECT * FROM b2buser u WHERE u.userId = ‘1’
    UNION ALL
    SELECT * FROM b2buser u WHERE u.username=’test’;

UNION 和 UNION ALL 操作符
UNION 操作符用於合併兩個或多個 SELECT 語句的結果集。請注意,UNION 內部的 SELECT 語句必須擁有相同數量的列。列也必須擁有相似的數據類型。同時,每條 SELECT 語句中的列的順序必須相同。UNION ALL 命令和 UNION 命令幾乎是等效的,不過 UNION ALL 命令會列出所有的值。

  1. in 和 not in 也要慎用,否則會導致全表掃描。

如:

select id from t where num in(1,2,3)
對於連續的數值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3

很多時候用 exists 代替 in 是一個好的選擇:
select num from a where num in(select num from b)
用下面的語句替換:
select num from a where exists(select 1 from b where num=a.num)

exists : 強調的是是否返回結果集,不要求知道返回什麼;exists引導的子句有結果集返回,那麼exists這個條件就算成立了,注意返回的字段始終爲1,如果改成“select 2 from a where …”,那麼返回的字段就是2,這個數字沒有意義。

而 exists 與 in 最大的區別在於in引導的子句只能返回一個字段,exists子句是允許f返回多個字段的。
如:

SELECT c.chainCode FROM chain c WHERE c.chainId IN (SELECT ci.chainId FROM chain_i18n ci);

SELECT c.chainCode FROM chain c WHERE EXISTS (SELECT 1,2,3 FROM chain_i18n ci WHERE ci.chainId=c.chainId AND ci.chainName = c.chainCode);

其中EXISTS後面的查詢返回的1,2,3沒有實際意義。

  1. 下面的查詢也將導致全表掃描:
    select id from t where name like ‘%abc%’
    若要提高效率,可以考慮全文檢索。

  2. 如果在 where 子句中使用參數,也會導致全表掃描。因爲SQL只有在運行時纔會解析局部變量,但優化程序不能將訪問計劃的選擇推遲到運行時;它必須在編譯時進行選擇。然 而,如果在編譯時建立訪問計劃,變量的值還是未知的,因而無法作爲索引選擇的輸入項。

如下面語句將進行全表掃描:
select id from t where num = @num
可以改爲強制查詢使用索引:
select id from t with(index(索引名)) where num = @num

應儘量避免在 where 子句中對字段進行表達式操作,這將導致引擎放棄使用索引而進行全表掃描。
如:
select id from t where num/2 = 100
應改爲:
select id from t where num = 100*2

  1. 應儘量避免在where子句中對字段進行函數操作,這將導致引擎放棄使用索引而進行全表掃描。如:
    select id from t where substring(name,1,3) = ’abc’ -–name以abc開頭的id
    select id from t where datediff(day,createdate,’2005-11-30′) = 0 -–‘2005-11-30’ –生成的id
    應改爲:
    select id from t where name like ‘abc%’
    select id from t where createdate >= ‘2005-11-30’ and createdate < ‘2005-12-1’

  2. 不要在 where 子句中的“=”左邊進行函數、算術運算或其他表達式運算,否則系統將可能無法正確使用索引。

  3. 在使用索引字段作爲條件時,如果該索引是複合索引,那麼必須使用到該索引中的第一個字段作爲條件時才能保證系統使用該索引,否則該索引將不會被使用,並且應儘可能的讓字段順序與索引順序相一致。

  4. 不要寫一些沒有意義的查詢,如需要生成一個空表結構:
    select col1,col2 into #t from t where 1=0
    這類代碼不會返回任何結果集,但是會消耗系統資源的,應改成這樣:
    create table #t(…)

  5. Update 語句,如果只更改1、2個字段,不要Update全部字段,否則頻繁調用會引起明顯的性能消耗,同時帶來大量日誌。

  6. 對於多張大數據量(這裏幾百條就算大了)的表JOIN,要先分頁再JOIN,否則邏輯讀會很高,性能很差。

  7. select count(*) from table;這樣不帶任何條件的count會引起全表掃描,並且沒有任何業務意義,是一定要杜絕的。

  8. 索引並不是越多越好,索引固然可以提高相應的 select 的效率,但同時也降低了 insert 及 update 的效率,因爲 insert 或 update 時有可能會重建索引,所以怎樣建索引需要慎重考慮,視具體情況而定。一個表的索引數最好不要超過6個,若太多則應考慮一些不常使用到的列上建的索引是否有 必要。

  9. 應儘可能的避免更新 clustered 索引數據列,因爲 clustered 索引數據列的順序就是表記錄的物理存儲順序,一旦該列值改變將導致整個表記錄的順序的調整,會耗費相當大的資源。若應用系統需要頻繁更新 clustered 索引數據列,那麼需要考慮是否應將該索引建爲 clustered 索引。

  10. 儘量使用數字型字段,若只含數值信息的字段儘量不要設計爲字符型,這會降低查詢和連接的性能,並會增加存儲開銷。這是因爲引擎在處理查詢和連 接時會逐個比較字符串中每一個字符,而對於數字型而言只需要比較一次就夠了。

  11. 儘可能的使用 varchar/nvarchar 代替 char/nchar ,因爲首先變長字段存儲空間小,可以節省存儲空間,其次對於查詢來說,在一個相對較小的字段內搜索效率顯然要高些。

  12. 任何地方都不要使用 select * from t ,用具體的字段列表代替“*”,不要返回用不到的任何字段。

20.儘量使用表變量來代替臨時表。如果表變量包含大量數據,請注意索引非常有限(只有主鍵索引)。

  1. 添加索引
    數據庫索引,是數據庫管理系統中一個排序的數據結構,以協助快速查詢、更新數據庫表中數據。
    在數據之外,數據庫系統還維護着滿足特定查找算法的數據結構,這些數據結構以某種方式引用(指向)數據,這樣就可以在這些數據結構上實現高級查找算法。這種數據結構,就是索引。
    爲表設置索引要付出代價的:一是增加了數據庫的存儲空間,二是在插入和修改數據時要花費較多的時間(因爲索引也要隨之變動)。
1.添加PRIMARY KEY(主鍵索引) 
mysql>ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` ) 
2.添加UNIQUE(唯一索引) 
mysql>ALTER TABLE `table_name` ADD UNIQUE ( 
`column` 
) 
3.添加INDEX(普通索引) 
mysql>ALTER TABLE `table_name` ADD INDEX index_name ( `column` ) 
4.添加FULLTEXT(全文索引) 
mysql>ALTER TABLE `table_name` ADD FULLTEXT ( `column`) 
5.添加多列索引 
mysql>ALTER TABLE `table_name` ADD INDEX index_name ( `column1`, `column2`, `column3` )
  1. 避免頻繁創建和刪除臨時表,以減少系統表資源的消耗。臨時表並不是不可使用,適當地使用它們可以使某些例程更有效,例如,當需要重複引用大型表或常用表中的某個數據集時。但是,對於一次性事件, 最好使用導出表。

23.在新建臨時表時,如果一次性插入數據量很大,那麼可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果數據量不大,爲了緩和系統表的資源,應先create table,然後insert。

24.如果使用到了臨時表,在存儲過程的最後務必將所有的臨時表顯式刪除,先 truncate table ,然後 drop table ,這樣可以避免系統表的較長時間鎖定。

25.儘量避免使用遊標,因爲遊標的效率較差,如果遊標操作的數據超過1萬行,那麼就應該考慮改寫。

26.使用基於遊標的方法或臨時表方法之前,應先尋找基於集的解決方案來解決問題,基於集的方法通常更有效。

27.與臨時表一樣,遊標並不是不可使用。對小型數據集使用 FAST_FORWARD 遊標通常要優於其他逐行處理方法,尤其是在必須引用幾個表才能獲得所需的數據時。在結果集中包括“合計”的例程通常要比使用遊標執行的速度快。如果開發時 間允許,基於遊標的方法和基於集的方法都可以嘗試一下,看哪一種方法的效果更好。

28.在所有的存儲過程和觸發器的開始處設置 SET NOCOUNT ON ,在結束時設置 SET NOCOUNT OFF 。無需在執行存儲過程和觸發器的每個語句後向客戶端發送 DONE_IN_PROC 消息。

29.儘量避免大事務操作,提高系統併發能力。

30.儘量避免向客戶端返回大數據量,若數據量過大,應該考慮相應需求是否合理。

  1. explain顯示了MySQL如何使用索引來處理select語句以及連接表。可以幫助選擇更好的索引和寫出更優化的查詢語句。

如:
EXPLAIN
SELECT * FROM b2buser u WHERE u.userId = ‘1’
UNION ALL
SELECT * FROM b2buser u WHERE u.username=’test’;

查詢結果中的possible_keys提示使用哪個索引會在該表中找到行,keys 是MYSQL使用的索引,key_len是MYSQL使用的索引長度。

分區和分表

當數據量變多的時候,如果可以分區或者分表,那將起到非常好的效果。當一張表中的數據量變多的時候操作速度就慢了,所以很容易想到的就是將數據分到多個表中保存,但是這麼做之後操作起來比較麻煩,想操作(增刪改查)一個數據還需要先找到對應的表,如果涉及多個表還得跨表操作。
其實在常用的數據庫中可以不分表而達到跟分表類似的效果,那就是分區。分區就是將一張表中的數據按照一定的規則分到不同的區來保存,這樣在查詢數據時如果數據的範圍在同一個區內那麼可以只對一個區的數據進行操作,這樣操作的數據量更少,速度更快,而且這種方法對程序是透明的,程序不需要做任何改動。

索引優化

索引的大致原理是在數據發生變化(增刪改)的時候就預先按指定字段的順序排列後保存到一個類似表的結構中,這樣在查找索引字段爲條件的記錄時就可以很快地從索引中找到對應記錄的指針並從表中獲取到記錄,這樣速度就快多了。
不過索引也是一把雙刃劍,它在提高查詢速度的同時也降低了增刪改的速度,因爲每次數據的變化都需要更新相應的索引。不過合理使用索引對提升查詢速度的效果非常明顯,所以對哪些字段使用索引、使用什麼類型的索引都需要仔細琢磨,並且最好
再做一些測試。

使用存儲過程代替直接操作

在操作過程複雜而且調用頻率高的業務中,可以通過使用存儲過程代替直接操作來提高效率,因爲存儲過程只需要編譯一次,而且可以在一個存儲過程裏面做一些複雜的操作。


分離活躍數據

雖然有些數據總數據量非常大,但是活躍數據並不多,這種情況就可以將活躍數據單獨保存起來從而提高處理效率。比如,對網站來說,用戶很多時候就是這種數據,註冊用戶很多,但是活躍用戶卻不多,而不活躍的用戶中有的偶爾也會登錄網站,因此還不能刪除。這時就可以通過一個定期處理的任務將不活躍的用戶轉移
到別的數據表中,在主要操作的數據表中只保存活躍用戶,查詢時先從默認表中查找,如果找不到再從不活躍用戶表中查找,這樣就可以提高查詢的效率。判斷活躍用戶可以通過最近登錄時間,也可以通過指定時間段內登錄次數。除了用戶外還有很多這種類型的數據,如一個網站上的文章(特別是新聞類的)、企業業務系統中按時間記錄的數據等。


批量讀取和延遲修改

批量讀取和延遲修改的原理是通過減少操作的次數來提高效率,如果使用得恰當,效率將會呈數量級提升。

批量讀取是將多次查詢合併到一次中進行,比如,在一個業務系統中需要批量導入工人信息,在導入前需要檢查工人的編碼是否已經在數據庫中、工人對應的部門信息是否正確(在部門表中是否存在)、工人的工種信息在工種表中是否存在等,如果每保存一條記錄都查詢一次數據庫,那麼對每個需要檢查的字段,都需要查詢與要保存的記錄條數相同次數的數據庫,這時可以先將所有要保存的數據的相應字段讀取到一個變量中,然後使用in語句統一查詢一次數據庫,這樣就可以將n(要保存記錄的條數)次查詢變爲一次查詢了。
除了這種對同一個請求中的數據批量讀取,在高併發的情況下還可以將多個請求的查詢合併到一次進行,如將3秒或5秒內的所有請求合併到一起統一查詢一次數據庫,這樣就可以有效減少查詢數據庫的次數,這種類型可以用異步請求來處理。

延遲修改主要針對高併發且頻繁修改(包括新增)的數據。


讀寫分離

讀寫分離,基本的原理是讓主數據庫處理事務性增、改、刪操作(INSERT、UPDATE、DELETE),而從數據庫處理SELECT查詢操作。數據庫複製被用來把事務性操作導致的變更同步到集羣中的從數據庫。


分佈式數據庫

分佈式數據庫是將不同的表存放到不同的數據庫中然後再放到不同的服務器。這樣在處理請求時,如果需要調用多個表,則可以讓多臺服務器同時處理,從而提高處理速度。


NoSQL和Hadoop

NoSQL是近年來發展非常迅速的一項技術,它的核心就是非結構化。我們一般使用的數據庫(SQL數據庫)都是需要先將表的結構定義出來,一個表有幾個字段,每個字段各是什麼類型,然後才能往裏面按照相應的類型保存數據,而且按照數據庫範式的規定,一個字段只能保存單一的信息,不可以包括多層內容,這就對使用的靈活性帶來了很大的制約,NoSQL就是突破了這些條條框框,可以非常靈活地進行操作,另外因爲NoSQL通過多個塊存儲數據的特點,其操作大數據的速度也非常快,這些特性正是現在的互聯網程序最需要的,所以NoSQL發展得非常快。現在NoSQL主要使用在互聯網的程序中,在企業業務系統中使用的還不多,而且現在NoSQL還不是很成熟,但由於靈活和高效的特性,NoSQL發展的前景是非常好的。

Hadoop是專門針對大數據處理的一套框架,Hadoop是一個開發和運行處理大規模數據的軟件平臺,是Appach的一個用java語言實現開源軟件框架,實現在大量計算機組成的集羣中對海量數據進行分佈式計算.


高併發的解決方案

應用和靜態資源分離

剛開始的時候應用和靜態資源是保存在一起的,當併發量達到一定程度時就需要將靜態資源保存到專門的服務器中,靜態資源主要包括圖片、視頻、js、css和一些資源文件等,這些文件因爲沒有狀態,所以分離比較簡單,直接存放到相應的服務器就可以了,一般會通過專門的域名去訪問。


頁面緩存

頁面緩存是將應用生成的頁面緩存起來,這樣就不需要每次都重新生成頁面了,從而可以節省大量CPU資源,如果將緩存的頁面放到內存中速度就更快了。如果使用了Nginx服務器就可以使用它自帶的緩存功能,當然也可以使用專門的Squid服務器。頁面緩存的默認失效機制一般是按緩存時間處理的,當然也可以在修改數據之後手動讓相應緩存失效。

有部分經常變化的數據的頁面怎麼使用頁面緩存呢?可以先緩存頁面,再用ajax修改變化的部分。


集羣與分佈式

集羣和分佈式處理都是使用多臺服務器進行處理的,集羣是每臺服務器都具有相同的功能,處理請求時調用哪臺服務器都可以,主要起分流的作用,分佈式是將不同的業務放到不同的服務器中,處理一個請求可能需要用到多臺服務器,這樣就可以提高一個請求的處理速度,而且集羣和分佈式也可以同時使用。

集羣有兩個方式:一種是靜態資源集羣。另一種是應用程序集羣。
靜態資源集羣比較簡單,而應用程序集羣就有點複雜了。因爲應用程序在處理過程中可能會使用到一些緩存的數據,如果集羣就需要同步這些數據,其中最重要的就是Session,Session同步也是應用程序集羣中非常核心的一個問題。
Session同步有兩種處理方式:一種是在Session發生變化後自動同步到其他服務器,另外一種方式是用一個程序統一管理Session。所有集羣的服務器都使用同一個Session,Tomcat默認使用的就是第一種方式,通過簡單的配置就可以實現,第二種方式可以使用專門的緩存程序來管理緩存如Memcached、Redis等。


反向代理

反向代理(Reverse Proxy)方式是指以代理服務器來接受internet上的連接請求,然後將請求轉發給內部網絡上的服務器,並將從服務器上得到的結果返回給internet上請求連接的客戶端,此時代理服務器對外就表現爲一個反向代理服務器。

反向代理服務器和代理服務器的區別
代理服務器的作用是代我們獲取想要的資源然後將結果返回給我們,所要獲取的資源是我們主動告訴代理服務器的,比如,我們想訪問Facebook,但是直接訪問不了,這時就可以讓代理服務器訪問,然後將結果返回給我們。
反向代理服務器是我們正常訪問一臺服務器的時候,服務器自己調用了別的服務器資源返回結果給我們,我們自己並不知道。

代理服務器是我們主動使用的,是爲我們服務的,它不需要有自己的域名;反向代理服務器是服務器自己使用的,我們並不知道,它有自己的域名,我們訪問它跟訪問正常的網址沒有任何區別。

反向代理服務器可以和實際處理請求的服務器在同一臺主機上,而且一臺反向代理服務器也可以訪問多臺實際處理請求的服務器。反向代理服務器主要有三個作用:
①可以作爲前端服務器跟實際處理請求的服務器(如Tomcat)集成;
②可以用做負載均衡;
③轉發請求,比如,可以將不同類型的資源請求轉發到不同的服務器去處理,可以將動態資源轉發到Tomcat、Php等動態程序而將圖片等靜態資源的請求轉發到靜態資源的服務器,另外也可以在url地址結構發生變化後將新地址轉發到原來的舊地址上。


CDN

CDN其實是一種特殊的集羣頁面緩存服務器,它和普通集羣的多臺頁面緩存服務器比主要是它存放的位置和分配請求的方式有點特殊。

CDN的全稱是Content Delivery Network,即內容分發網絡。其基本思路是儘可能避開互聯網上有可能影響數據傳輸速度和穩定性的瓶頸和環節,使內容傳輸的更快、更穩定。通過在網絡各處放置節點服務器所構成的在現有的互聯網基礎之上的一層智能虛擬網絡,CDN系統能夠實時地根據網絡流量和各節點的連接、負載狀況以及到用戶的距離和響應時間等綜合信息將用戶的請求重新導向離用戶最近的服務節點上。其目的是使用戶可就近取得所需內容,解決 Internet網絡擁擠的狀況,提高用戶訪問網站的響應速度。


底層的優化

前面講到的所有架構都是建立在最前面介紹的基礎架構之上的,而且很多地方都需要通過網絡傳輸數據,如果可以加快網絡傳輸的速度,那將會讓整個系統從根本上得到改善。網絡傳輸數據都是按照各種協議進行的,不過協議並不是不可以改變,Google就邁出了這一步,它制定了Quic、Spdy等協議來傳輸數據,Quic比TCP效率高而且比UDP安全,Spdy協議在現有HTTP協議的基礎上增加了很多新特性,提高了傳輸的效率,不過有些特性已經包含到了HTTP/2協議中,而且Google也已經放棄了Spdy而使用HTTP/2了。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章