当年,兔子学姐靠这个面试小抄拿了个22k
本文順序是操作系統(tǒng)(jvm)、網(wǎng)絡(luò)、數(shù)據(jù)庫(mysql/redis),都是當(dāng)時兔子的學(xué)姐準(zhǔn)備面試的時候總結(jié)的,學(xué)生面試基本不會跑出這個范圍,懂行的應(yīng)該能看出來。
學(xué)姐原話:因為我本身的知識是A集合,我覺得一次記不住,需要反復(fù)看的,我就寫了下來B集合。這些并不一定是所有,特別簡單的或者特別生僻的A-B都沒寫。
所以按我的理解,那些,特別特別基礎(chǔ)的,不用學(xué)姐說,自己也應(yīng)該會,然后再看這個文章。
目錄
生產(chǎn)消費(fèi)讀者寫者
進(jìn)程vs線程
線程生命周期
進(jìn)程間通信方式
線程間通信方式
進(jìn)程調(diào)度算法
死鎖
頁面置換算法
磁盤調(diào)度算法
jvmjvmjvmjvm區(qū)域
判斷對象死亡?引用計數(shù) vs 可達(dá)性分析
如何回收對象?垃圾收集算法
垃圾收集器
分配回收策略(老年代新生代)
GC調(diào)優(yōu)
JVM常用參數(shù)
Cookie session
網(wǎng)絡(luò)分層
握手揮手
UDP想可靠
網(wǎng)絡(luò)訪問過程
為什么tcp可靠?
UDP可靠
HTTP and HTTPS
HTTP方法及相互區(qū)別
狀態(tài)碼
http結(jié)構(gòu)
Tcp結(jié)構(gòu)
Socket
redisredisredis數(shù)據(jù)結(jié)構(gòu)
對象
跳表
HyperLogLog
單線程
持久化
Redis和數(shù)據(jù)庫雙寫一致性
緩存擊穿雪崩穿透
解決Redis并發(fā)競爭Key問題
Redis緩存策略
哨兵
解決會話
sqlsqlsqlsqllsqlslqslqslq數(shù)據(jù)類型
mysql特點(diǎn)
存儲引擎
增刪改
范式
索引優(yōu)缺點(diǎn)和使用原則
索引分類
索引區(qū)別
事務(wù)特性:ACID
并發(fā)錯誤
隔離級別
并發(fā)控制技術(shù)(鎖)
死鎖
分布式事務(wù)
SQL語句性能優(yōu)化
索引的優(yōu)化
分庫分表
Kafka
Es
生產(chǎn)消費(fèi)讀者寫者
讀者寫者:
多讀者/多寫者互斥/多讀者互斥
消費(fèi)者生產(chǎn)者:全互斥
生產(chǎn)者:p(empty),p(mutex),v(mutex),v(full)
消費(fèi)者:p(full),p(mutex)v(mutex)v(empty)
進(jìn)程vs線程
進(jìn)程是資源(CPU、內(nèi)存等)分配的基本單位,它是程序執(zhí)行時的一個實(shí)例。
線程是程序執(zhí)行時的最小單位,是CPU調(diào)度和分派的基本單位。
線程間共享進(jìn)程的所有資源,每個線程有自己的堆棧和局部變量。
線程由CPU獨(dú)立調(diào)度執(zhí)行,在多CPU環(huán)境下就允許多個線程同時運(yùn)行。
線程生命周期
(五個狀態(tài)):新建、就緒、運(yùn)行、阻塞、死亡
新建狀態(tài):線程對象已經(jīng)創(chuàng)建,還沒有在其上調(diào)用start()方法
就緒狀態(tài):當(dāng)線程調(diào)用start方法,但調(diào)度程序還沒有把它選定為運(yùn)行線程時線程
運(yùn)行狀態(tài):線程調(diào)度程序從可運(yùn)行池中選擇一個線程作為當(dāng)前線程時線程所處的狀態(tài)。(是線程進(jìn)入運(yùn)行狀態(tài)的唯一方式)
阻塞(等待/睡眠)狀態(tài):線程仍舊是活的,但是當(dāng)前沒有條件運(yùn)行。當(dāng)某件事件出現(xiàn),他可能返回到可運(yùn)行狀態(tài)
死亡狀態(tài):當(dāng)線程的run()方法完成時就認(rèn)為它死去。線程一旦死亡,就不能復(fù)生。 一個死去的線程上調(diào)用start()方法,會拋出java.lang.IllegalThreadStateException異常
進(jìn)程間通信方式
?
為了正確地實(shí)現(xiàn)信號量,信號量值的測試及減1操作應(yīng)當(dāng)是原子操作。為此,信號量通常是在內(nèi)核中實(shí)現(xiàn)的。Linux環(huán)境中,有三種類型:Posix(可移植性操作系統(tǒng)接口)有名信號量(使用Posix IPC名字標(biāo)識)、Posix基于內(nèi)存的信號量(存放在共享內(nèi)存區(qū)中)、System V信號量(在內(nèi)核中維護(hù))。這三種信號量都可用于進(jìn)程間或線程間的同步。
?
(套接字位于傳輸層與應(yīng)用層之間)
套接字是支持TCP/IP網(wǎng)絡(luò)通信的基本操作單元,可以看做主機(jī)間進(jìn)程進(jìn)行雙向通信的端點(diǎn)
?套節(jié)字通信基本過程
線程間通信方式
1.鎖機(jī)制:包括互斥鎖(對應(yīng)于JAVA中的Synchronized)、條件變量(對應(yīng)于JAVA中的volatile)
*互斥鎖提供了以排他方式防止數(shù)據(jù)結(jié)構(gòu)被并發(fā)修改的方法。
*條件變量可以以原子的方式阻塞進(jìn)程,直到某個特定條件為真為止。對條件的測試是在互斥鎖的保護(hù)下進(jìn)行的。條件變量始終與互斥鎖一起使用。
2.信號機(jī)制:包括無名線程信號量和命名線程信號量
JAVA中線程通信的具體方式有:1.Volatile和Synchronized 2.wait()和notify()機(jī)制 3.管道輸入/輸出流:PipedReader, PipedWriter 4.Thread.join() 5.TheadLocal類:線程變量,用于線程內(nèi)部共享數(shù)據(jù)。以ThreadLocal對象為鍵,任意對象為值的存儲結(jié)構(gòu) ThreadLocal<String> tl = new ThreadLocal<>();
進(jìn)程調(diào)度算法
先來先服務(wù)(FCFS)最簡單調(diào)度算法。按照進(jìn)入后備隊列的先后次序來加入就緒隊列等待執(zhí)行。是非搶占式,易于實(shí)現(xiàn),效率不高,利于長作業(yè)(CPU繁忙)不利于短作業(yè)(I/O繁忙)
短作業(yè)優(yōu)先(Short Job First)是非搶占式的,具有很好性能,降低平均等待時間,提高吞吐量。不利于長作業(yè),可能一直處于等待出現(xiàn)饑餓;未考慮作業(yè)緊迫程度,不能用于實(shí)時系統(tǒng)。
高響應(yīng)比優(yōu)先調(diào)度算法(Highest Reponse Ratio First, HRRF)(響應(yīng)比高意味著等待時間長而服務(wù)時間短,優(yōu)先處理)響應(yīng)比 = (等待 + 服務(wù)) / 服務(wù)時間 = 等待 / 服務(wù)時間 + 1
時間片輪轉(zhuǎn):用于分時系統(tǒng)的進(jìn)程調(diào)度,搶占式。
基本思想:將CPU時間分為若干時間片(q),進(jìn)程按到達(dá)順序排列。每次調(diào)度隊首,執(zhí)行1個時間片后,該進(jìn)程移至隊尾。能在給定時間響應(yīng)所有用戶請求,達(dá)到分時系統(tǒng)的目的。
其性能主要取決于時間片q的大小,q太大,則所有的進(jìn)程在1個時間片完成;太小則進(jìn)程頻繁切換,系統(tǒng)開銷大。
多級反饋隊列調(diào)度算法:將時間片輪轉(zhuǎn)與優(yōu)先級調(diào)度相結(jié)合,把進(jìn)程按優(yōu)先級分成不同的隊列,先按優(yōu)先級調(diào)度,優(yōu)先級相同的,按時間片輪轉(zhuǎn)。優(yōu)點(diǎn)是兼顧長短作業(yè),有較好的響應(yīng)時間,可行性強(qiáng),適用于各種作業(yè)環(huán)境。
死鎖
什么是死鎖?
當(dāng)兩個以上的運(yùn)算單元,雙方都在等待對方停止運(yùn)行,以獲取系統(tǒng)資源,但是沒有一方提前退出時,就稱為死鎖。
必要條件
禁止搶占 - 資源不能被強(qiáng)制從一個進(jìn)程中退出
持有和等待 - 一個進(jìn)程可以在等待時持有系統(tǒng)資源
互斥 – 某個資源在一段時間內(nèi)只能由一個進(jìn)程占有
循環(huán)等待 - 一系列進(jìn)程互相持有其他進(jìn)程所需要的資源,銀行家算法-破環(huán)循環(huán)等待條件,合理分配系統(tǒng)資源
死鎖的應(yīng)對方法
1. 最簡單、最常用方法是重新啟動,不過代價很大,意味著之前所有進(jìn)程計算都將付之東流
2. 撤消進(jìn)程,剝奪資源。終止參與死鎖的進(jìn)程,收回它們占有的資源,從而解除死鎖。
分兩種情況:一次性剝奪全部資源;或逐步撤消參與死鎖的進(jìn)程,選擇逐步撤消目的是撤消代價最小的進(jìn)程,比如按進(jìn)程優(yōu)先級確定代價;考慮進(jìn)程運(yùn)行代價和此進(jìn)程相關(guān)作業(yè)代價等;
3. 進(jìn)程回退策略,即讓參與死鎖的進(jìn)程回退到?jīng)]有發(fā)生死鎖前某一點(diǎn)處。操作起來系統(tǒng)開銷大,要有堆棧這樣的機(jī)構(gòu)記錄進(jìn)程的每一步變化,以便今后的回退,有時這是無法做到的。
頁面置換算法
最佳置換算法(OPT)
最佳(OPT)置換算法所選擇的被淘汰頁面將是以后永不使用的,或者是在最長時間內(nèi)不再被訪問的頁面,這樣可以保證獲得最低的缺頁率。但由于人們目前無法預(yù)知進(jìn)程在內(nèi)存頁面中哪個是未來最長時間內(nèi)不再被訪問的,因而該算法無法實(shí)現(xiàn)。
先進(jìn)先出置換算法(FIFO)
最簡單的頁面置換算法是先入先出(FIFO)法。這種算法的實(shí)質(zhì)是,總是選擇在主存中停留時間最長(即最老)的一頁置換,即先進(jìn)入內(nèi)存的頁,先退出內(nèi)存。
最近最久未使用(LRU)算法
它的實(shí)質(zhì)是,當(dāng)需要置換一頁時,選擇在最近一段時間里最久沒有使用過的頁面予以置換。
?
?
磁盤調(diào)度算法
1.FIFO:先來先服務(wù)算法;(依次處理)
2.SSTF: 最短尋道時間算法;(距離當(dāng)前磁道最近的有限處理)
3.SCAN:電梯調(diào)度算法;(這樣命名很形象,先按一個方向,掃描的過程依次訪問要求服務(wù)的隊列,當(dāng)掃描到最里層的一個服務(wù)序列時就反向掃描)
4.CSCAN: 循環(huán)掃描算法(從最里面一個磁道訪問完之后,立即返回最外層,也稱單向掃描調(diào)度算法)
5.FSCAN:分步電梯調(diào)度算法(分兩個隊列)
?
jvmjvmjvmjvm區(qū)域
線程私有的:程序計數(shù)器、(虛擬機(jī)棧、本地方法棧)
線程共享的:堆、方法區(qū)、直接內(nèi)存 (非運(yùn)行時數(shù)據(jù)區(qū)的一部分)
程序計數(shù)器是一塊較小的內(nèi)存空間,是當(dāng)前線程執(zhí)行字節(jié)碼的行號指示。
工作時通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等功能都需要依賴這個計數(shù)器來完成。為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個獨(dú)立的程序計數(shù)器。
作用:
通過改變程序計數(shù)器來依次讀取指令實(shí)現(xiàn)流程控制,如:順序執(zhí)行、選擇、循環(huán)、異常處理。
多線程:記錄當(dāng)前線程執(zhí)行的位置,線程被切換回來時能夠知道該線程上次運(yùn)行到哪兒了。
虛擬機(jī)棧(Java 內(nèi)存可以粗糙的區(qū)分為堆內(nèi)存(Heap)和棧內(nèi)存 (Stack))
存放基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double)和對象引用
會出現(xiàn)兩種異常:StackOverFlowError 和 OutOfMemoryError。
StackOverFlowError:不動態(tài)拓展,?線程請求棧的深度超過當(dāng)前虛擬機(jī)棧的最大深度
OutOfMemoryError:?動態(tài)擴(kuò)展,且內(nèi)存用完了,無法擴(kuò)展
堆:唯一目的就是存放對象實(shí)例,幾乎所有實(shí)例和數(shù)組都在這里。
方法區(qū)(永久代):存儲已加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等。
《Java 虛擬機(jī)規(guī)范》規(guī)定了有方法區(qū)和作用,并沒實(shí)現(xiàn)。(像接口),在不同的 JVM 上方法區(qū)的實(shí)現(xiàn)肯定是不同的了。?永久代是 HotSpot 虛擬機(jī)對方法區(qū)的一種實(shí)現(xiàn)方式。(像類)
判斷對象死亡?引用計數(shù) vs 可達(dá)性分析
引用計數(shù)算法:給對象添加一個引用計數(shù)器,每當(dāng)有一個地方引用到他,就加1;引用失效就減1。有問題,有可能存在循環(huán)引用,導(dǎo)致對象無法被回收。
可達(dá)性分析算法:以GC Roots對象為起始點(diǎn),從這些節(jié)點(diǎn)向下搜索
可以作為GC ROOT對象的有:Java虛擬機(jī)棧中的引用對象。本地方法棧中JNI(既一般說的Native方法)引用的對象。方法區(qū)中類靜態(tài)屬性所引用的對象。方法區(qū)中常量所引用的對象。
如何回收對象?垃圾收集算法
標(biāo)記清除算法:分為標(biāo)記和清除兩個階段。
首先從根集合進(jìn)行掃描,對存活的對象進(jìn)行標(biāo)記,標(biāo)記完畢后,再掃描整個空間中未被標(biāo)記的對象并進(jìn)行回收(老年代)
主要不足有兩個:效率問題:標(biāo)記和清除兩個過程的效率都不高;
空間問題:不進(jìn)行對象的移動,并且僅對不存活的對象進(jìn)行處理,因此標(biāo)記清除之后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,可能會導(dǎo)致以后在程序運(yùn)行過程中需要分配較大對象時,無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動作。
復(fù)制算法將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對象復(fù)制到另外一塊上面,然后再把已使用過的內(nèi)存空間一次清理掉。這種算法適用于對象存活率低的場景,比如新生代。
標(biāo)記整理算法的標(biāo)記過程類似標(biāo)記清除算法,但后續(xù)步驟不是直接對可回收對象進(jìn)行清理,而是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內(nèi)存,類似于磁盤整理的過程,該垃圾回收算法適用于對象存活率高的場景(老年代)
垃圾收集器
Serial收集器(復(fù)制算法): 新生代單線程收集器,標(biāo)記和清理都是單線程,優(yōu)點(diǎn)是簡單高效;
Serial Old收集器 (標(biāo)記-整理算法): 老年代單線程收集器,Serial收集器的老年代版本;
ParNew收集器 (復(fù)制算法): 新生代并行收集器,實(shí)際上是Serial收集器的多線程版本,在多核CPU環(huán)境下有著比Serial更好的表現(xiàn);
Parallel Old收集器 (標(biāo)記-整理算法): 老年代并行收集器,吞吐量優(yōu)先,Parallel Scavenge收集器的老年代版本;
CMS(Concurrent Mark Sweep)收集器(標(biāo)記-清除算法): 老年代并行收集器,以獲取最短回收停頓時間為目標(biāo)的收集器,具有高并發(fā)、低停頓的特點(diǎn),追求最短GC回收停頓時間。
收集過程分為:初始標(biāo)記,并發(fā)標(biāo)記,重新標(biāo)記,垃圾回收
G1(Garbage First)收集器 (標(biāo)記-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一個新收集器,G1收集器基于“標(biāo)記-整理”算法實(shí)現(xiàn),也就是說不會產(chǎn)生內(nèi)存碎片。此外,G1收集器不同于之前的收集器的一個重要特點(diǎn)是:G1回收的范圍是整個Java堆(包括新生代,老年代),而前六種收集器回收的范圍僅限于新生代或老年代。
?
分配回收策略(老年代新生代)
自動內(nèi)存管理可以歸結(jié)為自動化地解決兩個問題:
分配內(nèi)存、回收內(nèi)存。
一般而言,對象主要分配在新生代的Eden區(qū)上,少數(shù)情況下也可能直接分配在老年代中。
1)??? 對象優(yōu)先在Eden分配,當(dāng)Eden區(qū)沒有足夠空間時,虛擬機(jī)將發(fā)起一次MinorGC。
2)??? 大對象直接進(jìn)入老年代。所謂的大對象是指,很長的字符串以及數(shù)組。
3)??? 長期存活對象進(jìn)入老年代。在新生代中經(jīng)歷n次(默認(rèn)15)Minor GC后,晉升老年代。
4)??? 動態(tài)對象年齡判定。為了更好地適應(yīng)不同程序的內(nèi)存狀況,虛擬機(jī)并不是永遠(yuǎn)地要求對象年齡必須達(dá)到了MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進(jìn)入老年代,無須等到MaxTenuringThreshold中要求的年齡。
?
GC調(diào)優(yōu)
各分區(qū)的大小對GC的性能影響很大。
如何將各分區(qū)調(diào)整到合適的大小,分析活躍數(shù)據(jù)的大小是很好的切入點(diǎn)。
活躍數(shù)據(jù)的大小是指,應(yīng)用程序穩(wěn)定運(yùn)行時長期存活對象在堆中占用的空間大小,也就是Full GC后堆中老年代占用空間的大小。可以通過GC日志中Full GC之后老年代數(shù)據(jù)大小得出,比較準(zhǔn)確的方法是在程序穩(wěn)定后,多次獲取GC數(shù)據(jù),通過取平均值的方式計算活躍數(shù)據(jù)的大小
通過收集GC信息,結(jié)合系統(tǒng)需求,確定優(yōu)化方案,例如選用合適的GC回收器、重新設(shè)置內(nèi)存比例、調(diào)整JVM參數(shù)等。
根據(jù)對象生命周期的分布情況:如果應(yīng)用存在大量的短期對象,應(yīng)該選擇較大的年輕代;如果存在相對較多的持久對象,老年代應(yīng)該適當(dāng)增大。
?
JVM常用參數(shù)
-Xms: 初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:設(shè)置年輕代大小
-XX:NewRatio=n:設(shè)置年輕代和年老代的比值。如:為3表示年輕代和年老代比值為1:3
-XX:SurvivorRatio=n:年輕代中Eden區(qū)與兩個Survivor區(qū)的比值。注意Survivor區(qū)有兩個。如3表示Eden:3 Survivor:2(3:1:1),一個Survivor區(qū)占整個年輕代的1/5
-XX:MaxPermSize=n:設(shè)置永久代大小
?
Cookie session
Cookie與Session都是用來跟蹤瀏覽器用戶身份的會話方式。
Cookie放客戶瀏覽器,Session放服務(wù)器。
Cookie不安全,別人可以分析本地Cookie進(jìn)行Cookie欺騙,用加密的Cookie或Session。
Session存服務(wù)器。占服務(wù)器性能,如果減輕負(fù)擔(dān)方面,用Cookie。
單個Cookie在客戶端的限制是4K,很多瀏覽器都限制一個站點(diǎn)最多保存20個Cookie。
網(wǎng)絡(luò)分層
物理層:在傳輸媒體上傳輸數(shù)據(jù)比特流。屏蔽介質(zhì)和通信手段差異,使數(shù)據(jù)鏈路層感覺不到
數(shù)據(jù)鏈路層:主機(jī)間有很多鏈路,為相鄰結(jié)點(diǎn)間服務(wù)。把網(wǎng)絡(luò)層傳來的分組封裝成幀。
網(wǎng)絡(luò)層:為主機(jī)間提供傳輸服務(wù),把運(yùn)輸層傳遞下來的報文段或數(shù)據(jù)報封裝成分組。
運(yùn)輸層:進(jìn)程間的通用數(shù)據(jù)傳輸服務(wù)。
???????? 傳輸控制協(xié)議 TCP,提供面向連接、可靠的數(shù)據(jù)傳輸服務(wù),數(shù)據(jù)單位為報文段;
???????? 用戶數(shù)據(jù)報協(xié)議 UDP,提供無連接數(shù)據(jù)傳輸服務(wù),數(shù)據(jù)單位為用戶數(shù)據(jù)報。
???????? TCP 主要提供完整性服務(wù),UDP 主要提供及時性服務(wù)。
應(yīng)用層:為特定應(yīng)用程序提供數(shù)據(jù)傳輸服務(wù),例如 HTTP、DNS 等。數(shù)據(jù)單位為報文。
握手揮手
序號seq :對字節(jié)流編號,序號為 301,編號為 301,如攜帶數(shù)據(jù) 100 字節(jié),下一個報文段的序號應(yīng)為 401。用來標(biāo)記順序
確認(rèn)號 ack:期望收到的下一個報文段序號。例如 B 收到 A報文段序號為 501,長度 200 ,因此 B 期望701,B 發(fā)送給 A 的確認(rèn)報文段中確認(rèn)號就為 701。
確認(rèn) ACK :當(dāng) ACK=1 時確認(rèn)號ack字段有效,否則無效。
同步 SYN :在連接建立時用來同步序號。當(dāng) SYN=1,ACK=0 時表示這是一個連接請求報文段。若對方同意建立連接,則響應(yīng)報文中 SYN=1,ACK=1。
終止 FIN :用來釋放一個連接,當(dāng) FIN=1 時,表示發(fā)送方已發(fā)送完畢,要求釋放連接。
窗口 :窗口值作為接收方讓發(fā)送方設(shè)置其發(fā)送窗口的依據(jù)。這個限制是因為接收方的數(shù)據(jù)緩存空間有限
?
1、第一次握手:客戶端給服務(wù)器發(fā)送一個 SYN 報文。
2、第二次握手:服務(wù)器收到 SYN 報文之后,會應(yīng)答一個 SYN+ACK 報文。
3、第三次握手:客戶端收到 SYN+ACK 報文之后,會回應(yīng)一個 ACK 報文。
4、服務(wù)器收到 ACK 報文之后,三次握手建立完成。
第一次:seq隨機(jī)x,ACK=0,syn=1,ack=0,
第二次:seq隨機(jī)y,ACK=X+1,syn=1,ack=1
第三次:seq=x+1,ACK=Y+1,syn=0,ack=1
第一次握手:客戶端發(fā),服務(wù)端收到。服務(wù)端就能得出結(jié)論:客戶端的發(fā)送能力、服務(wù)端接收能力是正常
第二次握手:服務(wù)端發(fā),客戶端收到。客戶端就能得出結(jié)論:服務(wù)端的接收發(fā)送能力,客戶端的接收發(fā)送能力是正常的。服務(wù)器不能確認(rèn)客戶端的接收能力是否正常。
第三次握手:客戶端發(fā),服務(wù)端收到了。這樣服務(wù)端就能得出結(jié)論:客戶端的接收發(fā)送能力正常,服務(wù)器自己的發(fā)送、接收能力也正常。
第三次握手是為了防止失效的連接請求到達(dá)服務(wù)器,讓服務(wù)器錯誤打開連接。
客戶端等待一個超時重傳時間之后,就會重新請求連接。但是這個滯留的連接請求最后還是會到達(dá)服務(wù)器,如果不進(jìn)行三次握手,那么服務(wù)器就會打開兩個連接。
為什么三次四次?
三次:Server在收到建立連接請求后,可以直接把ACK和SYN放在一個報文發(fā)送給Client。
四次:關(guān)閉連接時,當(dāng)收到對方的FIN報文時,僅僅表示對方不再發(fā)送數(shù)據(jù)了但是還能接收數(shù)據(jù),己方也未必全部數(shù)據(jù)都發(fā)送給對方了,因此,己方ACK和FIN一般都會分開發(fā)送。
?
四次揮手:
客戶機(jī):我想和你斷開連接,你同意嗎?(FIN=1) ????????????服務(wù)器:我同意(ACK=1)
(期間服務(wù)器可能還會向客戶機(jī)發(fā)送數(shù)據(jù),但是反之不可以)
服務(wù)器:客戶機(jī),我想要和你斷開連接,你同意嗎?(FIN=1) 客戶機(jī):我同意。(ACK=1)
?
短連接表示“業(yè)務(wù)處理+傳輸數(shù)據(jù)的時間 遠(yuǎn)遠(yuǎn)小于 TIMEWAIT超時的時間”的連接。
?
UDP想可靠
就要接收方收到UDP之后回復(fù)個確認(rèn)包,發(fā)送方有個機(jī)制,收不到確認(rèn)包就要重新發(fā)送,每個包有遞增的序號,接收方發(fā)現(xiàn)中間丟了包就要發(fā)重傳請求,當(dāng)網(wǎng)絡(luò)太差時候頻繁丟包,防止越丟包越重傳的惡性循環(huán),要有個發(fā)送窗口的限制,發(fā)送窗口的大小根據(jù)網(wǎng)絡(luò)傳輸情況調(diào)整,調(diào)整算法要有一定自適應(yīng)性。
網(wǎng)絡(luò)訪問過程
域名解析
--> 發(fā)起TCP的3次握手 拿到域名對應(yīng)的IP地址之后,瀏覽器向服務(wù)器的WEB程序(tomcat,nginx)80端口發(fā)起TCP連接請求。
--> 建立TCP連接后發(fā)http請求
--> 服務(wù)器響應(yīng)http請求,瀏覽器得到html代碼
--> 瀏覽器解析html代碼,并請求html代碼中的資源(如js、css、圖片)
--> 瀏覽器對頁面渲染呈現(xiàn)
?
域名解析:搜索瀏覽器的DNS緩存 操作系統(tǒng)的DNS緩存? hosts文件C:\Windows 迭代DNS解析請求(根名稱/頂級名稱/二級名稱/權(quán)威名稱服務(wù)器)
?
為什么tcp可靠?
流量控制(滑動窗口)
發(fā)送方通過維持一個發(fā)送窗口確保不會發(fā)生發(fā)送方發(fā)太快接收方無法及時處理。
TCP如何保證可靠傳輸?
1、確認(rèn)和重傳:接收方收到報文就會確認(rèn),發(fā)送方發(fā)送一段時間后沒有收到確認(rèn)就重傳。
2、數(shù)據(jù)校驗
1)ack:數(shù)據(jù)丟失或延遲。發(fā)送時會起定時器,如果指定時間內(nèi)沒接收到ACK seq + 1,就再發(fā)一次。
2)數(shù)據(jù)亂序:接收方上一個收到的正確數(shù)據(jù)是seq + 4,它返回seq + 5作為ACK。這時候它收到了seq + 7,因為順序錯了,所以接收方會再次返回seq + 5給發(fā)送方。
3)數(shù)據(jù)錯誤:每一個TCP數(shù)據(jù)都會帶著數(shù)據(jù)的校驗和。接收方收到數(shù)據(jù)seq + 3以后會先對校驗和進(jìn)行驗證。如果結(jié)果不對,則發(fā)送ACK seq + 3,讓發(fā)送方重新發(fā)送數(shù)據(jù)。
4)數(shù)據(jù)重復(fù):接收方直接丟棄重復(fù)數(shù)據(jù)。
3、流量控制:當(dāng)接收方來不及處理發(fā)送方的數(shù)據(jù),能提示發(fā)送方降低發(fā)送的速率,防止包丟失。
4、擁塞控制:當(dāng)網(wǎng)絡(luò)擁塞時,減少數(shù)據(jù)的發(fā)送。
流量控制與滑動窗口
流量控制是為了控制發(fā)送方發(fā)送速率,保證接收方來得及接收。
接收方發(fā)送的確認(rèn)報文中的窗口字段可以用來控制發(fā)送方窗口大小,從而影響發(fā)送方的發(fā)送速率。將窗口字段設(shè)置為0,則發(fā)送方不能發(fā)送數(shù)據(jù)。
發(fā)送方和接收方各有一個窗口,接收方通過窗口字段告訴發(fā)送方自己的窗口大小,發(fā)送方根據(jù)這個值和其它信息設(shè)置自己的窗口大小。
TCP使用累計確認(rèn)(發(fā)送方一次發(fā)送多個連續(xù)包,接收方只需要確認(rèn)最后一個包),快速重傳(收到3個冗余的ACK包立馬重傳,不用等待超時)以及選擇重傳(只對丟失的包進(jìn)行重傳)提高效率
TCP的擁塞控制與擁塞窗口
擁塞控制:防止過多的數(shù)據(jù)注入到網(wǎng)絡(luò)中,這樣可以使網(wǎng)絡(luò)中的路由器或鏈路不致過載。
發(fā)送方維持一個擁塞窗口 cwnd的狀態(tài)變量。動態(tài)地在變化。發(fā)送方讓自己的發(fā)送窗口等于擁塞。
原則:沒擁塞,窗口就增大一些,以便把更多的分組發(fā)送出去。
出現(xiàn)擁塞,擁塞窗口就減小一些,以減少注入到網(wǎng)絡(luò)中的分組數(shù)。
幾種擁塞控制方法:慢開始、擁塞避免、快重傳和快恢復(fù)。
慢開始:不清楚網(wǎng)絡(luò)的負(fù)荷情況。因此先探測一下,由小到大逐漸增大發(fā)送窗口,也就是說,由小到大逐漸增大擁塞窗口數(shù)值。
通常在剛剛開始發(fā)送報文段時,先把擁塞窗口 cwnd 設(shè)置為一個最大報文段MSS的數(shù)值。而在每收到一個對新的報文段的確認(rèn)后,把擁塞窗口增加至多一個MSS的數(shù)值。用這樣的方法逐步增大發(fā)送方的擁塞窗口 cwnd ,可以使分組注入到網(wǎng)絡(luò)的速率更加合理。
慢開始門限ssthresh的用法如下:
?? ?? 當(dāng) cwnd < ssthresh 時,使用慢開始算法
當(dāng) cwnd > ssthresh 時,改用擁塞避免算法。
??? 當(dāng) cwnd = ssthresh 時,都可以
擁塞避免算法:經(jīng)過一個往返時間RTT就把擁塞窗口cwnd加1,不是加倍。按線性規(guī)律緩慢增長,比慢開始算法的擁塞窗口增長速率緩慢得多。
只要發(fā)送方判斷網(wǎng)絡(luò)出現(xiàn)擁塞(其根據(jù)就是沒有收到確認(rèn)),就要把門限ssthresh設(shè)置為出現(xiàn)擁塞時的發(fā)送方窗口值的一半(但不能小于2)。然后把擁塞窗口cwnd重新設(shè)置為1,執(zhí)行慢開始算法。目的就是要迅速減少主機(jī)發(fā)送到網(wǎng)絡(luò)中的分組數(shù),使得發(fā)生擁塞的路由器有足夠時間把隊列中積壓的分組處理完畢。
快速恢復(fù)算法:發(fā)送方認(rèn)為網(wǎng)絡(luò)很可能沒有發(fā)生擁塞,因此而是把cwnd值設(shè)置為門限減半后的數(shù)值,然后使擁塞窗口線性增大。
UDP可靠
最簡單的方式是在應(yīng)用層模仿傳輸層TCP的可靠性傳輸。下面不考慮擁塞處理,可靠UDP的簡單設(shè)計。
?
1、添加seq/ack機(jī)制,確保數(shù)據(jù)發(fā)送到對端
2、添加發(fā)送和接收緩沖區(qū),主要是用戶超時重傳。
3、添加超時重傳機(jī)制。
詳細(xì)說明:送端發(fā)送數(shù)據(jù)時,生成一個隨機(jī)seq=x,然后每一片按照數(shù)據(jù)大小分配seq。數(shù)據(jù)到達(dá)接收端后接收端放入緩存,并發(fā)送一個ack=x的包,表示對方已經(jīng)收到了數(shù)據(jù)。發(fā)送端收到了ack包后,刪除緩沖區(qū)對應(yīng)的數(shù)據(jù)。時間到后,定時任務(wù)檢查是否需要重傳數(shù)據(jù)。
?
目前有如下開源程序利用udp實(shí)現(xiàn)了可靠的數(shù)據(jù)傳輸。分別為RUDP、RTP、UDT。
HTTP and HTTPS
通信使用明文,內(nèi)容可能被竊聽(重要密碼泄露)
不驗證通信方身份,有可能遭遇偽裝(跨站點(diǎn)請求偽造)
無法證明報文的完整性,有可能已遭篡改(運(yùn)營商劫持)
http+加密+認(rèn)證+完整性保護(hù)=https
1、https協(xié)議需要到ca申請證書,一般免費(fèi)證書較少,因而需要一定費(fèi)用。
2、https則是通過TLS加密后傳輸。SSL 是“Secure Sockets Layer”的縮寫,中文叫做“安全套接層”
3、http和https使用的是完全不同的連接方式,用的端口也不一樣,前者是80,后者是443。
對稱密鑰加密,又稱私鑰加密,即發(fā)送方和接收方用同一個密鑰去加密解密。優(yōu)勢是速度快,適合于對大數(shù)據(jù)量進(jìn)行加密,但密鑰管理困難。
非對稱密鑰加密,又稱公鑰加密,需要使用一對密鑰分別完成加密和解密,一個公開發(fā)布,即公開密鑰,另一個由用戶自己保存,即私用密鑰。信息發(fā)送者用公開密鑰去加密,而信息接收者則用私用密鑰去解密。
從功能角度而言非對稱加密比對稱加密功能強(qiáng)大,但加密和解密速度卻比對稱密鑰加密慢得多。
SSL/TLS協(xié)議,公鑰加密法,客戶端先向服務(wù)器端索要公鑰,然后用公鑰加密信息,服務(wù)器收到密文后,用自己的私鑰解密,如何保證公鑰不被篡改?
解決方法:將公鑰放在數(shù)字證書中,只要證書是可信的,公鑰就是可信的。
HTTP方法及相互區(qū)別
GET 用于獲取,而 POST 用于傳輸實(shí)體主體。
GET 和 POST 的請求都能使用額外的參數(shù),但是 GET 的參數(shù)是以查詢字符串出現(xiàn)在 URL 中,而 POST 的參數(shù)存儲在實(shí)體主體中。不能因為 POST 參數(shù)存儲在實(shí)體主體中就認(rèn)為它的安全性更高,因為照樣可以通過一些抓包工具(Fiddler)查看
因為 URL 只支持 ASCII 碼,因此 GET 的參數(shù)中如果存在中文等字符就需要先進(jìn)行編碼。例如中文會轉(zhuǎn)換為%E4%B8%AD%E6%96%87 ,而空格會轉(zhuǎn)換為%20。POST參數(shù)支持標(biāo)準(zhǔn)字符集。
GET請求在URL中傳送的參數(shù)是有長度限制的,而POST么有
HEAD獲取報文首部,和 GET 方法類似,但是不返回報文實(shí)體主體部分
狀態(tài)碼
1正常2成功3重定向4客戶端錯誤5服務(wù)端錯誤
100 Continue :表明到目前為止都很正常,客戶端可以繼續(xù)發(fā)送請求或者忽略這個響應(yīng)。
200 OK
204 No Content :請求已經(jīng)成功處理,但是返回的響應(yīng)報文不包含實(shí)體的主體部分。一般在只需要從客戶端往服務(wù)器發(fā)送信息,而不需要返回數(shù)據(jù)時使用。
206 Partial Content :表示客戶端進(jìn)行了范圍請求,響應(yīng)報文包含由 Content-Range 指定范圍的實(shí)體內(nèi)容。
301 Moved Permanently :永久性重定向
302 Found :臨時性重定向
303 See Other :和 302 有著相同的功能,但是 303 明確要求客戶端應(yīng)該采用 GET 方法獲取資源。
304 Not Modified :表示資源在由請求頭中的If-Modified-Since或If-None-Match參數(shù)指定的這一版本之后,未曾被修改。在這種情況下,由于客戶端仍然具有以前下載的副本,因此不需要重新傳輸資源。
307 Temporary Redirect :臨時重定向,與302相反,當(dāng)重新發(fā)出原始請求時,不允許更改請求方法。
400 Bad Request :請求報文中存在語法錯誤
401 Unauthorized :該狀態(tài)碼表示發(fā)送的請求需要有認(rèn)證信息。如果之前已進(jìn)行過一次請求,則表示用戶認(rèn)證失敗
403 Forbidden :請求被拒絕
404 Not Found :請求失敗,請求所希望得到的資源未被在服務(wù)器上發(fā)現(xiàn),但允許用戶的后續(xù)請求。
500 Internal Server Error :服務(wù)器正在執(zhí)行請求時發(fā)生錯誤
503 Service Unavailable :服務(wù)器暫時處于超負(fù)載或正在進(jìn)行停機(jī)維護(hù),現(xiàn)在無法處理請求。
504 Gateway Timeout作為網(wǎng)關(guān)或者代理工作的服務(wù)器嘗試執(zhí)行請求時,未能及時從上游服務(wù)器
?
http結(jié)構(gòu)
一個HTTP請求報文由四個部分組成:請求行、請求頭部、空行、請求數(shù)據(jù)。
1.請求行:由請求方法字段、URL字段和HTTP協(xié)議版本字段3個字段組成,它們用空格分隔。比如 GET /data/info.html HTTP/1.1
方法字段就是HTTP使用的請求方法,比如常見的GET/POST
其中HTTP協(xié)議版本有兩種:HTTP1.0/HTTP1.1 可以這樣區(qū)別:
HTTP1.0對于每個連接都只能傳送一個請求和響應(yīng),請求就會關(guān)閉,HTTP1.0沒有Host字段;而HTTP1.1在同一個連接中可以傳送多個請求和響應(yīng),多個請求可以重疊和同時進(jìn)行,HTTP1.1必須有Host字段。
2.請求頭部
指明請求類型(一般是GET或者 POST)。大多數(shù)請求頭并不是必需的,但post的Content-Length除外。
Accept: 瀏覽器可接受的MIME類型。
Accept-Charset:瀏覽器可接受的字符集。
Accept-Encoding:瀏覽器能夠進(jìn)行解碼的數(shù)據(jù)編碼方式,比如gzip。
Accept-Language:瀏覽器所希望的語言種類
Authorization:授權(quán)信息,通常出現(xiàn)在對服務(wù)器發(fā)送的WWW-Authenticate頭的應(yīng)答中。
Content-Length:表示請求消息正文的長度。
Host: 客戶機(jī)通過這個頭告訴服務(wù)器,想訪問的主機(jī)名。Host頭域指定請求資源的Intenet主機(jī)和端口號,必須表示請求url的原始服務(wù)器或網(wǎng)關(guān)的位置。HTTP/1.1請求必須包含主機(jī)頭域,否則系統(tǒng)會以400狀態(tài)碼返回。
Referer:客戶機(jī)通過這個頭告訴服務(wù)器,它是從哪個資源來訪問服務(wù)器的(防盜鏈)。包含一個URL,用戶從該URL代表的頁面出發(fā)訪問當(dāng)前請求的頁面。
User-Agent:User-Agent頭域的內(nèi)容包含發(fā)出請求的用戶信息。瀏覽器類型,如果Servlet返回的內(nèi)容與瀏覽器類型有關(guān)則該值非常有用。
Cookie:客戶機(jī)通過這個頭可以向服務(wù)器帶數(shù)據(jù),這是最重要的請求頭信息之一。
From:請求發(fā)送者的email地址,由一些特殊的Web客戶程序使用,瀏覽器不會用到它。
Connection:處理完這次請求后是否斷開連接還是繼續(xù)保持連接。
同時指定幾個范圍:bytes=500-600,601-999
3.空行
它的作用是通過一個空行,告訴服務(wù)器請求頭部到此為止。
4.請求數(shù)據(jù)
若方法字段是GET,則此項為空,沒有數(shù)據(jù)
若方法字段是POST,則通常來說此處放置的就是要提交的數(shù)據(jù)
?
HTTP響應(yīng)報文由三部分組成:響應(yīng)行、響應(yīng)頭、響應(yīng)體
1.響應(yīng)行
響應(yīng)行一般由協(xié)議版本、狀態(tài)碼及其描述組成 比如 HTTP/1.1 200 OK
其中協(xié)議版本HTTP/1.1或者HTTP/1.0,200就是它的狀態(tài)碼,OK則為它的描述。
//常見狀態(tài)碼:
100~199:表示成功接收請求,要求客戶端繼續(xù)提交下一次請求才能完成整個處理過程。
200~299:表示成功接收請求并已完成整個處理過程。常用200
300~399:為完成請求,客戶需進(jìn)一步細(xì)化請求。例如:請求的資源已經(jīng)移動一個新地址、常用302(意味著你請求我,我讓你去找別人),307和304(我不給你這個資源,自己拿緩存)
400~499:客戶端的請求有錯誤,常用404(意味著你請求的資源在web服務(wù)器中沒有)403(服務(wù)器拒絕訪問,權(quán)限不夠)
500~599:服務(wù)器端出現(xiàn)錯誤,常用500
2.響應(yīng)頭
響應(yīng)頭用于描述服務(wù)器的基本信息,以及數(shù)據(jù)的描述,服務(wù)器通過這些數(shù)據(jù)的描述信息,可以通知客戶端如何處理等一會兒它回送的數(shù)據(jù)。
設(shè)置Cookie,指定修改日期,指示瀏覽器按照指定的間隔刷新頁面,聲明文檔的長度以便利用持久HTTP連接,……等等許多其他任務(wù)。
?
常見的響應(yīng)頭字段含義:
Allow:服務(wù)器支持哪些請求方法(如GET、POST等)。
Content-Encoding:文檔的編碼(Encode)方法。只有在解碼之后才可以得到Content-Type頭指定的內(nèi)容類型。利用gzip壓縮文檔能夠顯著地減少HTML文檔的下載時間。
Content-Length:表示內(nèi)容長度。只有當(dāng)瀏覽器使用持久HTTP連接時才需要這個數(shù)據(jù)。
Content- Type:表示后面的文檔屬于什么MIME類型。Servlet默認(rèn)為text/plain,但通常需要顯式地指定為text/html。由于經(jīng)常要設(shè)置 Content-Type,因此HttpServletResponse提供了一個專用的方法setContentType。
?
Date:當(dāng)前的GMT時間,例如,Date:Mon,31Dec200104:25:57GMT。Date描述的時間表示世界標(biāo)準(zhǔn)時,換算成本地時間,需要知道用戶所在的時區(qū)。你可以用setDateHeader來設(shè)置這個頭以避免轉(zhuǎn)換時間格式的麻煩。
Location:這個頭配合302狀態(tài)碼使用,用于重定向接收者到一個新URI地址。表示客戶應(yīng)當(dāng)?shù)侥睦锶ヌ崛∥臋n。Location通常不是直接設(shè)置的,而是通過HttpServletResponse的sendRedirect方法,該方法同時設(shè)置狀態(tài)代碼為302。
Refresh:告訴瀏覽器隔多久刷新一次,以秒計。
?
Set-Cookie:設(shè)置和頁面關(guān)聯(lián)的Cookie。Servlet不應(yīng)使用response.setHeader(“Set-Cookie”, …),而是應(yīng)使用HttpServletResponse提供的專用方法addCookie。
?
Transfer-Encoding:告訴瀏覽器數(shù)據(jù)的傳送格式。
?
?
注:設(shè)置應(yīng)答頭最常用是HttpServletResponse的setHeader,方法有兩個參數(shù):應(yīng)答頭的名字和值。和設(shè)置狀態(tài)代碼相似,設(shè)置應(yīng)答頭應(yīng)該在發(fā)送任何文檔內(nèi)容之前進(jìn)行。
setDateHeader方法和setIntHeadr方法專門用來設(shè)置包含日期和整數(shù)值的應(yīng)答頭,前者避免了把Java時間轉(zhuǎn)換為GMT時間字符串的麻煩,后者則避免了把整數(shù)轉(zhuǎn)換為字符串的麻煩。
HttpServletResponse設(shè)置
setContentType:設(shè)置Content-Type頭。大多數(shù)Servlet都要用到這個方法。
addCookie:設(shè)置一個Cookie(Servlet API中沒有setCookie方法,因為應(yīng)答往往包含多個Set-Cookie頭)。
3.響應(yīng)體
響應(yīng)體就是響應(yīng)的消息體,如果是純數(shù)據(jù)就是返回純數(shù)據(jù),如果請求的是HTML頁面,那么返回的就是HTML代碼,如果是JS就是JS代碼,如此之類。
?
Tcp結(jié)構(gòu)
六位標(biāo)志位包含以下幾項:
URG:表示緊急指針是否有效;
ACK:表示確認(rèn)號是否有效,攜帶ACK標(biāo)志的數(shù)據(jù)報文段為確認(rèn)報文段;
PSH:提示接收端的應(yīng)用程序應(yīng)該立即從TCP接受緩沖區(qū)中讀走數(shù)據(jù),為接受后續(xù)數(shù)據(jù)騰出空間;
RST:表示要求對方重新建立連接,攜帶RST標(biāo)志位的TCP報文段成為復(fù)位報文段;
SYN:表示請求建立一個連接,攜帶SYN標(biāo)志的TCP報文段為同步報文段;
FIN:通知對方本端要關(guān)閉了,帶FIN標(biāo)志的TCP報文段為結(jié)束報文段。
確認(rèn)序號:
Ack序號,占32位,只有ACK標(biāo)志位為1時,確認(rèn)序號字段才有效,Ack=Seq+1。
TCP頭部選項:
TCP頭部選項是一個可變長的信息,這部分最多包含40字節(jié),因為TCP頭部最長60字節(jié),(其中還包含前面20字節(jié)的固定部分)。
為什么在TCP首部的開始便是首部長度字段而UDP首部卻沒有?
因為UDP提供無連接服務(wù),它的數(shù)據(jù)包包頭,是固定長度的8字節(jié),不存在可選字段,可以減少很多傳輸開銷,所以它無需使用首部字段長,因為它的首部就是固定的。
而TCP提供連接服務(wù),它的數(shù)據(jù)包包頭,除了固定的20字節(jié)之外,還存在一個可選項,這個可選項字段,是根據(jù)TCP連接的要求而變動。這一字段最常見到的就是最大報文大小MSS,它指明發(fā)送端所能接收的最大長度的報文段。因為這個字段的存在,所以TCP包頭使用了首部長字段。它占4位,以四字節(jié)為單位表示TCP包頭長度,也就是說,TCP的首部最大長度可以是15x4=60字節(jié),而可選項長可以為60-20=40字節(jié)
Socket
ServerSocket serverSocket = new ServerSocket(12016);//監(jiān)聽do {Socket socket = serverSocket.accept();//獲取IO流InputStream is = socket.getInputStream();OutputStream os = socket.getOutputStream();byte[] cache = new byte[1024];is.read(cache);//讀cmd = new String(cache);os.write(returnText.getBytes());//寫os.flush();is.close();os.close();} while (!returnText.equals("end"));redisredisredis數(shù)據(jù)結(jié)構(gòu)
字符串
相對于c的改進(jìn):
獲取長度:c遍歷字符串,redis直接讀len
緩沖區(qū)安全:c字符串容易緩沖區(qū)溢出,比如:沒有分配足夠空間就執(zhí)行拼接操作。
redis會先檢查空間是否滿足要求,如果不滿足會自動擴(kuò)充。
內(nèi)存分配:c長度變化總是內(nèi)存重新分配。
redis:1、預(yù)分配:如修改后大于1MB,分配? 1MBfree空間。修改長度時檢查,夠的話就用free空間2、惰性空間釋放:縮短時不需要釋放空間,用free記錄即可,留作以后使用。
二進(jìn)制安全
c字符串程序讀到空字符會誤以為是結(jié)尾,所以 c字符串不能保存二進(jìn)制文件。
而redis字符串二進(jìn)制安全,因為有l(wèi)en記錄長度。
鏈表:雙端、無環(huán)、帶長度記錄、多態(tài):用 void* 指針來保存節(jié)點(diǎn)值。
字典:數(shù)組、大小、掩碼(等于size-1),已有節(jié)點(diǎn)數(shù)量
整數(shù)集合:數(shù)組、數(shù)量、編碼方式
升級:計算新空間、轉(zhuǎn)換并放入原來的數(shù)、放入新數(shù)
降級:無
壓縮列表
連鎖更新:在一個壓縮列表中有多個連續(xù)的、長度介于?250?字節(jié)到?253?字節(jié)之間的節(jié)點(diǎn) ,這時, 如果我們將一個長度大于等于?254?字節(jié)的新節(jié)點(diǎn)?new?設(shè)置為壓縮列表的表頭節(jié)點(diǎn)
對象
對象組成:// 類型// 編碼// 指向底層實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)的指針
不為對象關(guān)聯(lián)固定編碼,提升靈活性和效率,可以根據(jù)使用場景為對象設(shè)置不同編碼。
字符串對象:long、字符串、embstr
列表對象:所有字符串元素長度小于 64 字節(jié);元素數(shù)量小于 512 個:ziplist否則鏈表
哈希對象:所有鍵值對的字符串長度都小于 64 字節(jié);數(shù)量小于 512 :ziplist 否則hashtable
集合對象:所有元素都是整數(shù)值;數(shù)量不超過 512 個:intset,否則hashtable
有序集合:所有元素的長度都小于 64 字節(jié);元素數(shù)量小于 128 個;ziplist否則跳表
場景:
string:常規(guī)計數(shù):微博數(shù),粉絲數(shù)等。
hash 特別適合用于存儲對象,如用戶,商品
list查找遍歷等,可以lrange命令實(shí)現(xiàn)不斷下拉分頁
set去重,點(diǎn)贊關(guān)注
sortedset 有序,做實(shí)時排行榜,禮物榜彈幕榜。
跳表
比較
哈希表不是有序的。因此,在哈希表上只能做單個key的查找,不適宜做范圍查找。
做范圍查找,平衡樹比skiplist操作要復(fù)雜。
在平衡樹上,我們找到指定范圍的小值之后,需要以中序遍歷的順序繼續(xù)尋找其它不超過大值的節(jié)點(diǎn)。如果不對平衡樹進(jìn)行一定的改造,這里的中序遍歷并不容易實(shí)現(xiàn)。而在skiplist上進(jìn)行范圍查找就非常簡單,只需要在找到小值之后,對第1層鏈表進(jìn)行若干步的遍歷就可以實(shí)現(xiàn)。
平衡樹的插入和刪除操作可能引發(fā)子樹的調(diào)整,邏輯復(fù)雜,而skiplist的插入和刪除只需要修改相鄰節(jié)點(diǎn)的指針,操作簡單又快速。
從內(nèi)存占用上來說,skiplist比平衡樹更靈活一些。一般來說,平衡樹每個節(jié)點(diǎn)包含2個指針(分別指向左右子樹),而skiplist每個節(jié)點(diǎn)包含的指針數(shù)目平均為1/(1-p),具體取決于參數(shù)p的大小。如果像Redis里的實(shí)現(xiàn)一樣,取p=1/4,那么平均每個節(jié)點(diǎn)包含1.33個指針,比平衡樹更有優(yōu)勢。
從算法實(shí)現(xiàn)難度上來比較,skiplist比平衡樹要簡單得多。
Redis中的skiplist和經(jīng)典有何不同
skiplist的key允許重復(fù)。這在最開始介紹的經(jīng)典skiplist中是不允許的。
在比較時,不僅比較key,還比較數(shù)據(jù)本身。在Redis的skiplist實(shí)現(xiàn)中,數(shù)據(jù)本身的內(nèi)容唯一標(biāo)識這份數(shù)據(jù),而不是由key來唯一標(biāo)識。另外,當(dāng)多個元素分?jǐn)?shù)相同的時候,還需要根據(jù)數(shù)據(jù)內(nèi)容來進(jìn)字典排序。
第1層鏈表不是一個單向鏈表,而是一個雙向鏈表。這是為了方便以倒序方式獲取一個范圍內(nèi)的元素。
在skiplist中可以很方便地計算出每個元素的排名(rank)。
作者原話
有幾個原因:
1)它們的記憶力不是很強(qiáng)。基本上由你決定。更改有關(guān)節(jié)點(diǎn)具有給定數(shù)量級別的概率的參數(shù)將使內(nèi)存密集度低于btree。
2)排序集通常是許多Zrange或Zrevrange操作的目標(biāo),即作為鏈表遍歷跳過列表。通過此操作,跳過列表的緩存區(qū)域性至少與其他類型的平衡樹一樣好。
3)它們易于實(shí)現(xiàn)、調(diào)試等。例如,由于跳過列表的簡單性,我收到了一個補(bǔ)丁(已經(jīng)在redis master中),其中包含在o(log(n))中實(shí)現(xiàn)zrank的擴(kuò)展跳過列表。它只需要對代碼稍作修改。
HyperLogLog
是一種概率數(shù)據(jù)結(jié)構(gòu),用來估算數(shù)據(jù)的基數(shù)。數(shù)據(jù)集可以是訪客 IP 地址,E-mail 或用戶 ID。
基數(shù)就是指一個集合中不同值的數(shù)目set
使用 Redis 統(tǒng)計集合的基數(shù)有三種方法:用 Redis 的 HashMap,BitMap 和 HyperLogLog。前兩個在集合的數(shù)量級增長時,所消耗的內(nèi)存會大大增加,但是 HyperLogLog 不會。
HyperLogLog 通過犧牲準(zhǔn)確率來減少內(nèi)存空間的消耗,只需要12K內(nèi)存,在標(biāo)準(zhǔn)誤差0.81%的前提下,能夠統(tǒng)計2^64個數(shù)據(jù)。所以 HyperLogLog 是否適合在比如統(tǒng)計日活月活此類的對精度要不不高的場景。
這是一個很驚人的結(jié)果,以如此小的內(nèi)存來記錄如此大數(shù)量級的數(shù)據(jù)基數(shù)。
命令
> PFADD visitors a b c//插入
(integer) 1//數(shù)量變化返回1
> PFCOUNT visitors//獲取數(shù)量
(integer) 3
> PFADD customers alice dan->(integer) 1
> PFMERGE a b d//合并
OK
> PFCOUNT everyone->(integer) 4
通過Hash函數(shù),將元素轉(zhuǎn)為64位比特串,0 代表反面,1 代表正面,從左往右第1個1
HyperLogLog: 一共分了 2^14=16384 個桶,。每個桶中是一個 6 bit 的數(shù)組。將上文所說的 64 位比特串的低 14 位單獨(dú)拿出,值對應(yīng)桶號,然后將第一次出現(xiàn) 1 的位置值設(shè)置到桶中。50位中出現(xiàn)1的位置值最大為50,所以每個桶中的 6 位數(shù)組正好可以表示該值。
HyperLogLog 對象中的?registers?數(shù)組就是桶,它有兩種存儲結(jié)構(gòu),分別為密集存儲結(jié)構(gòu)和稀疏存儲結(jié)構(gòu),從中我們可以看到 Redis 對節(jié)省內(nèi)存極致地追求。
真使用足夠多的?uint8_t?字節(jié)去表示,只是此時會涉及到字節(jié)位置和桶的轉(zhuǎn)換,因為字節(jié)有 8 位,而桶只需要 6 位。所以我們需要將桶的序號轉(zhuǎn)換成對應(yīng)的字節(jié)偏移量 offsetbytes 和其內(nèi)部的位數(shù)偏移量 offsetbits。
當(dāng) offset_bits 小于等于2時,說明一個桶就在該字節(jié)內(nèi),只需要進(jìn)行倒轉(zhuǎn)就能得到桶的值。
?offset_bits 大于 2 ,則說明一個桶分布在兩個字節(jié)內(nèi),此時需要將兩個字節(jié)的內(nèi)容都進(jìn)行倒置,然后再進(jìn)行拼接得到桶的值。
Redis 為了方便表達(dá)稀疏存儲,
單線程
1、完全基于內(nèi)存
2、數(shù)據(jù)結(jié)構(gòu)簡單, Redis中的數(shù)據(jù)結(jié)構(gòu)是專門進(jìn)行設(shè)計的;
3、采用單線程,避免了不必要的上下文切換和競爭條件,也不存在多進(jìn)程或者多線程導(dǎo)致的切換而消耗 CPU,不用考慮鎖的問題,沒有因為可能出現(xiàn)死鎖。
4、使用多路I/O復(fù)用模型,非阻塞IO;
多路I/O復(fù)用模型是利用 select、poll、epoll 可以同時監(jiān)察多個流的 I/O 事件的能力,空閑時,當(dāng)前線程阻塞,當(dāng)有流有 I/O 事件時,就喚醒,于是程序就會輪詢一遍所有的流(epoll 只輪詢那些發(fā)出了事件的流),并且只依次順序的處理就緒的流,避免了大量無用操作。
持久化
RDB持久化-獲得內(nèi)存里的數(shù)據(jù)在某個時間點(diǎn)上的副本。 (這稱為“半持久化模式”,RDB,redis默認(rèn)持久化方式);實(shí)際操作過程是fork一個子進(jìn)程,先將數(shù)據(jù)集寫入臨時文件,寫入成功后,再替換之前文件,用二進(jìn)制壓縮存儲。
優(yōu)勢:1,數(shù)據(jù)庫將只包含一個文件,這對于文件備份來說非常完美;
2, 恢復(fù)容易。我們可以輕松的將一個文件壓縮后轉(zhuǎn)移到其它存儲介質(zhì)上;
3, 性能最大化;對于服務(wù)進(jìn)程而言,開始持久化時,它唯一需要做的只是fork出子進(jìn)程,之后再由子進(jìn)程完成這些持久化的工作,這樣就可以極大的避免服務(wù)進(jìn)程執(zhí)行IO操作了。
劣勢:
1, 存在數(shù)據(jù)丟失的可能。此前沒有來得及寫入磁盤的數(shù)據(jù)都將丟失。
2, 由于RDB是通過fork子進(jìn)程來協(xié)助完成數(shù)據(jù)持久化工作的,因此,當(dāng)數(shù)據(jù)集較大時,可能會導(dǎo)致整個服務(wù)器停止服務(wù)幾百毫秒,甚至是1秒鐘。
?
AOF持久化-把每一次數(shù)據(jù)變化都寫入到一個AOF文件中(“全持久化模式”)
優(yōu)點(diǎn):
1, 該機(jī)制可以帶來更高的數(shù)據(jù)安全性。AOF有3種同步策略,即每秒同步、每修改同步和不同步。
缺點(diǎn):平時運(yùn)行效率低;AOF文件通常大于RDB文件;恢復(fù)速度慢。
?
Redis和數(shù)據(jù)庫雙寫一致性
讀的時候,先讀緩存,緩存沒有的話,就讀數(shù)據(jù)庫,然后取出數(shù)據(jù)放入緩存,同時返回響應(yīng)。
更新的時候,先刪除緩存,然后更新數(shù)據(jù)庫。
(1)先淘汰緩存
(2)再寫數(shù)據(jù)庫(這兩步和原來一樣)
(3)休眠1秒,再次淘汰緩存
這么做,可以將1秒內(nèi)所造成的緩存臟數(shù)據(jù),再次刪除。
?
緩存擊穿雪崩穿透
緩存擊穿是針對緩存中沒有但數(shù)據(jù)庫有的數(shù)據(jù)。場景是,當(dāng)某個Key失效后,瞬間涌入大量的請求同一個Key,這些請求不會命中Redis,都會請求到DB,導(dǎo)致數(shù)據(jù)庫壓力過大
解決辦法
1、設(shè)置熱點(diǎn)Key,自動檢測熱點(diǎn)Key,將熱點(diǎn)Key的過期時間加大或者永不過期。
2、加互斥鎖。當(dāng)發(fā)現(xiàn)沒有命中Redis,去查數(shù)據(jù)庫的時候,在執(zhí)行更新緩存的操作上加鎖,當(dāng)一個線程訪問時,其它線程等待,這個線程訪問過后,緩存中的數(shù)據(jù)會被重建,這樣其他線程就可以從緩存中取值。
緩存雪崩是指大量Key同時失效,對這些Key的請求又會打到DB上,同樣會導(dǎo)致數(shù)據(jù)庫壓力過大甚至掛掉。
解決辦法
1)讓Key的失效時間分散開,可以在統(tǒng)一的失效時間上再加一個隨機(jī)值,或者使用更高級的算法分散失效時間。
2)構(gòu)建多個redis實(shí)例,個別節(jié)點(diǎn)掛了還有別的可以用。
3)多級緩存:比如增加本地緩存,減小redis壓力。
4)對存儲層增加限流措施,當(dāng)請求超出限制,提供降級服務(wù)(一般就是返回錯誤即可)
緩存穿透: 一些惡意的請求會故意查詢redis不存在的key,請求量很大,就會對后端系統(tǒng)造成很大的壓力。
1:對查詢結(jié)果為空的情況也進(jìn)行緩存,這樣,再次訪問時,緩存層會直接返回空值。緩存時間設(shè)置短一點(diǎn),或者該key對應(yīng)的數(shù)據(jù)insert了之后清理緩存。
2:對一定不存在的key進(jìn)行過濾。具體請看布隆過濾器
?
解決Redis并發(fā)競爭Key問題
多個系統(tǒng)同時對一個 key 進(jìn)行操作,但是最后執(zhí)行的順序和我們期望的順序不同,這樣也就導(dǎo)致了結(jié)果的不同!
推薦一種方案:分布式鎖(zookeeper 和 redis 都可以實(shí)現(xiàn)分布式鎖)。(如果不存在 Redis 的并發(fā)競爭 Key 問題,不要使用分布式鎖,這樣會影響性能)
Redis緩存策略
1. volatile-lru:從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中挑選最近最少使用的數(shù)據(jù)淘汰
當(dāng)需要淘汰一個key時,隨機(jī)選擇3個key,淘汰其中間隔時間最長的key。**基本上淘汰key效果很好。后來隨機(jī)3個key改成一個配置項"N隨機(jī)key"。但把默認(rèn)值提高改成5個后效果大大提高。考慮到它的效果,你根本不用修改他。
版權(quán)聲明:本文為CSDN博主「一只大白兔兔兔兔兔」的原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:https://fantianzuo.blog.csdn.net/article/details/102580321
2. volatile-ttl:挑選將要過期的數(shù)據(jù)淘汰
3. volatile-random:任意選擇數(shù)據(jù)淘汰
4. allkeys-lru:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時,在鍵空間中,移除最近最少使用的key(這個是最常用的).
5. allkeys-random:從數(shù)據(jù)集(server.db[i].dict)中任意選擇數(shù)據(jù)淘汰
6. no-eviction:禁止驅(qū)逐數(shù)據(jù),也就是說當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時,新寫入操作會報錯。
哨兵
1)Sentinel(哨兵) 進(jìn)程用于監(jiān)控集群中 Master 主服務(wù)器工作的狀態(tài)
2)在主服務(wù)器故障時,可以實(shí)現(xiàn) Master 和 Slave 服務(wù)器切換,保證系統(tǒng)的高可用
工作方式
1)每個 Sentinel(哨兵)進(jìn)程每秒鐘一次向所有服務(wù)器和哨兵進(jìn)程發(fā)送一個 PING 命令。
2. 如果一個實(shí)例距離最后一次有效回復(fù) PING 命令的時間超過 down-after-milliseconds 選項所指定的值, 這個實(shí)例會被 Sentinel(哨兵)進(jìn)程標(biāo)記為主觀下線。
3. 如果一個 Master 主服務(wù)器被標(biāo)記為主觀下線,則正在監(jiān)視這個 Master 主服務(wù)器的所有 Sentinel(哨兵)進(jìn)程要以每秒一次的頻率確認(rèn) Master 主服務(wù)器的確進(jìn)入了主觀下線狀態(tài)。
4. 當(dāng)有足夠數(shù)量的 Sentinel(哨兵)進(jìn)程(大于等于配置文件指定的值)在指定的時間范圍內(nèi)確認(rèn) Master 主服務(wù)器進(jìn)入了主觀下線狀態(tài), 則Master 主服務(wù)器會被標(biāo)記為客觀下線(ODOWN)。
5. 在一般情況下, 每個 Sentinel(哨兵)進(jìn)程會以每 10 秒一次的頻率向集群中的所有Master 主服務(wù)器、Slave 從服務(wù)器發(fā)送 INFO 命令。
6. 當(dāng) Master 主服務(wù)器被 Sentinel(哨兵)進(jìn)程標(biāo)記為客觀下線時,Sentinel(哨兵)進(jìn)程向下線的 Master 主服務(wù)器的所有 Slave 從服務(wù)器發(fā)送 INFO 命令的頻率會從 10 秒一次改為每秒一次。
7. 若沒有足夠數(shù)量的 Sentinel(哨兵)進(jìn)程同意 Master 主服務(wù)器下線, Master 主服務(wù)器的客觀下線狀態(tài)就會被移除。若 Master 主服務(wù)器重新向 Sentinel(哨兵)進(jìn)程發(fā)送 PING 命令返回有效回復(fù),Master 主服務(wù)器的主觀下線狀態(tài)就會被移除。
解決會話
(粘性session)
你可以設(shè)置nginx的分配策略,下次同一個還讓同一個服務(wù)器來處理
但是很顯然,這就和分布式和nginx初衷違背了:負(fù)載很難保證均衡。
(同步session)
一臺服務(wù)器的session給所有服務(wù)器復(fù)制一份
第一,性能不好。第二,產(chǎn)生了一定的耦合
(專門session)
專門一臺服務(wù)器來解決,存session,其它服務(wù)器來這個服務(wù)器取session再用。
但是也有問題:你這個服務(wù)器掛了怎么辦?別的服務(wù)器都是依賴這個服務(wù)器工作的。我們分布式部署本來就是為了解決性能的瓶頸啊。
很容易想到,我們把那個處理session的服務(wù)器搞個集群:
更不行,想想就知道,本來就是為了解決分布式部署的問題,你把單獨(dú)解決session的服務(wù)器又搞集群,和之前有什么區(qū)別呢?還不如一個服務(wù)器存一份簡單呢。
可以,但是傳統(tǒng)的關(guān)系數(shù)據(jù)庫是存到硬盤里,速度太慢。
(nosql)
最終,我們的主流辦法使用nosql數(shù)據(jù)庫,比如redis,來解決這個問題的。
sqlsqlsqlsqllsqlslqslqslq數(shù)據(jù)類型
整數(shù):1byte是tinyint 2 smallint 3mediumint 4int 8bigint
浮點(diǎn)數(shù):float/double/real。定義方式為:FLOAT(M,D)一共顯示M位其中D位小數(shù)點(diǎn)后面
Bit:MySQL把BIT當(dāng)做字符串類型,而非數(shù)字類型。
字符串:CHAR、VARCHAR、BINARY、VARBINARY、BLOB、TEXT、ENUM和SET。
Char固定長度,varchar隨便,BINARY、VARBINARY是二進(jìn)制,BLOB、TEXT是大的
mysql特點(diǎn)
1.數(shù)據(jù)以表格的形式出現(xiàn)
2.每行為各種記錄名稱
3.每列為記錄名稱所對應(yīng)的數(shù)據(jù)域
4.許多的行和列組成一張表單
5.若干的表單組成database
注:保證數(shù)據(jù)一致性。
注:關(guān)系型數(shù)據(jù)庫,表與表之間存在對應(yīng)關(guān)系。
注:非關(guān)系行數(shù)據(jù)庫,表之間不存在關(guān)系,數(shù)據(jù)獨(dú)立,隨便存。
存儲引擎
如果要提供提交、回滾和恢復(fù)的事務(wù)安全(ACID 兼容)能力,并要求實(shí)現(xiàn)并發(fā)控制,InnoDB
如果數(shù)據(jù)表主要用來插入和查詢記錄,則 MyISAM 引擎提供較高的處理效率。
如果只是臨時存放數(shù)據(jù),數(shù)據(jù)量不大,并且不需要較高的數(shù)據(jù)安全性,在內(nèi)存的 MEMORY ,MySQL 中使用該引擎作為臨時表,存放查詢的中間結(jié)果。
增刪改
贈
--1. 表名后沒有指定屬性列:插入一條完整的
insert into student values ('201215128', '陳東', '18', '男', 'IS');
--2. 在表明后指定要插入數(shù)據(jù)的表名及屬性列,屬性列的順序可與表定義中的順序不一致
insert into student(sno, sname, sage, ssex, sdept)
values ('201215138', '陳東棟', '18', '男', 'CS');
--3. 插入部分列,未顯示給出的列按空值計算,當(dāng)然前提條件是那些列可以為空值
insert into student(sno, sname) values ('201215148', '陳棟');
改
--1. 修改某些符合where子句中的條件的元組的值
update student set sage = 92 where sno = '200215121';
--2. where子句缺省,默認(rèn)修改所有元組的該屬性的值
--3. 帶子查詢的修改
update sc set grade = 100
where 'CS' in (select sdeptfrom studentwhere sc.sno = student.sno);
刪
Delete from student where sno = '201215148';
范式
最重要的好處歸結(jié)為三點(diǎn):
1.減少數(shù)據(jù)冗余(這是最主要的好處,其他好處都是由此而附帶的)
2.消除異常(插入異常,更新異常,刪除異常)
3.讓數(shù)據(jù)組織的更加和諧
第一范式 – 每表中的字段不可再分割
第二范式 – 要有主鍵,并且其他字段依賴于主鍵(方便唯一確定數(shù)據(jù)行,并定位其他字段)
第三范式 – 在二范式的基礎(chǔ)上,消除傳遞依賴,各種信息只在一個地方存儲,不出現(xiàn)在多張表中。比如學(xué)生信息表的學(xué)號為主鍵,此時要查看他所在的系信息,那么不能直接將系信息存儲在學(xué)生信息表中,因為系信息已經(jīng)在系別管理表中,此時只能在學(xué)生信息中添加系別管理表中的系編號字段(主鍵),這樣既能根據(jù)系編號找到系別信息,又避免了冗余存儲的問題。
BC范式 – 每個表中只有一個候選鍵(唯一標(biāo)識表中每一行的鍵,候選鍵可以包含多個屬性)
?
優(yōu)化:
1)1對N關(guān)系中復(fù)制非鍵屬性以減少連接
例:查詢學(xué)生以及所在學(xué)院名,可以在學(xué)生表中不僅存儲學(xué)院id,并且存儲學(xué)院名
維護(hù)時:
如果在UI中,只允許用戶進(jìn)行選擇,不能自行輸入,保證輸入一致性
如果是程序員,對于類似學(xué)院名這種一般不變的代碼表,在修改時直接對兩張表都進(jìn)行修改;如果經(jīng)常變化,則可以加一個觸發(fā)器。
2)N對N關(guān)系中復(fù)制屬性,把兩張表中經(jīng)常需要的內(nèi)容復(fù)制到中間關(guān)系表中以減少連接
索引優(yōu)缺點(diǎn)和使用原則
優(yōu)點(diǎn):
1、所有的MySql列類型(字段類型)都可以被索引,也就是可以給任意字段設(shè)置索引
2、大大加快數(shù)據(jù)的查詢速度
缺點(diǎn):
1、創(chuàng)建索引和維護(hù)索引要耗費(fèi)時間,并且隨著數(shù)據(jù)量的增加所耗費(fèi)的時間也會增加
2、占空間,數(shù)據(jù)也會有最大上線設(shè)置的,大量索引文件可能比數(shù)據(jù)文件更快到上線
3、對表中的數(shù)據(jù)進(jìn)行增加刪改時,索引也需要動態(tài)的維護(hù),降低了數(shù)據(jù)的維護(hù)速度。
使用原則:
1、對經(jīng)常更新的表避免過多索引,對經(jīng)常用于查詢的字段應(yīng)建索引,
2、數(shù)據(jù)量小的表最好不要使用索引,可能查詢?nèi)繑?shù)據(jù)比遍歷索引的時間還要短,
3、在值少的列上不要建立索引,比如"性別"字段上只有男女。
3-5、盡量選擇區(qū)分度高的列,公式是count(distinct col)/count(*),表示字段不重復(fù)的比例,比例越大我們掃描的記錄數(shù)越少
唯一鍵的區(qū)分度是1,而一些狀態(tài)、性別字段可能在大數(shù)據(jù)面前區(qū)分度就是0
一般需要join的字段我們都要求是0.1以上,即平均1條掃描10條記錄
4、最左前綴匹配原則,非常重要的原則,mysql會一直向右匹配直到遇到范圍查詢(>、<、between、like)就停止匹配
5、索引列不能參與計算,比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因:存的都是數(shù)據(jù)表中的字段值,需要所有元素都應(yīng)用函數(shù)才能比較
索引分類
普通索引
(1)直接創(chuàng)建索引CREATE INDEX index_name ON table(column(length))?
(3)創(chuàng)建表的時候同時創(chuàng)建索引
CREATE TABLE `table` (
??? `id` int(11) NOT NULL AUTO_INCREMENT ,
??? `title` char(255) CHARACTER NOT NULL ,
??? PRIMARY KEY (`id`),
??? INDEX index_name (title(length))
)
唯一索引
列的值必須唯一,但允許有空值。如果是組合索引,則列值的組合必須唯一。
(1)創(chuàng)建唯一索引CREATE UNIQUE INDEX indexName ON table(column(length))
(3)創(chuàng)建表的時候直接指定
主鍵索引
特殊的唯一索引,一個表只能有一個主鍵,不允許有空值。建表同時創(chuàng)建主鍵索引:
CREATE TABLE `table` (
??? `id` int(11) NOT NULL AUTO_INCREMENT ,
??? `title` char(255) NOT NULL ,
??? PRIMARY KEY (`id`)
);
組合索引
指多個字段上創(chuàng)建的索引,只有在查詢條件中使用了創(chuàng)建索引時的第一個字段,索引才會被使用。使用組合索引時遵循最左前綴集合
ALTER TABLE `table` ADD INDEX name_city_age (name,city,age);
全文索引
主要用來查找文本中的關(guān)鍵字,而不是直接與索引中的值相比較。fulltext索引跟其它索引大不相同,它更像是一個搜索引擎,而不是簡單的where語句的參數(shù)匹配。
fulltext索引配合match against操作使用,而不是一般的where語句加like。
它可以在create table,alter table ,create index使用,不過目前只有char、varchar,text 列上可以創(chuàng)建全文索引。
在數(shù)據(jù)量較大時候,現(xiàn)將數(shù)據(jù)放入一個沒有全局索引的表中,然后再用CREATE index創(chuàng)建fulltext索引,要比先為一張表建立fulltext然后再將數(shù)據(jù)寫入的速度快很多。
CREATE TABLE `table` (
??? `id` int(11) NOT NULL AUTO_INCREMENT ,
??? `content` text CHARACTER NULL ,
??? PRIMARY KEY (`id`),
??? FULLTEXT (content)
);
聚集索引的葉子節(jié)點(diǎn)存儲行記錄,因此, InnoDB必須要有,且只有一個聚集索引:
(1)如果表定義了PK,則PK就是聚集索引;
(2)如果表沒有定義PK,則第一個not NULL unique列是聚集索引;
(3)否則,InnoDB會創(chuàng)建一個隱藏的row-id作為聚集索引;
所以PK查詢非常快,直接定位行記錄。
回表:查到一個字段,再去找對應(yīng)行
5.6優(yōu)化,索引下推:a=1 and b like….and c like…..,之前按查出第一個條件,返回數(shù)據(jù),再篩,現(xiàn)在按索引篩完了like再返回查。
索引區(qū)別
哈希索引適合等值查詢,但是無法范圍查詢
哈希索引沒辦法利用索引完成排序
哈希索引不支持多列聯(lián)合索引的最左匹配規(guī)則
如果有大量重復(fù)鍵值的情況下,哈希索引的效率會很低,因為存在哈希碰撞問題
?
局部性原理:當(dāng)一個數(shù)據(jù)被用到時,其附近的數(shù)據(jù)也通常會馬上被使用——程序運(yùn)行期間所需要的數(shù)據(jù)通常比較集中。磁盤順序讀取的效率很高(不需尋道時間,只需很少的旋轉(zhuǎn)時間)
預(yù)讀的長度一般為頁的整倍數(shù)。
在經(jīng)典B+Tree的基礎(chǔ)上進(jìn)行了優(yōu)化,增加了順序訪問指針。
平衡樹缺點(diǎn):深、離得遠(yuǎn)、
?
B和b+:只存索引,值全在葉子節(jié)點(diǎn)。
默認(rèn)16KB一頁,1行數(shù)據(jù)1K,一頁16條數(shù)據(jù)。假設(shè) bigint=8字節(jié),指針在innodb是6字節(jié),8+6=14,16KB/14=1170,三層可滿足千萬記錄。
事務(wù)特性:ACID
原子性(Atomicity):事務(wù)作為一個整體執(zhí)行,對數(shù)據(jù)的操作要么全被執(zhí)行,要么都不執(zhí)行。
一致性(Consistency):事務(wù)應(yīng)確保數(shù)據(jù)庫的狀態(tài)從一個一致狀態(tài)轉(zhuǎn)變?yōu)榱硪粋€一致狀態(tài)。一致狀態(tài)的含義是數(shù)據(jù)庫中的數(shù)據(jù)應(yīng)滿足完整性約束(約定好的約束,如:AB相互轉(zhuǎn)錢,A有500,B也有500,那么無論他們怎么轉(zhuǎn),最后的數(shù)額總數(shù)都會是1000)
隔離性(Isolation):多個事務(wù)并發(fā)執(zhí)行時,不互相影響。
持久性(Durability):一個事務(wù)一旦提交,他對數(shù)據(jù)庫的修改應(yīng)該永久保存在數(shù)據(jù)庫中。
并發(fā)錯誤
第一類丟失更新:某一個事務(wù)的回滾,導(dǎo)致另外一個事務(wù)已更新的數(shù)據(jù)丟失了。
第二類丟失更新:某一個事務(wù)的提交,導(dǎo)致另外一個事務(wù)已更新的數(shù)據(jù)丟失了。
臟讀:另一個事務(wù)修改了,但尚未提交,本事務(wù)讀到未被提交的數(shù)據(jù)。
不可重復(fù)讀:一個執(zhí)行中,另一個更新,本事務(wù)先后讀到的數(shù)據(jù)不一致。
幻讀:某事務(wù)對同一個表前后查詢到的行數(shù)不一致(中間有人插入)
幻讀解決辦法:使用串行化讀的隔離級別
隔離級別
- Read Uncommitted:讀取未提交的數(shù)據(jù)。事務(wù)可以讀取到另一個事務(wù)未提交的修改。
- Read Committed:(有的庫默認(rèn))讀取已提交的數(shù)據(jù)。事務(wù)只能讀取另個事務(wù)已提交修改。
- Repeatable Read:(mysql默認(rèn))可重復(fù)讀。同一個事務(wù)多次讀取相同數(shù)據(jù)返回結(jié)果一樣。
- Serializable:串行化 事務(wù)串行執(zhí)行。讀寫數(shù)據(jù)鎖表
1、讀提交時,寫數(shù)據(jù)只會鎖住相應(yīng)的行
2、可重復(fù)讀時,如果檢索條件有索引(包括主鍵索引),默認(rèn)加鎖方式是next-key 鎖;如果檢索條件沒有索引,更新數(shù)據(jù)鎖住整張表。一個間隙被事務(wù)加了鎖,其他事務(wù)是不能在這個間隙插入記錄的,這樣可以防止幻讀。
是否會發(fā)生
并發(fā)控制技術(shù)(鎖)
封鎖(locking)(主要使用的)
基本的封鎖類型有兩種:排它鎖(X鎖,exclusive locks)、共享鎖(S 鎖,share locks)
排它鎖又稱寫鎖,對A加排它鎖之后,其他事務(wù)不能對A加 任何類型的鎖(排斥讀和寫)
共享鎖又稱讀鎖,對A加共享鎖之后,其他事務(wù)只能對A加S鎖,不能加X鎖(只排斥寫)
表級鎖:開銷小,加鎖快、鎖沖突概率高、不死鎖。
行級鎖:mysql默認(rèn),開銷大、加鎖慢、鎖沖突概率高、可能死鎖
間隙鎖(NK):行級,使用范圍條件時,對范圍內(nèi)不存在的記錄加鎖。一是為了防止幻讀,二是為了滿足恢復(fù)和復(fù)制的需要
增加行級鎖之前,InnoDB會自動給表加意向鎖;
? 執(zhí)行增刪改語句時,InnoDB會自動給數(shù)據(jù)加排他鎖;
? 執(zhí)行查詢語句時
共享鎖(S):SELECT … FROM … WHERE … LOCK IN SHARE MODE;
排他鎖(X):SELECT … FROM … WHERE … FOR UPDATE;
間隙鎖(NK):上述SQL采用范圍條件時,InnoDB對不存在的記錄自動增加間隙鎖。
?
MVCC的實(shí)現(xiàn),是通過保存數(shù)據(jù)在某個時間點(diǎn)的快照來實(shí)現(xiàn)的。也就是說,不管需要執(zhí)行多長時間,每個事務(wù)看到的數(shù)據(jù)是一致的。根據(jù)事務(wù)開始的時間不同,每個事物對同一張表,同一時刻看到的數(shù)據(jù)可能是不一樣的。
否則就更新數(shù)據(jù)(版本號+1)。
悲觀鎖:假定會發(fā)生并發(fā)沖突,屏蔽一切可能違反數(shù)據(jù)完整性的操作。
樂觀鎖:假設(shè)不會發(fā)生并發(fā)沖突,只在提交操作時檢查是否違反數(shù)據(jù)完整性。
使用數(shù)據(jù)版本(Version)記錄機(jī)制實(shí)現(xiàn)
樂觀鎖(自定義)
- 版本號、時間戳等。。。在更新數(shù)據(jù)前,檢查版本號是否發(fā)生變化。若變化則取消本次更新,
1. 版本號機(jī)制
UPDATE … SET …,VERSION=#{version+1} WHERE … AND VERSION=${version}
2. CAS算法(Compare and swap)
是一種無鎖的算法,該算法涉及3個操作數(shù)(內(nèi)存值V、舊值A(chǔ)、新值B),當(dāng)V等于A時,
采用原子方式用B的值更新V的值。該算法通常采用自旋操作,也叫自旋鎖。它的缺點(diǎn)是:
? ABA問題:某線程將A該為B,再改回A,則CAS會誤認(rèn)為A沒被修改過。
? 自旋操作采用循環(huán)的方式實(shí)現(xiàn),若加鎖時間長,則會給CPU帶來巨大的開銷。
? CAS只能保證一個共享變量的原子操作。
?
死鎖
? 場景
事務(wù)1: UPDATE T SET … WHERE ID = 1; UPDATE T SET … WHERE ID = 2;
事務(wù)2: UPDATE T SET … WHERE ID = 2; UPDATE T SET … WHERE ID = 1;
? 解決方案
1. 一般InnoDB會自動檢測到,并使一個事務(wù)回滾,另一個事務(wù)繼續(xù);
2. 設(shè)置超時等待參數(shù) innodb_lock_wait_timeout;
? 避免死鎖
1. 不同的業(yè)務(wù)并發(fā)訪問多個表時,應(yīng)約定以相同的順序來訪問這些表;
2. 以批量的方式處理數(shù)據(jù)時,應(yīng)事先對數(shù)據(jù)排序,保證線程按固定的順序來處理數(shù)據(jù);
3. 在事務(wù)中,如果要更新記錄,應(yīng)直接申請足夠級別的鎖,即排他鎖;
分布式事務(wù)
分布式事務(wù)就是指事務(wù)的參與者、支持事務(wù)的服務(wù)器、資源服務(wù)器以及事務(wù)管理器分別位于不同的分布式系統(tǒng)的不同節(jié)點(diǎn)之上。
分布式事務(wù)產(chǎn)生的原因總的來說有兩個:1.service產(chǎn)生多個節(jié)點(diǎn)2.resource產(chǎn)生多個節(jié)點(diǎn)
CAP理論
- C (一致性):對某個指定的客戶端來說,讀操作能返回最新的寫操作。
對于數(shù)據(jù)分布在不同節(jié)點(diǎn)上的數(shù)據(jù)來說,如果在某個節(jié)點(diǎn)更新了數(shù)據(jù),那么在其他節(jié)點(diǎn)如果都能讀取到這個最新的數(shù)據(jù),那么就稱為強(qiáng)一致,如果有某個節(jié)點(diǎn)沒有讀取到,那就是分布式不一致。
A (可用性):非故障的節(jié)點(diǎn)在合理的時間內(nèi)返回合理的響應(yīng)(不是錯誤和超時的響應(yīng))。
可用性的兩個關(guān)鍵一個是合理的時間,一個是合理的響應(yīng)。合理的時間指的是請求不能無限被阻塞,應(yīng)該在合理的時間給出返回。合理的響應(yīng)指的是系統(tǒng)應(yīng)該明確返回結(jié)果并且結(jié)果是正確的,這里的正確指的是比如應(yīng)該返回50,而不是返回40。
P (分區(qū)容錯性):當(dāng)出現(xiàn)網(wǎng)絡(luò)分區(qū)后,系統(tǒng)能夠繼續(xù)工作。打個比方,這里個集群有多臺機(jī)器,有臺機(jī)器網(wǎng)絡(luò)出現(xiàn)了問題,但是這個集群仍然可以正常工作。
熟悉CAP的人都知道,三者不能共有,如果感興趣可以搜索CAP的證明,在分布式系統(tǒng)中,網(wǎng)絡(luò)無法100%可靠,分區(qū)其實(shí)是一個必然現(xiàn)象,如果我們選擇了CA而放棄了P,那么當(dāng)發(fā)生分區(qū)現(xiàn)象時,為了保證一致性,這個時候必須拒絕請求,但是A又不允許,所以分布式系統(tǒng)理論上不可能選擇CA架構(gòu),只能選擇CP或者AP架構(gòu)。
分布式事務(wù)常見的方案:2PC(兩階段提交)協(xié)調(diào)者(都成功才執(zhí)行)
運(yùn)行過程:
準(zhǔn)備階段(協(xié)調(diào)者詢問參與者事務(wù)是否執(zhí)行成功,參與者發(fā)回事務(wù)執(zhí)行結(jié)果)
提交階段(如果事務(wù)在每個參與者上都執(zhí)行成功,事務(wù)協(xié)調(diào)者發(fā)送通知讓參與者提交事務(wù);否則,協(xié)調(diào)者發(fā)送通知讓參與者回滾事務(wù))
需要注意的是,在準(zhǔn)備階段,參與者執(zhí)行了事務(wù),但是還未提交。只有在提交階段接收到協(xié)調(diào)者發(fā)來的通知后,才進(jìn)行提交或者回滾。
存在問題:
1.同步阻塞:所有事務(wù)參與者在等待其它參與者時都阻塞。
2.單點(diǎn)問題:協(xié)調(diào)者在 2PC 中起到非常大的作用,發(fā)生故障將會造成很大影響。
3.數(shù)據(jù)不一致:網(wǎng)絡(luò)異常,只有部分參與者接收到 Commit 消息,只有部分參與者提交了事務(wù),使得系統(tǒng)數(shù)據(jù)不一致。
?
SQL語句性能優(yōu)化
1.??? 對查詢進(jìn)行優(yōu)化,應(yīng)盡量避免全表掃描,首先應(yīng)考慮在 where 及 order by 涉及的列上建立索引。
2.??? 應(yīng)盡量避免在 where 子句中使用!=或<>操作符, MySQL只有對以下操作符才使用索引:<,<=,=,>,>=,BETWEEN,IN,以及某些時候的LIKE。
3.??? 應(yīng)盡量避免在 where 子句中使用 or 來連接條件, 否則將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描,可以使用UNION合并查詢
4.??? 盡可能的使用 varchar代替 char, 因為首先變長字段存儲空間小,可以節(jié)省存儲空間,其次對于查詢來說,在一個相對較小的字段內(nèi)搜索效率顯然要高些。
索引的優(yōu)化
1.避免有索引但未被用到的情況:(1) Like的參數(shù)以通配符開頭時(2)where條件不符合最左前綴原則時(3)使用!= 或 <> 操作符時(4)使用or來連接條件
2.避免使用 Select *
3.對order by語句進(jìn)行優(yōu)化:1.重寫order by語句以使用索引 2.避免在order by子句中使用表達(dá)式。
4.對group by語句進(jìn)行優(yōu)化:將不需要的記錄在group by之前過濾掉
5.使用exists代替in
分庫分表
垂直分庫就是根據(jù)業(yè)務(wù)耦合性,將關(guān)聯(lián)度低的不同表存儲在不同的數(shù)據(jù)庫。
水平切分分為庫內(nèi)分表和分庫分表,是根據(jù)表內(nèi)數(shù)據(jù)內(nèi)在的邏輯關(guān)系,將同一個表按不同的條件分散到多個數(shù)據(jù)庫或多個表中,每個表中只包含一部分?jǐn)?shù)據(jù),從而使得單個表的數(shù)據(jù)量變小,達(dá)到分布式的效果。
Kafka
引入依賴- spring-kafka
? 配置Kafka- 配置server、consumer
? 訪問Kafka
- 生產(chǎn)者kafkaTemplate.send(topic, data);
- 消費(fèi)者@KafkaListener(topics = {"test"})
public void handleMessage(ConsumerRecord record) {}
一、Kafka的優(yōu)勢如下:
?????? 高吞吐量、低延遲:kafka每秒可以處理幾十萬條消息,它的延遲最低只有幾毫秒;
?????? 持久性、可靠性:消息被持久化到本地磁盤,并且支持?jǐn)?shù)據(jù)備份防止數(shù)據(jù)丟失;
?????? 容錯性:允許集群中節(jié)點(diǎn)故障(若副本數(shù)量為n,則允許n-1個節(jié)點(diǎn)故障);
?????? 高并發(fā):支持?jǐn)?shù)千個客戶端同時讀寫。
二、Kafka適合以下應(yīng)用場景:
?????? 日志收集:一個公司可以用Kafka可以收集各種服務(wù)的log,通過kafka以統(tǒng)一接口服務(wù)的方式開放給各種consumer;
?????? 消息系統(tǒng):解耦生產(chǎn)者和消費(fèi)者、緩存消息等;
??????? 用戶活動跟蹤:kafka經(jīng)常被用來記錄web用戶或者app用戶的各種活動,如瀏覽網(wǎng)頁、搜索、點(diǎn)擊等活動,這些活動信息被各個服務(wù)器發(fā)布到kafka的topic中,然后消費(fèi)者通過訂閱這些topic來做實(shí)時的監(jiān)控分析,亦可保存到數(shù)據(jù)庫;
?????? 運(yùn)營指標(biāo):kafka也經(jīng)常用來記錄運(yùn)營監(jiān)控數(shù)據(jù)。包括收集各種分布式應(yīng)用的數(shù)據(jù),生產(chǎn)各種操作的集中反饋,比如報警和報告;
?????? 流式處理:比如spark streaming和storm。
?
Broker:Kafka節(jié)點(diǎn),一個Kafka節(jié)點(diǎn)就是一個broker,多個broker可以組成一個Kafka集群。
Topic:一類消息,消息存放的目錄即主題,例如page view日志、click日志等都可以以topic的形式存在,Kafka集群能夠同時負(fù)責(zé)多個topic的分發(fā)。
Partition:topic物理上的分組,一個topic可以分為多個partition,每個partition是一個有序的隊列
Segment:partition物理上由多個segment組成,每個Segment存著message信息
Producer : 生產(chǎn)message發(fā)送到topic
Consumer : 訂閱topic消費(fèi)message, consumer作為一個線程來消費(fèi)
Consumer Group:一個Consumer Group包含多個consumer, 這個是預(yù)先在配置文件中配置好的。各個consumer可以組成一個組(group ),partition中的每個message只能被組中的一個consumer消費(fèi),如果message可以被多個consumer消費(fèi)的話,那么這些consumer必須在不同的組。
Kafka不支持一個partition中的message由兩個或兩個以上的consumer thread來處理,即便是來自不同的consumer group的也不行。它不能像AMQ那樣可以多個BET作為consumer去處理message,這是因為多個BET去消費(fèi)一個Queue中的數(shù)據(jù)的時候,由于要保證不能多個線程拿同一條message,所以就需要行級別悲觀所(for update),這就導(dǎo)致了consume的性能下降,吞吐量不夠。而kafka為了保證吞吐量,只允許一個consumer線程去訪問一個partition。如果覺得效率不高的時候,可以加partition的數(shù)量來橫向擴(kuò)展,那么再加新的consumer thread去消費(fèi)。這樣沒有鎖競爭,充分發(fā)揮了橫向的擴(kuò)展性,吞吐量極高。這也就形成了分布式消費(fèi)的概念。
import java.util.*;class BoundedBlockingQueue {private int size;private int capacity;private int[] queue;// 指向隊頭的指針private int last;??// 指向隊列尾private int first;// JDK 的通知模式private final ReentrantLock lock;private final Condition notFull;private final Condition notEmpty;public BoundedBlockingQueue(int capacity) {this.size = 0;this.last = 0;this.first = 0;this.capacity = capacity;this.queue = new int[capacity+1];this.lock = new ReentrantLock();notFull = this.lock.newCondition();notEmpty = this.lock.newCondition();}// 進(jìn)隊列public void enqueue(int element) throws InterruptedException {// 隊列不滿的情況下可以入隊尾,在隊尾添加元素final ReentrantLock lock = this.lock;lock.lockInterruptibly();try{while(size == capacity)notFull.await();// 隊列不滿的情況下可以入隊尾,在隊尾添加元素queue[last] = element;last = (last + 1)% capacity;size ++;notEmpty.signal();}finally{lock.unlock();}}// 出隊列public int dequeue() throws InterruptedException {// 有元素的情況下才可以出隊列,刪掉隊頭的元素final ReentrantLock lock = this.lock;lock.lockInterruptibly();try{while(size == 0)notEmpty.await();int e = queue[first];first = (first + 1) % capacity;size --;notFull.signal();return e;}finally{lock.unlock();}}public int size() {return size;}}Es
? 搜索服務(wù)
- 將帖子保存至Elasticsearch服務(wù)器。
- 從Elasticsearch服務(wù)器刪除帖子。
- 從Elasticsearch服務(wù)器搜索帖子。
? 發(fā)布事件
- 發(fā)布帖子時,將帖子異步的提交到Elasticsearch服務(wù)器。
- 增加評論時,將帖子異步的提交到Elasticsearch服務(wù)器。
- 在消費(fèi)組件中增加一個方法,消費(fèi)帖子發(fā)布事件。
? 顯示結(jié)果
- 在控制器中處理搜索請求,在HTML上顯示搜索結(jié)果。
?
ES:?????? index(索引)-->type(類型)-->document(文檔)-->field(字段)
mysql:?? database(數(shù)據(jù)庫)-->table(表)-->row(行)-->line(列)
ES是一個開源分布式搜索引擎。
同時ES是分布式文檔數(shù)據(jù)庫,每個字段均可被索引,而且每個字段的數(shù)據(jù)均可被搜索,能夠橫向擴(kuò)展至數(shù)以百計的服務(wù)器存儲以及處理PB級的數(shù)據(jù)。
可以在極短的時間內(nèi)存儲、搜索和分析大量的數(shù)據(jù)。通常作為具有復(fù)雜搜索場景情況下的核心發(fā)動機(jī)。
?
分別為每個field都建立了一個倒排索引,Kate, John, 24, Female這些叫term,而[1,2]就是Posting List。Posting list就是一個int的數(shù)組,存儲了所有符合某個term的文檔id。
?
內(nèi)存字典樹+硬盤倒排索引。
public class Trie {private boolean is_string=false;private Trie next[]=new Trie[26];public Trie(){}public void insert(String word){//插入單詞Trie root=this;char w[]=word.toCharArray();for(int i=0;i<w.length;++i){if(root.next[w[i]-'a']==null)root.next[w[i]-'a']=new Trie();root=root.next[w[i]-'a'];}root.is_string=true;}public boolean search(String word){//查找單詞Trie root=this;char w[]=word.toCharArray();for(int i=0;i<w.length;++i){if(root.next[w[i]-'a']==null)return false;root=root.next[w[i]-'a'];}return root.is_string;}public boolean startsWith(String prefix){//查找前綴Trie root=this;char p[]=prefix.toCharArray();for(int i=0;i<p.length;++i){if(root.next[p[i]-'a']==null)return false;root=root.next[p[i]-'a'];}return true;}}總結(jié)
以上是生活随笔為你收集整理的当年,兔子学姐靠这个面试小抄拿了个22k的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C# 静态类的使用
- 下一篇: n位数的全排列(需要考虑大数的情况)