Spring Core:框架的最基礎部分,提供 IoC 容器,對 bean 進行管理。前端
Spring Context:繼承BeanFactory,提供上下文信息,擴展出JNDI、EJB、電子郵件、國際化等功能。java
Spring DAO:提供了JDBC的抽象層,還提供了聲明性事務管理方法。mysql
Spring ORM:提供了JPA、JDO、Hibernate、MyBatis 等ORM映射層.nginx
Spring AOP:集成了全部AOP功能web
Spring Web:提供了基礎的 Web 開發的上下文信息,現有的Web框架,如JSF、Tapestry、Structs等,提供了集成redis
Spring Web MVC:提供了 Web 應用的 Model-View-Controller 全功能實現。算法
singleton(單例) prototype(原型) request session global sessionspring
resource定位 即尋找用戶定義的bean資源,由 ResourceLoader經過統一的接口Resource接口來完成 beanDefinition載入 BeanDefinitionReader讀取、解析Resource定位的資源 成BeanDefinition 載入到ioc中(經過HashMap進行維護BD) BeanDefinition註冊 即向IOC容器註冊這些BeanDefinition, 經過BeanDefinitionRegistery實現sql
定義BeanDefinitionReader解析xml的document BeanDefinitionDocumentReader解析document成beanDefinition數據庫
過程在Ioc初始化後,依賴注入的過程是用戶第一次向IoC容器索要Bean時觸發
若是設置lazy-init=true,會在第一次getBean的時候才初始化bean, lazy-init=false,會容器啓動的時候直接初始化(singleton bean);
調用BeanFactory.getBean()生成bean的;
生成bean過程運用裝飾器模式產生的bean都是beanWrapper(bean的加強);
其實就是經過在beanDefinition載入時,若是bean有依賴關係,經過佔位符來代替,在調用getbean時候,若是遇到佔位符,從ioc裏獲取bean注入到本實例來
IOC:控制反轉:將對象的建立權,由Spring管理. DI(依賴注入):在Spring建立對象的過程當中,把對象依賴的屬性注入到類中。
構造器注入 setter方法注入 註解注入 接口注入
Bean在建立的時候能夠給該Bean打標,若是遞歸調用回來發現正在建立中的話,即說明了循環依賴了。
Spring中循環依賴場景有:
一、切面(aspect):類是對物體特徵的抽象,切面就是對橫切關注點的抽象
二、橫切關注點:對哪些方法進行攔截,攔截後怎麼處理,這些關注點稱之爲橫切關注點。
三、鏈接點(joinpoint):被攔截到的點,由於 Spring 只支持方法類型的鏈接點,因此在Spring 中鏈接點指的就是被攔截到的方法,實際上鍊接點還能夠是字段或者構造器。
四、切入點(pointcut):對鏈接點進行攔截的定義
五、通知(advice):所謂通知指的就是指攔截到鏈接點以後要執行的代碼,通知分爲前置、後置、異常、最終、環繞通知五類。
六、目標對象:代理的目標對象
七、織入(weave):將切面應用到目標對象並致使代理對象建立的過程
八、引入(introduction):在不修改代碼的前提下,引入能夠在運行期爲類動態地添加方法或字段。
傳統oop開發代碼邏輯自上而下的,這個過程當中會產生一些橫切性問題,這些問題與咱們主業務邏輯關係不大,會散落在代碼的各個地方,形成難以維護,aop思想就是把業務邏輯與橫切的問題進行分離,達到解耦的目的,提升代碼重用性和開發效率;
@EnableAspectJAutoProxy給容器(beanFactory)中註冊一個AnnotationAwareAspectJAutoProxyCreator對象;
AnnotationAwareAspectJAutoProxyCreator對目標對象進行代理對象的建立,對象內部,是封裝JDK和CGlib兩個技術,實現動態代理對象建立的(建立代理對象過程當中,會先建立一個代理工廠,獲取到全部的加強器(通知方法),將這些加強器和目標類注入代理工廠,再用代理工廠建立對象);
代理對象執行目標方法,獲得目標方法的攔截器鏈,利用攔截器的鏈式機制,依次進入每個攔截器進行執行
(1):用戶請求發送給DispatcherServlet,DispatcherServlet調用HandlerMapping處理器映射器;
(2):HandlerMapping根據xml或註解找到對應的處理器,生成處理器對象返回給DispatcherServlet;
(3):DispatcherServlet會調用相應的HandlerAdapter;
(4):HandlerAdapter通過適配調用具體的處理器去處理請求,生成ModelAndView返回給DispatcherServlet
(5):DispatcherServlet將ModelAndView傳給ViewReslover解析生成View返回給DispatcherServlet;
(6):DispatcherServlet根據View進行渲染視圖;
->DispatcherServlet->HandlerMapping->Handler ->DispatcherServlet->HandlerAdapter處理handler->ModelAndView ->DispatcherServlet->ModelAndView->ViewReslover->View ->DispatcherServlet->返回給客戶
默認狀況下一級緩存是開啓的,並且是不能關閉的。
new springApplication對象,利用spi機制加載applicationContextInitializer, applicationLister接口實例(META-INF/spring.factories);
調run方法準備Environment,加載應用上下文(applicationContext),發佈事件 不少經過lister實現
建立spring容器, refreshContext() ,實現starter自動化配置,spring.factories文件加載, bean實例化
核心註解是@SpringBootApplication 由如下三種組成
spring-boot-starter-web - Web 和 RESTful 應用程序;spring-boot-starter-test - 單元測試和集成測試;spring-boot-starter-jdbc - 傳統的 JDBC;spring-boot-starter-security - 使用 SpringSecurity 進行身份驗證和受權;spring-boot-starter-data-jpa - 帶有 Hibernate 的 Spring Data JPA;spring-boot-starter-data-rest - 使用 Spring Data REST 公佈簡單的 REST 服務
(1):Application.yml 通常用來定義單個應用級別的,若是搭配 spring-cloud-config 使用
(2).Bootstrap.yml(先加載) 系統級別的一些參數配置,這些參數通常是不變的
(1):zuul則是netflix公司的項目集成在spring-cloud中使用而已, Gateway是spring-cloud的 一個子項目;
(2):zuul不提供異步支持流控等均由hystrix支持, gateway提供了異步支持,提供了抽象負載均衡,提供了抽象流控;理論上gateway則更適合於提升系統吞吐量(但不必定能有更好的性能),最終性能還須要經過嚴密的壓測來決定
(3):二者底層實現都是servlet,可是gateway多嵌套了一層webflux框架
(4):zuul可用至其餘微服務框架中,內部沒有實現限流、負載均衡;gateway只能用在springcloud中;
(1):請求給zuulservlet處理(HttpServlet子類) zuulservlet中有一個zuulRunner對象,該對象中初始化了RequestContext(存儲請求的數據),RequestContext被全部的zuulfilter共享;
(2):zuulRunner中有 FilterProcessor(zuulfilter的管理器),其從filterloader 中獲取zuulfilter;
(3):有了這些filter以後, zuulservelet執行的Pre-> route-> post 類型的過濾器,若是在執行這些過濾器有錯誤的時候則會執行error類型的過濾器,執行完後把結果返回給客戶端.
(1):請求到達DispatcherHandler, DispatchHandler在IOC容器初始化時會在容器中實例化HandlerMapping接口
(2):用handlerMapping根據請求URL匹配到對應的Route,而後有對應的filter作對應的請求轉發最終response返回去
Zookeeper 的核心是原子廣播,這個機制保證了各個 server 之間的同步。實現這個機制的協議叫作 Zab 協議。Zab 協議有兩種模式,它們分別是恢復模式和廣播模式。
經過維護一個本身的線程池,當線程池達到閾值的時候,就啓動服務降級,返回fallback默認值
防止雪崩,及時釋放資源,防止系統發生更多的額級聯故障,須要對故障和延遲進行隔離,防止單個依賴關係的失敗影響整個應用程序;
ActiveMQ:Apache出品,最先使用的消息隊列產品,時間比較長了,最近版本更新比較緩慢。RabbitMQ:erlang語言開發,支持不少的協議,很是重量級,更適合於企業級的開發。性能較好,可是不利於作二次開發和維護。RocketMQ:阿里開源的消息中間件,純Java開發,具備高吞吐量、高可用性、適合大規模分佈式系統應用的特色,分佈式事務。ZeroMQ:號稱最快的消息隊列系統,尤爲針對大吞吐量的需求場景,採用 C 語言實現。消息隊列的選型須要根據具體應用需求而定,ZeroMQ 小而美,RabbitMQ 大而穩,Kakfa 和 RocketMQ 快而強勁
(1個字節是8個bit) 整數型:byte(1字節)、short(2字節)、int(4字節)、long(8字節) 浮點型:float(4字節)、double(8字節) 布爾型:boolean(1字節) 字符型:char(2字節)
包括 類File,outputStream,inputStream,writer,readerseralizable(5類1接口)
NIO三大核心內容 selector(選擇器,用於監聽channel),channel(通道),buffer(緩衝區)
NIO與IO區別,IO面向流,NIO面向緩衝區;io阻塞,nio非阻塞
throwable爲父類,子爲error跟exception,exception分runtime(空指針,越界等)跟checkexception(sql,io,找不到類等異常)
sleep屬於線程類,wait屬於object類;sleep不釋放鎖
線程中建立副本,訪問本身內部的副本變量,內部實現是其內部類名叫ThreadLocalMap的成員變量threadLocals,key爲自己,value爲實際存值的變量副本
若是是強引用,設置tl=null,可是key的引用依然指向ThreadLocal對象,因此會有內存泄漏,而使用弱引用則不會;可是仍是會有內存泄漏存在,ThreadLocal被回收,key的值變成null,致使整個value再也沒法被訪問到;解決辦法:在使用結束時,調用ThreadLocal.remove來釋放其value的引用;
ThreadLocal是不具有繼承性的,因此是沒法獲取到的,可是咱們能夠用InteritableThreadLocal來實現這個功能。InteritableThreadLocal繼承來ThreadLocal,重寫了createdMap方法,已經對應的get和set方法,不是在利用了threadLocals,而是interitableThreadLocals變量。
這個變量會在線程初始化的時候(調用init方法),會判斷父線程的interitableThreadLocals變量是否爲空,若是不爲空,則把放入子線程中,可是其實這玩意沒啥鳥用,當父線程建立完子線程後,若是改變父線程內容是同步不到子線程的。。。一樣,若是在子線程建立完後,再去賦值,也是沒啥鳥用的
線程池有5種狀態:running,showdown,stop,Tidying,TERMINATED。
running:線程池處於運行狀態,能夠接受任務,執行任務,建立線程默認就是這個狀態了
showdown:調用showdown()函數,不會接受新任務,可是會慢慢處理完堆積的任務。
stop:調用showdownnow()函數,不會接受新任務,不處理已有的任務,會中斷現有的任務。
Tidying:當線程池狀態爲showdown或者stop,任務數量爲0,就會變爲tidying。這個時候會調用鉤子函數terminated()。
TERMINATED:terminated()執行完成。
在線程池中,用了一個原子類來記錄線程池的信息,用了int的高3位表示狀態,後面的29位表示線程池中線程的個數。
若是提交任務的時候使用了submit,則返回的feature裏會存有異常信息,可是若是數execute則會打印出異常棧。可是不會給其餘線程形成影響。以後線程池會刪除該線程,會新增長一個worker。
contentionList(請求鎖線程隊列) entryList(有資格的候選者隊列) waitSet(wait方法後阻塞隊列) onDeck(競爭候選者) ower(競爭到鎖線程) !ower(執行成功釋放鎖後狀態);Synchronized 是非公平鎖。
Synchronized 在線程進入 ContentionList 時,等待的線程會先嚐試自旋獲取鎖,若是獲取不到就進入 ContentionList,這明顯對於已經進入隊列的線程是不公平的,還有一個不公平的事情就是自旋獲取鎖的線程還可能直接搶佔 OnDeck 線程的鎖資源。
底層是由一對monitorenter和monitorexit指令實現的(監視器鎖)
每一個對象有一個監視器鎖(monitor)。當monitor被佔用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的全部權,過程:
內部自定義了同步器 Sync,加鎖的時候經過CAS 算法 ,將線程對象放到一個雙向鏈表 中,每次獲取鎖的時候 ,看下當前維 護的那個線程ID和當前請求的線程ID是否同樣,同樣就可重入了;
(1):tryLock 能得到鎖就返回 true,不能就當即返回 false,
(2):tryLock(long timeout,TimeUnit unit),能夠增長時間限制,若是超過該時間段還沒得到鎖,返回 false
(3):lock 能得到鎖就返回 true,不能的話一直等待得到鎖
(4):lock 和 lockInterruptibly,若是兩個線程分別執行這兩個方法,但此時中斷這兩個線程, lock 不會拋出異常,而 lockInterruptibly 會拋出異常。
CountDownLatch是等待其餘線程執行到某一個點的時候,在繼續執行邏輯(子線程不會被阻塞,會繼續執行),只能被使用一次。最多見的就是join形式,主線程等待子線程執行完任務,在用主線程去獲取結果的方式(固然不必定),內部是用計數器相減實現的(沒錯,又特麼是AQS),AQS的state承擔了計數器的做用,初始化的時候,使用CAS賦值,主線程調用await()則被加入共享線程等待隊列裏面,子線程調用countDown的時候,使用自旋的方式,減1,知道爲0,就觸發喚醒。
CyclicBarrier迴環屏障,主要是等待一組線程到底同一個狀態的時候,放閘。CyclicBarrier還能夠傳遞一個Runnable對象,能夠到放閘的時候,執行這個任務。CyclicBarrier是可循環的,當調用await的時候若是count變成0了則會重置狀態,如何重置呢,CyclicBarrier新增了一個字段parties,用來保存初始值,當count變爲0的時候,就從新賦值。還有一個不一樣點,CyclicBarrier不是基於AQS的,而是基於RentrantLock實現的。存放的等待隊列是用了條件變量的方式。
信號量是一種固定資源的限制的一種併發工具包,基於AQS實現的,在構造的時候會設置一個值,表明着資源數量。信號量主要是應用因而用於多個共享資源的互斥使用,和用於併發線程數的控制(druid的數據庫鏈接數,就是用這個實現的),信號量也分公平和非公平的狀況,基本方式和reentrantLock差很少,在請求資源調用task時,會用自旋的方式減1,若是成功,則獲取成功了,若是失敗,致使資源數變爲了0,就會加入隊列裏面去等待。調用release的時候會加一,補充資源,並喚醒等待隊列。
(1):可重入鎖是指同一個線程能夠屢次獲取同一把鎖,不會由於以前已經獲取過還沒釋放而阻塞;
(2):reentrantLock和synchronized都是可重入鎖
(3):可重入鎖的一個優勢是可必定程度避免死鎖
(1):先經過CAS嘗試獲取鎖, 若是此時已經有線程佔據了鎖,那就加入AQS隊列而且被掛起;
(2):當鎖被釋放以後, 排在隊首的線程會被喚醒CAS再次嘗試獲取鎖,
(3):若是是非公平鎖, 同時還有另外一個線程進來嘗試獲取可能會讓這個線程搶到鎖;
(4):若是是公平鎖, 會排到隊尾,由隊首的線程獲取到鎖。
Node內部類構成的一個雙向鏈表結構的同步隊列,經過控制(volatile的int類型)state狀態來判斷鎖的狀態,對於非可重入鎖狀態不是0則去阻塞;
對於可重入鎖若是是0則執行,非0則判斷當前線程是不是獲取到這個鎖的線程,是的話把state狀態+1,好比重入5次,那麼state=5。而在釋放鎖的時候,一樣須要釋放5次直到state=0其餘線程纔有資格得到鎖
內存值V,舊的預期值A,要修改的新值B,當A=V時,將內存值修改成B,不然什麼都不作;
(1):ABA問題;(2):若是CAS失敗,自旋會給CPU帶來壓力;(3):只能保證對一個變量的原子性操做,i++這種是不能保證的
(1):Atomic系列
(1):公平鎖指在分配鎖前檢查是否有線程在排隊等待獲取該鎖,優先分配排隊時間最長的線程,非公平直接嘗試獲取鎖 (2):公平鎖需多維護一個鎖線程隊列,效率低;默認非公平
(1):ReentrantLock爲獨佔鎖(悲觀加鎖策略) (2):ReentrantReadWriteLock中讀鎖爲共享鎖 (3):JDK1.8 郵戳鎖(StampedLock), 不可重入鎖 讀的過程當中也容許獲取寫鎖後寫入!這樣一來,咱們讀的數據就可能不一致,因此,須要一點額外的代碼來判斷讀的過程當中是否有寫入,這種讀鎖是一種樂觀鎖, 樂觀鎖的併發效率更高,但一旦有小几率的寫入致使讀取的數據不一致,須要能檢測出來,再讀一遍就行
無鎖
偏向鎖 會偏向第一個訪問鎖的線程,當一個線程訪問同步代碼塊得到鎖時,會在對象頭和棧幀記錄裏存儲鎖偏向的線程ID,當這個線程再次進入同步代碼塊時,就不須要CAS操做來加鎖了,只要測試一下對象頭裏是否存儲着指向當前線程的偏向鎖 若是偏向鎖未啓動,new出的對象是普通對象(即無鎖,有稍微競爭會成輕量級鎖),若是啓動,new出的對象是匿名偏向(偏向鎖) 對象頭主要包括兩部分數據:Mark Word(標記字段, 存儲對象自身的運行時數據)、class Pointer(類型指針, 是對象指向它的類元數據的指針)
輕量級鎖(自旋鎖) (1):在把線程進行阻塞操做以前先讓線程自旋等待一段時間,可能在等待期間其餘線程已經 解鎖,這時就無需再讓線程執行阻塞操做,避免了用戶態到內核態的切換。(自適應自旋時間爲一個線程上下文切換的時間)
(2):在用自旋鎖時有可能形成死鎖,當遞歸調用時有可能形成死鎖
(3):自旋鎖底層是經過指向線程棧中Lock Record的指針來實現的
重量級鎖
(1):輕量級鎖是經過CAS來避免進入開銷較大的互斥操做
(2):偏向鎖是在無競爭場景下徹底消除同步,連CAS也不執行
(1):某線程自旋次數超過10次;
(2):等待的自旋線程超過了系統core數的一半;
經常使用的讀寫鎖ReentrantReanWritelock,這個其實和reentrantLock類似,也是基於AQS的,可是這個是基於共享資源的,不是互斥,關鍵在於state的處理,讀寫鎖把高16爲記爲讀狀態,低16位記爲寫狀態,就分開了,讀讀狀況其實就是讀鎖重入,讀寫/寫讀/寫寫都是互斥的,只要判斷低16位就行了。
(1):利用節點名稱惟一性來實現,加鎖時全部客戶端一塊兒建立節點,只有一個建立成功者得到鎖,解鎖時刪除節點。
(2):利用臨時順序節點實現,加鎖時全部客戶端都建立臨時順序節點,建立節點序列號最小的得到鎖,不然監視比本身序列號次小的節點進行等待
(3):方案2比1好處是當zookeeper宕機後,臨時順序節點會自動刪除釋放鎖,不會形成鎖等待;
(4):方案1會產生驚羣效應(當有不少進程在等待鎖的時候,在釋放鎖的時候會有不少進程就過來爭奪鎖)。
(5):因爲須要頻繁建立和刪除節點,性能上不如redis鎖
(1):變量可見性
(2):防止指令重排序
(3):保障變量單次讀,寫操做的原子性,但不能保證i++這種操做的原子性,由於本質是讀,寫兩次操做
volatile可見性是有指令原子性保證的,在jmm中定義了8類原子性指令,好比write,store,read,load。而volatile就要求write-store,load-read成爲一個原子性操做,這樣子能夠確保在讀取的時候都是從主內存讀入,寫入的時候會同步到主內存中(準確來講也是內存屏障),指令重排則是由內存屏障來保證的,由兩個內存屏障:
jdk是最小的開發環境,由jre++java工具組成。
jre是java運行的最小環境,由jvm+核心類庫組成。
jvm是虛擬機,是java字節碼運行的容器,若是隻有jvm是沒法運行java的,由於缺乏了核心類庫。
(1):堆<對象,靜態變量,共享
(2):方法區<存放類信息,常量池,共享>(java8移除了永久代(PermGen),替換爲元空間(Metaspace))
(3):虛擬機棧<線程執行方法的時候內部存局部變量會存堆中對象的地址等等數據>
(4):本地方法棧<存放各類native方法的局部變量表之類的信息>
(5):程序計數器<記錄當前線程執行到哪一條字節碼指令位置>
(1):強(內存泄露主因)
(2):軟(只有軟引用的話,空間不足將被回收),適合緩存用
(3):弱(只,GC會回收)
(4):虛引用(用於跟蹤GC狀態)用於管理堆外內存
一個對象分爲3個區域:對象頭、實例數據、對齊填充
對象頭:主要是包括兩部分,1.存儲自身的運行時數據好比hash碼,分代年齡,鎖標記等(可是不是絕對哦,鎖狀態若是是偏向鎖,輕量級鎖,是沒有hash碼的。。。是不固定的)2.指向類的元數據指針。還有可能存在第三部分,那就是數組類型,會多一塊記錄數組的長度(由於數組的長度是jvm判斷不出來的,jvm只有元數據信息)
實例數據:會根據虛擬機分配策略來定,分配策略中,會把相同大小的類型放在一塊兒,並按照定義順序排列(父類的變量也會在哦)
對齊填充:這個意義不是很大,主要在虛擬機規範中對象必須是8字節的整數,因此當對象不知足這個狀況時,就會用佔位符填充
通常判斷對象是否存活有兩種算法,一種是引用計數,另一種是可達性分析。在java中主要是第二種
根據GC ROOTS。GC ROOTS能夠的對象有:虛擬機棧中的引用對象,方法區的類變量的引用,方法區中的常量引用,本地方法棧中的對象引用。
(1):加載 獲取類的二進制字節流,將其靜態存儲結構轉化爲方法區的運行時數據結構
(2):校驗 文件格式驗證,元數據驗證,字節碼驗證,符號引用驗證
(3):準備 在方法區中對類的static變量分配內存並設置類變量數據類型默認的初始值,不包括實例變量,實例變量將會在對象實例化的時候隨着對象一塊兒分配在Java堆中
(4):解析 將常量池內的符號引用替換爲直接引用的過程
(5):初始化 爲類的靜態變量賦予正確的初始值(Java代碼中被顯式地賦予的值)
(1):啓動類加載器(home) 加載jvm核心類庫,如java.lang.*等
(2):擴展類加載器(ext), 父加載器爲啓動類加載器,從jre/lib/ext下加載類庫
(3):應用程序類加載器(用戶classpath路徑) 父加載器爲擴展類加載器,從環境變量中加載類
(1):類加載器收到類加載的請求
(2):把這個請求委託給父加載器去完成,一直向上委託,直到啓動類加載器
(3):啓動器加載器檢查能不能加載,能就加載(結束);不然,拋出異常,通知子加載器進行加載
(4):保障類的惟一性和安全性以及保證JDK核心類的優先加載
保證java基礎類在不一樣的環境仍是同一個Class對象,避免出現了自定義類覆蓋基礎類的狀況,致使出現安全問題。還能夠避免類的重複加載。
(1):自定義類加載器,繼承ClassLoader類重寫loadClass方法;
(2):SPI
tomcat有着特殊性,它須要容納多個應用,須要作到應用級別的隔離,並且須要減小重複性加載,因此劃分爲:/common 容器和應用共享的類信息,/server容器自己的類信息,/share應用通用的類信息,/WEB-INF/lib應用級別的類信息。總體能夠分爲:boostrapClassLoader->ExtensionClassLoader->ApplicationClassLoader->CommonClassLoader->CatalinaClassLoader(容器自己的加載器)/ShareClassLoader(共享的)->WebAppClassLoader。雖然第一眼是知足雙親委派模型的,可是不是的,由於雙親委派模型是要先提交給父類裝載,而tomcat是優先判斷是不是本身負責的文件位置,進行加載的。
(1):服務提供接口(服務發現機制):
(2):經過加載ClassPath下META_INF/services,自動加載文件裏所定義的類
(3):經過ServiceLoader.load/Service.providers方法經過反射拿到實現類的實例
(1):應用於JDBC獲取數據庫驅動鏈接過程就是應用這一機制
(2):apache最先提供的common-logging只有接口.沒有實現..發現日誌的提供商經過SPI來具體找到日誌提供商實現類
(1):雙親委派核心是越基礎的類由越上層的加載器進行加載, 基礎的類老是做爲被調用代碼調用的API,沒法實現基礎類調用用戶的代碼….
(2):JNDI服務它的代碼由啓動類加載器去加載,可是他須要調獨立廠商實現的應用程序,如何解決? 線程上下文件類加載器(Thread Context ClassLoader), JNDI服務使用這個線程上下文類加載器去加載所須要的SPI代碼,也就是父類加載器請求子類加載器去完成類加載動做Java中全部涉及SPI的加載動做基本上都採用這種方式,例如JNDI,JDBC
(1):老年代空間不足
(2):永久代(方法區)空間不足
(3):顯式調用system.gc()
Ehcache中的一些版本,各類 NIO 框架,Dubbo,Memcache 等中會用到,NIO包下ByteBuffer來建立堆外內存 堆外內存,其實就是不受JVM控制的內存。
減小了垃圾回收的工做,由於垃圾回收會暫停其餘的工做。加快了複製的速度。由於堆內在 flush 到遠程時,會先複製到直接內存(非堆內存),而後在發送;而堆外內存至關於省略掉了複製這項工做。能夠擴展至更大的內存空間。好比超過 1TB 甚至比主存還大的空間。
堆外內存難以控制,若是內存泄漏,那麼很難排查,經過-XX:MaxDirectMemerySize來指定,當達到閾值的時候,調用system.gc來進行一次full gc 堆外內存相對來講,不適合存儲很複雜的對象。通常簡單的對象或者扁平化的比較適合 jstat查看內存回收概況,實時查看各個分區的分配回收狀況, jmap查看內存棧,查看內存中對象佔用大小, jstack查看線程棧,死鎖,性能瓶頸
(1):Serial 收集器 複製算法,單線程,新生代)
(2):ParNew 收集器(複製算法,多線程,新生代)
(3):Parallel Scavenge 收集器(多線程,複製算法,新生代,高吞吐量)
(4):Serial Old 收集器(標記-整理算法,老年代)
(5):Parallel Old 收集器(標記-整理算法,老年代,注重吞吐量的場景下,jdk8默認採用 Parallel Scavenge + Parallel Old 的組合)
(6):CMS 收集器(標記-清除算法,老年代,垃圾回收線程幾乎能作到與用戶線程同時工做,吞吐量低,內存碎片)以犧牲吞吐量爲代價來得到最短回收停頓時間-XX:+UseConcMarkSweepGC jdk1.8 默認垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代) jdk1.9 默認垃圾收集器G1
(1):應用程序對停頓比較敏感
(2):在JVM中,有相對較多存活時間較長的對象(老年代比較大)會更適合使用CMS
(1):初始標識<找到gcroot(stw)>
1:系統類加載器加載的對象
2:處於激活狀態的線程
3:JNI棧中的對象
4:正在被用於同步的各類鎖對象
5:JVM自身持有的對象,好比系統類加載器等。
(2):併發標記(三色標記算法) 三色標記算法處理併發標記出現對象引用變化狀況:黑:本身+子對象標記完成 灰:本身完成,子對象未完成 白:未標記;併發標記 黑->灰->白 從新標記 灰->白引用消失,黑引用指向->白,致使白漏標 cms處理辦法是incremental update方案 (增量更新)把黑色變成灰色 多線程下併發標記依舊會產生漏標問題,因此cms必須remark一遍(jdk1.9之後不用cms了)
SATB(snapshot at the begining)把白放入棧中,標記過程是和應用程序併發運行的(不須要Stop-The-World) 這種方式會形成某些是垃圾的對象也被當作是存活的,因此G1會使得佔用的內存被實際須要的內存大。不過下一次就回收了 ZGC 處理方案:顏色指針(color pointers) 2*42方=4T
(3):從新標記(stw)
(4)併發清理
備註:從新標記是防止標記成垃圾以後,對象被引用
(5):G1 收集器(新生代 + 老年代,在多 CPU 和大內存的場景下有很好的性能) G1在java9 即是默認的垃圾收集器,是cms 的替代者 邏輯分代,用分區(region)的思想(默認分2048份) 仍是有stw 爲解決CMS算法產生空間碎片HotSpot提供垃圾收集器,經過-XX:+UseG1GC來啓用
(1):young gc(eden region被耗盡沒法申請內存時,就會觸發)
(2):mixed gc(當老年代大小佔整個堆大小百分比達到該閾值時,會觸發)
(3):full gc(對象內存分配速度過快,mixed gc來不及回收,致使老年代被填滿,就會觸發)
(8):ZGC和shenandoah (oracle產收費) no stw
(1):dashboard命令查看整體jvm運行狀況
(2):jvm顯示jvm詳細信息
(3):thread 顯示jvm裏面全部線程信息(相似於jstack) 查看死鎖線程命令thread -b
(4):sc * 顯示全部類(search class)
(5):trace 跟蹤方法
第一步:jps獲取進程號 第二步:jmap -histo pid | head -20 得知有個對象在不斷建立 備註:jmap若是線上服務器堆內存特別大,,會卡死需堆轉存(通常會說在測試環境壓測,導出轉存) -XX:+HeapDumpOnOutOfMemoryError或jmap -dumpLformat=b,file=xxx pid 轉出文件進行分析 (arthas沒有實現jmap命令)heapdump --live /xxx/xx.hprof導出文件
回收過程 (1):young gc(年輕代回收)--當年輕代的Eden區用盡時--stw 第一階段,掃描根。根是指static變量指向的對象,正在執行的方法調用鏈條上的局部變量等 第二階段,更新RS(Remembered Sets)。處理dirty card queue中的card,更新RS。此階段完成後,RS能夠準確的反映老年代對所在的內存分段中對象的引用 第三階段,處理RS。識別被老年代對象指向的Eden中的對象,這些被指向的Eden中的對象被認爲是存活的對象。第四階段,複製對象。此階段,對象樹被遍歷,Eden區內存段中存活的對象會被複制到Survivor區中空的內存分段 第五階段,處理引用。處理Soft,Weak,Phantom,Final,JNI Weak 等引用。
(2):concrruent marking(老年代併發標記) 當堆內存使用達到必定值(默認45%)時,不須要Stop-The-World,在併發標記前先進行一次young gc
(3):混合回收(mixed gc) 併發標記過程結束之後,緊跟着就會開始混合回收過程。混合回收的意思是年輕代和老年代會同時被回收
(4):Full GC? Full GC是指上述方式不能正常工做,G1會中止應用程序的執行,使用單線程的內存回收算法進行垃圾回收,性能會很是差,應用程序停頓時間會很長。要避免Full GC的發生,一旦發生須要進行調整。
好比堆內存過小,當G1在複製存活對象的時候沒有空的內存分段可用,則會回退到full gc,這種狀況能夠經過增大內存解決
儘管G1堆內存仍然是分代的,可是同一個代的內存再也不採用連續的內存結構
年輕代分爲Eden和Survivor兩個區,老年代分爲Old和Humongous兩個區
新分配的對象會被分配到Eden區的內存分段上
Humongous區用於保存大對象,若是一個對象佔用的空間超過內存分段Region的一半;
若是對象的大小超過一個甚至幾個分段的大小,則對象會分配在物理連續的多個Humongous分段上。
Humongous對象由於佔用內存較大而且連續會被優先回收
爲了在回收單個內存分段的時候沒必要對整個堆內存的對象進行掃描(單個內存分段中的對象可能被其餘內存分段中的對象引用)引入了RS數據結構。RS使得G1能夠在年輕代回收的時候沒必要去掃描老年代的對象,從而提升了性能。每個內存分段都對應一個RS,RS保存了來自其餘分段內的對象對於此分段的引用
JVM會對應用程序的每個引用賦值語句object.field=object進行記錄和處理,把引用關係更新到RS中。可是這個RS的更新並非實時的。G1維護了一個Dirty Card Queue
這是爲了性能的須要,使用隊列性能會好不少。
棧上分配->tlab->堆上分配 因爲堆內存是應用程序共享的,應用程序的多個線程在分配內存的時候須要加鎖以進行同步。爲了不加鎖,提升性能每個應用程序的線程會被分配一個TLAB。TLAB中的內存來自於G1年輕代中的內存分段。當對象不是Humongous對象,TLAB也能裝的下的時候,對象會被優先分配於建立此對象的線程的TLAB中。這樣分配會很快,由於TLAB隸屬於線程,因此不須要加鎖
G1會在年輕代回收過程當中把Eden區中的對象複製(「提高」)到Survivor區中,Survivor區中的對象複製到Old區中。G1的回收過程是多線程執行的,爲了不多個線程往同一個內存分段進行復制,那麼複製的過程也須要加鎖。爲了不加鎖,G1的每一個線程都關聯了一個PLAB,這樣就不須要進行加鎖了
(1):jmap -heap 10765如上圖,能夠查看新生代,老生代堆內存的分配大小以及使用狀況;
(2):jstat 查看GC收集狀況
(3):jmap -dump:live,format=b,file=到本地
(4):經過MAT工具打開分析
(1):生產者(Provider)啓動,向註冊中心(Register)註冊
(2):消費者(Consumer)訂閱,然後註冊中心通知消費者
(3):消費者從生產者進行消費
(4):監控中心(Monitor)統計生產者和消費者
推薦使用Hessian序列化,還有Duddo、FastJson、Java自帶序列化
默認使用 Netty 框架,也是推薦的選擇,另外內容還集成有Mina、Grizzly。
(1):隨機調用<默認>
(2):權重輪詢
(3):最少活躍數
(4):一致性Hash
(1)消費者調用須要消費的服務,
(2):客戶端存根將方法、入參等信息序列化發送給服務端存根
(3):服務端存根反序列化操做根據解碼結果調用本地的服務進行相關處理
(4):本地服務執行具體業務邏輯並將處理結果返回給服務端存根
(5):服務端存根序列化
(6):客戶端存根反序列化
(7):服務消費方獲得最終結果
RPC框架的實現目標PC框架的實現目標是把調用、編碼/解碼的過程給封裝起來,讓用戶感受上像調用本地服務同樣的調用遠程服務
(1):純內存操做,避免大量訪問數據庫,減小直接讀取磁盤數據,redis將數據儲存在內存裏面,讀寫數據的時候都不會受到硬盤 I/O 速度的限制,因此速度快
(2):單線程操做,避免了沒必要要的上下文切換和競爭條件,也不存在多進程或者多線程致使的切換而消耗CPU,不用去考慮各類鎖的問題,不存在加鎖釋放鎖操做,沒有由於可能出現死鎖而致使的性能消耗
(3):採用了非阻塞I/O多路複用機制
(1)Simple dynamic string(SDS)的數據結構
struct sdshdr{
//記錄buf數組中已使用字節的數量
//等於 SDS 保存字符串的長度
int len;
//記錄 buf 數組中未使用字節的數量
int free;
//字節數組,用於保存字符串
char buf[];
}
它的優勢:(1)不會出現字符串變動形成的內存溢出問題
(2)獲取字符串長度時間複雜度爲1
(3)空間預分配, 惰性空間釋放free字段,會默認留夠必定的空間防止屢次重分配內存
應用場景:String 緩存結構體用戶信息,計數
數組+鏈表的基礎上,進行了一些rehash優化;1.Reids的Hash採用鏈地址法來處理衝突,而後它沒有使用紅黑樹優化。
2.哈希表節點採用單鏈表結構。
3.rehash優化 (採用分而治之的思想,將龐大的遷移工做量劃分到每一次CURD中,避免了服務繁忙)
應用場景:保存結構體信息可部分獲取不用序列化全部字段
應用場景:(1):好比twitter的關注列表,粉絲列表等均可以用Redis的list結構來實現
(2):list的實現爲一個雙向鏈表,便可以支持反向查找和遍歷
內部實現是一個 value爲null的HashMap,實際就是經過計算hash的方式來快速排重的,這也是set能提供判斷一個成員 是否在集合內的緣由。應用場景:去重的場景,交集(sinter)、並集(sunion)、差集(sdiff),實現如共同關注、共同喜愛、二度好友等功能
內部使用HashMap和跳躍表(SkipList)來保證數據的存儲和有序,HashMap裏放的是成員到score的映射,而跳躍表裏存放的是全部的成員,排序依據是HashMap裏存的score,使用跳躍表的結構能夠得到比較高的查找效率,而且在實現上比較簡單。跳錶:每一個節點中維持多個指向其餘節點的指針,從而達到快速訪問節點的目的 應用場景:實現延時隊列
(1):Multi開啓事務
(2):Exec執行事務塊內命令
(3):Discard 取消事務
(4):Watch 監視一個或多個key,若是事務執行前key被改動,事務將打斷
(1):全部命令都將會被串行化的順序執行,事務執行期間,Redis不會再爲其它客戶端的請求提供任何服務,從而保證了事物中的全部命令被原子的執行
(2):Redis事務中若是有某一條命令執行失敗,其後的命令仍然會被繼續執行
(3):在事務開啓以前,若是客戶端與服務器之間出現通信故障並致使網絡斷開,其後全部待執行的語句都將不會被服務器執行。然而若是網絡中斷事件是發生在客戶端執行EXEC命令以後,那麼該事務中的全部命令都會被服務器執行
(4):當使用Append-Only模式時,Redis會經過調用系統函數write將該事務內的全部寫操做在本次調用中所有寫入磁盤。
然而若是在寫入的過程當中出現系統崩潰,如電源故障致使的宕機,那麼此時也許只有部分數據被寫入到磁盤,而另一部分數據卻已經丟失。
Redis服務器會在從新啓動時執行一系列必要的一致性檢測,一旦發現相似問題,就會當即退出並給出相應的錯誤提示。此時,咱們就要充分利用Redis工具包中提供的redis-check-aof工具,該工具能夠幫助咱們定位到數據不一致的錯誤,並將已經寫入的部分數據進行回滾。修復以後咱們就能夠再次從新啓動Redis服務器了
(1):全量拷貝, 1.slave第一次啓動時,鏈接Master,發送PSYNC命令,
2.master會執行bgsave命令來生成rdb文件,期間的全部寫命令將被寫入緩衝區。
master bgsave執行完畢,向slave發送rdb文件
slave收到rdb文件,丟棄全部舊數據,開始載入rdb文件
rdb文件同步結束以後,slave執行從master緩衝區發送過來的因此寫命令。
此後 master 每執行一個寫命令,就向slave發送相同的寫命令。
(2):增量拷貝 若是出現網絡閃斷或者命令丟失等異常狀況,從節點以前保存了自身已複製的偏移量和主節點的運行ID
主節點根據偏移量把複製積壓緩衝區裏的數據發送給從節點,保證主從複製進入正常狀態。
(1) Master最好不要作任何持久化工做,如RDB內存快照和AOF日誌文件
(2) 若是數據比較重要,某個Slave開啓AOF備份數據,策略設置爲每秒同步一次
(3) 爲了主從複製的速度和鏈接的穩定性,Master和Slave最好在同一個局域網內
(4) 儘可能避免在壓力很大的主庫上增長從庫
(5) 主從複製不要用圖狀結構,用單向鏈表結構更爲穩定,即:Master <- Slave1 <- Slave2 <- Slave3…這樣的結構方便解決單點故障問題,實現Slave對Master的替換。若是Master掛了,能夠馬上啓用Slave1作Master,其餘不變。
(1):官方cluster方案
(2):twemproxy
代理方案twemproxy是一個單點,很容易對其形成很大的壓力,因此一般會結合keepalived來實twemproy的高可用
(3):codis 基於客戶端來進行分片
(1):master掛掉,且當前master沒有slave
(2):集羣超過半數以上master掛掉,不管是否有slave集羣進入fail狀態
(1):會話緩存session cache
(2):排行榜/計數器ZRANGE
(3):發佈/訂閱
(1):先進先出算法(FIFO)
(2):最近使用最少Least Frequently Used(LFU)
(3):最長時間未被使用的Least Recently Used(LRU)
當存在熱點數據時,LRU的效率很好,但偶發性的、週期性的批量操做會致使LRU命中率急劇降低,緩存污染狀況比較嚴重
(1):惰性刪除,cpu友好,可是浪費cpu資源
(2):定時刪除(不經常使用)
(3):按期刪除,cpu友好,節省空間
同一時刻大量緩存失效;
(1):緩存數據增長過時標記
(2):設置不一樣的緩存失效時間
(3):雙層緩存策略C1爲短時間,C2爲長期
(4):定時更新策略
頻繁請求查詢系統中不存在的數據致使;
(1):cache null策略,查詢反饋結果爲null仍然緩存這個null結果,設置不超過5分鐘過時時間
(2):布隆過濾器,全部可能存在的數據映射到足夠大的bitmap中 google布隆過濾器:基於內存,重啓失效不支持大數據量,沒法在分佈式場景 redis布隆過濾器:可擴展性,不存在重啓失效問題,須要網絡io,性能低於google
(1):數據結構使用不合理bigkey
(2):CPU飽和
(3):持久化阻塞,rdb fork子線程,aof每秒刷盤等
(1):使用本地緩存
(2):利用分片算法的特性,對key進行打散處理(給hot key加上前綴或者後綴,把一個hotkey 的數量變成 redis 實例個數N的倍數M,從而由訪問一個 redis key 變成訪問 N * M 個redis key)
2.6版本之後lua腳本保證setnx跟setex進行原子性(setnx以後,未setex,服務掛了,鎖不釋放) a獲取鎖,超過過時時間,自動釋放鎖,b獲取到鎖執行,a代碼執行完remove鎖,a和b是同樣的key,致使a釋放了b的鎖。解決辦法:remove以前判斷value(高併發下value可能被修改,應該用lua來保證原子性)
bgsave作鏡像全量持久化,aof作增量持久化。由於bgsave會耗費較長時間,不夠實時,在停機的時候會致使大量丟失數據 ,因此須要aof來配合使用。在redis實例重啓時,會使用bgsave持久化文件從新構建內存,再使用aof重放近期的操做指令來 實 現完整恢復重啓以前的狀態。
取決於aof日誌sync屬性的配置,若是不要求性能,在每條寫指令時都sync一下磁盤,就不會丟失數據。可是在高性能的要求下每次都sync是不現實的,通常都使用定時sync,好比1s1次,這個時候最多就會丟失1s的數據.
(1):基於redis的redission分佈式可重入鎖RLock,以及配合java集合中lock;
(2):Redission 內部提供了一個監控鎖的看門狗,不斷延長鎖的有效期,默認檢查鎖的超時時間是30秒
(3):此方案的問題:若是你對某個redis master實例,寫入了myLock這種鎖key的value,此時會異步複製給對應的master ,slave實例。可是這個過程當中一旦發生redis master宕機,主備切換,redis slave變爲了redis master。
接着就會致使,客戶端2來嘗試加鎖的時候,在新的redis master上完成了加鎖,而客戶端1也覺得本身成功加了鎖。此時就會致使多個客戶端對一個分佈式鎖完成了加鎖 解決辦法:只須要將新的redis實例,在一個TTL時間內,對客戶端不可用便可,在這個時間內,全部客戶端鎖將被失效或者自動釋放.
fork和cow。fork是指redis經過建立子進程來進行bgsave操做,cow指的是copy on write,子進程建立後,父子進程共享數據段,父進程繼續提供讀寫服務,寫進的頁面數據會逐漸和子進程分離開來。
(1):R文件格式緊湊,方便數據恢復,保存rdb文件時父進程會fork出子進程由其完成具體持久化工做,最大化redis性能,恢復大數據集速度更快,只有手動提交save命令或關閉命令時才觸發備份操做;
(2):A記錄對服務器的每次寫操做(默認1s寫入一次),保存數據更完整,在redis重啓是會重放這些命令來恢復數據,操做效率高,故障丟失數據更少,可是文件體積更大;
使用keys指令能夠掃出指定模式的key列表。若是這個redis正在給線上的業務提供服務,那使用keys指令會有什麼問題?redis的單線程的。keys指令會致使線程阻塞一段時間,線上服務會停頓,直到指令執行完畢,服務才能恢復。這個時候能夠使用scan指令,scan指令能夠無阻塞的提取出指定模式的key列表,可是會有必定的重複機率,在客戶端作一次去重就能夠了 ,可是總體所花費的時間會比直接用keys指令長。
通常使用list結構做爲隊列,rpush生產消息,lpop消費消息。當lpop沒有消息的時候,要適當sleep一會再重試。
list還有個指令叫blpop,在沒有消息的時候,它會阻塞住直到消息到來。
使用pub/sub主題訂閱者模式,能夠實現1:N的消息隊列。
在消費者下線的狀況下,生產的消息會丟失,得使用專業的消息隊列如rabbitmq等。
使用sortedset,想要執行時間的時間戳做爲score,消息內容做爲key調用zadd來生產消息,消費者用zrangebyscore指令獲取N秒以前的數據輪詢進行處理。
(1):skiplist的複雜度和紅黑樹同樣,並且實現起來更簡單。
(2):在併發環境下紅黑樹在插入和刪除時須要rebalance,性能不如跳錶。
一:確保每列的原子性
二:非主鍵列不存在對主鍵的部分依賴 (要求每一個表只描述一件事情)
三:知足第二範式,而且表中的列不存在對非主鍵列的傳遞依賴
(1):主庫db的更新事件(update、insert、delete)被寫到binlog
(2):主庫建立一個binlog dump thread線程,把binlog的內容發送到從庫
(3):從庫建立一個I/O線程,讀取主庫傳過來的binlog內容並寫入到relay log.
(4):從庫還會建立一個SQL線程,從relay log裏面讀取內容寫入到slave的db.
(1):異步複製(默認) 主庫寫入binlog日誌後便可成功返回客戶端,無須等待binlog日誌傳遞給從庫的過程,可是一旦主庫宕機,就有可能出現丟失數據的狀況。
(2)半同步複製:( 5.5版本以後) (安裝半同步複製插件)確保從庫接收完成主庫傳遞過來的binlog內容已經寫入到本身的relay log(傳送log)後纔會通知主庫上面的等待線程。若是等待超時,則關閉半同步複製,並自動轉換爲異步複製模式,直到至少有一臺從庫通知主庫已經接收到binlog信息爲止
(1):Myiasm是mysql默認的存儲引擎,不支持數據庫事務,行級鎖,外鍵;插入更新需鎖表,效率低,查詢速度快,Myisam使用的是非彙集索引
(2):innodb 支持事務,底層爲B+樹實現,適合處理多重併發更新操做,普通select都是快照讀,快照讀不加鎖。InnoDb使用的是彙集索引
(1):彙集索引就是以主鍵建立的索引
(2):每一個表只能有一個聚簇索引,由於一個表中的記錄只能以一種物理順序存放,實際的數據頁只能按照一顆 B+ 樹進行排序
(3):表記錄的排列順序和與索引的排列順序一致
(4):彙集索引存儲記錄是物理上連續存在
(5):聚簇索引主鍵的插入速度要比非聚簇索引主鍵的插入速度慢不少
(6):聚簇索引適合排序,非聚簇索引不適合用在排序的場合,由於聚簇索引葉節點自己就是索引和數據按相同順序放置在一塊兒,索引序便是數據序,數據序便是索引序,因此很快。非聚簇索引葉節點是保留了一個指向數據的指針,索引自己固然是排序的,可是數據並未排序,數據查詢的時候須要消耗額外更多的I/O,因此較慢
(7):更新彙集索引列的代價很高,由於會強制innodb將每一個被更新的行移動到新的位置
(1):除了主鍵之外的索引
(2):彙集索引的葉節點就是數據節點,而非聚簇索引的葉節點仍然是索引節點,並保留一個連接指向對應數據塊
(3):聚簇索引適合排序,非聚簇索引不適合用在排序的場合
(4):彙集索引存儲記錄是物理上連續存在,非彙集索引是邏輯上的連續。
使用聚簇索引找到包含第一個值的行後,即可以確保包含後續索引值的行在物理相鄰
在聚簇索引中不要包含常常修改的列,由於碼值修改後,數據行必須移動到新的位置,索引此時會重排,會形成很大的資源浪費
優先使用用戶自定義主鍵做爲主鍵,若是用戶沒有定義主鍵,則選取一個Unique鍵做爲主鍵,若是表中連Unique鍵都沒有定義的話,則InnoDB會爲表默認添加一個名爲row_id隱藏列做爲主鍵。
每一個表你最多能夠創建249個非聚簇索引。非聚簇索引須要大量的硬盤空間和內存
(1):BTree索引可能須要屢次運用折半查找來找到對應的數據塊 (2):HASH索引是經過HASH函數,計算出HASH值,在表中找出對應的數據 (3):大量不一樣數據等值精確查詢,HASH索引效率一般比B+TREE高 (4):HASH索引不支持模糊查詢、範圍查詢和聯合索引中的最左匹配規則,而這些Btree索引都支持
(1):須要查詢,排序,分組和聯合操做的字段適合創建索引
(2):索引多,數據更新表越慢,儘可能使用字段值不重複比例大的字段做爲索引,聯合索引比多個獨立索引效率高
(3):對數據進行頻繁查詢進創建索引,若是要頻繁更改數據不建議使用索引
(4):當對錶中的數據進行增長、刪除和修改的時候,索引也要動態的維護,下降了數據的維護速度。
(1):B+Tree非葉子節點只存儲鍵值信息,下降B+Tree的高度,全部葉子節點之間都有一個鏈指針,數據記錄都存放在葉子節點中
(2):紅黑樹這種結構,h明顯要深的多,效率明顯比B-Tree差不少
(3):B+樹也存在劣勢,因爲鍵會重複出現,所以會佔用更多的空間。可是與帶來的性能優點相比,空間劣勢每每能夠接受,所以B+樹的在數據庫中的使用比B樹更加普遍
(1):條件是or,若是還想讓or條件生效,給or每一個字段加個索引
(2):like開頭%
(3):若是列類型是字符串,那必定要在條件中將數據使用引號引用起來,不然不會使用索引
(4):where中索引列使用了函數或有運算
ACID 原子性,一致性,隔離性,永久性
(1):經過預寫日誌方式實現的,redo和undo機制是數據庫實現事務的基礎
(2):redo日誌用來在斷電/數據庫崩潰等情況發生時重演一次刷數據的過程,把redo日誌裏的數據刷到數據庫裏,保證了事務 的持久性(Durability)
(3):undo日誌是在事務執行失敗的時候撤銷對數據庫的操做,保證了事務的原子性
(1):讀未提交read-uncommitted-- 髒,不可重複讀--幻讀 A讀取了B未提交的事務,B回滾,A 出現髒讀;
(2):不可重複讀read-committed-- 不可重複讀--幻讀 A只能讀B已提交的事務,可是A還沒結束,B又更新數據隱式提交,而後A又讀了一次出現不可重複讀;
(3):可重複讀repeatable-read<默認>-- 幻讀 事務開啓,不容許其餘事務的UPDATE修改操做 A讀取B已提交的事務,然而B在該表插入新的行,以後A在讀取的時候多出一行,出現幻讀;
(4):串行化serializable--
(1)Propagation.REQUIRED<默認> 若是當前存在事務,則加入該事務,若是當前不存在事務,則建立一個新的事務。
(2)Propagation.SUPPORTS 若是當前存在事務,則加入該事務;若是當前不存在事務,則以非事務的方式繼續運行。
(3)Propagation.MANDATORY 若是當前存在事務,則加入該事務;若是當前不存在事務,則拋出異常。
(4)Propagation.REQUIRES_NEW 從新建立一個新的事務,若是當前存在事務,延緩當前的事務。
(5)Propagation.NOT_SUPPORTED 以非事務的方式運行,若是當前存在事務,暫停當前的事務。
(6)Propagation.NEVER 以非事務的方式運行,若是當前存在事務,則拋出異常。
(7)Propagation.NESTED 若是沒有,就新建一個事務;若是有,就在當前事務中嵌套其餘事務。
(1):互斥:資源x的任意一個時刻只能被一個線程持有 (2):佔有且等待:線程1佔有資源x的同時等待資源y,並不釋放x (3):不可搶佔:資源x一旦被線程1佔有,其餘線程不能搶佔x (4):循環等待:線程1持有x,等待y,線程2持有y,等待x 當所有知足時纔會死鎖
底層實現是AOP,動態代理 (1):實現是經過Spring代理來實現的。生成當前類的代理類,調用代理類的invoke()方法,在invoke()方法中調用 TransactionInterceptor攔截器的invoke()方法;
(2):非public方式其事務是失效的;
(3):自調用也會失效,由於動態代理機制致使
(4)多個方法外層加入try...catch,解決辦法是能夠在catch裏 throw new RuntimeException()來處理
有一個事務管理器的概念,負責協調多個數據庫(資源管理器)的事務 不適合高併發場景,嚴重依賴數據庫層面,同步阻塞問題;協調者故障則全部參與者會阻塞
嚴重依賴代碼補償和回滾,通常銀行用,和錢相關的支付、交易等相關的場景,咱們會用TCC Try,對各個服務的資源作檢測,對資源進行鎖定或者預留 Confirm,在各個服務中執行實際的操做 Cancel,若是任何一個服務的業務方法執行出錯,那麼這裏就須要進行補償,即執行已操做成功的業務邏輯的回滾操做
1):本地消息服務 本地消息表實際上是國外的 ebay 搞出來的這麼一套思想。主動方是認證服務,有個消息異常處理系統,mq,還有消息消費端應用系統,還有采集服務;
2):獨立消息最終一致性方案:A 主動方應用系統,B消息服務子系統,C消息狀態確認子系統,C2消息管理子系統 D 消息恢復子系統,mq ,消息消費端E ,被動系統F
流程:
A預發送消息給B,而後執行A業務邏輯,B存儲預發送消息,A執行完業務邏輯發送業務操做結果給B,B更新預發送消息爲確認併發送消息狀態同時發送消息給mq,而後被E監聽而後發送給F消費掉
C:對預發送消息異常的處理,去查詢待確認狀態超時的消息,去A中查詢進行數據處理,若是A中業務處理成功了,那麼C需改消息狀態爲確認併發送狀態,而後發送消息給mq;若是A中業務處理失敗了..那麼C直接把消息刪除便可.
C2 : 查詢消息的頁面,對消息的可視化,以及批量處理死亡消息;
D:B給mq放入數據若是失敗,,經過D去重試,屢次重試失敗,消息設置爲死亡
E:確保F執行完成,發送消息給B刪除消息
優化建議:
(1)數據庫:若是用redis,持久化要配置成appendfsync always,確保每次新添加消息都能持久化進磁盤
(2)在被動方應用業務冪等性判斷比較麻煩或者比較耗性能狀況下,增長消息日誌記錄表.用於判斷以前有無發送過;
(1)業務主動方完成業務處理以後,設置時間階梯型通知規則向業務活動的被動方發送消息,容許消息丟失.
(2)被動方根據定時策略,向主動方查詢,恢復丟失的業務消息
(3)被動方的處理結果不影響主動方的處理結果
(4)需增長業務查詢,通知服務,校對系統服務的建設成本
(5)適用於對業務最終一致性的時間敏感度低,跨企業的業務通知活動
(6)好比銀行通知,商戶通知,交易業務平臺間商戶通知,屢次通知,查詢校對等
應用層基於SQL解析實現了自動補償,從而最大程度的下降業務侵入性;將分佈式事務中TC(事務協調者)獨立部署,負責事務的註冊、回滾;經過全局鎖實現了寫隔離與讀隔離。
TCP向上層提供面向鏈接的可靠服務 ,UDP向上層提供無鏈接不可靠服務。雖然 UDP 並無 TCP 傳輸來的準確,可是也能在不少實時性要求高的地方有所做爲 對數據準確性要求高,速度能夠相對較慢的,能夠選用TCP
(1):客戶端發送終止命令FIN
(2):服務端收到後回覆ACK,處於close_wait狀態
(3):服務器將關閉前須要發送信息發送給客戶端後處於last_ack狀態
(4):客戶端收到FIN後發送ack後處於tim-wait然後進入close狀態
爲了防止服務器端開啓一些無用的鏈接增長服務器開銷以及防止已失效的鏈接請求報文段忽然又傳送到了服務端
java也開始認可了函數式編程, 就是說函數既能夠做爲參數,也能夠做爲返回值, 大大的簡化了代碼的開發
打破接口裏面是隻能有抽象方法,不能有任何方法的實現,接口裏面也能夠有方法的實現了
以前使用的java.util.Date月份從0開始,咱們通常會+1使用,很不方便,java.time.LocalDate月份和星期都改爲了enum java.util.Date和SimpleDateFormat都不是線程安全的,而LocalDate和LocalTime和最基本的String同樣,是不變類型,不但線程安全,並且不能修改。新接口更好用的緣由是考慮到了日期時間的操做,常常發生往前推或日後推幾天的狀況。用java.util.Date配合Calendar要寫好多代碼,並且通常的開發人員還不必定能寫對。
(1):JDK1.7版本的ReentrantLock+Segment+HashEntry(數組)
(2):JDK1.7採用segment的分段鎖機制實現線程安全
(3):JDK1.8版本中synchronized+CAS+HashEntry(數組)+紅黑樹
(4):JDK1.8採用CAS+Synchronized保證線程安全
(5):查詢時間複雜度從原來的遍歷鏈表O(n),變成遍歷紅黑樹O(logN)
1.8 HashMap數組+鏈表+紅黑樹來實現hashmap,當碰撞的元素個數大於8時 & 總容量大於64,會有紅黑樹的引入 除了添加以後,效率都比鏈表高,1.8以後鏈表新進元素加到末尾
(1):由於粒度下降了,在相對而言的低粒度加鎖方式,synchronized並不比ReentrantLock差
(2):基於JVM的synchronized優化空間更大
(3):在大數據量下,基於API的ReentrantLock會比基於JVM的內存壓力開銷更多的內存
模塊是一個包的容器,Java 9 最大的變化之一是引入了模塊系統(Jigsaw 項目)。
一般,您但願在代碼中建立一個集合(例如,List 或 Set ),並直接用一些元素填充它。實例化集合,幾個 「add」 調用,使得代碼重複。Java 9,添加了幾種集合工廠方法:
Set<Integer> ints = Set.of(1, 2, 3);
List<String> strings = List.of("first", "second");
Stream 接口中添加了 4 個新的方法:dropWhile, takeWhile, ofNullable。還有個 iterate 方法的新重載方法
Javadoc 如今支持在 API 文檔中的進行搜索。另外,Javadoc 的輸出如今符合兼容 HTML5 標準。
redis代理集羣模式,spring有哪些註解,b+b 紅黑樹區別,三次握手,valitile重排序底層代碼, cas 事務的4個特性,java8 java11 特性, filter和interceptor的區別 @autowired原理, dispatcherservlet,分佈式事務解決方案spring都有哪些模塊,fork join隊列,排序算法,
兩種:collection和map,其中collection分爲set和List。
ArrayList和linkedList使用的最多,也最具表明性。
ArrayList實現是一個數組,可變數組,默認初始化長度爲10,也能夠咱們設置容量,可是沒有設置的時候是默認的空數組,只有在第一步add的時候會進行擴容至10(從新建立了數組),後續擴容按照3/2的大小進行擴容,是線程不安全的,適用多讀取,少插入的狀況
linkedList是基於雙向鏈表的實現,使用了尾插法的方式,內部維護了鏈表的長度,以及頭節點和尾節點,因此獲取長度不須要遍歷。適合一些插入/刪除頻繁的狀況。
Vector是線程安全的,實現方式和ArrayList類似,也是基於數組,可是方法上面都有synchronized關鍵詞修飾。其擴容方式是原來的兩倍。
hashMap是map類型的一種最經常使用的數據結構,其底部實現是數組+鏈表(在1.8版本後變爲了數組+鏈表/紅黑樹的方式),其key是能夠爲null的,默認hash值爲0。擴容以2的冪等次(爲何。。。由於只有是2的冪等次的時候(n-1)&x==x%n,固然不必定只有一個緣由)。是線程不安全的
hashTable的實現形式和hashMap差很少,它是線程安全的,是繼承了Dictionary,也是key-value的模式,可是其key不能爲null。
ConcurrentHashMap是JUC併發包的一種,在hashMap的基礎上作了修改,由於hashmap實際上是線程不安全的,那在併發狀況下使用hashTable嘛,可是hashTable是全程加鎖的,性能很差,因此採用分段的思想,把本來的一個數組分紅默認16段,就能夠最多容納16個線程併發操做,16個段叫作Segment,是基於ReetrantLock來實現的
hashMap是Map的結構,內部用了數組+鏈表的方式,在1.8後,當鏈表長度達到8的時候,會變成紅黑樹,這樣子就能夠把查詢的複雜度變成O(nlogn)了,默認負載因子是0.75,爲何是0.75呢?
咱們知道當負載因子過小,就很容易觸發擴容,若是負載因子太大就容易出現碰撞。因此這個是空間和時間的一個均衡點,在1.8的hashmap介紹中,就有描述了,貌似是0.75的負載因子中,能讓隨機hash更加知足0.5的泊松分佈。
除此以外,1.7的時候是頭插法,1.8後就變成了尾插法,主要是爲了解決rehash出現的死循環問題,並且1.7的時候是先擴容後插入,1.8則是先插入後擴容(爲何?正常來講,若是先插入,就有可能節點變爲樹化,那麼是否是多作一次樹轉化,比1.7要多損耗,我的猜想,由於讀寫問題,由於hashmap並非線程安全的,若是說是先擴容,後寫入,那麼在擴容期間,是訪問不到新放入的值的,是否是不太合適,因此會先放入值,這樣子在擴容期間,那個值是在的)。
1.7版本的時候用了9次擾動,5次異或,4次位移,減小hash衝突,可是1.8就只用了兩次,以爲就足夠了一次異或,一次位移。
concurrentHashMap是線程安全的map結構,它的核心思想是分段鎖。在1.7版本的時候,內部維護了segment數組,默認是16個,segment中有一個table數組(至關於一個segmeng存放着一個hashmap。。。),segment繼承了reentrantlock,使用了互斥鎖,map的size其實就是segment數組的count和。而在1.8的時候作了一個大改版,廢除了segment,採用了cas加synchronize方式來進行分段鎖(還有自旋鎖的保證),並且節點對象改用了Node不是以前的HashEntity。
Node能夠支持鏈表和紅黑樹的轉化,好比TreeBin就是繼承了Node,這樣子能夠直接用instanceof來區分。1.8的put就很複雜來,會先計算出hash值,而後根據hash值選出Node數組的下標(默認數組是空的,因此一開始put的時候會初始化,指定負載因子是0.75,不可變),判斷是否爲空,若是爲空,則用cas的操做來賦值首節點,若是失敗,則由於自旋,會進入非空節點的邏輯,這個時候會用synchronize加鎖頭節點(保證整條鏈路鎖定)這個時候還會進行二次判斷,是不是同一個首節點,在分首節點究竟是鏈表仍是樹結構,進行遍歷判斷。
1.7版本的concurrentHashMap是基於了segment的,segment內部維護了HashEntity數組,因此擴容是在這個基礎上的,類比hashmap的擴容,
1.8版本的concurrentHashMap擴容方式比較複雜,利用了ForwardingNode,先會根據機器內核數來分配每一個線程能分到的busket數,(最小是16),這樣子能夠作到多線程協助遷移,提高速度。而後根據本身分配的busket數來進行節點轉移,若是爲空,就放置ForwardingNode,表明已經遷移完成,若是是非空節點(判斷是否是ForwardingNode,是就結束了),加鎖,鏈路循環,進行遷移。
判斷key是不是null,若是是null對應的hash值就是0,得到hash值事後則進行擾動,(1.7是9次,5次異或,4次位移,1.8是2次),獲取到的新hash值找出所在的index,(n-1)&hash,根據下標找到對應的Node/entity,而後遍歷鏈表/紅黑樹,若是遇到hash值相同且equals相同,則覆蓋值,若是不是則新增。若是節點數大於8了,則進行樹化(1.8)。完成後,判斷當前的長度是否大於閥值,是就擴容(1.7是先擴容在put)。
都是map惹的禍,咱們知道在map中判斷是不是同一個對象的時候,會先判斷hash值,在判斷equals的,若是咱們只是重寫了hashcode,沒有順便修改equals,好比Intger,hashcode就是value值,若是咱們不改寫equals,而是用了Object的equals,那麼就是判斷二者指針是否一致了,那就會出現valueOf和new出來的對象會對於map而言是兩個對象,那就是個問題了
TreeMap是Map中的一種很特殊的map,咱們知道Map基本是無序的,可是TreeMap是會自動進行排序的,也就是一個有序Map(使用了紅黑樹來實現),若是設置了Comparator比較器,則會根據比較器來對比二者的大小,若是沒有則key須要是Comparable的子類(代碼中沒有事先check,會直接拋出轉化異常,有點坑啊)。
LinkedHashMap是HashMap的一種特殊分支,是某種有序的hashMap,和TreeMap是不同的概念,是用了HashMap+鏈表的方式來構造的,有二者有序模式:訪問有序,插入順序,插入順序是一直存在的,由於是調用了hashMap的put方法,並無重載,可是重載了newNode方法,在這個方法中,會把節點插入鏈表中,訪問有序默認是關閉的,若是打開,則在每次get的時候都會把鏈表的節點移除掉,放到鏈表的最後面。這樣子就是一個LRU的一種實現方式。
TODO(未完待續)