1.鎖:
鎖的定義:用于對共享資源的控制
Java的鎖有兩種,lock接口和synchronized關(guān)鍵字间聊。
synchronized:方法淳玩、代碼塊旁瘫,如果方法是非靜態(tài)的祖凫,取得的鎖是對象。如果作用的是類酬凳、靜態(tài)方法惠况,取得的鎖是類,該類所有對象共用一把鎖
lock:sync的缺點:鎖的釋放情況少宁仔,試圖獲得鎖時不能設(shè)定超時稠屠,不能中斷一個正在試圖獲得鎖的線程,無法知道是否成功獲取到鎖
lock(),unlock(),trylock(time)在超時時間內(nèi)獲取到鎖返回true,lockInterruptibly()可中斷鎖
鎖的分類
要不要同步鎖住資源:樂觀鎖,悲觀鎖
樂觀鎖:比較樂觀权埠,認(rèn)為自己在處理操作的時候榨了,不會有其它線程來干擾,所以并不會鎖住操作對象
在更新的時候攘蔽,去對比我修改期間的數(shù)據(jù)有沒有被改變過龙屉,如沒有,就正常的修改數(shù)據(jù)满俗,在mysql表里添加一個verson字段转捕,每次更新記錄都是加一,在更新時唆垃,如果version對不上五芝,表示在此期間數(shù)據(jù)被修改過了。
如果數(shù)據(jù)和我一開始拿到的不一樣了辕万,說明其他人在這段時間內(nèi)改過枢步,會選擇放棄,報錯蓄坏,重試等策略
適合并發(fā)寫入少价捧,讀取多的情況,提高讀取性能
悲觀鎖:認(rèn)為如果我不鎖住這個資源涡戳,別人就會來爭搶结蟋,就會造成數(shù)據(jù)結(jié)果錯誤,所以它會鎖住操作對象渔彰,Java中悲觀鎖的實現(xiàn)就是syn和Lock相關(guān)類多線程是否能共享一把鎖:共享鎖嵌屎,獨占鎖
獨占鎖;寫鎖恍涂。ReentrantReadWriteLock.WriteLock
共享鎖:讀鎖宝惰。ReentrantReadWriteLock.ReadLock多線程競爭時,是否要排隊:公平鎖再沧,非公平鎖尼夺。
new Reentrant(true)
在項目中的應(yīng)用:
Lock,有一個從其他系統(tǒng)同步衛(wèi)星傳感器數(shù)據(jù)的功能炒瘸,由客戶在頁面點擊按鈕發(fā)起淤堵,這是一個全局的功能,由于執(zhí)行時間較長顷扩,因此使用了ReenTrantLock的tryLock方法拐邪,設(shè)置超時時間,如果獲取到鎖就執(zhí)行同步數(shù)據(jù)功能隘截,如果沒有獲取到鎖說明同步數(shù)據(jù)功能正在執(zhí)行扎阶,返回給客戶信息汹胃。這樣可以防止客戶重復(fù)發(fā)起,及時給客戶反饋东臀。(此處用的是單機(jī)應(yīng)用着饥,如果是分布式,需要用到分布式鎖)
synchronized啡邑,開發(fā)netty網(wǎng)絡(luò)接口時贱勃,為了channel的可復(fù)用,設(shè)置了channel連接池谤逼,有兩個大小為10的數(shù)組贵扰,一個數(shù)組存儲channel,一個數(shù)組存儲channel對應(yīng)的鎖流部,一個channel對應(yīng)一把鎖戚绕,在使用channel時,隨機(jī)從數(shù)組中取出channel枝冀,如果channel已建立舞丛,返回channel,如果還未建立果漾,則創(chuàng)建一個球切。在創(chuàng)建時,用synchronized绒障,給channel對應(yīng)的鎖對象添加鎖吨凑,然后再次判斷channel是否已建立,如果還沒建立户辱,則創(chuàng)建channel鸵钝。這里用到了單例模式的雙重檢測。
還有就是netty的網(wǎng)絡(luò)io(包括接收和發(fā)送數(shù)據(jù))是異步的庐镐,所以每次發(fā)送數(shù)據(jù)需要新建一個回調(diào)對象恩商,用于接收返回數(shù)據(jù),然后執(zhí)行對象的wait和notify方法必逆,在執(zhí)行這兩個方法前怠堪,需要用synchronized給回調(diào)對象加鎖。
2. mysql隔離級別:
先說定義
事務(wù)的定義:事務(wù)就是要保證一組數(shù)據(jù)庫操作名眉,要么全部成功粟矿,要么全部失敗。
數(shù)據(jù)庫事務(wù)隔離級別定義:主要作用是實現(xiàn)事務(wù)工作期間璧针,數(shù)據(jù)庫操作讀的隔離特性嚷炉,所謂讀的操作就是將數(shù)據(jù)頁可以調(diào)取到內(nèi)存渊啰;然后可以讀取數(shù)據(jù)頁中相應(yīng)數(shù)據(jù)行的能力探橱,并且不同事務(wù)之間的數(shù)據(jù)頁讀操作相互隔離申屹;可以簡單理解為:一個事務(wù)在對數(shù)據(jù)頁中數(shù)據(jù)行做更新操作時,在沒有更新提交前隧膏,另一個事務(wù)此時是不能讀取數(shù)據(jù)頁中數(shù)據(jù)行內(nèi)容的哗讥;
- 讀未提交:可以讀取到事務(wù)未提交的數(shù)據(jù),隔離性差胞枕,會出現(xiàn)臟讀(當(dāng)前內(nèi)存讀)杆煞,不可重復(fù)讀,幻讀問題
臟讀:事務(wù)B的更新數(shù)據(jù)腐泻,沒有提交决乎,被事務(wù)A讀取,但是事務(wù)B回滾了派桩,更新數(shù)據(jù)全部還原构诚,也就是說事務(wù)A剛剛讀到的數(shù)據(jù)并沒有存在于數(shù)據(jù)庫中。從宏觀來看铆惑,就是事務(wù)A讀出了一條不存在的數(shù)據(jù) - 讀已提交:可以讀取到事務(wù)已提交的數(shù)據(jù)范嘱,隔離性一般,不會出現(xiàn)臟讀問題员魏,但是會出現(xiàn)不可重復(fù)讀丑蛤,幻讀問題;
不可重復(fù)讀:在一個事務(wù)內(nèi)多次讀取同一個數(shù)據(jù)撕阎,如果出現(xiàn)前后兩次讀到的數(shù)據(jù)不一樣的情況受裹,就意味著發(fā)生了「不可重復(fù)讀」現(xiàn)象。事務(wù) A 多次讀取同一數(shù)據(jù)闻书,事務(wù) B 在事務(wù)A多次讀取的過程中名斟,對數(shù)據(jù)作了更新并提交,導(dǎo)致事務(wù)A多次讀取同一數(shù)據(jù)時魄眉,結(jié)果不一致 - 可重復(fù)讀:一個事務(wù)執(zhí)行過程中看到的數(shù)據(jù)砰盐,總是跟這個事務(wù)在啟動時看到的數(shù)據(jù)是一致的】勇桑可以防止臟讀(當(dāng)前內(nèi)存讀)岩梳,防止不可重復(fù)讀問題,防止會出現(xiàn)的幻讀問題晃择,但是并發(fā)能力較差冀值。在可重復(fù)讀隔離級別下,事務(wù)會對讀取的數(shù)據(jù)行進(jìn)行快照宫屠,保證在同一個事務(wù)內(nèi)多次讀取同一行數(shù)據(jù)時的一致性列疗。
- 可串行化:對于同一行記錄,“寫”會加“寫鎖”浪蹂,“讀”會加“讀鎖”抵栈。當(dāng)出現(xiàn)讀寫鎖沖突的時候告材,后訪問的事務(wù)必須等前一個事務(wù)執(zhí)行完成,才能繼續(xù)執(zhí)行古劲。隔離性好斥赋,并發(fā)性差。
3. MySQL索引
說一下MySQL的索引
先說定義
索引的作用相當(dāng)于圖書的目錄产艾,可以根據(jù)目錄中的頁碼快速找到所需的內(nèi)容疤剑。
索引類型
根據(jù)是否包含重復(fù)值:創(chuàng)建索引時,可以規(guī)定索引是否可包含重復(fù)值闷堡。如果不包含隘膘,則索引應(yīng)該創(chuàng)建為 PRIMARY KEY 或 UNIQUE 索引。對于單列唯一性索引杠览,這保證單列不包含重復(fù)的值棘幸。對于多列唯一性索引,保證多個值的組合不重復(fù)倦零。
根據(jù)葉子節(jié)點的內(nèi)容误续,索引類型分為主鍵索引和非主鍵索引。主鍵索引的葉子節(jié)點存的是整行數(shù)據(jù)扫茅。在 InnoDB 里蹋嵌,主鍵索引也被稱為聚簇索引。非主鍵索引的葉子節(jié)點內(nèi)容是主鍵的值葫隙。在 InnoDB 里栽烂,非主鍵索引也被稱為二級索引。
主鍵索引和普通索引查詢的區(qū)別
如果通過普通索引查詢恋脚,先查詢到主鍵值腺办,再根據(jù)主鍵值查詢主鍵的索引樹,這個過程稱為回表糟描。所以在應(yīng)用中盡量使用主鍵查詢怀喉。
1.普通索引index :加速查找
2.唯一索引
主鍵索引:primary key :加速查找+約束(不為空且唯一)
唯一索引:unique:加速查找+約束 (唯一)
3.聯(lián)合索引
-primary key(id,name):聯(lián)合主鍵索引
-unique(id,name):聯(lián)合唯一索引
-index(id,name):聯(lián)合普通索引
與b樹的區(qū)別,為什么MySQL索引選擇B+樹
①B+樹非葉子節(jié)點不存儲數(shù)據(jù)船响,所有 data 存儲在葉節(jié)點導(dǎo)致查詢時間復(fù)雜度固定為 log n躬拢。而B-樹查詢時間復(fù)雜度不固定,與 key 在樹中的位置有關(guān)见间,最好為O(1)聊闯。
②B+樹葉子節(jié)點首尾連接形成有序鏈表,大大增加區(qū)間訪問性米诉,可使用在范圍查詢等菱蔬,而B-樹每個節(jié)點 key 和 data 在一起,則無法區(qū)間查找。
③B+樹更適合外部存儲拴泌。由于只有葉子結(jié)點存儲數(shù)據(jù)犹褒,非葉子節(jié)點只存儲鍵和指針,每個節(jié)點能索引的范圍更大更精確
索引的使用
1.覆蓋索引
一個索引包含了滿足查詢結(jié)果的數(shù)據(jù)就叫做覆蓋索引弛针。優(yōu)點是不需要回表等操作。 如何實現(xiàn)李皇?在編寫sql時 索引列+主鍵 包含 SELECT 到 FROM之間查詢的列削茁。
- 最左匹配原則
在說最左匹配原則之前要先了解聯(lián)合索引的結(jié)構(gòu)和執(zhí)行過程:
https://blog.csdn.net/weixin_44440311/article/details/130495246
索引下推
https://mp.weixin.qq.com/s?__biz=MzU3Mjk2NDc3Ng==&mid=2247483828&idx=1&sn=c21c6720a83240c2b899ed81ef57b912&chksm=fcc9ab73cbbe2265ac05ec67cca251bba661e995a00ad2acf2fabb69471bdc56987cdcf5b5e9&scene=27
定義:以最左邊的為起點任何連續(xù)的索引都能匹配上。同時遇到范圍查詢(>掉房、<茧跋、between、like)就會停止匹配卓囚。
舉例:比如:索引 abc_index:(a,b,c)瘾杭,只會在 where 條件中帶有 (a)、(a,b)哪亿、(a,b,c) 的三種類型的查詢中使用粥烁。其實這里說的有一點歧義,其實當(dāng) where 條件只有 (a,c) 時也會走蝇棉,但是只走a字段索引讨阻,不會走 c 字段。
失效情況:1篡殷、查詢條件中钝吮,缺失優(yōu)先級最高的索引 “a”
當(dāng) where b = 6300 and c = ‘JJJ疾風(fēng)劍豪’ 這種沒有以 a 為條件來檢索時;B+樹就不知道第一步該查哪個節(jié)點板辽,從而需要去全表掃描了(即不走索引)
當(dāng) where a =1 and c =“JJJ疾風(fēng)劍豪” 這樣的數(shù)據(jù)來檢索時奇瘦;B+ 樹可以用 a 來指定第一步搜索方向,但由于下一個字段 b 的缺失劲弦,所以只能把 a = 1 的數(shù)據(jù)主鍵ID都找到耳标,通過查到的主鍵ID回表查詢相關(guān)行,再去匹配 c = ‘JJJ疾風(fēng)劍豪’ 的數(shù)據(jù)了邑跪,當(dāng)然麻捻,這至少把 a = 1 的數(shù)據(jù)篩選出來了,總比直接全表掃描好多了呀袱。
這就是MySQL非常重要的原則贸毕,即索引的最左匹配原則。
https://www.jb51.net/article/270219.htm
4. 線程池參數(shù):
int corePoolSize, // 線程池的核心線程數(shù)量
int maximumPoolSize, // 線程池的最大線程數(shù)
long keepAliveTime, // 當(dāng)線程數(shù)大于核心線程數(shù)時夜赵,多余的空閑線程存活的最長時間
TimeUnit unit, // 存活時間的時間單位
BlockingQueue<Runnable> workQueue, // 任務(wù)隊列明棍,用來儲存等待執(zhí)行任務(wù)的隊列
ThreadFactory threadFactory, // 線程工廠,用來創(chuàng)建線程寇僧,一般默認(rèn)即可
RejectedExecutionHandler handler // 拒絕策略摊腋,當(dāng)提交的任務(wù)過多而不能及時處理時沸版,我們可以定制策略來處理任務(wù)
corePoolSize : 任務(wù)隊列未達(dá)到隊列容量時,最大可以同時運行的線程數(shù)量兴蒸。
maximumPoolSize : 任務(wù)隊列中存放的任務(wù)達(dá)到隊列容量的時候视粮,當(dāng)前可以同時運行的線程數(shù)量變?yōu)樽畲缶€程數(shù)。
workQueue:新任務(wù)來的時候會先判斷當(dāng)前運行(正在執(zhí)行任務(wù)的線程)的線程數(shù)量是否達(dá)到核心線程數(shù)橙凳,如果達(dá)到的話蕾殴,新任務(wù)就會被存放在隊列中。
keepAliveTime:線程池維護(hù)線程所允許的空閑時間岛啸。當(dāng)線程池中的線程數(shù)量大于 corePoolSize時钓觉,超過corePoolSize的線程如果空閑時間超過keepAliveTime,線程將被終止坚踩。
jdk中提供了四種拒絕策略(policy:政策荡灾,策略):
①CallerRunsPolicy
該策略下,在調(diào)用者線程中直接執(zhí)行被拒絕任務(wù)的run方法瞬铸。如果線程池已經(jīng)shutdown批幌,則直接拋棄任務(wù)。
②AbortPolicy
該策略下嗓节,直接丟棄任務(wù)逼裆,并拋出RejectedExecutionException異常。(abort:放棄赦政,丟棄)
③DiscardPolicy
該策略下胜宇,直接丟棄任務(wù),什么都不做恢着。(discard:丟棄)
④DiscardOldestPolicy
該策略下桐愉,拋棄進(jìn)入隊列最早的那個任務(wù),然后嘗試把這次拒絕的任務(wù)放入隊列
5.Redis分布式鎖
為什么需要分布式鎖掰派?
與分布式鎖相對就的是單機(jī)鎖从诲,我們在寫多線程程序時,避免同時操作一個共享資源產(chǎn)生數(shù)據(jù)問題靡羡,通常會使用一把鎖來互斥以保證共享資源的正確性系洛。
單機(jī)鎖的使用范圍是在同一個進(jìn)程中。如果換做是多個進(jìn)程略步,需要同時操作一個共享資源描扯,如何互斥呢?現(xiàn)在的業(yè)務(wù)應(yīng)用通常是微服務(wù)架構(gòu)趟薄,這也意味著一個應(yīng)用會部署多個進(jìn)程绽诚,多個進(jìn)程如果需要修改MySQL中的同一行記錄,為了避免操作亂序?qū)е屡K數(shù)據(jù),此時就需要引入分布式鎖了恩够。
想要實現(xiàn)分布式鎖卒落,必須借助一個外部系統(tǒng),所有進(jìn)程都去這個系統(tǒng)上申請加鎖蜂桶。而這個外部系統(tǒng)儡毕,必須要實現(xiàn)互斥能力,即兩個請求同時進(jìn)來扑媚,只會給一個進(jìn)程加鎖成功腰湾,另一個失敗。這個外部系統(tǒng)可以是數(shù)據(jù)庫钦购,也可以是Redis或Zookeeper,但為了追求性能褂萧,我們通常會選擇使用Redis或Zookeeper來做押桃。
Redis本身可以被多個客戶端共享訪問,正好就是一個共享存儲系統(tǒng)导犹,可以用來保存分布式鎖唱凯。而且 Redis 的讀寫性能高,可以應(yīng)對高并發(fā)的鎖操作場景谎痢。
Redis實現(xiàn)分布式鎖的思路是這樣的:
1.Redis可以使用鍵值對保存鎖變量磕昼,實現(xiàn)互斥功能的方法是使用SETNX命令,SET IF NOT EXIST节猿,即如果key不存在票从,才會設(shè)置它的值,否則什么也不做滨嘱。
基本邏輯是:
// 加鎖
SETNX lock_key 1
// 業(yè)務(wù)邏輯
DO THINGS
// 釋放鎖
DEL lock_key
問題:死鎖峰鄙。
以上實現(xiàn)存在一個很大的問題,當(dāng)客戶端1拿到鎖后太雨,如果發(fā)生下面的場景吟榴,就會造成死鎖。
1)程序處理業(yè)務(wù)邏輯異常囊扳,沒及時釋放鎖
2)進(jìn)程掛了吩翻,沒機(jī)會釋放鎖
以上情況會導(dǎo)致已經(jīng)獲得鎖的客戶端一直占用鎖,其他客戶端永遠(yuǎn)無法獲取到鎖
如何避免死鎖锥咸?
2.在申請鎖時狭瞎,給鎖設(shè)置一個過期時間,假設(shè)操作共享資源的時間不會超過10s搏予,那么加鎖時脚作,給這個key設(shè)置10s過期即可。
SETNX lock_key 1
EXPIRE lock_key 10
問題:加鎖、設(shè)置過期時間是2條命令球涛,有可能只執(zhí)行了第一條劣针,第二條卻執(zhí)行失敗
SETNX執(zhí)行成功后,由于(1)網(wǎng)絡(luò)異常亿扁,EXPIRE執(zhí)行失敗捺典、(2)Redis異常宕機(jī),EXPIRE沒有機(jī)會執(zhí)行从祝、(3)客戶端異常崩潰襟己,EXPIRE沒有機(jī)會執(zhí)行
總之這兩條命令如果不能保證是原子操作,就有潛在的風(fēng)險導(dǎo)致過期時間設(shè)置失敗牍陌,依舊有可能發(fā)生死鎖問題擎浴。幸好在Redis 2.6.12之后,Redis擴(kuò)展了SET命令的參數(shù)毒涧,可以在SET的同時指定EXPIRE時間贮预,這條操作是原子的,例如以下命令是設(shè)置鎖的過期時間為10秒
SET lock_key 1 EX 10 NX
這樣解決了死鎖的問題契讲,但是還會出現(xiàn)兩個問題:
(1)鎖過期
(2)釋放了別人的鎖
客戶端1加鎖成功仿吞,開始操作共享資源
客戶端1操作共享資源耗時太久,超過了鎖的過期時間捡偏,鎖失效(鎖被自動釋放)
客戶端2加鎖成功唤冈,開始操作共享資源
客戶端1操作共享資源完成,在finally塊中手動釋放鎖银伟,但此時它釋放的是客戶端2的鎖你虹。
第1個問題是評估操作共享資源的時間不準(zhǔn)確導(dǎo)致的,如果只是一味增大過期時間彤避,只能緩解問題降低出現(xiàn)問題的概率售葡,依舊無法徹底解決問題。
第2個問題是釋放了別人的鎖忠藤,原因在于釋放鎖時并沒有檢查這把鎖的歸屬挟伙,這樣解鎖不嚴(yán)謹(jǐn)。
鎖被別人釋放了如何解決模孩。第一步在加鎖時尖阔,設(shè)置一個唯一標(biāo)識,例如線程ID作為value榨咐。第二步在釋放鎖時介却,要先判斷這把鎖是否歸自己持有,只有是自己的才能釋放它块茁。
這里釋放鎖使用的是GET + DEL兩條命令齿坷,這時又會遇到原子性問題了桂肌。
客戶端1執(zhí)行GET,判斷鎖是自己的永淌,剛好鎖過期崎场。
客戶端2執(zhí)行了SET命令,強(qiáng)制獲取到鎖(雖然發(fā)生概念很低遂蛀,但要嚴(yán)謹(jǐn)考慮鎖的安全性)
客戶端1執(zhí)行DEL谭跨,卻釋放了客戶端2的鎖
怎樣原子執(zhí)行兩條命令呢。把以上邏輯寫成Lua腳本李滴,讓Redis執(zhí)行螃宙。因為Redis處理每個請求是單線程執(zhí)行的,在執(zhí)行一個Lua腳本時其它請求必須等待所坯,直到這個Lua腳本處理完成谆扎,這樣一來GET+DEL之間就不會有其他命令執(zhí)行了。
4.這樣一路優(yōu)先下來芹助,整個加鎖堂湖、解鎖流程就更嚴(yán)謹(jǐn)了,先小結(jié)一下周瞎,基于Redis實現(xiàn)的分布式鎖苗缩,一個嚴(yán)謹(jǐn)?shù)牧鞒倘缦拢?br>
(1)加鎖時要設(shè)置過期時間饵蒂,添加唯一標(biāo)識声诸。SET lock_key unique_value EX expire_time NX
(2)操作共享資源
(3)釋放鎖:Lua腳本,先GET判斷鎖是否歸屬自己退盯,再DEL釋放鎖
如何確定鎖過期的時間彼乌。
加鎖時,先設(shè)置一個預(yù)估的過期時間渊迁,然后開啟一個守護(hù)線程慰照,定時去檢測這個鎖的失效時間,如果鎖快要過期了琉朽,操作共享資源還未完成毒租,那么就自動對鎖進(jìn)行續(xù)期,重新設(shè)置過期時間箱叁。
這個方案可以用Redission庫來實現(xiàn)墅垮,客戶端一旦加鎖成功,就會啟動一個watch dog看門狗線程耕漱,它是一個后臺線程算色,會每隔一段時間(這段時間的長度與設(shè)置的鎖的過期時間有關(guān))檢查一下,如果檢查時客戶端還持有鎖key(也就是說還在操作共享資源)喷众,那么就會延長鎖key的生存時間蔓彩。
6 Redis的基本數(shù)據(jù)類型
Redis的數(shù)據(jù)類型
Redis的基本數(shù)據(jù)類型有5種,分別是string(字符串)夭委,list(列表)若河,hash(哈希)能岩,set(集合),zset(有序集合)
01 字符串 String
是 Redis 最簡單的數(shù)據(jù)結(jié)構(gòu)牡肉,可以存儲字符串捧灰、整數(shù)或者浮點數(shù)。最常見的應(yīng)用場景就是對象緩存统锤,例如緩存用戶對象毛俏,key是"userInfo"+#{用戶ID},value是用戶信息對象的JSON字符串饲窿。
基本操作有
127.0.0.1:6379> set name hzy # 設(shè)置
OK
127.0.0.1:6379> get name # 獲取
"hzy"
127.0.0.1:6379> exists name # 判斷是否存在
(integer) 1
127.0.0.1:6379> del name # 刪除
(integer) 1
批量操作:
127.0.0.1:6379> mset name1 xiaoming name2 xiaohong # 批量設(shè)置
OK
127.0.0.1:6379> mget name1 name2 # 批量獲取
1) "xiaoming"
2) "xiaohong"
計數(shù)操作:
如果 value 值是一個整數(shù)煌寇,我們可以對它進(jìn)行自增長操作。
127.0.0.1:6379> incr likenum # 自增1
(integer) 1
127.0.0.1:6379> get likenum
"1"
127.0.0.1:6379> decr likenum # 減1
(integer) 0
127.0.0.1:6379> get number
"0"
過期操作:
127.0.0.1:6379> expire name 60 # 設(shè)置超時時間
(integer) 1
127.0.0.1:6379> setex name 60 value # 等價于 setex + expire
OK
127.0.0.1:6379> ttl key # 查看數(shù)據(jù)還有多久過期
(integer) 11
Redis String的使用場景:
(1)緩存:把一個對象轉(zhuǎn)成json字符串逾雄,然后放到redis里緩存
(2)計數(shù)器:像博客文章的閱讀量阀溶、評論數(shù)、點贊數(shù)等等
(3)分布式系統(tǒng)生成自增長ID
02 List
Redis 的列表相當(dāng)于 Java 語言里面的 LinkedList鸦泳。
LinkedList優(yōu)點:插入性能高银锻,不管是從末尾插入還是中間插入
LinkedList缺點:隨機(jī)讀性能差,例如LinkedList.get(10)做鹰,這種操作击纬,性能就很低,因為他需要遍歷這個鏈表钾麸,從頭開始遍歷這個鏈表更振,直到找到index = 10的這個元素為止。
Redis List 數(shù)據(jù)類型如何實現(xiàn)隊列饭尝?
右邊進(jìn)左邊出
127.0.0.1:6379> rpush apps qq # 將元素插入到列表的尾部(最右邊)
(integer) 1
127.0.0.1:6379> rpush apps wechat taobao # 將多個元素插入到列表的尾部(最右邊)
(integer) 3
127.0.0.1:6379> lpop apps # 移除并返回列表的第一個元素(最左邊)
"qq"
127.0.0.1:6379> lrange apps 0 1 # 返回列表中指定區(qū)間內(nèi)的元素肯腕,0表示第一個,1表示第二個钥平,-1表示最后一個
1) "wechat"
2) "taobao"
127.0.0.1:6379> lrange apps 0 -1 # -1表示倒數(shù)第一
1) "wechat"
2) "taobao"
Redis List 數(shù)據(jù)類型如何實現(xiàn)棧实撒?
先進(jìn)先出,右邊進(jìn)右邊出
127.0.0.1:6379> rpush apps qq wechat taobao
(integer) 3
127.0.0.1:6379> rpop apps # 移除列表的最后一個元素涉瘾,返回值為移除的元素
"taobao"
Redis List 使用場景
異步隊列
任務(wù)輪詢(RPOPLPUSH)
文章列表(lrange key 0 9)
03 Hash
Redis的Hash結(jié)構(gòu)相當(dāng)于Java語言的HashMap知态,內(nèi)部實現(xiàn)結(jié)構(gòu)上與JDK1.7的HashMap一致,底層通過數(shù)組+鏈表實現(xiàn)睡汹。
127.0.0.1:6379> hmset userInfo name "hzy" age "24" sex "1"
OK
127.0.0.1:6379> hexists userInfo name # 相當(dāng)于HashMap的containsKey()
(integer) 1
127.0.0.1:6379> hget userInfo name # 相當(dāng)于HashMap的get()
"hzy"
127.0.0.1:6379> hget userInfo age
"24"
127.0.0.1:6379> hgetall userInfo # 數(shù)據(jù)量大時肴甸,謹(jǐn)慎使用!獲取在哈希表中指定 key 的所有字段和值
1) "name"
2) "hzy"
3) "age"
4) "24"
5) "sex"
6) "1"
127.0.0.1:6379> hkeys userInfo # 數(shù)據(jù)量大時囚巴,謹(jǐn)慎使用原在!獲取 key 列表
1) "name"
2) "age"
3) "sex"
127.0.0.1:6379> hvals userInfo # 獲取 value 列表
1) "hzy"
2) "24"
3) "1"
127.0.0.1:6379> hset userInfo name "test" # 修改某個字段對應(yīng)的值
127.0.0.1:6379> hget userInfo name
"test"
Redis Hash 使用場景
記錄博客中某個博主的主頁訪問量友扰、博主的姓名、聯(lián)系方式庶柿、住址
04 Set
Redis的set集合相當(dāng)于Java的HashSet村怪,是一個集合,里面的元素是無序的浮庐,他里面的元素不能重復(fù)的甚负。
127.0.0.1:6379> sadd apps wechat qq # 添加元素
(integer) 2
127.0.0.1:6379> sadd apps qq # 重復(fù)
(integer) 0
127.0.0.1:6379> smembers apps # 注意:查詢順序和插入的并不一致,因為 set 是無序的
1) "qq"
2) "wechat"
127.0.0.1:6379> scard apps # 獲取長度
(integer) 2
127.0.0.1:6379> sismember apps qq # 謹(jǐn)慎使用审残!檢查某個元素是否存在set中梭域,只能接收單個元素
(integer) 1
127.0.0.1:6379> sadd apps2 wechat qq
(integer) 2
127.0.0.1:6379> sinterstore apps apps apps2 # 獲取 apps 和 apps 的交集并存放在 apps3 中
(integer) 1
127.0.0.1:6379> smembers app3
1) "qq"
2) "wechat"
注意: 當(dāng)集合中最后一個元素移除之后,數(shù)據(jù)結(jié)構(gòu)自動刪除搅轿,內(nèi)存被回收
Redis Set 使用場景
微博抽獎:如果數(shù)據(jù)量不是特別大的時候病涨,可以使用spop(移除并返回集合中的一個隨機(jī)元素)或srandmember(返回集合中一個或多個隨機(jī)數(shù))
QQ標(biāo)簽:一個用戶多個便簽
共同關(guān)注(交集)sinter key1 key2 …求多個集合的交集
共同好友(交集)
05 sorted set
有序集合,sorted set 增加了一個權(quán)重參數(shù) score璧坟,使得集合中的元素能夠按 score 進(jìn)行有序排列既穆,還可以通過 score 的范圍來獲取元素的列表。使得它類似于Java的TreeSet和HashMap的結(jié)合體雀鹃。
Sorted set 常用命令
127.0.0.1:6379> zadd apps 3.0 qq # 添加元素到 sorted set 中,3.0是score的值
(integer) 1
127.0.0.1:6379> zadd apps 2.0 wechat 1.0 aliyun # 一次添加多個元素
(integer) 2
127.0.0.1:6379> zcard apps # 查看 sorted set 中的元素數(shù)量
(integer) 3
127.0.0.1:6379> zscore apps wechat # 查看某個 value 的權(quán)重
"2.0"
127.0.0.1:6379> zrange apps 0 -1 # 通過索引區(qū)間返回有序集合指定區(qū)間內(nèi)的成員幻工,0 -1 表示輸出所有元素
1) "aliyun"
2) "wechat"
3) "qq"
127.0.0.1:6379> zrange apps 0 1 # 通過索引區(qū)間返回有序集合指定區(qū)間內(nèi)的成員
1) "aliyun"
2) "wechat"
127.0.0.1:6379> zrevrange apps 0 1 # 相當(dāng)于zrange的反轉(zhuǎn)
1) "qq"
2) "wechat"
注意: sorted set 中最后一個 value 被移除后,數(shù)據(jù)結(jié)構(gòu)自動刪除黎茎,內(nèi)存被回收
Sorted set 使用場景
排行榜
訂單支付超時(下單時插入囊颅,member為訂單號,score為訂單超時時間戳工三,然后寫個定時任務(wù)每隔一段時間執(zhí)行zrange)
https://www.modb.pro/db/99437
7 Redis的持久化
Redis是一個基于內(nèi)存的數(shù)據(jù)庫迁酸,它的數(shù)據(jù)是存放在內(nèi)存中先鱼,內(nèi)存有個問題就是關(guān)閉服務(wù)或者斷電會丟失俭正。Redis的數(shù)據(jù)也支持寫到硬盤中,這個過程就叫做持久化焙畔。
RDB(Redis DataBase) :就是在指定的時間間隔內(nèi)掸读,定時的將 redis 存儲的數(shù)據(jù)生成Snapshot快照并存儲到磁盤上;
AOF(Append Of File) :將 redis 執(zhí)行過的所有寫指令記錄下來宏多,在下次 redis 重新啟動時儿惫,只要把這些寫指令從前到后再重復(fù)執(zhí)行一遍,就可以實現(xiàn)數(shù)據(jù)恢復(fù)了伸但。
01 RDB(Redis DataBase):redis備份默認(rèn)方式
同時允許使用兩種方式: 其實 RDB 和 AOF 兩種方式也可以同時使用肾请,在這種情況下,如果 redis 重啟的話更胖,則會優(yōu)先采用 AOF 方式來進(jìn)行數(shù)據(jù)恢復(fù)铛铁,這是因為 AOF 方式的數(shù)據(jù)恢復(fù)完整度更高隔显。
可以選擇關(guān)閉持久化: 如果你沒有數(shù)據(jù)持久化的需求,也完全可以關(guān)閉 RDB 和 AOF 方式饵逐,這樣的話括眠,redis 將變成一個純內(nèi)存數(shù)據(jù)庫,就像 memcache 一樣倍权。
觸發(fā)RDB持久化的方式有兩種掷豺,一是自動備份,需配置備份規(guī)則薄声,redis.conf中配置自動備份的規(guī)則当船,save的格式: save 秒鐘 寫操作次數(shù)
方式2:手動執(zhí)行命令備份(save | bgsave)
有2個命令可以觸發(fā)備份。
save:save時只管保存默辨,其他不管生年,全部阻塞,手動保存廓奕,不建議使用抱婉。
bgsave:redis會在后臺異步進(jìn)行快照操作,快照同時還可以響應(yīng)客戶端情況桌粉。
5蒸绩、RDB優(yōu)缺點
優(yōu)勢:
適合大規(guī)模數(shù)據(jù)恢復(fù)
對數(shù)據(jù)完整性和一致性要求不高更適合使用
節(jié)省磁盤空間
基于二進(jìn)制存儲的,恢復(fù)速度快
劣勢:
Fork的時候铃肯,內(nèi)存中的數(shù)據(jù)會被克隆一份患亿,大致2倍的膨脹,需要考慮
雖然Redis在fork的時候使用了寫時拷貝技術(shù)押逼,但是如果數(shù)據(jù)龐大時還是比較消耗性能
在備份周期在一定間隔時間做一次備份步藕,所以如果Redis意外down的話,就會丟失最后一次快照后所有修改
02 AOF
流程:
客戶端的請求寫命令會被append追加到AOF緩沖區(qū)內(nèi)
AOF緩沖區(qū)會根據(jù)AOF持久化策略[always,everysec,no]將操作sync同步到磁盤的AOF文件中
AOF文件大小超過重寫策略或手動重寫時挑格,會對AOF文件進(jìn)行重寫(rewrite)咙冗,壓縮AOF文件容量
redis服務(wù)器重啟時,會重新load加載AOF文件中的寫操作達(dá)到數(shù)據(jù)恢復(fù)的目的
5漂彤、AOF優(yōu)缺點
優(yōu)勢:
備份機(jī)制更穩(wěn)健雾消,丟失數(shù)據(jù)概率更低
可讀的日志文本,通過操作AOF文件挫望,可以處理誤操作
劣勢:
比RDB占用更多的磁盤空間
恢復(fù)備份速度要慢
每次讀寫都同步的話立润,有一定的性能壓力
存在個別bug,造成不能恢復(fù)
03 Redis 4.0 混合持久化
重啟 Redis 時媳板,我們很少使用 rdb 來恢復(fù)內(nèi)存狀態(tài)桑腮,因為會丟失大量數(shù)據(jù)。我們通常使用 AOF 日志重放蛉幸,但是重放 AOF 日志性能相對 rdb 來說要慢很多破讨,這樣在 Redis 實例很大的情況下旨巷,啟動需要花費很長的時間。
將 rdb 文件的內(nèi)容和增量的 AOF 日志文件存在一起添忘。
這里的 AOF 日志不再是全量的日志采呐,而是自持久化開始到持久化結(jié)束的這段時間發(fā)生的增量 AOF 日志,通常這部分 AOF 日志很小搁骑。
于是在 Redis 重啟的時候斧吐,可以先加載 rdb 的內(nèi)容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放仲器,重啟效率因此大幅得到提升煤率。
優(yōu)點:
混合持久化結(jié)合了 RDB 和 AOF 持久化的優(yōu)點,開頭為 RDB 的格式乏冀,使得 Redis 可以更快的啟動蝶糯,同時結(jié)合 AOF的優(yōu)點,有減低了大量數(shù)據(jù)丟失的風(fēng)險辆沦。
缺點:
AOF 文件中添加了 RDB 格式的內(nèi)容昼捍,使得 AOF 文件的可讀性變得很差;
兼容性差肢扯,如果開啟混合持久化妒茬,那么此混合持久化 AOF 文件,就不能用在 Redis 4.0 之前版本了蔚晨。
8 Java 流
數(shù)據(jù)源:
數(shù)據(jù)源data source乍钻,提供數(shù)據(jù)的原始媒介。常見的數(shù)據(jù)源有:數(shù)據(jù)庫铭腕、文件银择、其他程 序、內(nèi)存累舷、網(wǎng)絡(luò)連接浩考、lo 設(shè)備。 數(shù)據(jù)源分為:源設(shè)備笋粟、目標(biāo)設(shè)備怀挠。
源設(shè)備:為程序提供數(shù)據(jù)析蝴,一般對應(yīng)輸入流害捕。
目標(biāo)設(shè)備:程序數(shù)據(jù)的目的地,一般對應(yīng)輸出流闷畸。
流的概念:
流是一個抽象尝盼、動態(tài)的概念,是一連串連續(xù)動態(tài)的數(shù)據(jù)集合佑菩。 對于輸入流而言盾沫,數(shù)據(jù)源就像水箱裁赠,流(stream)就像水管中流動著的水流,程序就是我們最終的用戶赴精。我們通過流(AStream)將數(shù)據(jù)源(Source)中的數(shù)據(jù)(information)輸送到程序(Program)中佩捞。對于輸出流而言,目標(biāo)數(shù)據(jù)源就是目的地(dest)蕾哟,我們通過流(A Stream)將程序(Program)中的數(shù)據(jù)(information)輸送到目的數(shù)據(jù)源(dest)中一忱。
01 按流的方向分類:
輸入流:數(shù)據(jù)流向是數(shù)據(jù)源到程序(以InputStream、Reader結(jié)尾的流)谭确。 輸出流:數(shù)據(jù)流向是程序到目的地(以 OutPutStream帘营、Writer結(jié)尾的流)。
02 按處理的數(shù)據(jù)單元分類:
字節(jié)流:以字節(jié)為單位獲取數(shù)據(jù)逐哈,命名上以Stream結(jié)屋的流一般是字節(jié)流 芬迄。如FilelnputStream、FileOutputStream昂秃。
字符流:以字符為單位獲取數(shù)據(jù)禀梳,命名上以Reader/Writer結(jié)屋的流一般是字符流,如 FileReader肠骆、FileWriter
03 按處理對象不同分類:
節(jié)點流:可以直接從數(shù)據(jù)源或目的地讀寫數(shù)據(jù)出皇,如 FilelnputStream、FileReaderDatalnputStream等哗戈。
處理流:不直接連接到數(shù)據(jù)源或目的地郊艘,是”處理流的流”。通過對其他流的處理提高程序的性能唯咬,如 BufferedInputStream纱注、BufferedReader等。
字節(jié)流和字符流的區(qū)別與聯(lián)系
區(qū)別:
- 數(shù)據(jù)類型:字節(jié)流以字節(jié)為單位進(jìn)行讀寫胆胰,而字符流以字符為單位進(jìn)行讀寫狞贱。字節(jié)流可以處理任意類型的數(shù)據(jù),包括文本蜀涨、圖像瞎嬉、音頻等,而字符流主要用于處理文本數(shù)據(jù)厚柳。
- 編碼方式:字節(jié)流是以字節(jié)的形式直接讀寫數(shù)據(jù)氧枣,不關(guān)心數(shù)據(jù)的具體編碼方式。而字符流是以字符的形式讀寫數(shù)據(jù)别垮,會根據(jù)指定的字符編碼將字符轉(zhuǎn)換為字節(jié)進(jìn)行處理便监。
- 處理效率:字節(jié)流的處理效率通常比字符流高,因為字節(jié)流直接操作底層的字節(jié)數(shù)據(jù),不需要進(jìn)行字符編碼的轉(zhuǎn)換烧董。
- 使用場景:字節(jié)流適用于處理二進(jìn)制數(shù)據(jù)毁靶,如文件的復(fù)制、網(wǎng)絡(luò)傳輸?shù)妊芬啤W址鬟m用于處理文本數(shù)據(jù)预吆,如文件的讀寫、文本的處理等胳泉。
聯(lián)系: - 繼承關(guān)系:字節(jié)流和字符流都是抽象類InputStream和OutputStream的子類啡浊,以及Reader和Writer的子類。
- 使用方式:字節(jié)流和字符流都提供了類似的讀寫方法胶背,如read()和write()方法巷嚣。
- 轉(zhuǎn)換:可以通過InputStreamReader和OutputStreamWriter類將字節(jié)流轉(zhuǎn)換為字符流,以便處理文本數(shù)據(jù)钳吟。
- 字符流是建立在字節(jié)流的基礎(chǔ)上的廷粒。在字符流中,使用了字符編碼來處理字符數(shù)據(jù)红且,而字符編碼又是通過字節(jié)流來實現(xiàn)的坝茎。因此,字符流可以看作是字節(jié)流的高級封裝暇番,提供了更方便的字符處理功能嗤放。
原文鏈接:https://blog.csdn.net/weixin_42594143/article/details/133947547
文件字符流
緩沖字節(jié)流
緩沖字符流
字節(jié)數(shù)組流
數(shù)據(jù)流
對象流
ObjectInputStream/ObjectOutputStream是以“對象”為數(shù)據(jù)源,但是必須將傳輸?shù)膶ο筮M(jìn)行序列化與反序列化操作壁酬。 需要序列化的對象次酌,需要實現(xiàn)接口:iava.io.Serializable對象序列化的作用有如下兩種: 持久化:把對象的字節(jié)序列永久地保存到硬盤上 通常存放在一 個文件中,比如:休眠的實現(xiàn)舆乔。以后服務(wù)器session管理,hibernate將對象持久化實現(xiàn)岳服。 網(wǎng)絡(luò)通信:在網(wǎng)絡(luò)上傳送對象的字節(jié)序列。比如:服務(wù)器之間的數(shù)據(jù)通信希俩、對象傳遞吊宋。
https://blog.csdn.net/weixin_74469493/article/details/131152841