自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

使用Memcache做頻率限制引發(fā)的問(wèn)題

存儲(chǔ) 存儲(chǔ)軟件
本文簡(jiǎn)略介紹了一種限制用戶資源訪問(wèn)頻率的實(shí)現(xiàn)方案,并就實(shí)現(xiàn)中遇到的一些問(wèn)題,分享出來(lái)與大家共勉。

 線上服務(wù)為了限制用戶頻繁訪問(wèn)敏感資源,通常會(huì)引入一種機(jī)制來(lái)限制這種訪問(wèn)操作。其中一種常見(jiàn)的方案就是為每個(gè)用戶的訪問(wèn)做一次時(shí)間戳,同一個(gè)用戶再次訪問(wèn)對(duì)應(yīng)資源時(shí),檢查當(dāng)前時(shí)間和已經(jīng)記錄的時(shí)間戳的差值 -- 如果此差值小于我們定義的超時(shí)時(shí)間,此次訪問(wèn)被判定為頻繁訪問(wèn)。

[[242633]]

我們?cè)谀诚到y(tǒng)的實(shí)現(xiàn)中便采用了此種機(jī)制,限定用戶在 1s內(nèi)不能連續(xù)訪問(wèn)2次,配合Memcache,實(shí)現(xiàn)起來(lái)非常簡(jiǎn)單。 核心代碼如下:

  1. public boolean isOutOfTime(String key){     
  2.    return memCachedClient.add(key,"abc",new Date(System.currentTimeMillis() + 1000)); 

 問(wèn)題

一切看起來(lái)很順利,直到有一天線上報(bào)錯(cuò)資源在100ms內(nèi)被訪問(wèn)兩次。也就是說(shuō),同一個(gè)用戶的超時(shí)鍵被設(shè)置為1s以后,100ms再次去檢查居然鍵過(guò)期了。 什么鬼?邏輯上無(wú)懈可擊的代碼怎么可能會(huì)有漏洞?先不管那些,復(fù)現(xiàn)再說(shuō)。

代碼簡(jiǎn)單粗暴,就是啟5個(gè)線程,每個(gè)線程連續(xù)嘗試過(guò)濾某個(gè)鍵十萬(wàn)次。

運(yùn)行上述代碼,每次都有很多鍵被判定為過(guò)期。充分分析整個(gè)流程,定位可能的問(wèn)題原因:

  1. 后臺(tái)業(yè)務(wù)服務(wù)器與Memcache服務(wù)器時(shí)鐘不同步。Memcache的過(guò)期時(shí)間是一個(gè)時(shí)間戳,而不是相對(duì)時(shí)間偏移量,所以如果Memcache客戶端和服務(wù)器有時(shí)間差的話,比如客戶端的時(shí)間比服務(wù)器時(shí)間慢1s,那么客戶端設(shè)置的過(guò)期時(shí)間(它當(dāng)前的時(shí)間 + 1000ms)在服務(wù)器看來(lái)卻已經(jīng)過(guò)期了。
  2. Memcache的鍵清理機(jī)制導(dǎo)致。在極端情況下(比如說(shuō)Memcache被分配的內(nèi)存不夠用了),Memcache會(huì)清理一些鍵值對(duì),即使這些鍵還沒(méi)有過(guò)期。

但是以上兩個(gè)原因中,時(shí)鐘不同步的原因很快被排除了。因?yàn)閺娜罩痉治鰜?lái)看,相當(dāng)一部分頻繁請(qǐng)求是被攔截下來(lái)的,如果時(shí)鐘不同步,應(yīng)該有相當(dāng)比例的頻繁請(qǐng)求被放過(guò)才對(duì)。并且跟運(yùn)維確認(rèn),線上的服務(wù)器都開(kāi)啟了時(shí)鐘同步功能,兩個(gè)服務(wù)器的時(shí)鐘差不會(huì)超過(guò)10ms。

現(xiàn)在看來(lái)只有內(nèi)存清理機(jī)制這一個(gè)原因了。研究了下Memcache的鍵清理機(jī)制,總結(jié)如下:

  1. 當(dāng)有新數(shù)據(jù)需要存儲(chǔ)的時(shí)候,Memcache會(huì)先看數(shù)據(jù)大小對(duì)應(yīng)的Slab是否有空閑Item,如果有,將數(shù)據(jù)存入Item,同時(shí)更新LRU表。
  2. 如果沒(méi)有空閑Item,Memcache會(huì)嘗試去看對(duì)應(yīng)Slab是否有過(guò)期鍵。如果有,清空過(guò)期鍵,將數(shù)據(jù)存入新的Item,同時(shí)更新LRU表。
  3. 如果沒(méi)有過(guò)期鍵,Memcache會(huì)嘗試申請(qǐng)一個(gè)新的Slab,如果申請(qǐng)成功,將數(shù)據(jù)存入新Slab對(duì)應(yīng)的Item,同時(shí)更新LRU表。
  4. 如果申請(qǐng)失敗,并且Memcache配置了強(qiáng)制淘汰機(jī)制,會(huì)將LRU鏈表尾部的Item強(qiáng)制清空,并存入新Item,同時(shí)更新LRU表。

總體看下來(lái),強(qiáng)制淘汰的觸發(fā)條件還是很苛刻的,并且具體的實(shí)現(xiàn)中,LRU鏈表分為Hot,Warm,Cold三個(gè)區(qū)域,新加入的數(shù)據(jù)會(huì)在Hot區(qū),等Hot區(qū)滿了,較早的數(shù)據(jù)才會(huì)被降級(jí)到其他區(qū)。也就是說(shuō),假設(shè)存入數(shù)據(jù)為大小為100B,對(duì)應(yīng)Slab在Memcache服務(wù)器上只有一個(gè)(一般會(huì)有很多),那么此Slab中可用Item數(shù)量約為10000個(gè)。在這種情況下,如果要觸發(fā)剛剛存入100ms的未過(guò)期鍵被強(qiáng)制清理的話,需要在100ms內(nèi)有超過(guò)10000條100B左右大小的數(shù)據(jù)寫入Memcache。在測(cè)試環(huán)境幾乎不可能。但是這是一個(gè)公共的Memcache,誰(shuí)知道呢?所以需要排除一下這個(gè)情況。

 診斷

本地起一個(gè)虛擬機(jī),裝個(gè)Memcache,順便打開(kāi)日志打?。ū緛?lái)的目的是為了看到鍵淘汰日志)。如果是強(qiáng)制淘汰機(jī)制引起,那在只有一個(gè)client的本地Memcache上,應(yīng)該就不會(huì)出現(xiàn)這個(gè)問(wèn)題(測(cè)試代碼可以控制鍵數(shù)量和寫入速度),但是不幸的是,在這個(gè)空的Memcache上也出現(xiàn)了同樣的現(xiàn)象 -- 這直接排除了此現(xiàn)象是由強(qiáng)制淘汰機(jī)制導(dǎo)致的的可能性。

在本地虛擬機(jī)啟動(dòng)的Memcache打印的日志中,發(fā)現(xiàn)了一個(gè)現(xiàn)象:所有時(shí)間戳都是類似于這樣的格式:1527001620,有點(diǎn)奇怪,比毫秒時(shí)間戳短。去查了一下源碼,果然被猜中:

而rel_time_t的定義為:

  1. typedef unsigned int rel_time_t; 

毫無(wú)疑問(wèn),Memcache的時(shí)間是用秒計(jì)算而不是毫秒。我們使用的客戶端接口方法:

  1. public boolean add(String key, Object value, Date expiry); 

非常具有誤導(dǎo)性,因?yàn)镈ate是精確到毫秒的,這也使我們一直理所當(dāng)然地以為Memcache提供毫秒精度的過(guò)期時(shí)間校驗(yàn),然而這是不對(duì)的。

 原因

至此,問(wèn)題的原因就很明朗了,Memcache的過(guò)期判斷代碼如下:

最重要的一句是:

  1. it->exptime <= current_time  

即:過(guò)期檢測(cè)中,當(dāng)前時(shí)間與過(guò)期時(shí)間相等即被判定為過(guò)期。 在這個(gè)前提下,當(dāng)如下情況發(fā)生時(shí)就會(huì)偶現(xiàn)線上的現(xiàn)象。

  1. 第一個(gè)請(qǐng)求,當(dāng)前時(shí)間××××01900 ,計(jì)算出的過(guò)期時(shí)間是××××02900(+1000ms) → 存入的過(guò)期時(shí)間是××××02
  2. 第二次請(qǐng)求,當(dāng)前時(shí)間××××02000,計(jì)算出的過(guò)期時(shí)間是××××03000(+1000ms) → 請(qǐng)求時(shí),服務(wù)器判斷鍵過(guò)期(鍵過(guò)期時(shí)間 ××××02,當(dāng)前時(shí)間××××02) 此次請(qǐng)求add成功。

第一次請(qǐng)求和第二次請(qǐng)求僅隔100ms。

事實(shí)上,如果過(guò)期時(shí)間設(shè)置為1000ms,Memcache能幫我們隨機(jī)過(guò)濾0 ~ 1000ms內(nèi)的請(qǐng)求。頻繁請(qǐng)求是否被過(guò)濾依賴于最后一次成功請(qǐng)求的時(shí)間。

總結(jié)

使用Memcache的add方法做過(guò)期判斷時(shí)需要注意以下三點(diǎn):

  • Memcache客戶端與服務(wù)器時(shí)間要同步;
  • 內(nèi)存被強(qiáng)制淘汰的可能性極低,除非過(guò)期時(shí)間比較長(zhǎng),Memcache內(nèi)存吃緊時(shí),需要關(guān)注此問(wèn)題;
  • 過(guò)期時(shí)間精度為秒。
責(zé)任編輯:武曉燕 來(lái)源: 樂(lè)得技術(shù)
相關(guān)推薦

2020-11-09 15:49:38

PHPMemcache網(wǎng)絡(luò)安全

2013-06-20 09:59:12

Javascriptvar

2021-12-28 21:43:51

緩存搜索頻率

2010-09-02 14:59:23

非授權(quán)DHCP

2022-03-20 10:40:11

Linuxawk 腳本

2021-09-12 17:27:41

PeerDepende項(xiàng)目命令

2018-07-19 09:43:41

MemcacheRedis緩存

2024-02-28 08:12:25

SSE接口代理

2017-09-26 10:00:15

前端JS語(yǔ)法

2010-01-07 11:21:25

2009-02-03 09:30:00

2021-04-08 19:07:54

安全Spring Bootendpoint

2021-11-23 09:00:59

消息堆積擴(kuò)容RocketMQ

2010-06-07 14:44:24

MySQL導(dǎo)入

2021-10-13 11:00:27

數(shù)據(jù)泄露漏洞信息安全

2010-01-15 10:32:24

LinuxMemcache

2009-06-30 16:08:19

性能問(wèn)題代碼寫法

2023-06-25 08:05:09

MySQL事務(wù)并發(fā)

2013-09-30 09:18:39

2021-09-07 10:57:30

物聯(lián)網(wǎng)安全物聯(lián)網(wǎng)IOT
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)