架構秘笈:移花接木,使用MySQL模擬Redis

2021-01-10 51CTO

這年頭,你看到的東西未必就是你認為的東西。一個mysql協議的後面,可能是tidb;一個linux機器後面,可能是一個精簡的docker;你覺得xjjdog是個女的,但可能ta自己也不太清楚;而當你大呼php萬歲的時候,可能是研發人員和你開個玩笑,重寫了後綴,而後端用的卻是java。

大家都知道redis速度快,但它的容量和內存容量有關,很容易達到瓶頸。有些網際網路公司,直接使用redis作為後端資料庫(在下佩服)。當業務量暴增,就面臨一個redis容量和價格的權衡問題。改業務代碼是來不及了,只好用一些持久化存儲 ,來模擬redis的一些數據結構。

redis支持近十種數據類型,最常用的有5種。string、hash、zset、set、list等。本文將針對幾種常見的數據結構,探討一下常用操作的模擬實現。

其實,我們所需要開發的,就是一個redis代理proxy。redis的客戶端,連接上我們的代理之後,會進行協議解析。解析出來的命令,將會被模擬,然後根據配置的路由,定位到相應的mysql中。

也就是你所使用的redis,其實使用mysql來存儲數據的。沒有rdb,也沒有aof。

Redis是文本協議

redis是文本協議,協議名稱叫做RESP。RESP 是 Redis 序列化協議的簡寫。它是一種直觀的文本協議,優勢在於實現異常簡單,解析性能極好。

如圖,Redis 協議將傳輸的結構數據,可以總結為 5 種最小單元類型。每個單元結束時,統一加上回車換行符號\r\n 。

下面是幾個規則:

單行字符串 以 + 開頭; 多行字符串 以 $ 開頭,後跟字符串長度; 整數值 以 : 開頭,後跟整數的字符串形式; 錯誤消息 以 - 符號開頭; 數組 以 * 號開頭,後跟數組的長度; 

比如,下面這個就是數組[9,9,6]的報文。

*3\r\n:9\r\n:9\r\n:6\r\n 

所以這個協議的解析和拼裝,是非常簡單的。拿netty來說,就有codec-redis 模塊供我們使用。

實現:數據結構設計

在數據表的設計上,我們發現,kv和hash在效率上沒有什麼差別,因為它能夠直接根據key定位到。

反倒是zset,由於有排序的功能,造成了很多操作的執行效率都不盡人意。

另外,由於我們不同的數據結構,是使用不同的表進行存儲的。所以刪除操作,要在每張表上都執行一遍。

kv設計

kv,即string,是redis裡最基本的數據類型。一個key對應一個value,string類型的值最大能存儲512MB。

設計專用的資料庫表rstore_kv,其中,rkey是主鍵。

rkey        varchar val     varchar lastTime    bigint 

set操作

insert into rstore_kv("rkey","val","lastTime") values($1,$2,$3) on duplicate key update set "val"=$2,"lastTime"=$3 

get操作

select val from rstore_kv where "rkey" = $1 

del操作

delete from rstore_kv where "rkey" = $1 

exists操作

select count(*) as n from rstore_kv where  "rkey" = $1 

ttl操作

select lastTIme from rstore_kv  where  "rkey" = $1 

hash設計

hash 是一個鍵值(key=>value)對集合。hash 特別適合用於存儲對象。

設計專用的資料庫表rstore_hash,其中,rkey和hkey是聯合主鍵。

rkey        varchar hkey        varchar val     varchar lastTime    bigint 

hset操作

insert into rstore_hash("rkey","hkey","val","lastTime") values($1,$2,$3,$4) on duplicate key update set "val"=$3,"lastTime"=$4 

hget操作

select val from rstore_hash where "rkey" = $1 and "hkey" = $2 

hgetall操作

select hkey,val from rstore_hash where "rkey" = $1 

hdel操作

delete from rstore_hash where "rkey" = $1 and "hkey" = $2 

del操作

delete from rstore_hash where "rkey" = $1 

hlen,hexists操作

select count(*) as num from rstore_hash where "rkey" = $1 

ttl操作

select max(lastTIme) from rstore_hash  where  "rkey" = $1 

zset設計

Redis zset 和 set 一樣也是string類型元素的集合,且不允許重複的成員。不同的是每個元素都會關聯一個double類型的分數。redis正是通過分數來為集合中的成員進行從小到大的排序。它的底層結構是跳躍表,效率特別高,但是會佔用大量內存。

設計專用的資料庫表rstore_zset,其中,rkey和member是聯合主鍵。

rkey        varchar member        varchar score     double lastTime    bigint 

zadd操作

insert into rstore_zset("rkey","member","score","lastTime") values($1,$2,$3,$4) on duplicate key update update set "score"=$3,"lastTime"=$4 

zscore操作

select score from rstore_zset where "rkey" = $1 and "member" = $2 

