成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

使用Memcache做頻率限制引發的問題

存儲 存儲軟件
本文簡略介紹了一種限制用戶資源訪問頻率的實現方案,并就實現中遇到的一些問題,分享出來與大家共勉。

 線上服務為了限制用戶頻繁訪問敏感資源,通常會引入一種機制來限制這種訪問操作。其中一種常見的方案就是為每個用戶的訪問做一次時間戳,同一個用戶再次訪問對應資源時,檢查當前時間和已經記錄的時間戳的差值 -- 如果此差值小于我們定義的超時時間,此次訪問被判定為頻繁訪問。

[[242633]]

我們在某系統的實現中便采用了此種機制,限定用戶在 1s內不能連續訪問2次,配合Memcache,實現起來非常簡單。 核心代碼如下:

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

 問題

一切看起來很順利,直到有一天線上報錯資源在100ms內被訪問兩次。也就是說,同一個用戶的超時鍵被設置為1s以后,100ms再次去檢查居然鍵過期了。 什么鬼?邏輯上無懈可擊的代碼怎么可能會有漏洞?先不管那些,復現再說。

代碼簡單粗暴,就是啟5個線程,每個線程連續嘗試過濾某個鍵十萬次。

運行上述代碼,每次都有很多鍵被判定為過期。充分分析整個流程,定位可能的問題原因:

  1. 后臺業務服務器與Memcache服務器時鐘不同步。Memcache的過期時間是一個時間戳,而不是相對時間偏移量,所以如果Memcache客戶端和服務器有時間差的話,比如客戶端的時間比服務器時間慢1s,那么客戶端設置的過期時間(它當前的時間 + 1000ms)在服務器看來卻已經過期了。
  2. Memcache的鍵清理機制導致。在極端情況下(比如說Memcache被分配的內存不夠用了),Memcache會清理一些鍵值對,即使這些鍵還沒有過期。

但是以上兩個原因中,時鐘不同步的原因很快被排除了。因為從日志分析來看,相當一部分頻繁請求是被攔截下來的,如果時鐘不同步,應該有相當比例的頻繁請求被放過才對。并且跟運維確認,線上的服務器都開啟了時鐘同步功能,兩個服務器的時鐘差不會超過10ms。

現在看來只有內存清理機制這一個原因了。研究了下Memcache的鍵清理機制,總結如下:

  1. 當有新數據需要存儲的時候,Memcache會先看數據大小對應的Slab是否有空閑Item,如果有,將數據存入Item,同時更新LRU表。
  2. 如果沒有空閑Item,Memcache會嘗試去看對應Slab是否有過期鍵。如果有,清空過期鍵,將數據存入新的Item,同時更新LRU表。
  3. 如果沒有過期鍵,Memcache會嘗試申請一個新的Slab,如果申請成功,將數據存入新Slab對應的Item,同時更新LRU表。
  4. 如果申請失敗,并且Memcache配置了強制淘汰機制,會將LRU鏈表尾部的Item強制清空,并存入新Item,同時更新LRU表。

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

 診斷

本地起一個虛擬機,裝個Memcache,順便打開日志打印(本來的目的是為了看到鍵淘汰日志)。如果是強制淘汰機制引起,那在只有一個client的本地Memcache上,應該就不會出現這個問題(測試代碼可以控制鍵數量和寫入速度),但是不幸的是,在這個空的Memcache上也出現了同樣的現象 -- 這直接排除了此現象是由強制淘汰機制導致的的可能性。

在本地虛擬機啟動的Memcache打印的日志中,發現了一個現象:所有時間戳都是類似于這樣的格式:1527001620,有點奇怪,比毫秒時間戳短。去查了一下源碼,果然被猜中:

而rel_time_t的定義為:

  1. typedef unsigned int rel_time_t; 

毫無疑問,Memcache的時間是用秒計算而不是毫秒。我們使用的客戶端接口方法:

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

非常具有誤導性,因為Date是精確到毫秒的,這也使我們一直理所當然地以為Memcache提供毫秒精度的過期時間校驗,然而這是不對的。

 原因

至此,問題的原因就很明朗了,Memcache的過期判斷代碼如下:

最重要的一句是:

  1. it->exptime <= current_time  

即:過期檢測中,當前時間與過期時間相等即被判定為過期。 在這個前提下,當如下情況發生時就會偶現線上的現象。

  1. 第一個請求,當前時間××××01900 ,計算出的過期時間是××××02900(+1000ms) → 存入的過期時間是××××02
  2. 第二次請求,當前時間××××02000,計算出的過期時間是××××03000(+1000ms) → 請求時,服務器判斷鍵過期(鍵過期時間 ××××02,當前時間××××02) 此次請求add成功。

第一次請求和第二次請求僅隔100ms。

事實上,如果過期時間設置為1000ms,Memcache能幫我們隨機過濾0 ~ 1000ms內的請求。頻繁請求是否被過濾依賴于最后一次成功請求的時間。

總結

使用Memcache的add方法做過期判斷時需要注意以下三點:

  • Memcache客戶端與服務器時間要同步;
  • 內存被強制淘汰的可能性極低,除非過期時間比較長,Memcache內存吃緊時,需要關注此問題;
  • 過期時間精度為秒。
責任編輯:武曉燕 來源: 樂得技術
相關推薦

2020-11-09 15:49:38

PHPMemcache網絡安全

2013-06-20 09:59:12

Javascriptvar

2021-12-28 21:43:51

緩存搜索頻率

2010-09-02 14:59:23

非授權DHCP

2021-09-12 17:27:41

PeerDepende項目命令

2017-09-26 10:00:15

前端JS語法

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

消息堆積擴容RocketMQ

2024-02-28 08:12:25

SSE接口代理

2022-03-20 10:40:11

Linuxawk 腳本

2021-10-13 11:00:27

數據泄露漏洞信息安全

2018-07-19 09:43:41

MemcacheRedis緩存

2021-09-07 10:57:30

物聯網安全物聯網IOT

2009-06-30 16:08:19

性能問題代碼寫法

2013-09-30 09:18:39

2023-06-25 08:05:09

MySQL事務并發

2018-10-14 15:52:46

MySQL數據清理數據庫

2010-06-07 14:44:24

MySQL導入
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久国产精品一区二区 | 亚洲欧美一区二区三区1000 | 国产精品美女久久久久久免费 | 特黄色一级毛片 | 国产精品色一区二区三区 | 中文字幕av在线播放 | av天天操 | 四虎国产| 久久免费精品 | 久草福利| 亚洲精品乱码久久久久久黑人 | 91免费观看 | 麻豆一区二区三区 | 九九av | 成人免费视屏 | 国产一区二区久久 | 瑟瑟激情| 在线免费观看黄a | 国产精品高 | 亚洲视频免费在线观看 | 国产精品污www一区二区三区 | 97国产爽爽爽久久久 | 91pao对白在线播放 | 久久综合久久自在自线精品自 | 久久大陆| 国产精品爱久久久久久久 | h片在线看 | 女人毛片a毛片久久人人 | 精品视频久久久久久 | 免费观看一级毛片视频 | 99re热这里只有精品视频 | 国产精品久久久久久久7电影 | 国产黄色在线观看 | 日韩三级 | 亚洲一区二区三区在线观看免费 | 日本黄色高清视频 | 国产精品精品久久久 | 成人午夜激情 | 一级毛片观看 | 久久久国产精品一区 | 99精品亚洲国产精品久久不卡 |