<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          舒服,又偷學(xué)到一個(gè)高并發(fā)場(chǎng)景面試題的解決方案.

          共 3435字,需瀏覽 7分鐘

           ·

          2021-12-27 15:49

          你好呀,我是歪歪。

          是這樣的,前幾天我收到掘金的系統(tǒng)通知說我在活動(dòng)中中獎(jiǎng)了。

          dfa1064b19dc263a6eadb1ecd094216f.webp

          然后我點(diǎn)進(jìn)去一看,好家伙榮登榜眼:

          3eb4b2a7dac878a9ae5601c9010e4902.webp

          是我之前寫的這篇文章:《面試官:要不我們聊一下“心跳”的設(shè)計(jì)?》

          但是這不重要,重要的是我看一下第一名寫的文章,有點(diǎn)東西,又學(xué)到一個(gè)問題的解決方案,所以我想分享一下。

          文章地址在這里:

          https://juejin.cn/post/7041076758711369764

          高并發(fā) PV 問題

          他的文章標(biāo)題是這樣的:

          10caae0e3ba637531bad6bdf2a87c10f.webp

          首先他給出了一個(gè)業(yè)務(wù)場(chǎng)景:在一些需要統(tǒng)計(jì) PV(Page View), 即頁(yè)面瀏覽量或點(diǎn)擊量高并發(fā)系統(tǒng)中,如:知乎文章瀏覽量,淘寶商品頁(yè)瀏覽量等,需要統(tǒng)計(jì)相應(yīng)數(shù)據(jù)做分析。

          比如我的公眾號(hào)閱讀量大概在 3000 左右,如果要統(tǒng)計(jì)我這種小號(hào)主的 PV 其實(shí)就很簡(jiǎn)單,就用 Redis 的 incr 命令輕輕松松就實(shí)現(xiàn)了。

          但是,假設(shè)微信公眾號(hào)每天要統(tǒng)計(jì) 10 萬篇文章,每篇文章的訪問量 10 萬,如果采用 Redis 的 incr命令來實(shí)現(xiàn)計(jì)數(shù)器的話,每天 Redis=100 億次的寫操作,按照每天高峰 12 小時(shí)來算,那么 Redis 大約 QPS=57萬。

          如此大的并發(fā)量,CPU 肯定滿負(fù)載運(yùn)行,網(wǎng)絡(luò)資源消耗也巨大,所以直接使用 incr 命令這種技術(shù)方案是行不通的。

          47ef3430061ba89509aa998702d1a879.webp

          假設(shè)這是一個(gè)面試場(chǎng)景題,你會(huì)怎么去回答呢?

          其實(shí)你也別想的有多復(fù)雜,剝離開場(chǎng)景,這無外乎就是一個(gè)高并發(fā)的問題。

          而高并發(fā)問題的解決方案,基本上逃不過這三板斧:緩存、拆分、加錢。

          所以這個(gè)老哥給出的方案就是:緩存。

          二級(jí)緩存

          Redis 都已經(jīng)是緩存了,那么再加緩存算什么回事呢?

          那就算是二級(jí)緩存了。

          而且這個(gè)緩存,就在 JVM 內(nèi)存里面,比 Redis 還快。

          其核心思想是減少 Redis 的訪問量。這些理論的東西,大家應(yīng)該都知道。

          那么通過什么方案去減少 Redis 的訪問量呢,這個(gè)二級(jí)緩存應(yīng)該怎么去設(shè)計(jì)呢?

          首先,文章服務(wù)采用了集群部署,在線上可以部署多臺(tái)。

          然后每個(gè)文章服務(wù),增加一級(jí) JVM 緩存,即用 Map 存儲(chǔ)在 JVM,key 為當(dāng)前請(qǐng)求所屬的時(shí)間塊。

          就是這個(gè)意思:

          Map> = Map<時(shí)間塊,Map<文章id,訪問量>>

          但是我覺得巧妙的地方在于這里提到的“時(shí)間塊”的概念。

          什么是時(shí)間塊?

          就是把時(shí)間切割為一塊塊,例如:一篇文章在1小時(shí),30分鐘、5分鐘的時(shí)間內(nèi)產(chǎn)生了多少閱讀量。

          如何切割時(shí)間塊呢?

          這里利用到了時(shí)間是不斷增長(zhǎng)的特性。

          時(shí)間戳是自 1970 年1月1日(00:00:00 GMT)至當(dāng)前時(shí)間的總數(shù),通過確定時(shí)間塊大小,算出當(dāng)前請(qǐng)求所屬的時(shí)間戳從 1970 年算起位于第幾個(gè)時(shí)間塊,這個(gè)算出來的第幾個(gè)時(shí)間塊就是小時(shí) key ,即 map 的 key。

          舉個(gè)例子:

          我們把時(shí)間按照“小時(shí)”的維度進(jìn)行劃分。

          先把當(dāng)前的時(shí)間轉(zhuǎn)換為為毫秒的時(shí)間戳,然后除以一小時(shí),即當(dāng)前時(shí)間 T/1000*60*60=小時(shí)key,然后用這個(gè)小時(shí)序號(hào)作為key。

          比如:

          2021-12-26 15:00:00 = 1640502000000毫秒

          那么小時(shí)key= 1640502000000/1000*60*60=455695,即是距離 1970 年開始算的第 455695 個(gè)時(shí)間塊。

          2021-12-26 15:10:00 = 1640502600000毫秒,那么算出來的 key =455695.1,向下取整,key 還是等于 455695。

          意思是這段時(shí)間的時(shí)間塊是一樣的,所以統(tǒng)計(jì)到 JVM 內(nèi)存中的 Map 的時(shí)候,對(duì)應(yīng)的 key 是一樣的。

          畫個(gè)圖示意一下:

          6f434d9ed5f74cfbd32867de85566201.webp

          上圖中,在 2021-12-25?15:00:00 到?2021-12-25?15:59:59 時(shí)間段內(nèi)產(chǎn)生的閱讀量都會(huì)映射到 Map 的 key= 455695 的位置去。

          ?2021-12-25?16:00:00??2021-12-25?16:59:59 時(shí)間段內(nèi)產(chǎn)生的閱讀量都會(huì)映射到 Map 的 key= 455696 的位置去。

          以此類推,每一次PV操作時(shí),先計(jì)算當(dāng)前時(shí)間是那個(gè)時(shí)間塊,然后存儲(chǔ)Map中。

          整體方案

          當(dāng)我們把數(shù)據(jù)緩存到內(nèi)存中之后,就極大的減少了對(duì)于 Redis 的訪問。

          但是我們還是得把數(shù)據(jù)同步到 Redis 里面去,因?yàn)樵L問文章數(shù)據(jù)的時(shí)候還是得從 Redis 中獲取數(shù)據(jù)。

          所以,這里就涉及到一個(gè)問題:什么時(shí)候、怎么把數(shù)據(jù)同步到 Redis 呢?

          看一下作者給出的方案設(shè)計(jì):

          b33c7c1a2b68884b316b4ef64f49ec4a.webp

          整體流程還是比較清楚,主要說一下里面的兩個(gè)定時(shí)任務(wù)。

          其中一級(jí)緩存定時(shí)器的邏輯是這樣的:假設(shè)每 5 分鐘(可以根據(jù)需求調(diào)整)從 JVM 的 Map 里面把時(shí)間塊的閱讀 PV 讀取出來,然后 push 到 Redis 的 list 數(shù)據(jù)結(jié)構(gòu)中。

          list 存儲(chǔ)的數(shù)據(jù)為 Map<文章Id,訪問量PV>,即每個(gè)時(shí)間塊的 PV 數(shù)據(jù)。

          另外一個(gè)二級(jí)緩存定時(shí)器的邏輯是這樣的:每 6 分鐘(需要比一級(jí)緩存的時(shí)間長(zhǎng)),從 Redis 的 list 數(shù)據(jù)結(jié)構(gòu)中 pop 出數(shù)據(jù),即Map<文章Id,訪問量PV>。

          然后把對(duì)應(yīng)的數(shù)據(jù)同步到 DB 和 Redis 中。

          代碼實(shí)戰(zhàn)

          代碼主要分為四個(gè)步驟,我也把代碼粘過來給大家看看。

          步驟1:PV請(qǐng)求處理邏輯

          //保存時(shí)間塊和pv數(shù)據(jù)的map
          public??static?final??Map>?PV_MAP=new?ConcurrentHashMap();

          /**
          ?* pv請(qǐng)求調(diào)用:
          ?*?即當(dāng)前時(shí)間T/1000*60*60=小時(shí)key,然后用這個(gè)小時(shí)序號(hào)作為key。
          ?*?例如:
          ?*?2021-11-09?15:30:00?=?1636443000000毫秒?
          ?*?小時(shí)key=1636443000000/1000\*60\*60=454567.5=454567
          ?*
          ?*?每一次PV操作時(shí),先計(jì)算當(dāng)前時(shí)間是那個(gè)時(shí)間塊,然后存儲(chǔ)Map中。
          ?*?@param?id?文章id
          ?*/
          public?void?addPV(Integer?id)?{
          ????//生成環(huán)境:時(shí)間塊為5分鐘
          ????//為了方便測(cè)試?改為1分鐘?時(shí)間塊
          ????int?timer=1;
          ????long?m1=System.currentTimeMillis()/(1000*60*timer);
          ????//拿出這個(gè)時(shí)間塊的所有文章數(shù)據(jù)
          ????Map?mMap=Constants.PV_MAP.get(m1);
          ????if?(CollectionUtils.isEmpty(mMap)){
          ????????mMap=new?ConcurrentHashMap();
          ????????mMap.put(id,new?Integer(1));
          ????????//<1分鐘的時(shí)間塊,Map<文章Id,訪問量>>
          ????????Constants.PV_MAP.put(m1,?mMap);
          ????}else?{
          ????????//通過文章id?取出瀏覽量
          ????????Integer?value=mMap.get(id);
          ????????if?(value==null){
          ????????????mMap.put(id,new?Integer(1));
          ????????}else{
          ????????????mMap.put(id,value+1);
          ????????}
          ????}
          }

          步驟2:一級(jí)緩存定時(shí)器消費(fèi)

          定時(shí)(5分鐘)從 JVM 的 ?Map 把時(shí)間塊的閱讀 PV 取出來,然后 push 到 Reids 的 list 數(shù)據(jù)結(jié)構(gòu)中,list 的存儲(chǔ)的數(shù)據(jù)為 Map<文章id,訪問量PV> 即每個(gè)時(shí)間塊的 PV 數(shù)據(jù)

          /**
          ?*?一級(jí)緩存定時(shí)器消費(fèi)調(diào)用方法:
          ?*?定時(shí)器,定時(shí)(5分鐘)從jvm的map把時(shí)間塊的閱讀pv取出來,
          ?*?然后push到reids的list數(shù)據(jù)結(jié)構(gòu)中,list的存儲(chǔ)的書為Map<文章id,訪問量PV>即每個(gè)時(shí)間塊的pv數(shù)據(jù)
          ?*/
          public?void?consumePV(){
          ????//為了方便測(cè)試?改為1分鐘?時(shí)間塊
          ????long?m1=System.currentTimeMillis()/(1000*60*1);
          ????Iterator?iterator=?Constants.PV_MAP.keySet().iterator();
          ????while?(iterator.hasNext()){
          ????????//取出map的時(shí)間塊
          ????????Long?key=iterator.next();
          ????????//小于當(dāng)前的分鐘時(shí)間塊key?,就消費(fèi)
          ????????if?(key????????????//先push
          ????????????Map?map=Constants.PV_MAP.get(key);
          ????????????//push到reids的list數(shù)據(jù)結(jié)構(gòu)中,list的存儲(chǔ)的書為Map<文章id,訪問量PV>即每個(gè)時(shí)間塊的pv數(shù)據(jù)
          ????????????this.redisTemplate.opsForList().leftPush(Constants.CACHE_PV_LIST,map);
          ????????????//后remove
          ????????????Constants.PV_MAP.remove(key);
          ????????????log.info("push進(jìn){}",map);
          ????????}
          ????}
          }

          步驟3:二級(jí)緩存定時(shí)器消費(fèi)

          定時(shí)(5分鐘),從 Redis 的 list 數(shù)據(jù)結(jié)構(gòu) pop 彈出 Map<文章id,訪問量PV>,彈出來做了2件事:

          • 先把 Map<文章id,訪問量PV>,保存到數(shù)據(jù)庫(kù)
          • 再把 Map<文章id,訪問量PV>,同步到 Redis 緩存的計(jì)數(shù)器 incr

          步驟4:查看瀏覽量

          用了一級(jí)緩存,所有的高并發(fā)流量都收集到了本地 JVM,然后 5 分鐘同步給二級(jí)緩存,從而給 Redis 降壓。

          @GetMapping(value?=?"/view")
          public?String?view(Integer?id)?{
          ???//文章pv的key
          ????String?key=?Constants.CACHE_ARTICLE+id;
          ????//調(diào)用redis的get命令
          ????String?n=this.stringRedisTemplate.opsForValue().get(key);
          ????log.info("key={},閱讀量為{}",key,?n);
          ????return?n;
          }

          對(duì)應(yīng)視頻

          另外,我在 B 站也找到這篇文章對(duì)應(yīng)的視頻:

          https://www.bilibili.com/video/BV1PY411p7MG?p=1

          d682b17e6a202c3627cd5a635288f203.webpcfbd04674867b723506bbb23c05d7d8a.webp

          如果大家有沒有看明白的地方,可以去 B 站看一下對(duì)應(yīng)的視頻,講的還是很清楚的。

          整體方案是沒有問題的,時(shí)間塊的設(shè)計(jì)也非常的巧妙。

          當(dāng)然了如果你非要找方案的瑕疵的話,那就是數(shù)據(jù)時(shí)效性和數(shù)據(jù)一致性的問題了。

          其實(shí)我了解到這個(gè)方案之后,我還是覺得萬變不離其宗,這個(gè)方案就是一種合并提交的理念。

          比如我之前寫過的這篇文章,就聊到了請(qǐng)求合并的這個(gè)概念,有興趣的可以去看看:

          《面試官問我:什么是高并發(fā)下的請(qǐng)求合并?》

          荒腔走板

          前幾天趁著圣誕節(jié)這個(gè)機(jī)會(huì),順便求了個(gè)婚:

          d5eac657e45369ce41ca49083687e473.webp

          為什么是順便呢?

          因?yàn)?Merry Christmas,里面有 Marry me,所以可以假借圣誕節(jié)之名,行求婚之實(shí)。本來我的計(jì)劃是不經(jīng)意間把 Merry Christmas 變化成 Marry me 的。

          但是她一回家就發(fā)現(xiàn)了,然后對(duì)我說:你知道嗎,其實(shí)你可以把 Merry Christmas 變成 Marry me,這樣就變成求婚了。

          她邊說就開始邊操作。

          雖然這不在我的計(jì)劃內(nèi),但是誰(shuí)擺都是擺,所以等她擺字母的時(shí)候,我已經(jīng)單膝跪地,她一轉(zhuǎn)頭才發(fā)現(xiàn)原來這就是一場(chǎng)求婚。

          求婚只需要一分鐘,但是這一分鐘會(huì)是寶貴的回憶。

          拉個(gè)票

          時(shí)間荏苒,白駒過隙,轉(zhuǎn)眼間一年就過去了,又到一年拉票的時(shí)間了。

          回想上次拉票,仿佛還是在上次。

          有掘金 APP 的小伙伴可以幫我投上幾票:

          每人每天可以投的票數(shù)是遞增的,網(wǎng)頁(yè)端投完票了,APP 端還可以再投篇一直到 29 號(hào)。

          掘金是我體驗(yàn)過的一個(gè)非常不錯(cuò)的技術(shù)分享平臺(tái),如果你之前沒有接觸過的話,可以去了解一下,順便給我投上一票。

          我常常說寫文章是非常需要正反饋的,這就是體現(xiàn)正反饋?zhàn)詈玫臅r(shí)候了,我的目標(biāo)已經(jīng)從最開始的沖擊前 30 調(diào)整為保住前 100 了。

          能不能實(shí)現(xiàn)就看你投不投票了。不管你投不投我吧,至少謝謝你點(diǎn)進(jìn)了這篇文章,抱拳了,老鐵。

          7cb285d7ac4556c0169c72fbe980e110.webp

          推薦???:扒一扒這個(gè)注解,我發(fā)現(xiàn)還有點(diǎn)意思。

          推薦???:我也想說說日志,但是我不想說漏洞。

          推薦???:當(dāng)面試官問你這個(gè)問題的時(shí)候,想聽到什么?

          推薦???:面試官:你給我說一下什么是時(shí)間輪吧?

          推薦???:當(dāng)我看源碼的時(shí)候,我在想什么?

          ··································

          你好呀,我是歪歪。一個(gè)主要敲代碼,經(jīng)常懟文章,偶爾拍視頻的成都人。


          我沒進(jìn)過一線大廠,沒創(chuàng)過業(yè),也沒寫過書,更不是技術(shù)專家,所以也沒有什么亮眼的title。


          當(dāng)年以超過二本線 13 分的優(yōu)異成績(jī)順利進(jìn)入某二本院校計(jì)算機(jī)專業(yè),誤打誤撞,進(jìn)入了程序員的行列,開始了運(yùn)氣爆棚的程序員之路。


          說起程序員之路還是有點(diǎn)意思,可以看看。點(diǎn)擊藍(lán)字,查看我的程序員之路

          瀏覽 66
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  午夜探花视频 | 亚洲色图处女 | 亚洲天堂一区在线观看 | 欧美在线免费播放不卡欧美 | 操人天堂视频 |