使用 Redis 實現 SQL 伸縮
我喜歡Redis。這是目前的技術當中唯一讓你奇怪為什麼需要這麼長時間編譯它的技術。可預測的,高效能並且適應性強,這是我過去幾年越來越多使用它的原因。Sentry主要在PostgreSQL上運行已經不是秘密(儘管目前它還依賴於一系列其它技術)
一個多星期前,我在 Python Nordeste 上作了主題演講。某種程度上而言我只能作一些快速的總結,我決定去找一些駭客來探討大量使用Sentry,特別是Redis技術。這篇文章是一個5分鐘討論的擴充。
緩解行之間的爭奪
我們採用了早在哨兵發展的東西是什麼成為著名的sentry.buffers。這是一個簡單的系統,使我們能夠實現非常有效緩衝計數器,一個簡單的上次寫入贏的策略。重要的是要注意,我們完全與此幾乎杜絕任何形式的耐用性(這是非常可以接受的哨兵的工作方式)。
該操作是相當簡單的,每當一個更新進來,我們做到以下幾點:
1.建立綁定到給定實體散列鍵
2.增量'反'使用HINCRBY
3.HEST各種不同LWW資料(如“最後一次見到”)
4.ZADD散列鍵到'掛起'使用目前時間戳設定
現在,每個刻度(在哨兵的情況下,這是10秒),我們要轉儲這些緩衝區和扇出的寫入。這看起來像下面這樣:
1.開始使用ZRANGE所有鍵
2. 火了一個作業分成RabbitMQ的每一個懸而未決的關鍵
3. ZREM給定的鍵
現在RabbitMQ作業將能夠讀取和清除雜湊表,和“懸而未決”更新已經彈出了一套。有幾件事情需要注意:
在下面我們想要只彈出一個設定的數量的例子中我們將使用一組排序(舉例來說我們需要那100箇舊集合)。
假使我們為了處理一個索引值來結束多道排序的作業,這個人會得到no-oped由於另一個已經存在的處理和清空雜湊的過程。
該系統能夠在許多Redis節點上不斷擴充下去僅僅是通過在每個節點上安置把一個'懸置'主鍵來實現。
我們有了這個處理問題的模型之後,能夠確保“大部分情況下”每次在SQL中只有一行能夠被馬上更新,而這樣的處理方式減輕了我們能夠預見到的鎖問題。考慮到將會處理一個突然產生且所有最終組合在一起進入同一個計數器的資料的情境,這種策略對Sentry用處很多。
速度限制
出於哨兵的局限性,我們必須終結持續的拒絕服務的攻擊。我們通過限制連線速度來應對這種問題,其中一項是通過Redis支援的。這無疑是在sentry.quotas範圍內更直接的實現。
它的邏輯相當直接,如同下面展示的那般:
def
incr_and_check_limit(user_id, limit):
key =
'{user_id}:{epoch}'
.format(user_id, int(time() /
60
))
pipe = redis.pipeline()
pipe.incr(key)
pipe.expire(key,
60
)
current_rate,
_
= pipe.execute()
return
int(current_rate) > limit
我們所闡明的限制速率的方法是 Redis在快取服務上最基本的功能之一:增加空的鍵字。在快取服務中實現同樣的行為可能最終使用這種方法:
def
incr_and_check_limit_memcache(user_id, limit):
key =
'{user_id}:{epoch}'
.format(user_id, int(time() /
60
))
if
cache.add(key,
0
,
60
):
return
False
current_rate = cache.incr(key)
return
current_rate > limit
事實上我們最終採取這種策略可以使哨兵追蹤不同事件的短期資料。在這種情況下,我們通常對使用者資料進行排序以便可以在最短的時間內找到最活躍使用者的資料。
基本鎖
雖然Redis的是可用性不高,我們的用例鎖,使其成為工作的好工具。我們沒有使用這些在哨兵的核心了,但一個樣本用例是,我們希望盡量減少並發性和簡單無操作的操作,如果事情似乎是已經在運行。這對於可能需要執行每隔一段時間類似cron任務非常有用,但不具備較強的協調。
在Redis的這樣使用SETNX操作是相當簡單的:
from
contextlib
import
contextmanagerr
=
Redis()@contextmanagerdef lock(key, nowait
=
True
):
while
not
r.setnx(key,
'1'
):
if
nowait:
raise
Locked(
'try again soon!'
)
sleep(
0.01
)
# limit lock time to 10 seconds
r.expire(key,
10
)
# do something crazy
yield
# explicitly unlock
r.delete(key)
而鎖()內的哨兵利用的memcached的,但絕對沒有理由我們不能在其切換到Redis。
時間序列資料
近來我們創造一個新的機制在Sentry(包含在sentry.tsdb中)儲存時間序列資料。這是受RRD模型啟發,特別是Graphite。我們期望一個快速簡單的方式儲存短期(比如一個月)時間序列數,以便於處理高速寫入資料,特別是在極端情況下計算潛在的短期速率。儘管這是第一個模型,我們依舊期望在Redis儲存資料��它也是使用計數器的簡單範例。
在目前的模型中,我們使用單一的hash map來儲存全部時間序列資料。例如,這意味所有資料項目在都將同一個雜湊鍵擁有一個資料類型和1秒的生命週期。如下所示:
{
"<type enum>:<epoch>:<shard number>"
: {
"<id>"
: <count>
}}
因此在這種狀況,我們需要追蹤事件的數目。事件類型映射到枚舉類型"1".該判斷的時間是1s,因此我們的處理時間需要以秒計。散列最終看起來是這樣的:
{
"1:1399958363:0"
: {
"1"
: 53,
"2"
: 72,
}}
一個可修改模型可能僅使用簡單的鍵並且僅在儲存區上增加一些增量寄存器。
"1:1399958363:0:1": 53
我們選擇雜湊映射模型基於以下兩個原因:
此外,離散的數字鍵允許我們在將虛擬離散索引值映射到固定數目的索引值上,並在此分配單一儲存區(我們可以使用64,映射到32個物理結點上)
現在通過使用Nydus和它的map()(依賴於一個工作區)(),資料查詢已經完成。這次操作的代碼是相當健壯的,但幸好它並不龐大。
def
get_range(
self
, model, keys, start, end, rollup
=
None
):
""" To get a range of data for group ID=[1, 2, 3]: Start and end are both inclusive. >>> now = timezone.now() >>> get_keys(tsdb.models.group, [1, 2, 3], >>> start=now - timedelta(days=1), >>> end=now) """
normalize_to_epoch
=
self
.normalize_to_epoch
normalize_to_rollup
=
self
.normalize_to_rollup
make_key
=
self
.make_key
if
rollup
is
None
:
rollup
=
self
.get_optimal_rollup(start, end)
results
=
[]
timestamp
=
end
with
self
.conn.
map
() as conn:
while
timestamp >
=
start:
real_epoch
=
normalize_to_epoch(timestamp, rollup)
norm_epoch
=
normalize_to_rollup(timestamp, rollup)
for
key
in
keys:
model_key
=
self
.get_model_key(key)
hash_key
=
make_key(model, norm_epoch, model_key)
results.append((real_epoch, key, conn.hget(hash_key, model_key)))
timestamp
=
timestamp
-
timedelta(seconds
=
rollup)
results_by_key
=
defaultdict(
dict
)
for
epoch, key, count
in
results:
results_by_key[key][epoch]
=
int
(count
or
0
)
for
key, points
in
results_by_key.iteritems():
results_by_key[key]
=
sorted
(points.items())
return
dict
(results_by_key)
歸結如下:
簡單的選擇
我是一個喜歡用簡單的方案解決問題的人,在這個範疇裡使用Redis無疑是很適合的。它的文檔是那樣讓人驚訝,那是因為(閱讀)其文檔的門檻非常的低。雖然他也有折衷(主要是如果你使用持久化),但是他們工作地很好並且比較直觀。
那麼Redis為您解決什麼問題呢?
Ubuntu 14.04下Redis安裝及簡單測試
Redis叢集明細文檔
Ubuntu 12.10下安裝Redis(圖文詳解)+ Jedis串連Redis
Redis系列-安裝部署維護篇
CentOS 6.3安裝Redis
Redis安裝部署學習筆記
Redis設定檔redis.conf 詳解
Redis 的詳細介紹:請點這裡
Redis 的:請點這裡
本文永久更新連結地址: