Java面試題(自己不會的查大佬的貼,持續記錄中)

目錄

1.Java運算符優先級... 9

2.HTML,JS,CSS的區別... 10

1、HTML—Hypertext Markup Language. 10

2、CSS—Cascading Style Sheet 10

3、JavaScript 10

3.從輸入URL到網頁呈現的過程... 10

TCP/IP請求... 11

三次握手的步驟:(抽象派)... 11

爲何不是二次握手,四次握手?... 11

四次揮手的步驟:(抽象派)... 12

4.五層協議:... 12

5.HTTP長連接和短連接原理淺析... 13

1. HTTP協議與TCP/IP協議的關係... 13

2. 如何理解HTTP協議是無狀態的... 13

3. 什麼是長連接、短連接?... 13

3.1 TCP連接... 13

3.2 TCP短連接... 14

3.3 TCP長連接... 15

3.4 長連接短連接操作過程... 15

4. 長連接和短連接的優點和缺點... 15

5. 什麼時候用長連接,短連接?... 15

6.Java中List幾種去重方式的比較... 16

7.樂觀鎖和悲觀鎖... 16

一、概念... 16

二、兩種鎖的使用場景... 16

三、樂觀鎖的實現方式... 16

四、樂觀鎖的缺點... 16

五、CAS與synchronized的使用情景... 17

8.HashMap底層實現原理 擴容機制... 17

實現原理:... 17

擴容機制:... 18

Hashmap爲什麼大小是2的冪次?... 19

get方法實現... 20

9.運算符... 20

1.十進制轉二進制... 20

2.二進制轉十進制... 21

3.位異或運算(^)... 21

4.位與運算符(&)... 21

5.位或運算符(|)... 21

6.位非運算符(~)... 21

10.ConcurrentHashMap,put值時key和value都不能爲空... 22

11.Java-五種線程池,四種拒絕策略,三種阻塞隊列... 22

三種阻塞隊列:... 22

四種拒絕策略:... 23

五種線程池:... 23

12.Map、Set、List是否有序... 23

HashSet:... 24

LinkedHashSet:... 24

13.重寫對象的equals()方法... 26

14.Redis的持久化(Rdb. Aof) 26

RDB 方式與 AOF 方式的優勢對比... 26

RDB 方式的優點... 26

AOF 方式的優點... 26

優點對比總結... 27

RDB 方式與 AOF 方式的缺點對比... 27

RDB 方式的缺點... 27

AOF 方式的缺點... 27

缺點對比總結... 27

15.Redis事務使用方法 watch和unwatch. 28

16.主從複製原理(配從不配主slaveof 主機IP 主機端口)... 28

redis主從複製可以根據是否是全量分爲全量同步和增量同步... 28

17.哨兵機制... 28

18.緩存穿透及優化... 29

問題解決:... 29

19.緩存擊穿及優化... 29

解決:... 29

20.MYSQL. 29

UNION.. 29

索引(where的查找速度orderby的排序) 30

優點... 30

缺點... 30

有利於建索引的條件... 30

不利於建索引的情況... 30

操作實例... 31

21.java 字符流與字節流哪個快... 31

22.http與https連接建立... 31

建立連接... 31

tcp的三次揮手和四次揮手... 31

HTTP請求過程... 32

HTTPS. 33

23.數據庫四大特性和事務隔離級別... 33

1、事務的隔離級別... 33

(1)Read uncommitted(未授權讀取、讀未提交):... 34

(2)Read committed(授權讀取、讀提交):... 34

(3)Repeatable read(可重複讀取):... 34

(4)Serializable(序列化):... 34

2、出現的問題:... 34

(1)髒讀... 35

(2)不可重複讀... 35

(3)虛讀(幻讀) 36

3、MySQL數據庫提供的四種隔離級別:... 36

4、事務的四大特性... 38

(1)原子性(Atomicity)... 38

(2)一致性(Consistency)... 38

(3)隔離性(Isolation)... 38

(4)持久性(Durability)... 38

24.處理高併發的六種方法... 39

25. 高併發的解決方案... 39

1.應用和靜態資源分離... 39

2.頁面緩存... 40

3.集羣與分佈式... 40

4. 反向代理... 41

4.1 反向代理服務器和代理服務器的區別... 42

4.2反向代理服務器主要有三個作用:... 42

5. CDN.. 42

6. 底層的優化... 43

7.數據庫集羣和庫表散列... 43

8. 小結... 43

其它簡單總結:... 43

高併發的解決方案... 44

海量數據的解決方案... 45

1 使用緩存... 45

2 頁面靜態化---不想解釋,還有什麼值得去解釋呢?... 45

3 數據庫優化... 45

4 分離數據庫中的活躍數據... 45

5 讀寫分離... 45

26.數據庫讀寫分離,主從同步複製實現方法... 45

實現思路... 45

基礎知識... 46

主從同步複製有以下幾種方式:... 46

主從數據庫設置的具體步驟... 46

1.打開mysql數據庫配置文件... 47

2.在主服務器master上配置開啓Binary log,主要是在[mysqld]下面添加:... 47

3.重啓mysql服務... 47

4.檢查配置效果,進入主數據庫並執行... 47

5.配置從服務器的 my.cnf 47

6.接下來配置兩個數據庫的關聯... 48

27. 如何保證緩存與數據庫的雙寫一致性?... 49

最初級的緩存不一致問題及解決方案... 49

28. 同步和異步,阻塞和非阻塞... 49

29. 在UTF-8中,一個漢字爲什麼需要三個字節?... 50

30.逃逸分析... 51

31. 進程間通信機制有哪些?... 52

32.TCP和UDP的區別和優缺點... 53

1、TCP與UDP區別總結:... 53

2、爲什麼UDP有時比TCP更有優勢?. 54

33. HTTP狀態碼... 54

消息... 54

100 Continue. 54

101 Switching Protocols. 54

102 Processing.. 54

成功... 54

200 OK.. 55

201 Created.. 55

202 Accepted.. 55

203 Non-Authoritative Information.. 55

204 No Content. 55

205 Reset Content. 55

206 Partial Content. 55

207 Multi-Status. 56

重定向... 56

300 Multiple Choices. 56

301 Moved Permanently. 56

302 Move Temporarily. 56

303 See Other. 57

304 Not Modified.. 57

305 Use Proxy. 57

306 Switch Proxy. 57

307 Temporary Redirect 57

請求錯誤... 58

400 Bad Request. 58

401 Unauthorized.. 58

402 Payment Required.. 58

403 Forbidden.. 58

404 Not Found.. 58

405 Method Not Allowed.. 58

406 Not Acceptable. 58

407 Proxy Authentication Required.. 59

408 Request Timeout. 59

409 Conflict. 59

410 Gone. 59

411 Length Required.. 59

412 Precondition Failed.. 59

413 Request Entity Too Large. 59

414 Request-URI Too Long.. 59

415 Unsupported Media Type. 60

416 Requested Range Not Satisfiable. 60

417 Expectation Failed.. 60

418 I'm a teapot. 60

421 Too Many Connections. 60

422 Unprocessable Entity. 60

423 Locked.. 60

424 Failed Dependency. 60

425 Too Early. 60

426 Upgrade Required.. 60

449 Retry With.. 60

451 Unavailable For Legal Reasons. 61

服務器錯誤... 61

500 Internal Server Error. 61

501 Not Implemented.. 61

502 Bad Gateway. 61

503 Service Unavailable. 61

504 Gateway Timeout. 61

505 HTTP Version Not Supported.. 61

506 Variant Also Negotiates. 61

507 Insufficient Storage. 61

509 Bandwidth Limit Exceeded.. 62

510 Not Extended.. 62

600 Unparseable Response Headers. 62

34.DNS原理及其解析過程... 62

DNS 的過程?... 62

Dns服務的工作過程... 62

解析順序... 64

35. HTTP請求報文(請求行、請求頭、請求體)... 64

1.簡介... 64

2.特點... 64

3.HTTP請求報文... 65

請求行:... 65

請求頭:... 65

請求體:... 65

Accept 65

Accept:text/plain. 66

cookie. 66

Referer 66

Cache-Control 66

4.HTTP響應報文... 66

響應行:... 67

響應頭:... 67

響應體:... 67

響應狀態碼... 67

常見的HTTP響應報文頭屬性... 68

ETag. 68

Location. 68

5.cookie機制:... 68

36. MySQL索引查詢失效的幾個情況:... 69

首先,複習一下索引的創建:... 69

索引查詢失效的幾個情況:... 69

37.Mysql 鎖類型和加鎖分析... 70

一、鎖類型介紹:... 70

二、死鎖產生原因和示例... 70

1、產生原因:... 70

2、產生示例:... 71

此類死鎖,產生的幾個前提:... 78

38. 進程所佔的虛擬內存和物理內存是什麼樣的... 78

擴展資料... 79

39.jvm進程所佔用的虛存大於了虛擬機的堆棧設置參數,爲什麼不報錯... 79

40. Mysql怎麼保證一致性的?... 80

41.Mysql怎麼保證原子性的?... 81

42. Mysql怎麼保證持久性的?... 81

43. Mysql怎麼保證隔離性的?... 82

44. Mysql中的MVCC策略... 84

什麼是MVCC?. 84

特點... 84

基本原理... 85

基本特徵... 85

InnoDB存儲引擎MVCC的實現策略... 85

MVCC下InnoDB的增刪查改是怎麼work的... 85

關於Mysql中MVCC的總結... 87

45.mysql分表的3種方法... 88

一,先說一下爲什麼要分表... 88

二,分表... 88

1,做mysql集羣,... 88

2,預先估計會出現大數據量並且訪問頻繁的表,將其分爲若干個表... 89

3,利用merge存儲引擎來實現分表... 90

三,總結一下... 92

46. Mysql分表和分區的區別、分庫分表介紹與區別... 93

分表與分區:... 93

一,什麼是mysql分表,分區... 93

二, mysql分表和分區有什麼區別呢... 93

1.實現方式上... 93

3.提高性能上... 94

三,mysql分表和分區有什麼聯繫呢... 94

分庫與分表:... 95

1 基本思想之什麼是分庫分表?... 95

2 基本思想之爲什麼要分庫分表?... 95

3 分庫分表的實施策略。... 95

4 分庫分表存在的問題。... 97

47. session,cookie和token究竟是什麼... 98

http是一個無狀態協議... 98

cookie和session. 98

注意... 98

小結... 99

token. 99

組成... 99

存放... 99

token認證流程... 99

token可以抵抗csrf,cookie+session不行... 99

分佈式情況下的session和token. 100

總結... 100

48. token 防止csrf 100

驗證 HTTP Referer 字段... 100

在請求地址中添加 token 並驗證... 101

在 HTTP 頭中自定義屬性並驗證... 102

49. 內存泄漏和內存溢出的區別和聯繫... 103

1、內存泄漏memory leak : 103

2、內存溢出 out of memory : 103

3、二者的關係... 103

4、內存泄漏的分類(按發生方式來分類)... 104

5、內存溢出的原因及解決方法:... 104

內存溢出原因:... 104

內存溢出的解決方案:... 105

6.Java內存泄露引起原因... 105

1、靜態集合類引起內存泄露(static 對象是不會被GC回收的):... 106

2、當集合裏面的對象屬性被修改後,再調用remove()方法時不起作用。... 106

3、各種連接... 107

4、單例模式... 107

5、讀取流沒有關閉... 108

6、監聽器... 108

7、String的intern方法... 108

8、 將沒有實現hashCode()和equals()方法的對象加入到HashSet中... 108

7. 查找內存泄漏的方法... 109

3.1 記錄gc日誌... 109

3.2 進行profiling. 109

3.3 代碼審查... 109

50.到底什麼是hash呢?hash碰撞?爲什麼HashMap的初始容量是16?... 109

一 、到底什麼是hash呢?. 109

預備小知識:... 109

二,什麼是hash碰撞... 111

三、碰撞處理... 111

(1)開放尋址法... 112

(2)鏈接法... 114

四,爲什麼HashMap初始容量是2<<3?. 115

51. 線程及與進程的對比... 116

一、爲什麼要引入線程... 116

二、線程... 117

1、線程的基本概念... 117

2、引入線程的好處... 117

3、線程的適用範圍... 117

4、線程分類與執行... 118

三、進程與線程... 119

1、進程和線程的關係... 119

2、線程與進程的區別... 119

3、線程與進程對比... 119

52. Java基本數據類型及所佔字節大小... 121

一、Java基本數據類型... 121

二、各數據類型所佔字節大小... 121

53. MySQL有哪幾種join方式,底層原理是什麼?... 122

 

1.Java運算符優先級

2.HTML,JS,CSS的區別

1、HTML—Hypertext Markup Language

 

超文本標記語言。它通過標記符號來標記要顯示的網頁中的各個部分。網頁文件本身是一種文本文件,通過在文本文件中添加標記符,可以告訴瀏覽器如何顯示其中的內容(比如文字如何處理,畫面如何安排,圖片如何顯示等)。瀏覽器按順序閱讀網頁文件,然後根據標記符解釋和顯示其標記的內容:

<html> <head> <title>HTML</title> </head> <body> </body> </html>

HTML文本中包含了所謂的“鏈接點”HTML利用超鏈接的方法,將各種不同空間的文字信息組織在一起的網狀文本。總的來說,HTML就是整合網頁結構和內容顯示的一種語言。

 

2、CSS—Cascading Style Sheet

 

層疊樣式表單。是將樣式信息與網頁內容分離的一種標記語言。我們在牛腩新聞發佈系統中,我們使用過CSS文件,對一些標籤的樣式進行修改。

 

3、JavaScript

 

Javascript是一種基於對象(Object)和事件驅動(Event Driven)並具有安全性能的腳本語言。使用它的目的是與HTML超文本標記語言、Java腳本語言(Java小程序)一起實現在一個Web頁面中鏈接多個對象,與Web客戶交互作用。例如可以設置鼠標懸停效果,在客戶端驗證表單,創建定製的HTML頁面,顯示警告框,設置cookie等等。

 

3.從輸入URL到網頁呈現的過程

開啓網絡線程到發出一個完整的http請求

到這步,瀏覽器就要開始發送請求了,但是在發送請求之前,需要進行諸如dns解析,tcp/ip建立鏈接,五層因特網協議棧之類的操作。

首先,第一步就是進行dns解析。

此處依次分爲如下幾步:

檢查瀏覽器DNS緩存

檢查本地DNS緩存

檢查HOST文件

向DNS域名服務器查詢

DNS查詢用的是UDP協議,DNS協議工作在應用層,使用53號端口,UDP協議工作在傳輸層

 

TCP/IP請求

http的本質就是tcp/ip請求

 

首先TCP協議有以下特點:

面向連接,可靠傳輸(無差錯,不丟失,不重複且有序)

每個TCP連接只能有兩個端點,即點對點,一對一連接

TCP提供全雙工通信,所以TCP連接的兩端都有緩存

發送方緩存存儲:1.需要發送但還未發送的數據 2.已發送,但還未收到確認的數據

接收方緩存存儲:1.按序到達,但未被應用程序讀取的數據 2.未按序到達的數據

工作在傳輸層(傳輸層的作用是負責應用程序之間的通信,是端到端的通信,這也是爲什麼TCP報文首部固定字段【20字節】中最先存的就是源端口號和目的端口號)

需要了解3次握手規則建立連接以及斷開連接時的四次揮手

 

tcp將http長報文劃分爲短報文,通過三次握手與服務端建立連接,進行可靠傳輸

 

三次握手的步驟:(抽象派)

 

客戶端:喂,你聽得到嗎?

服務端:我聽得到呀,你聽得到我嗎?

客戶端:我能聽到你,今天balabala……

從這個連接建立的過程,有以下幾點知識點:

 

客戶機向服務器發送一個連接請求,這時要將SYN(爲1表示該報文爲連接請求或者連接接收報文)標識爲1,同時隨機選擇一個起始序列號seq = x

不能攜帶數據,但要消耗一個序號

服務器接收到連接請求後,返回確認報文,設置SYN = 1,ACK = 1,seq = y(隨機設置),ack = x + 1(表示x已收到,期待x + 1的到來),表示確認。

SYN = 1, ACK = 0 表示連接請求報文,SYN = 1,ACK = 1 表示連接接收報文

SYN = 1時,報文段不能有數據部分,而且要消耗一個序號

seq爲序號字段,表示本報文數據的第一個字節的序號,不過三次握手前兩次中,是不允許帶數據的

ack爲確認號,表示期望收到對方下一個報文的數據的第一個字節的序號,也就是前面的數據都收到了

這步時,服務器已經開始給連接分配緩存和變量了,而客戶機是在第三步才分配,所以服務器易收到SYN洪泛攻擊

客戶機收到服務器的連接接收報文,還要向服務器給出確認,並且也要給連接分配緩存和變量。此時返回ACK = 1,seq = x + 1,ack = y + 1,此時可以攜帶數據

客戶機是在第三次握手時才分配連接緩存和變量

由於此時不用將SYN設置爲1,所以報文可以攜帶數據

如果不攜帶數據,則不用消耗序號

之後就可以愉快的傳輸數據了,記得ACK得一直設爲1

爲何不是二次握手,四次握手?

簡單來說不是二次握手是這樣:

 

1:A發,B收, B知道A能發

2:B發,A收, A知道B能發收

3:A發,B收, B知道A能收

而不用四次或更多握手,是因爲:

三次是保證雙方互相明確對方能收能發的最低值。

 

理論上講不論握手多少次都不能確認一條信道是“可靠”的,但通過3次握手可以至少確認它是“可用”的,再往上加握手次數不過是提高“它是可用的”這個結論的可信程度。

1

然後,待到斷開連接時,需要進行四次揮手(因爲是全雙工的,所以需要四次揮手)

 

四次揮手的步驟:(抽象派)

 

1. 主動方:我已經關閉了向你那邊的主動通道了,只能被動接收了

2. 被動方:收到通道關閉的信息

3. 被動方:那我也告訴你,我這邊向你的主動通道也關閉了

4. 主動方:最後收到數據,之後雙方無法通信

詳細有以下知識點:

 

客戶端請求關閉連接,發送FIN = 1, seq = u

服務器接收到客戶端的關閉請求,返回ACK,返回ACK = 1, seq = v, ack = u + 1

此時服務器可以關閉讀通道了,因爲客戶端不會再發送數據了

此時服務器還可以繼續發送數據

服務端接收到ACK後,關閉寫通道,它可以不再寫數據了

服務器數據發送數據完畢,也要關閉連接了,發送FIN = 1, ACK = 1, seq = w, ack = u + 1

客戶端收到該消息後,可以關閉讀通道了,因爲服務器不會再發送數據了

客戶端發送最後的ACK,服務端關閉寫通道,關閉連接,發送ACK = 1, seq = u + 1, ack = w + 1

客戶端發送最後的ACK後還要等待2MSL。1.護送最後的ACK,避免服務端沒收到,又發來FIN重傳。2.假如連接立馬釋放,而客戶端和服務器的ip和端口剛好被別的客戶端和服務器佔用,則新的客戶端和服務器會收到不屬於它們的報文。爲此等待2MSL,使得鏈路中的原始報文都消失掉。2MSL的原因是,1MSL等待一方報文消失,1MSL等待報文ACK消失

 

 

DNS查詢用的是UDP協議,DNS協議工作在應用層,使用53號端口,UDP協議工作在傳輸層

 

4.五層協議:

1.應用層(dns,http) DNS解析成IP併發送http請求

 

2.傳輸層(tcp,udp) 建立tcp連接(三次握手)

 

3.網絡層(IP,ARP) IP尋址

 

4.數據鏈路層(PPP) 封裝成幀

 

5.物理層(利用物理介質傳輸比特流) 物理傳輸(然後傳輸的時候通過雙絞線,電磁波等各種介質)

 

 

5.HTTP長連接和短連接原理淺析

1. HTTP協議與TCP/IP協議的關係

HTTP的長連接和短連接本質上是TCP長連接和短連接。HTTP屬於應用層協議,在傳輸層使用TCP協議,在網絡層使用IP協議。IP協議主要解決網絡路由和尋址問題,TCP協議主要解決如何在IP層之上可靠的傳遞數據包,使在網絡上的另一端收到發端發出的所有包,並且順序與發出順序一致。TCP有可靠,面向連接的特點。

2. 如何理解HTTP協議是無狀態的

HTTP協議是無狀態的,指的是協議對於事務處理沒有記憶能力,服務器不知道客戶端是什麼狀態。也就是說,打開一個服務器上的網頁和你之前打開這個服務器上的網頁之間沒有任何聯繫。HTTP是一個無狀態的面向連接的協議,無狀態不代表HTTP不能保持TCP連接,更不能代表HTTP使用的是UDP協議(無連接)。

3. 什麼是長連接、短連接?

在HTTP/1.0中,默認使用的是短連接。也就是說,瀏覽器和服務器每進行一次HTTP操作,就建立一次連接,但任務結束就中斷連接。如果客戶端瀏覽器訪問的某個HTML或其他類型的 Web頁中包含有其他的Web資源,如JavaScript文件、圖像文件、CSS文件等;當瀏覽器每遇到這樣一個Web資源,就會建立一個HTTP會話。

但從 HTTP/1.1起,默認使用長連接,用以保持連接特性。使用長連接的HTTP協議,會在響應頭有加入這行代碼:

Connection:keep-alive

在使用長連接的情況下,當一個網頁打開完成後,客戶端和服務器之間用於傳輸HTTP數據的 TCP連接不會關閉,如果客戶端再次訪問這個服務器上的網頁,會繼續使用這一條已經建立的連接。Keep-Alive不會永久保持連接,它有一個保持時間,可以在不同的服務器軟件(如Apache)中設定這個時間。實現長連接要客戶端和服務端都支持長連接。

HTTP協議的長連接和短連接,實質上是TCP協議的長連接和短連接。

3.1 TCP連接

當網絡通信時採用TCP協議時,在真正的讀寫操作之前,server與client之間必須建立一個連接,當讀寫操作完成後,雙方不再需要這個連接 時它們可以釋放這個連接,連接的建立是需要三次握手的,而釋放則需要4次握手,所以說每個連接的建立都是需要資源消耗和時間消耗的

經典的三次握手示意圖:

http://static.codeceo.com/images/2015/08/a25fb124b65178c39c04e428d1913a15.png

經典的四次握手關閉圖:

http://static.codeceo.com/images/2015/08/f05cb2b32b06337cbd4abe7567dcbbcd.png

3.2 TCP短連接

我們模擬一下TCP短連接的情況,client向server發起連接請求,server接到請求,然後雙方建立連接。client向server 發送消息,server迴應client,然後一次讀寫就完成了,這時候雙方任何一個都可以發起close操作,不過一般都是client先發起 close操作。爲什麼呢,一般的server不會回覆完client後立即關閉連接的,當然不排除有特殊的情況。從上面的描述看,短連接一般只會在 client/server間傳遞一次讀寫操作

短連接的優點是:管理起來比較簡單,存在的連接都是有用的連接,不需要額外的控制手段

3.3 TCP長連接

接下來我們再模擬一下長連接的情況,client向server發起連接,server接受client連接,雙方建立連接。Client與server完成一次讀寫之後,它們之間的連接並不會主動關閉,後續的讀寫操作會繼續使用這個連接。

首先說一下TCP/IP詳解上講到的TCP保活功能,保活功能主要爲服務器應用提供,服務器應用希望知道客戶主機是否崩潰,從而可以代表客戶使用資源。如果客戶已經消失,使得服務器上保留一個半開放的連接,而服務器又在等待來自客戶端的數據,則服務器將應遠等待客戶端的數據,保活功能就是試圖在服務 器端檢測到這種半開放的連接。

如果一個給定的連接在兩小時內沒有任何的動作,則服務器就向客戶發一個探測報文段,客戶主機必須處於以下4個狀態之一:

客戶主機依然正常運行,並從服務器可達。客戶的TCP響應正常,而服務器也知道對方是正常的,服務器在兩小時後將保活定時器復位。

客戶主機已經崩潰,並且關閉或者正在重新啓動。在任何一種情況下,客戶的TCP都沒有響應。服務端將不能收到對探測的響應,並在75秒後超時。服務器總共發送10個這樣的探測 ,每個間隔75秒。如果服務器沒有收到一個響應,它就認爲客戶主機已經關閉並終止連接。

客戶主機崩潰並已經重新啓動。服務器將收到一個對其保活探測的響應,這個響應是一個復位,使得服務器終止這個連接。

客戶機正常運行,但是服務器不可達,這種情況與2類似,TCP能發現的就是沒有收到探查的響應。

3.4 長連接短連接操作過程

短連接的操作步驟是:

建立連接——數據傳輸——關閉連接...建立連接——數據傳輸——關閉連接

長連接的操作步驟是:

建立連接——數據傳輸...(保持連接)...數據傳輸——關閉連接

4. 長連接和短連接的優點和缺點

由上可以看出,長連接可以省去較多的TCP建立和關閉的操作,減少浪費,節約時間。對於頻繁請求資源的客戶來說,較適用長連接。不過這裏存在一個問題,存活功能的探測週期太長,還有就是它只是探測TCP連接的存活,屬於比較斯文的做法,遇到惡意的連接時,保活功能就不夠使了。在長連接的應用場景下,client端一般不會主動關閉它們之間的連接,Client與server之間的連接如果一直不關閉的話,會存在一個問題,隨着客戶端連接越來越多,server早晚有扛不住的時候,這時候server端需要採取一些策略,如關閉一些長時間沒有讀寫事件發生的連接,這樣可 以避免一些惡意連接導致server端服務受損;如果條件再允許就可以以客戶端機器爲顆粒度,限制每個客戶端的最大長連接數,這樣可以完全避免某個蛋疼的客戶端連累後端服務。

短連接對於服務器來說管理較爲簡單,存在的連接都是有用的連接,不需要額外的控制手段。但如果客戶請求頻繁,將在TCP的建立和關閉操作上浪費時間和帶寬。

長連接和短連接的產生在於client和server採取的關閉策略,具體的應用場景採用具體的策略,沒有十全十美的選擇,只有合適的選擇。

5. 什麼時候用長連接,短連接?

長連接多用於操作頻繁,點對點的通訊,而且連接數不能太多情況,。每個TCP連接都需要三步握手,這需要時間,如果每個操作都是先連接,再操作的話那麼處理速度會降低很多,所以每個操作完後都不斷開,次處理時直接發送數據包就OK了,不用建立TCP連接。例如:數據庫的連接用長連接, 如果用短連接頻繁的通信會造成socket錯誤,而且頻繁的socket 創建也是對資源的浪費。

而像WEB網站的http服務一般都用短鏈接,因爲長連接對於服務端來說會耗費一定的資源,而像WEB網站這麼頻繁的成千上萬甚至上億客戶端的連接用短連接會更省一些資源,如果用長連接,而且同時有成千上萬的用戶,如果每個用戶都佔用一個連接的話,那可想而知吧。所以併發量大,但每個用戶無需頻繁操作情況下需用短連好。

 

6.Java中List幾種去重方式的比較

用JDK提供的Set對元素進行去重

Set<Integer> set = new HashSet<>(list);

用普通方法對List進行去重 for()循環剔除重複元素

用JDK1.8 Stream中對List進行去重

list.stream().distinct();

 

 

7.樂觀鎖和悲觀鎖

一、概念

悲觀鎖:總是假設最壞的情況,認爲競爭總是存在,每次拿數據的時候都認爲會被修改,因此每次都會先上鎖。其他線程阻塞等待釋放鎖。
樂觀鎖:總是假設最好的情況,認爲競爭總是不存在,每次拿數據的時候都認爲不會被修改,因此不會先上鎖,在最後更新的時候比較數據有無更新,可通過版本號或CAS實現。

二、兩種鎖的使用場景

悲觀鎖:用於寫比較多的情況,避免了樂觀鎖不斷重試從而降低性能
樂觀鎖:用於讀比較多的情況,避免了不必要的加鎖的開銷

三、樂觀鎖的實現方式

版本號機制:
一般通過在數據庫增加version列實現。

CAS(compare and swap):

需要三個操作數
需要讀寫的內存值 V
進行比較的值 A
擬寫入的新值 B

當且僅當 V 的值等於 A時,CAS通過原子方式用新值B來更新V的值,否則不會執行任何操作(比較和替換是一個原子操作)。一般情況下是一個自旋操作,即不斷的重試。

四、樂觀鎖的缺點

1.ABA問題
2.自旋時間CPU開銷大
3.JUC包下的原子類只能包含一個變量的原子操作,但是1.5之後的AtomicReference,能夠保證引用對象的原子性。

五、CAS與synchronized的使用情景

CAS屬於樂觀鎖,適用於寫比較少的情況,衝突較少
synchronized屬於悲觀鎖,適用於衝突寫比較多的情況,衝突較多

競爭較少的場景:synchronized會阻塞和喚醒線程並在用戶態和內核態切換浪費消耗cpu資源。CAS基於硬件實現,不需要進入內核,不需要切換線程,操作自旋機率較少,因此可以獲得更高的性能。
競爭嚴重的場景:CAS自旋的概率會比較大,從而浪費更多的CPU資源,效率低於synchronized。

8.HashMap底層實現原理 擴容機制

實現原理:

HashMap本質是一個一定長度的數組,數組中存放的是鏈表。

https://img-blog.csdn.net/20180620162511570?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbmRlcmx1c3RMZWU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70

它是一個Entry類型的數組,Entry的源碼:

 

static class Entry<K,V> implements Map.Entry<K,V> { 

        final K key; 

        V value; 

        final int hash; 

        Entry<K,V> next; 

}

其中存放了Key,Value,hash值,還有指向下一個元素的引用。

 

當向HashMap中put(key,value)時,會首先通過hash算法計算出存放到數組中的位置,比如位置索引爲i,將其放入到Entry[i]中,如果這個位置上面已經有元素了,那麼就將新加入的元素放在鏈表的頭上,最先加入的元素在鏈表尾。比如,第一個鍵值對A進來,通過計算其key的hash得到的index=0,記做:Entry[0] = A。一會後又進來一個鍵值對B,通過計算其index也等於0,現在怎麼辦?HashMap會這樣做:B.next = A,Entry[0] = B,如果又進來C,index也等於0,那麼C.next = B,Entry[0] = C;這樣我們發現index=0的地方其實存取了A,B,C三個鍵值對,他們通過next這個屬性鏈接在一起,也就是說數組中存儲的是最後插入的元素。

https://img-blog.csdn.net/20180620164825363?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbmRlcmx1c3RMZWU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70

HashMap的get(key)方法是:首先計算key的hashcode,找到數組中對應位置的某一元素,然後通過key的equals方法在對應位置的鏈表中找到需要的元素。從這裏我們可以想象得到,如果每個位置上的鏈表只有一個元素,那麼hashmap的get效率將是最高的。所以我們需要讓這個hash算法儘可能的將元素平均的放在數組中每個位置上。

 

擴容機制:

 

當HashMap中的元素越來越多的時候,hash衝突的機率也就越來越高,因爲數組的長度是固定的。所以爲了提高查詢的效率,就要對HashMap的數組進行擴容。

 

HashMap的容量由一下幾個值決定:

 

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;      // HashMap初始容量大小(16)

static final int MAXIMUM_CAPACITY = 1 << 30;               // HashMap最大容量

transient int size;                                       // The number of key-value mappings contained in this map

 

static final float DEFAULT_LOAD_FACTOR = 0.75f;          // 負載因子

 

HashMap的容量size乘以負載因子[默認0.75] = threshold;  // threshold即爲開始擴容的臨界值

 

transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;    // HashMap的基本構成Entry數組

當HashMap中的元素個數超過數組大小(數組總大小length,不是數組中個數size)*loadFactor時,就會進行數組擴容,loadFactor的默認值爲0.75,這是一個折中的取值。也就是說,默認情況下,數組大小爲16,那麼當HashMap中元素個數超過16*0.75=12(這個值就是代碼中的threshold值,也叫做臨界值)的時候,就把數組的大小擴展爲 2*16=32,即擴大一倍,然後重新計算每個元素在數組中的位置。

 

0.75這個值成爲負載因子,那麼爲什麼負載因子爲0.75呢?這是通過大量實驗統計得出來的,如果過小,比如0.5,那麼當存放的元素超過一半時就進行擴容,會造成資源的浪費;如果過大,比如1,那麼當元素滿的時候才進行擴容,會使get,put操作的碰撞機率增加。

 

HashMap中擴容是調用resize()方法,方法源碼:

 

void resize(int newCapacity) {

    Entry[] oldTable = table;

    int oldCapacity = oldTable.length;

    //如果當前的數組長度已經達到最大值,則不在進行調整

    if (oldCapacity == MAXIMUM_CAPACITY) {

        threshold = Integer.MAX_VALUE;

        return;

    }

    //根據傳入參數的長度定義新的數組

    Entry[] newTable = new Entry[newCapacity];

    //按照新的規則,將舊數組中的元素轉移到新數組中

    transfer(newTable);

    table = newTable;

    //更新臨界值

    threshold = (int)(newCapacity * loadFactor);

}

//舊數組中元素往新數組中遷移

void transfer(Entry[] newTable) {

    //舊數組

    Entry[] src = table;

    //新數組長度

    int newCapacity = newTable.length;

    //遍歷舊數組

    for (int j = 0; j < src.length; j++) {

        Entry<K,V> e = src[j];

        if (e != null) {

            src[j] = null;

            do {

                Entry<K,V> next = e.next;

                int i = indexFor(e.hash, newCapacity);//放在新數組中的index位置

                e.next = newTable[i];//實現鏈表結構,新加入的放在鏈頭,之前的的數據放在鏈尾

                newTable[i] = e;

                e = next;

            } while (e != null);

        }

    }

}

可以看到HashMap不是無限擴容的,當達到了實現預定的MAXIMUM_CAPACITY,就不再進行擴容。

 

 

 

Hashmap爲什麼大小是2的冪次?

 

因爲在計算元素該存放的位置的時候,用到的算法是將元素的hashcode與當前map長度-1進行與運算。源碼:

 

static int indexFor(int h, int length) {

    // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";

    return h & (length-1);

}

如果map長度爲2的冪次,那長度-1的二進制一定爲11111...這種形式,進行與運算就看元素的hashcode,但是如果map的長度不是2的冪次,比如爲15,那長度-1就是14,二進制爲1110,無論與誰相與最後一位一定是0,0001,0011,0101,1001,1011,0111,1101這幾個位置就永遠都不能存放元素了,空間浪費相當大。也增加了添加元素是發生碰撞的機會。減慢了查詢效率。所以Hashmap的大小是2的冪次。

 

 

 

get方法實現

 

Hashmap get一個元素是,是計算出key的hashcode找到對應的entry,這個時間複雜度爲O(1),然後通過對entry中存放的元素key進行equal比較,找出元素,這個的時間複雜度爲O(m),m爲entry的長度。

 

 

 

9.運算符

1.十進制轉二進制

原理:給定的數循環除以2,直到商爲0或者1爲止。將每一步除的結果的餘數記錄下來,然後反過來就得到相應的二進制了。

比如8轉二進制,第一次除以2等於4(餘數0),第二次除以2等於2(餘數0),第三次除以2等於1(餘數0),最後餘數1,得到的餘數依次是0 0 0 1 ,

反過來就是1000,計算機內部表示數的字節長度是固定的,比如8位,16位,32位。所以在高位補齊,java中字節碼是8位的,所以高位補齊就是00001000.

寫法位(8)10=(00001000)2;

代碼實現:

 

 1 package sourceCode.hashMap;

 2

 3 public class mapHashCodeTest {

 4     public static void main(String[] args) {

 5         String str = toBinary(8);

 6         System.out.println(str);

 7     }

 8

 9     static String toBinary(int num) {

10         String str = "";

11         while (num != 0) {

12             str = num % 2 + str;

13             num = num / 2;

14         }

15         return str;

16     }

17

18 }

 

運行結果:1000

2.二進制轉十進制

計算也很簡單,比如8的二進制表示位00001000,去掉補齊的高位就是1000.此時從個位開始計算2的冪(個位是0,依次往後推)乘以對應位數上的數,然後得到的值想加

於是有了,(2的0次冪)*0+(2的1次冪)*0+(2的2次冪)*0+(2的3次冪)*1 = 8

代碼實現,直接調用Integer.parseInt("",2);

1 System.out.println(Integer.parseInt("1000",2));

運行結果:8

3.位異或運算(^)

運算規則是:兩個數轉爲二進制,然後從高位開始比較,如果相同則爲0,不相同則爲1。

比如:8^11.

8轉爲二進制是1000,11轉爲二進制是1011.從高位開始比較得到的是:0011.然後二進制轉爲十進制,就是Integer.parseInt("0011",2)=3;

4.位與運算符(&)

運算規則:兩個數都轉爲二進制,然後從高位開始比較,如果兩個數都爲1則爲1,否則爲0。

比如:129&128.

129轉換成二進制就是10000001,128轉換成二進制就是10000000。從高位開始比較得到,得到10000000,即128.

 5.位或運算符(|)

運算規則:兩個數都轉爲二進制,然後從高位開始比較,兩個數只要有一個爲1則爲1,否則就爲0。

比如:129|128.

129轉換成二進制就是10000001,128轉換成二進制就是10000000。從高位開始比較得到,得到10000001,即129.

 

6.位非運算符(~)

運算規則:如果位爲0,結果是1,如果位爲1,結果是0.

比如:~37

在Java中,所有數據的表示方法都是以補碼的形式表示,如果沒有特殊說明,Java中的數據類型默認是int,int數據類型的長度是8位,一位是四個字節,就是32字節,32bit.

8轉爲二進制是100101.

補碼後爲: 00000000 00000000 00000000 00100101

取反爲:    11111111 11111111 11111111 11011010

因爲高位是1,所以原碼爲負數,負數的補碼是其絕對值的原碼取反,末尾再加1。

因此,我們可將這個二進制數的補碼進行還原: 首先,末尾減1得反碼:11111111 11111111 11111111 11011001 其次,將各位取反得原碼:

00000000 00000000 00000000 00100110,此時二進制轉原碼爲38

所以~37 = -38. 

 

 

<<      :     左移運算符,num << 1,相當於num乘以2

>>      :     右移運算符,num >> 1,相當於num除以2

>>>    :     無符號右移,忽略符號位,空位都以0補齊

10.ConcurrentHashMap,put值時key和value都不能爲空

if (key == null || value == null) throw new NullPointerException();

調用hashCode時調用傳入對象的hashcode,如果沒有重寫hashcode,默認調用object的native的hashcode方法

 

 

11.Java-五種線程池,四種拒絕策略,三種阻塞隊列

三種阻塞隊列:

    BlockingQueue<Runnable> workQueue = null;

    workQueue = new ArrayBlockingQueue<>(5) ;//基於數組的先進先出隊列,有界

    workQueue = new LinkedBlockingQueue<>() ;//基於鏈表的先進先出隊列,無界

    workQueue = new SynchronousQueue<>();    //無緩衝的等待隊列,無界

四種拒絕策略:

    RejectedExecutionHandler rejected = null;

    rejected = new ThreadPoolExecutor.AbortPolicy();//默認,隊列滿了丟任務拋出異常

    rejected = new ThreadPoolExecutor.DiscardPolicy();//隊列滿了丟任務不異常

    rejected = new ThreadPoolExecutor.DiscardOldestPolicy();//將最早進入隊列的任務刪,之後再嘗試加入隊列

    rejected = new ThreadPoolExecutor.CallerRunsPolicy();//如果添加到線程池失敗,那麼主線程會自己去執行該任務

五種線程池:

    ExecutorService threadPool = null;

    threadPool = Executors.newCachedThreadPool();//有緩衝的線程池,線程數 JVM 控制

    threadPool = Executors.newFixedThreadPool(3);//固定大小的線程池

    threadPool = Executors.newScheduledThreadPool(2);

    threadPool = Executors.newSingleThreadExecutor();//單線程的線程池,只有一個線程在工作

threadPool = new ThreadPoolExecutor();//默認線程池,可控制參數比較多  

 

12.Map、Set、List是否有序

首先明確概念:這裏的有序和無序不是指集合中的排序,而是是否按照元素添加的順序來存儲對象。

list是按照元素的添加順序來存儲對象的,因此是有序的。他的實現類ArrayList、LinkedList、Vector都是有序的。

HashMap JDK1.8中對hash算法做了調整,key是int型的或者可以轉換爲int型的,算法會自動按排序的結果插入到map中去。 如下圖:

Map<String,String> map=new HashMap<>();
map.put("13","13");
map.put("14","14");
map.put("12", "12");
map.put("15","15");
int size=map.size();

Map是無序的,它的存儲結構是哈希表<key,value>鍵值對,map中插入元素是根據key計算出的哈希值來存儲元素的,因此他不是按照元素的添加順序來存儲對象的,所以Map是無序的。它的實現類有:HashMap、TableMap和TreeMap。

 

其中LinkedHashMap是有序的,hashMap用來保證存儲的值鍵值對,list用來保證插入的順序和存儲的順序一致。

 

   Set是無序的,並且set中的元素不能重複。set的底層實現其實是Map,它是計算key的哈希值來確定元素在數組中的存放位置,所以是無序的,應爲在Map中key的值不能重複,所以set中的元素不能重複。它的實現類有:hashSet、TreeSet。

HashSet:

        HashSet類按照哈希算法來存取集合中的對象,存取速度比較快。

        1、Set中是不能出現重複數據的。

        2、Set中可以出現空數據。

        3、Set中的數據是無序的。

LinkedHashSet:

     1、LinkedHashSet是有序的(不同於HashSet)。

       2、LinkedHashSet在迭代訪問Set中的全部元素時,性能比HashSet好,但是插入時性能稍微遜色於HashSet。

    TreeSet:

        1.不能寫入空數據

             2.寫入的數據是有序的。

             3.不寫入重複數據

 

 

 

 

 

 

 

 

 

13.重寫對象的equals()方法

首先直接使用==判斷這兩個對象的地址是否相同,如果相同,直接返回true;

判斷輸入參數對象的類是否instanceof當前對象所在的類,如果不是,直接返回false;

然後判斷當前對象和要與之equals()的對象是否爲空,1)爲空判斷當前對象是否爲空,也爲空,返回true;2)不爲空,調用Object的equals 方法。如下                      return o1 == null ? o2 == null : o1.equals(o2);

 

 

14.Redis的持久化(Rdb. Aof)

Rdb(Redis Database)

RDB持久化方式能夠在指定的時間間隔能對你的數據進行快照存儲(快照存儲到內盤);

Aof(Append Only File)

以日誌的形式記錄每個寫操作,

AOF持久化方式記錄每次對服務器寫的操作,當服務器重啓的時候會重新執行這些命令來恢復原始的數據,AOF命令以redis協議追加保存每次寫的操作到文件末尾.Redis還能對AOF文件進行後臺重寫,使得AOF文件的體積不至於過大。

RDB 方式與 AOF 方式的優勢對比

RDB 方式的優點 

RDB 是一個非常緊湊的文件,它保存了某個時間點的數據集,非常適用於數據集的備份,比如你可以在每個小時報保存一下過去24小時內的數據,同時每天保存過去30天的數據,這樣即使出了問題你也可以根據需求恢復到不同版本的數據集。

RDB 是一個緊湊的單一文件,很方便傳送到另一個遠端數據中心,非常適用於災難恢復。

RDB 在保存 RDB 文件時父進程唯一需要做的就是 fork 出一個子進程,接下來的工作全部由子進程來做,父進程不需要再做其他 IO 操作,所以 RDB 持久化方式可以最大化 Redis 的性能。

與AOF相比,在恢復大的數據集的時候,RDB 方式會更快一些。

當 Redis 需要保存 dump.rdb 文件時, 服務器執行以下操作:

Redis 調用forks. 同時擁有父進程和子進程。

子進程將數據集寫入到一個臨時 RDB 文件中。

當子進程完成對新 RDB 文件的寫入時,Redis 用新 RDB 文件替換原來的 RDB 文件,並刪除舊的 RDB 文件。

這種工作方式使得 Redis 可以從寫時複製(copy-on-write)機制中獲益。

AOF 方式的優點

 使用AOF 會讓你的Redis更加耐久:

 你可以使用不同的 fsync 策略:無 fsync、每秒 fsync 、每次寫的時候 fsync .使用默認的每秒 fsync 策略, Redis 的性能依然很好( fsync 是由後臺線程進行處理的,主線程會盡力處理客戶端請求),一旦出現故障,你最多丟失1秒的數據。

AOF文件是一個只進行追加的日誌文件,所以不需要寫入seek,即使由於某些原因(磁盤空間已滿,寫的過程中宕機等等)未執行完整的寫入命令,你也也可使用redis-check-aof工具修復這些問題。

Redis 可以在 AOF 文件體積變得過大時,自動地在後臺對 AOF 進行重寫: 重寫後的新 AOF 文件包含了恢復當前數據集所需的最小命令集合。 整個重寫操作是絕對安全的,因爲 Redis 在創建新 AOF 文件的過程中,會繼續將命令追加到現有的 AOF 文件裏面,即使重寫過程中發生停機,現有的 AOF 文件也不會丟失。 而一旦新 AOF 文件創建完畢,Redis 就會從舊 AOF 文件切換到新 AOF 文件,並開始對新 AOF 文件進行追加操作。

AOF 文件有序地保存了對數據庫執行的所有寫入操作, 這些寫入操作以 Redis 協議的格式保存, 因此 AOF 文件的內容非常容易被人讀懂, 對文件進行分析(parse)也很輕鬆。 導出(export) AOF 文件也非常簡單: 舉個例子, 如果你不小心執行了 FLUSHALL 命令, 但只要 AOF 文件未被重寫, 那麼只要停止服務器, 移除 AOF 文件末尾的 FLUSHALL 命令, 並重啓 Redis , 就可以將數據集恢復到 FLUSHALL 執行之前的狀態。

優點對比總結

RDB 方式可以保存過去一段時間內的數據,並且保存結果是一個單一的文件,可以將文件備份到其他服務器,並且在回覆大量數據的時候,RDB 方式的速度會比 AOF 方式的回覆速度要快。

AOF 方式默認每秒鐘備份1次,頻率很高,它的操作方式是以追加的方式記錄日誌而不是數據,並且它的重寫過程是按順序進行追加,所以它的文件內容非常容易讀懂。可以在某些需要的時候打開 AOF 文件對其編輯,增加或刪除某些記錄,最後再執行恢復操作。

RDB 方式與 AOF 方式的缺點對比

RDB 方式的缺點

如果你希望在 Redis 意外停止工作(例如電源中斷)的情況下丟失的數據最少的話,那麼 RDB 不適合你.雖然你可以配置不同的save時間點(例如每隔 5 分鐘並且對數據集有 100 個寫的操作),是 Redis 要完整的保存整個數據集是一個比較繁重的工作,你通常會每隔5分鐘或者更久做一次完整的保存,萬一在 Redis 意外宕機,你可能會丟失幾分鐘的數據。

RDB 需要經常 fork 子進程來保存數據集到硬盤上,當數據集比較大的時候, fork 的過程是非常耗時的,可能會導致 Redis 在一些毫秒級內不能響應客戶端的請求。如果數據集巨大並且 CPU 性能不是很好的情況下,這種情況會持續1秒, AOF 也需要 fork ,但是你可以調節重寫日誌文件的頻率來提高數據集的耐久度。

AOF 方式的缺點

對於相同的數據集來說,AOF 文件的體積通常要大於 RDB 文件的體積。

根據所使用的 fsync 策略,AOF 的速度可能會慢於 RDB 。 在一般情況下, 每秒 fsync 的性能依然非常高, 而關閉 fsync 可以讓 AOF 的速度和 RDB 一樣快, 即使在高負荷之下也是如此。 不過在處理巨大的寫入載入時,RDB 可以提供更有保證的最大延遲時間(latency)。

缺點對比總結

RDB 由於備份頻率不高,所以在回覆數據的時候有可能丟失一小段時間的數據,而且在數據集比較大的時候有可能對毫秒級的請求產生影響。

AOF 的文件提及比較大,而且由於保存頻率很高,所以整體的速度會比 RDB 慢一些,但是性能依舊很高。

Redis 持久化 之 AOF 和 RDB 同時開啓,Redis聽誰的?

聽AOF的,RDB與AOF同時開啓 默認無腦加載AOF的配置文件
相同數據集,AOF文件要遠大於RDB文件,恢復速度慢於RDB
AOF運行效率慢於RDB,但是同步策略效率好,不同步效率和RDB相同

15.Redis事務使用方法 watch和unwatch

執行MULTI後,在EXEC或DISCARD之前發送的所有命令都會被Redis按順序緩存起來,並返回QUEUED。執行EXEC後,Redis取出緩存的命令開始執行,並返回每一條命令的返回值。

使用WATCH監控的key如果發生了變化,事務將被打斷。在EXEC或DISCARD執行後,WATCH監控自動失效,否則使用UNWATCH取消監控。

①執行MULTI後不能再執行WATCH,否則返回錯誤“WATCH inside MULTI is not allowed”;

②如果命令有語法錯誤Redis會報錯,但是運行時出現的錯誤會被Redis忽略不影響後面的執行,需要自己確保不出現運行時錯誤;

16.主從複製原理(配從不配主slaveof 主機IP 主機端口)

主機掛了,從機進入等待狀態,主機回覆之後從機依舊作爲從機;

從機掛了重新啓動後從機狀態清除,變爲主機狀態,必須重新設置爲主機的從機或者在配置文件中寫入主從關係。

Info replication 查看機器的主從狀態

反客爲主:slaveof no one (讓當前從機變爲主機)但其他從機都要手動改爲主機的從機

主從多個redis集合在一起,以一個master多個slave爲模式對外提供服務,配置master可讀可寫,配置slave提供讀,這樣就即能保證redis集羣中的數據同步,又可以實現redis的讀寫分離,如果寫比較多的情況一般就以異步的形式提供服務

redis主從複製可以根據是否是全量分爲全量同步和增量同步

全量同步,一般發生在slave初始化階段,這時slave需要將master上的所有數據都複製一份增量同步,slave初始化後開始正常工作時主服務器發生的寫操作同步到從服務器的過程,增量複製的過程主要是主服務器每執行一個寫命令就會向從服務器發送相同的寫命令,從服務器接收並執行收到的寫命令

redis主從同步策略主從剛剛連接的時候,進行全量同步;全同步結束後,進行增量同步。如果有需

要,slave 在任何時候都可以發起全量同步。redis 策略是,無論如何,首先會嘗試進行增量同步,如不成功,要求從機進行全量同步

注意:如果多個slave斷線了,需要重啓的時候,因爲只要slave啓動,就會發送sync請求和主機全量同步,當多個同時出現的時候,可能會導致Master IO劇增宕機

17.哨兵機制

哨兵是一個分佈式系統,可以在一個架構中運行多個哨兵,該系統執行以下三個任務:

監控:哨兵會不斷地檢查master和slave是否運行正常,只需配置主節點的監控即可,通過向主節點發送info,獲取從節點的信息,並當有新的從節點加入時可以馬上感知

提醒:當被監控的redis出現問題時,哨兵通過API向管理員或其他程序發送通知

自動故障遷移:當一個master不能正常工作時,它會將失效master中的一個slave 升級爲新的master(這個master可以進行讀寫操作),並讓失效master的其他 slave改爲複製新的master,當客戶端試圖連接到失效master時,集羣也會向客戶端返回新的master的地址,使得集羣可以使用master代替失效的master,如果之前的主服務器恢復,會自動跟隨新選舉出來的master成爲slave

哨兵的工作方式:每隔一秒哨兵會向它所知的master,slave以及其他哨兵發送一次ping命令做一次心跳檢測,如果ping的時間超過指定值,哨兵節點則認爲該節點錯誤或下線,主觀下線

如果一個master被標記爲主觀下線,則監視這個master的所有哨兵都要以每秒一次的頻率確認master的確進入了主觀下線狀態,當有足夠數量的哨兵(大於配置文件的值)確認master進入主觀下線狀態,則master會標記爲客觀下線

18.緩存穿透及優化

緩存穿透是指查詢一個不存在的key數據,由於緩存沒命中需要從數據庫查詢,查不到數據則不寫入緩存,這將導致這個不存在的數據每次請求都要到數據庫去查詢,流量大時DB會掛掉,這也是經常提的緩存命中率

問題解決:

1.如果查詢數據庫也爲空,直接設置一個默認值存放到緩存,這樣第二次到緩衝中獲取就有值了,而不會繼續訪問數據庫,這種辦法最簡單粗暴

2.在控制層對要查詢的key進行校驗,不符合則丟棄,然後再放行給後面的正常緩存處理邏輯

19. 緩存雪崩及優化

緩存雪崩是由於大量的key設置了相同的過期時間,原有緩存失效,新緩存未到期間。所有請求都去查詢數據庫,而對數據庫CPU和內存造成巨大壓力,嚴重的會造成數據庫宕機造成整個系統崩潰

爲什麼設置過期時間:比如我們在發送手機驗證碼的時候,將手機號作爲redis key,驗證碼作爲redis value存儲在redis中,並設置過期時間爲60秒,如果60秒的時間到了,懂了沒?

解決:

1.一般併發量不是特別多的時候,使用最多的解決方案是加鎖排隊

2.高併發下給每一個緩存數據增加相應的緩存標記,用來記錄緩存的是否失效,如果緩存標記失效,則更新數據緩存

3.簡單有效的方法:緩存失效時間分散開,比如我們可以在原有的失效時間基礎上增加一個隨機值,比如1-5分鐘隨機,這樣每一個緩存的過期時間的重複率就會降低

20.MYSQL

UNION

UNION 操作符用於合併兩個或多個 SELECT 語句的結果集。

請注意,UNION 內部的 SELECT 語句必須擁有相同數量的列。列也必須擁有相似的數據類型。同時,每條 SELECT 語句中的列的順序必須相同。

註釋:默認地,UNION 操作符選取不同的值。如果允許重複的值,請使用 UNION ALL。

UNION 結果集中的列名總是等於 UNION 中第一個 SELECT 語句中的列名。

mysql中join的7種連接方式

mysql中join的7種連接方式https://blog.csdn.net/qq_32296307/article/details/81812589.

索引(where的查找速度orderby的排序)

索引是對數據庫表中一列或多列的值進行排序的一種數據結構,使用索引可快速訪問數據庫表中的特定信息。索引的一個主要目的就是加快檢索表中數據,亦即能協助信息搜索者儘快的找到符合限制條件的記錄ID的輔助數據結構

數據庫索引好比是一本書前面的目錄,能加快數據庫的查詢速度。

建立索引的目的是加快對錶中記錄的查找或排序。但爲表設置索引要付出代價的:一是增加了數據庫的存儲空間,二是在插入和修改數據時要花費較多的時間(因爲索引也要隨之變動)。

優點

在設計數據庫時,通過創建一個惟一的索引,能夠在索引和信息之間形成一對一的映射式的對應關係,增加數據的惟一性特點。

能提高數據的搜索及檢索速度,符合數據庫建立的初衷。

能夠加快表與表之間的連接速度,這對於提高數據的參考完整性方面具有重要作用。

在信息檢索過程中,若使用分組及排序子句進行時,通過建立索引能有效的減少檢索過程中所需的分組及排序時間,提高檢索效率。

建立索引之後,在信息查詢過程中可以使用優化隱藏器,這對於提高整個信息檢索系統的性能具有重要意義。

缺點

在數據庫建立過程中,需花費較多的時間去建立並維護索引,特別是隨着數據總量的增加,所花費的時間將不斷遞增。

在數據庫中創建的索引需要佔用一定的物理存儲空間,這其中就包括數據表所佔的數據空間以及所創建的每一個索引所佔用的物理空間,如果有必要建立起聚簇索引,所佔用的空間還將進一步的增加

在對錶中的數據進行修改時,例如對其進行增加、刪除或者是修改操作時,索引還需要進行動態的維護,這給數據庫的維護速度帶來了一定的麻煩。

有利於建索引的條件

在經常需要搜索的列上,可以加快搜索的速度;

在作爲主鍵的列上,強制該列的唯一性和組織表中數據的排列結構;

在經常用在連接的列上,這些列主要是一些外鍵,可以加快連接的速度;在經常需要根據範圍進行搜索的列上創建索引,因爲索引已經排序,其指定的範圍是連續的;

在經常需要排序的列上創建索引,因爲索引已經排序,這樣查詢可以利用索引的排序,加快排序查詢時間;

在經常使用在WHERE子句中的列上面創建索引,加快條件的判斷速度。

不利於建索引的情況

第一,對於那些在查詢中很少使用或者參考的列不應該創建索引。這是因爲,既然這些列很少使用到,因此有索引或者無索引,並不能提高查詢速度。相反,由於增加了索引,反而降低了系統的維護速度和增大了空間需求。

第二,對於那些只有很少數據值的列也不應該增加索引。這是因爲,由於這些列的取值很少,例如人事表的性別列,在查詢的結果中,結果集的數據行佔了表中數據行的很大比例,即需要在表中搜索的數據行的比例很大。增加索引,並不能明顯加快檢索速度。

第三,對於那些定義爲text, image和bit數據類型的列不應該增加索引。這是因爲,這些列的數據量要麼相當大,要麼取值很少,不利於使用索引。

第四,當修改性能遠遠大於檢索性能時,不應該創建索引。這是因爲,修改性能和檢索性能是互相矛盾的。當增加索引時,會提高檢索性能,但是會降低修改性能。當減少索引時,會提高修改性能,降低檢索性能。因此,當修改操作遠遠多於檢索操作時,不應該創建索引。

操作實例

1. SELECT * FROM mytable WHERE category_id=1;

最直接的應對之道,是爲category_id建立一個簡單的索引

CREATE INDEX mytable_categoryid ON mytable (category_id);

OK.如果有不止一個選擇條件呢?例如:

SELECT * FROM mytable WHERE category_id=1 AND user_id=2;

第一反應可能是,再給user_id建立一個索引。不好,這不是一個最佳的方法。可以建立多重的索引

CREATE INDEX mytable_categoryid_userid ON mytable(category_id,user_id);

-----使用orderby------

21.java 字符流與字節流哪個快

Java 流在處理上分爲字符流和字節流。字符流處理的單元爲 2 個字節的 Unicode 字符,分別操作字符、字符數組或字符串,而字節流處理單元爲 1 個字節,操作字節和字節數組。

Java 內用 Unicode 編碼存儲字符,字符流處理類負責將外部的其他編碼的字符流和 java 內 Unicode 字符流之間的轉換。而類 InputStreamReader 和 OutputStreamWriter 處理字符流和字節流的轉換。字符流(一次可以處理一個緩衝區)一次操作比字節流(一次一個字節)效率高。

22.http與https連接建立

HTTPS是在HTTP的基礎上和ssl/tls證書結合起來的一種協議,保證了傳輸過程中的安全性,減少了被惡意劫持的可能.很好的解決了http的三個缺點(被監聽、被篡改、被僞裝)。

建立連接

  • HTTP和HTTPS都需要在建立連接的基礎上來進行數據傳輸,是基本操作
  • 當客戶在瀏覽器中輸入網址的並且按下回車,瀏覽器會在瀏覽器DNS緩存,本地DNS緩存,和Hosts中尋找對應的記錄,如果沒有獲取到則會請求DNS服務來獲取對應的ip
  • 當獲取到ip後,tcp連接會進行三次握手建立連接

tcp的三次揮手和四次揮手

三次揮手(建立連接)

  • 第一次:建立連接時,客戶端發送SYN包(syn=j)到服務器,並進入SYN_SEND狀態,等待服務器確認;
  • 第二次:服務器收到SYN包,向客戶端返回ACK(ack=j+1),同時自己也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RCVD狀態;
  • 第三次:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手。
  • 完成三次握手,客戶端與服務器開始傳送數據,也就是ESTABLISHED狀態。
  • 三次握手保證了不會建立無效的連接,從而浪費資源。

四次揮手(斷開連接)

  • 第一次: TCP客戶端發送一個FIN,用來關閉客戶到服務器的數據傳送。
  • 第二次:服務器收到這個FIN,它發回一個ACK,確認序號爲收到的序號加1。和SYN一樣,一個FIN將佔用一個序號。
  • 第三次:服務器關閉客戶端的連接,發送一個FIN給客戶端。
  • 第四次:客戶端發回ACK報文確認,並將確認序號設置爲收到序號加1。

https://images2017.cnblogs.com/blog/1260476/201711/1260476-20171116161802952-584681349.png

 

HTTP請求過程

  • 建立連接完畢以後客戶端會發送響應給服務端
  • 服務端接受請求並且做出響應發送給客戶端
  • 客戶端收到響應並且解析響應響應給客戶

HTTPS

https://images2017.cnblogs.com/blog/1260476/201711/1260476-20171116160813812-635766483.png

  • 在使用HTTPS是需要保證服務端配置正確了對應的安全證書
  • 客戶端發送請求到服務端
  • 服務端返回公鑰和證書到客戶端
  • 客戶端接收後會驗證證書的安全性,如果通過則會隨機生成一個隨機數,用公鑰對其加密,發送到服務端
  • 服務端接受到這個加密後的隨機數後會用私鑰對其解密得到真正的隨機數,隨後用這個隨機數當做私鑰對需要發送的數據進行對稱加密
  • 客戶端在接收到加密後的數據使用私鑰(即生成的隨機值)對數據進行解密並且解析數據呈現結果給客戶
  • SSL加密建立

23.數據庫四大特性和事務隔離級別

數據庫中經常被問到四大特性和隔離級別,一般都是涉及到概念性問題,在此做一些整理總結,方便理解。

1、事務的隔離級別

由低到高依次爲Read uncommitted(未授權讀取、讀未提交)、Read committed(授權讀取、讀提交)、Repeatable read(可重複讀取)、Serializable(序列化),這四個級別可以逐個解決髒讀、不可重複讀、幻讀這幾類問題。

(1)Read uncommitted(未授權讀取、讀未提交):

1)其他事務讀未提交數據,出現髒讀;
2)如果一個事務已經開始寫數據,則另外一個事務則不允許同時進行寫操作,但允許其他事務讀此行數據。該隔離級別可以通過“排他寫鎖”實現。
3)避免了更新丟失,卻可能出現髒讀。也就是說事務B讀取到了事務A未提交的數據。
(讀未提交:一個事務寫數據時,只允許其他事務對這行數據進行讀,所以會出現髒讀,事務T1讀取T2未提交的數據)

(2)Read committed(授權讀取、讀提交):

1)允許寫事務,所以會出現不可重複讀
2)讀取數據的事務允許其他事務繼續訪問該行數據,但是未提交的寫事務將會禁止其他事務訪問該行。
3)該隔離級別避免了髒讀,但是卻可能出現不可重複讀。事務A事先讀取了數據,事務B緊接了更新了數據,並提交了事務,而事務A再次讀取該數據時,數據已經發生了改變。
(讀已提交:讀取數據的事務允許其他事務進行操作,避免了髒讀,但是會出現不可重複讀,事務T1讀取數據,T2緊接着更新數據並提交數據,事務T1再次讀取數據的時候,和第一次讀的不一樣。即虛讀)

(3)Repeatable read(可重複讀取):

1)禁止寫事務;
2)讀取數據的事務將會禁止寫事務(但允許讀事務),寫事務則禁止任何其他事務。
3)避免了不可重複讀取和髒讀,但是有時可能出現幻讀。這可以通過“共享讀鎖”和“排他寫鎖”實現。
(可重複讀:讀事務會禁止所有的寫事務,但是允許讀事務,避免了不可重複讀和髒讀,但是會出現幻讀,即第二次查詢數據時會包含第一次查詢中未出現的數據)

(4)Serializable(序列化):

1)禁止任何事務,一個一個進行;
2)提供嚴格的事務隔離。它要求事務序列化執行,事務只能一個接着一個地執行,但不能併發執行。如果僅僅通過“行級鎖”是無法實現事務序列化的,必須通過其他機制保證新插入的數據不會被剛執行查詢操作的事務訪問到。
3)序列化是最高的事務隔離級別,同時代價也花費最高,性能很低,一般很少使用,在該級別下,事務順序執行,不僅可以避免髒讀、不可重複讀,還避免了幻讀。

2、出現的問題:

當多個線程都開啓事務操作數據庫中的數據時,數據庫系統要能進行隔離操作,以保證各個線程獲取數據的準確性,在介紹數據庫提供的各種隔離級別之前,我們先看看如果不考慮事務的隔離性,會發生的幾種問題:髒讀、不可重複讀、幻讀。

(1)髒讀

1.髒讀定義:

1)說法1:指在一個事務處理過程裏讀取了另一個未提交的事務中的數據,讀取數據不一致。
  2)說法2:指事務A對數據進行增刪改操作,但未提交,另一事務B可以讀取到未提交的數據。如果事務A這時候回滾了,則第二個事務B讀取的即爲髒數據。

2.舉例:

當一個事務正在多次修改某個數據,而在這個事務中多次的修改都還未提交,這時一個併發的事務來訪問該數據,就會造成兩個事務得到的數據不一致。
例如:用戶A向用戶B轉賬100元,對應SQL命令如下
  update account set money=money+100 where name=’B’; (此時A通知B)
  update account set money=money - 100 where name=’A’;
當只執行第一條SQL時,A通知B查看賬戶,B發現確實錢已到賬(此時即發生了髒讀),而之後無論第二條SQL是否執行,只要該事務不提交,則所有操作都將回滾,那麼當B以後再次查看賬戶時就會發現錢其實並沒有轉。

(2)不可重複讀

1.不可重複讀定義:

1)說法1:是指在對於數據庫中的某個數據,一個事務範圍內多次查詢卻返回了不同的數據值,這是由於在查詢間隔,被另一個事務修改並提交了。
  2)說法2:一個事務A中發生了兩次讀操作,第一次讀操作和第二次讀操作之間,另一個事務B對數據進行了修改,這時兩個事務讀取的數據不一致。

2.舉例:

例如事務T1在讀取某一數據,而事務T2立馬修改了這個數據並且提交事務給數據庫,事務T1再次讀取該數據就得到了不同的結果,發送了不可重複讀。

3.不可重複讀和髒讀的區別:

髒讀是某一事務讀取了另一個事務未提交的髒數據,而不可重複讀則是讀取了前一事務提交的數據。
  在某些情況下,不可重複讀並不是問題,比如我們多次查詢某個數據當然以最後查詢得到的結果爲主。但在另一些情況下就有可能發生問題,例如對於同一個數據A和B依次查詢就可能不同,A和B就可能打起來了……

(3)虛讀(幻讀)

1.幻讀定義:

1)說法1:是事務非獨立執行時發生的一種現象。
  2)說法2:第一個事務A對一定範圍的數據進行批量修改,第二個事務B在這個範圍增加一條數據,這時候第一個事務就會丟失對新增數據的修改。

2.舉例:

例如事務T1對一個表中所有的行的某個數據項做了從“1”修改爲“2”的操作,這時事務T2又對這個表中插入了一行數據項,而這個數據項的數值還是爲“1”並且提交給數據庫。而操作事務T1的用戶如果再查看剛剛修改的數據,會發現還有一行沒有修改,其實這行是從事務T2中添加的,就好像產生幻覺一樣,這就是發生了幻讀。

3.幻讀和不可重複讀區別:

都是讀取了另一條已經提交的事務(這點就髒讀不同),所不同的是不可重複讀查詢的都是同一個數據項,而幻讀針對的是一批數據整體(比如數據的個數)。

3、MySQL數據庫提供的四種隔離級別:

•   ① Serializable (串行化):可避免髒讀、不可重複讀、幻讀的發生。
•   ② Repeatable read (可重複讀):可避免髒讀、不可重複讀的發生。
•   ③ Read committed (讀已提交):可避免髒讀的發生。
•   ④ Read uncommitted (讀未提交):最低級別,任何情況都無法保證。

以上四種隔離級別最高的是Serializable級別,最低的是Read uncommitted級別,當然級別越高,執行效率就越低。像Serializable這樣的級別,就是以鎖表的方式(類似於Java多線程中的鎖)使得其他的線程只能在鎖外等待,所以平時選用何種隔離級別應該根據實際情況。在MySQL數據庫中默認的隔離級別爲Repeatable read (可重複讀)。
  在MySQL數據庫中,支持上面四種隔離級別,默認的爲Repeatable read (可重複讀);而在Oracle數據庫中,只支持Serializable (串行化)級別和Read committed (讀已提交)這兩種級別,其中默認的爲Read committed級別。
1)在MySQL數據庫中查看當前事務的隔離級別:

select @@tx_isolation;

2)在MySQL數據庫中設置事務的隔離 級別:

set  [glogal | sessiontransaction isolation level 隔離級別名稱;
set tx_isolation=’隔離級別名稱;’

例1:查看隔離級別:

查看當前事務的隔離級別


例2:將事務的隔離級別設置爲(Repatable read)可重複讀或者Read uncommitted級別:

設置隔離級別(1)

或者

設置隔離級別(2)

注意:設置數據庫的隔離級別一定要是在開啓事務之前!
  如果是使用JDBC對數據庫的事務設置隔離級別的話,也應該是在調用Connection對象的setAutoCommit(false)方法之前。調用Connection對象的setTransactionIsolation(level)即可設置當前鏈接的隔離級別,至於參數level,可以使用Connection對象的字段:

Connection對象的字段

在JDBC中設置隔離級別的部分代碼:

JDBC設置隔離級別

 

4、事務的四大特性

數據庫中事務的四大特性(ACID):原子性、一致性、隔離性、持久性。如果一個數據庫聲稱支持事務的操作,那麼該數據庫必須要具備以下四個特性:

(1)原子性(Atomicity)

原子性是指事務包含的所有操作要麼全部成功,要麼全部失敗回滾,因此事務的操作如果成功就必須要完全應用到數據庫,如果操作失敗則不能對數據庫有任何影響。

(2)一致性(Consistency)

一致性是指事務必須使數據庫從一個一致性狀態變換到另一個一致性狀態,也就是說一個事務執行之前和執行之後都必須處於一致性狀態。
  如:拿轉賬來說,假設用戶A和用戶B兩者的錢加起來一共是5000,那麼不管A和B之間如何轉賬,轉幾次賬,事務結束後兩個用戶的錢相加起來應該還得是5000,這就是事務的一致性。

(3)隔離性(Isolation)

隔離性是當多個用戶併發訪問數據庫時,比如操作同一張表時,數據庫爲每一個用戶開啓的事務,不能被其他事務的操作所幹擾,多個併發事務之間要相互隔離。
  即要達到這麼一種效果:對於任意兩個併發的事務T1和T2,在事務T1看來,T2要麼在T1開始之前就已經結束,要麼在T1結束之後纔開始,這樣每個事務都感覺不到有其他事務在併發地執行。

(4)持久性(Durability)

持久性是指一個事務一旦被提交了,那麼對數據庫中的數據的改變就是永久性的,即便是在數據庫系統遇到故障的情況下也不會丟失提交事務的操作。
  例如:我們在使用JDBC操作數據庫時,在提交事務方法後,提示用戶事務操作完成,當我們程序執行完成直到看到提示後,就可以認定事務以及正確提交,即使這時候數據庫出現了問題,也必須要將我們的事務完全執行完成,否則就會造成我們看到提示事務處理完畢,但是數據庫因爲故障而沒有執行事務的重大錯誤。

後記:其實說來說去也就三方面的概念
1)四大隔離級別:串行化、可重複讀、讀已提交、讀未提交;
2)四大特性(ACID):原子性、一致性、隔離性、持久性;
3)三個問題:髒讀、不可重複度、幻讀;

24.處理高併發的六種方法

1:系統拆分,將一個系統拆分爲多個子系統,用dubbo來搞。然後每個系統連一個數據庫,這樣本來就一個庫,現在多個數據庫,這樣就可以抗高併發。

2:緩存,必須得用緩存。大部分的高併發場景,都是讀多寫少,那你完全可以在數據庫和緩存裏都寫一份,然後讀的時候大量走緩存不就得了。畢竟人家redis輕輕鬆鬆單機幾萬的併發啊。沒問題的。所以你可以考的慮考慮你的項目裏,那些承載主要請求讀場景,怎麼用緩存來抗高併發。

3:MQ(消息隊列),必須得用MQ。可能你還是會出現高併發寫的場景,比如說一個業務操作裏要頻繁搞數據庫幾十次,增刪改增刪改,瘋了。那高併發絕對搞掛你的系統,人家是緩存你要是用redis來承載寫那肯定不行,數據隨時就被LRU(淘汰掉最不經常使用的)了,數據格式還無比簡單,沒有事務支持。所以該用mysql還得用mysql啊。那你咋辦?用MQ吧,大量的寫請求灌入MQ裏,排隊慢慢玩兒,後邊系統消費後慢慢寫,控制在mysql承載範圍之內。所以你得考慮考慮你的項目裏,那些承載複雜寫業務邏輯的場景裏,如何用MQ來異步寫,提升併發性。MQ單機抗幾萬併發也是ok的。

4:分庫分表,可能到了最後數據庫層面還是免不了抗高併發的要求,好吧,那麼就將一個數據庫拆分爲多個庫,多個庫來抗更高的併發;然後將一個表拆分爲多個表,每個表的數據量保持少一點,提高sql跑的性能。

5:讀寫分離,這個就是說大部分時候數據庫可能也是讀多寫少,沒必要所有請求都集中在一個庫上吧,可以搞個主從架構,主庫寫入,從庫讀取,搞一個讀寫分離。讀流量太多的時候,還可以加更多的從庫。

6:solrCloud:
SolrCloud(solr 雲)是Solr提供的分佈式搜索方案,可以解決海量數據的 分佈式全文檢索,因爲搭建了集羣,因此具備高可用的特性,同時對數據進行主從備份,避免了單點故障問題。可以做到數據的快速恢復。並且可以動態的添加新的節點,再對數據進行平衡,可以做到負載均衡:

25. 高併發的解決方案

1.應用和靜態資源分離

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

通過不同的域名可以讓瀏覽器直接訪問資源服務器而不需要再訪問應用服務器了。架構圖如下:

2.頁面緩存

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

頁面緩存主要是使用在數據很少發生變化的頁面,但是很多頁面是大部分數據都很少發生變化,而其中很少一部分數據變化頻率卻非常高,比如說一個顯示文章的頁面,正常來說完全可以靜態化,但是如果文章後面有“頂”和“踩”的功能而且顯示的有響應的數量,這個數據的變化頻率就比較高了,這就會影響靜態化。這個問題可以用先生成靜態頁面然後使用Ajax來讀取並修改響應的數據,這樣就可以一舉兩得來,既可以使用頁面緩存也可以實時顯示一些變化頻率高的數據來。

其實大家都知道,效率最高、消耗最小的就是純靜態化的html頁面,所以我們儘可能使我們的網站上的頁面採用靜態頁面來實現,這個最簡單的方法其實也是最有效的方法。但是對於大量內容並且頻繁更新的網站,我們無法全部手動去挨個實現,於是出現了我們常見的信息發佈系統CMS,像我們常訪問的各個門戶站點的新聞頻道,甚至他們的其他頻道,都是通過信息發佈系統來管理和實現的,信息發佈系統可以實現最簡單的信息錄入自動生成靜態頁面,還能具備頻道管理、權限管理、自動抓取等功能,對於一個大型網站來說,擁有一套高效、可管理的CMS是必不可少的。

除了門戶和信息發佈類型的網站,對於交互性要求很高的社區類型網站來說,儘可能的靜態化也是提高性能的必要手段,將社區內的帖子、文章進行實時的靜態化,有更新的時候再重新靜態化也是大量使用的策略,像Mop的大雜燴就是使用了這樣的策略,網易社區等也是如此。

同時,html靜態化也是某些緩存策略使用的手段,對於系統中頻繁使用數據庫查詢但是內容更新很小的應用,可以考慮使用html靜態化來實現,比如論壇中論壇的公用設置信息,這些信息目前的主流論壇都可以進行後臺管理並且存儲再數據庫中,這些信息其實大量被前臺程序調用,但是更新頻率很小,可以考慮將這部分內容進行後臺更新的時候進行靜態化,這樣避免了大量的數據庫訪問請求。

3.集羣與分佈式

集羣是每臺服務器都具有相同的功能,處理請求時調用那臺服務器都可以,主要起分流作用。

分佈式是將不同的業務放到不同的服務器中,處理一個請求可能需要用到多臺服務器,這樣就可以提高一個請求的處理速度,而且集羣和分佈式也可以同時使用。

集羣有兩個方式:一種是在靜態資源集羣。另一種是應用程序集羣。靜態資源集羣比較簡單。應用程序集羣在處理過程中最核心的問題就是Session 同步問題。

Session 同步有兩種處理方式:一種是在Session 發生變化後自動同步到其他服務器,另一種就是用個程序統一管理Session。所有集羣的服務器都使用同一個Session,Tomcat 默認使用就是第一種方式,通過簡單的配置就可以實現,第二種方式可以使用專門的服務器安裝Mencached等高效的緩存程序統一來管理session,然後再應用程序中通過重寫Request並覆蓋getSession 方法來獲取制定服務器中的Session。

對於集羣來說還有一個核心的問題就是負載均衡,也就是接收到一個請求後具體分配到那個服務器去處理的問題,這個問題可以通過軟件處理也可以使用專門的硬件(如:F5)解決。

4. 反向代理

反向代理指的是客戶端直接訪問的服務器並不真正提供服務,它從別的服務器獲取資源然後將結果返回給用戶。

圖:

4.1 反向代理服務器和代理服務器的區別

代理服務器的作用是代我門獲取想要的資源然後將結果返回給我們,所要獲取的資源是我門主動告訴代理服務器的,比如,我門想訪問Facebook,但是直接訪問不了,這時就可以讓代理服務器訪問,然後將結果返回給我們。

反向代理服務器是我門正常訪問一臺服務器的時候,服務器自己去調用了別的服務器資源並將結果返回給我們,我門自己並不知道。

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

4.2反向代理服務器主要有三個作用:

1. 可以作爲前端服務器跟實際處理請求的服務器集成;

2. 可以做負載均衡

3. 轉發請求,比如說可以將不同類型的資源請求轉發到不同的服務器去處理。

5. CDN

cdn其實是一種特殊的集羣頁面緩存服務器,他和普通集羣的多臺頁面緩存服務器相比,主要是它存放的位置和分配請求的方式有點特殊。CDN 服務器是分佈在全國各地的,當接收到用戶請求後會將請求分配到最合適的CDN服務器節點獲取數據。比如聯通的用戶分配到聯通的節點,上海的用戶分配到上海的節點。

CDN的每個節點其實就是一個頁面緩存服務器,如果沒有請求資源的緩存就會從主服務器獲取,否則直接返回緩存的頁面。

CDN分配請求(負載均衡)的方式是用專門的CDN域名解析服務器在解析域名的時候就分配好的。一般的做法是在ISP哪裏試用CNAME將域名解析到一個特定的域名,然後再將解析到的那個域名用專門的CDN服務器解析道相應的CDN節點。如圖。

第二步訪問CDN的DNS服務器是應爲CNAME記錄的目標域名使用NS記錄指向了CDN的DNS服務器。CDN的每個節點可能也是集羣了多臺服務器。

 

6. 底層的優化

前面說的所有都是架構都是建立在最前面介紹的基礎結構之上的。很多地方都需要通過網絡傳輸數據,如果可以加快網絡傳輸的速度,那將會讓整個系統得到改善。

7.數據庫集羣和庫表散列

大型網站都有複雜的應用,這些應用必須使用數據庫,那麼在面對大量訪問的時候,數據庫的瓶頸很快就能顯現出來,這時一臺數據庫將很快無法滿足應用,於是我們需要使用數據庫集羣或者庫表散列。

在數據庫集羣方面,很多數據庫都有自己的解決方案,Oracle、Sybase等都有很好的方案,常用的MySQL提供的Master/Slave也是類似的方案,您使用了什麼樣的DB,就參考相應的解決方案來實施即可。

上面提到的數據庫集羣由於在架構、成本、擴張性方面都會受到所採用DB類型的限制,於是我們需要從應用程序的角度來考慮改善系統架構,庫表散列是常用並且最有效的解決方案。我們在應用程序中安裝業務和應用或者功能模塊將數據庫進行分離,不同的模塊對應不同的數據庫或者表,再按照一定的策略對某個頁面或者功能進行更小的數據庫散列,比如用戶表,按照用戶ID進行表散列,這樣就能夠低成本的提升系統的性能並且有很好的擴展性。sohu的論壇就是採用了這樣的架構,將論壇的用戶、設置、帖子等信息進行數據庫分離,然後對帖子、用戶按照板塊和ID進行散列數據庫和表,最終可以在配置文件中進行簡單的配置便能讓系統隨時增加一臺低成本的數據庫進來補充系統性能。

8. 小結

網站架構的整個演變過程主要是圍繞大數據和高併發這兩個問題展開的,解決方案主要分爲使用緩存和多資源兩種類型。多資源主要指多存儲(包括多內存)、多CPU和多網絡,對於多資源來說又可以分爲單個資源處理一個完整的請求和多個資源合作處理一個請求兩種類型,如多存儲和多CPU中的集羣和分佈式,多網絡中的CDN和靜態資源分離。理解了整個思路之後就抓住了架構演變的本質,而且自己可能還可以設計出更好的架構。

其它簡單總結:

首先,我認爲解決問題之前首先要有清晰的思路,如果只是用來別人的解決方案那也只能是拿來主義,沒有真正理解,沒有做到舉一反三。

海量數據和高併發經常被連在一塊說事兒,雖然他們完全是兩回事兒。海量數據純指的是數據庫的海量數據,而併發指的卻包括數據庫和服務器的高訪問量。

那麼問題來了,既然是數據庫的數據量大,那怎麼辦呢?要想解決問題,首先要知道問題是什麼!!!那麼海量數據會給我帶來什麼樣的問題呢?

海量數據帶來的問題無非就是增刪改查的問題,除了之外還能有啥問題呢?總不能是帶來安全問題吧(打臉一,還真有可能是安全問題)

1 數據庫訪問緩慢

2 插入更新緩慢,這個問題只能通過分庫分表解決

要解決數據庫訪問緩慢的問題還有幾種方法,既然訪問數據庫慢的話,在邏輯允許的情況下可以不訪問數據庫呢?

1 使用緩存

2 使用頁面靜態化

既然不訪問數據庫逃不過去了,那我們就對數據庫進行優化

3 優化數據庫(包含的內容非常多,比如參數配置,索引優化,sql優化等等)

4 分離數據庫中活躍的數據

5 讀寫分離

6 批量讀取和延遲修改;

7 使用搜索引擎搜索數據庫中的數據;

8 使用NoSQL和Hadoop等技術;

9 進行業務的拆分;

高併發的解決方案

其實這個問題必須結合上面的海量數據來討論,什麼情況下會出現高併發呢?一定是平時訪問量就比較大的情況,那麼平時訪問量比較大相應的數據存儲也就越來越多,這都是相輔相成的,當然也有個例,比如剛需,比如12306,這裏的高併發相比於它的數據來說已經不算海量了。那麼平時訪問量大如何解決呢?因爲這裏牽扯到服務器和數據庫的問題,所以要從這兩方面來進行優化

1 增加web服務器數量,也就是做集羣,做負載均衡。既然一臺服務器無法完成任務,那就多用幾臺,幾臺不夠用機房

 在通向第二種解決方法之前,還有沒有除了數據庫服務器之外能做的一些優化手段呢?當然有

1.1 頁面緩存

1.2 cdn

1.3 反向代理

1.4 應用程序和靜態資源分離(比如專供下載的資源單獨放在一起,給這臺服務器提供很高的帶寬資源)

2 增加數據庫服務器數量,同樣做集羣,做負載均衡。

海量數據的解決方案

1 使用緩存

好多事情都是相輔相成的,相比來說使用緩存更多是用來解決高併發問題的,因爲海量數據導致了訪問的緩慢,容易造成高併發問題的嚴重性,又因爲數據庫一般是web訪問的瓶頸,所以我們在業務邏輯允許的情況下儘量先避免操作數據庫,於是,就有了緩存。將必要的數據存放在內存中,而不必每次都去數據庫中讀取造成不必要的性能浪費和加快訪問速度---這就是緩存帶來的好處。那使用緩存以及選用管理緩存軟件時應該注意些什麼東西呢?

2 頁面靜態化---不想解釋,還有什麼值得去解釋呢?

3 數據庫優化

3.1 數據庫表結構涉及

3.2 數據類型的選用

3.3 sql優化

3.4 索引優化

3.5 配置優化

需要注意的地方實在太多,應該作爲單獨的一章拿出來講

4 分離數據庫中的活躍數據

爲什麼要分離呢?說一個我實際環境中遇到的問題吧!有一個表只有10幾個字段,表有130萬條數據,但大小已經到了5G的數據,這本身是不太合理的,這麼少的數據佔用了太多的數據,說明其中有些字段存儲了大量的字符串(比如說文章內容等),每次檢索這個表時大部分是用不到這些大字段內容的,但卻需要耗時比較長,產生很多的慢日誌。這時我們可以考慮將表進行垂直切分,將活躍數據分離開來,這樣能大大加快訪問速度

5 讀寫分離

26.數據庫讀寫分離,主從同步複製實現方法

實現思路

通過設置主從數據庫實現讀寫分離,主數據庫負責“寫操作”,從數據庫負責“讀操作”,根據壓力情況,從數據庫可以部署多個提高“讀”的速度,藉此來提高系統總體的性能。

基礎知識

要實現讀寫分離,就要解決主從數據庫數據同步的問題,在主數據庫寫入數據後要保證從數據庫的數據也要更新。

主服務器master記錄數據庫操作日誌到Binary log,從服務器開啓i/o線程將二進制日誌記錄的操作同步到relay log(存在從服務器的緩存中),另外sql線程將relay log日誌記錄的操作在從服務器執行。

記住這張圖,接下來基於這個圖實際設置主從數據庫。

主從同步複製有以下幾種方式:

(1)同步複製,master的變化,必須等待slave-1,slave-2,...,slave-n完成後才能返回。

(2)異步複製,master只需要完成自己的數據庫操作即可,至於slaves是否收到二進制日誌,是否完成操作,不用關心。MYSQL的默認設置。

(3)半同步複製,master只保證slaves中的一個操作成功,就返回,其他slave不管。這個功能,是由google爲MYSQL引入的。

主從數據庫設置的具體步驟

首先要有兩個數據庫服務器master、slave(也可以用一個服務器安裝兩套數據庫環境運行在不同端口,slave也可以舉一反三設置多個),我們窮人就買虛擬雲服務器玩玩就行 0.0。以下操作假設你的兩臺服務器上都已經安裝好了mysql服務。

1.打開mysql數據庫配置文件

vim /etc/my.cnf

2.在主服務器master上配置開啓Binary log,主要是在[mysqld]下面添加:

server-id=1

log-bin=master-bin

log-bin-index=master-bin.index

3.重啓mysql服務

service mysql restart

ps:重啓方式隨意

4.檢查配置效果,進入主數據庫並執行

mysql> SHOW MASTER STATUS;

5.配置從服務器的 my.cnf

在[mysqld]節點下面添加:

master status
server-id=2
relay-log-index=slave-relay-bin.index
relay-log=slave-relay-bin

這裏面的server-id 一定要和主庫的不同,如圖:

slave my.cnf

配置完成後同樣重啓從數據庫一下

service mysql restart

6.接下來配置兩個數據庫的關聯

首先我們先建立一個操作主從同步的數據庫用戶,切換到主數據庫執行:

mysql> create user repl;

mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl'@'從xxx.xxx.xxx.xx' IDENTIFIED BY 'mysql';

mysql> flush privileges;

這個配置的含義就是創建了一個數據庫用戶repl,密碼是mysql, 在從服務器使用repl這個賬號和主服務器連接的時候,就賦予其REPLICATION SLAVE的權限, *.* 表面這個權限是針對主庫的所有表的,其中xxx就是從服務器的ip地址。

進入從數據庫後執行:

mysql> change master to

master_host='主xxx.xxx.xxx.xx',

master_port=3306,

master_user='repl',

master_password='mysql',

master_log_file='master-bin.000001',

master_log_pos=0;

這裏面的xxx是主服務器ip,同時配置端口,repl代表訪問主數據庫的用戶,上述步驟執行完畢後執行start slave啓動配置:

mysql> start slave;

start slave

停止主從同步的命令爲:

mysql> stop slave;

 

查看狀態命令,\G表示換行查看

mysql> show slave status \G;

可以看到狀態如下:

slave status

這裏看到從數據庫已經在等待主庫的消息了,接下來在主庫的操作,在從庫都會執行了。我們可以主庫負責寫,從庫負責讀(不要在從庫進行寫操作),達到讀寫分離的效果。

27. 如何保證緩存與數據庫的雙寫一致性?

最經典的緩存+數據庫讀寫的模式,就是 Cache Aside Pattern。

  • 讀的時候,先讀緩存,緩存沒有的話,就讀數據庫,然後取出數據後放入緩存,同時返回響應分佈式session
  • 更新的時候,先更新數據庫,然後再刪除緩存

爲什麼是刪除緩存,而不是更新緩存?

原因很簡單,很多時候,在複雜點的緩存場景,緩存不單單是數據庫中直接取出來的值。

另外更新緩存的代價有時候是很高的,用到緩存纔去算緩存

你頻繁修改一個緩存涉及的多個表,緩存也頻繁更新。但是問題在於,這個緩存到底會不會被頻繁訪問到?

其實刪除緩存,而不是更新緩存,就是一個 lazy 計算的思想,不要每次都重新做複雜的計算,不管它會不會用到,而是讓它到需要被使用的時候再重新計算。像 mybatis,hibernate,都有懶加載思想。

最初級的緩存不一致問題及解決方案

問題:先修改數據庫,再刪除緩存。如果刪除緩存失敗了,那麼會導致數據庫中是新數據,緩存中是舊數據,數據就出現了不一致。

解決思路:先刪除緩存,再修改數據庫。如果數據庫修改失敗了,那麼數據庫中是舊數據,緩存中是空的,那麼數據不會不一致。因爲讀的時候緩存沒有,則讀數據庫中舊數據,然後更新到緩存中。

 

28. 同步和異步,阻塞和非阻塞

同步和異步,阻塞和非阻塞,這幾個詞已經是老生常談,但是還是有很多同學分不清楚,以爲同步肯定就是阻塞,異步肯定就是非阻塞,其實他們並不是一回事。

同步和異步關注的是結果消息的通信機制

同步:調用方需要主動等待結果的返回。

異步:不需要主動等待結果的返回,而是通過其他手段,比如狀態通知,回調函數等。

阻塞和非阻塞主要關注的是等待結果返回調用方的狀態:

阻塞:是指結果返回之前,當前線程被掛起,不做任何事。

非阻塞:是指結果在返回之前,線程可以做一些其他事,不會被掛起。

可以看見同步和異步,阻塞和非阻塞主要關注的點不同,有人會問同步還能非阻塞,異步還能阻塞?

當然是可以的,下面爲了更好的說明它們的組合之間的意思,用幾個簡單的例子說明:

同步阻塞:同步阻塞基本也是編程中最常見的模型,打個比方你去商店買衣服,你去了之後發現衣服賣完了,那你就在店裏面一直等,期間不做任何事(包括看手機),等着商家進貨,直到有貨爲止,這個效率很低。

同步非阻塞:同步非阻塞在編程中可以抽象爲一個輪詢模式,你去了商店之後,發現衣服賣完了。

這個時候不需要傻傻的等着,你可以去其他地方比如奶茶店,買杯水,但是你還是需要時不時的去商店問老闆新衣服到了嗎。

異步阻塞:異步阻塞這個編程裏面用的較少,有點類似你寫了個線程池,submit 然後馬上 future.get(),這樣線程其實還是掛起的。

有點像你去商店買衣服,這個時候發現衣服沒有了,這個時候你就給老闆留個電話,說衣服到了就給我打電話,然後你就守着這個電話,一直等着它響什麼事也不做。這樣感覺的確有點傻,所以這個模式用得比較少。

異步非阻塞:這也是現在高併發編程的一個核心,也是今天主要講的一個核心。

好比你去商店買衣服,衣服沒了,你只需要給老闆說這是我的電話,衣服到了就打。然後你就隨心所欲的去玩,也不用操心衣服什麼時候到,衣服一到,電話一響就可以去買衣服了。

 

 

29. 在UTF-8中,一個漢字爲什麼需要三個字節?

UNICODE是萬能編碼,包含了所有符號的編碼,它規定了所有符號在計算機底層的二進制的表示順序。有關Unicode爲什麼會出現就不敘述了。    

Unicode是針對所有計算機的使用者定義一套統一的編碼規範,這樣計算機使用者就避免了編碼轉換的問題。

Unicode定義了所有符號的二進制形式,也就是符號如何在計算機內部存儲的,而且每個符號規定都必須使用兩個字節來表示,也就是用16位二進制去代表一個符號,這樣就導致了一個問題,英文編碼的空間浪費,因爲在ANSI中的符號都是一個字節來表示的,而使用了UNICODE編碼就白白浪費了一個字節。也就代表着Unicode需要使用兩倍的空間去存儲相應的ANSI編碼下的符號。雖然現在硬盤或者內存都很廉價,但是在網絡傳輸中,這個問題就凸顯出來了,你可以這樣想想,本來1M的帶寬在ANSI下可以代表1024*1024個字符,但是在Unicode下卻只能代表1024*1024/2個字符。也就是1MB/s的帶寬只能等價於512KB/s,這個很可怕啊。所以爲了解決符號在網絡中傳輸的浪費問題,就出現了UTF-8編碼,Unicode transfer format -8 ,後面的8代表是以8位二進制爲單位來傳輸符號的。

但是這樣又導致了一個問題,雖然UTF-8可以使用一個字節來表示ANSI下的符號,但是對於其它類似漢語的符號,得需要兩個字節來表示,所以計算機不知道如何去截取一個符號,也就是一個符號對應的二進制的截取開始位置和截取結束位置。

所以爲了解決Unicode下的ANSI符號的空間浪費和網絡傳輸下如何截取字符的問題,UTF規定:

如果一個符號只佔一個字節,那麼這個8位字節的第一位就爲0。
如果爲兩個字節,那麼規定第一個字節的前兩位都爲1,然後第一個字節的第三位爲0,第二個字節的前兩位爲10。如果是三個字節的話,那麼第一個字節的前三位爲111,第四位爲0,剩餘的兩個字節的前兩位都爲10。
按照這樣的算法去思考一箇中文字符的UTF-8是怎麼表示的:一箇中文字符需要兩個字節來表示,兩個字節一共是16位,那麼UTF-8下,兩個字節是不夠的,因爲兩個字節下,第一個字節已經佔據了三位:110,然後剩餘的一個字節佔據了兩位:10,現在就只剩下11位,與Unicode下的兩個字節,16位去表示任意一個字符是相悖的。所以就使用三個字節去表示非ANSI字符:三個字節下,一共是24位,第一個字節頭四位是:1110,後兩個字節的前兩位都是:10,那麼24位-8位=16位,剛好兩個字節去表示Unicode下的任意一個非ANSI字符。這也就是爲什麼UTF-8需要使用三個字節去表示一個非ANSI字符的原因了!Unicode符號範圍 | UTF-8編碼方式

(十六進制) | (二進制)

--------------------+---------------------------------------------

0000 0000-0000 007F | 0xxxxxxx

0000 0080-0000 07FF | 110xxxxx 10xxxxxx

0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx

0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
跟據上表,解讀UTF-8編碼非常簡單。如果一個字節的第一位是0,則這個字節單獨就是一個字符;如果第一位是1,則連續有多少個1,就表示當前字符佔用多少個字節。
題外話:
  然,中國的漢字多達10多萬,常用的漢字3500左右[08年統計],如果用3個字節來表示,一共只有2^16(65535)種可能,不足以表示10多萬的漢字。所以中日韓的超大字符集是採用的4個字節來表示的,多達6萬多個。但是平時使用超大字符集的概率0.01%都不到。所以我們一般認爲日常的中文在UTF-8中佔三個字節 即可!
多個字節提供的位數超過了所需要的,多餘的位以0補全到編碼前面

 

30.逃逸分析

逃逸分析是是一種動態確定指針動態範圍的靜態分析,它可以分析在程序的哪些地方可以訪問到指針。

換句話說,逃逸分析的目的是判斷對象的作用域是否有可能逃出方法體

判斷依據有兩個

  1. 對象是否被存入堆中(靜態字段或堆中對象的實例字段)
  2. 對象是否被傳入未知代碼中(方法的調用者和參數)

對於第一點對象是否被存入堆中,我們知道堆內存是線程共享的,一旦對象被分配在堆中,那所有線程都可以訪問到該對象,這樣即時編譯器就追蹤不到所有使用到該對象的地方了,這樣的對象就屬於逃逸對象

 

第二點是對象是否被傳入未知代碼中,Java 的即時編譯器是以方法爲單位進行編譯,即時編譯器會把方法中未被內聯的方法當成未知代碼,所以無法判斷這個未知方法的方法調用會不會將調用者或參數放到堆中,所以認爲方法的調用者和參數是逃逸的,如下所示

  1. public class Escape {
  2.     private static User u; 
  3.     public static void alloc(User user) {
  4.         u = user;
  5.     }
  6. }

分配在堆上的好處是方法結束後自動釋放對應的內存

                         

31. 進程間通信機制有哪些?

進程間的通信方式:

   1.管道(pipe)及有名管道(named pipe):

    管道是一種半雙工的通信方式,數據只能單向流動,而且只能在具有親緣關係的進程間使用。進程的親緣關係通常是指父子進程關係。

    有名管道也是半雙工的通信方式,但是它允許無親緣關係進程間的通信。

    2.信號(signal):

     信號量是一個計數器,可以用來控制多個進程對共享資源的訪問。它常作爲一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源。因此,主要作爲進程間以及同一進程內不同線程之間的同步手段。

   3.消息隊列(message queue):

    消息隊列是消息的鏈接表,它克服了上兩種通信方式中信號量有限的缺點,具有寫權限得進程可以按照一定得規則向消息隊列中添加新信息;對消息隊列有讀權限得進程則可以從消息隊列中讀取信息。      

   其基本思想是:根據”生產者-消費者”原理,利用內存中公用消息緩衝區實現進程之間的信息交換. 

   內存中開闢了若干消息緩衝區,用以存放消息.每當一個進程向另一個進程發送消息時,便申請一個消息緩衝區,並把已準備好的消息送到緩衝區,然後把該消息緩衝區插入到接收進程的消息隊列中,最後通知接收進程.接收進程收到發送里程發來的通知後,從本進程的消息隊列中摘下一消息緩衝區,取出所需的信息,然後把消息緩衝區不定期給系統.系統負責管理公用消息緩衝區以及消息的傳遞. 

   一個進程可以給若干個進程發送消息,反之,一個進程可以接收不同進程發來的消息.顯然,進程中關於消息隊列的操作是臨界區.當發送進程正往接收進程的消息隊列中添加一條消息時,接收進程不能同時從該消息隊列中到出消息:反之也一樣. 

 

   4.共享內存(shared memory):

    可以說這是最有用的進程間通信方式。它使得多個進程可以訪問同一塊內存空間,不同進程可以及時看到對方進程中對共享內存中數據得更新。這種方式需要依靠某種同步操作,如互斥鎖和信號量等。

    這種通信模式需要解決兩個問題:第一個問題是怎樣提供共享內存;第二個是公共內存的互斥關係則是程序開發人員的責任。  

   5.信號量(semaphore):

     主要作爲進程之間及同一種進程的不同線程之間得同步和互斥手段。

   6.套接字(socket)

     套解口也是一種進程間通信機制,與其他通信機制不同的是,它可用於不同及其間的進程通信。

32.TCP和UDP的區別和優缺點

1、TCP與UDP區別總結:

1、TCP面向連接(如打電話要先撥號建立連接);UDP是無連接的,即發送數據之前不需要建立連接

2、TCP提供可靠的服務。也就是說,通過TCP連接傳送的數據,無差錯,不丟失,不重複,且按序到達;UDP盡最大努力交付,即不保證可靠交付,Tcp通過校驗和,重傳控制,序號標識,滑動窗口、確認應答實現可靠傳輸。如丟包時的重發控制,還可以對次序亂掉的分包進行順序控制。

3、UDP具有較好的實時性,工作效率比TCP高,適用於對高速傳輸和實時性有較高的通信或廣播通信。

4.每一條TCP連接只能是點到點的;UDP支持一對一,一對多,多對一和多對多的交互通信

5、TCP對系統資源要求較多,UDP對系統資源要求較少。

2、爲什麼UDP有時比TCP更有優勢?

UDP以其簡單、傳輸快的優勢,在越來越多場景下取代了TCP,如實時遊戲。

(1)網速的提升給UDP的穩定性提供可靠網絡保障,丟包率很低,如果使用應用層重傳,能夠確保傳輸的可靠性。

(2)TCP爲了實現網絡通信的可靠性,使用了複雜的擁塞控制算法,建立了繁瑣的握手過程,由於TCP內置的系統協議棧中,極難對其進行改進。

採用TCP,一旦發生丟包,TCP會將後續的包緩存起來,等前面的包重傳並接收到後再繼續發送,延時會越來越大,基於UDP對實時性要求較爲嚴格的情況下,採用自定義重傳機制,能夠把丟包產生的延遲降到最低,儘量減少網絡問題對遊戲性造成影響。

33. HTTP狀態碼

消息

這一類型的狀態碼,代表請求已被接受,需要繼續處理。這類響應是臨時響應,只包含狀態行和某些可選的響應頭信息,並以空行結束。由於 HTTP/1.0 協議中沒有定義任何 1xx 狀態碼,所以除非在某些試驗條件下,服務器禁止向此類客戶端發送 1xx 響應。

100 Continue

客戶端應當繼續發送請求。這個臨時響應是用來通知客戶端它的部分請求已經被服務器接收,且仍未被拒絕。客戶端應當繼續發送請求的剩餘部分,或者如果請求已經完成,忽略這個響應。服務器必須在請求完成後向客戶端發送一個最終響應。

101 Switching Protocols

服務器已經理解了客戶端的請求,並將通過Upgrade 消息頭通知客戶端採用不同的協議來完成這個請求。在發送完這個響應最後的空行後,服務器將會切換到在Upgrade 消息頭中定義的那些協議。

只有在切換新的協議更有好處的時候才應該採取類似措施。例如,切換到新的HTTP 版本比舊版本更有優勢,或者切換到一個實時且同步的協議以傳送利用此類特性的資源。

102 Processing

WebDAVRFC 2518)擴展的狀態碼,代表處理將被繼續執行。

成功

編輯

這一類型的狀態碼,代表請求已成功被服務器接收、理解、並接受。

200 OK

請求已成功,請求所希望的響應頭或數據體將隨此響應返回。出現此狀態碼是表示正常狀態。

201 Created

請求已經被實現,而且有一個新的資源已經依據請求的需要而建立,且其 URI 已經隨Location 頭信息返回。假如需要的資源無法及時建立的話,應當返回 '202 Accepted'

202 Accepted

服務器已接受請求,但尚未處理。正如它可能被拒絕一樣,最終該請求可能會也可能不會被執行。在異步操作的場合下,沒有比發送這個狀態碼更方便的做法了。

返回202狀態碼的響應的目的是允許服務器接受其他過程的請求(例如某個每天只執行一次的基於批處理的操作),而不必讓客戶端一直保持與服務器的連接直到批處理操作全部完成。在接受請求處理並返回202狀態碼的響應應當在返回的實體中包含一些指示處理當前狀態的信息,以及指向處理狀態監視器或狀態預測的指針,以便用戶能夠估計操作是否已經完成。

203 Non-Authoritative Information

服務器已成功處理了請求,但返回的實體頭部元信息不是在原始服務器上有效的確定集合,而是來自本地或者第三方的拷貝。當前的信息可能是原始版本的子集或者超集。例如,包含資源的元數據可能導致原始服務器知道元信息的超集。使用此狀態碼不是必須的,而且只有在響應不使用此狀態碼便會返回200 OK的情況下才是合適的。

204 No Content

服務器成功處理了請求,但不需要返回任何實體內容,並且希望返回更新了的元信息。響應可能通過實體頭部的形式,返回新的或更新後的元信息。如果存在這些頭部信息,則應當與所請求的變量相呼應。

如果客戶端是瀏覽器的話,那麼用戶瀏覽器應保留髮送了該請求的頁面,而不產生任何文檔視圖上的變化,即使按照規範新的或更新後的元信息應當被應用到用戶瀏覽器活動視圖中的文檔。

由於204響應被禁止包含任何消息體,因此它始終以消息頭後的第一個空行結尾。

205 Reset Content

服務器成功處理了請求,且沒有返回任何內容。但是與204響應不同,返回此狀態碼的響應要求請求者重置文檔視圖。該響應主要是被用於接受用戶輸入後,立即重置表單,以便用戶能夠輕鬆地開始另一次輸入。

204響應一樣,該響應也被禁止包含任何消息體,且以消息頭後的第一個空行結束。

206 Partial Content

服務器已經成功處理了部分 GET 請求。類似於 FlashGet 或者迅雷這類的 HTTP下載工具都是使用此類響應實現斷點續傳或者將一個大文檔分解爲多個下載段同時下載。

該請求必須包含 Range 頭信息來指示客戶端希望得到的內容範圍,並且可能包含 If-Range 來作爲請求條件。

響應必須包含如下的頭部域:

Content-Range 用以指示本次響應中返回的內容的範圍;如果是 Content-Type multipart/byteranges 的多段下載,則每一 multipart 段中都應包含 Content-Range 域用以指示本段的內容範圍。假如響應中包含 Content-Length,那麼它的數值必須匹配它返回的內容範圍的真實字節數。

Date

ETag / Content-Location,假如同樣的請求本應該返回200響應。

Expires, Cache-Control,和/ Vary,假如其值可能與之前相同變量的其他響應對應的值不同的話。

假如本響應請求使用了 If-Range 強緩存驗證,那麼本次響應不應該包含其他實體頭;假如本響應的請求使用了 If-Range 弱緩存驗證,那麼本次響應禁止包含其他實體頭;這避免了緩存的實體內容和更新了的實體頭信息之間的不一致。否則,本響應就應當包含所有本應該返回200響應中應當返回的所有實體頭部域。

假如 ETag Last-Modified 頭部不能精確匹配的話,則客戶端緩存應禁止將206響應返回的內容與之前任何緩存過的內容組合在一起。

207 Multi-Status

WebDAV(RFC 2518)擴展的狀態碼,代表之後的消息體將是一個XML消息,並且可能依照之前子請求數量的不同,包含一系列獨立的響應代碼。

重定向

編輯

這類狀態碼代表需要客戶端採取進一步的操作才能完成請求。通常,這些狀態碼用來重定向,後續的請求地址(重定向目標)在本次響應的 Location 域中指明。

當且僅當後續的請求所使用的方法是 GET 或者 HEAD 時,用戶瀏覽器纔可以在沒有用戶介入的情況下自動提交所需要的後續請求。客戶端應當自動監測無限循環重定向(例如:A->A,或者A->B->C->A),因爲這會導致服務器和客戶端大量不必要的資源消耗。按照 HTTP/1.0 版規範的建議,瀏覽器不應自動訪問超過5次的重定向。

300 Multiple Choices

被請求的資源有一系列可供選擇的回饋信息,每個都有自己特定的地址和瀏覽器驅動的商議信息。用戶或瀏覽器能夠自行選擇一個首選的地址進行重定向。

除非這是一個 HEAD 請求,否則該響應應當包括一個資源特性及地址的列表的實體,以便用戶或瀏覽器從中選擇最合適的重定向地址。這個實體的格式由 Content-Type 定義的格式所決定。瀏覽器可能根據響應的格式以及瀏覽器自身能力,自動作出最合適的選擇。當然,RFC 2616規範並沒有規定這樣的自動選擇該如何進行。

如果服務器本身已經有了首選的回饋選擇,那麼在 Location 中應當指明這個回饋的 URI;瀏覽器可能會將這個 Location 值作爲自動重定向的地址。此外,除非額外指定,否則這個響應也是可緩存的。

301 Moved Permanently

被請求的資源已永久移動到新位置,並且將來任何對此資源的引用都應該使用本響應返回的若干個 URI 之一。如果可能,擁有鏈接編輯功能的客戶端應當自動把請求的地址修改爲從服務器反饋回來的地址。除非額外指定,否則這個響應也是可緩存的。

新的永久性的URI 應當在響應的 Location 域中返回。除非這是一個 HEAD 請求,否則響應的實體中應當包含指向新的 URI 的超鏈接及簡短說明。

如果這不是一個 GET 或者 HEAD 請求,因此瀏覽器禁止自動進行重定向,除非得到用戶的確認,因爲請求的條件可能因此發生變化。

注意:對於某些使用 HTTP/1.0 協議的瀏覽器,當它們發送的 POST 請求得到了一個301響應的話,接下來的重定向請求將會變成 GET 方式。

302 Move Temporarily

請求的資源臨時從不同的 URI響應請求。由於這樣的重定向是臨時的,客戶端應當繼續向原有地址發送以後的請求。只有在Cache-ControlExpires中進行了指定的情況下,這個響應纔是可緩存的。

上文有提及。

如果這不是一個 GET 或者 HEAD 請求,那麼瀏覽器禁止自動進行重定向,除非得到用戶的確認,因爲請求的條件可能因此發生變化。

注意:雖然RFC 1945RFC 2068規範不允許客戶端在重定向時改變請求的方法,但是很多現存的瀏覽器將302響應視作爲303響應,並且使用 GET 方式訪問在 Location 中規定的 URI,而無視原先請求的方法。狀態碼303307被添加了進來,用以明確服務器期待客戶端進行何種反應。

303 See Other

對應當前請求的響應可以在另一個 URL 上被找到,而且客戶端應當採用 GET 的方式訪問那個資源。這個方法的存在主要是爲了允許由腳本激活的POST請求輸出重定向到一個新的資源。這個新的 URI 不是原始資源的替代引用。同時,303響應禁止被緩存。當然,第二個請求(重定向)可能被緩存。

注意:許多 HTTP/1.1 版以前的瀏覽器不能正確理解303狀態。如果需要考慮與這些瀏覽器之間的互動,302狀態碼應該可以勝任,因爲大多數的瀏覽器處理302響應時的方式恰恰就是上述規範要求客戶端處理303響應時應當做的。

304 Not Modified

如果客戶端發送了一個帶條件的 GET 請求且該請求已被允許,而文檔的內容(自上次訪問以來或者根據請求的條件)並沒有改變,則服務器應當返回這個狀態碼。304響應禁止包含消息體,因此始終以消息頭後的第一個空行結尾。

該響應必須包含以下的頭信息:

Date,除非這個服務器沒有時鐘。假如沒有時鐘的服務器也遵守這些規則,那麼代理服務器以及客戶端可以自行將 Date 字段添加到接收到的響應頭中去(正如RFC 2068中規定的一樣),緩存機制將會正常工作。

ETag / Content-Location,假如同樣的請求本應返回200響應。

Expires, Cache-Control,和/Vary,假如其值可能與之前相同變量的其他響應對應的值不同的話。

假如本響應請求使用了強緩存驗證,那麼本次響應不應該包含其他實體頭;否則(例如,某個帶條件的 GET 請求使用了弱緩存驗證),本次響應禁止包含其他實體頭;這避免了緩存了的實體內容和更新了的實體頭信息之間的不一致。

