高併發與大資料解決方案概述
概述
隨著業務的不斷豐富,高併發和海量資料的處理日益成為影響系統性能的重要問題。下面將提供一些針對併發問題和海量資料處理的解決方案。
海量資料的解決方案:
- 快取
- 頁面靜態化
- 資料庫優化
- 分離活躍資料
- 批量讀取和延遲修改
- 讀寫分離
- 分散式資料庫
- 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是一張圖片或一個聲音檔案,由於它們的尺寸,必須使用特殊的方式來處理(例如:上傳、下載或者存放到一個數據庫)。
字元編碼
字符集直接決定了資料在MySQL中的儲存編碼方式,由於同樣的內容使用不同字符集表示所佔用的空間大小會有較大的差異,所以通過使用合適的字符集,可以幫助我們儘可能減少資料量,進而減少IO操作次數。
1.純拉丁字元能表示的內容,沒必要選擇 latin1 之外的其他字元編碼,因為這會節省大量的儲存空間
2.如果我們可以確定不需要存放多種語言,就沒必要非得使用UTF8或者其他UNICODE字元型別,這回造成大量的儲存空間浪費
3.MySQL的資料型別可以精確到欄位,所以當我們需要大型資料庫中存放多位元組資料的時候,可以通過對不同表不同欄位使用不同的資料型別來較大程度減小資料儲存量,進而降低 IO 操作次數並提高快取命中率適當拆分
有些時候,我們可能會希望將一個完整的物件對應於一張資料庫表,這對於應用程式開發來說是很有好的,但是有些時候可能會在效能上帶來較大的問題。當我們的表中存在類似於 TEXT 或者是很大的VARCHAR型別的大欄位的時候,如果我們大部分訪問這張表的時候都不需要這個欄位,我們就該義無反顧的將其拆分到另外的獨立表中,以減少常用資料所佔用的儲存空間。這樣做的一個明顯好處就是每個資料塊中可以儲存的資料條數可以大大增加,既減少物理IO次數,也能大大提高記憶體中的快取命中率。適度冗餘
為什麼我們要冗餘?這不是增加了每條資料的大小,減少了每個資料塊可存放記錄條數嗎?
確實,這樣做是會增大每條記錄的大小,降低每條記錄中可存放資料的條數,但是在有些場景下我們仍然還是不得不這樣做:
被頻繁引用且只能通過 Join 2張(或者更多)大表的方式才能得到的獨立小欄位
這樣的場景由於每次Join僅僅只是為了取得某個小欄位的值,Join到的記錄又大,會造成大量不必要的 IO,完全可以通過空間換取時間的方式來優化。不過,冗餘的同時需要確保資料的一致性不會遭到破壞,確保更新的同時冗餘欄位也被更新儘量使用NOT NULL
NULL 型別比較特殊,SQL難優化。
雖然 MySQL NULL型別和 Oracle 的NULL 有差異,會進入索引中,但如果是一個組合索引,那麼這個NULL 型別的欄位會極大影響整個索引的效率。此外,NULL 在索引中的處理也是特殊的,也會佔用額外的存放空間。
很多人覺得 NULL會節省一些空間,所以儘量讓NULL來達到節省IO的目的,但是大部分時候這會適得其反,雖然空間上可能確實有一定節省,倒是帶來了很多其他的優化問題,不但沒有將IO量省下來,反而加大了SQL的IO量。所以儘量確保DEFAULT值不是 NULL,也是一個很好的表結構設計優化習慣。
SQL語句優化
SQL語句優化也是非常重要的,基礎的SQL優化是語法層面的優化,不過更重要的是處理邏輯的優化,這也需要根據實際情況具體處理,而且要和索引快取等配合使用。
不過SQL優化有一個通用的做法就是,首先要將涉及大資料的業務的SQL語句執行時間詳細記錄下來,其次通過仔細分析日誌(同一條語句對不同條件的執行時間也可能不同,這點也需要仔細分析)找出需要優化的語句和其中的問題,然後再有的放矢地優化,而不是不分重點對每條語句都花同樣的時間和精力優化。
優化舉例:
對查詢進行優化,要儘量避免全表掃描,首先應考慮在 where 及 order by 涉及的列上建立索引。
應儘量避免在 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應儘量避免在 where 子句中使用 != 或 <> 操作符,否則將引擎放棄使用索引而進行全表掃描。
應儘量避免在 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 命令會列出所有的值。
- 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
FROMchain
c WHERE c.chainId
IN (SELECT ci.chainId
FROMchain_i18n
ci);SELECT c.
chainCode
FROMchain
c WHERE EXISTS (SELECT 1,2,3 FROMchain_i18n
ci WHERE ci.chainId
=c.chainId
AND ci.chainName
= c.chainCode
);
其中EXISTS後面的查詢返回的1,2,3沒有實際意義。
下面的查詢也將導致全表掃描:
select id from t where name like ‘%abc%’
若要提高效率,可以考慮全文檢索。如果在 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
應儘量避免在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’不要在 where 子句中的“=”左邊進行函式、算術運算或其他表示式運算,否則系統將可能無法正確使用索引。
在使用索引欄位作為條件時,如果該索引是複合索引,那麼必須使用到該索引中的第一個欄位作為條件時才能保證系統使用該索引,否則該索引將不會被使用,並且應儘可能的讓欄位順序與索引順序相一致。
不要寫一些沒有意義的查詢,如需要生成一個空表結構:
select col1,col2 into #t from t where 1=0
這類程式碼不會返回任何結果集,但是會消耗系統資源的,應改成這樣:
create table #t(…)Update 語句,如果只更改1、2個欄位,不要Update全部欄位,否則頻繁呼叫會引起明顯的效能消耗,同時帶來大量日誌。
對於多張大資料量(這裡幾百條就算大了)的表JOIN,要先分頁再JOIN,否則邏輯讀會很高,效能很差。
select count(*) from table;這樣不帶任何條件的count會引起全表掃描,並且沒有任何業務意義,是一定要杜絕的。
索引並不是越多越好,索引固然可以提高相應的 select 的效率,但同時也降低了 insert 及 update 的效率,因為 insert 或 update 時有可能會重建索引,所以怎樣建索引需要慎重考慮,視具體情況而定。一個表的索引數最好不要超過6個,若太多則應考慮一些不常使用到的列上建的索引是否有 必要。
應儘可能的避免更新 clustered 索引資料列,因為 clustered 索引資料列的順序就是表記錄的物理儲存順序,一旦該列值改變將導致整個表記錄的順序的調整,會耗費相當大的資源。若應用系統需要頻繁更新 clustered 索引資料列,那麼需要考慮是否應將該索引建為 clustered 索引。
儘量使用數字型欄位,若只含數值資訊的欄位儘量不要設計為字元型,這會降低查詢和連線的效能,並會增加儲存開銷。這是因為引擎在處理查詢和連 接時會逐個比較字串中每一個字元,而對於數字型而言只需要比較一次就夠了。
儘可能的使用 varchar/nvarchar 代替 char/nchar ,因為首先變長欄位儲存空間小,可以節省儲存空間,其次對於查詢來說,在一個相對較小的欄位內搜尋效率顯然要高些。
任何地方都不要使用 select * from t ,用具體的欄位列表代替“*”,不要返回用不到的任何欄位。
20.儘量使用表變數來代替臨時表。如果表變數包含大量資料,請注意索引非常有限(只有主鍵索引)。
- 新增索引
資料庫索引,是資料庫管理系統中一個排序的資料結構,以協助快速查詢、更新資料庫表中資料。
在資料之外,資料庫系統還維護著滿足特定查詢演算法的資料結構,這些資料結構以某種方式引用(指向)資料,這樣就可以在這些資料結構上實現高階查詢演算法。這種資料結構,就是索引。
為表設定索引要付出代價的:一是增加了資料庫的儲存空間,二是在插入和修改資料時要花費較多的時間(因為索引也要隨之變動)。
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` )
- 避免頻繁建立和刪除臨時表,以減少系統表資源的消耗。臨時表並不是不可使用,適當地使用它們可以使某些例程更有效,例如,當需要重複引用大型表或常用表中的某個資料集時。但是,對於一次性事件, 最好使用匯出表。
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.儘量避免向客戶端返回大資料量,若資料量過大,應該考慮相應需求是否合理。
- 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了。