亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Redis的鍵String全面詳解

 更新時(shí)間:2023年06月06日 10:59:34   作者:言西早  
這篇文章主要為大家介紹了Redis的鍵String全面詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

String開篇

在介紹之前,筆者想介紹一下Redis的設(shè)計(jì)精髓,也就是其單線程設(shè)計(jì),對(duì)于一個(gè)宣稱能抗住十萬qps的數(shù)據(jù)庫,其單線程的設(shè)計(jì)讓人不可思議,但redis開創(chuàng)的單線程設(shè)計(jì)哲學(xué)是至今其仍是KV型數(shù)據(jù)庫一方霸主的重要原因,而其驕傲的資本是其立足于內(nèi)存,而又不止步于內(nèi)存(持久化、壓縮、淘汰算法)的諸多設(shè)計(jì)。

之所以想介紹它的單線程設(shè)計(jì),正是因?yàn)镽edis的很多數(shù)據(jù)結(jié)構(gòu),都是為了阻擊單線程架構(gòu)的宿敵——阻塞而產(chǎn)生的,這也會(huì)是系列文章的一個(gè)主要脈絡(luò)

Redis是KV型的數(shù)據(jù)庫,其數(shù)據(jù)結(jié)構(gòu)代表著對(duì)應(yīng)的鍵,Redis的鍵有五大類型,分別是String、List、Hash、Set、Zset,另外還有三種不常用的類型 HyperLogLog、BitMap、Geo,本文著重介紹前五種

1 字符串鍵

1.1 C語言的字符串實(shí)現(xiàn)

字符串鍵的使用和實(shí)現(xiàn)都比較簡(jiǎn)單

Redis的底層實(shí)現(xiàn)是C語言,但是C語言的字符串具有種種弊端,對(duì)于Redis來說,最不可接受的有如下幾點(diǎn):

  • 二進(jìn)制不安全

C語言中用于識(shí)別字符串的函數(shù)會(huì)自動(dòng)對(duì)”\0”(也就是空字符字符作出截?cái)?,這對(duì)于二進(jìn)制文本來說是極不安全的,如果有如“redis\0is\0good”的文本,那么C語言就只能識(shí)別到”redis”,這從根本上斷絕了用它來傳輸二進(jìn)制文本的可能

  • 字符串長度獲取復(fù)雜度太高

C語言中并未維護(hù)獲取字符串長度的變量,每次獲取它的長度,都必須從頭到尾遍歷字符串一次,才能夠獲取它的長度,當(dāng)這個(gè)字符串長度太長時(shí),Redis的單線程會(huì)不可避免地陷入阻塞,這是Redis地設(shè)計(jì)者不希望看到的

  •  緩沖區(qū)溢出問題

C語言中的字符串拼接默認(rèn)地假定在該字符串的后面有足夠的內(nèi)存以放下后來的字符串,一旦這個(gè)假定成立,就會(huì)發(fā)生緩沖區(qū)溢出,C語言處理此類問題的方式也是相當(dāng)粗暴,一旦溢出發(fā)生,后面”無辜”的數(shù)據(jù)內(nèi)存就會(huì)被覆蓋掉,這對(duì)于數(shù)據(jù)庫來說是無法容忍的。

1.2 Redis的利器,SDS

SDS是Redis自己的字符串實(shí)現(xiàn),其對(duì)于以上三個(gè)問題都給出了很好的解決

我們可以通過如下代碼發(fā)現(xiàn),SDS實(shí)現(xiàn)的字符串具有更好的封裝性,顯得更面向?qū)ο罅?/p>

struct sdshdr{
        int len;//記錄字符串長度
        int free;//記錄可用空間的長度
        char buf[];//保存每一個(gè)字符
//APIs
}
  • 通過len,我們得以實(shí)現(xiàn)常數(shù)復(fù)雜度獲取長度
  • char數(shù)組保存空間,以實(shí)現(xiàn)二進(jìn)制安全

