以下內(nèi)容均來(lái)自于網(wǎng)絡(luò)摘抄(會(huì)給出原鏈接),沒(méi)有本人的思考,我是一個(gè)莫得感情的機(jī)器。
在IDEA里使用本地Tomcat部署的時(shí)候把a(bǔ)pplicationContext改成'/',要不然在頁(yè)面里寫(xiě)根目錄會(huì)跳出當(dāng)前war的目錄
通過(guò)注解的方式裝配bean:@Component("beanID")如果沒(méi)有id,默認(rèn)類的首字母小寫(xiě)作為這個(gè)bean的id
遇到了一個(gè)詭異的問(wèn)題(也沒(méi)有很詭異,下次注意吧):在沒(méi)有創(chuàng)建package的情況下,即使把Component和ComponentScan的類都放在同一文件夾,還是會(huì)報(bào)錯(cuò)。解決方法是建個(gè)package就行了。
Spring里Bean的作用域:
singleton, prototype, request, session
Spring里xml配置的Bean其實(shí)還有一個(gè)<scope>屬性,默認(rèn)是singleton,也就是說(shuō)Spring維護(hù)了一個(gè)單例(包括@Autowired產(chǎn)生的),這個(gè)單例不是線程安全的,需要自己維護(hù)。
@Autowired: 可以放在構(gòu)造函數(shù),方法和字段上實(shí)現(xiàn)自動(dòng)注入。默認(rèn)是byType,按類型注入。如果一個(gè)接口類型有多個(gè)實(shí)現(xiàn)類,會(huì)發(fā)生沖突,需要@Qualifier指定。如果沒(méi)有指定,會(huì)嘗試byName通過(guò)bean名字匹配
SpringMVC里有獲取Cookie值的簡(jiǎn)便方法(@CookieValue),但沒(méi)有設(shè)置Cookie的方法,所以還是用Servlet容器提供的HttpServletResponse.addCookie
配置數(shù)據(jù)庫(kù)連接池: apache的DBCP連接池BasicDataSource,阿里的Druid
mybatis
一級(jí)緩存(SqlSession級(jí)別):session內(nèi)的查詢結(jié)果會(huì)被緩存,當(dāng)執(zhí)行同樣的查詢語(yǔ)句時(shí)會(huì)直接返回緩存結(jié)果。這個(gè)緩存在session間不能共享。
二級(jí)緩存(SqlSessionFactory級(jí)別):在配置文件中添加<cache>開(kāi)啟,要求被緩存對(duì)象支持序列化。自定義的緩存器要實(shí)現(xiàn)???接口,例如Redis的實(shí)現(xiàn)???
思考:
客戶端收到了一個(gè)null,可能是什么原因?qū)е碌模?br>
首先假設(shè)傳輸是穩(wěn)定的,考慮服務(wù)器業(yè)務(wù)邏輯的問(wèn)題:
1. 可能是服務(wù)器收到了一個(gè)未知或者錯(cuò)誤的命令,沒(méi)有響應(yīng)
2. 服務(wù)器訪問(wèn)數(shù)據(jù)庫(kù)發(fā)現(xiàn)對(duì)應(yīng)的數(shù)據(jù)項(xiàng)不存在,返回空
3. 如果返回的數(shù)據(jù)形式是JSON,可能是在序列化的時(shí)候沒(méi)有把對(duì)應(yīng)的字段寫(xiě)進(jìn)去
再假設(shè)服務(wù)器邏輯沒(méi)有問(wèn)題,考慮傳輸?shù)脑颉H绻?wù)器和客戶端走的是UDP協(xié)議,那么可能是出現(xiàn)了丟包。如果走的是TCP協(xié)議,那么可能是因?yàn)榫W(wǎng)絡(luò)環(huán)境非常差,客戶端掉線,超過(guò)了TCP的重傳時(shí)間(一般是10分鐘左右,如果沒(méi)有數(shù)據(jù)傳輸?shù)脑捠?小時(shí),Keep-alive機(jī)制)
(注意TCP的Keep alive和HTTP的Keep alive是兩樣?xùn)|西)
思考:
分布式情況下怎么進(jìn)行session管理?
問(wèn)題描述:分布式情況下,session分布在不同的服務(wù)器中,同一個(gè)用戶的session第一次訪問(wèn)被存在了服務(wù)器A,第二次訪問(wèn)被映射到了服務(wù)器B,但這里沒(méi)有它的session,會(huì)被要求再次登錄。
答:
1. 利用應(yīng)用服務(wù)器自身(如tomcat)的session復(fù)制特性(不推薦,占內(nèi)存占帶寬)
2. 修改負(fù)載均衡模塊,每次都把同一個(gè)瀏覽器的同一個(gè)用戶映射到固定的服務(wù)器
3. 把用戶本身的信息存在Cookie中(不安全,且長(zhǎng)度有限制)(注意:參考JWT, Java web token)
4. 將session剝離出來(lái)單獨(dú)放在一個(gè)公用的存儲(chǔ)器里(如redis)。推薦的方式是結(jié)合spring session,通過(guò)一個(gè)SessionFilter將所有請(qǐng)求攔截,查詢r(jià)edis
思考
URL重寫(xiě),這個(gè)長(zhǎng)度有限制嗎?
HTTP報(bào)文構(gòu)成:

