Spring-IOC
1、解析注冊:使用Resource定位xml配置刮刑;使用BeanDefinitionReader讀取配置喉祭,并封裝成BeanDefinition;使用BeanDefinitionRegistry將BeanDefinition注冊到BeanDefinitionMap中
2雷绢、BeanFatory中bean的加載過程
**轉(zhuǎn)換對(duì)應(yīng)的beanName:傳入的參數(shù)可能不是bean的name泛烙,可能是別名、FactoryBean(&開頭)
**如果是單例柳琢,嘗試從緩存中獲取單例bean媳搪,獲取失敗再嘗試從singletonFactories中獲取單例工廠拗胜,通過單例工廠去加載(在創(chuàng)建單例bean時(shí)為解決依賴注入万哪,不等bean創(chuàng)建完成就將創(chuàng)建bean的工廠提早曝光并加入緩存中冲杀,其他單例bean創(chuàng)建時(shí)如果需要依賴該bean运嗜,直接從緩存中獲取單例bean或獲取工廠去創(chuàng)建)(調(diào)用工廠的getObject方法先獲取實(shí)例化但還未初始化的單例bean姿染,加入到earlySingletonObjects緩存中中鼠,將單例工廠從singletonFactories中移除茴丰,返回單例bean),單例bean的轉(zhuǎn)換:獲取的bean可能是原始狀態(tài)(有可能獲取的是Factorybean汰规,需要調(diào)用FactoryBean中的getObject方法獲取單例bean)
**如果緩存中沒有刚梭,以下開始創(chuàng)建單例bean
**根據(jù)beanName嘗試從beanDefinitionMap中獲取對(duì)應(yīng)的beanDefinition中的配置嫩痰,如果獲取不到配置窍箍,嘗試遞歸根據(jù)parentBeanFactory去加載(調(diào)用父類工廠的getBean方法)
**前置處理:創(chuàng)建單例bean之前始赎,記錄單例bean正在創(chuàng)建狀態(tài),用于檢測循環(huán)依賴
**通過單例工廠創(chuàng)建單例bean仔燕,調(diào)用單例工廠的getObject方法獲取提早曝光的單例bean(實(shí)例化還未初始化):處理override屬性:為了后面實(shí)例化單例bean時(shí)更好的處理,這里先預(yù)先判斷一下是否需要覆蓋或重載魔招,后面處理的原理就是在實(shí)例化bean時(shí)如果檢測到methodOverrides時(shí)晰搀,會(huì)動(dòng)態(tài)地為當(dāng)前bean生成代理并使用對(duì)應(yīng)的攔截器為bean做增強(qiáng)處理;實(shí)例化單例bean前處理办斑;短路處理:Spring AOP代理實(shí)現(xiàn)外恕,如果需要使用代理bean且代理bean已經(jīng)創(chuàng)建杆逗,直接返回;實(shí)例化單例bean(將BeanDefinition轉(zhuǎn)換為BeanWrapper(對(duì)反射相關(guān)API的簡單封裝鳞疲,使得上層使用反射完成相關(guān)的業(yè)務(wù)邏輯大大的簡化)罪郊,需要選擇不同的實(shí)例化策略:如果有需要覆蓋或動(dòng)態(tài)替換的方法,需要使用cglib進(jìn)行動(dòng)態(tài)代理尚洽,因?yàn)榭梢栽趧?chuàng)建代理的同時(shí)將動(dòng)態(tài)方法織入類中悔橄,否則可以直接用反射;構(gòu)造函數(shù)注入循環(huán)依賴問題spring不能解決 循環(huán)依賴是在實(shí)例化后處理的)腺毫;實(shí)例化bean后處理癣疟;如果需要解決循環(huán)依賴(滿足3個(gè)條件:單例、允許循環(huán)依賴潮酒、當(dāng)前bean正在創(chuàng)建)睛挚,則提早曝光單例工廠(將單例工廠放入工廠緩存singletonFactories中,其他單例bean在創(chuàng)建時(shí)調(diào)用getObject方法可以獲取未創(chuàng)建好的單例bean急黎,getObject方法中實(shí)現(xiàn)Spring AOP 的advice動(dòng)態(tài)織入扎狱;屬性注入(填充)(遞歸初始化);激活aware方法(通過aware方法可以獲取對(duì)應(yīng)的資源:BeanNameAware 獲取bean名稱勃教,BeanClassLoaderAware 獲取bean的類加載器淤击;BeanFactory 獲取bean的工廠,即加載到IOC容器中)荣回;初始化單例bean前處理遭贸;激活用戶自定義的init方法:如afterPropertiesSet方法、init-method心软,afterPropertiesSet先執(zhí)行壕吹,init-method后執(zhí)行;初始化單例bean后處理(spring AOP 在這里實(shí)現(xiàn))删铃;檢測循環(huán)依賴耳贬;注冊destroy-method銷毀方法);
**后置處理:創(chuàng)建單例bean之后猎唁,移除單例bean正在創(chuàng)建狀態(tài)咒劲,用于檢測循環(huán)依賴
**將單例bean放入單例緩存singletonObjects,從單例工廠緩存singletonFactories中移除單例工廠诫隅,從單例bean緩存earlySingletonObjects中移除單例bean腐魂,保存已注冊的單例bean
**類型轉(zhuǎn)換:將bean轉(zhuǎn)換為需要的類型
3、ApplicationContext
**環(huán)境準(zhǔn)備:如系統(tǒng)屬性或環(huán)境變量的準(zhǔn)備及驗(yàn)證
**加載BeanFactory逐纬,并讀取配置文件:創(chuàng)建BeanFactory(DefaultListableBeanFactory)蛔屹;定制BeanFactory(在基本容器的基礎(chǔ)上,增加了是否允許覆蓋是否允許擴(kuò)展的設(shè)置豁生,并提供了對(duì)注解@Qualifier兔毒、@Autowired的支持)漫贞;加載beanDefinition,讀取配置文件(通過beanDefinitionReader加載beanDefinition(并注冊到beanFactory的BeanDefinitionMap中))育叁;使用全局變量記錄beanFactory
**對(duì)BeanFactory進(jìn)行功能填充:如對(duì)@Qualifier和@Autowired注解的支持迅脐;增加AspectJ支持;增加屬性注冊編輯器(Spring DI 依賴注入時(shí) Date類型是無法識(shí)別的)
**子類通過覆蓋方法做額外處理
**激活(調(diào)用)BeanFactory處理器(容器級(jí))豪嗽,可以有多個(gè)谴蔑,通過排序依次處理;beanFactory處理器可以在實(shí)例化任何bean之前獲得配置元數(shù)據(jù)并修改BeanDefinition(如${}替換)昵骤;@ComponentScan就是在這里實(shí)現(xiàn)的树碱;注冊bean處理器(BeanFactory沒有注冊(因此不能使用),需要手動(dòng)注冊)变秦,在bean創(chuàng)建時(shí)調(diào)用
**注冊攔截bean創(chuàng)建的bean處理器成榜,這里只是注冊,真正調(diào)用是在getBean方法中
**國際化處理
**初始化應(yīng)用消息廣播器
**子類覆蓋方法處理
**在所有注冊的bean中查找要監(jiān)聽的bean蹦玫,注冊到消息廣播器中(注冊監(jiān)聽器赎婚,并在合適的時(shí)候通知監(jiān)聽器)
**通過beanFactory加載bean(非延遲加載):ApplicationContext在啟動(dòng)時(shí)會(huì)加載所有的單例bean,調(diào)用getBean方法(上面Spring中BeanFactory加載bean的過程)
**完成刷新樱溉,通知生命周期管理器lifecycleProcessor刷新過程挣输,并通過事件通知監(jiān)聽者
Spring-FactoryBean、BeanFactory福贞、ObjectFactory
1撩嚼、FactoryBean:
一般情況下,Spring通過反射機(jī)制來實(shí)例化bean挖帘,而這樣可能需要很多配置完丽,可以通過實(shí)現(xiàn)FactoryBean接口以編碼的方式來代替
在IOC容器的基礎(chǔ)上給Bean的實(shí)現(xiàn)加上了一個(gè)簡單工廠模式和裝飾模式,是一個(gè)可以生產(chǎn)對(duì)象和裝飾對(duì)象的工廠bean
它是泛型的拇舀,只能固定生產(chǎn)某一類對(duì)象逻族,而不像BeanFactory那樣可以生產(chǎn)多種類型的Bean
當(dāng)bean實(shí)現(xiàn)FactoryBean接口時(shí),通過工廠的getBean方法返回的是FactoryBean中的getObject方法返回的實(shí)例骄崩,如果想要返回當(dāng)前bean聘鳞,需要以&開頭
2、BeanFactory:對(duì)象工廠要拂,用于實(shí)例化和保存對(duì)象
3抠璃、ObjectFactory:某個(gè)特定的工廠,用于在項(xiàng)目啟動(dòng)時(shí)脱惰,延遲實(shí)例化對(duì)象搏嗡,解決循環(huán)依賴問題, 調(diào)用它的getObject方法時(shí)枪芒,才會(huì)觸發(fā) Bean 實(shí)例化
Spring單例下如何解決循環(huán)依賴(三級(jí)緩存)
Spring中循環(huán)依賴包括構(gòu)造器依賴和setter注入依賴彻况,Spring只能解決單例setter注入依賴(注入時(shí)會(huì)返回提前暴露的創(chuàng)建中的bean),構(gòu)造器依賴無法解決(因?yàn)橹挥袑?shí)例化之后才能曝光舅踪,實(shí)例化前曝光是有風(fēng)險(xiǎn)的)纽甘,對(duì)于原型模式,循環(huán)依賴也是無法解決的(因?yàn)椴皇褂镁彺妫?br>
bean什么時(shí)候才會(huì)提早曝光:單例抽碌、創(chuàng)建中悍赢、允許循環(huán)依賴
1、嘗試從singletionObjects中獲取實(shí)例
2货徙、嘗試從earlySingletionObjects中獲取實(shí)例
3左权、根據(jù)beanName嘗試從singletonFactories中獲取ObjectFactory,調(diào)用getObject方法創(chuàng)建bean(這里只是實(shí)例化了bean)痴颊,放到earlySingletionObjects中赏迟,并從singletonFactories中移除ObjectFactory(互斥操作)這時(shí)已經(jīng)可以通過容器的getBean方法獲取到bean
Spring-AOP
1、靜態(tài)代理蠢棱、動(dòng)態(tài)代理:靜態(tài)代理直接調(diào)用目標(biāo)類方法锌杀;動(dòng)態(tài)代理通過反射調(diào)用目標(biāo)類方法
2、JDK動(dòng)態(tài)代理朱巨、CGLIB動(dòng)態(tài)代理:JDK是在運(yùn)行期間創(chuàng)建接口的實(shí)現(xiàn)類來完成對(duì)目標(biāo)對(duì)象的代理落恼;CGLIB采用了非常底層的字節(jié)碼技術(shù)荒适,其原理是通過字節(jié)碼技術(shù)為一個(gè)類創(chuàng)建子類(生成代理類Class的二進(jìn)制字節(jié)碼;通過Class.forName加載二進(jìn)制字節(jié)碼突想,生成Class對(duì)象;通過反射機(jī)制獲取代理類實(shí)例構(gòu)造究抓,并初始化代理類對(duì)象)猾担,并在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用,順勢織入橫切邏輯
3漩蟆、連接點(diǎn)(方法執(zhí)行處)垒探、切入點(diǎn)(何處織入通知)、通知(處理時(shí)機(jī)及處理邏輯)怠李、切面(包括切入點(diǎn)和通知)
4圾叼、源碼:
**解析器解析AOP代理注解,生成BeanDefination捺癞,并注冊到BeanDefinitionMap中
**創(chuàng)建自動(dòng)代理創(chuàng)建器(用來實(shí)現(xiàn)AOP)(AnnotationAwareAspectJAutoProxyCreator)
**選擇代理實(shí)現(xiàn)方式:默認(rèn)如果目標(biāo)對(duì)象有實(shí)現(xiàn)接口夷蚊,則使用JDK動(dòng)態(tài)代理;否則使用CGLIB代理(無法覆寫final方法髓介,可以通過proxy-target-class屬性強(qiáng)制使用CGLIB代理)惕鼓;expose-proxy屬性是為了解決有時(shí)候目標(biāo)對(duì)象內(nèi)部的自我調(diào)用無法實(shí)現(xiàn)切面增強(qiáng)的問題(強(qiáng)制暴露代理,在代碼中可以獲取這個(gè)代理進(jìn)行顯式調(diào)用)
**創(chuàng)建AOP動(dòng)態(tài)代理:自動(dòng)代理創(chuàng)建器實(shí)現(xiàn)了BeanPostProcessor接口唐础,Spring在目標(biāo)bean初始化完成之后會(huì)調(diào)用其postProcessAfterInitialization方法來創(chuàng)建AOP動(dòng)態(tài)代理箱歧;獲取增強(qiáng)矾飞,獲取所有增強(qiáng)中目標(biāo)bean可用的增強(qiáng);創(chuàng)建代理工廠呀邢,根據(jù)配置設(shè)置JDK動(dòng)態(tài)代理洒沦,或者CGLIB代理,將目標(biāo)bean及其增強(qiáng)添加到代理工廠价淌,通過代理工廠創(chuàng)建代理并返回(將增強(qiáng)組成攔截器鏈申眼,執(zhí)行目標(biāo)方法時(shí),執(zhí)行攔截器鏈蝉衣,中間會(huì)調(diào)用目標(biāo)方法)
Spring事務(wù)失效的場景
1括尸、注解@Transactional配置的方法非public權(quán)限修飾(可以開啟 AspectJ 代理模式解決)
2、注解@Transactional所在類非Spring容器管理的bean
3病毡、注解@Transactional所在類中濒翻,注解修飾的方法被類內(nèi)部方法調(diào)用(無事務(wù)方法調(diào)用有事務(wù)方法,事務(wù)失效剪验,使用代理對(duì)象調(diào)用解決肴焊,且要在啟動(dòng)類上加注解@EnableAspectJAutoProxy(exposeProxy = true)):Spring在掃描Bean的時(shí)候會(huì)自動(dòng)為標(biāo)注了@Transactional注解的類生成一個(gè)代理對(duì)象(proxy),當(dāng)有注解的方法被調(diào)用的時(shí)候功戚,實(shí)際上是代理對(duì)象調(diào)用的娶眷,代理對(duì)象在調(diào)用之前會(huì)開啟事務(wù),執(zhí)行事務(wù)的操作啸臀,但是同類中的方法互相調(diào)用届宠,相當(dāng)于this.B(),此時(shí)的B方法并非是代理對(duì)象調(diào)用乘粒,而是直接通過原有的Bean直接調(diào)用豌注,所以注解會(huì)失效)
4、業(yè)務(wù)代碼拋出異常類型非RuntimeException灯萍,事務(wù)失效
5轧铁、業(yè)務(wù)代碼中存在異常時(shí),使用try…catch…語句塊捕獲旦棉,而catch語句塊沒有throw new RuntimeExecption異常(最難被排查到問題且容易忽略)
6齿风、注解@Transactional中Propagation屬性值設(shè)置錯(cuò)誤即Propagation.NOT_SUPPORTED(一般不會(huì)設(shè)置此種傳播機(jī)制)
7、mysql關(guān)系型數(shù)據(jù)庫绑洛,且存儲(chǔ)引擎是MyISAM而非InnoDB救斑,則事務(wù)會(huì)不起作用(基本開發(fā)中不會(huì)遇到)
@Transactional原理
@Transactional是基于Spring AOP的,以@Transactional注解為連接點(diǎn)真屯,@Transactional注解的切面邏輯類似于@Around
Spring Boot-啟動(dòng)原理
@SpringBootApplication注解:
1脸候、@SpringBootConfiguration注解:繼承Configuration,表示啟動(dòng)類是IOC容器的配置類
2、@EnableAutoConfiguration注解:通過@AutoConfigurationPackage注解獲取自動(dòng)配置包运沦,返回當(dāng)前主類的同級(jí)以及子級(jí)的中斷自動(dòng)配置組件泵额;開啟springboot的配置功能,借助@Import({EnableAutoConfigurationImportSelector.class})實(shí)現(xiàn)携添,將自動(dòng)配置組件對(duì)應(yīng)的bean定義都加載到IoC容器中梯刚,通過Spring的SpringFactoriesLoader(Spring工廠加載器)去讀取META-INF/spring.factories中的配置類信息,通過反射生成一個(gè)配置類(里面有許多bean定義)薪寓,最后將這些bean定義加載到IOC容器中(但是不是所有存在于spring.factories中的配置都進(jìn)行加載,而是通過@ConditionalOnClass注解進(jìn)行判斷條件是否成立(只要導(dǎo)入相應(yīng)的stater澜共,條件就能成立)向叉,如果條件成立則加載配置類,否則不加載該配置類)
https://www.cnblogs.com/xiaopotian/p/11052917.html
3嗦董、@ComponentScan注解:自動(dòng)掃描并加載符合條件的組件(如@Component和@Repository等)或者bean定義母谎,最終將這些bean加載到IoC容器中,在beanFactoryProcessor中調(diào)用
run方法:
1京革、創(chuàng)建監(jiān)聽器
2奇唤、加載配置環(huán)境
3、創(chuàng)建ConfigurableApplicationContext
4匹摇、spring.factories加載咬扇,bean實(shí)例化
Spring Cloud
分布式事務(wù)
CAP:一致性、可用性廊勃、容錯(cuò)性
BASE:可用性懈贺、容錯(cuò)性、最終一致性(可能會(huì)存在中間狀態(tài)如:處理中)
1坡垫、兩階段提交(2PC)(基于數(shù)據(jù)庫梭灿,MySQL和Oracle支持):準(zhǔn)備階段(資源鎖定,執(zhí)行本地事務(wù)冰悠,并寫日志undo(修改后數(shù)據(jù))/redo日志(修改前數(shù)據(jù)))堡妒、提交/回滾階段,中間由事務(wù)管理器控制全局事務(wù)溉卓,資源鎖需要等到兩個(gè)階段結(jié)束才釋放皮迟,性能較差,會(huì)出現(xiàn)死鎖問題
2的诵、seata改進(jìn)2PC:事務(wù)管理器(事務(wù)發(fā)起服務(wù)引入万栅,負(fù)責(zé)發(fā)起全局事務(wù),發(fā)起全局提交或全局回滾的指令)西疤、事務(wù)協(xié)調(diào)器(單獨(dú)的服務(wù)烦粒,控制,維護(hù)全局事務(wù)狀態(tài),協(xié)調(diào)各分支事務(wù)提交/回滾)扰她、資源管理器(控制每個(gè)分支事務(wù)兽掰,使用DataSourceProxy連接數(shù)據(jù)庫,使用ConnectionProxy操作數(shù)據(jù)庫徒役,目的就是在第一階段執(zhí)行本地事務(wù)的同時(shí)孽尽,寫入undo_log表(保存修改前和修改后的數(shù)據(jù)),因此第一階段就能進(jìn)行事務(wù)提交忧勿,并釋放資源杉女;第二階段提交時(shí)只需要?jiǎng)h除undo_log表數(shù)據(jù),回滾時(shí)反向執(zhí)行即可)
3鸳吸、TCC:預(yù)處理Try(業(yè)務(wù)檢查(一致性)及資源預(yù)留(隔離)執(zhí)行)熏挎、確認(rèn) Confirm(確認(rèn)提交)、撤銷Cancel(回滾)晌砾;如處理表(中間有狀態(tài)坎拐、流水號(hào))、被調(diào)用方保持冪等养匈、并提供查詢接口/回調(diào)
4哼勇、可靠消息最終一致性:本地消息表+消息中間件(通過本地事務(wù)保證數(shù)據(jù)業(yè)務(wù)操作和消息的一致性,然后通過定時(shí)任務(wù)將消息發(fā)送至消息中間件呕乎,待確認(rèn)消息發(fā)送給消費(fèi)方成功再將消息刪除)
要解決以下問題:
**本地事務(wù)與消息發(fā)送的原子性問題
**事務(wù)參與方接收消息的可靠性(一定能夠接收到消息)
**消息重復(fù)消費(fèi)的問題(冪等性)
MQ的ack(即消息確認(rèn))機(jī)制积担,消費(fèi)者監(jiān)聽MQ,如果消費(fèi)者接收到消息并且業(yè)務(wù)處理完成后向MQ 發(fā)送ack(即消息確認(rèn))猬仁,此時(shí)說明消費(fèi)者正常消費(fèi)消息完成磅轻,MQ將不再向消費(fèi)者推送消息,否則消費(fèi)者會(huì)不斷重試向消費(fèi)者來發(fā)送消息
5逐虚、最大努力通知
zookeeper
zookeeper是一個(gè)典型的分布式數(shù)據(jù)一致性解決方案
節(jié)點(diǎn)類型:持久節(jié)點(diǎn)聋溜、持久順序節(jié)點(diǎn)、臨時(shí)節(jié)點(diǎn)(客戶端會(huì)話叭爱,非TCP連接撮躁,只能作為葉子節(jié)點(diǎn))、臨時(shí)順序節(jié)點(diǎn)
集群:Leader买雾、Follower把曼、Observer(不參與Leader選舉、也不參與寫操作的“過半寫成功”策略)
Leader選舉:
分布式鎖
Redis分布式鎖:
1漓穿、加鎖:set 命令要用 set key value px milliseconds nx嗤军,替代 setnx + expire 需要分兩次執(zhí)行命令的方式,保證了原子性晃危;給鎖加上一個(gè)過期時(shí)間叙赚,即使Redis客戶端中間出現(xiàn)異常(來不及調(diào)用lua腳本釋放鎖)也可以保證過期時(shí)鎖會(huì)自動(dòng)釋放(但是如果Redis服務(wù)端異常就沒辦法解決)老客;超時(shí)問題解決:lua腳本+額外線程進(jìn)行鎖延時(shí)
2、解鎖:將lua腳本傳到j(luò)edis.eval()方法里震叮,并使參數(shù)KEYS[1]賦值為lockKey(鎖標(biāo)志)胧砰,ARGV[1]賦值為requestId(Redis客戶端標(biāo)志);在執(zhí)行的時(shí)候苇瓣,首先會(huì)獲取鎖對(duì)應(yīng)的value值尉间,檢查是否與requestId相等,如果相等則解鎖(刪除key)击罪;比較requestId是為了解決超時(shí)問題:如果在加鎖和釋放鎖之間的邏輯執(zhí)行的太長哲嘲,以至于超出了鎖的超時(shí)限制,就會(huì)出現(xiàn)問題媳禁,因?yàn)檫@時(shí)候鎖過期了撤蚊,第二個(gè)線程重新持有了這把鎖,但是緊接著第一個(gè)線程執(zhí)行完了業(yè)務(wù)邏輯损话,就把鎖給釋放了,第三個(gè)線程就會(huì)在第二個(gè)線程邏輯執(zhí)行完之前拿到了鎖
3槽唾、可重入性:對(duì)客戶端的 set 方法進(jìn)行包裝丧枪,使用線程的 Threadlocal 變量存儲(chǔ)當(dāng)前線程持有鎖的計(jì)數(shù),還需要考慮內(nèi)存鎖計(jì)數(shù)的過期時(shí)間
問題:如果存儲(chǔ)鎖對(duì)應(yīng)key的那個(gè)節(jié)點(diǎn)掛了的話庞萍,就可能存在丟失鎖的風(fēng)險(xiǎn)拧烦,導(dǎo)致出現(xiàn)多個(gè)客戶端持有鎖的情況,這樣就不能實(shí)現(xiàn)資源的獨(dú)占了(即Redis服務(wù)端出現(xiàn)問題)钝计,即使是Redis主從也不能解決問題(Redis的主從同步通常是異步的)
解決:
1恋博、Redlock算法:輪流嘗試在每個(gè)節(jié)點(diǎn)上創(chuàng)建鎖,過期時(shí)間較短私恬,一般就幾十毫秒债沮,至少要在大多數(shù)節(jié)點(diǎn)上成功創(chuàng)建鎖,才說明獲取到鎖本鸣,客戶端計(jì)算創(chuàng)建鎖的時(shí)間疫衩,如果創(chuàng)建鎖的時(shí)間小于超時(shí)時(shí)間,就是創(chuàng)建成功了荣德;如果創(chuàng)建鎖失敗了闷煤,那么就依次刪除以前創(chuàng)建過的鎖;如果其他客戶端已經(jīng)創(chuàng)建鎖涮瞻,就得不斷輪詢?nèi)L試獲取鎖
2鲤拿、Redisson:RedissonLock 同樣沒有解決節(jié)點(diǎn)掛掉的時(shí)候,存在丟失鎖的風(fēng)險(xiǎn)的問題署咽;Redisson 提供了實(shí)現(xiàn)了redlock算法的 RedissonRedLock近顷,RedissonRedLock 真正解決了單點(diǎn)失敗的問題,代價(jià)是需要額外的為 RedissonRedLock 搭建Redis環(huán)境
zookeeper分布式鎖:
獲取鎖:客戶端獲取鎖時(shí),調(diào)用create方法創(chuàng)建臨時(shí)順序節(jié)點(diǎn)(Zookeeper會(huì)保證所有的客戶端中幕庐,最終只有一個(gè)客戶端能夠創(chuàng)建成功久锥,沒有獲取到鎖的客戶端需要?jiǎng)?chuàng)建一個(gè)節(jié)點(diǎn)去Watch監(jiān)聽鎖節(jié)點(diǎn))
釋放鎖:當(dāng)前獲取鎖的客戶端宕機(jī)/業(yè)務(wù)邏輯執(zhí)行完都會(huì)移除臨時(shí)順序鎖節(jié)點(diǎn),并通知所有Watch節(jié)點(diǎn)去重新嘗試獲取鎖
Redis-基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)
1异剥、String:類似于ArrayList瑟由,字節(jié)數(shù)組,用途:緩存用戶信息(序列化和反序列化)
2冤寿、List:類似于LinkedList歹苦,鏈表+壓縮列表(數(shù)據(jù)量少時(shí),只用壓縮列表)督怜,增刪快殴瘦,查詢慢,用途:異步隊(duì)列
3号杠、Hash:類似于HashMap蚪腋,數(shù)組+鏈表,漸進(jìn)式reHash(中間新舊數(shù)據(jù)都會(huì)讀)姨蟋,用途:緩存用戶信息
4屉凯、Set:類似于HashSet,value值為空眼溶,用途:去重
5悠砚、ZSet: 類似于SortedSet 和 HashMap 的結(jié)合體,跳躍列表堂飞,既要隨機(jī)增刪灌旧,又要排序,用途:核心企業(yè)/供應(yīng)商列表
6绰筛、跳躍列表:每一層是一個(gè)單向鏈表枢泰,每一層有一個(gè)額外的節(jié)點(diǎn)去定位每一層的頭節(jié)點(diǎn)
Redis-緩存一致性
1、先刪除緩存铝噩,再更新數(shù)據(jù)庫(緩存設(shè)置過期時(shí)間)
問題:如果兩個(gè)并發(fā)操作宗苍,一個(gè)讀操作,一個(gè)寫操作薄榛,寫操作刪除緩存讳窟,讀操作從緩存讀取數(shù)據(jù)失敗,從數(shù)據(jù)庫讀取數(shù)據(jù)成功敞恋,然后更新緩存丽啡,寫操作更新數(shù)據(jù)庫,無法避免這種情況的緩存一致性
解決:延遲雙刪:寫操作更新數(shù)據(jù)庫成功后硬猫,sleep(睡眠時(shí)間不好控制)一段時(shí)間补箍,再刪除一次緩存
2改执、先更新數(shù)據(jù)庫,再刪除緩存(緩存設(shè)置過期時(shí)間)(推薦)
原理:更新數(shù)據(jù)庫時(shí)坑雅,會(huì)加鎖辈挂,其他操作不能操作這條數(shù)據(jù)
問題:寫操作時(shí),更新數(shù)據(jù)庫成功裹粤,刪除緩存失敗终蒂,讀操作仍讀取緩存中的舊數(shù)據(jù)
解決:
**消息隊(duì)列:更新數(shù)據(jù)庫,插入本地消息表遥诉,刪除緩存拇泣,消息隊(duì)列重試去刪除緩存(需要考慮消息隊(duì)列的一些常見問題)
**消息隊(duì)列+binlog日志:更新數(shù)據(jù)庫時(shí),會(huì)插入binlog日志矮锈,通過canal讀取binlog日志霉翔,推送給消息隊(duì)列,消息隊(duì)列重試去刪除緩存(與業(yè)務(wù)代碼解耦)
3苞笨、為什么是刪除緩存债朵,而不是更新緩存
**問題1:如果兩個(gè)并發(fā)操作,一個(gè)寫操作a瀑凝,一個(gè)寫操作b序芦,a更新數(shù)據(jù)庫,釋放鎖猜丹,b更新數(shù)據(jù)庫,釋放鎖硅卢,b更新緩存完成射窒,a更新緩存完成,無法避免這種情況的緩存一致性
**問題2:每次都更新緩存将塑,會(huì)導(dǎo)致性能消耗
Redis-緩存穿透脉顿、緩存雪崩、緩存擊穿
緩存穿透
定義:查找一定不存在的數(shù)據(jù)(如惡意攻擊)点寥,在緩存中根據(jù)key查不到艾疟,在數(shù)據(jù)庫中也查詢不到,所以不會(huì)存到緩存中敢辩,每次都要查詢數(shù)據(jù)庫
解決:
1蔽莱、布隆過濾:將一切可能查詢的key存到map中,請(qǐng)求過來時(shí)先去map中查找戚长,不存在直接丟棄
2盗冷、即使在數(shù)據(jù)庫中查詢不到,空值也存到緩存中同廉,設(shè)置過期時(shí)間(浪費(fèi)空間仪糖,且在有效期內(nèi)可能數(shù)據(jù)不一致)
緩存雪崩
定義:緩存中大量的數(shù)據(jù)同時(shí)失效(如服務(wù)掛掉和同時(shí)過期)柑司,直接去數(shù)據(jù)庫中查詢
解決:
1、過期時(shí)間設(shè)置均勻(避免同時(shí)過期)(事前)
2锅劝、緩存服務(wù)高可用(主從+哨兵)(事前)
3攒驰、限流:緩存失效時(shí),通過加鎖或者隊(duì)列來控制讀數(shù)據(jù)庫寫緩存的線程數(shù)量(如對(duì)某個(gè)key只允許一個(gè)線程查詢數(shù)據(jù)和寫緩存故爵,其他線程等待)玻粪,避免數(shù)據(jù)庫崩掉(事中)
4、Redis 持久化:一旦重啟稠集,自動(dòng)從磁盤上加載數(shù)據(jù)奶段,快速恢復(fù)緩存數(shù)據(jù)(事后)
緩存擊穿
定義:大量請(qǐng)求同時(shí)訪問一個(gè)或多個(gè)熱點(diǎn)key,key如果瞬間失效剥纷,會(huì)直接請(qǐng)求數(shù)據(jù)庫痹籍,導(dǎo)致數(shù)據(jù)庫崩掉
解決:
1、若緩存的數(shù)據(jù)是基本不會(huì)發(fā)生更新的晦鞋,則可嘗試將該熱點(diǎn)數(shù)據(jù)設(shè)置為永不過期
2蹲缠、若緩存的數(shù)據(jù)更新不頻繁,且緩存刷新的整個(gè)流程耗時(shí)較少的情況下悠垛,則可以采用基于 Redis线定、zookeeper 等分布式中間件的分布式互斥鎖,或者本地互斥鎖以保證僅少量的請(qǐng)求能請(qǐng)求數(shù)據(jù)庫并重新構(gòu)建緩存确买,其余線程則在鎖釋放后能訪問到新緩存
3斤讥、若緩存的數(shù)據(jù)更新頻繁或者緩存刷新的流程耗時(shí)較長的情況下,可以利用定時(shí)任務(wù)在緩存過期前主動(dòng)的重新構(gòu)建緩存或者延后緩存的過期時(shí)間湾趾,以保證所有的請(qǐng)求能一直訪問到對(duì)應(yīng)的緩存
Redis-持久化
1芭商、RDB全量快照(數(shù)據(jù))直接復(fù)制,快
需要解決的問題:不能影響服務(wù)響應(yīng)搀缠,如何保證邊持久化邊響應(yīng)
解決:使用Copy On Write機(jī)制來實(shí)現(xiàn)快照持久化
原理:Redis 在持久化時(shí)會(huì)產(chǎn)生一個(gè)子進(jìn)程铛楣,子進(jìn)程剛剛產(chǎn)生時(shí),和父進(jìn)程共享內(nèi)存里面的代碼段和數(shù)據(jù)段艺普,這是 Linux 操作系統(tǒng)的機(jī)制簸州,在進(jìn)程分離的一瞬間,內(nèi)存的增長幾乎沒有明顯變化歧譬;子進(jìn)程只做數(shù)據(jù)持久化岸浑,不會(huì)修改現(xiàn)有的內(nèi)存數(shù)據(jù)結(jié)構(gòu),只是對(duì)數(shù)據(jù)結(jié)構(gòu)進(jìn)行遍歷讀取瑰步,然后序列化寫到磁盤中助琐;當(dāng)父進(jìn)程對(duì)其中一個(gè)頁面的數(shù)據(jù)進(jìn)行修改時(shí),會(huì)將被共享的頁面復(fù)制一份分離出來面氓,然后對(duì)這個(gè)復(fù)制的頁面進(jìn)行修改兵钮,這時(shí)子進(jìn)程相應(yīng)的頁面是沒有變化的蛆橡,還是進(jìn)程產(chǎn)生時(shí)那一瞬間的數(shù)據(jù)
2、AOF增量日志(指令)需要指令重放掘譬,慢泰演,一般是先存到磁盤,然后再執(zhí)行指令
需要解決的問題:文件會(huì)越來越大葱轩,需要定期進(jìn)行AOF重寫睦焕;服務(wù)宕機(jī),數(shù)據(jù)丟失的問題
原理:創(chuàng)建子進(jìn)程對(duì)內(nèi)存遍歷轉(zhuǎn)換成指令靴拱,序列化到一個(gè)新的AOF日志文件中垃喊;序列化完畢后再將操作期間發(fā)生的增量AOF日志追加到這個(gè)新的AOF日志文件中,追加完畢后就立即替代舊的AOF日志文件袜炕,瘦身工作就完成了本谜;AOF日志在內(nèi)存緩存中,需要異步將數(shù)據(jù)刷回到磁盤偎窘,如果機(jī)器突然宕機(jī)乌助,AOF日志內(nèi)容可能還沒有來得及完全刷到磁盤中,這個(gè)時(shí)候就會(huì)出現(xiàn)日志丟失陌知,因此需要每隔 1s(可配置)左右執(zhí)行一次 fsync 操作(強(qiáng)制刷新到緩存)
3他托、通常 Redis 的主節(jié)點(diǎn)不會(huì)進(jìn)行持久化操作,持久化操作主要在從節(jié)點(diǎn)進(jìn)行仆葡,從節(jié)點(diǎn)是備份節(jié)點(diǎn)赏参,沒有來自客戶端請(qǐng)求的壓力
4、混合持久化:重啟 Redis 時(shí)沿盅,很少使用 RDB 來恢復(fù)內(nèi)存狀態(tài)把篓,因?yàn)闀?huì)丟失大量數(shù)據(jù)(重啟期間的數(shù)據(jù)沒有保存),通常使用 AOF 日志重放(重啟期間的數(shù)據(jù)會(huì)追加)嗡呼,但是重放 AOF 日志性能相對(duì) RDB 來說要慢很多纸俭,這樣在 Redis 實(shí)例很大的情況下皇耗,啟動(dòng)需要花費(fèi)很長的時(shí)間南窗;可以將RDB的內(nèi)容和增量的AOF存放在一起,這里的AOF不再是全量的日志郎楼,而是自持久化開始到持久化結(jié)束的這段時(shí)間發(fā)生的增量 AOF日志万伤,通常這部分AOF日志很小,于是在Redis重啟的時(shí)候呜袁,開啟AOF敌买,先加載RDB的內(nèi)容,然后再重放增量AOF日志就可以完全替代之前的AOF全量文件重放阶界,重啟效率因此大幅得到提升
Redis-過期策略
Redis是單線的虹钮,刪除也會(huì)占用線程的時(shí)間聋庵,Redis采用的是定期刪除 + 懶惰刪除策略(定期刪除是集中處理,惰性刪除是零散處理)
定期刪除策略
Redis單線程默認(rèn)會(huì)每秒進(jìn)行十次過期掃描芙粱,過期掃描不會(huì)遍歷過期字典中所有的 key祭玉,而是采用了一種簡單的貪心策略
1、從過期字典中隨機(jī) 20 個(gè) key
2春畔、刪除這 20 個(gè) key 中已經(jīng)過期的 key
3脱货、如果過期的 key 比率超過 1/4,那就重復(fù)步驟 1
4律姨、同時(shí)振峻,為了保證過期掃描不會(huì)出現(xiàn)循環(huán)過度,導(dǎo)致線程卡死現(xiàn)象择份,算法還增加了掃描時(shí)間的上限扣孟,默認(rèn)不會(huì)超過 25ms
為什么不掃描所有的過期key?
1缓淹、會(huì)導(dǎo)致線上讀寫請(qǐng)求出現(xiàn)明顯的卡頓現(xiàn)象(所以要盡量避免大量key同時(shí)過期)
2哈打、即使掃描有 25ms 的時(shí)間上限:假如有 101 個(gè)客戶端同時(shí)將請(qǐng)求發(fā)過來了,然后前 100 個(gè)請(qǐng)求的執(zhí)行時(shí)間都是25ms讯壶,那么第 101 個(gè)指令需要等待多久才能執(zhí)行料仗?2500ms(因?yàn)閱尉€程),這個(gè)就是客戶端的卡頓時(shí)間
從庫的過期策略
1伏蚊、從庫不會(huì)進(jìn)行過期掃描立轧,從庫對(duì)過期的處理是被動(dòng)的,主庫在 key 到期時(shí)躏吊,會(huì)在 AOF 文件里增加一條 del 指令氛改,同步到所有的從庫,從庫通過執(zhí)行這條 del 指令來刪除過期的key
2比伏、因?yàn)橹噶钔绞钱惒竭M(jìn)行的胜卤,所以主庫過期的 key 的 del 指令沒有及時(shí)同步到從庫的話,會(huì)出現(xiàn)主從數(shù)據(jù)的不一致赁项,主庫沒有的數(shù)據(jù)在從庫里還存在
懶惰刪除策略
為什么要懶惰刪除葛躏?
1、刪除指令 del 會(huì)直接釋放對(duì)象的內(nèi)存悠菜,大部分情況下舰攒,這個(gè)指令非常快悔醋,沒有明顯延遲摩窃,不過如果刪除的 key 是一個(gè)非常大的對(duì)象,比如一個(gè)包含了千萬元素的 hash芬骄,那么刪除操作就會(huì)導(dǎo)致單線程卡頓
2猾愿、Redis 內(nèi)部實(shí)際上并不是只有一個(gè)主線程鹦聪,它還有幾個(gè)異步線程專門用來處理一些耗時(shí)的操作,可以用異步線程實(shí)現(xiàn)懶惰刪除
Redis-內(nèi)存淘汰機(jī)制
1蒂秘、noeviction:當(dāng)內(nèi)存超出最大內(nèi)存椎麦,寫入請(qǐng)求會(huì)報(bào)錯(cuò),但是刪除和讀請(qǐng)求可以繼續(xù)(一般不使用材彪,但是是默認(rèn)的)
2观挎、allkeys-lru:當(dāng)內(nèi)存超出最大內(nèi)存,在所有的key中段化,移除最少使用的key嘁捷,只把Redis當(dāng)作緩存時(shí)使用(推薦)
3、allkeys-random:當(dāng)內(nèi)存超出最大內(nèi)存显熏,在所有的key中雄嚣,隨機(jī)移除某個(gè)key(一般不使用)
4、volatile-lru:當(dāng)內(nèi)存超出最大內(nèi)存喘蟆,在設(shè)置了過期時(shí)間key的字典中缓升,移除最少使用的key(不會(huì)移除沒有設(shè)置過期時(shí)間的),把Redis既當(dāng)緩存蕴轨,又做持久化的時(shí)候使用
5港谊、volatile-random:當(dāng)內(nèi)存超出最大內(nèi)存,在設(shè)置了過期時(shí)間key的字典中橙弱,隨機(jī)移除某個(gè)key(不會(huì)移除沒有設(shè)置過期時(shí)間的)
6歧寺、volatile-ttl:當(dāng)內(nèi)存超出最大內(nèi)存,在設(shè)置了過期時(shí)間key的字典中棘脐,優(yōu)先移除剩余時(shí)間ttl 最少的(不會(huì)移除沒有設(shè)置過期時(shí)間的)
LRU算法:
1斜筐、實(shí)現(xiàn) LRU 算法除了需要 key/value 字典外,還需要附加一個(gè)鏈表蛀缝,鏈表中的元素按照一定的順序進(jìn)行排列
2顷链、當(dāng)空間滿的時(shí)候,會(huì)踢掉鏈表尾部的元素
3屈梁、當(dāng)字典的某個(gè)元素被訪問時(shí)嗤练,它在鏈表中的位置會(huì)被移動(dòng)到表頭,所以鏈表的元素排列順序就是元素最近被訪問的時(shí)間順序
4俘闯、位于鏈表尾部的元素就是不被重用的元素潭苞,所以會(huì)被踢掉忽冻;位于表頭的元素就是最近剛剛被人用過的元素真朗,所以暫時(shí)不會(huì)被踢(雙向鏈表)
近似 LRU 算法
1、Redis 使用的是一種近似 LRU 算法僧诚,之所以不使用 LRU 算法遮婶,是因?yàn)樾枰拇罅康念~外的內(nèi)存蝗碎,需要對(duì)現(xiàn)有的數(shù)據(jù)結(jié)構(gòu)進(jìn)行較大的改造
2、近似LRU 算法則很簡單旗扑,在現(xiàn)有數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ)上使用隨機(jī)采樣法+額外字段(最后一次被訪問的時(shí)間戳)來淘汰元素蹦骑,能達(dá)到和 LRU 算法非常近似的效果
LFU算法
1、Redis 4.0 里引入了一個(gè)新的淘汰策略 —— LFU(最近最少使用)算法
2臀防、LFU 表示按最近的訪問頻率進(jìn)行淘汰眠菇,它比 LRU 更加精準(zhǔn)地表示了一個(gè) key 被訪問的熱度
3、如果一個(gè) key 長時(shí)間不被訪問袱衷,只是剛剛偶然被用戶訪問了一下捎废,那么在使用 LRU 算法下它是不容易被淘汰的,因?yàn)?LRU 算法認(rèn)為當(dāng)前這個(gè) key 是很熱的
4致燥、而 LFU 是需要追蹤最近一段時(shí)間的訪問頻率登疗,如果某個(gè) key 只是偶然被訪問一次是不足以變得很熱的,它需要在近期一段時(shí)間內(nèi)被訪問很多次才有機(jī)會(huì)被認(rèn)為很熱
Redis-哨兵
1嫌蚤、負(fù)責(zé)持續(xù)監(jiān)控主從節(jié)點(diǎn)的健康辐益,當(dāng)主節(jié)點(diǎn)掛掉時(shí),自動(dòng)選擇一個(gè)最優(yōu)的從節(jié)點(diǎn)切換為主節(jié)點(diǎn)
2脱吱、客戶端來連接主從時(shí)智政,會(huì)首先連接哨兵,通過哨兵來查詢主節(jié)點(diǎn)的地址箱蝠,然后再去連接主節(jié)點(diǎn)進(jìn)行數(shù)據(jù)交互
3女仰、當(dāng)主節(jié)點(diǎn)發(fā)生故障時(shí),客戶端會(huì)重新向哨兵獲取主節(jié)點(diǎn)地址抡锈,哨兵會(huì)將最新的主節(jié)點(diǎn)地址告訴客戶端疾忍,無需重啟即可自動(dòng)完成節(jié)點(diǎn)切換
4、主節(jié)點(diǎn)掛掉了床三,原先的主從復(fù)制也斷開了一罩,客戶端和損壞的主節(jié)點(diǎn)也斷開了,從節(jié)點(diǎn)被提升為新的主節(jié)點(diǎn)撇簿,其它從節(jié)點(diǎn)開始和新的主節(jié)點(diǎn)建立復(fù)制關(guān)系聂渊,客戶端通過新的主節(jié)點(diǎn)繼續(xù)進(jìn)行交互
5、哨兵會(huì)持續(xù)監(jiān)控已經(jīng)掛掉了主節(jié)點(diǎn)四瘫,待它恢復(fù)后汉嗽,集群會(huì)進(jìn)行調(diào)整,原先掛掉的主節(jié)點(diǎn)現(xiàn)在變成了從節(jié)點(diǎn)找蜜,從現(xiàn)在的主節(jié)點(diǎn)那里建立復(fù)制關(guān)系
6饼暑、哨兵進(jìn)行主從切換時(shí),客戶端如何知道地址變更了 ? 在建立連接的時(shí)候進(jìn)行了主節(jié)點(diǎn)地址變更判斷,查詢主節(jié)點(diǎn)地址弓叛,然后跟內(nèi)存中的主節(jié)點(diǎn)地址進(jìn)行比對(duì)彰居,如果變更了,就斷開所有連接撰筷,重新使用新地址建立新連接陈惰;如果是舊的主節(jié)點(diǎn)掛掉了,那么所有正在使用的連接都會(huì)被關(guān)閉毕籽,然后在重連時(shí)就會(huì)用上新地址
Redis-應(yīng)用
1抬闯、Scan:掃描海量數(shù)據(jù)(有游標(biāo))
2、HyperLogLog:統(tǒng)計(jì)UV
3关筒、布隆過濾器:推薦去重(布隆過濾器能準(zhǔn)確過濾掉那些已經(jīng)看過的內(nèi)容画髓,那些沒有看過的新內(nèi)容,它也會(huì)過濾掉極小一部分 (誤判))平委,當(dāng)布隆過濾器說某個(gè)值存在時(shí)奈虾,這個(gè)值可能不存在;當(dāng)它說不存在時(shí)廉赔,那就肯定不存在
原理:對(duì)key多次無偏hash肉微,每個(gè)hash取模數(shù)組長度徘郭,確定位置鼓黔,將該位置置為1徐绑;查詢是否存在時(shí)做鹰,再次對(duì)key多次無偏hash,取模诊杆,確定位置是否為1船侧;位置可以重復(fù)利用蔬螟,因此會(huì)有誤差
HashMap
MySQL-索引
B+樹索引的優(yōu)點(diǎn):
1琅摩、索引按照順序存儲(chǔ)數(shù)據(jù)铁孵,可以用來做ORDER BY和GROUP BY操作
2、索引中存儲(chǔ)了實(shí)際的索引列值房资,所以某些査詢只使用索引就能夠完成全部査詢(非一級(jí)索引的葉子節(jié)點(diǎn)存儲(chǔ)主鍵)
3蜕劝、索引大大減少了服務(wù)器需要掃描的數(shù)據(jù)量
4、索引可以幫助服務(wù)器避免排序和臨時(shí)表
5轰异、索引可以將隨機(jī)I/O變?yōu)轫樞騃/O
B+樹索引的缺點(diǎn):
1岖沛、如果不是按照索引的最左列開始査找,則無法使用索引
2搭独、不能跳過索引中的列(只能使用跳過前的列)
3婴削、如果查詢中有某個(gè)列的范圍査詢,則其右邊所有列都無法使用索引優(yōu)化査找(但是可以作為值返回)牙肝;如果范圍査詢列值的數(shù)量有限唉俗,那么可以使用多個(gè)等于條件來代替范圍條件
索引策略
1嗤朴、獨(dú)立的列:索引列不能是表達(dá)式的一部分,也不能是函數(shù)的參數(shù)互躬,因此要始終將索引列單獨(dú)放在比較符號(hào)的一側(cè)
2、前綴索引和索引選擇性:前綴越長颂郎,選擇性越好
3吼渡、聚合(多列)索引:
當(dāng)出現(xiàn)服務(wù)器對(duì)多個(gè)索引做相交操作時(shí)(通常有多個(gè)AND條件)或?qū)Χ鄠€(gè)索引做聯(lián)合操作時(shí)(通常有多個(gè)OR條件),通常意味著需要一個(gè)包含所有相關(guān)列的多列索引乓序,而不是多個(gè)獨(dú)立的單列索引(需要合并)
多列索引中索引的順序(需要兼顧排序和分組):當(dāng)不需要考慮排序和分組時(shí)寺酪,將選擇性最高的列放在前面通常是很好的
4、聚簇索引:當(dāng)表有聚簇索引時(shí)替劈,它的數(shù)據(jù)行實(shí)際上存放在索引的葉子頁中寄雀,因?yàn)闊o法同時(shí)把數(shù)據(jù)行存放在兩個(gè)不同的地方,所以一個(gè)表只能有一個(gè)聚簇索引(覆蓋索引可以模擬多個(gè)聚簇索引的情況)
5陨献、二級(jí)索引:訪問需要兩次索引査找盒犹,而不是一次,因?yàn)槎?jí)索引葉子節(jié)點(diǎn)保存的不是指向行的物理位置的指針眨业,而是行的主鍵值急膀,這意味著通過二級(jí)索引查找行,存儲(chǔ)引擎需要找到二級(jí)索引的葉子節(jié)點(diǎn)獲得對(duì)應(yīng)的主鍵值龄捡,然后根據(jù)這個(gè)值去聚簇索引中査找到對(duì)應(yīng)的行卓嫂,這里做了重復(fù)的工作:兩次B+樹査找而不是一次(回表查詢)
6、普通索引和唯一索引:普通索引在查找到一條記錄后會(huì)繼續(xù)查找聘殖,而唯一索引會(huì)終止查找
使用索引排序
1晨雳、只有當(dāng)索引的列順序和ORDER BY子句的順序完全一致,并且所有列的排序方向(倒序或正序)都一樣時(shí)奸腺,MySQL才能夠使用索引來對(duì)結(jié)果做排序
2餐禁、如果査詢需要關(guān)聯(lián)多張表,則只有當(dāng)ORDER BY子句引用的字段全部為第一個(gè)表時(shí)突照,才能使用索引做排序
3坠宴、ORDER BY子句和查找型查詢的限制是一樣的:需要滿足索引的最左前綴的要求;否則MySQL都需要執(zhí)行排序操作绷旗,而無法利用索引排序
有一種情況下ORDER BY子句可以不滿足索引的最左前綴的要求喜鼓,就是前導(dǎo)列為常量的時(shí)候;即如果WHERE子句或者JOIN子句中對(duì)這些列指定了常量衔肢,就可以“彌補(bǔ)”索引的不足
B樹與B+樹的區(qū)別
1庄岖、B樹可能在非葉子節(jié)點(diǎn)命中返回;B+不可能在非葉子結(jié)點(diǎn)命中
2角骤、B+樹葉子節(jié)點(diǎn)存放所有數(shù)據(jù)隅忿;B+樹葉子節(jié)點(diǎn)之間又是一個(gè)鏈表
MySQL-事務(wù)
事務(wù)的特性
1心剥、原子性:一個(gè)事務(wù)必須被視為一個(gè)不可分割的最小工作單元,整個(gè)事務(wù)中的所有操作要么全部提交成功背桐,要么全部失敗回滾
2优烧、一致性:數(shù)據(jù)庫總是從一個(gè)一致性的狀態(tài)轉(zhuǎn)換到另外一個(gè)一致性的狀態(tài)
3、隔離性:通常來說(涉及到隔離級(jí)別)链峭,一個(gè)事務(wù)所做的修改在最終提交以前畦娄,對(duì)其他事務(wù)是不可見的
4、持久性:通常來說(涉及到持久級(jí)別)弊仪,一旦事務(wù)提交熙卡,則其所做的修改就會(huì)永久保存到數(shù)據(jù)庫中,此時(shí)即使系統(tǒng)崩潰励饵,修改的數(shù)據(jù)也不會(huì)丟失
事務(wù)的隔離級(jí)別
1驳癌、讀未提交:一個(gè)事務(wù)可以讀取到其他事務(wù)未提交的數(shù)據(jù)(臟讀)
2、讀已提交(不可重復(fù)讀):一個(gè)事務(wù)只能讀取到其他事務(wù)此刻已提交的數(shù)據(jù)
3役听、可重復(fù)讀:一個(gè)事務(wù)只能讀取到其他事務(wù)在該事務(wù)開啟時(shí)已提交的數(shù)據(jù)(快照讀)颓鲜,但是無法避免幻讀(單指插入,兩次讀取的結(jié)果不一致)
可重復(fù)讀是MySQL的默認(rèn)事務(wù)隔離級(jí)別典予,InnoDB通過MVCC(多版本并發(fā)控制)和next-key lock解決了幻讀
4灾杰、串行讀:強(qiáng)制事務(wù)串行執(zhí)行,解決了幻讀問題熙参,在讀取的每一行數(shù)據(jù)上都加鎖艳吠,所以可能導(dǎo)致大量的超時(shí)和鎖爭用的問題
幻讀:
1、幻讀指的是一個(gè)事務(wù)在前后兩次查詢同一個(gè)范圍的時(shí)候孽椰,后一次查詢看到了前一次查詢沒有看到的行(單指插入)昭娩;在可重復(fù)讀隔離級(jí)別下,普通的查詢是快照讀黍匾,是不會(huì)看到別的事務(wù)插入的數(shù)據(jù)的(MVCC)栏渺,幻讀在“當(dāng)前讀”下仍會(huì)出現(xiàn)(加鎖讀時(shí),只鎖當(dāng)前滿足條件的行)
2锐涯、通過加間隙鎖解決當(dāng)前讀導(dǎo)致的幻讀磕诊,跟間隙鎖存在沖突關(guān)系的,是跟 “往這個(gè)間隙中插入一個(gè)記錄 ”這個(gè)操作纹腌,間隙鎖之間都不存在沖突關(guān)系霎终;間隙鎖和行鎖合稱next-key lock,每個(gè)next-key lock是前開后閉區(qū)間
3升薯、間隙鎖是在可重復(fù)讀隔離級(jí)別下才會(huì)生效的
4莱褒、一個(gè)并發(fā)問題:任意鎖住一行,如果這一行不存在的話就插入涎劈,如果存在這一行就更新它的數(shù)據(jù)广凸;因?yàn)殒i的是間隙鎖阅茶,并發(fā)時(shí)會(huì)鎖競爭,發(fā)生死鎖
MVCC-多版本并發(fā)控制(可重復(fù)讀下)
查詢(同時(shí)滿足下面條件的谅海,才作為結(jié)果返回):
a脸哀、査找版本早于當(dāng)前事務(wù)版本的數(shù)據(jù)行(也就是行的系統(tǒng)版本號(hào)小于或等于事務(wù)的系統(tǒng)版本號(hào)),這樣可以確保事務(wù)讀取的行扭吁,要么是在事務(wù)開始前已經(jīng)存在的撞蜂,要么是事務(wù)自身插入或者修改過的
b、行的刪除版本要么未定義智末,要么大于當(dāng)前事務(wù)版本號(hào)谅摄,這可以確保事務(wù)讀取到的行徒河,在事務(wù)開始之前未被刪除
插入:為新插入的每一行保存當(dāng)前事務(wù)版本號(hào)作為行版本號(hào)
刪除:為刪除的每一行保存當(dāng)前事務(wù)版本號(hào)作為行刪除標(biāo)識(shí)
修改:插入一行新記錄系馆,保存當(dāng)前事務(wù)版本號(hào)作為行版本號(hào),同時(shí)保存當(dāng)前事務(wù)版本號(hào)到原來的行作為行刪除標(biāo)識(shí)
優(yōu)點(diǎn):保存這兩個(gè)額外系統(tǒng)版本號(hào)顽照,使大多數(shù)讀操作都可以不用加鎖由蘑,這樣設(shè)計(jì)使得讀數(shù)據(jù)操作很簡單,性能很好代兵,并且也能保證只會(huì)讀取到符合標(biāo)準(zhǔn)的行
缺點(diǎn):每行記錄都需要額外的存儲(chǔ)空間尼酿,需要做更多的行檢査工作,以及一些額外的維護(hù)工作
MySQL沒有完全解決幻讀問題
如:事務(wù)a先查詢(MVCC)植影,事務(wù)b插入(next-key)裳擎,事務(wù)a更新(next-key,會(huì)加版本號(hào))思币,事務(wù)a查詢(MVCC)鹿响,兩次查詢的結(jié)果不同
意向鎖(表級(jí)鎖):意向鎖是由數(shù)據(jù)庫自己維護(hù)的,一般來說谷饿,給一行數(shù)據(jù)加上共享鎖之前惶我,數(shù)據(jù)庫會(huì)自動(dòng)在這張表上面加一個(gè)意向共享鎖(IS鎖);給一行數(shù)據(jù)加上排他鎖之前博投,數(shù)據(jù)庫會(huì)自動(dòng)在這張表上面加一個(gè)意向排他鎖(IX鎖)
意向鎖可以認(rèn)為是共享鎖和互斥鎖在數(shù)據(jù)表上的標(biāo)識(shí)绸贡,通過意向鎖可以快速判斷表中是否有記錄被上鎖,從而避免通過遍歷的方式來查看表中有沒有記錄被上鎖毅哗,提升加鎖效率
如要加表級(jí)別的互斥鎖听怕,這時(shí)候數(shù)據(jù)表里面如果存在行級(jí)別的互斥鎖或者共享鎖的,加鎖就會(huì)失敗虑绵,此時(shí)直接根據(jù)意向鎖就能知道這張表是否有行級(jí)別的X鎖或者S鎖
MySQL-查詢性能優(yōu)化
從下面幾點(diǎn)進(jìn)行優(yōu)化:
1叉跛、減少掃描行數(shù)(索引)
2、減少返回的行數(shù)或列數(shù)(limit或避免*)
如:
1蒸殿、證件號(hào)碼筷厘、證件名稱鸣峭、證件類型(聚合索引,冗余索引酥艳,順序問題摊溶,減少回表)
2、select a.id,a.name from user a inner join (select b.id from user order by a.userId limit 1000,10) b on a.id = b.id
(原始的寫法:select a.id,a.name from user a order by a.userId limit 1000,10)
(使用一級(jí)索引充石,減少回表)
join(小表驅(qū)動(dòng)大表莫换,大表用索引)
1、在可以使用被驅(qū)動(dòng)表的索引(join字段)情況下骤铃,使用join語句拉岁,性能比強(qiáng)行拆成多個(gè)單表執(zhí)行SQL語句的性能要好;如果使用join語句的話惰爬,需要讓小表(根據(jù)條件查詢出來少的表)做驅(qū)動(dòng)表
2喊暖、在判斷要不要使用join語句時(shí),就是看explain結(jié)果里面撕瞧,Extra字段里面有沒有出現(xiàn)“Block Nested Loop”字樣(出現(xiàn)則不用join)
3陵叽、如果用left join的話,左邊的表一定是驅(qū)動(dòng)表嗎丛版?不是
4巩掺、如果兩個(gè)表的join包含多個(gè)條件的等值匹配,是都要寫到on里面呢页畦,還是只把一個(gè)條件寫到on里面胖替,其他條件寫到where部分?寫到on里面
5豫缨、在MySQL里独令,NULL跟任何值執(zhí)行等值判斷和不等值判斷的結(jié)果,都是NULL州胳;select NULL =NULL 的結(jié)果记焊,也是返回 NULL
MySQL-執(zhí)行流程
MySQL執(zhí)行一個(gè)査詢的過程:
1、客戶端發(fā)送一條査詢給服務(wù)器
2栓撞、服務(wù)器先檢査査詢緩存遍膜,如果命中了緩存,則立刻返回存儲(chǔ)在緩存中的結(jié)果瓤湘,否則進(jìn)入下一階段
3瓢颅、服務(wù)器端進(jìn)行SQL解析、預(yù)處理弛说,再由優(yōu)化器生成對(duì)應(yīng)的執(zhí)行計(jì)劃
4挽懦、MySQL根據(jù)優(yōu)化器生成的執(zhí)行計(jì)劃,調(diào)用存儲(chǔ)引擎的API來執(zhí)行査詢
5木人、將結(jié)果返回給客戶端
更新:先找到要更新的數(shù)據(jù)信柿,從磁盤讀入內(nèi)存冀偶;在執(zhí)行器中執(zhí)行語句,調(diào)用引擎先把記錄寫到redo log(覆蓋寫渔嚷,磁盤中进鸠,物理日志)里面(原先的記錄會(huì)寫到undo log中,用于回滾)形病,并更新內(nèi)存客年,此時(shí)還未提交事務(wù),在適當(dāng)?shù)臅r(shí)候漠吻,再將記錄更新到磁盤量瓜;執(zhí)行器寫到binlog(不覆蓋寫,磁盤中途乃,邏輯日志(語句))绍傲;引擎將redo log改成提交狀態(tài),更新完成欺劳,即兩階段提交
ThreadLocal
1唧取、一個(gè)線程對(duì)應(yīng)多個(gè)ThreadLocal铅鲤,但只有一個(gè)ThreadLocalMap(在當(dāng)前線程內(nèi))
2划提、多個(gè)線程可以使用同一個(gè)ThreadLocal,但是是隔離的
3邢享、ThreadLocalMap是ThreadLocal的內(nèi)部類
4鹏往、ThreadLocalMap的key為ThreadLocal(弱引用),value為存儲(chǔ)的值
5骇塘、內(nèi)存泄漏:當(dāng)ThreadLocal被回收時(shí)伊履,ThreadLocalMap中就可能出現(xiàn)key為null的Entry,沒有任何辦法訪問這些key為null的Entry的value款违,如果當(dāng)前線程再遲遲不結(jié)束的話唐瀑,這些key為null的Entry的value就會(huì)一直存在一條強(qiáng)引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠(yuǎn)無法回收(沒有調(diào)用remove),造成內(nèi)存泄漏
6插爹、為什么key使用弱引用:如果key使用強(qiáng)引用哄辣,如果當(dāng)前線程再遲遲不結(jié)束的話(如線程池中復(fù)用線程),可能會(huì)出現(xiàn)整個(gè)Entry對(duì)象都不會(huì)被回收赠尾,也會(huì)出現(xiàn)內(nèi)存泄漏問題(更難解決)力穗;如果key使用弱引用,即使沒有手動(dòng)刪除气嫁,key也會(huì)被回收当窗,但是會(huì)出現(xiàn)value不會(huì)被回收
7、內(nèi)存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一樣長寸宵,如果沒有手動(dòng)刪除對(duì)應(yīng)key就會(huì)導(dǎo)致內(nèi)存泄漏崖面,而不是因?yàn)槿跻?br>
8元咙、內(nèi)存泄漏解決:使用static修飾ThreadLocal引用(這樣保證ThreadLocal始終保持被引用,不會(huì)被回收)巫员,但是最后還是要調(diào)用remove方法(ThreadLocal不會(huì)被回收蛾坯,value會(huì)回收)