而free有什么用呢?

  • 和C語言中字符串的緩沖區(qū)大小完全聽天由命不同,每一個(gè)SDS被創(chuàng)建、修改時(shí),都會(huì)有一個(gè)等同于自身大小的緩沖區(qū)(這個(gè)空間最大為1M)
  • 有了這個(gè)緩沖區(qū),Redis得以避免頻繁的空間重分配,因?yàn)檫w移字符串是一個(gè)極其消耗性能的操作,它必須找到一塊新空間,并把原來的字符串一個(gè)個(gè)搬運(yùn)過去,這也會(huì)增加Redis主線程阻塞的幾率
  • 這種做法體現(xiàn)了一種在空間和時(shí)間上的權(quán)衡,拿空間換時(shí)間,其帶來的好處就是Redis空間重分配的時(shí)間大大減少

1.3 String In Action

接下來筆者想就String在技術(shù)層面和業(yè)務(wù)層面的作用來講講String的妙用

String在點(diǎn)贊系統(tǒng)中的應(yīng)用

點(diǎn)贊系統(tǒng)社區(qū)功能里最常見核心的功能之一,其承載的巨大數(shù)據(jù)量又是一般的業(yè)務(wù)所不具有的,而String在此系統(tǒng)的設(shè)計(jì)中扮演了較為重要的作用,這里借用Bilibili的點(diǎn)贊架構(gòu)來講述String在計(jì)數(shù)功能中發(fā)揮何種作用(為了方便理解有做改動(dòng),思想不變)

  • 核心需求:點(diǎn)贊數(shù)目、最近點(diǎn)贊列表
  • 技術(shù)選型:Redis+MySQL,更新方式采用CacheAside

數(shù)據(jù)模型:

  • Redis中:

以likeCount:userId存儲(chǔ)某個(gè)稿件點(diǎn)贊的數(shù)目

key-value = likeCount:{userId}- {likes},{disLikes}
//用業(yè)務(wù)ID和該業(yè)務(wù)下的實(shí)體ID作為緩存的Key,并將點(diǎn)贊數(shù)與點(diǎn)踩數(shù)拼接起來存儲(chǔ)以及更新

以Zset存儲(chǔ)最近的點(diǎn)贊,但是此集合不能無限膨脹,需要剪裁,當(dāng)需要更多信息時(shí),返回DB以查詢更多

key-value = user:likes:{entityId} - member(messageID)-score(likeTimestamp)

* 用userId作為key,value則是一個(gè)ZSet,member為被點(diǎn)贊的實(shí)體ID,score為點(diǎn)贊的時(shí)間。

當(dāng)改業(yè)務(wù)下某用戶有新的點(diǎn)贊操作的時(shí)候,被點(diǎn)贊的實(shí)體則會(huì)通過 zadd的方式把最新的點(diǎn)贊\

記錄加入到該ZSet里面來

* 為了維持用戶點(diǎn)贊列表的長度(不至于無限擴(kuò)張),需要在每一次加入新的點(diǎn)贊記錄的時(shí)候,

按照固定長度裁剪用戶的點(diǎn)贊記錄緩存。該設(shè)計(jì)也就代表用戶的點(diǎn)贊記錄在緩存中是有限度

長度的,超過該長度的數(shù)據(jù)請(qǐng)求需要回源DB查詢MySQL中:

有人會(huì)問了,Redis的效率極高,還支持持久化,為何我不采用set或者Zset以存在Redis里?這對(duì)于熱點(diǎn)的like數(shù)據(jù)不是更好嗎?

  • 點(diǎn)贊記錄表 - likes : 每一次的點(diǎn)贊記錄(用戶userId、被點(diǎn)贊的實(shí)體ID(entityId)、點(diǎn)贊來源、時(shí)間)等信息,并且在userId、entityId兩個(gè)維度上建立了滿足業(yè)務(wù)求的聯(lián)合索引。
  • 點(diǎn)贊數(shù)表 - counts :以實(shí)體ID(entityID)為主鍵,聚合了該實(shí)體的點(diǎn)贊數(shù)、點(diǎn)踩數(shù)等信息。并且按照entityID維度建立滿足業(yè)務(wù)查詢的索引。

Why Not Set or Zset?

  • 首先說說Set實(shí)現(xiàn)like有什么問題,首當(dāng)其沖的是用無序的set存幾乎沒有任何拓展性,比較經(jīng)典的實(shí)現(xiàn)是,在以like:{entityId}的set鍵里存儲(chǔ)userId,在調(diào)用它isMember以查看是否點(diǎn)贊過,以及其大小時(shí),可以獲得風(fēng)馳電掣的速度,但在面對(duì)諸如點(diǎn)贊列表、分析用戶在時(shí)間尺度上的點(diǎn)贊行為等業(yè)務(wù)需求上,set顯得無能為力

而對(duì)于Zset,似乎是實(shí)現(xiàn)此結(jié)構(gòu)的天然首選

Member-存entityId
Score-存時(shí)間戳
點(diǎn)贊列表-求zset的最后n項(xiàng)即可

其排序和查找特性似乎是為了上述的需求量身定制,然而Zset的問題比set還要糟糕,set僅僅是業(yè)務(wù)拓展能力不足,Zset作為點(diǎn)贊的容器極有可能引起redis主線程的阻塞:

  • Zset對(duì)于isMember的查詢是O(N)的,也就是說無異于去遍歷整個(gè)鏈表,這對(duì)于業(yè)務(wù)量很可能在十幾萬甚至上百萬的點(diǎn)贊上來說,是不可接受的開銷
  • 還是數(shù)據(jù)量的問題,對(duì)于每個(gè)實(shí)體來說都要存上十萬個(gè)實(shí)體id與它們的時(shí)間戳,這樣的實(shí)體可能還有上萬個(gè),這對(duì)Redis是無法承受之重,畢竟Redis也不是專門服務(wù)你點(diǎn)贊業(yè)務(wù)的(這個(gè)問題在Set中同樣存在)

String在分布式鎖中的應(yīng)用

SET 命令有個(gè) NX 參數(shù)可以實(shí)現(xiàn)「key不存在才插入」,可以用它來實(shí)現(xiàn)分布式鎖:

這個(gè)命令是:

加鎖

SET {加鎖的鍵} {客戶端標(biāo)識(shí)} NX PX {持有鎖的最大時(shí)間}’

  • 如果 key 不存在,則顯示插入成功,可以用來表示加鎖成功
  • 如果 key 存在,則會(huì)顯示插入失敗,可以用來表示加鎖失敗
  • 客戶端標(biāo)識(shí)用于表示此鎖的擁有權(quán)
  • PX 10000表示此鎖在10000秒內(nèi)失效,防止發(fā)生異常產(chǎn)生無法釋放鎖的情況

釋放鎖的過程就是刪除此鍵,讓我們回想一下CAS思路下的替換操作

這顯然是兩步操作,需要用LUA腳本來進(jìn)行原子化,具體的邏輯如下

// 釋放鎖時(shí),先比較 unique_value 是否相等,避免鎖的誤釋放
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end
  • 首先是確認(rèn)此鎖屬于自己(客戶端id、是否是原值,等等形式)
  • 若是屬于自己,再對(duì)其進(jìn)行改動(dòng),如釋放、釋放后通知在等待鎖的線程(此例子中是刪除此鍵)
  • 其他的應(yīng)用
  • 共享Session、JSON對(duì)象、單點(diǎn)過濾,此處不再一一贅述。

總結(jié)

作為KV型數(shù)據(jù)庫中最簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu),SDS的功用僅僅是為Redis的強(qiáng)大開了個(gè)頭,但即便是如此簡(jiǎn)單的結(jié)構(gòu),我們?nèi)阅茉谄渲锌匆娫S多其為了避免Redis陷入阻塞噩夢(mèng)的巧思,在接下來的介紹中我們能看見更多的Redis的精妙設(shè)計(jì)與實(shí)現(xiàn)

參考資料

《Redis設(shè)計(jì)與實(shí)現(xiàn)》

《Redis開發(fā)與運(yùn)維》

以上就是Redis的鍵String全面詳解的詳細(xì)內(nèi)容,更多關(guān)于Redis鍵String的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論