zrem操作

delete from rstore_zset where "rkey" = $1 and "member" = $2" 

zcard,exists操作

select count(*) as num from rstore_zset where "rkey" = $1 

zcount操作

select count(*) as num from rstore_zset where "rkey" = $1 and score>=$2 and score<=$3 

zremrangebyscore操作

delete from rstore_zset where "rkey" = $1 and score>=$2 and score<=$3 

zrangebyscore操作

select member,score from rstore_zset where "rkey" = $1 and score>=$2 and score<=$3 order by score asc,member asc 

zrange操作

select member,score from rstore_zset where "rkey" = $1 order by score asc offset $2 limit $3 

zrank操作

select rank from (select member,rank() over (order by "score" asc, "lastTime" asc) as rank from rstore_zset where "rkey" = $1 ) m where m."member"= $2; 

ttl操作

select max(lastTIme) from rstore_zset  where  "rkey" = $1 

del操作

delete from rstore_zset where "rkey" = $1 

set設計

rkey        varchar member        varchar lastTime    bigint 

sadd操作

insert into rstore_set("rkey","member","lastTime") values($1,$2,$3) on duplicate key update update set "lastTime"=$3 

scard操作

select count(*) as num from rstore_set where "rkey" = $1 

sismember操作

select member from rstore_set where "rkey" = $1 and "member" = $2 

smembers操作

select member from rstore_set where "rkey" = $1 

srem操作

delete from rstore_set where "rkey" = $1 and "member" = $2 

del操作

delete from rstore_set where "rkey" = $1 

ttl操作

select max(lastTIme) from rstore_set  where  "rkey" = $1 

End

本篇文章僅僅模擬了常用數據結構的常用功能,有很多很多功能是不支持的,比較明顯的就是分布式鎖setnx等。所以這個proxy層的開發,要想做到ok,並不是那麼簡單。

同時,我們以一種模擬的視角,來看一下redis的數據結構,在關係型資料庫中的表現形式。這樣,更能夠加深我們對redis的認識,明白它存在的價值。

【編輯推薦】

【責任編輯:

武曉燕

TEL:(010)68476606】

點讚 0

