還認爲Redis只能作緩存嗎?太OUT了吧!

 前言

大多數數據庫,因爲常常和磁盤打交道,在高併發場景下,響應會很是的慢。爲了解決這種速度差別,大多數系統都習慣性的加入一個緩存層,來加速數據的讀取。redis因爲它優秀的處理能力和豐富的數據結構,已經成爲了事實上的分佈式緩存標準。java

可是,若是你覺得redis只能作緩存的話,那就過小看它了。mysql

redis豐富的數據結構,使得它的業務使用場景很是普遍,加上rdb的持久化特性,它甚至可以被看成落地的數據庫使用。在這種狀況下,redis可以撐起大多數互聯網公司,尤爲是社交、遊戲、直播類公司的半壁江山。redis

Redis可以勝任存儲工做

redis提供了很是豐富的集羣模式:主從哨兵cluster,知足服務高可用的需求。同時,redis提供了兩種持久化方式:aofrdb,經常使用的是rdb。sql

經過bgsave指令,主進程會fork出新的進程,回寫磁盤。bgsave至關於作了一個快照,因爲它並無WAL日誌和checkpoint機制,是沒法作到實時備份的。若是機器忽然斷電,那就很容易丟失數據。數據庫

幸運的是,redis是內存型的數據庫,主叢同步的速度是很是快的。若是你的集羣維護的好,內存分配的合理,那麼除非機房斷電,不然redis的SLA,會一直保持在很是高的水平。編程

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

聽起來不是絕對可靠啊,有丟失數據的可能!這在通常CRUD的業務中,是沒法忍受的。但爲何redis可以知足大多數互聯網公司的需求?這也是由業務屬性所決定的。api

在決定最大限度擁抱redis以前,你須要確認你的業務是否有如下特色:數組

除了核心業務,是否大多數業務對於數據的可靠性要求較低,丟失一兩條數據是能夠忍受的?緩存

  1. 面對的是C端用戶,可根據用戶ID快速定位到一類數據,數據集合廣泛較小?無大量範圍查詢需求?
  2. 是否能忍受內存型數據的成本需求?
  3. 是否業務幾乎不須要事務操做?

很幸運的是,這類業務需求特別的多。好比常見的社交,遊戲、直播、運營類業務,都是能夠徹底依賴Redis的。安全

Reids應用場景

Redis具備鬆散的文檔結構,豐富的數據類型,可以適應變幻無窮的scheme變動需求,接下來我將介紹Redis除緩存外的大量的應用場景。

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

1. 基本用戶數據存儲

在傳統的數據庫設計中,用戶表是很是難以設計的,變動的時候會傷筋動骨。使用Redis的hash結構,能夠實現鬆散的數據模型設計。某些不固定,驗證型的功能屬性,能夠以JSON接口直接存儲在hash的value中。使用hash結構,能夠採用HGET和HMGET等指令,只獲取本身所須要的數據,在使用上也是很是便捷的。

>HSET user:199929 sex m
>HSET user:199929 age 22
>HGETALL user:199929
1) "sex"
2) "m"
3) "age"
4) "22"

這種非統計型的、讀多寫少的場景,是很是適合使用KV結構進行存儲的。Redis的hash結構提供了很是豐富的指令,某個屬性也可使用HINCRBY進行遞增遞減,很是的方便。

2. 實現計數器

上面稍微提了一下HINCRBY指令,而對於Redis的Key自己來講,也有INCRBY指令,實現某個值的遞增遞減。

好比如下場景:統計某個帖子的點贊數;存放某個話題的關注數;存放某個標籤的粉絲數;存儲一個大致的評論數;某個帖子熱度;紅點消息數;點贊、喜歡、收藏數等。

> INCRBY feed:e3kk38j4kl:like 1
> INCRBY feed:e3kk38j4kl:like 1
> GET feed:e3kk38j4kl:like
"2"