假如某個304響應指明瞭當前某個實體沒有緩存,那麼緩存系統必須忽視這個響應,並且重複發送不包含限制條件的請求。

假如接收到一個要求更新某個緩存條目的304響應,那麼緩存系統必須更新整個條目以反映所有在響應中被更新的字段的值。

305 Use Proxy

被請求的資源必須通過指定的代理才能被訪問。Location 域中將給出指定的代理所在的 URI 信息,接收者需要重複發送一個單獨的請求,通過這個代理才能訪問相應資源。只有原始服務器才能建立305響應。

注意:RFC 2068中沒有明確305響應是爲了重定向一個單獨的請求,而且只能被原始服務器建立。忽視這些限制可能導致嚴重的安全後果。

306 Switch Proxy

在最新版的規範中,306狀態碼已經不再被使用。

307 Temporary Redirect

請求的資源臨時從不同的URI 響應請求。

新的臨時性的URI 應當在響應的 Location 域中返回。除非這是一個HEAD 請求,否則響應的實體中應當包含指向新的URI 的超鏈接及簡短說明。因爲部分瀏覽器不能識別307響應,因此需要添加上述必要信息以便用戶能夠理解並向新的 URI 發出訪問請求。

如果這不是一個GET 或者 HEAD 請求,那麼瀏覽器禁止自動進行重定向,除非得到用戶的確認,因爲請求的條件可能因此發生變化。

請求錯誤

編輯

這類的狀態碼代表了客戶端看起來可能發生了錯誤,妨礙了服務器的處理。除非響應的是一個 HEAD 請求,否則服務器就應該返回一個解釋當前錯誤狀況的實體,以及這是臨時的還是永久性的狀況。這些狀態碼適用於任何請求方法。瀏覽器應當向用戶顯示任何包含在此類錯誤響應中的實體內容。

如果錯誤發生時客戶端正在傳送數據,那麼使用TCP的服務器實現應當仔細確保在關閉客戶端與服務器之間的連接之前,客戶端已經收到了包含錯誤信息的數據包。如果客戶端在收到錯誤信息後繼續向服務器發送數據,服務器的TCP棧將向客戶端發送一個重置數據包,以清除該客戶端所有還未識別的輸入緩衝,以免這些數據被服務器上的應用程序讀取並干擾後者。

400 Bad Request

1、語義有誤,當前請求無法被服務器理解。除非進行修改,否則客戶端不應該重複提交這個請求。

2、請求參數有誤。

401 Unauthorized

當前請求需要用戶驗證。該響應必須包含一個適用於被請求資源的 WWW-Authenticate 信息頭用以詢問用戶信息。客戶端可以重複提交一個包含恰當的 Authorization 頭信息的請求。如果當前請求已經包含了 Authorization 證書,那麼401響應代表着服務器驗證已經拒絕了那些證書。如果401響應包含了與前一個響應相同的身份驗證詢問,且瀏覽器已經至少嘗試了一次驗證,那麼瀏覽器應當向用戶展示響應中包含的實體信息,因爲這個實體信息中可能包含了相關診斷信息。參見RFC 2617

402 Payment Required

該狀態碼是爲了將來可能的需求而預留的。

403 Forbidden

服務器已經理解請求,但是拒絕執行它。與401響應不同的是,身份驗證並不能提供任何幫助,而且這個請求也不應該被重複提交。如果這不是一個 HEAD 請求,而且服務器希望能夠講清楚爲何請求不能被執行,那麼就應該在實體內描述拒絕的原因。當然服務器也可以返回一個404響應,假如它不希望讓客戶端獲得任何信息。

404 Not Found

請求失敗,請求所希望得到的資源未被在服務器上發現。沒有信息能夠告訴用戶這個狀況到底是暫時的還是永久的。假如服務器知道情況的話,應當使用410狀態碼來告知舊資源因爲某些內部的配置機制問題,已經永久的不可用,而且沒有任何可以跳轉的地址。404這個狀態碼被廣泛應用於當服務器不想揭示到底爲何請求被拒絕或者沒有其他適合的響應可用的情況下。出現這個錯誤的最有可能的原因是服務器端沒有這個頁面。

405 Method Not Allowed

請求行中指定的請求方法不能被用於請求相應的資源。該響應必須返回一個Allow 頭信息用以表示出當前資源能夠接受的請求方法的列表。

鑑於 PUTDELETE 方法會對服務器上的資源進行寫操作,因而絕大部分的網頁服務器都不支持或者在默認配置下不允許上述請求方法,對於此類請求均會返回405錯誤。

406 Not Acceptable

請求的資源的內容特性無法滿足請求頭中的條件,因而無法生成響應實體。

除非這是一個 HEAD 請求,否則該響應就應當返回一個包含可以讓用戶或者瀏覽器從中選擇最合適的實體特性以及地址列表的實體。實體的格式由 Content-Type 頭中定義的媒體類型決定。瀏覽器可以根據格式及自身能力自行作出最佳選擇。但是,規範中並沒有定義任何作出此類自動選擇的標準。

407 Proxy Authentication Required

401響應類似,只不過客戶端必須在代理服務器上進行身份驗證。代理服務器必須返回一個 Proxy-Authenticate 用以進行身份詢問。客戶端可以返回一個 Proxy-Authorization 信息頭用以驗證。參見RFC 2617

408 Request Timeout

請求超時。客戶端沒有在服務器預備等待的時間內完成一個請求的發送。客戶端可以隨時再次提交這一請求而無需進行任何更改。

409 Conflict

由於和被請求的資源的當前狀態之間存在衝突,請求無法完成。這個代碼只允許用在這樣的情況下才能被使用:用戶被認爲能夠解決衝突,並且會重新提交新的請求。該響應應當包含足夠的信息以便用戶發現衝突的源頭。

衝突通常發生於對 PUT 請求的處理中。例如,在採用版本檢查的環境下,某次 PUT 提交的對特定資源的修改請求所附帶的版本信息與之前的某個(第三方)請求向衝突,那麼此時服務器就應該返回一個409錯誤,告知用戶請求無法完成。此時,響應實體中很可能會包含兩個衝突版本之間的差異比較,以便用戶重新提交歸併以後的新版本。

410 Gone

被請求的資源在服務器上已經不再可用,而且沒有任何已知的轉發地址。這樣的狀況應當被認爲是永久性的。如果可能,擁有鏈接編輯功能的客戶端應當在獲得用戶許可後刪除所有指向這個地址的引用。如果服務器不知道或者無法確定這個狀況是否是永久的,那麼就應該使用404狀態碼。除非額外說明,否則這個響應是可緩存的。

410響應的目的主要是幫助網站管理員維護網站,通知用戶該資源已經不再可用,並且服務器擁有者希望所有指向這個資源的遠端連接也被刪除。這類事件在限時、增值服務中很普遍。同樣,410響應也被用於通知客戶端在當前服務器站點上,原本屬於某個個人的資源已經不再可用。當然,是否需要把所有永久不可用的資源標記爲'410 Gone',以及是否需要保持此標記多長時間,完全取決於服務器擁有者。

411 Length Required

服務器拒絕在沒有定義 Content-Length 頭的情況下接受請求。在添加了表明請求消息體長度的有效 Content-Length 頭之後,客戶端可以再次提交該請求。

412 Precondition Failed

服務器在驗證在請求的頭字段中給出先決條件時,沒能滿足其中的一個或多個。這個狀態碼允許客戶端在獲取資源時在請求的元信息(請求頭字段數據)中設置先決條件,以此避免該請求方法被應用到其希望的內容以外的資源上。

413 Request Entity Too Large

服務器拒絕處理當前請求,因爲該請求提交的實體數據大小超過了服務器願意或者能夠處理的範圍。此種情況下,服務器可以關閉連接以免客戶端繼續發送此請求。

如果這個狀況是臨時的,服務器應當返回一個 Retry-After 的響應頭,以告知客戶端可以在多少時間以後重新嘗試。

414 Request-URI Too Long

請求的URI 長度超過了服務器能夠解釋的長度,因此服務器拒絕對該請求提供服務。這比較少見,通常的情況包括:

本應使用POST方法的表單提交變成了GET方法,導致查詢字符串(Query String)過長。

重定向URI “黑洞,例如每次重定向把舊的 URI 作爲新的 URI 的一部分,導致在若干次重定向後 URI 超長。

客戶端正在嘗試利用某些服務器中存在的安全漏洞攻擊服務器。這類服務器使用固定長度的緩衝讀取或操作請求的 URI,當 GET 後的參數超過某個數值後,可能會產生緩衝區溢出,導致任意代碼被執行[1]。沒有此類漏洞的服務器,應當返回414狀態碼。

415 Unsupported Media Type

對於當前請求的方法和所請求的資源,請求中提交的實體並不是服務器中所支持的格式,因此請求被拒絕。

416 Requested Range Not Satisfiable

如果請求中包含了 Range 請求頭,並且 Range 中指定的任何數據範圍都與當前資源的可用範圍不重合,同時請求中又沒有定義 If-Range 請求頭,那麼服務器就應當返回416狀態碼。

假如 Range 使用的是字節範圍,那麼這種情況就是指請求指定的所有數據範圍的首字節位置都超過了當前資源的長度。服務器也應當在返回416狀態碼的同時,包含一個 Content-Range 實體頭,用以指明當前資源的長度。這個響應也被禁止使用 multipart/byteranges 作爲其 Content-Type

417 Expectation Failed

在請求頭 Expect 中指定的預期內容無法被服務器滿足,或者這個服務器是一個代理服務器,它有明顯的證據證明在當前路由的下一個節點上,Expect 的內容無法被滿足。

418 I'm a teapot

421 Too Many Connections

There are too many connections from your internet address

從當前客戶端所在的IP地址到服務器的連接數超過了服務器許可的最大範圍。通常,這裏的IP地址指的是從服務器上看到的客戶端地址(比如用戶的網關或者代理服務器地址)。在這種情況下,連接數的計算可能涉及到不止一個終端用戶。

422 Unprocessable Entity

