為什麼點贊手速過快會出現計贊 2 次?

問題可能略弱,但實在好奇……
在知乎和微博都可能出現這樣的狀況,這只是前端顯示問題呢?還是延遲問題?標記點贊的用戶ID只有一個,資料庫到底怎麼計數呢?跟系統所使用架構有關係嗎?如果是Bug大家都知道,為什麼不改=_=
謝各位 ^_^

附知乎圖:
點贊同前

點贊同後

取消贊同


這應該是交互設計的問題+bug的共同結果。

(各位!點了贊就不要取消了啊!試驗之後點了就點了,別刪啊!你能理解提示有幾十個贊同,打開之後才發現還是原來那幾隻……)

--------------------------------------------------以下為詳細解釋--------------------------------------------------

1.交互方面
在設計產品的時候,有一個原則叫做「Don"t make me wait」。
等待,會讓用戶流失。無反饋的長時間等待,會讓用戶大量流失。

有數據表明,如果網頁載入時長超過3秒,超過半數的用戶會直接走開。

所有做產品的都希望載入時間越短越好,最好是短到沒有。但很遺憾,這並不現實。很多時候客戶端(網頁)需要向伺服器獲取數據來實現頁面上內容的更新。而這樣的一來一回至少也需要幾十甚至上百毫秒。為了避免這零點幾秒的等待,產品狗們想出了一個花招——先展示後同步。

對於那些不重要的數據,在用戶操作的時候,先直接在本地(客戶端或網頁)上展示出來,同時在後台向伺服器同步數據。當獲得返回值的時候,如果一切正常,就不做任何顯示;如果出現了什麼錯誤結果,再視情況向用戶展示不同的錯誤信息。

知乎的點贊就是這樣。簡略流程如下。

  1. 用戶點贊。
  2. 客戶端(或網頁)在本地(就是用戶自己的電腦上)顯示出點贊行為(向上箭頭變藍)且點贊數+1。
  3. 客戶端(或網頁)向伺服器發送數據。
  4. 伺服器接收數據,並在資料庫中記錄用戶的點贊行為。
  5. 伺服器向客戶端(或網頁)返回點贊成功的消息。
  6. 操作完結。

在這樣的流程下,如果用戶在短時間內快速連續點贊,快到在一次「發送-返回」流程完成之前就點了第二下,那麼在客戶端(或網頁)的本地就會顯示出「點贊數+2」的效果。當然,在伺服器的記錄中只記錄了點贊+1。只要刷新一下數據就正常了。

(上述流程是為了方便理解而寫的簡略流程,不是正式的流程)

2.bug方面
當然,產品設計師們是知道這個漏洞的。所以在產品開發的時候,會在按鈕上加一個限制。當按鈕被點擊之後就會鎖定點擊的狀態,不允許再次點擊。而按題主的說法,連續兩次點擊有效。就說明這應該是按鈕被點擊的時候沒有及時切換狀態就觸發了第二次點擊。這應該是一個bug。