像微博這樣容易出現熱點的業務,傳統的數據庫,確定是撐不住的,就要藉助於內存數據庫。因爲Redis的速度很是快,就不用再採用傳統DB很是慢的count操做,全部這種遞增操做都是毫秒級別的,並且效果都是實時的。

3. 排行榜

排行榜能提升參與者的積極性,因此這項業務很是常見,它本質上是一個topn的問題。

Redis中有一個叫作zset的數據結構,使用跳錶實現的有序列表,能夠很容易實現排行榜一類的問題。當存入zset中的數據,達到千萬甚至是億的級別,依然可以保持很是高的併發讀寫,且擁有很是棒的平均響應時間(5ms之內)。

使用zadd 能夠添加新的記錄,咱們會使用排行相關的分數,做爲記錄的score值,而後使用zrevrange指令便可獲取實時的排行榜數據,而zrevrank則能夠很是容易的獲取用戶的實時排名。

>ZADD sorted:xjjdog:2021-07  55 dog0
>ZADD sorted:xjjdog:2021-07  89 dog1
>ZADD sorted:xjjdog:2021-07  32 dog2
>ZCARD sorted:xjjdog:2021-07
>3
> ZREVRANGE  sorted:xjjdog:2021-07  0 -10 WITHSCORES # top10排行榜
1) "dog1"
2) "89"
3) "dog0"
4) "55"
5) "dog2"
6) "32"

4. 好友關係

set結構,是一個沒有重複數據的集合,你能夠將某個用戶的關注列表、粉絲列表、雙向關注列表、黑名單、點贊列表等,使用獨立的zset進行存儲。

使用ZADDZRANK等,將用戶的黑名單使用ZADD添加,ZRANK使用返回的sorce值判斷是否存在黑名單中。使用sinter指令,能夠獲取A和B的共同好友。

除了好友關係,有着明確黑名單、白名單業務場景的數據,均可以使用set結構進行存儲。這種業務場景還有不少,好比某個用戶上傳的通信錄,計算通信錄的好友關係等等。

在實際使用中,使用zset存儲這類關係的更多一些。zset同set同樣,都不容許有重複值,但zset多了一個score字段,咱們能夠存儲一個時間戳,用來標明關係創建所發生的時間,有更明確的業務含義。

5. 統計活躍用戶數

相似統計天天的活躍用戶、用戶簽到、用戶在線狀態,這種零散的需求,實在是太多了。若是爲每個用戶存儲一個bool變量,那佔用的空間就太多了。這種狀況下,咱們可使用bitmap結構,來節省大量的存儲空間。

>SETBIT online:2021-07-23 3876520333 1
>SETBIT online:2021-07-24 3876520333 1
>GETBIT online:2021-07-23 3876520333
1
>BITOP AND active online:2021-07-23 online:2021-07-24
>GETBIT active 3876520333
1
>DEBUG OBJECT online:2021-07-23
Value at:0x7fdfde438bf0 refcount:1 encoding:raw serializedlength:5506446 lru:16410558 lru_seconds_idle:5
(0.96s)

注意,若是你的id很大,你須要先進行一次預處理,不然它會佔用很是多的內存。

bitmap包含一串連續的2進制數字,使用1bit來表示真假問題。在bitmap上,可使用and、or、xor等位操做(bitop)。

6. 分佈式鎖

Redis的分佈式鎖,是一種輕量級的解決方案。雖然它的可靠性比不上Zookeeper之類的系統,但Redis分佈式鎖有着極高的吞吐量。

一個最簡陋的加鎖動做,可使用redis帶nx和px參數的set指令去完成。下面是一小段簡單的分佈式樣例代碼。

public String lock(String key, int timeOutSecond) {
    for (; ; ) {
        String stamp = String.valueOf(System.nanoTime());
        boolean exist = redisTemplate.opsForValue().setIfAbsent(key, stamp, timeOutSecond, TimeUnit.SECONDS);
        if (exist) {
            return stamp;
        }
    }
}
public void unlock(String key, String stamp) {
    redisTemplate.execute(script, Arrays.asList(key), stamp);
}