首先,HTTP協(xié)議對(duì)于請(qǐng)求行/頭/體的大小均沒(méi)有限制。但實(shí)際瀏覽器和服務(wù)器可能會(huì)對(duì)URL有長(zhǎng)度限制。例如Chrome: Chrome limits URLs to a maximum length of 2MB for practical reasons and to avoid causing denial-of-service problems in inter-process communication
引申:URL與URI的關(guān)系:URL(locator)是URI(identifier)的子集,地址是識(shí)別身份的信息之一

Spring Bean的初始化過(guò)程???
SpringMVC里一個(gè)請(qǐng)求的經(jīng)過(guò)的流程

ArrayList默認(rèn)初始容量10,超出容量會(huì)擴(kuò)大原始容量*0.5+1,(10+10*0.5+1=16),線程安全的版本是table和vector
HashMap使用節(jié)點(diǎn)數(shù)組+鏈表實(shí)現(xiàn),默認(rèn)初始容量16, 裝載因子0.75,超過(guò)0.75*16=12容量擴(kuò)大為原來(lái)的兩倍。在1.8中進(jìn)行了優(yōu)化,當(dāng)鏈表長(zhǎng)度大于8時(shí),改用紅黑樹(shù)
ConcurrentHashMap在數(shù)據(jù)結(jié)構(gòu)上也作了同樣的改進(jìn),同時(shí)
類加載
垃圾回收
在jvm里 majorGC等同于 fullGC
線程的6種狀態(tài)
Java線程有6種狀態(tài),如圖:
注意Thread.sleep()與Object.wait()都會(huì)讓出cpu,但前者不會(huì)釋放鎖。另外Thread.yield()就是讓出時(shí)間片,在1.7?的實(shí)現(xiàn)中,等同于Thead.sleep(0)。
修正了一個(gè)誤區(qū)(重要!):synchronzied修飾一般方法等價(jià)于 synchronized(this),鎖的是類實(shí)例。只有在修飾靜態(tài)方法的時(shí)候才鎖的是class
synchronized原理:
1. 對(duì)于同步代碼塊,通過(guò)monitorenter/exit指令進(jìn)入退出監(jiān)視器(Monitor)對(duì)象實(shí)現(xiàn)(javap看class文件可以看出來(lái))
2. 對(duì)于同步方法,通過(guò)讀取運(yùn)行時(shí)常量池里方法的ACC_SYNCHRONIZED標(biāo)志來(lái)實(shí)現(xiàn)???
對(duì)象頭與Monitor對(duì)象:
java對(duì)象在內(nèi)存中的布局分為三塊區(qū)域:對(duì)象頭、實(shí)例變量和對(duì)齊填充。對(duì)象頭包含 MarkWord 和 ClassMetadataAddress,其中MarkWord在32位jvm下的構(gòu)造如圖:

其中重量級(jí)鎖也就是通常意義上的synchronized鎖,可以看到每個(gè)對(duì)象都會(huì)有一個(gè)監(jiān)視器對(duì)象與之關(guān)聯(lián),在HotSpot虛擬機(jī)中,監(jiān)視器的實(shí)現(xiàn)是
MonitorObject。Java1.6針對(duì)重量級(jí)鎖進(jìn)行了優(yōu)化:
偏向鎖:基于“大部分情況下鎖都是由同一線程獲得”的經(jīng)驗(yàn),MarkWord里會(huì)記錄線程的ID,當(dāng)這個(gè)線程再次獲取時(shí),無(wú)需同步操作即獲取到鎖。若失敗,則升級(jí)為輕量級(jí)鎖。
輕量級(jí)鎖:基于“對(duì)絕大部分的鎖,在整個(gè)同步周期內(nèi)都不存在競(jìng)爭(zhēng)”的經(jīng)驗(yàn),也就是說(shuō)適用于線程交替訪問(wèn)同步塊,不在同一時(shí)間競(jìng)爭(zhēng)同一個(gè)鎖的場(chǎng)景。
自旋鎖:若獲取輕量級(jí)鎖也失敗,為了避免線程在操作系統(tǒng)層面掛起[1][2],會(huì)再嘗試循環(huán)等待一小段時(shí)間的方式(自旋)獲取鎖。這是基于“大部分情況下線程持有鎖的時(shí)間都不會(huì)太長(zhǎng)”的經(jīng)驗(yàn)。如果嘗試幾次都失敗,那么才會(huì)升級(jí)為重量級(jí)鎖。
[1]:Java線程與操作系統(tǒng)線程的關(guān)系:目前主流的虛擬機(jī)實(shí)現(xiàn)都把Java線程一一映射到操作系統(tǒng)的線程上,把調(diào)度工作交給了操作系統(tǒng),jvm本身只是對(duì)這一過(guò)程作了包裝。
[2]:操作系統(tǒng)的線程切換涉及內(nèi)核態(tài)與用戶態(tài)的切換,耗時(shí)操作。
wait(), notify()與notifyAll()
這幾個(gè)方法的調(diào)用需要先獲取到當(dāng)前對(duì)象的監(jiān)視器對(duì)象,否則會(huì)拋 IllegalMonitorState 異常,這也是為什么這些方法必須要放在synchronized方法或者代碼塊里。監(jiān)視器對(duì)象
MonitorObject包含幾個(gè)重要的屬性:_owner指向當(dāng)前持有該監(jiān)視器的線程;_WaitSet(雙向循環(huán)鏈表)等待隊(duì)列,用于存放wait的線程以及_EntryList同步隊(duì)列,用于存放阻塞獲取該對(duì)象鎖的線程。wait()方法實(shí)現(xiàn): 把當(dāng)前線程包裝成
ObjectWaiter->加入等待隊(duì)列->釋放當(dāng)前MonitorObject,最終操作系統(tǒng)park()掛起當(dāng)前線程notify()方法實(shí)現(xiàn):從
_WaitSet選一個(gè)線程(API文檔里說(shuō)隨機(jī)選擇,其實(shí)是選隊(duì)頭元素),放到_EntryListnotifyAll()方法實(shí)現(xiàn):將
_WaitSet隊(duì)列中的所有線程放到_EntryList注意 notify() 和notifyAll() 執(zhí)行后并不會(huì)立即釋放鎖,只有退出同步區(qū)域后鎖才被釋放。
實(shí)現(xiàn)生產(chǎn)者消費(fèi)者模型
- sychronized+wait+notifyAll:
調(diào)用wait和notify之前線程必須獲取到這個(gè)對(duì)象的監(jiān)視器鎖,也就是他們必須在同步方法或同步塊中使用,否則會(huì)拋IllegalMonitorStateException
兩個(gè)坑: 1. 不加while判斷而用if會(huì)超取/超放 2. 不用notifyAll而用notify會(huì)假死 - ReentrantLock+Condition
使用lock.lock/unlock()手動(dòng)加/釋放鎖,注意定義了兩個(gè)Condition,empty和full,分別用于通知生產(chǎn)者和消費(fèi)者。
遇到有面試官問(wèn)用第一種synchronzied實(shí)現(xiàn)有什么問(wèn)題,盲猜一波是性能上的。因?yàn)橹荒芸恳粋€(gè)對(duì)象通知,比如喚醒的時(shí)候不分生產(chǎn)者消費(fèi)者都喚醒了,加劇了競(jìng)爭(zhēng)。而用兩個(gè)Condition分別通知生產(chǎn)者消費(fèi)者會(huì)好一些。(我瞎猜的)
ReentrantLock原理???
ThreadLocal
http://www.jasongj.com/java/threadlocal/
CAS實(shí)現(xiàn)
CountdownLatch: 一個(gè)計(jì)數(shù)器,使用場(chǎng)景例如一個(gè)線程等待其他5個(gè)線程結(jié)束之后再執(zhí)行。那么可以new一個(gè)CountdownLatch(5),其他線程在執(zhí)行完之后加一句countdown(), 而這個(gè)線程在一開(kāi)始就await(), 當(dāng)計(jì)數(shù)減為0這個(gè)就被喚醒。
CyclicBarrier
線程池
Callable, Runnable: Callable有返回值,Runnable沒(méi)有
單個(gè)任務(wù)的提交:submit(Runnable / Callable), execute(Runnable), 其中submit會(huì)返回Future<T>,可以cancel(), isDone(), get()
多個(gè)任務(wù)提交并等待結(jié)束: invokeAll()
Netty具有異步和零拷貝兩種特性
- 關(guān)于異步:Netty是基于NIO實(shí)現(xiàn)的。NIO是IO多路復(fù)用,主要包含ByteBuffer, Channel, Selector,即所謂的Reactor模式。而異步是Proactor模式的,所以Netty為什么是異步這一點(diǎn)本人暫時(shí)還有疑問(wèn)(???)。實(shí)際上NIO2才是真正的異步,在Linux上基于AIO庫(kù)實(shí)現(xiàn)(Need ref),在Windows上基于IOCP實(shí)現(xiàn),但Netty從4.0起拋棄了NIO2的支持(NeedRef),理由是在linux上AIO的性能被證實(shí)不高(NeedRef);對(duì)于Windows則因?yàn)槠湓诜?wù)器領(lǐng)域可憐的份額而不考慮支持……
- 關(guān)于零拷貝:Netty的零拷貝和傳統(tǒng)意義上的零拷貝不太一樣,主要是針對(duì)數(shù)據(jù)邏輯操作上的。例如傳統(tǒng)方法要將兩個(gè)緩沖區(qū)的內(nèi)容合并,需要再分配一塊更大的緩沖區(qū),將兩個(gè)緩沖區(qū)的內(nèi)容拷貝進(jìn)來(lái)。但Netty的
ByteBuf可以通過(guò)包裝使得這兩個(gè)緩沖區(qū)在邏輯上是一個(gè),避免了復(fù)制的過(guò)程。
零拷貝
關(guān)于零拷貝,在OS層面通常指避免在用戶態(tài)(User-space) 與 內(nèi)核態(tài)(Kernel-space) 之間來(lái)回拷貝數(shù)據(jù),這里的兩張圖作了很形象的解釋。以一個(gè)讀取文件發(fā)送到網(wǎng)絡(luò)的例子說(shuō)明,傳統(tǒng)方法必須要先從硬盤(pán)讀取內(nèi)容到內(nèi)核態(tài)的緩沖區(qū),然后復(fù)制到用戶態(tài)的緩沖區(qū)進(jìn)行操作,隨后又復(fù)制到內(nèi)核態(tài)的socket寫(xiě)緩沖區(qū),這里涉及了兩次內(nèi)核態(tài)切換和兩次復(fù)制。使用零拷貝,讀取與拷貝直接在內(nèi)核態(tài)進(jìn)行,只需要一次復(fù)制。
零拷貝的一個(gè)真實(shí)例子是 Linux 提供的 mmap 系統(tǒng)調(diào)用, 它可以將一段用戶空間內(nèi)存映射到內(nèi)核空間, 當(dāng)映射成功后, 用戶對(duì)這段內(nèi)存區(qū)域的修改可以直接反映到內(nèi)核空間; 同樣地, 內(nèi)核空間對(duì)這段區(qū)域的修改也直接反映用戶空間. 正因?yàn)橛羞@樣的映射關(guān)系, 我們就不需要在用戶態(tài)與內(nèi)核態(tài)之間拷貝數(shù)據(jù), 提高了數(shù)據(jù)傳輸?shù)男?
Java input streams can support zero-copy through the java.nio.channels.FileChannel's transferTo() method if the underlying operating system also supports zero copy
Netty里的FileRegion包裝了FileChannel.tranferTo(),可以實(shí)現(xiàn)文件傳輸?shù)牧憧截?/p>
引申:nio.ByteBuffer.allocateDirect()在直接內(nèi)存分配緩沖區(qū),直接內(nèi)存是通過(guò)JNI調(diào)用malloc()分配的。如果不分配直接內(nèi)存,而調(diào)用allocate()在java堆分配,那么會(huì)發(fā)生內(nèi)核緩沖-->用戶緩沖--> Java堆緩沖兩次復(fù)制。
注意:看到這里可能需要區(qū)分一下堆內(nèi)存、直接內(nèi)存、用戶空間和內(nèi)核空間。Linux虛擬地址被劃分為內(nèi)核空間與用戶空間,出于安全考慮,不允許直接訪問(wèn)內(nèi)核空間。用戶空間對(duì)于Java虛擬機(jī)而言,在虛擬機(jī)之外的是直接內(nèi)存(需要C++ malloc),而堆內(nèi)存就是虛擬機(jī)內(nèi)的堆區(qū)域。
引申2:直接內(nèi)存的垃圾回收?
Linux五種IO模型
這篇文章講的很清楚,這里再簡(jiǎn)單總結(jié)一下:
網(wǎng)絡(luò)IO分為兩步:
1. 等待數(shù)據(jù)分組到達(dá),然后復(fù)制到內(nèi)核緩沖區(qū)
2. 將數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到用戶空間
同步IO模型:
1. 同步阻塞(recvfrom系統(tǒng)調(diào)用,阻塞直到兩步完成)
2. 同步非阻塞(在第一步recvfrom不斷輪詢,失敗立即返回,進(jìn)程做其他事情;若成功則阻塞等待第二步完成)
3. 多路復(fù)用IO(第一步select/poll/epoll系統(tǒng)調(diào)用阻塞住,輪詢多個(gè)連接,若有一個(gè)成功,調(diào)用recvfrom進(jìn)行第二步)
4. 信號(hào)驅(qū)動(dòng)式IO
異步IO模型:用戶進(jìn)程發(fā)出aio_read系統(tǒng)調(diào)用立即返回,轉(zhuǎn)而做其他事情,內(nèi)核在背后完成了兩步之后再通知用戶進(jìn)程
select, poll, epoll ???
select的幾大缺點(diǎn):
1. 每次調(diào)用select,都需要把fd集合從用戶態(tài)拷貝到內(nèi)核態(tài),這個(gè)開(kāi)銷在fd很多時(shí)會(huì)很大
2. 同時(shí)每次調(diào)用select都需要在內(nèi)核遍歷傳遞進(jìn)來(lái)的所有fd,這個(gè)開(kāi)銷在fd很多時(shí)也很大
3. select支持的文件描述符數(shù)量太小了,默認(rèn)是1024
三次握手:
SYN_SENT [SYN]
[SYN+ACK] SYN_RECVD (默認(rèn)重試五次)
ESTABLISHED [ACK]
ESTABLISHED
Syn flood
四次揮手
(FIN_WAIT_1) [FIN]
FIN_WAIT_2 [ACK] CLOSE_WAIT
[FIN] LAST_ACK
TIME_WAIT [ACK] CLOSED
CLOSED(2MSL)
等待兩個(gè)最大報(bào)文時(shí)長(zhǎng):
1. 防止服務(wù)器沒(méi)有收到ACK,服務(wù)器再次發(fā)送FIN,收到RST認(rèn)為連接錯(cuò)誤,違反可靠性要求
2. 確保殘余的數(shù)據(jù)包在網(wǎng)絡(luò)中被拋棄
UDP是無(wú)連接的,且沒(méi)有擁塞控制。對(duì)于應(yīng)用層下發(fā)的數(shù)據(jù)包,UDP既不拆分也不合并,添加上首部后即下發(fā)給IP層。如果分組太大IP層會(huì)進(jìn)行分片,如果太小則效率不高,所以使用UDP時(shí)需要注意應(yīng)用層發(fā)送數(shù)據(jù)的大小。一些實(shí)時(shí)應(yīng)用會(huì)對(duì)UDP的不可靠傳輸進(jìn)行適當(dāng)改進(jìn),減少數(shù)據(jù)包的丟失,例如前向糾錯(cuò)和重傳。
TCP
重傳時(shí)間 RTO:
RTT_s = (1-a)RTT_{old} + a·RTT_{new}, a = 1/8
RTT_D=(1-b)RTT_D_{old} + b·|RTT_s-RTT_{new}|,b = 1/4
RTO=RTT_s + 4·RTT_D
滑窗:連續(xù)ARQ,累積確認(rèn),Go-back-N。在TCP頭部附帶自己的接收窗口大小。零窗口的持續(xù)計(jì)時(shí)器,零窗口探測(cè)報(bào)文段(1字節(jié))。
針對(duì)小包的Nagle算法:先發(fā)一字節(jié),同時(shí)緩存后面的數(shù)據(jù)。收到ACK后,再把數(shù)據(jù)通過(guò)一個(gè)報(bào)文段發(fā)出去,同時(shí)繼續(xù)緩存,只有在收到前一個(gè)報(bào)文段的ACK后才發(fā)下一個(gè)報(bào)文段。(好像回退到了停等式ARQ?)這種方法在數(shù)據(jù)到達(dá)較快但網(wǎng)絡(luò)速率較慢的情況下能有效減少網(wǎng)絡(luò)帶寬。
糊涂窗口綜合征:接收方緩存消化的速率太慢,剛有了一點(diǎn)小空間就急著通知給發(fā)送方。解決方法:讓接收方等待一段時(shí)間,直到有一半的空閑空間或者能容納一個(gè)更大的報(bào)文段。
?;钣?jì)時(shí)器:每收到一次來(lái)自客戶端的數(shù)據(jù)就重置該計(jì)時(shí)器。超過(guò)兩小時(shí),發(fā)送一次探測(cè)報(bào)文段,之后每隔75秒發(fā)送一次,共計(jì)10次,若超過(guò)10次沒(méi)有響應(yīng)(可能客戶端故障或網(wǎng)絡(luò)狀況不佳),服務(wù)器就關(guān)閉連接
流量控制:通信雙方的傳輸速率可能是不一致的,例如一方發(fā)送的太快,另一方來(lái)不及接受。因此雙方在TCP頭部附帶自己的接受窗口大小,告訴對(duì)方自己最多能接受這么多字節(jié)。
擁塞控制:
流量控制與擁塞控制的區(qū)別:流量控制是針對(duì)端對(duì)端之間通信量的控制。擁塞控制是針對(duì)全局性的網(wǎng)絡(luò),防止過(guò)多的數(shù)據(jù)注入到網(wǎng)絡(luò)中,使網(wǎng)絡(luò)中的路由器和鏈路不至于過(guò)載。
擁塞控制方法:發(fā)送方維持一個(gè)擁塞窗口cwnd,并使自己的發(fā)送窗口等于擁塞窗口(考慮到前面的流量控制,實(shí)際上應(yīng)該是擁塞窗口和對(duì)方接收窗口的最小值)
-
慢開(kāi)始:初始時(shí)把
TCP擁塞窗口cwnd在擁塞控制時(shí)的變化情況
OSI各層的傳輸數(shù)據(jù)單元格式
每下降一層(除了物理層),都會(huì)在原數(shù)據(jù)包上添加新的控制頭
應(yīng)用層:[HTTP報(bào)文]
傳輸層:[TCP頭(20Bytes)/UDP頭(8Bytes)][HTTP報(bào)文]:TCP叫報(bào)文段(Segment),UDP叫數(shù)據(jù)報(bào)(Datagram)
網(wǎng)絡(luò)層:[IP頭(20Bytes)][TCP頭][HTTP報(bào)文]:叫IP分組/數(shù)據(jù)報(bào)
數(shù)據(jù)鏈路層:[控制頭][IP頭][TCP頭][HTTP報(bào)文][控制尾]:叫幀
數(shù)據(jù)鏈路層的幀有大小限制,叫MTU,以太網(wǎng)的MTU是1500字節(jié)。
傳輸層也有大小限制,TCP報(bào)文的長(zhǎng)度限制叫MSS,在建立連接的時(shí)候會(huì)協(xié)商取雙方的最小值(???)。實(shí)際上往往還是取決于MTU的大小,一般是1500-20(IP頭)-20(TCP頭)=1460Bytes
HTTP
HTTP1.0時(shí)代,HTTP對(duì)于每個(gè)請(qǐng)求都要新建連接,完成后立即斷開(kāi)連接。從HTTP1.1(RFC2616)開(kāi)始,默認(rèn)持久連接(Keep-Alive),簡(jiǎn)單說(shuō)就是不去主動(dòng)關(guān)閉TCP連接,讓后續(xù)請(qǐng)求都沿用已有的連接。Keep-Alive并不能保證客戶端和服務(wù)器的TCP連接是活躍的。
因?yàn)椴捎昧顺志眠B接,導(dǎo)致不能明確區(qū)分請(qǐng)求的結(jié)束標(biāo)志。HTTP使用兩種方法解決這個(gè)問(wèn)題:
(注意請(qǐng)求頭是不區(qū)分大小寫(xiě)的,所以這里也隨便大小寫(xiě)了)
1. Content-length:指明請(qǐng)求體的長(zhǎng)度,在這個(gè)長(zhǎng)度之后就結(jié)束了。缺點(diǎn)是需要提前知道整個(gè)請(qǐng)求體的大小
2.Transfer-Encoding: chunked,目前主流的規(guī)范里只定義了一種分塊編碼,通過(guò)0分塊標(biāo)志結(jié)束,其格式如下:
require('net').createServer(function(sock) {
sock.on('data', function(data) {
sock.write('HTTP/1.1 200 OK\r\n'); // 請(qǐng)求行
sock.write('Transfer-Encoding: chunked\r\n'); // 請(qǐng)求體
sock.write('\r\n');
sock.write('5\r\n'); // 第一個(gè)塊:5字節(jié)(不包括\r\n)
sock.write('abcde\r\n');
sock.write('0\r\n'); // 塊結(jié)束
sock.write('\r\n');
});
}).listen(9090, '127.0.0.1');
需要注意區(qū)分Transfer-encoding和Content-encoding:Content-encoding先對(duì)內(nèi)容壓縮編碼(例如gzip),然后Transfer-encoding指示傳輸分塊編碼。
狀態(tài)碼304應(yīng)用于條件Get:
條件Get是HTTP的緩存策略,用于減少不必要的網(wǎng)絡(luò)流量。當(dāng)客戶端第一次訪問(wèn)時(shí),服務(wù)器會(huì)在響應(yīng)頭附帶Last-Modified(日期)和Cache-control(緩存時(shí)長(zhǎng))。瀏覽器在Cache-control指定的時(shí)間內(nèi)會(huì)一直使用緩存而不用訪問(wèn)服務(wù)器。當(dāng)緩存超時(shí)后,客戶端再次訪問(wèn)該資源時(shí),會(huì)在請(qǐng)求頭附帶If-Modified-Since字段,服務(wù)器如果檢查發(fā)現(xiàn)沒(méi)有更改,那么會(huì)返回304 Not modified,客戶端可以繼續(xù)使用緩存。
HTTP2
二進(jìn)制分幀、多路復(fù)用(通過(guò)一條連接同時(shí)發(fā)送多個(gè)消息,每個(gè)消息分成多個(gè)幀亂序發(fā)送,最后根據(jù)數(shù)據(jù)流標(biāo)識(shí)符重新組裝)、頭部壓縮(服務(wù)端維護(hù)一個(gè)首部表,客戶端只需傳輸需要更新的首部信息)、服務(wù)端推送(主動(dòng)傳輸將來(lái)需要的資源)
SSL/TLS:
SSL1.0, 2.0, 3.0 均已被廢棄,TLS是其升級(jí)版本。SSL/TLS是應(yīng)用層和傳輸層之間的一種安全傳輸協(xié)議。SSL握手過(guò)程:
1. 客戶端發(fā)起請(qǐng)求,帶上自己生成的隨機(jī)數(shù)rand1和SSL版本、支持的加密算法
2. 服務(wù)端響應(yīng)請(qǐng)求,帶上自己生成的隨機(jī)數(shù)rand2并下發(fā)CA證書(shū), 證書(shū)中包含公鑰
3. 客戶端驗(yàn)證CA證書(shū),拿到公鑰,生成一個(gè)隨機(jī)數(shù)rand3并用公鑰加密發(fā)送給服務(wù)端
4. 服務(wù)端
SQL注入、XML注入、ReDos
RabbitMQ
ConnectionFactory, Channel
結(jié)構(gòu):生產(chǎn)者-->Exchange-->多個(gè)隊(duì)列-->多個(gè)消費(fèi)者
過(guò)程:生產(chǎn)者將消息發(fā)送到Exchange,Exchange把消息路由到相應(yīng)的隊(duì)列,由綁定這個(gè)隊(duì)列的消費(fèi)者取出執(zhí)行
一. 為了防止消費(fèi)者取出消息后沒(méi)執(zhí)行完就宕機(jī),造成消息丟失。要求消費(fèi)者處理完之后發(fā)送一個(gè)消息回執(zhí)給RMQ,RMQ在收到消息回執(zhí)之后才會(huì)刪除對(duì)應(yīng)的消息。
如果RMQ沒(méi)收到消息并且檢測(cè)到這個(gè)消費(fèi)者的連接斷開(kāi),就會(huì)把消息分給其他消費(fèi)者處理。注意只要連接不斷開(kāi),消息處理時(shí)間再長(zhǎng)也不會(huì)導(dǎo)致消息被發(fā)給其他消費(fèi)者。
二. 防止RMQ本身造成消息丟失:將隊(duì)列和消息設(shè)成可持久化的。更進(jìn)一步,可以使用事務(wù)。
三. 路由規(guī)則由ExchangeType和生產(chǎn)者發(fā)送消息附帶的routingKey與消費(fèi)者綁定隊(duì)列的bindingKey決定:
四種ExchangeType
- fanout:分配到所有隊(duì)列
- direct:分配到和routingKey完全匹配的bindingKey隊(duì)列
- topic:routingKey是.分隔的單詞,bingdingKey可以帶通配符(*匹配一個(gè)單詞,#匹配多個(gè)單詞)
- headers:根據(jù)消息內(nèi)容里headers屬性包含的鍵值對(duì)進(jìn)行匹配
四:RPC
RMQ本身是異步的消息處理。如果需要同步,即生產(chǎn)者等待消費(fèi)者處理完成再根據(jù)結(jié)果進(jìn)行下一步處理,相當(dāng)于RPC。
RMQ支持RPC的方式是:生產(chǎn)者發(fā)送的消息里附帶replyTo和correlationId。replyTo是一個(gè)隊(duì)列名稱,表示消息處理完后把通知發(fā)送到這個(gè)隊(duì)列;correlationId是請(qǐng)求的標(biāo)識(shí)號(hào),生產(chǎn)者據(jù)此判斷是哪個(gè)消息完成了。
SpringBoot集成RMQ
思考:
如下是一個(gè)學(xué)生成績(jī)數(shù)據(jù)表:1. 選出不及格科目多于兩門的學(xué)生 2.選出不及格科目多于兩門的學(xué)生的平均成績(jī)
create table if not exists t_grade (
id int auto_increment,
name varchar(20),
subject varchar(20),
grade int,
primary key(id));
select name from t_grade where grade<60 group by name having count(grade)>2-
select name, sum(grade<60) as numFailed, avg(grade) group by name having numFailed>2(注意這里的sum不能換成count)
注:
where不能使用聚合函數(shù)(avg, sum, ...)。
where用于過(guò)濾行,having用于過(guò)濾組(group by)。
tab1 left [outer] join tab2:返回左表所有行,即使在右表沒(méi)有匹配
tab1 right [outer] join tab2:返回右表所有行,即使在左表沒(méi)有匹配
[inner] join:求交
數(shù)據(jù)庫(kù)的四種范式

B樹(shù)(我終于也有b shu了)
性質(zhì):一個(gè)m階的B樹(shù)表示節(jié)點(diǎn)最多有m個(gè)子節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)最多有m-1個(gè)關(guān)鍵字,最少有ceil(m/2)-1個(gè)關(guān)鍵字(根節(jié)點(diǎn)可以最少有一個(gè)關(guān)鍵字),節(jié)點(diǎn)內(nèi)的關(guān)鍵字都是排好序的。葉節(jié)點(diǎn)都處于同一層。
B樹(shù)的特點(diǎn)是深度很小,這是為了減小磁盤(pán)尋道帶來(lái)的時(shí)間開(kāi)銷。
插入:超過(guò)m-1個(gè)關(guān)鍵字會(huì)發(fā)生分裂:中間關(guān)鍵字上浮到父節(jié)點(diǎn),然后左右分裂成兩個(gè)子樹(shù)。若父節(jié)點(diǎn)也超過(guò)m-1,那么對(duì)父節(jié)點(diǎn)重復(fù)這個(gè)過(guò)程。
刪除:如果是非葉節(jié)點(diǎn),那么用后繼節(jié)點(diǎn)覆蓋這個(gè)值,然后遞歸刪除后繼節(jié)點(diǎn),直到到達(dá)葉節(jié)點(diǎn)。如果葉節(jié)點(diǎn)刪除后小于ceil(m/2)-1個(gè)關(guān)鍵字:(1)如果兄弟節(jié)點(diǎn)大于ceil(m/2)-1,則父節(jié)點(diǎn)關(guān)鍵字下移,兄弟節(jié)點(diǎn)的一個(gè)關(guān)鍵字上浮 (2) 如果左右兄弟節(jié)點(diǎn)都沒(méi)有大于ceil(m/2)-1的,則父節(jié)點(diǎn)關(guān)鍵字下移,并且選一個(gè)左或右兄弟一起合并成新的節(jié)點(diǎn)。
B+樹(shù):
和B樹(shù)的主要區(qū)別在于:
1. 節(jié)點(diǎn)分為內(nèi)部節(jié)點(diǎn)和葉節(jié)點(diǎn)。內(nèi)部結(jié)點(diǎn)不保存數(shù)據(jù),而葉節(jié)點(diǎn)才存儲(chǔ)數(shù)據(jù)。
2. 葉節(jié)點(diǎn)之間通過(guò)指針形成一個(gè)有序鏈表
為什么要使用B+樹(shù)作為索引結(jié)構(gòu):數(shù)據(jù)庫(kù)的數(shù)據(jù)表和索引一般都很大,都是放在硬盤(pán)上的。而一次硬盤(pán)IO的時(shí)間(包括尋道時(shí)間(5ms左右)、旋轉(zhuǎn)延遲(7200轉(zhuǎn)約4ms)、傳輸時(shí)間(可忽略))很長(zhǎng),所以根據(jù)內(nèi)存局部性原理一次讀取多個(gè)連續(xù)的塊放到內(nèi)存頁(yè)中[1]。B+樹(shù)的每個(gè)節(jié)點(diǎn)對(duì)應(yīng)一個(gè)頁(yè)大小[NeedRef],相比于B樹(shù)把數(shù)據(jù)也保存在節(jié)點(diǎn)中,B+樹(shù)由于只存索引所以單節(jié)點(diǎn)保存的索引數(shù)量更多,減少了硬盤(pán)讀寫(xiě)的次數(shù)。
- 一個(gè)表有且僅有一個(gè)聚簇索引[2]。一般情況,用主鍵來(lái)作為聚簇索引。如果沒(méi)有定義主鍵,使用第一個(gè)
unique且not null的列來(lái)作為聚簇索引。否則,會(huì)內(nèi)部根據(jù)行ID值生成一個(gè)隱藏的聚簇索引GEN_CLUST_INDEX。
覆蓋索引
最左前綴匹配
explain執(zhí)行計(jì)劃
[1] Linux下默認(rèn)內(nèi)存頁(yè)大小是4k?
[2] 聚簇索引與非聚簇索引
數(shù)據(jù)庫(kù)事務(wù)特性(ACID)
1. 原子性:整個(gè)事務(wù)中的操作要么都完成,要么都不完成。發(fā)生錯(cuò)誤就會(huì)回滾成執(zhí)行前的狀態(tài),不會(huì)處在中間某個(gè)操作狀態(tài)
2. 一致性:數(shù)據(jù)庫(kù)在事務(wù)執(zhí)行前后都是一致的(AB轉(zhuǎn)賬,不管怎么轉(zhuǎn),賬戶總額都是一定的)
3. 隔離性(Isolation):多個(gè)事務(wù)操作同一張表時(shí),單個(gè)事務(wù)感受不到其他事務(wù)的存在。即從單個(gè)事務(wù)角度來(lái)看,其他事務(wù)要么已經(jīng)執(zhí)行完畢,要么還沒(méi)開(kāi)始。
4. 持久性(Durability):事務(wù)提交后,提示操作完成;數(shù)據(jù)庫(kù)一定會(huì)保證事務(wù)已經(jīng)成功執(zhí)行,即使數(shù)據(jù)庫(kù)出現(xiàn)了故障。
數(shù)據(jù)庫(kù)隔離級(jí)別
臟讀:一個(gè)事務(wù)讀取了另外一個(gè)事務(wù)還沒(méi)有提交的數(shù)據(jù)
不可重復(fù)讀:一個(gè)事務(wù)多次讀取同一數(shù)據(jù)卻得到了不同的結(jié)果,因?yàn)槠渌硞€(gè)事務(wù)在讀取間隔進(jìn)行了修改并提交。(與臟讀的區(qū)別是:臟讀是讀了其他事務(wù)沒(méi)提交的數(shù)據(jù),不可重復(fù)讀是讀其他事務(wù)已提交的數(shù)據(jù))
幻讀:一個(gè)事務(wù)批量修改了一些數(shù)據(jù),但另外一個(gè)事務(wù)在其中插入了一條歷史數(shù)據(jù),如果這個(gè)事務(wù)再去讀就會(huì)發(fā)現(xiàn)好像這條數(shù)據(jù)沒(méi)有被修改一樣(出現(xiàn)了幻覺(jué)?)
針對(duì)上述情況有四種隔離級(jí)別:
1. 讀未提交(Read uncommited): 什么也無(wú)法保證
2. 讀已提交(Read commited): 避免臟讀
3. 可重復(fù)讀(Repeatable read): 避免臟讀和不可重復(fù)讀
4. 串行化(Serializable): 串行執(zhí)行,避免所有問(wèn)題(效率也最差)
MySQL默認(rèn)隔離級(jí)別是可重復(fù)讀。InnoDB引擎在可重復(fù)讀級(jí)別可以通過(guò)MVCC解決幻讀???
redis單個(gè)命令都是原子的,因?yàn)閞edis是單線程的。但如果包含多個(gè)原子命令那就不是原子的了(廢話),結(jié)果可能會(huì)錯(cuò)誤,這還是因?yàn)榭蛻舳税l(fā)送命令時(shí)候線程切換導(dǎo)致的。
redis的高可用
單例模式
一個(gè)線程安全的單例(double check):
適配器模式
將一個(gè)類的接口轉(zhuǎn)換成客戶希望的另外一個(gè)接口。例如,假如我們看到這樣的接口:
public class ThinkpadComputer implements Computer {
@Override
public String readSD(SDCard sdCard) {
if(sdCard == null)throw new NullPointerException("sd card null");
return sdCard.readSD();
}
}
現(xiàn)在我們希望在不改變readSD(SDCard sdcard)這個(gè)接口形式的情況下,讀一個(gè)TFCard,怎么辦呢?
這個(gè)時(shí)候就需要適配器模式:使用方式是用一個(gè)Adapter去實(shí)現(xiàn)SDCard接口,內(nèi)部持有一個(gè)TFCard,readSD()的時(shí)候使用TFCard的實(shí)現(xiàn)就行了
代理模式
- 反轉(zhuǎn)棧,只允許使用常數(shù)個(gè)變量
兩次遞歸。先pop,再遞歸,然后push_bottom。 push_bottom也需要遞歸。
最小生成樹(shù)
并查集求解島嶼數(shù)量
兩個(gè)有序數(shù)組的中位數(shù)
1G大小的文件,每行一個(gè)單詞,每個(gè)單詞不超過(guò)16字節(jié)??捎脙?nèi)存1M,返回出現(xiàn)次數(shù)最多的100個(gè)單詞
首先把1G文件分成n個(gè)小文件(1G=1024M,取大一點(diǎn),比如2000吧),把每個(gè)單詞通過(guò)hash(word) % n映射過(guò)去,這樣就保證了同樣的單詞都在同一個(gè)文件里。然后遍歷,對(duì)每個(gè)文件建一個(gè)hashmap統(tǒng)計(jì)詞頻(如果不重復(fù)的單詞太多hashmap放不下,就再重復(fù)上述分的過(guò)程; 或者把n取大一點(diǎn)),得到n個(gè)詞頻文件。弄一個(gè)大小100的最小堆,讀入詞頻找出詞頻最多的100個(gè)。