相關焦點

  • redis集群架構的演進之路
    一、redis的主從架構一開始我們的業務量不大時,一個redis節點就能滿足我們的業務需求,當我們的業務量不斷上漲,單臺redis節點已經不能滿足我們的業務需求時,這個時候redis的主從結構就出現了。
  • 高性能Mysql主從架構的複製原理及配置詳解
    (2)熱拷貝(warm copy)如果你僅使用MyISAM表,你可以使用mysqlhotcopy拷貝,即使伺服器正在運行。(3)使用mysqldump<1>鎖表:如果你還沒有鎖表,你應該對表加鎖,防止其它連接修改資料庫,否則,你得到的數據可以是不一致的。
  • 分布式鎖解決方案-Redis
    ## 為什麼要學習分布式鎖解決方案為了解決分布式架構帶來的數據準確性問題!我們用synchronized或者 ReentrantLock(瑞恩吹特) 能解決問題嗎?真實生產環境我們採用集群的方式去訪問秒殺商品(nginx為我們做了負載均衡)。
  • Spring Redis中使用Lua腳本實現高並發原子操作
    今天就結合Spring Data Redis這個我們經常使用的 Redis 開發組件來實際嘗試一下 Lua 腳本。2. Lua 實現抽獎模擬一個抽獎場景,從獎池中進行隨機抽獎。規則如下:規則有了,我們先來分析如何使用 Redis 實現。
  • 搭建MySQL主從集群,主從複製過程中同步延遲問題
    上一節我們成功搭建了主從複製、讀寫分離,實際上並發量和數據量不大的情況下,使用起來也是非常的流暢,無任何問題,可以正常運行了。有的網友就說我們可以升級主伺服器的配置來解決,我說可以解決暫時的,一臺伺服器再怎麼升級也有極限,如果使用多臺伺服器並且可以擴容的話,我們不是很好處理這個問題嗎?好了,我們這一節正要講解同步延遲問題,解決掉數據同步延遲問題。
  • MySQL的主從備份
    如何解決單點問題增加額外的資料庫伺服器,組建資料庫集群同一集群中的資料庫伺服器需要具有相同的數據集群中的任一伺服器宕機後,其他伺服器可以取代宕機伺服器MySQL主從複製架構/sql_log/mysql-binserver_id= 100 ##建議用ip的最後一段從伺服器配置log_bin = /data/mysql/sql_log/mysql-binserver-id = 101relay_log = /data/mysql/sql_log/relay-binread_only = onsuper_read_only = onskip_slave_sart
  • 京東面試:說說MySQL的架構體系
    字數:3620,閱讀耗時:4分35秒最近群裡一位兄弟在面試中被問到:「MySQL的架構體系是什麼」。雖然他搞java開發好幾年了,也一直使用的是MySQL資料庫,但是面對這個問題依然是一臉懵逼,還以為面試官要問索引、慢查詢、性能優化之類的(因為這些都是網上找點面試題背過了)。
  • 看完這篇別再說你不懂單機redis了
    4、Redis架構模式Redis介紹與MySQL不同的是,redis是一種非關係型資料庫。redis之所以分這麼多個資料庫,也是為了區分業務,不同的業務存放在不同的庫,但是一個redis,一般是給一個項目用,項目內的不同業務,單獨用一個庫,這樣不會相互有數據交叉。現在很多微服務項目,一個項目裡有多個微服務,redis統一由團隊管理,每個服務連接自己的庫就可以了。
  • Redis-Window Server快速搭建Redis Cluster集群
    按照上圖配置進行ruby安裝Redis 安裝配置Redis Cluster 搭建3(master)+3(slave)的演示環境,這裡使用了github.com/microsoftarchive/redis/releases選擇redis 3.0+版本配置redis在redis安裝目錄中找到redis.windows.conf,修改配置文件中配置,修改選項如下:cluster-enabled yes
  • MySQL 資料庫的哈希表-愛可生
    >"db2"],"oracle","mongodb","sql server","redis""memcached",mysql> set @a='{"10":"mysql","20":"db2","30":"oracle","40":"mongodb"}';Query OK, 0 rows affected (0.00 sec)mysql> select json_keys(@a);
  • 騰訊開源分布式存儲系統 Tendis,可完全兼容 Redis
    完全兼容 redis 協議,支持 redis 主要數據結構和接口,兼容大部分原生Redis命令。 持久化存儲。使用 rocksdb 作為存儲引擎,所有數據以特定格式存儲在 rocksdb 中,最大支持 PB 級存儲。 去中心化架構。
  • Redis是如何實現點讚、取消點讚的?
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫redis相關文章:分布式系統關注點——先寫DB還是「緩存」?一個牛逼的多級緩存實現方案基於redis分布式鎖實現「秒殺」(含代碼)百億數據量下,掌握這些Redis技巧你就能Hold全場9個提升逼格的redis命令
  • 每天5分鐘|輕鬆掌握開發工作中必會的k8s-基礎組件與架構
    簡單來講,就是選擇其中一臺作為master節點,另外兩臺作為worker節點,分別在這3個節點上安裝k8s對應的組件,然後就可以使用k8s來管理節點上的應用容器。cAdvisor:cAdvisor是用來分析資源使用率和性能的工具,kubelet通過 cAdvisor 獲取其所在 Node 及容器(Docker)的數據,包含CPU、內存、文件系統和網絡使用等統計信息。
  • 資料庫基礎:mysql主從集群搭建
    本文轉載自【微信公眾號:java架構師進階之路,ID:gh_a39b0d322dde】經微信公眾號授權轉載,如需轉載與原文作者聯繫前言:Mysql資料庫沒有增量備份的機制還好mysql資料庫提供了一種主從備份的機制,其實就是把主資料庫的所有的數據同時寫到備份的資料庫中。實現mysql資料庫的熱備份。 要想實現雙機的熱備,首先要了解主從資料庫伺服器的版本的需求。要實現熱備mysql的版本都高於3.2。還有一個基本的原則就是作為從資料庫的數據版本可以高於主伺服器資料庫的版本,但是不可以低於主伺服器的資料庫版本。
  • MySQL5.7中如何使用開窗函數
    但是我們目前還有很多人在使用5.7版本。那麼在5.7版本中,如何實現開窗函數的功能呢?`emp`(`id`,`emp_name`,`dept_no`,`emp_salary`,`emp_hire_date`)VALUES(1,'張三','0001',5000,'2017-01-11');INSERTINTO`mysql_db`.
  • Redis緩存與NodeJS的初學教程
    wget https://download.redis.io/releases/redis-6.0.9.tar.gztar xzf redis-6.0.9.tar.gzcd redis-6.0.9make
  • MySQL 數據校驗工具-愛可生|mysql|perl|伺服器|node01_網易訂閱
    您可能應該使用 --max-load 選項為伺服器設置一個合理的值。  校驗和通常是一個低優先級的任務,應該讓位給伺服器上的其他?作。然而,一個必須經常重啟的共工具是很難使用的。因此,pt 表校驗和對錯誤具有很強的彈性。例如,如果資料庫管理員出於任何原因需要殺死 pt-table-checksum 的查詢,這就不是一個致命錯誤。?
  • 輕鬆搭建MySQL主從複製、讀寫分離(雙機熱備)
    因此,我們利用mysql自帶的REPLICATION來實現mysql多機熱備的功能,mysql版本為5.7進行演示。讀寫分離:就是把對資料庫的讀操作和寫操作分離開,將讀寫壓力分擔到多臺伺服器上,通常用於讀遠大於寫的場景。