刪除操做的lua爲。

local stamp = ARGV[1]
local key = KEYS[1]
local current = redis.call("GET",key)
if stamp == current then
    redis.call("DEL",key)
    return "OK"
end

redisson的RedLock,是使用最廣泛的分佈式鎖解決方案,有讀寫鎖的差異,並處理了多redis實例狀況下的異常問題。

7. 分佈式限流

使用計數器去實現簡單的限流,在Redis中是很是方便的,只須要使用incr配合expire指令便可。

incr key
 expire key 1

這種簡單的實現,一般來講不會有問題,但在流量比較大的狀況下,在時間跨度上會有流量忽然飆升的風險。根本緣由,就是這種時間切分方式太固定了,沒有相似滑動窗口這種平滑的過分方案。

一樣是redisson的RRateLimiter,實現了與guava中相似的分佈式限流工具類,使用很是便捷。下面是一個簡短的例子:

RRateLimiter limiter = redisson.getRateLimiter("xjjdogLimiter");
 // 只須要初始化一次
 // 每2秒鐘5個許可
 limiter.trySetRate(RateType.OVERALL, 5, 2, RateIntervalUnit.SECONDS);
 
 // 沒有可用的許可,將一直阻塞    
 limiter.acquire(3);

8. 消息隊列

redis能夠實現簡單的隊列。在生產者端,使用LPUSH加入到某個列表中;在消費端,不斷的使用RPOP指令取出這些數據,或者使用阻塞的BRPOP指令獲取數據,適合小規模的搶購需求。

Redis還有PUB/SUB模式,不過pubsub更適合作消息廣播之類的業務。

在Redis5.0中,增長了stream類型的數據結構。它比較相似於Kafka,有主題和消費組的概念,能夠實現多播以及持久化,已經能知足大多數業務需求了。

9. LBS應用

早早在Redis3.2版本,就推出了GEO功能。經過GEOADD指令追加lat、lng經緯數據,能夠實現座標之間的距離計算、包含關係計算、附近的人等功能。

關於GEO功能,最強大的開源方案是基於PostgreSQL的PostGIS,但對於通常規模的GEO服務,redis已經足夠用了。

10. 更多擴展應用場景

要看redis能幹什麼,就不得不提如下java的客戶端類庫redisson。redisson包含豐富的分佈式數據結構,所有是基於redis進行設計的。

redisson提供了好比Set、 SetMultimap、 ScoredSortedSet、 SortedSet, Map、 ConcurrentMap、 List、 ListMultimap、 Queue、BlockingQueue等很是多的數據結構,使得基於redis的編程更加的方便。

對於某個語言來講,基本的數組、鏈表、集合等api,配合起來可以完成大部分業務的開發。Redis也不例外,它擁有這些基本的api操做能力,一樣可以組合成分佈式的、線程安全的高併發應用。

因爲Redis是基於內存的,因此它的速度很是快,咱們也會把它看成一箇中間數據的存儲地去使用。好比一些公用的配置,放到redis中進行分享,它就充當了一個配置中心的做用;好比把JWT的令牌存放到Redis中,就能夠突破JWT的一些限制,作到安全登出。

 

一站式Redis面臨的挑戰

redis的數據結構豐富,通常不會在功能性上形成困擾。但隨着請求量的增長,SLA要求的提升,咱們勢必會對Redis進行一些改造和定製性開發。

1. 高可用挑戰

redis提供了主從、哨兵、cluster等三種集羣模式,其中cluster模式爲目前大多數公司所採用的方式。

可是,redis的cluster模式,有很多的硬傷。redis cluster採用虛擬槽的概念,把全部的key映射到 0~16383個整數槽內,屬於無中心化的架構。但它的維護成本較高,slave也不可以參與讀取操做。

它的主要問題,在於一些批量操做的限制。因爲key被hash到多臺機器上,因此mget、hmset、sunion等操做就很是的不友好,常常發生性能問題。