請求格式正確,但是由於含有語義錯誤,無法響應。(RFC 4918 WebDAV

423 Locked

當前資源被鎖定。(RFC 4918 WebDAV

424 Failed Dependency

由於之前的某個請求發生的錯誤,導致當前請求失敗,例如 PROPPATCH。(RFC 4918 WebDAV

425 Too Early

狀態碼 425 Too Early 代表服務器不願意冒風險來處理該請求,原因是處理該請求可能會被重放,從而造成潛在的重放攻擊。(RFC 8470 [1] 

426 Upgrade Required

客戶端應當切換到TLS/1.0。(RFC 2817

449 Retry With

由微軟擴展,代表請求應當在執行完適當的操作後進行重試。

451 Unavailable For Legal Reasons

該請求因法律原因不可用。(RFC 7725

服務器錯誤

編輯

56字頭)

這類狀態碼代表了服務器在處理請求的過程中有錯誤或者異常狀態發生,也有可能是服務器意識到以當前的軟硬件資源無法完成對請求的處理。除非這是一個HEAD 請求,否則服務器應當包含一個解釋當前錯誤狀態以及這個狀況是臨時的還是永久的解釋信息實體。瀏覽器應當向用戶展示任何在當前響應中被包含的實體。

這些狀態碼適用於任何響應方法。

500 Internal Server Error

服務器遇到了一個未曾預料的狀況,導致了它無法完成對請求的處理。一般來說,這個問題都會在服務器端的源代碼出現錯誤時出現。

501 Not Implemented

服務器不支持當前請求所需要的某個功能。當服務器無法識別請求的方法,並且無法支持其對任何資源的請求。

502 Bad Gateway

作爲網關或者代理工作的服務器嘗試執行請求時,從上游服務器接收到無效的響應。

503 Service Unavailable

由於臨時的服務器維護或者過載,服務器當前無法處理請求。這個狀況是臨時的,並且將在一段時間以後恢復。如果能夠預計延遲時間,那麼響應中可以包含一個 Retry-After 頭用以標明這個延遲時間。如果沒有給出這個 Retry-After 信息,那麼客戶端應當以處理500響應的方式處理它。

注意:503狀態碼的存在並不意味着服務器在過載的時候必須使用它。某些服務器只不過是希望拒絕客戶端的連接。

504 Gateway Timeout

作爲網關或者代理工作的服務器嘗試執行請求時,未能及時從上游服務器(URI標識出的服務器,例如HTTPFTPLDAP)或者輔助服務器(例如DNS)收到響應。

注意:某些代理服務器在DNS查詢超時時會返回400或者500錯誤

505 HTTP Version Not Supported

服務器不支持,或者拒絕支持在請求中使用的 HTTP 版本。這暗示着服務器不能或不願使用與客戶端相同的版本。響應中應當包含一個描述了爲何版本不被支持以及服務器支持哪些協議的實體。

506 Variant Also Negotiates

由《透明內容協商協議》(RFC 2295)擴展,代表服務器存在內部配置錯誤:被請求的協商變元資源被配置爲在透明內容協商中使用自己,因此在一個協商處理中不是一個合適的重點。

507 Insufficient Storage

服務器無法存儲完成請求所必須的內容。這個狀況被認爲是臨時的。WebDAV (RFC 4918)

509 Bandwidth Limit Exceeded

服務器達到帶寬限制。這不是一個官方的狀態碼,但是仍被廣泛使用。

510 Not Extended

獲取資源所需要的策略並沒有被滿足。(RFC 2774

600 Unparseable Response Headers

 

34.DNS原理及其解析過程

DNS 的過程?

關於DNS的獲取流程:
DNS是應用層協議,事實上他是爲其他應用層協議工作的,包括不限於HTTP和SMTP以及FTP,用於將用戶提供的主機名解析爲ip地址。
具體過程如下:
①用戶主機上運行着DNS的客戶端,就是我們的PC機或者手機客戶端運行着DNS客戶端了
②瀏覽器將接收到的url中抽取出域名字段,就是訪問的主機名,比如

http://www.baidu.com/     

, 並將這個主機名傳送給DNS應用的客戶端
③DNS客戶機端向DNS服務器端發送一份查詢報文,報文中包含着要訪問的主機名字段(中間包括一些列緩存查詢以及分佈式DNS集羣的工作)
④該DNS客戶機最終會收到一份回答報文,其中包含有該主機名對應的IP地址
⑤一旦該瀏覽器收到來自DNS的IP地址,就可以向該IP地址定位的HTTP服務器發起TCP連接

Dns服務的工作過程

DNS 客戶機需要查詢程序中使用的名稱時,它會查詢本地DNS 服務器來解析該名稱。客戶機發送的每條查詢消息都包括3條信息,以指定服務器應回答的問題。
指定的 DNS 域名,表示爲完全合格的域名 (FQDN)
指定的查詢類型,它可根據類型指定資源記錄,或作爲查詢操作的專門類型。
● DNS域名的指定類別。
對於DNS 服務器,它始終應指定爲 Internet 類別。例如,指定的名稱可以是計算機的完全合格的域名,如

im.qq.com

,並且指定的查詢類型用於通過該名稱搜索地址資源記錄。
DNS 查詢以各種不同的方式進行解析。客戶機有時也可通過使用從以前查詢獲得的緩存信息就地應答查詢。DNS 服務器可使用其自身的資源記錄信息緩存來應答查詢,也可代表請求客戶機來查詢或聯繫其他 DNS 服務器,以完全解析該名稱,並隨後將應答返回至客戶機。這個過程稱爲遞歸。
另外,客戶機自己也可嘗試聯繫其他的 DNS 服務器來解析名稱。如果客戶機這麼做,它會使用基於服務器應答的獨立和附加的查詢,該過程稱作迭代,即DNS服務器之間的交互查詢就是迭代查詢。
DNS 查詢的過程如下圖所示。

https://pic4.zhimg.com/80/7fcd81756bdc8b52ade0531402c43e43_hd.jpg
 

 

1、在瀏覽器中輸入www . qq .com 域名,操作系統會先檢查自己本地的hosts文件是否有這個網址映射關係,如果有,就先調用這個IP地址映射,完成域名解析。

2、如果hosts裏沒有這個域名的映射,則查找本地DNS解析器緩存,是否有這個網址映射關係,如果有,直接返回,完成域名解析。

3、如果hosts與本地DNS解析器緩存都沒有相應的網址映射關係,首先會找TCP/ip參數中設置的首選DNS服務器,在此我們叫它本地DNS服務器,此服務器收到查詢時,如果要查詢的域名,包含在本地配置區域資源中,則返回解析結果給客戶機,完成域名解析,此解析具有權威性。

4、如果要查詢的域名,不由本地DNS服務器區域解析,但該服務器已緩存了此網址映射關係,則調用這個IP地址映射,完成域名解析,此解析不具有權威性。

5、如果本地DNS服務器本地區域文件與緩存解析都失效,則根據本地DNS服務器的設置(是否設置轉發器)進行查詢,如果未用轉發模式,本地DNS就把請求發至13臺根DNS,根DNS服務器收到請求後會判斷這個域名(.com)是誰來授權管理,並會返回一個負責該頂級域名服務器的一個IP。本地DNS服務器收到IP信息後,將會聯繫負責.com域的這臺服務器。這臺負責.com域的服務器收到請求後,如果自己無法解析,它就會找一個管理.com域的下一級DNS服務器地址(http://qq.com)給本地DNS服務器。當本地DNS服務器收到這個地址後,就會找http://qq.com域服務器,重複上面的動作,進行查詢,直至找到www . qq .com主機。

6、如果用的是轉發模式,此DNS服務器就會把請求轉發至上一級DNS服務器,由上一級服務器進行解析,上一級服務器如果不能解析,或找根DNS或把轉請求轉至上上級,以此循環。不管是本地DNS服務器用是是轉發,還是根提示,最後都是把結果返回給本地DNS服務器,由此DNS服務器再返回給客戶機。 

從客戶端到本地DNS服務器是屬於遞歸查詢,而DNS服務器之間就是的交互查詢就是迭代查詢。

解析順序

  1 瀏覽器緩存

  當用戶通過瀏覽器訪問某域名時,瀏覽器首先會在自己的緩存中查找是否有該域名對應的IP地址(若曾經訪問過該域名且沒有清空緩存便存在);

  2 系統緩存

  當瀏覽器緩存中無域名對應IP則會自動檢查用戶計算機系統Hosts文件DNS緩存是否有該域名對應IP

  3 路由器緩存

  當瀏覽器及系統緩存中均無域名對應IP則進入路由器緩存中檢查,以上三步均爲客服端的DNS緩存;

  4 ISP(互聯網服務提供商)DNS緩存

  當在用戶客服端查找不到域名對應IP地址,則將進入ISP DNS緩存中進行查詢。比如你用的是電信的網絡,則會進入電信的DNS緩存服務器中進行查找;

  5 根域名服務器

  當以上均未完成,則進入根服務器進行查詢。全球僅有13臺根域名服務器,1個主根域名服務器,其餘12爲輔根域名服務器。根域名收到請求後會查看區域文件記錄,若無則將其管轄範圍內頂級域名(如.com)服務器IP告訴本地DNS服務器;

  6 頂級域名服務器

  頂級域名服務器收到請求後查看區域文件記錄,若無則將其管轄範圍內主域名服務器的IP地址告訴本地DNS服務器;

  7 主域名服務器

  主域名服務器接受到請求後查詢自己的緩存,如果沒有則進入下一級域名服務器進行查找,並重復該步驟直至找到正確紀錄;

  8)保存結果至緩存

  本地域名服務器把返回的結果保存到緩存,以備下一次使用,同時將該結果反饋給客戶端,客戶端通過這個IP地址與web服務器建立鏈接。

35. HTTP請求報文(請求行、請求頭、請求體)

1.簡介

HTTP協議(Hyper Text Transfer Protocol,超文本傳輸協議),是用於從萬維網(WWW:World Wide Web )服務器傳輸超文本到本地瀏覽器的傳送協議。

HTTP基於TCP/IP通信協議來傳遞數據。

HTTP基於客戶端/服務端(C/S)架構模型,通過一個可靠的鏈接來交換信息,是一個無狀態的請求/響應協議。

2.特點

1HTTP無連接:無連接的含義是限制每次連接只處理一個請求。服務器處理完客戶的請求,並收到客戶的應答後,即斷開連接。採用這種方式可以節省傳輸時間。

2HTTP媒體獨立的:只要客戶端和服務器知道如何處理的數據內容,任何類型的數據都可以通過HTTP發送。客戶端以及服務器指定使用適合的MIME-type內容類型。

3HTTP無狀態:無狀態是指協議對於事務處理沒有記憶能力。缺少狀態意味着如果後續處理需要前面的信息,則它必須重傳,這樣可能導致每次連接傳送的數據量增大。另一方面,在服務器不需要先前信息時它的應答就較快。

3.HTTP請求報文

HTTP請求報文由3部分組成(請求行+請求頭+請求體):
https://img-blog.csdn.net/20170707143243946?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaGV5dWVfOTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

 

請求行:

①是請求方法,GETPOST是最常見的HTTP方法,除此以外還包括DELETEHEADOPTIONSPUTTRACE。②爲請求對應的URL地址,它和報文頭的Host屬性組成完整的請求URL

③是協議名稱及版本號。

請求頭:

④是HTTP的報文頭,報文頭包含若干個屬性,格式爲屬性名:屬性值,服務端據此獲取客戶端的信息。

與緩存相關的規則信息,均包含在header

請求體:

⑤是報文體,它將一個頁面表單中的組件值通過param1=value1&param2=value2的鍵值對形式編碼成一個格式化串,它承載多個請求參數的數據。不但報文體可以傳遞請求參數,請求URL也可以通過類似於“/chapter15/user.html? param1=value1&param2=value2”的方式傳遞請求參數。 
HTTP
請求報文頭屬性

Accept 

請求報文可通過一個“Accept”報文頭屬性告訴服務端 客戶端接受什麼類型的響應。 
如下報文頭相當於告訴服務端,俺客戶端能夠接受的響應類型僅爲純文本數據啊,你丫別發其它什麼圖片啊,視頻啊過來,那樣我會歇菜的~~~

Accept:text/plain

Accept屬性的值可以爲一個或多個MIME類型的值(描述消息內容類型的因特網標準, 消息能包含文本、圖像、音頻、視頻以及其他應用程序專用的數據)

cookie

客戶端的Cookie就是通過這個報文頭屬性傳給服務端的哦!如下所示:

Cookie: $Version=1; Skin=new;jsessionid=5F4771183629C9834F8382E23

服務端是怎麼知道客戶端的多個請求是隸屬於一個Session呢?注意到後臺的那個jsessionid = 5F4771183629C9834F8382E23木有?原來就是通過HTTP請求報文頭的Cookie屬性的jsessionid的值關聯起來的!(當然也可以通過重寫URL的方式將會話ID附帶在每個URL的後面哦)。

Referer

表示這個請求是從哪個URL過來的,假如你通過google搜索出一個商家的廣告頁面,你對這個廣告頁面感興趣,鼠標一點發送一個請求報文到商家的網站,這個請求報文的Referer報文頭屬性值就是http://www.google.com

Cache-Control

對緩存進行控制,如一個請求希望響應返回的內容在客戶端要被緩存一年,或不希望被緩存就可以通過這個報文頭達到目的。

4.HTTP響應報文

HTTP的響應報文也由三部分組成(響應行+響應頭+響應體)

https://img-blog.csdn.net/20170707145557633?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaGV5dWVfOTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center
 

響應行:

①報文協議及版本; 
②狀態碼及狀態描述;
 

響應頭:

③響應報文頭,也是由多個屬性組成;
 

響應體:

④響應報文體,即我們真正要的乾貨


 

響應狀態碼 

和請求報文相比,響應報文多了一個響應狀態碼,它以清晰明確的語言告訴客戶端本次請求的處理結果。 
HTTP
的響應狀態碼由5段組成:
 

1xx 消息,一般是告訴客戶端,請求已經收到了,正在處理,別急...

2xx 處理成功,一般表示:請求收悉、我明白你要的、請求已受理、已經處理完成等信息.

3xx 重定向到其它地方。它讓客戶端再發起一個請求以完成整個處理。

4xx 處理髮生錯誤,責任在客戶端,如客戶端的請求一個不存在的資源,客戶端未被授權,禁止訪問等。

5xx 處理髮生錯誤,責任在服務端,如服務端拋出異常,路由出錯,HTTP版本不支持等。

以下是幾個常見的狀態碼: 
200 OK 

你最希望看到的,即處理成功! 
303 See Other 

我把你redirect到其它的頁面,目標的URL通過響應報文頭的Location告訴你。
304 Not Modified 
告訴客戶端,你請求的這個資源至你上次取得後,並沒有更改,你直接用你本地的緩存吧,我很忙哦,你能不能少來煩我啊! 
404 Not Found 

你最不希望看到的,即找不到頁面。如你在google上找到一個頁面,點擊這個鏈接返回404,表示這個頁面已經被網站刪除了,google那邊的記錄只是美好的回憶。
500 Internal Server Error
看到這個錯誤,你就應該查查服務端的日誌了,肯定拋出了一堆異常,別睡了,起來改BUG去吧!
 

200 (OK): 找到了該資源,並且一切正常。
 

302/307:臨時重定向,指出請求的文檔已被臨時移動到別處, 此文檔的新的urllocation響應頭中給出

304 (NOT MODIFIED): 該資源在上次請求之後沒有任何修改。這通常用於瀏覽器的緩存機制。

401 (UNAUTHORIZED): 客戶端無權訪問該資源。這通常會使得瀏覽器要求用戶輸入用戶名和密碼,以登錄到服務器。

403 (FORBIDDEN): 客戶端未能獲得授權。這通常是在401之後輸入了不正確的用戶名或密碼。

404 (NOT FOUND): 在指定的位置不存在所申請的資源。

常見的HTTP響應報文頭屬性

Cache-Control 
響應輸出到客戶端後,服務端通過該報文頭屬告訴客戶端如何控制響應內容的緩存。常見的取值有privatepublicno-cachemax-ageno-store,默認爲private
private:             客戶端可以緩存
public:              客戶端和代理服務器都可緩存(前端的同學,可以認爲publicprivate是一樣的)
max-age=xxx:   緩存的內容將在 xxx 秒後失效
no-cache:          需要使用對比緩存來驗證緩存數據
no-store:           所有內容都不會緩存

默認爲private,緩存時間爲31536000秒(365天)也就是說,在365天內再次請求這條數據,都會直接獲取緩存數據庫中的數據,直接使用。

ETag 

一個代表響應服務端資源(如頁面)版本的報文頭屬性,如果某個服務端資源發生變化了,這個ETag就會相應發生變化。它是Cache-Control的有益補充,可以讓客戶端更智能地處理什麼時候要從服務端取資源,什麼時候可以直接從緩存中返回響應。
 

Location

 我們在JSP中讓頁面Redirect到一個某個A頁面中,其實是讓客戶端再發一個請求到A頁面,這個需要Redirect到的A頁面的URL,其實就是通過響應報文頭的Location屬性告知客戶端的,如下的報文頭屬性,將使客戶端redirectiteye的首頁中:
 

Location: http://www.iteye.com 

Set-Cookie 
服務端可以設置客戶端的Cookie,其原理就是通過這個響應報文頭屬性實現的:

Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1 

5.cookie機制:

客戶端請求服務器,如果服務器需要記錄該用戶狀態,就使用response向客戶端瀏覽器頒發一個Cookie。客戶端瀏覽器會把Cookie保存起來。當瀏覽器再請求該網站時,瀏覽器把請求的網址連同該Cookie一同提交給服務器。服務器檢查該Cookie,以此來辨認用戶狀態。服務器還可以根據需要修改Cookie的內容。

CookiemaxAge決定着Cookie的有效期,單位爲秒(Second)。Cookie中通過getMaxAge()方法與setMaxAge(int maxAge)方法來讀寫maxAge屬性。

如果maxAge屬性爲正數,則表示該Cookie會在maxAge秒之後自動失效。

如果maxAge爲負數,則表示該Cookie僅在本瀏覽器窗口以及本窗口打開的子窗口內有效,關閉窗口後該Cookie即失效。

如果maxAge0,則表示刪除該Cookie

 

Cookie並不提供修改、刪除操作。如果要修改某個Cookie,只需要新建一個同名的Cookie,添加到response中覆蓋原來的Cookie

如果要刪除某個Cookie,只需要新建一個同名的Cookie,並將maxAge設置爲0,並添加到response中覆蓋原來的Cookie
 

Cookie cookie = new Cookie("username","helloweenvsfei");   // 新建Cookie

cookie.setMaxAge(0);                          // 設置生命週期爲0,不能爲負數

response.addCookie(cookie);                    // 必須執行這一句 輸出到客戶端

36. MySQL索引查詢失效的幾個情況:

首先,複習一下索引的創建:

普通的索引的創建:

CREATE INDEX  (自定義)索引名  ON  數據表(字段);

複合索引的創建:

CREATE INDEX  (自定義)索引名  ON  數據表(字段,字段,。。。);

刪除索引:DROP INDEX 索引名;

以下通過explain顯示出mysql執行的字段內容:

  • id: SELECT 查詢的標識符. 每個 SELECT 都會自動分配一個唯一的標識符.
  • select_type: SELECT 查詢的類型.
  • table: 查詢的是哪個表
  • partitions: 匹配的分區
  • type: join 類型
  • possible_keys: 此次查詢中可能選用的索引
  • key: 此次查詢中確切使用到的索引.
  • ref: 哪個字段或常數與 key 一起被使用
  • rows: 顯示此查詢一共掃描了多少行. 這個是一個估計值.
  • filtered: 表示此查詢條件所過濾的數據的百分比
  • extra: 額外的信息

 索引查詢失效的幾個情況

1like %開頭,索引無效;當like前綴沒有%,後綴有%時,索引有效。

2or語句前後沒有同時使用索引。當or左右查詢字段只有一個是索引,該索引失效,只有當or左右查詢字段均爲索引時,纔會生效

3、組合索引,不是使用第一列索引,索引失效。

4、數據類型出現隱式轉化。如varchar不加單引號的話可能會自動轉換爲int型,使索引無效,產生全表掃描。(如果列類型是字符串,那一定要在條件中將數據使用引號引用起來,否則不使用索引

5、在索引列上使用 IS NULL IS NOT NULL操作。索引是不索引空值的,所以這樣的操作不能使用索引,可以用其他的辦法處理,例如:數字類型,判斷大於0,字符串類型設置一個默認值,判斷是否等於默認值即可。

6、在索引字段上使用not<>!=。不等於操作符是永遠不會用到索引的,因此對它的處理只會產生全表掃描。 優化方法: key<>0 改爲 key>0 or key<0

7、對索引字段進行計算操作、字段上使用函數。(索引爲 emp(ename,empno,sal)

8、當全表掃描速度比索引速度快時,mysql會使用全表掃描,此時索引失效。

 

37.Mysql 鎖類型和加鎖分析

一、鎖類型介紹:     

MySQL有三種鎖的級別:頁級、表級、行級。

 

表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的概率最高,併發度最低。

行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。

頁面鎖:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度一般

算法:

next KeyLocks鎖,同時鎖住記錄(數據),並且鎖住記錄前面的Gap   

Gap鎖,不鎖記錄,僅僅記錄前面的Gap

Recordlock鎖(鎖數據,不鎖Gap)

所以其實 Next-KeyLocks=Gap鎖+ Recordlock鎖

 

二、死鎖產生原因和示例

1、產生原因:

        所謂死鎖<DeadLock>:是指兩個或兩個以上的進程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去.此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。表級鎖不會產生死鎖.所以解決死鎖主要還是針對於最常用的InnoDB。

 

死鎖的關鍵在於:兩個(或以上)的Session加鎖的順序不一致。

 

那麼對應的解決死鎖問題的關鍵就是:讓不同的session加鎖有次序

 

2、產生示例:

案例一

需求:將投資的錢拆成幾份隨機分配給借款人。

 

起初業務程序思路是這樣的:

 

投資人投資後,將金額隨機分爲幾份,然後隨機從借款人表裏面選幾個,然後通過一條條select for update 去更新借款人表裏面的餘額等。

 

例如兩個用戶同時投資,A用戶金額隨機分爲2份,分給借款人1,2

 

B用戶金額隨機分爲2份,分給借款人2,1

 

由於加鎖的順序不一樣,死鎖當然很快就出現了。 

 

對於這個問題的改進很簡單,直接把所有分配到的借款人直接一次鎖住就行了。

 

Select * from xxx where id in (xx,xx,xx) for update //for update 加行級鎖

 

在in裏面的列表值mysql是會自動從小到大排序,加鎖也是一條條從小到大加的鎖

 

例如(以下會話id爲主鍵):

 

Session1:

 

mysql> select * from t3 where id in (8,9) for update;

+----+--------+------+---------------------+

| id | course | name | ctime               |

+----+--------+------+---------------------+

|  8 | WA     | f    | 2016-03-02 11:36:30 |

|  9 | JX     | f    | 2016-03-01 11:36:30 |

+----+--------+------+---------------------+

rows in set (0.04 sec)

 

Session2:

select * from t3 where id in (10,8,5) for update;

鎖等待中……

 

其實這個時候id=10這條記錄沒有被鎖住的,但id=5的記錄已經被鎖住了,鎖的等待在id=8的這裏

不信請看

 

Session3:

mysql> select * from t3 where id=5 for update;

鎖等待中

 

 Session4:

mysql> select * from t3 where id=10 for update;

+----+--------+------+---------------------+

| id | course | name | ctime               |

+----+--------+------+---------------------+

| 10 | JB     | g    | 2016-03-10 11:45:05 |

+----+--------+------+---------------------+

row in set (0.00 sec)

在其它session中id=5是加不了鎖的,但是id=10是可以加上鎖的。

案例二

 

在開發中,經常會做這類的判斷需求:根據字段值查詢(有索引),如果不存在,則插入;否則更新。

 

以id爲主鍵爲例,目前還沒有id=22的行

 

Session1:

select * from t3 where id=22 for update;

Empty set (0.00 sec)

 

session2:

select * from t3 where id=23  for update;

Empty set (0.00 sec)

 

Session1:

insert into t3 values(22,'ac','a',now());

鎖等待中……

 Session2:

insert into t3 values(23,'bc','b',now());

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

當對存在的行進行鎖的時候(主鍵),mysql就只有行鎖。

當對未存在的行進行鎖的時候(即使條件爲主鍵),mysql是會鎖住一段範圍(有gap鎖) 

鎖住的範圍爲:

 (無窮小或小於表中鎖住id的最大值,無窮大或大於表中鎖住id的最小值) 

如:如果表中目前有已有的id爲(11 , 12)

那麼就鎖住(12,無窮大)

如果表中目前已有的id爲(11 , 30)

那麼就鎖住(11,30)

對於這種死鎖的解決辦法是:

insert into t3(xx,xx) on duplicate key update `xx`='XX';

用mysql特有的語法來解決此問題。因爲insert語句對於主鍵來說,插入的行不管有沒有存在,都會只有行鎖

案例三

mysql> select * from t3 where id=9 for update;

+----+--------+------+---------------------+

| id | course | name | ctime               |

+----+--------+------+---------------------+

|  9 | JX     | f    | 2016-03-01 11:36:30 |

+----+--------+------+---------------------+

 

row in set (0.00 sec)

 

 Session2:

mysql> select * from t3 where id<20 for update;

鎖等待中

 Session1:

mysql> insert into t3 values(7,'ae','a',now());

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

 這個跟案例一其它是差不多的情況,只是session1不按常理出牌了,

 

Session2在等待Session1的id=9的鎖,session2又持了1到8的鎖(注意9到19的範圍並沒有被session2鎖住),最後,session1在插入新行時又得等待session2,故死鎖發生了。

 

這種一般是在業務需求中基本不會出現,因爲你鎖住了id=9,卻又想插入id=7的行,這就有點跳了,當然肯定也有解決的方法,那就是重理業務需求,避免這樣的寫法。 

 

案例四

一般的情況,兩個session分別通過一個sql持有一把鎖,然後互相訪問對方加鎖的數據產生死鎖。

 

案例五

兩個單條的sql語句涉及到的加鎖數據相同,但是加鎖順序不同,導致了死鎖。

 死鎖場景如下:

 

表結構:

 

CREATE TABLE dltask (

    id bigint unsigned NOT NULL AUTO_INCREMENT COMMENT ‘auto id’,

    a varchar(30) NOT NULL COMMENT ‘uniq.a’,

    b varchar(30) NOT NULL COMMENT ‘uniq.b’,

    c varchar(30) NOT NULL COMMENT ‘uniq.c’,

    x varchar(30) NOT NULL COMMENT ‘data’,  

    PRIMARY KEY (id),

    UNIQUE KEY uniq_a_b_c (a, b, c)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=’deadlock test’;

      a,b,c三列,組合成一個唯一索引,主鍵索引爲id列。

 

事務隔離級別:

 

      RR (Repeatable Read)

 

每個事務只有一條SQL:

 

       delete from dltask where a=? and b=? and c=?;

 

SQL的執行計劃:

 

 

 

死鎖日誌:

 

 

 

         衆所周知,InnoDB上刪除一條記錄,並不是真正意義上的物理刪除,而是將記錄標識爲刪除狀態。(注:這些標識爲刪除狀態的記錄,後續會由後臺的Purge操作進行回收,物理刪除。但是,刪除狀態的記錄會在索引中存放一段時間。) 在RR隔離級別下,唯一索引上滿足查詢條件,但是卻是刪除記錄,如何加鎖?InnoDB在此處的處理策略與前兩種策略均不相同,或者說是前兩種策略的組合:對於滿足條件的刪除記錄,InnoDB會在記錄上加next key lock X(對記錄本身加X鎖,同時鎖住記錄前的GAP,防止新的滿足條件的記錄插入。) Unique查詢,三種情況,對應三種加鎖策略,總結如下:

 

       此處,我們看到了next key鎖,是否很眼熟?對了,前面死鎖中事務1,事務2處於等待狀態的鎖,均爲next key鎖。明白了這三個加鎖策略,其實構造一定的併發場景,死鎖的原因已經呼之欲出。但是,還有一個前提策略需要介紹,那就是InnoDB內部採用的死鎖預防策略。 

 

找到滿足條件的記錄,並且記錄有效,則對記錄加X鎖,No Gap鎖(lock_mode X locks rec but not gap);

找到滿足條件的記錄,但是記錄無效(標識爲刪除的記錄),則對記錄加next key鎖(同時鎖住記錄本身,以及記錄之前的Gap:lock_mode X);

未找到滿足條件的記錄,則對第一個不滿足條件的記錄加Gap鎖,保證沒有滿足條件的記錄插入(locks gap before rec);

死鎖預防策略 

 

      InnoDB引擎內部(或者說是所有的數據庫內部),有多種鎖類型:事務鎖(行鎖、表鎖),Mutex(保護內部的共享變量操作)、RWLock(又稱之爲Latch,保護內部的頁面讀取與修改)。

 

      InnoDB每個頁面爲16K,讀取一個頁面時,需要對頁面加S鎖,更新一個頁面時,需要對頁面加上X鎖。任何情況下,操作一個頁面,都會對頁面加鎖,頁面鎖加上之後,頁面內存儲的索引記錄纔不會被併發修改。

 

因此,爲了修改一條記錄,InnoDB內部如何處理:

 

根據給定的查詢條件,找到對應的記錄所在頁面;

對頁面加上X鎖(RWLock),然後在頁面內尋找滿足條件的記錄;

在持有頁面鎖的情況下,對滿足條件的記錄加事務鎖(行鎖:根據記錄是否滿足查詢條件,記錄是否已經被刪除,分別對應於上面提到的3種加鎖策略之一);

死鎖預防策略:相對於事務鎖,頁面鎖是一個短期持有的鎖,而事務鎖(行鎖、表鎖)是長期持有的鎖。因此,爲了防止頁面鎖與事務鎖之間產生死鎖。InnoDB做了死鎖預防的策略:持有事務鎖(行鎖、表鎖),可以等待獲取頁面鎖;但反之,持有頁面鎖,不能等待持有事務鎖。 

 

       根據死鎖預防策略,在持有頁面鎖,加行鎖的時候,如果行鎖需要等待。則釋放頁面鎖,然後等待行鎖。此時,行鎖獲取沒有任何鎖保護,因此加上行鎖之後,記錄可能已經被併發修改。因此,此時要重新加回頁面鎖,重新判斷記錄的狀態,重新在頁面鎖的保護下,對記錄加鎖。如果此時記錄未被併發修改,那麼第二次加鎖能夠很快完成,因爲已經持有了相同模式的鎖。但是,如果記錄已經被併發修改,那麼,就有可能導致本文前面提到的死鎖問題。

 

        以上的InnoDB死鎖預防處理邏輯,對應的函數,是row0sel.c::row_search_for_mysql()。感興趣的朋友,可以跟蹤調試下這個函數的處理流程,很複雜,但是集中了InnoDB的精髓。

 

 剖析死鎖的成因

 

        做了這麼多鋪墊,有了Delete操作的3種加鎖邏輯、InnoDB的死鎖預防策略等準備知識之後,再回過頭來分析本文最初提到的死鎖問題,就會手到拈來,事半而功倍。

 

首先,假設dltask中只有一條記錄:(1, ‘a’, ‘b’, ‘c’, ‘data’)。三個併發事務,同時執行以下的這條SQL:

 

delete from dltask where a=’a’ and b=’b’ and c=’c’;

 

並且產生了以下的併發執行邏輯,就會產生死鎖:

     上面分析的這個併發流程,完整展現了死鎖日誌中的死鎖產生的原因。其實,根據事務1步驟6,與事務0步驟3/4之間的順序不同,死鎖日誌中還有可能產生另外一種情況,那就是事務1等待的鎖模式爲記錄上的X鎖 + No Gap鎖(lock_mode X locks rec but not gap waiting)。這第二種情況,也是”潤潔”同學給出的死鎖用例中,使用MySQL 5.6.15版本測試出來的死鎖產生的原因。

 

此類死鎖,產生的幾個前提:

Delete操作,針對的是唯一索引上的等值查詢的刪除;(範圍下的刪除,也會產生死鎖,但是死鎖的場景,跟本文分析的場景,有所不同)

至少有3個(或以上)的併發刪除操作;

併發刪除操作,有可能刪除到同一條記錄,並且保證刪除的記錄一定存在;

事務的隔離級別設置爲Repeatable Read,同時未設置innodb_locks_unsafe_for_binlog參數(此參數默認爲FALSE);(Read Committed隔離級別,由於不會加Gap鎖,不會有next key,因此也不會產生死鎖)

使用的是InnoDB存儲引擎;(廢話!MyISAM引擎根本就沒有行鎖)

38. 進程所佔的虛擬內存和物理內存是什麼樣的

你的程序運行在一個線程中,而線程運行在java虛擬機中,虛擬機內存又是由內存來,所以,內存 > 虛擬機內存 > 每個線程所開闢的內存。

 

1物理內存是指由於安裝內存條而獲得的臨時儲存空間。主要作用是在計算機運行時爲操作系統和各種程序提供臨時儲存。常見的物理內存規格有256M、512M、1G、2G等,當物理內存不足時,可以用虛擬內存代替。

2虛擬內存是計算機系統內存管理的一種技術。它使得應用程序認爲它擁有連續可用的內存(一個連續完整的地址空間),它通常是被分隔成多個物理內存碎片,還有部分暫時存儲在外部磁盤存儲器上,在需要時進行數據交換。

擴展資料

虛擬內存工作原理:

1、虛擬內存中央處理器訪問主存的邏輯地址分解成組號a和組內地址b,並對組號a進行地址變換,即將邏輯組號a作爲索引,查地址變換表,以確定該組信息是否存放在主存內。

2、虛擬內存基於對地址空間的重定義的,即把地址空間定義爲“連續的虛擬內存地址”,以藉此“欺騙”程序,使它們以爲自己正在使用一大塊的“連續”地址。

39.jvm進程所佔用的虛存大於了虛擬機的堆棧設置參數,爲什麼不報錯

  1. 首先要搞清楚JVM的內存機制: 
    JVM內存區域總體分兩類,heap區 和 非heap 區(本地內存) 。 
    - heap區: 堆區分爲Young Gen(新生代),Tenured Gen(老年代-養老區)。其中新生代又分爲Eden Space(伊甸園)、Survivor Space(倖存者區)。 
    - 非heap區: Code Cache(代碼緩存區)、Perm Gen(永久代)、Jvm Stack(java虛擬機棧)、Local Method Statck(本地方法棧)。

    首先JVM本身是一個應用程序,一般是通過C、C++實現的,這個應用程序要正常運行,是一定要向操作系統申請內存的,維持這個進程正常運行的內存,即可以理解成本地內存。 

    其次,Java程序在運行過程中,會new出很多對象,這些對象又是保存在JVM的堆內存中的,Java程序在執行過程中,會加載很多類,這些類也是保存在堆內存中。 

    JVM本身要對堆內存進行維護和管理,還負責垃圾回收,這些也同時會消耗本地內存,JVM在啓動過程中,會依賴一些動態庫,這同樣也消耗本地內存。 

    JAVA_OPTS='-Xms3096m -Xmx3096m -Dsun.net.inetaddr.ttl=180' 這個只是用於設置堆內存的大小,而JVM運行過程中到底會向操作系統申請多少內存,這個是由JVM在運行過程中動態決定的,我們無法設置

40. Mysql怎麼保證一致性的?

這個問題分爲兩個層面來說。

從數據庫層面,數據庫通過原子性、隔離性、持久性來保證一致性。也就是說ACID四大特性之中,C(一致性)是目的,A(原子性)、I(隔離性)、D(持久性)是手段,是爲了保證一致性,數據庫提供的手段。數據庫必須要實現AID三大特性,纔有可能實現一致性。例如,原子性無法保證,顯然一致性也無法保證。

但是,如果你在事務裏故意寫出違反約束的代碼,一致性還是無法保證的。例如,你在轉賬的例子中,你的代碼裏故意不給B賬戶加錢,那一致性還是無法保證。

因此,還必須從應用層角度考慮。

從應用層面,通過代碼判斷數據庫數據是否有效,然後決定回滾還是提交數據!

41.Mysql怎麼保證原子性的?

是利用Innodb的undo log。

undo log名爲回滾日誌,是實現原子性的關鍵,當事務回滾時能夠撤銷所有已經成功執行的sql語句,他需要記錄你要回滾的相應日誌信息。

例如

(1)當你delete一條數據的時候,就需要記錄這條數據的信息,回滾的時候,insert這條舊數據

(2)當你update一條數據的時候,就需要記錄之前的舊值,回滾的時候,根據舊值執行update操作

(3)當年insert一條數據的時候,就需要這條記錄的主鍵,回滾的時候,根據主鍵執行delete操作

undo log記錄了這些回滾需要的信息,當事務執行失敗或調用了rollback,導致事務需要回滾,便可以利用undo log中的信息將數據回滾到修改之前的樣子。

ps:具體的undo log日誌長啥樣,這個可以寫一篇文章了。而且寫出來,看的人也不多,姑且先這麼簡單的理解吧。

42. Mysql怎麼保證持久性的?

是利用Innodb的redo log。

正如之前說的,Mysql是先把磁盤上的數據加載到內存中,在內存中對數據進行修改,再刷回磁盤上。如果此時突然宕機,內存中的數據就會丟失。

怎麼解決這個問題?

簡單啊,事務提交前直接把數據寫入磁盤就行啊。

這麼做有什麼問題?

只修改一個頁面裏的一個字節,就要將整個頁面刷入磁盤,太浪費資源了。畢竟一個頁面16kb大小,你只改其中一點點東西,就要將16kb的內容刷入磁盤,聽着也不合理。

畢竟一個事務裏的SQL可能牽涉到多個數據頁的修改,而這些數據頁可能不是相鄰的,也就是屬於隨機IO。顯然操作隨機IO,速度會比較慢。

於是,決定採用redo log解決上面的問題。當做數據修改的時候,不僅在內存中操作,還會在redo log中記錄這次操作。當事務提交的時候,會將redo log日誌進行刷盤(redo log一部分在內存中,一部分在磁盤上)。當數據庫宕機重啓的時候,會將redo log中的內容恢復到數據庫中,再根據undo log和binlog內容決定回滾數據還是提交數據。

採用redo log的好處?

其實好處就是將redo log進行刷盤比對數據頁刷盤效率高,具體表現如下

redo log體積小,畢竟只記錄了哪一頁修改了啥,因此體積小,刷盤快。

redo log是一直往末尾進行追加,屬於順序IO。效率顯然比隨機IO來的快。

ps:不想具體去談redo log具體長什麼樣,因爲內容太多了。

43. Mysql怎麼保證隔離性的?

利用的是鎖和MVCC機制還是拿轉賬例子來說明,有一個賬戶表如下

表名t_balance

id

user_id

balance

1

A

200

2

B

0

其中id是主鍵,user_id爲賬戶名,balance爲餘額。還是以轉賬兩次爲例,如下圖所示

至於MVCC,即多版本併發控制(Multi Version Concurrency Control),一個行記錄數據有多個版本對快照數據,這些快照數據在undo log中。

如果一個事務讀取的行正在做DELELE或者UPDATE操作,讀取操作不會等行上的鎖釋放,而是讀取該行的快照版本。

由於MVCC機制在可重複讀(Repeateable Read)和讀已提交(Read Commited)的MVCC表現形式不同,就不贅述了。

但是有一點說明一下,在事務隔離級別爲讀已提交(Read Commited)時,一個事務能夠讀到另一個事務已經提交的數據,是不滿足隔離性的。但是當事務隔離級別爲可重複讀(Repeateable Read)中,是滿足隔離性的。

44. Mysql中的MVCC策略

什麼是MVCC?

英文全稱爲Multi-Version Concurrency Control,翻譯爲中文即 多版本併發控制。在小編看來,他無非就是樂觀鎖的一種實現方式。在Java編程中,如果把樂觀鎖看成一個接口,MVCC便是這個接口的一個實現類而已。

 

特點

1.MVCC其實廣泛應用於數據庫技術,像Oracle,PostgreSQL等也引入了該技術,即適用範圍廣

2.MVCC並沒有簡單的使用數據庫的行鎖,而是使用了行級鎖,row_level_lock,而非InnoDB中的innodb_row_lock.

基本原理

MVCC的實現,通過保存數據在某個時間點的快照來實現的。這意味着一個事務無論運行多長時間,在同一個事務裏能夠看到數據一致的視圖。根據事務開始的時間不同,同時也意味着在同一個時刻不同事務看到的相同表裏的數據可能是不同的。

基本特徵

  • 每行數據都存在一個版本,每次數據更新時都更新該版本。
  • 修改時Copy出當前版本隨意修改,各個事務之間無干擾。
  • 保存時比較版本號,如果成功(commit),則覆蓋原記錄;失敗則放棄copy(rollback)

InnoDB存儲引擎MVCC的實現策略

在每一行數據中額外保存兩個隱藏的列:當前行創建時的版本號和刪除時的版本號(可能爲空,其實還有一列稱爲回滾指針,用於事務回滾,不在本文範疇)。這裏的版本號並不是實際的時間值,而是系統版本號。每開始新的事務,系統版本號都會自動遞增。事務開始時刻的系統版本號會作爲事務的版本號,用來和查詢每行記錄的版本號進行比較。

每個事務又有自己的版本號,這樣事務內執行CRUD操作時,就通過版本號的比較來達到數據版本控制的目的。

MVCC下InnoDB的增刪查改是怎麼work的

1.插入數據(insert):記錄的版本號即當前事務的版本號

執行一條數據語句:insert into testmvcc values(1,"test");

假設事務id爲1,那麼插入後的數據行如下:

 

2、在更新操作的時候,採用的是先標記舊的那行記錄爲已刪除,並且刪除版本號是事務版本號,然後插入一行新的記錄的方式。

比如,針對上面那行記錄,事務Id爲2 要把name字段更新

update table set name= 'new_value' where id=1;

 

3、刪除操作的時候,就把事務版本號作爲刪除版本號。比如

delete from table where id=1;

 

4、查詢操作:

從上面的描述可以看到,在查詢時要符合以下兩個條件的記錄才能被事務查詢出來:

1) 刪除版本號未指定或者大於當前事務版本號,即查詢事務開啓後確保讀取的行未被刪除。(即上述事務id爲2的事務查詢時,依然能讀取到事務id爲3所刪除的數據行)

2) 創建版本號 小於或者等於 當前事務版本號 ,就是說記錄創建是在當前事務中(等於的情況)或者在當前事務啓動之前的其他事物進行的insert。

(即事務id爲2的事務只能讀取到create version<=2的已提交的事務的數據集)

補充:

1.MVCC手段只適用於Msyql隔離級別中的讀已提交(Read committed)和可重複讀(Repeatable Read).

2.Read uncimmitted由於存在髒讀,即能讀到未提交事務的數據行,所以不適用MVCC.

原因是MVCC的創建版本和刪除版本只要在事務提交後纔會產生。

3.串行化由於是會對所涉及到的表加鎖,並非行鎖,自然也就不存在行的版本控制問題。

4.通過以上總結,可知,MVCC主要作用於事務性的,有行鎖控制的數據庫模型。

關於Mysql中MVCC的總結

客觀上,我們認爲他就是樂觀鎖的一整實現方式,就是每行都有版本號,保存時根據版本號決定是否成功。

但由於Mysql的寫操作會加排他鎖(前文有講),如果鎖定了還算不算是MVCC?

瞭解樂觀鎖的小夥伴們,都知道其主要依靠版本控制,即消除鎖定,二者相互矛盾,so從某種意義上來說,Mysql的MVCC並非真正的MVCC,他只是借用MVCC的名號實現了讀的非阻塞而已。

 

45.mysql分表的3種方法

一,先說一下爲什麼要分表

當一張的數據達到幾百萬時,你查詢一次所花的時間會變多,如果有聯合查詢的話,我想有可能會死在那兒了。分表的目的就在於此,減小數據庫的負擔,縮短查詢時間。

根據個人經驗,mysql執行一個sql的過程如下:

1,接收到sql;2,把sql放到排隊隊列中 ;3,執行sql;4,返回執行結果。在這個執行過程中最花時間在什麼地方呢?第一,是排隊等待的時間,第二,sql的執行時間。其實這二個是一回事,等待的同時,肯定有sql在執行。所以我們要縮短sql的執行時間。

mysql中有一種機制是表鎖定和行鎖定,爲什麼要出現這種機制,是爲了保證數據的完整性,我舉個例子來說吧,如果有二個sql都要修改同一張表的同一條數據,這個時候怎麼辦呢,是不是二個sql都可以同時修改這條數據呢?很顯然mysql對這種情況的處理是,一種是表鎖定(myisam存儲引擎),一個是行鎖定(innodb存儲引擎)。表鎖定表示你們都不能對這張表進行操作,必須等我對錶操作完纔行。行鎖定也一樣,別的sql必須等我對這條數據操作完了,才能對這條數據進行操作。如果數據太多,一次執行的時間太長,等待的時間就越長,這也是我們爲什麼要分表的原因。

二,分表

1,做mysql集羣,

例如:利用mysql cluster ,mysql proxy,mysql replication,drdb等等

有人會問mysql集羣,根分表有什麼關係嗎?雖然它不是實際意義上的分表,但是它啓到了分表的作用,做集羣的意義是什麼呢?爲一個數據庫減輕負擔,說白了就是減少sql排隊隊列中的sql的數量,舉個例子:有10個sql請求,如果放在一個數據庫服務器的排隊隊列中,他要等很長時間,如果把這10個sql請求,分配到5個數據庫服務器的排隊隊列中,一個數據庫服務器的隊列中只有2個,這樣等待時間是不是大大的縮短了呢?這已經很明顯了。所以我把它列到了分表的範圍以內,我做過一些mysql的集羣:

linux mysql proxy 的安裝,配置,以及讀寫分離

mysql replication 互爲主從的安裝及配置,以及數據同步

優點:擴展性好,沒有多個分表後的複雜操作(php代碼)

缺點:單個表的數據量還是沒有變,一次操作所花的時間還是那麼多,硬件開銷大。

2,預先估計會出現大數據量並且訪問頻繁的表,將其分爲若干個表

這種預估大差不差的,論壇裏面發表帖子的表,時間長了這張表肯定很大,幾十萬,幾百萬都有可能。 聊天室裏面信息表,幾十個人在一起一聊一個晚上,時間長了,這張表的數據肯定很大。像這樣的情況很多。所以這種能預估出來的大數據量表,我們就事先分出個N個表,這個N是多少,根據實際情況而定。以聊天信息表爲例:

我事先建100個這樣的表,message_00,message_01,message_02..........message_98,message_99.然後根據用戶的ID來判斷這個用戶的聊天信息放到哪張表裏面,你可以用hash的方式來獲得,可以用求餘的方式來獲得,方法很多,各人想各人的吧。下面用hash的方法來獲得表名:

  1. <?php  
  2. function get_hash_table($table,$userid) {  
  3.  $str = crc32($userid);  
  4.  if($str<0){  
  5.  $hash = "0".substr(abs($str), 0, 1);  
  6.  }else{  
  7.  $hash = substr($str, 0, 2);  
  8.  }  
  9.   
  10.  return $table."_".$hash;  
  11. }  
  12.   
  13. echo get_hash_table('message','user18991');     //結果爲message_10  
  14. echo get_hash_table('message','user34523');    //結果爲message_13  
  15. ?> 

說明一下,上面的這個方法,告訴我們user18991這個用戶的消息都記錄在message_10這張表裏,user34523這個用戶的消息都記錄在message_13這張表裏,讀取的時候,只要從各自的表中讀取就行了。

優點:避免一張表出現幾百萬條數據,縮短了一條sql的執行時間

缺點:當一種規則確定時,打破這條規則會很麻煩,上面的例子中我用的hash算法是crc32,如果我現在不想用這個算法了,改用md5後,會使同一個用戶的消息被存儲到不同的表中,這樣數據亂套了。擴展性很差。

3,利用merge存儲引擎來實現分表

我覺得這種方法比較適合,那些沒有事先考慮,而已經出現了得,數據查詢慢的情況。這個時候如果要把已有的大數據量表分開比較痛苦,最痛苦的事就是改代碼,因爲程序裏面的sql語句已經寫好了,現在一張表要分成幾十張表,甚至上百張表,這樣sql語句是不是要重寫呢?舉個例子,我很喜歡舉子

mysql>show engines;的時候你會發現mrg_myisam其實就是merge。

  1. mysql> CREATE TABLE IF NOT EXISTS `user1` (  
  2.  ->   `id` int(11) NOT NULL AUTO_INCREMENT,  
  3.  ->   `name` varchar(50) DEFAULT NULL,  
  4.  ->   `sex` int(1) NOT NULL DEFAULT '0',  
  5.  ->   PRIMARY KEY (`id`)  
  6.  -> ) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;  
  7. Query OK, 0 rows affected (0.05 sec)  
  8.   
  9. mysql> CREATE TABLE IF NOT EXISTS `user2` (  
  10.  ->   `id` int(11) NOT NULL AUTO_INCREMENT,  
  11.  ->   `name` varchar(50) DEFAULT NULL,  
  12.  ->   `sex` int(1) NOT NULL DEFAULT '0',  
  13.  ->   PRIMARY KEY (`id`)  
  14.  -> ) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;  
  15. Query OK, 0 rows affected (0.01 sec)  
  16.   
  17. mysql> INSERT INTO `user1` (`name`, `sex`) VALUES('張映', 0);  
  18. Query OK, 1 row affected (0.00 sec)  
  19.   
  20. mysql> INSERT INTO `user2` (`name`, `sex`) VALUES('tank', 1);  
  21. Query OK, 1 row affected (0.00 sec)  
  22.   
  23. mysql> CREATE TABLE IF NOT EXISTS `alluser` (  
  24.  ->   `id` int(11) NOT NULL AUTO_INCREMENT,  
  25.  ->   `name` varchar(50) DEFAULT NULL,  
  26.  ->   `sex` int(1) NOT NULL DEFAULT '0',  
  27.  ->   INDEX(id)  
  28.  -> ) TYPE=MERGE UNION=(user1,user2) INSERT_METHOD=LAST AUTO_INCREMENT=1 ;  
  29. Query OK, 0 rows affected, 1 warning (0.00 sec)  
  30.   
  31. mysql> select id,name,sex from alluser;  
  32. +----+--------+-----+  
  33. | id | name   | sex |  
  34. +----+--------+-----+  
  35. |  1 | 張映 |   0 |  
  36. |  1 | tank   |   1 |  
  37. +----+--------+-----+  
  38. 2 rows in set (0.00 sec)  
  39.   
  40. mysql> INSERT INTO `alluser` (`name`, `sex`) VALUES('tank2', 0);  
  41. Query OK, 1 row affected (0.00 sec)  
  42.   
  43. mysql> select id,name,sex from user2  
  44.  -> ;  
  45. +----+-------+-----+  
  46. | id | name  | sex |  
  47. +----+-------+-----+  
  48. |  1 | tank  |   1 |  
  49. |  2 | tank2 |   0 |  
  50. +----+-------+-----+  
  51. 2 rows in set (0.00 sec)  

從上面的操作中,我不知道你有沒有發現點什麼?假如我有一張用戶表user,有50W條數據,現在要拆成二張表user1和user2,每張表25W條數據,

INSERT INTO user1(user1.id,user1.name,user1.sex)SELECT (user.id,user.name,user.sex)FROM user where user.id <= 250000

INSERT INTO user2(user2.id,user2.name,user2.sex)SELECT (user.id,user.name,user.sex)FROM user where user.id > 250000

這樣我就成功的將一張user表,分成了二個表,這個時候有一個問題,代碼中的sql語句怎麼辦,以前是一張表,現在變成二張表了,代碼改動很大,這樣給程序員帶來了很大的工作量,有沒有好的辦法解決這一點呢?辦法是把以前的user表備份一下,然後刪除掉,上面的操作中我建立了一個alluser表,只把這個alluser表的表名改成user就行了。但是,不是所有的mysql操作都能用的

a,如果你使用 alter table 來把 merge 表變爲其它表類型,到底層表的映射就被丟失了。取而代之的,來自底層 myisam 表的行被複制到已更換的表中,該表隨後被指定新類型。

b,網上看到一些說replace不起作用,我試了一下可以起作用的。暈一個先

  1. mysql> UPDATE alluser SET sex=REPLACE(sex, 0, 1) where id=2;  
  2. Query OK, 1 row affected (0.00 sec)  
  3. Rows matched: 1  Changed: 1  Warnings: 0  
  4.   
  5. mysql> select * from alluser;  
  6. +----+--------+-----+  
  7. | id | name   | sex |  
  8. +----+--------+-----+  
  9. |  1 | 張映 |   0 |  
  10. |  1 | tank   |   1 |  
  11. |  2 | tank2  |   1 |  
  12. +----+--------+-----+  
  13. 3 rows in set (0.00 sec)  

c,一個 merge 表不能在整個表上維持 unique 約束。當你執行一個 insert,數據進入第一個或者最後一個 myisam 表(取決於 insert_method 選項的值)。mysql 確保唯一鍵值在那個 myisam 表裏保持唯一,但不是跨集合裏所有的表。

d,當你創建一個 merge 表之時,沒有檢查去確保底層表的存在以及有相同的機構。當 merge 表被使用之時,mysql 檢查每個被映射的表的記錄長度是否相等,但這並不十分可靠。如果你從不相似的 myisam 表創建一個 merge 表,你非常有可能撞見奇怪的問題。

好睏睡覺了,c和d在網上看到的,沒有測試,大家試一下吧。

優點:擴展性好,並且程序代碼改動的不是很大

缺點:這種方法的效果比第二種要差一點

三,總結一下

上面提到的三種方法,我實際做過二種,第一種和第二種。第三種沒有做過,所以說的細一點。哈哈。做什麼事都有一個度,超過個度就過變得很差,不能一味的做數據庫服務器集羣,硬件是要花錢買的,也不要一味的分表,分出來1000表,mysql的存儲歸根到底還以文件的形勢存在硬盤上面,一張表對應三個文件,1000個分表就是對應3000個文件,這樣檢索起來也會變的很慢。我的建議是

方法1和方法2結合的方式來進行分表

方法1和方法3結合的方式來進行分表

我的二個建議適合不同的情況,根據個人情況而定,我覺得會有很多人選擇方法1和方法3結合的方式、

46. Mysql分表和分區的區別、分庫分表介紹與區別

分表與分區:

一,什麼是mysql分表,分區 

什麼是分表,從表面意思上看呢,就是把一張表分成N多個小表。

什麼是分區,分區呢就是把一張表的數據分成N多個區塊,這些區塊可以在同一個磁盤上,也可以在不同的磁盤上,具體請參考mysql分區功能詳細介紹,以及實例 

二, mysql分表和分區有什麼區別呢 

1.實現方式上 

mysql的分表是真正的分表,一張表分成很多表後,每一個小表都是完正的一張表,都對應三個文件,一個.MYD數據文件,.MYI索引文件,.frm表結構文件。 

簡 單說明一下,上面的分表呢是利用了merge存儲引擎(分表的一種),alluser是總表,下面有二個分表,user1,user2。他們二個都是獨立 的表,取數據的時候,我們可以通過總表來取。這裏總表是沒有.MYD,.MYI這二個文件的,也就是說,總表他不是一張表,沒有數據,數據都放在分表裏面。我們來看看.MRG到底是什麼東西 

alluser.MRG裏面就存了一些分表的關係,以及插入數據的方式。可以把總表理解成一個外殼,或者是聯接池。 

分區不一樣,一張大表進行分區後,他還是一張表,不會變成二張表,但是他存放數據的區塊變多了。 

2.數據處理上 

a),分表後,數據都是存放在分表裏,總表只是一個外殼,存取數據發生在一個一個的分表裏面。看下面的例子: 
select * from alluser where id='12'表面上看,是對錶alluser進行操作的,其實不是的。是對alluser裏面的分表進行了操作。 
b),分區呢,不存在分表的概念,分區只不過把存放數據的文件分成了許多小塊,分區後的表呢,還是一張表。數據處理還是由自己來完成。 

3.提高性能上

a), 分表後,單表的併發能力提高了,磁盤I/O性能也提高了。併發能力爲什麼提高了呢,因爲查尋一次所花的時間變短了,如果出現高併發的話,總表可以根據不同 的查詢,將併發壓力分到不同的小表裏面。磁盤I/O性能怎麼搞高了呢,本來一個非常大的.MYD文件現在也分攤到各個小表的.MYD中去了。 
b),mysql提出了分區的概念,我覺得就想突破磁盤I/O瓶頸,想提高磁盤的讀寫能力,來增加mysql性能。 
在這一點上,分區和分表的測重點不同,分表重點是存取數據時,如何提高mysql併發能力上;而分區呢,如何突破磁盤的讀寫能力,從而達到提高mysql性能的目的。

4.實現的難易度上 

a),分表的方法有很多,用merge來分表,是最簡單的一種方式。這種方式根分區難易度差不多,並且對程序代碼來說可以做到透明的。如果是用其他分表方式就比分區麻煩了。

b),分區實現是比較簡單的,建立分區表,根建平常的表沒什麼區別,並且對開代碼端來說是透明的。

三,mysql分表和分區有什麼聯繫呢

1,都能提高mysql的性高,在高併發狀態下都有一個良好的表面。

2,分表和分區不矛盾,可以相互配合的,對於那些大訪問量,並且表數據比較多的表,我們可以採取分表和分區結合的方式(如果merge這種分表方式,不能和分區配合的話,可以用其他的分表試),訪問量不大,但是表數據很多的表,我們可以採取分區的方式等。

分庫與分表:

1 基本思想之什麼是分庫分表?

從字面上簡單理解,就是把原本存儲於一個庫的數據分塊存儲到多個庫上,把原本存儲於一個表的數據分塊存儲到多個表上。

2 基本思想之爲什麼要分庫分表?

數據庫中的數據量不一定是可控的,在未進行分庫分表的情況下,隨着時間和業務的發展,庫中的表會越來越多,表中的數據量也會越來越大,相應地,數據操作,增刪改查的開銷也會越來越大;另外,一臺服務器的資源(CPU、磁盤、內存、IO等)是有限的,最終數據庫所能承載的數據量、數據處理能力都將遭遇瓶頸,。

3 分庫分表的實施策略。

如果你的單機性能很低了,那可以嘗試分庫。分庫,業務透明,在物理實現上分成多個服務器,不同的分庫在不同服務器上。分區可以把表分到不同的硬盤上,但不能分配到不同服務器上。一臺機器的性能是有限制的,用分庫可以解決單臺服務器性能不夠,或者成本過高問題。

當分區之後,表還是很大,處理不過來,這時候可以用分庫。

orderid,userid,ordertime,.....

userid%4=0,用分庫1

userid%4=1,用分庫2

userid%4=2, 用分庫3

userid%4=3,用分庫4

上面這個就是一個簡單的分庫路由,根據userid選擇分庫,即不同的服務器

3.1 何謂垂直切分

垂直切分,即將表按照功能模塊、關係密切程度劃分出來,部署到不同的庫上。例如,我們會建立定義數據庫workDB、商品數據庫payDB、用戶數據庫userDB、日誌數據庫logDB等,分別用於存儲項目數據定義表、商品定義表、用戶數據表、日誌數據表等。

如userid,name,addr一個表,爲了防止表過大,分成2個表。

userid,name

userid,addr

3.2 何謂水平切分

水平切分,當一個表中的數據量過大時,我們可以把該表的數據按照某種規則,例如userID散列、按性別、按省,進行劃分,然後存儲到多個結構相同的表,和不同的庫上。例如,我們的userDB中的用戶數據表中,每一個表的數據量都很大,就可以把userDB切分爲結構相同的多個userDB:part0DB、part1DB等,再將userDB上的用戶數據表userTable,切分爲很多userTable:userTable0、userTable1等,然後將這些表按照一定的規則存儲到多個userDB上。

3.3垂直切分與水平切分的選擇

應該使用哪一種方式來實施數據庫分庫分表,這要看數據庫中數據量的瓶頸所在,並綜合項目的業務類型進行考慮。

如果數據庫是因爲表太多而造成海量數據,並且項目的各項業務邏輯劃分清晰、低耦合,那麼規則簡單明瞭、容易實施的垂直切分必是首選

而如果數據庫中的表並不多,但單表的數據量很大、或數據熱度很高,這種情況之下就應該選擇水平切分,水平切分比垂直切分要複雜一些,它將原本邏輯上屬於一體的數據進行了物理分割,除了在分割時要對分割的粒度做好評估,考慮數據平均和負載平均,後期也將對項目人員及應用程序產生額外的數據管理負擔。

在現實項目中,往往是這兩種情況兼而有之,這就需要做出權衡,甚至既需要垂直切分,又需要水平切分。我們的遊戲項目便綜合使用了垂直與水平切分,我們首先對數據庫進行垂直切分,然後,再針對一部分表,通常是用戶數據表,進行水平切分。

4 分庫分表存在的問題。

4.1 事務問題。

在執行分庫分表之後,由於數據存儲到了不同的庫上,數據庫事務管理出現了困難。如果依賴數據庫本身的分佈式事務管理功能去執行事務,將付出高昂的性能代價;如果由應用程序去協助控制,形成程序邏輯上的事務,又會造成編程方面的負擔。

4.2 跨庫跨表的join問題。

在執行了分庫分表之後,難以避免會將原本邏輯關聯性很強的數據劃分到不同的表、不同的庫上,這時,表的關聯操作將受到限制,我們無法join位於不同分庫的表,也無法join分表粒度不同的表,結果原本一次查詢能夠完成的業務,可能需要多次查詢才能完成。

4.3 額外的數據管理負擔和數據運算壓力。

額外的數據管理負擔,最顯而易見的就是數據的定位問題和數據的增刪改查的重複執行問題,這些都可以通過應用程序解決,但必然引起額外的邏輯運算,例如,對於一個記錄用戶成績的用戶數據表userTable,業務要求查出成績最好的100位,在進行分表之前,只需一個order by語句就可以搞定,但是在進行分表之後,將需要n個order by語句,分別查出每一個分表的前100名用戶數據,然後再對這些數據進行合併計算,才能得出結果。

47. sessioncookietoken究竟是什麼

http是一個無狀態協議

什麼是無狀態呢?就是說這一次請求和上一次請求是沒有任何關係的,互不認識的,沒有關聯的。這種無狀態的的好處是快速。壞處是假如我們想要把www.zhihu.com/login.htmlwww.zhihu.com/index.html關聯起來,必須使用某些手段和工具

cookiesession

由於http的無狀態性,爲了使某個域名下的所有網頁能夠共享某些數據,sessioncookie出現了。客戶端訪問服務器的流程如下

  • 首先,客戶端會發送一個http請求到服務器端。
  • 服務器端接受客戶端請求後,建立一個session,併發送一個http響應到客戶端,這個響應頭,其中就包含Set-Cookie頭部。該頭部包含了sessionId。Set-Cookie格式如下,具體請看Cookie詳解
    Set-Cookie: value[; expires=date][; domain=domain][; path=path][; secure]
  • 在客戶端發起的第二次請求,假如服務器給了set-Cookie,瀏覽器會自動在請求頭中添加cookie
  • 服務器接收請求,分解cookie,驗證信息,覈對成功後返回response給客戶端

注意

  • cookie只是實現session的其中一種方案。雖然是最常用的,但並不是唯一的方法。禁用cookie後還有其他方法存儲,比如放在url中
  • 現在大多都是Session + Cookie,但是隻用session不用cookie,或是隻用cookie,不用session在理論上都可以保持會話狀態。可是實際中因爲多種原因,一般不會單獨使用
  • 用session只需要在客戶端保存一個id,實際上大量數據都是保存在服務端。如果全部用cookie,數據量大的時候客戶端是沒有那麼多空間的。
  • 如果只用cookie不用session,那麼賬戶信息全部保存在客戶端,一旦被劫持,全部信息都會泄露。並且客戶端數據量變大,網絡傳輸的數據量也會變大

小結

簡而言之, session 有如用戶信息檔案表, 裏面包含了用戶的認證信息和登錄狀態等信息. cookie 就是用戶通行證

token

token 也稱作令牌,由uid+time+sign[+固定參數]
token
的認證方式類似於臨時的證書籤名, 並且是一種服務端無狀態的認證方式, 非常適合於 REST API 的場景. 所謂無狀態就是服務端並不會保存身份認證相關的數據。

組成

  • uid: 用戶唯一身份標識
  • time: 當前時間的時間戳
  • sign: 簽名, 使用 hash/encrypt 壓縮成定長的十六進制字符串,以防止第三方惡意拼接
  • 固定參數(可選): 將一些常用的固定參數加入到 token 中是爲了避免重複查庫

存放

token在客戶端一般存放於localStoragecookie,或sessionStorage中。在服務器一般存於數據庫中

token認證流程

token 的認證流程與cookie很相似

  • 用戶登錄,成功後服務器返回Token給客戶端。
  • 客戶端收到數據後保存在客戶端
  • 客戶端再次訪問服務器,將token放入headers中
  • 服務器端採用filter過濾器校驗。校驗成功則返回請求數據,校驗失敗則返回錯誤碼

token可以抵抗csrfcookie+session不行

假如用戶正在登陸銀行網頁,同時登陸了攻擊者的網頁,並且銀行網頁未對csrf攻擊進行防護。攻擊者就可以在網頁放一個表單,該表單提交srchttp://www.bank.com/api/transferbodycount=1000&to=Tom。倘若是session+cookie,用戶打開網頁的時候就已經轉給Tom1000元了.因爲form 發起的 POST 請求並不受到瀏覽器同源策略的限制,因此可以任意地使用其他域的 Cookie 向其他域發送 POST 請求,形成 CSRF 攻擊。在post請求的瞬間,cookie會被瀏覽器自動添加到請求頭中。但token不同,token是開發者爲了防範csrf而特別設計的令牌,瀏覽器不會自動添加到headers裏,攻擊者也無法訪問用戶的token,所以提交的表單無法通過服務器過濾,也就無法形成攻擊。

分佈式情況下的sessiontoken

我們已經知道session時有狀態的,一般存於服務器內存或硬盤中,當服務器採用分佈式或集羣時,session就會面對負載均衡問題。

  • 負載均衡多服務器的情況,不好確認當前用戶是否登錄,因爲多服務器不共享session。這個問題也可以將session存在一個服務器中來解決,但是就不能完全達到負載均衡的效果。當今的幾種解決session負載均衡的方法。

token是無狀態的,token字符串裏就保存了所有的用戶信息

  • 客戶端登陸傳遞信息給服務端,服務端收到後把用戶信息加密(token)傳給客戶端,客戶端將token存放於localStroage等容器中。客戶端每次訪問都傳遞token,服務端解密token,就知道這個用戶是誰了。通過cpu加解密,服務端就不需要存儲session佔用存儲空間,就很好的解決負載均衡多服務器的問題了。這個方法叫做JWT(Json Web Token)

總結

  • session存儲於服務器,可以理解爲一個狀態列表,擁有一個唯一識別符號sessionId,通常存放於cookie中。服務器收到cookie後解析出sessionId,再去session列表中查找,才能找到相應session。依賴cookie
  • cookie類似一個令牌,裝有sessionId,存儲在客戶端,瀏覽器通常會自動添加。
  • token也類似一個令牌,無狀態,用戶信息都被加密到token中,服務器收到token後解密就可知道是哪個用戶。需要開發者手動添加。
  • jwt只是一個跨域認證的方案

48. token 防止csrf

當前防禦 CSRF 的幾種策略

驗證 HTTP Referer 字段

根據 HTTP 協議,在 HTTP 頭中有一個字段叫 Referer,它記錄了該 HTTP 請求的來源地址。在通常情況下,訪問一個安全受限頁面的請求來自於同一個網站,比如需要訪問 http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory,用戶必須先登陸 bank.example,然後通過點擊頁面上的按鈕來觸發轉賬事件。這時,該轉帳請求的 Referer 值就會是轉賬按鈕所在的頁面的 URL,通常是以 bank.example 域名開頭的地址。而如果黑客要對銀行網站實施 CSRF 攻擊,他只能在他自己的網站構造請求,當用戶通過黑客的網站發送請求到銀行時,該請求的 Referer 是指向黑客自己的網站。因此,要防禦 CSRF 攻擊,銀行網站只需要對於每一個轉賬請求驗證其 Referer 值,如果是以 bank.example 開頭的域名,則說明該請求是來自銀行網站自己的請求,是合法的。如果 Referer 是其他網站的話,則有可能是黑客的 CSRF 攻擊,拒絕該請求。
這種方法的顯而易見的好處就是簡單易行,網站的普通開發人員不需要操心 CSRF 的漏洞,只需要在最後給所有安全敏感的請求統一增加一個攔截器來檢查 Referer 的值就可以。特別是對於當前現有的系統,不需要改變當前系統的任何已有代碼和邏輯,沒有風險,非常便捷。
然而,這種方法並非萬無一失。Referer 的值是由瀏覽器提供的,雖然 HTTP 協議上有明確的要求,但是每個瀏覽器對於 Referer 的具體實現可能有差別,並不能保證瀏覽器自身沒有安全漏洞。使用驗證 Referer 值的方法,就是把安全性都依賴於第三方(即瀏覽器)來保障,從理論上來講,這樣並不安全。事實上,對於某些瀏覽器,比如 IE6 或 FF2,目前已經有一些方法可以篡改 Referer 值。如果 bank.example 網站支持 IE6 瀏覽器,黑客完全可以把用戶瀏覽器的 Referer 值設爲以 bank.example 域名開頭的地址,這樣就可以通過驗證,從而進行 CSRF 攻擊。
即便是使用最新的瀏覽器,黑客無法篡改 Referer 值,這種方法仍然有問題。因爲 Referer 值會記錄下用戶的訪問來源,有些用戶認爲這樣會侵犯到他們自己的隱私權,特別是有些組織擔心 Referer 值會把組織內網中的某些信息泄露到外網中。因此,用戶自己可以設置瀏覽器使其在發送請求時不再提供 Referer。當他們正常訪問銀行網站時,網站會因爲請求沒有 Referer 值而認爲是 CSRF 攻擊,拒絕合法用戶的訪問。

在請求地址中添加 token 並驗證

CSRF 攻擊之所以能夠成功,是因爲黑客可以完全僞造用戶的請求,該請求中所有的用戶驗證信息都是存在於 cookie 中,因此黑客可以在不知道這些驗證信息的情況下直接利用用戶自己的 cookie 來通過安全驗證。要抵禦 CSRF,關鍵在於在請求中放入黑客所不能僞造的信息,並且該信息不存在於 cookie 之中。可以在 HTTP 請求中以參數的形式加入一個隨機產生的 token,並在服務器端建立一個攔截器來驗證這個 token,如果請求中沒有 token 或者 token 內容不正確,則認爲可能是 CSRF 攻擊而拒絕該請求。
這種方法要比檢查 Referer 要安全一些,token 可以在用戶登陸後產生並放於 session 之中,然後在每次請求時把 token 從 session 中拿出,與請求中的 token 進行比對,但這種方法的難點在於如何把 token 以參數的形式加入請求。對於 GET 請求,token 將附在請求地址之後,這樣 URL 就變成 http://url?csrftoken=tokenvalue。 而對於 POST 請求來說,要在 form 的最後加上 <input type=”hidden” name=”csrftoken” value=”tokenvalue”/>,這樣就把 token 以參數的形式加入請求了。但是,在一個網站中,可以接受請求的地方非常多,要對於每一個請求都加上 token 是很麻煩的,並且很容易漏掉,通常使用的方法就是在每次頁面加載時,使用 javascript 遍歷整個 dom 樹,對於 dom 中所有的 a 和 form 標籤後加入 token。這樣可以解決大部分的請求,但是對於在頁面加載之後動態生成的 html 代碼,這種方法就沒有作用,還需要程序員在編碼時手動添加 token。
該方法還有一個缺點是難以保證 token 本身的安全。特別是在一些論壇之類支持用戶自己發表內容的網站,黑客可以在上面發佈自己個人網站的地址。由於系統也會在這個地址後面加上 token,黑客可以在自己的網站上得到這個 token,並馬上就可以發動 CSRF 攻擊。爲了避免這一點,系統可以在添加 token 的時候增加一個判斷,如果這個鏈接是鏈到自己本站的,就在後面添加 token,如果是通向外網則不加。不過,即使這個 csrftoken 不以參數的形式附加在請求之中,黑客的網站也同樣可以通過 Referer 來得到這個 token 值以發動 CSRF 攻擊。這也是一些用戶喜歡手動關閉瀏覽器 Referer 功能的原因。

在 HTTP 頭中自定義屬性並驗證

這種方法也是使用 token 並進行驗證,和上一種方法不同的是,這裏並不是把 token 以參數的形式置於 HTTP 請求之中,而是把它放到 HTTP 頭中自定義的屬性裏。通過 XMLHttpRequest 這個類,可以一次性給所有該類請求加上 csrftoken 這個 HTTP 頭屬性,並把 token 值放入其中。這樣解決了上種方法在請求中加入 token 的不便,同時,通過 XMLHttpRequest 請求的地址不會被記錄到瀏覽器的地址欄,也不用擔心 token 會透過 Referer 泄露到其他網站中去。
然而這種方法的侷限性非常大。XMLHttpRequest 請求通常用於 Ajax 方法中對於頁面局部的異步刷新,並非所有的請求都適合用這個類來發起,而且通過該類請求得到的頁面不能被瀏覽器所記錄下,從而進行前進,後退,刷新,收藏等操作,給用戶帶來不便。另外,對於沒有進行 CSRF 防護的遺留系統來說,要採用這種方法來進行防護,要把所有請求都改爲 XMLHttpRequest 請求,這樣幾乎是要重寫整個網站,這代價無疑是不能接受的。

49. 內存泄漏和內存溢出的區別和聯繫

1、內存泄漏memory leak :

內存泄漏是指程序在申請內存後,無法釋放已申請的內存空間,一次內存泄漏似乎不會有大的影響,但內存泄漏堆積後的後果就是內存溢出。

2、內存溢出 out of memory :

內存溢出指程序申請內存時,沒有足夠的內存供申請者使用,或者說,給了你一塊存儲int類型數據的存儲空間,但是你卻存儲long類型的數據,那麼結果就是內存不夠用,此時就會報錯OOM,即所謂的內存溢出。

3、二者的關係

內存泄漏的堆積最終會導致內存溢出

內存溢出就是你要的內存空間超過了系統實際分配給你的空間,此時系統相當於沒法滿足你的需求,就會報內存溢出的錯誤。

內存泄漏是指你向系統申請分配內存進行使用(new),可是使用完了以後卻不歸還(delete),結果你申請到的那塊內存你自己也不能再訪問(也許你把它的地址給弄丟了),而系統也不能再次將它分配給需要的程序。就相當於你租了個帶鑰匙的櫃子,你存完東西之後把櫃子鎖上之後,把鑰匙丟了或者沒有將鑰匙還回去,那麼結果就是這個櫃子將無法供給任何人使用,也無法被垃圾回收器回收,因爲找不到他的任何信息。

內存溢出:一個盤子用盡各種方法只能裝4個果子,你裝了5個,結果掉倒地上不能吃了。這就是溢出。比方說棧,棧滿時再做進棧必定產生空間溢出,叫上溢,棧空時再做退棧也產生空間溢出,稱爲下溢。就是分配的內存不足以放下數據項序列,稱爲內存溢出。說白了就是我承受不了那麼多,那我就報錯,

4、內存泄漏的分類(按發生方式來分類)

常發性內存泄漏。發生內存泄漏的代碼會被多次執行到,每次被執行的時候都會導致一塊內存泄漏。

偶發性內存泄漏。發生內存泄漏的代碼只有在某些特定環境或操作過程下才會發生。常發性和偶發性是相對的。對於特定的環境,偶發性的也許就變成了常發性的。所以測試環境和測試方法對檢測內存泄漏至關重要。

一次性內存泄漏。發生內存泄漏的代碼只會被執行一次,或者由於算法上的缺陷,導致總會有一塊僅且一塊內存發生泄漏。比如,在類的構造函數中分配內存,在析構函數中卻沒有釋放該內存,所以內存泄漏只會發生一次。

隱式內存泄漏。程序在運行過程中不停的分配內存,但是直到結束的時候才釋放內存。嚴格的說這裏並沒有發生內存泄漏,因爲最終程序釋放了所有申請的內存。但是對於一個服務器程序,需要運行幾天,幾周甚至幾個月,不及時釋放內存也可能導致最終耗盡系統的所有內存。所以,我們稱這類內存泄漏爲隱式內存泄漏。

5、內存溢出的原因及解決方法:

內存溢出原因:

1.內存中加載的數據量過於龐大,如一次從數據庫取出過多數據;

2.集合類中有對對象的引用,使用完後未清空,使得JVM不能回收;

3.代碼中存在死循環或循環產生過多重複的對象實體;

4.使用的第三方軟件中的BUG;

5.啓動參數內存值設定的過小

內存溢出的解決方案:

第一步,修改JVM啓動參數,直接增加內存。(-Xms,-Xmx參數一定不要忘記加。)

第二步,檢查錯誤日誌,查看“OutOfMemory”錯誤前是否有其 它異常或錯誤。

第三步,對代碼進行走查和分析,找出可能發生內存溢出的位置。

重點排查以下幾點:

1.檢查對數據庫查詢中,是否有一次獲得全部數據的查詢。一般來說,如果一次取十萬條記錄到內存,就可能引起內存溢出。這個問題比較隱蔽,在上線前,數據庫中數據較少,不容易出問題,上線後,數據庫中數據多了,一次查詢就有可能引起內存溢出。因此對於數據庫查詢儘量採用分頁的方式查詢。

2.檢查代碼中是否有死循環或遞歸調用。

3.檢查是否有大循環重複產生新對象實體。

4.檢查對數據庫查詢中,是否有一次獲得全部數據的查詢。一般來說,如果一次取十萬條記錄到內存,就可能引起內存溢出。這個問題比較隱蔽,在上線前,數據庫中數據較少,不容易出問題,上線後,數據庫中數據多了,一次查詢就有可能引起內存溢出。因此對於數據庫查詢儘量採用分頁的方式查詢。

5.檢查List、MAP等集合對象是否有使用完後,未清除的問題。List、MAP等集合對象會始終存有對對象的引用,使得這些對象不能被GC回收。

第四步,使用內存查看工具動態查看內存使用情況

6.Java內存泄露引起原因 

內存泄露是指無用對象(不再使用的對象)持續佔有內存或無用對象的內存得不到及時釋放,從而造成的內存空間的浪費稱爲內存泄露。內存泄露有時不嚴重且不易察覺,這樣開發者就不知道存在內存泄露,但有時也會很嚴重,會提示你Out of memory。那麼,Java內存泄露根本原因是什麼呢?長生命週期的對象持有短生命週期對象的引用就很可能發生內存泄露,儘管短生命週期對象已經不再需要,但是因爲長生命週期對象持有它的引用而導致不能被回收,這就是java中內存泄露的發生場景。具體主要有如下幾大類

1、靜態集合類引起內存泄露(static 對象是不會被GC回收的):

集合類,如HashMap、Set、ArrayList、Vector等它們是內存泄漏的常見位置。如將他們聲明爲static,那麼它將擁有和主程序一樣的生命週期,如果他們裏面放置了大量對象(引用的關係),當這些對象不在使用時,因爲HashMap,集合等是全局的static類型,那麼垃圾回收將無法處理這些不在使用的對象,從而造成了內存泄漏。

 

public class MemoryLeak{

         static List<Student> list = new ArrayList<Student>();

        

         public static void main(String[] args) throws InterruptedException{

                          for(int i =0;i<1000000;i++){

                          Student stu = new Student();

                          list.add(stu);    //內存泄漏,無法回收

                  }

                  //便於觀察虛擬機運行情況

                  while(true){

                          Thread.sleep(20000);

                  }

         }

}

class Student{

}

2、當集合裏面的對象屬性被修改後,再調用remove()方法時不起作用。

public class MemoryLeak2 {

         public static void main(String[] args) {

 

                  Set<Person> set = new HashSet<Person>();

                  Person p1 = new Person("唐僧","pwd1",25);

                  Person p2 = new Person("孫悟空","pwd2",26);

                  Person p3 = new Person("豬八戒","pwd3",27);

                  set.add(p1);

                  set.add(p2);

                  set.add(p3);//現在set裏面有3個對象!

                  p3.setAge(2); //修改p3的年齡,此時p3的hashcode改變

                  set.remove(p3); //此時remove不掉,造成內存泄漏

                  set.add(p3); //重新添加,成功,現在set裏面有4個對象

         }

 

}

class Person{

    public Person(){}

    public Person(String name,String pwd,Integer age){

        ...

}

3、各種連接

比如數據庫連接(dataSourse.getConnection()),網絡連接(socket)和io連接,除非其顯式的調用了其close()方法將其連接關閉,否則是不會自動被GC 回收的。對於Resultset 和Statement 對象可以不進行顯式回收,但Connection 一定要顯式回收,因爲Connection 在任何時候都無法自動回收,而Connection一旦回收,Resultset 和Statement 對象就會立即爲NULL。但是如果使用連接池,情況就不一樣了,除了要顯式地關閉連接,還必須顯式地關閉Resultset Statement 對象(關閉其中一個,另外一個也會關閉),否則就會造成大量的Statement 對象無法釋放,從而引起內存泄漏。這種情況下一般都會在try裏面去的連接,在finally裏面釋放連接。

4、單例模式

如果單例對象持有外部對象的引用,那麼這個外部對象將不能被jvm正常回收,導致內存泄露不正確使用單例模式是引起內存泄露的一個常見問題,單例對象在被初始化後將在JVM的整個生命週期中存在(以靜態變量的方式),如果單例對象持有外部對象的引用,那麼這個外部對象將不能被jvm正常回收,導致內存泄露,考慮下面的例子:

class A{

    public A(){

        B.getInstance().setA(this);

    }

    ....

}

 //B類採用單例模式

class B{

    private A a;

    private static B instance=new B();

    public B(){}

    public static B getInstance(){

        return instance;

    }

    public void setA(A a){

        this.a=a;

    }

    //getter...

}

5、讀取流沒有關閉

開發中經常忘記關閉流,這樣會導致內存泄漏。因爲每個流在操作系統層面都對應了打開的文件句柄,流沒有關閉,會導致操作系統的文件句柄一直處於打開狀態,而jvm會消耗內存來跟蹤操作系統打開的文件句柄。

6、監聽器

在java 編程中,我們都需要和監聽器打交道,通常一個應用當中會用到很多監聽器,我們會調用一個控件的諸如addXXXListener()等方法來增加監聽器,但往往在釋放對象的時候卻沒有記住去刪除這些監聽器,從而增加了內存泄漏的機會。

7、String的intern方法

在大字符串上調用String.intern() 方法,intern()會將String放在jvm的內存池中(PermGen ),而jvm的內存池是不會被gc的。因此如果大字符串調用intern()方法後,會產生大量的無法gc的內存,導致內存泄漏。如果必須要使用大字符串的intern方法,應該通過-XX:MaxPermSize參數調整PermGen內存的大小(關於設置虛擬機內存大小後續會繼續發佈相關博客)。

8、 將沒有實現hashCode()和equals()方法的對象加入到HashSet中

這是一個簡單卻很常見的場景。正常情況下Set會過濾重複的對象,但是如果沒有hashCode() 和 equals()實現,重複對象會不斷被加入到Set中,並且再也沒有機會去移除。因此給類都加上hashCode() 和 equals()方法的實現是一個好的編程習慣。可以通過Lombok的@EqualsAndHashCode很方便實現這種功能。

7. 查找內存泄漏的方法

3.1 記錄gc日誌

通過在jvm參數中指定-verbose:gc,可以記錄每次gc的詳細情況,用於分析內存的使用。

3.2 進行profiling

通過Visual VM或jdk自帶的Java Mission Control,進行內存分析。

3.3 代碼審查

通過代碼審查和靜態代碼檢查,發現導致內存泄漏問題的錯誤代碼。

50.到底什麼是hash呢?hash碰撞?爲什麼HashMap的初始容量是16?

一 、到底什麼是hash呢?

hash(散列、雜湊)函數,是將任意長度的數據映射到有限長度的域上。直觀解釋起來,就是對一串數據m進行雜糅,輸出另一段固定長度的數據h,作爲這段數據的特徵(指紋)。

也就是說,無論數據塊m有多大,其輸出值h爲固定長度。到底是什麼原理?將m分成固定長度(如128位),依次進行hash運算,然後用不同的方法迭代即可(如前一塊的hash值與後一塊的hash值進行異或)。如果不夠128位怎麼辦?用0補全或者用1補全隨意,算法中約定好就可以了。

由於用途的不同,hash在數據結構中的含義和密碼學中的含義並不相同,所以在這兩種不同的領域裏,算法的設計側重點也不同。

預備小知識:

抗碰撞能力:對於任意兩個不同的數據塊,其hash值相同的可能性極小;對於一個給定的數據塊,找到和它hash值相同的數據塊極爲困難。

抗篡改能力:對於一個數據塊,哪怕只改動其一個比特位,其hash值的改動也會非常大。

在用到hash進行管理的數據結構中,比如hashmap,hash值(key)存在的目的是加速鍵值對的查找,key的作用是爲了將元素適當地放在各個桶裏,對於抗碰撞的要求沒有那麼高。換句話說,hash出來的key,只要保證value大致均勻的放在不同的桶裏就可以了。但整個算法的set性能,直接與hash值產生的速度有關,所以這時候的hash值的產生速度就尤爲重要,以JDK中的String.hashCode()方法爲例:

    public int hashCode() {

        int h = hash;

 //hash default value : 0

        if (h == 0 && value.length > 0) {

 //value : char storage

            char val[] = value;

 

            for (int i = 0; i < value.length; i++) {

                h = 31 * h + val[i];

            }

            hash = h;

        }

        return h;

    }

很簡潔的一個乘加迭代運算,在不少的hash算法中,使用的是異或+加法進行迭代,速度和前者差不多。

密碼學中,hash算法的作用主要是用於消息摘要和簽名,換句話說,它主要用於對整個消息的完整性進行校驗。舉個例子,我們登陸知乎的時候都需要輸入密碼,那麼知乎如果明文保存這個密碼,那麼黑客就很容易竊取大家的密碼來登陸,特別不安全。那麼知乎就想到了一個方法,使用hash算法生成一個密碼的簽名,知乎後臺只保存這個簽名值。由於hash算法是不可逆的,那麼黑客即便得到這個簽名,也絲毫沒有用處;而如果你在網站登陸界面上輸入你的密碼,那麼知乎後臺就會重新計算一下這個hash值,與網站中儲存的原hash值進行比對,如果相同,證明你擁有這個賬戶的密碼,那麼就會允許你登陸。銀行也是如此,銀行是萬萬不敢保存用戶密碼的原文的,只會保存密碼的hash值而而已。

在這些應用場景裏,對於抗碰撞和抗篡改能力要求極高,對速度的要求在其次。一個設計良好的hash算法,其抗碰撞能力是很高的。以MD5爲例,其輸出長度爲128位,設計預期碰撞概率爲,這是一個極小極小的數字——而即便是在MD5被王小云教授破解之後,其碰撞概率上限也高達,也就是說,至少需要找次纔能有1/2的概率來找到一個與目標文件相同的hash值。而對於兩個相似的字符串,MD5加密結果如下:

MD5("version1") = "966634ebf2fc135707d6753692bf4b1e";

MD5("version2") = "2e0e95285f08a07dea17e7ee111b21c8";

可以看到僅僅一個比特位的改變,二者的MD5值就天差地別了。

二,什麼是hash碰撞

如果兩個輸入串的hash函數的值一樣,則稱這兩個串是一個碰撞(Collision)。既然是把任意長度的字符串變成固定長度的字符串,所以必有一個輸出串對應無窮多個輸入串,碰撞是必然存在的。

一個優良的hash函數 f 應當滿足以下三個條件:

(1)對於任意y,尋找x,使得f(x)=y,在計算上是不可行的。

(2)給定x1∈A,找x2∈B,,使得f(x1)=f(x2),在計算上是不可能的,這也就是弱無碰撞性。

(3)尋找x1,x2,使得f(x1)=f(x2),在計算上也是不可行的,這也就是強無碰撞性。

這樣就稱爲安全保密的Hash函數,除了枚舉外不可能有別的更快的方法。如第3條,根據生日定理,要想找到這樣的x1,x2,理論上需要大約2^(n/2)的枚舉次數。

因爲前兩條都能被破壞的hash函數太弱而被拋棄,幾乎所有的hash函數的破解,都是指的破壞上面的第3條性質,即找到一個碰撞。在密碼學上還有一個概念是理論破解,指的是提出一個算法,使得可以用低於理論值得枚舉次數找到碰撞。

三、碰撞處理

   通常有兩類方法處理碰撞:開放尋址(Open Addressing)法和鏈接(Chaining)法。前者是將所有結點均存放在散列表T[0..m-1]中;後者通常是把散列到同一槽中的所有元素放在一個鏈表中,而將此鏈表的頭指針放在散列表T[0..m-1]中。

 (1)開放尋址法

  所有的元素都在散列表中,每一個表項或包含動態集合的一個元素,或包含NIL。這種方法中散列表可能被填滿,以致於不能插入任何新的元素。在開放尋址法中,當要插入一個元素時,可以連續地檢查或探測散列表的各項,直到有一個空槽來放置待插入的關鍵字爲止。有三種技術用於開放尋址法:線性探測、二次探測以及雙重探測。

<1>線性探測

  給定一個普通的散列函數h':U —>{0,1,.....,m-1},線性探測方法採用的散列函數爲:h(k,i) = (h'(k)+i)mod m,i=0,1,....,m-1  

     探測時從i=0開始,首先探查T[h'(k)],然後依次探測T[h'(k)+1],…,直到T[h'(k)+m-1],此後又循環到T[0],T[1],…,直到探測到T[h'(k)-1]爲止。探測過程終止於三種情況: 

  (1)若當前探測的單元爲空,則表示查找失敗(若是插入則將key寫入其中); 

  (2)若當前探測的單元中含有key,則查找成功,但對於插入意味着失敗; 

  (3)若探測到T[h'(k)-1]時仍未發現空單元也未找到key,則無論是查找還是插入均意味着失敗(此時表滿)。

  線性探測方法較容易實現,但是存在一次羣集問題,即連續被佔用的槽的序列變的越來越長。採用例子進行說明線性探測過程,已知一組關鍵字爲(26,36,41,38,44,15,68,12,6,51),用除餘法構造散列函數,初始情況如下圖所示:

散列過程如下圖所示:

<2>二次探測

   二次探測法的探查序列是:h(k,i) =(h'(k)+i*i)%m ,0≤i≤m-1 。初次的探測位置爲T[h'(k)],後序的探測位置在次基礎上加一個偏移量,該偏移量以二次的方式依賴於i。該方法的缺陷是不易探查到整個散列空間。

<3>雙重散列

  該方法是開放尋址的最好方法之一,因爲其產生的排列具有隨機選擇的排列的許多特性。採用的散列函數爲:h(k,i)=(h1(k)+ih2(k)) mod m。其中h1和h2爲輔助散列函數。初始探測位置爲T[h1(k)],後續的探測位置在此基礎上加上偏移量h2(k)模m。

 (2)鏈接法

  將所有關鍵字爲同義詞的結點鏈接在同一個鏈表中。若選定的散列表長度爲m,則可將散列表定義爲一個由m個頭指針組成的指針數組T[0..m-1]。凡是散列地址爲i的結點,均插入到以T[i]爲頭指針的單鏈表中。T中各分量的初值均應爲空指針。在拉鍊法中,裝填因子α可以大於1,但一般均取α≤1。

舉例說明鏈接法的執行過程,設有一組關鍵字爲(26,36,41,38,44,15,68,12,6,51),用除餘法構造散列函數,初始情況如下圖所示:

最終結果如下圖所示:

四,爲什麼HashMap初始容量是2<<3?

如果兩個元素不相同,但是hash函數的值相同,這兩個元素就是一個碰撞

因爲把任意長度的字符串變成固定長度的字符串,所以存在一個hash對應多個字符串的情況,所以碰撞必然存在

爲了減少hash值的碰撞,需要實現一個儘量均勻分佈的hash函數,在HashMap中通過利用key的hashcode值,來進行位運算

公式:index = e.hash & (newCap - 1)

 

舉個例子:

1.計算"book"的hashcode

    十進制 : 3029737

    二進制 : 101110001110101110 1001

 

2.HashMap長度是默認的16,length - 1的結果

    十進制 : 15

    二進制 : 1111

 

3.把以上兩個結果做與運算

    101110001110101110 1001 & 1111 = 1001

    1001的十進制 : 9,所以 index=9

 

hash算法最終得到的index結果,取決於hashcode值的最後幾位

 

爲了推斷HashMap的默認長度爲什麼是16

現在,我們假設HashMap的長度是10,重複剛纔的運算步驟:

hashcode : 101110001110101110 1001

length - 1 :                                     1001

index :                                            1001

 

再換一個hashcode 101110001110101110 1111 試試:

hashcode : 101110001110101110 1111

length - 1 :                                     1001

index :                                            1001

 

從結果可以看出,雖然hashcode變化了,但是運算的結果都是1001,也就是說,當HashMap長度爲10的時候,有些index結果的出現機率

會更大而有些index結果永遠不會出現(比如0111),這樣就不符合hash均勻分佈的原則

 

反觀長度16或者其他2的冪,length - 1的值是所有二進制位全爲1,這種情況下,index的結果等同於hashcode後幾位的值

只要輸入的hashcode本身分佈均勻,hash算法的結果就是均勻的

所以,HashMap的默認長度爲16,是爲了降低hash碰撞的機率

51. 線程及與進程的對比

一、爲什麼要引入線程

  進程是爲了提高CPU的執行效率,減少因程序等待帶來的CPU空轉以及其他計算機軟硬件資源而提出來的。進程是一個資源擁有者,因而在進程的創建、撤消和切換中,系統必須爲之付出較大的時空開銷。也正因爲如此,在系統中所設置的進程數目不宜過多,進程切換的頻率也不宜太高,但這也就限制了併發程度的進一步提高。如何能使多個程序更好地併發執行,同時又儘量減少系統的開銷,已成爲近年來設計操作系統時所追求的重要目標。於是,有不少操作系統的學者們想到,可否將進 程的上述屬性分開,由操作系統分開來進行處理。即對作爲調度和分派的基本單位,不同時作爲獨立分配資源的單位,以使之輕裝運行;而對擁有資源的基本單位, 又不頻繁地對之進行切換。正是在這種思想的指導下,產生了線程概念。即,爲了減少進程切換和創建的開銷,提高執行效率和節省資源,人們在開始操作系統中引入“線程”(thread)的概念。

二、線程

1、線程的基本概念

線程是進程的一部分,有時候也被稱爲輕量級進程(light weight process)。線程是進程中執行運算的最小單位,亦即執行處理機調度的基本單位。如果把進程理解爲在邏輯上操作系統所完成的任務,那麼線程表示完成該任務的許多可能的子任務之一。

2、引入線程的好處

(1)易於調度。

(2)提高併發性。通過線程可方便有效地實現併發性。進程可創建多個線程來執行同一程序的不同部分。

(3)開銷少。創建線程比創建進程要快,所需開銷很少。。

(4)利於充分發揮多處理器的功能。通過創建多線程進程(即一個進程可具有兩個或更多個線程),每個線程在一個處理器上運行,從而實現應用程序的併發性,使每個處理器都得到充分運行。

3、線程的適用範圍

線程可以有效地提高系統的執行效率,但並不是在所有計算機系統中都是適用的,如某些很少做進程調度和切換的實時系統。使用線程的最大好處是有多個任務需要處理機處理時,可以減少處理機的切換時間;而且,線程的創建和結束所需要的系統開銷也比進程的創建和結束要小得多。最適用使用線程的系統是多處理機系統、網絡系統或分佈式系統。

4、線程分類與執行

線程的兩個基本類型是用戶級線程和內核級線程(系統級線程)。

1、用戶級線程

 用戶級線程的管理過程全部由用戶程序完成,爲了對用戶級線程進行管理,操作系統提供一個在用戶空間執行的線程庫。該線程庫提供創建、調度和撤銷線程功能。同時,該線程庫也提供線程見的通信、線程的執行以及存儲線程上下文的功能。用戶級線程只使用用戶堆棧和分配給所屬進程的用戶寄存器。

 (1)用戶級線程的調度算法和調度過程全部由用戶自行選擇和確定,與操作系統內核無關。

 (2)用戶級線程的調度算法只進行線程上下文切換而不進行處理機切換。

 (3)因,用戶級線程的上下文切換和內核無關,所以可能出現,儘管相關進程的狀態是阻塞的或等待的,但所屬線程的狀態卻是執行的

2、內核級線程

由操作形同內核進行管理。操作系統內核給應用程序提供相應地系統調用和應用程序接口,以使用戶可以創建、執行和撤銷線程。操作系統內核,負責進程的調度,也負責進程內不同線程的調度,故內核級線程不會出現進程處於阻塞或等待狀態,而線程處於執行狀態的情況。

系統開銷:用戶級線程<內核級進程<進程

3、執行

線程有3個基本狀態:執行、就緒和阻塞。有五種基本操作:派生、阻塞、激活、調度和結束。

三、進程與線程

1、進程和線程的關係

(1)一個線程只能屬於一個進程,而一個進程可以有多個線程。線程是操作系統可識別的最小執行和調度單位。一個沒有線程的進程可以被看作是單線程。

(2)資源分配給進程,同一進程的所有線程共享該進程的所有資源。

2、線程與進程的區別

(1)線程的改變只代表了CPU執行過程的改變,而進程所擁有的資源都沒有發生改變。或者說,除了CPU之外,計算機內的軟硬件資源的分配與線程無關,線程只能共享它所屬進程的資源。

(2)與進程控制表和 PCB 相似,每個線程也有自己的線程控制表 TCB ,而這個 TCB 中所保存的線程狀態信息則要比 PCB 表少得多,這些信息主要是相關指針用堆棧(系統棧和用戶棧),寄存器中的狀態數據。

(3)進程擁有一個完整的虛擬地址空間,不依賴於線程而獨立存在;反之,線程是進程的一部分,沒有自己的地址空間,與進程內的其他線程一起共享分配給該進程的所有資源。

3、線程與進程對比

我們從調度、併發性、 系統開銷、擁有資源等方面,來比較線程與進程。

1.調度

在傳統的操作系統中,擁有資源的基本單位和獨立調度、分派的基本單位都是進程。而在引入線程的操作系統中,則把線程作爲調度和分派的基本單位。而把進程作爲資源擁有的基本單位,使傳統進程的兩個屬性分開,線程便能輕裝運行,從而可顯著地提高系統的併發程度。在同一進程中,線程的切換不會引起進程的切換,在由一個進程中的線程切換到另一個進程中的線程時,將會引起進程的切換。

2.併發性

在引入線程的操作系統中,不僅進程之間可以併發執行,而且在一個進程中的多個線程之間,亦可併發執行,因而使操作系統具有更好的併發性,從而能更有效地使 用系統資源和提高系統吞吐量。例如,在一個未引入線程的單CPU操作系統中,若僅設置一個文件服務進程,當它由於某種原因而被阻塞時,便沒有其它的文件服 務進程來提供服務。在引入了線程的操作系統中,可以在一個文件服務進程中,設置多個服務線程,當第一個線程等待時,文件服務進程中的第二個線程可以繼續運 行;當第二個線程阻塞時,第三個線程可以繼續執行,從而顯著地提高了文件服務的質量以及系統吞吐量。

3.擁有資源

不論是傳統的操作系統,還是設有線程的操作系統,進程都是擁有資源的一個獨立單位,它可以擁有自己的資源。一般地說,線程自己不擁有系統資源(也有一點必 不可少的資源),但它可以訪問其隸屬進程的資源。亦即,一個進程的代碼段、數據段以及系統資源,如已打開的文件、I/O設備等,可供問一進程的其它所有線 程共享。

4.系統開銷

由於在創建或撤消進程時,系統都要爲之分配或回收資源,如內存空間、I/o設備等。因此,操作系統所付出的開銷將顯著地大於在創建或撤消線程時的開銷。類 似地,在進行進程切換時,涉及到整個當前進程CPU環境的保存以及新被調度運行的進程的CPU環境的設置。而線程切換隻須保存和設置少量寄存器的內容,並 不涉及存儲器管理方面的操作。可見,進程切換的開銷也遠大於線程切換的開銷。此外,由於同一進程中的多個線程具有相同的地址空間,致使它們之間的同步和通信的實現,也變得比較容易。在有的系統中,線程的切換、同步和通信都無須

52. Java基本數據類型及所佔字節大小

一、Java基本數據類型

  基本數據類型有8種:byte、short、int、long、float、double、boolean、char

  分爲4類:整數型、浮點型、布爾型、字符型。

  整數型:byte、short、int、long

  浮點型:float、double

  布爾型:boolean

  字符型:char

 二、各數據類型所佔字節大小

  計算機的基本單位:bit .  一個bit代表一個0或1

  byte:1byte = 8bit     1個字節是8個bit

  short:2byte

  int:4byte

  long:8byte

  float:4byte

  double:8byte

  boolean:1byte

  char:2byte

 

 

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