為了點贊也是蠻拼的(淚奔~~

====================

這個是網路交互的問題。

我們來腦補一下從點贊到贊數加一再到贊按鈕變成已贊同的過程中發生了什麼。

首先點贊這個按鈕實現的功能是向伺服器發送贊數加一,然後在自己的手機上直接添加一個贊的數值。注意,這個過程手機上顯示的贊數並不是從伺服器取出,而是原來伺服器得到的數值加上你按贊的次數。所以這時候點幾下就相當於加幾個贊。

伺服器收到你點贊的請求之後,返回已經點贊的確認信息到你的手機。

手機收到伺服器發過來的確認信息之後,把點贊按鈕替換成已贊同。

從手機客戶端退出來再進入問題,這時候點贊數是重新從伺服器拿到的,所以就恢復正常了。


這個問題涉及到兩方面:點贊時給予用戶的反饋;點贊時客戶端與伺服器的通信。

設想如下場景:知乎某大V對於某熱門問題添加了一個精彩的答案,在某一個時刻同時有一百個用戶點下了「贊同」。

策略一:當用戶點擊「贊同」時,實時從伺服器獲取最新的贊同數量,取消時亦然。
對於這一百個用戶而言,優點是可以直觀地獲取最新數據,缺點在於,終端有反應速度快的iPhone6P,有速度較慢的國產Android平板;網速有快的4G和Wi-Fi,有速度較慢的GPRS。該種策略會導致處理速度慢,網速慢的終端需要1s以上的時間給用戶反饋,體驗較差,沒有即時感,會讓用戶產生「我成功點贊了嗎」的疑問;
對於伺服器而言,伺服器要處理100個點贊請求,發送100個新的數據包,共200次操作。如果50個用戶再取消點贊,就是300次操作。

策略二:當用戶點擊贊同時,直接在點贊數上+1,取消時撤銷更改。這個+1和取消的功能由本地update函數實現。
首先要說明的是,用戶在點擊贊同前,已經從伺服器獲得了該答案的贊同數X。
對用戶而言:獲得了實時的反饋,產生了「我也是點贊大軍的一分子」感覺(或者「哈哈哈這個答案只有我看得懂你們這群凡人」);缺點在於數據非實時,同時會出現題主提到的BUG。由於每次點擊小三角,由本地直接調用update函數,如果快速雙擊,就可能導致該函數被執行兩次。而取消時,直接用原本獲取的數據覆蓋掉現有數據即可,所以不存在連點兩次-2的情況。
更正:出現-1的情況就是連續兩次減一。。。
對於伺服器:100個點贊50個取消只需要150次操作

總體來講,策略二要優於策略一。對於用戶來講,獲得「我點贊了!」的反饋要遠遠優於獲得「這是真實實時的數據」的反饋。
——————————
以下為推測
但實際上,知乎並沒有單獨使用這兩種策略,而是綜合了兩種策略:每次點贊時,檢測最後一次獲取贊同數的時間;如果最後一次獲取的時間和現在的時間相差過大(大約為幾分鐘),就從伺服器實時刷新,否則就採用策略二。
理由是:在幾分鐘之內,贊同數變化不會太大,策略二的結果基本上不會與實際數據偏差太多(1347和1349贊幾乎沒區別),時間過長就會產生較大的誤差,因此從伺服器拉取。

——————————
這個答案點兩下就有兩個贊哦


頁面發起一個AJAX 請求,請求伺服器,你要點贊,伺服器開始看你有沒有點贊,有 無法點贊,沒有,可以點贊

並發,有10個人在統一毫秒內,時間完全一致的點擊了這個贊,不知道知乎那邊用的是什麼來處理並發,反正是會有方法來處理,讓同一時間的事情,變成一條一條來操作

這個就是點贊的基本情況

還有人點贊那我就補上兩句
如果沒有處理並發,那麼一個用戶 用極快的速度點贊 那麼就會出現贊+2 +3等可能來出現
如果用軟體 完全可以讓一個用戶給一個人點10~20個以上的贊 甚至更多


還有人點贊 那我就在詳細一點

有人來點贊 請求到伺服器了 伺服器想我得看看你有沒有點過 (在資料庫查詢發現點贊的值為0) 然後 剛要改寫 吧0改寫成為+1的時候 又來了一個請求 還是點贊的請求 這個時候 伺服器再次查詢 發現你的點贊次數還是0 然後 繼續改寫+1 這樣····你就給一個答案加了兩次贊~


真是快被你們玩壞了,說了不要點贊不要點贊,你們點個感謝也行啊,處女座的我快被你們弄的強迫症複發了。。。
-------------------------------------------

新的問題出現,請不要再點贊了。 @知乎小管家 下面的情況是因為他們都是點的兩下嗎?

------------------------------------------------------
之前的回答請暫時忽略。


我手上的滑鼠微動開關可能有問題,經常出現點一次變兩次的情況,於是拿首頁上的某個問題試了下,10分鐘左右,目前未發現多出一贊的情況,偶爾會顯示0或者3,不過就像中獎的轉盤一樣,等停下來了就變成正確的贊數,網頁端,不知道有無幫助。


這個問題初看挺簡單的,前台發了兩次post,這本身是前台設計的一個bug,其他同學也提到可以通過事先禁用按鈕改進。

但是,前台UI並不能阻止有人通過直接寫程序來發批量post。

歸根結底是後台資料庫事務處理的問題。當兩個post幾乎同時發送過來時,web伺服器可能有兩個thread甚至兩個vm來分別處理這兩個post。

假設資料庫結構是一張vote表,有questionid和userid兩列。那麼一種普通的邏輯是先select看此用戶有沒有贊過,如果沒有則insert。

假設沒有對questionid及userid做primary key,並且沒有在select的時候就上比較重的鎖,那麼有可能兩個select都先取到0行結果並分別執行了insert。結果是表中出站兩條相同記錄。

在顯示贊個數的時候則直接select count(*) from vote where questionid=xx。

從取消贊能把贊數恢復成0來看,更像是使用了上述表設計,而不是直接記錄贊的個數,要不然取消這個贊後的結果應該是1。

如何解決,簡單點的就是把questionid+userid做個unique constraint或者在事務過程中加一些鎖。但這些都有可能影響性能,相比來說這個問題本身的影響並不大。


3你們都起開!!
起初我以為是這樣:
「因為,從你打開這個頁面到你點贊的時間裡,還有別人點贊。」
後來@李語語 在評論里說「不是不是 我取消贊 就為0了 反覆試了好幾遍」

首先說結論:「出現這種問題不是因為點得快!」
===========================
點贊的交互過程應該是這樣的,web和app交互是類似的:
1. 用戶點贊
2. 發起Ajax POST請求後端http://www.zhihu.com/node/AnswerVoteBarV2

3. 後端針對這個答案和點贊人的id(session中取的)進行判重
4. 沒有重複,則資料庫計數自增
5. Ajax發起GET請求http://www.zhihu.com/node/AnswerVoteInfoV2?params=%7B%22answer_id%22%3A%2212466108%22%7D獲取當前被贊的數量,重新顯示在頁面上

===========================
大致能推測知乎的點贊是這麼實現的:
首先要介紹一個東西Bloom filter ,這個東西的主要作用就是可以以O(1)時間複雜度去判斷一個數據是否重複,但有個「小小」的缺點,存在一定的誤判概率。
所以這個演算法會經常用於投票的場景,而題主遇到的情況應該就是這個小概率事件。
===========================
用通俗的語言介紹一下Bloom Filter吧,就是預先設定N個哈希函數,例如
函數1:x % 2
函數2:md5(x) % 2
函數3:sha(x) % 2
……
函數N:md5(x * x) % 2

比如說我點了個贊,用戶id是123,通過這四個函數可以獲得N個比特位,記錄下來
0,1,0,0,……,0,1
如果再來一個人點贊,得到
1,0,0,1,……,0,1
然後數據記錄進行或的位運算得到
1,1,0,1,……,0,1
如果我再點贊,就會判斷到0,1,0,0,……,0,1在1,1,0,1,……,0,1中1的位置已經都為1了,則判斷我已經點過贊
========================
寫到這裡,我發現好像解釋不了這個問題,明天再想想吧,⊙﹏⊙b汗
========================

根據目前已知的情報,只能走到這裡了,他們應該沒用bloom filter,留在這裡方便大家了解我的分析過程。


正常的無bug點贊的過程應該是這樣的

用戶按下滑鼠後

1.前端禁用或者改變點贊按鈕,變成不能再點的狀態

2.向後台發送數據

3.後台驗證用戶是否登錄

4.後台驗證用戶是否已經贊過(查詢資料庫需要時間)如果已經贊過,跳過5 6步 直接返回數據

5.主題被贊次數+1

6.記錄用戶已經贊過

7.向前台返回數據


如果有些程序,網站點贊的時候沒有第一步防止連點的操作,用戶兩次(多次)點贊的時間小於後台程序操作第4步和第5步的時間,那麼就會出現多次點贊都被記錄的bug


因為沒有用callback。

——更新下答案:
不是沒用cb,而是ui操作和cb的順序。

但這個也是一個體驗上的設計問題,先更新ui,用戶不會感到卡頓;等待回調再更新,在網速不好的情況下,用戶會以為沒點擊成功,故而多次點擊,造成伺服器多次響應,會增加伺服器壓力。

所以←_←,綜合來說,較優的方案是先更新,並在回調後檢驗,也就是目前的方案。此外,不論怎麼做,其實,都不會-_-||造成點贊數據的多次統計啦。


快速點擊可能帶來了平行世界的一次漣漪。


手速過快明明不會是2次贊,而是三次,我試了好幾次,都是三次。
這種系統出現這樣的錯誤,都只能是奇數而不是偶數的。

——————補充————


說錯了,是你如果說點贊太快,是總贊數變到下一個奇數,比方說3變成5,5變成7。但如果上一個是偶數的話,比如8,那就會變成9。
(你們仔細看看就知道了,失敗了就再按一下點贊取消即可)

但是人不可能快到那一瞬間就讓數字變到11(除非你用兩個手指超快的點).

此外關注也會有這樣的情況,快速按關注的話,對方就會有兩個提示,速度更快會出來更多(只有一次成功變成四個)。
似乎關注提示信息跳出是以偶數形式出現的。

感謝的話倒是從來不會出錯的。


我曾經總遇到這問題;結果我取消贊總用點反對後取消反對的方法...


首先,你的網頁中的顯示不一定是當前這一個話題的實時贊同數,但一定是開發者想讓你看到的效果。

其次,贊同數在網頁中顯示的跟後台伺服器的運行沒有任何關係。如果說能夠連續兩次按贊同,那麼肯定是有其他人也在按贊同。否則就是這個程序猿出了嚴重的問題,沒有對這個東西進行判斷。按照正常的流程,用戶不管點多少個贊,哪怕點100個贊,這些點贊的記錄都會進入一個點贊的隊列裡頭。隊列的特點就是先進先出,後進後出。你的第一次點贊會進行一下你有沒有對這個話題進行點贊的記錄,如果沒有這個記錄在資料庫裡頭查不到,那麼就這一次,點贊會有效.這一次會將點贊數加一同時將你的點贊記錄,寫入資料庫,直到這些步驟都完成了之後才會進行隊列中下一個點贊記錄的處理。
如果你緊接著又點了一個贊,那麼系統運行到你這一條點贊記錄的時候,就會發現你已經對這條記錄,點了一個贊了。這時候你這個點贊記錄就會失效。如果沒有嚴格執行這些步驟,那麼這個程序員肯定是有問題的。或者說這個遺留是他們的主管故意留出來的,或者說是因為急著上線,沒有對所有的功能進行詳盡而又全面的檢查。


這種事直接看源碼啊:(雖然經過混淆,但是辨識度依然很高)

PS:只貼了相關度最高的部分,貼完會比較多。有興趣的自己拖下來看。

key: "handleVote",
value: function(e) {
var t = this.props,
n = t.voting,
r = t.onVote,
o = void 0,
a = void 0;
"up" === e ? 1 === n ? (o = "neutral", a = -1) : (o = "up", a = 1) : 1 === n ? (o = "down", a = -1) : 0 === n ? (o = "down", a = 0) : (o = "neutral", a = 0);
var i = void 0;
i = "up" === o ? "Upvote" : "neutral" === o ? a === -1 ? "UnUpvote" : "UnDownvote" : "Downvote", y.default.trackEvent(this, {
action: i,
element: "Button"
}), r(o, a)
}}