redis的主從模式是最簡單的模式,但沒法作到自動failover,一般在主從切換後,還須要修改業務代碼,這是不能忍受的。即便加上haproxy這樣的負載均衡組件,複雜性也是很是高的。

哨兵模式在主從數量比較多的時候,可以顯著的體現它的價值。一個哨兵集羣,可以監控成百上千個集羣,可是哨兵集羣自己的維護是比較困難的。幸運的是,redis的文本協議很是簡單,在netty中,甚至直接提供了redis的codec。自研一套哨兵系統,增強它的功能,是可行的。

2. 冷熱數據分離

redis的特色是,無論什麼數據,都一股腦地搞到內存裏作計算,這對於有時間序列概念,有冷熱數據之分的業務,形成了很是大的成本考驗。爲何大多數開發者喜歡把數據存放在MySQL中,而不是Redis中?除了事務性要求之外,很大緣由是歷史數據的問題。

一般,這種冷熱數據的切換,是由中間件完成的。咱們上面也談到了,Redis是一個文本協議,很是簡單。作一箇中間件,或者作一個協議兼容的Redis模擬存儲,是比較容易的。

好比咱們Redis中,只保留最近一年的活躍用戶。一個好幾年不活躍的用戶,忽然間訪問了系統,這時候咱們獲取數據的時候,就須要中間件進行轉換,從容量更大,速度更慢的存儲中查找。

這個時候,Redis的做用,更像是一個熱庫,更像是一個傳統cache層作的事情,發生在業務已經上規模的時候。可是注意,直到此時,咱們的業務層代碼,一直都是操做的redis的api。它們使用這衆多的函數指令,並不關心數據究竟是真正存儲在redis中,仍是在ssdb中。

3. 功能性需求

redis還能玩不少花樣。舉個例子,全文搜索。不少人都會首選es,但redis生態就提供了一個模塊:RediSearch,能夠作查詢,能夠作filter。

但咱們一般還會有更多的需求,好比統計類、搜索類、運營效果分析等。這類需求與大數據相關,即便是傳統的DB也不能勝任。這時候,咱們固然要把redis中的數據,導入到其餘平臺進行計算啦。

若是你選擇的是redis數據庫,那麼dba打交道的,就是rdb,而不是binlog。有不少的rdb解析工具(好比redis-rdb-tools),可以按期把rdb解析成記錄,導入到hadoop等其餘平臺。

此時,rdb成爲全部團隊的中樞,成爲基本的數據交換格式。導入到其餘db後的業務,該怎麼玩怎麼玩,徹底不會由於業務系統選用了redis就沒法運轉。

 

總結

大多數業務系統,跑在redis上,這是不少一直使用MySQL作業務系統的同窗所不能想象的。看完了上面的介紹,相信你可以對redis可以實現的存儲功能有個大致的瞭解。打開你的社交app、遊戲app、視頻app,看一下它們的功能,可以涵蓋多少呢?

我這裏要強調的是,某些數據,並非必定要落地到RDBMS纔算安全,它們並非一個強需求。

那既然redis這麼厲害,爲何還要有mysql、tidb這樣的存儲呢?關鍵還在於業務屬性上。

若是一個業務系統,每次交互的數據,都是一個很是大的結果集,並涉及到很是複雜的統計、過濾工做,那麼RDBMS是必須的;但若是一個系統,可以經過某個標識,快速定位到一類數據,這一類數據在能夠預見的將來,是有限的,那就很是適合Redis存儲

一個電商系統,選用redis作存儲就是做死,但一個社交系統就快活的多。在合適的場景選用合適的工具,纔是咱們應該作的。

可是一個系統,可否在產品驗證期,就能快速的響應變化,快速開發上線,纔是成功的關鍵。這也是使用redis作數據庫,所可以帶來的最大好處。千萬別被那幾率極低的丟數據場景,給嚇怕了。比起產品成功,你的系統即便是牢如鋼鐵,也一文不值。

相關文章
相關標籤/搜索