以下是沒什麼用的原答案

1:點擊以後沒有取消事件綁定。一般的做法是點擊以後取消事件綁定,接收到response以後重新綁定。(當然,也可以隱藏按鈕等等)。現在的bug更蛋疼:在評論區點贊以後需要手動刷新才能看到數字變化,點兩次會看到數字+1然後再-1,點第三次正常顯示。(反對樓上某說法【大網站就沒毛病】)

2:服務端可能沒有判定是否重複提交。(那等會兒我研究一下是否可以開發一個刷贊小工具?然而肯定會被封號的。)

3:2提到的問題也要考慮資源消耗。可能是權衡以後選擇了不進行判定,也可能是:根本就把這些問題忽略了。

4:公司的運作問題:可能是bug提交以後卡在工作流的某個節點上了。所以有bug沒人改。


程序員不用心啊。與後台交互先設置clickable為false,返回後再設為true就好了。
同樣的bug在創建收藏夾時也會發生。


為了不讓網速慢的你,在點擊後感到「無響應」罷了。


很簡單,當你看到點出2個贊的時候,刷新一下頁面,看看是否回到1個贊。
如果是,則說明剛才的2個贊是為了更快的客戶體驗,在純客戶端做的+1,速度太快沒有控制好按鈕狀態所以變成了+2。

如果還是2,那麼無疑是個大bug,後台沒有校驗重複點贊的問題。


點出了-1.。。。。。。。


這個問題這麼人關注,我有點沒搞懂~~~~~~~~~~


真的有這種情況嗎…我一直以為是騙人的


推薦閱讀:

零基礎自學 Android 並去找工作可行性大么?
「傳輸自動生成代碼並執行、生成所需文件」能否成為未來的下載方式?
在 GitHub 上保持 365 天全綠是怎樣一種體驗?
為什麼 Qt Creator 的編譯如此之慢?
兒童學編程,教什麼語言好?

TAG:互聯網 | 前端開發 | 編程 | 知乎使用 | 贊同(知乎功能) |