Go操作Redis

men 發表於 2020-07-31

簡介

Remote Dictionary Server, 翻譯為遠端字典服務, Redis是一個完全開源的基於Key-Value的NoSQL儲存系統,他是一個使用ANSIC語言編寫的,遵守BSD協議,支援網路、可基於記憶體的可持久化的日誌型、Key-Value資料庫,並提供多種語言的API.

它通常被稱為資料結構伺服器,因為值(value)可以是 字串(String), 雜湊(Hash), 列表(list), 集合(sets) 和 有序集合(sorted sets)等型別。

# Redis架構主要有兩個程式:
# 	Redis客戶端  redis-cli
# 	Redis伺服器  redis-server
起源

Redis作者是Salvatore Sanfilippo,來自義大利的西西里島.

2008年他在開發一個LLOOGG的網站時,需要一個高效能的佇列功能,最開始使用MySQL來實現,無奈效能總是提不上去,所以決定自己實現一個專屬於LLOOGG的資料庫,他就是Redis的前身。後臺 Sanfilippoj將 Redis1.0放到Github上大受歡迎。

BSD協議

Redis基於BSD開源協議,BSD開源協議是一個給於使用者很大自由的協議。可以自由的使用,修改原始碼,也可以將修改後的程式碼作為開源或者專有軟體再發布。當你釋出使用了BSD協議的程式碼,或者以BSD協議程式碼為基礎做二次開發自己的產品時,需要滿足三個條件:

  • 如果再發布的產品中包含原始碼,則在原始碼中必須帶有原來程式碼中的BSD協議。
  • 如果再發布的只是二進位制類庫/軟體,則需要在類庫/軟體的文件和版權宣告中包含原來程式碼中的BSD協議。
  • 不可以用開原始碼的作者/機構名字和原來產品的名字做市場推廣。

BSD程式碼鼓勵程式碼共享,但需要尊重程式碼作者的著作權。BSD由於允許使用者修改重新發布程式碼,也允許使用或在BSD程式碼上開發商業軟體釋出和銷售,因此是對商業整合很友好的協議。

很多的公司企業在選用開源產品的時候都首選BSD協議,因為可以完全控制這些第三方的程式碼,在必要的時候可以修改或者 二次開發。

Redis原理

命令執行結構

image.png

# 1. 客戶端傳送命令後,Redis伺服器將為這個客戶端連結創造一個'輸入快取',將命令放到裡面
# 2. 再由Redis伺服器進行分配挨個執行,順序是隨機的,這將不會產生併發衝突問題,也就不需要事務了.
# 3. 再將結果返回到客戶端的'輸出快取'中,輸出快取先存到'固定緩衝區',如果存滿了,就放入'動態緩衝區',客戶端再獲得資訊結果

# 如果資料時寫入命令,例如set name:1  zhangsan 方式新增一個字串.
# Redis將根據策略,將這對key:value來用內部編碼格式儲存,好處是改變內部編碼不會對外有影響,正常操作即可,
# 同時不同情況下儲存格式不一樣,發揮優勢.
為什麼需要Redis?

傳統資料庫在儲存資料時,需要先定義schema,以確定型別(位元組寬度),並以行儲存,所以每行所佔的位元組寬度是一致的(便於索引資料)。資料庫內部資料分為多個datapage(一般是4kb)儲存,隨著資料量的增大,資料庫查詢速度會越來越慢,其主要瓶頸在於磁碟I/O:

# 定址時間(ms)級別
# 頻寬(G/M)

由於資料量增大查詢datapage的時間也會變長,所以索引出現了。索引是一個B+T,儲存在記憶體中,根據索引記錄的資訊,可以快速定位到datapage的位置。索引雖然會大大加快查詢速度,但是因為增刪改需要維護索引的B+T,所以會把增刪改的速度拖慢,所以索引不適合頻繁寫的表。

此外,當資料庫高併發查詢的情況下,單位時間內所需的資料量也是很大的,此時可能會受到磁碟頻寬的影響,影響磁碟的查詢速度。

在I/O上,記憶體相比較於磁碟,擁有較好的效能;

# 定址時間(ns級別,磁碟是其10w倍)
# 頻寬(雙通道DDR400的寬頻為6.4GBps)

所以,出現了一批基於記憶體的關係型資料庫,比如SAP HAHA資料庫,其物理機器記憶體2T,包含軟體以及服務,購買需要1億元,由於記憶體關係型資料庫的昂貴价格,所以大部分公司採用了折中的方案,使用磁碟關係型資料庫+記憶體快取,比如 Oracle+Memcached,Mysql+Redis

為什麼使用Redis?

個人覺得專案中使用redis,主要從兩個角度去考慮效能和併發,當然,redis還具備可以做分散式鎖等其他功能,但是如果只是為了分散式鎖這些其他功能,完全還有其他中介軟體(如zookeeper等)代替,並不是非要使用redis,因此,這個問題主要從效能和併發兩個角度去答.

效能

如下圖所示,我摩恩碰到需要執行耗時特別久,且結果不頻繁變動的SQL,就特別適合將執行結果放入快取,這樣,後面的請求就去快取中讀取,使得請求能夠迅速響應.

image.png

迅速響應的標準,根據互動效果的不同,這個響應時間沒有固定標準。不過曾經有人這麼告訴我:"在理想狀態下,我們的頁面跳轉需要在瞬間解決,對於頁內操作則需要在剎那間解決。另外,超過一彈指的耗時操作要有進度提示,並且可以隨時中止或取消,這樣才能給使用者最好的體驗。"

那麼瞬間、剎那、一彈指具體是多少時間呢?

一剎那者為一念,二十念為一瞬,二十瞬為一彈指,二十彈指為一羅預,二十羅預為一須臾,一日一夜有三十須臾。

經過周密的計算,一瞬間為0.36秒,一剎那有0.018秒,一彈指changda7.2秒

併發

如下圖所示,在大併發的情況下,所有的請求直接訪問資料庫,資料庫會出現連線異常,這個時候,就需要使用redis做一個緩衝操作,讓請求先訪問到redis,而不是直接訪問資料庫.

image.png

單執行緒的Redis為什麼這麼快?
# 1. 基於記憶體的訪問,非阻塞I/O,Redis使用事件驅動模型epoll多路複用實現,連線,讀寫,關閉都轉換為事件不在網路I/O上浪費過多的時間.
# 2. 單執行緒避免高併發的時候,多執行緒有鎖的問題和執行緒切換的CPU開銷問題.《雖然是單執行緒,但可以開多例項彌補》
# 3. 使用C語言編寫,更好的發揮伺服器效能,並且程式碼簡介,效能高.
Redis的特點
  • 高效能: Redis將所有資料集儲存在記憶體中,可以在入門級Linux機器中每秒寫(SET)11萬次,讀(GET)8.1萬次。Redis支援Pipelining命令,可一次傳送多條命令來提高吞吐率,減少通訊延遲。
  • 持久化:當所有資料都存在於記憶體中時,可以根據自上次儲存以來經過的時間和/或更新次數,使用靈活的策略將更改非同步儲存在磁碟上。Redis支援僅附加檔案(AOF)持久化模式。
  • 資料結構: Redis支援各種型別的資料結構,例如字串,雜湊,集合,列表,帶有範圍查詢的有序集,點陣圖,超級日誌和帶有半徑查詢的地理空間索引。
  • 原子操作:處理不同資料型別的Redis操作是原子操作,因此可以安全地設定或增加鍵,新增和刪除組中的元素,增加計數器等。
  • 支援的語言: Redis支援許多語言,如ActionScript,C,C ++,C#,Clojure,Common Lisp,D,Dart,Erlang,Go,Haskell,Haxe,Io,Java,JavaScript(Node.js),Julia,Lua ,Objective-C,Perl,PHP,Python,R,Ruby,Rust,Scala,Smalltalk和Tcl。
  • 主/從複製: Redis遵循非常簡單快速的主/從複製。配置檔案中只需要一行來設定它,而Slave在Amazon EC2例項上完成10 MM key集的初始同步需要21秒。
  • 分片: Redis支援分片。與其他鍵值儲存一樣,跨多個Redis例項分發資料集非常容易。
  • 可移植: Redis是用ANSI C編寫的,適用於大多數POSIX系統,如Linux,BSD,Mac OS X,Solaris等。
Redis與其他key-value儲存有什麼不同
  • Redis有著更為複雜的資料結構並且提供對他們的原子性操作,這是一個不同於其他資料庫的進化路徑。Redis的資料型別都是基於基本資料結構的同時對程式設計師透明,無需進行額外的抽象。
  • Redis執行在記憶體中但是可以持久化到磁碟,所以在對不同資料集進行高速讀寫時需要權衡記憶體,因為資料量不能大於硬體記憶體。在記憶體資料庫方面的另一個優點是,相比在磁碟上相同的複雜的資料結構,在記憶體中操作起來非常簡單,這樣Redis可以做很多內部複雜性很強的事情。同時,因RDB和AOF兩種磁碟持久化方式是不適合隨機訪問,所以他們可以是緊湊的以追加的方式生成。

Redis應用場景

快取

合理的使用 快取 能夠明顯加快訪問的速度,同時降低資料來源的壓力。這也是 Redis 最常用的功能。Redis 提供了 鍵值過期時間EXPIRE key seconds)設定,並且也提供了靈活控制 最大記憶體記憶體溢位 後的 淘汰策略

排行榜

每個網站都有自己的排行榜,例如按照 熱度排名 的排行榜,釋出時間 的排行榜,答題排行榜 等等。Redis 提供了 列表list)和 有序集合zset)資料結構,合理的使用這些資料結構,可以很方便的構建各種排行榜系統。

計數器

計數器 在網站應用中非常重要。例如:點贊數1瀏覽數1。還有常用的 限流操作,限制每個使用者每秒 訪問系統的次數 等等。Redis 支援 計數功能INCR key),而且計數的 效能 也非常好,計數的同時也可以設定 超時時間,這樣就可以 實現限流

社交網路

贊/踩,粉絲,共同好友/喜好,推送,下拉重新整理等是社交網站必備的功能。由於社交網站 訪問量通常比較大,而且 傳統的資料庫 不太適合儲存這類資料,Redis 提供的 資料結構 可以相對比較容易實現這些功能。

訊息佇列

Redis 提供的 釋出訂閱PUB/SUB)和 阻塞佇列 的功能,雖然和專業的訊息佇列比,還 不夠強大,但對於一般的訊息佇列功能基本滿足。

Redis五種資料型別應用場景

此時只做介紹,資料型別具體介紹請看後面

  • 對於string 資料型別,常規的set/get操作,因為string 型別是二進位制安全的,可以用來存放圖片,視訊等內容,另外由於Redis的高效能讀寫功能,而string型別的value也可以是數字,一般做一些複雜的計數功能的快取,還可以用作計數器(INCR,DECR),比如分散式環境中統計系統的線上人數,秒殺等。
  • 對於 hash 資料型別,value 存放的是鍵值對結構化後的物件,比較方便操作其中某個欄位,比如可以做單點登入存放使用者資訊,以cookiele作為key,設定30分鐘為快取過期時間,能很好的模擬出類似session的效果.
  • 對於 list 資料型別,可以實現簡單的訊息佇列,另外可以利用lrange命令,做基於redis的分頁功能,效能極佳,使用者體驗好.
  • 對於 set 資料型別,由於底層是字典實現的,查詢元素特別快,另外set 資料型別不允許重複,利用這兩個特性我們可以進行全域性去重,比如在使用者註冊模組,判斷使用者名稱是否註冊;另外就是利用交集、並集、差集等操作,可以計算共同喜好,全部的喜好,自己獨有的喜好等功能。
  • 對於 zset 資料型別,有序的集合,可以做範圍查詢,排行榜應用,取 TOP N 操作等,還可以做延時任務.

Redis事務

事務表示

一組動作,要麼全部執行,要麼全部不執行,例如在社交網站上使用者A關注了
使用者B, 那麼需要在使用者A的關注表中加入使用者B, 並且在使用者B的粉絲表中
新增使用者A, 這兩個行為要麼全部執行, 要麼全部不執行, 否則會出現資料
不一致的情況。

Redis 提供了簡單的事務功能, 將一組需要一起執行的命令放到 multi
exec 兩個命令之間。 multi 命令代表事務開始, exec 命令代表事務結束, 它們
之間的命令是原子順序執行的, 例如下面操作實現了上述使用者關注問題。

之間執行命令將不執行,在緩衝中,等exec後裁真正開始執行

如果其中有語法錯誤,命令打錯了,那整個事務將結束.
如果把值寫錯了,多個字母,但語法正確,那事務是正確的,要手動恢復,不支援回滾.
在事務開始前,用watch key可以檢要操作的key,如果key在事務開始後有變化,例如multi開始修改時,這個key被其他客戶端修改,事務將不進行操作.

一個Redis從開始到執行會經歷以下階段

# 1. 事務開始
# 2. 命令入隊
# 3. 執行事務
Redis事務相關命令
命令 描述
DISCARD 取消事物,放棄執行事物內的所有命令
EXEC 執行所有事物塊內的命令 EXEC{執行事務}
MULTI 標誌一個事務塊的開始 MULTI{啟動一個事務}
UNWATCH 取消WATCH命令多所有key的監視

| WAHCH key [key …] | 監視一個(或多個)key,如果在事務執行之前這個(或這些) key 被其他命令所改動,那麼事務將被打斷。

Redis事務與Mysql事務區別
# 1. Redis不支援回滾,即一條命令當做事務執行時,當有一箇中間的命令發生錯誤,mysql將會把之前的操作取消並結束事務.

# 2. 但是Redis不會,Redis還會繼續把剩下的命令執行下去,忽略發生錯誤的命令.

Redis的過期策略以及記憶體淘汰機制

比如Redis只能存5G資料,可是你寫了10G,那會刪5G的資料。怎麼刪的,這個問題思考過麼?還有,你的資料已經設定了過期時間,但是時間到了,記憶體佔用率還是比較高,有思考過原因麼?

Redis採用的是定期刪除 + 惰性刪除策略

為什麼不用定時刪除策略?

定時刪除,用一個定時器來負責監視key,過期則自動刪除,雖然記憶體及時釋放,但是十分消耗CPU資源,大併發情況下,CPU要將時間應用在處理請求,而不是刪除key,因此沒有采用這一策略.

定期刪除+惰性刪除是如何工作的?

定期刪除,redis預設每個100ms檢查,是否有過期的key,有過期key則刪除。需要說明的是,redis不是每個100ms將所有的key檢查一次,而是隨機抽取進行檢查(如果每隔100ms,全部key進行檢查,redis豈不是卡死)。因此,如果只採用定期刪除策略,會導致很多key到時間沒有刪除。

於是,惰性刪除派上用場,也就是說你獲取某個key的時候,redis會檢查一下,這個key如果設定了過期時間那麼是否過期了?如果過期了此時就會刪除.

採用定期刪除+惰性刪除就沒其他問題了嗎?
並不是,如果定期刪除刪除沒刪除key。然後你也沒即時去請求key,也就是說惰性刪除也沒生效。這樣,redis的記憶體會越來越高。那麼就應該採用記憶體淘汰機制.
在redis.conf中有一行配置

# maxmemory-policy volatile-lru

#該配置就是配記憶體淘汰策略的
# 1. noeviction: 當記憶體不足以容納寫入資料時,新寫入會報錯,應該沒人用.
# 2. allkeys-lru: 當記憶體不足以容納新寫入資料時,在鍵空間中,移除最少使用的那個key.推薦使用
# 3. allkeys-random: 當記憶體不足以容納新寫入資料時,在鍵空間中,隨機移除某個key,應該也沒人用,不刪最少使用,隨機刪?
# 4. volatile-lru: 當記憶體不足以容納寫入資料時,在設定了過期時間的鍵空間中,移除最少使用的key,
# 一般是吧redis既當快取又當持久化儲存才用,不推薦.
# 5. volatile-random: 當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,隨機移除某個key,依然不推薦.
# 6. volatile-ttl: 當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,有更早過期的key優先移除,不推薦.

# 如果沒有設定expire的key,不滿足先決條件(prerequisites);
# 那麼volatile-lru,volatile-random和volatile-ttl策略行為和noeviction(不刪)基本一致

Redis單機部署

此篇文章只做單機伺服器搭建,高可用哨兵和叢集請看下一篇

環境
[Redis-Server]
	主機名 = redis-master-1
	系統 = CentOS7.6.1810
	地址 = 121.36.43.223
	軟體 = redis-4.0.14

# 版本
# Redis的奇數版本為非穩定版本,例如2.7.3.1,如果為偶數則為穩定版本,例如3.2,3.4;
節點名 IP 軟體版本 硬體 網路 說明
redis-master 192.168.171.136 list 裡面都有 2C4G Nat,內網 測試環境
下載解壓Redis原始碼包
yum -y install gcc
wget http://download.redis.io/releases/redis-4.0.14.tar.gz
tar xvf redis-4.0.14.tar.gz -C /opt/
cd /opt/redis-4.0.14
編譯安裝
# Redis的編譯,只將命令檔案編譯,將會在當前目錄生成bin目錄
make && make install  PREFIX=/usr/local/redis
cd ..
mv redis-4.0.14/* /usr/local/redis/

# 建立環境變數
echo 'PATH=$PATH:/usr/local/redis/src/' >> /etc/profile
source /etc/profile

# 此時在任何目錄位置都可以是用redis-server等相關命令
[[email protected] ~]# redis-
redis-benchmark  redis-check-rdb  redis-sentinel   redis-trib.rb    
redis-check-aof  redis-cli        redis-server 
Redis可執行檔案
可執行檔案 作用
redis-server 啟動redis服務
redis-cli redis 命令列客戶端
redis-benchmark Redis基準測試工具
redis-check-aof redis AOF持久化檔案檢測和修復工具
redis-check-dump redis RDB持久化檔案檢測和修復工具
redis-sentinel 啟動redis sentinel
修改配置檔案
# redis程式是否以守護程式的方式執行,yes為是,no為否(不以守護程式的方式執行會佔用一個終端)
daemonize no


# 指定redis程式的PID檔案存放位置
pidfile /var/run/redis.pid


# redis程式的埠號
port 6379


# 繫結的主機地址
bind 127.0.0.1


# 客戶端閒置多長時間後關閉連線,預設此引數為0即關閉此功能
timeout 300


# redis日誌級別,可用的級別有debug.verbose.notice.warning
loglevel verbose


# log檔案輸出位置,如果程式以守護程式的方式執行,此處又將輸出檔案設定為stdout的話,就會將日誌資訊輸出到/dev/null裡面去了
logfile stdout


# 設定資料庫的數量,預設為0可以使用select <dbid>命令在連線上指定資料庫id
databases 16


# 指定在多少時間內重新整理次數達到多少的時候會將資料同步到資料檔案
save <seconds> <changes>


# 指定儲存至本地資料庫時是否壓縮檔案,預設為yes即啟用儲存
rdbcompression yes


# 指定本地資料庫檔名
dbfilename dump.db


# 指定本地資料問就按存放位置
dir ./


# 指定當本機為slave服務時,設定master服務的IP地址及埠,在redis啟動的時候他會自動跟master進行資料同步
slaveof <masterip> <masterport>


# 當master設定了密碼保護時,slave服務連線master的密碼
masterauth <master-password>


# 設定redis連線密碼,如果配置了連線密碼,客戶端在連線redis是需要通過AUTH<password>命令提供密碼,預設關閉
requirepass footbared


# 設定同一時間最大客戶連線數,預設無限制。redis可以同時連線的客戶端數為redis程式可以開啟的最大檔案描述符,如果設定 maxclients 0,表示不作限制。當客戶端連線數到達限制時,Redis會關閉新的連線並向客戶端返回 max number of clients reached 錯誤資訊
maxclients 128


# 指定Redis最大記憶體限制,Redis在啟動時會把資料載入到記憶體中,達到最大記憶體後,Redis會先嚐試清除已到期或即將到期的Key。當此方法處理後,仍然到達最大記憶體設定,將無法再進行寫入操作,但仍然可以進行讀取操作。Redis新的vm機制,會把Key存放記憶體,Value會存放在swap區
maxmemory<bytes>


# 指定是否在每次更新操作後進行日誌記錄,Redis在預設情況下是非同步的把資料寫入磁碟,如果不開啟,可能會在斷電時導致一段時間內的資料丟失。因為redis本身同步資料檔案是按上面save條件來同步的,所以有的資料會在一段時間內只存在於記憶體中。預設為no。
appendonly no


# 指定跟新日誌檔名預設為appendonly.aof
appendfilename appendonly.aof


# 指定更新日誌的條件,有三個可選引數 - no:表示等作業系統進行資料快取同步到磁碟(快),always:表示每次更新操作後手動呼叫fsync()將資料寫到磁碟(慢,安全), everysec:表示每秒同步一次(折衷,預設值);
appendfsync everysec

我們需要修改的配置

# 設定後臺啟動
# 由於Redis預設是前臺啟動,不建議使用.可以修改為後臺
daemonize yes


# 禁止protected-mode yes/no(保護模式,是否只允許本地訪問)
protected-mode


# 設定遠端訪問
# Redis預設只允許本機訪問,把bind修改為bind 0.0.0.0 此設定會變成允許所有遠端訪問,如果指定限制訪問,可設定對應IP。
# bind指定是redis所在伺服器網路卡的IP,不指定本機網路卡IP,可能導致你的Redis例項無法啟動
# 如果想限制IP訪問,內網的話通過網路介面(網路卡限定),讓客戶端訪問固定網路卡連結redis
# 如果是公網,通過iptables指定某個IP允許訪問
bind 0.0.0.0

# 配置Redis日誌記錄
# 找到logfile,預設為logfile "",改為自定義日誌格式
logfile  /var/log/redis_6379.log

# 把requirepass修改為123456,修改之後重啟下服務
requirepass "123456"
# 不重啟Redis設定密碼
# 在配置檔案中配置requirepass的密碼(當Redis重啟時密碼依然生效)
127.0.0.1:6379> config set requirepass test123
# 查詢密碼
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "test123"

# 密碼驗證
127.0.0.1:6379> auth test123
OK
127.0.0.1:6379> set name flying
OK
127.0.0.1:6379> get name
"flying"

# 遠端主機連線
# redis-cli  -h  redis_ip -p redis_port -a password
啟動測試
# 放到後臺輸出,redis自帶日誌了,可以輸出到黑洞
nohup redis-server /usr/local/redis/redis.conf &> /usr/local/redis/redis.log &

# 關閉命令
redis-cli -h 127.0.0.1 -p 6379 -a 123456 shutdown
# 注意:不建議使用 kill -9,這種方式不但不會做持久化操作,還會造成緩衝區等資源不能優雅關閉。極端情況下造成 AOF 和 複製丟失資料 的情況。
# shutdown 還有一個引數,代表是否在關閉 redis 前,生成 持久化檔案,命令為 redis-cli shutdown nosave|save。


# 設定開機自啟動
echo "redis-server /usr/local/redis.conf" >> /etc/rc.local
註冊Redis為系統服務

在/etc/init.d目錄新增Redis服務的啟動,暫停和重啟指令碼

vim /etc/init.d/redis
#!/usr/bin/env bash
# chkconfig: 2345 10 90  
# description: Start and Stop redis 
PORT=6379
EXEC=/usr/local/redis/src/redis-server
CLIEXEC=/usr/local/redis/src/redis-cli

PIDFILE=/var/run/redis_${PORT}.pid
CONF="/etc/redis/${PORT}.conf"

case "$1" in
    start)
        if [ -f $PIDFILE ]
        then
                echo "$PIDFILE exists, process is already running or crashed"
        else
                echo "Starting Redis server..."
                $EXEC $CONF &>/dev/null &
        fi
        ;;
    stop)
	PID=$(cat $PIDFILE)
        if [ ! -f $PIDFILE ]
        then
                echo "$PIDFILE does not exist, process is not running"
        else
                echo "Stopping ..."
                $CLIEXEC -p $PORT shutdown
                while [ -d /proc/${PID} ]
                do
                    echo "Waiting for Redis to shutdown ..."
                    sleep 1
                done
                echo "Redis stopped"
        fi
        ;;
    restart)
        "$0" stop
        sleep 3
        "$0" start
        ;;
    *)
        echo "Please use start or stop as first argument"
        ;;
esac



chmod +x /etc/init.d/redis  
mkdir /etc/redis  
cp /usr/local/redis/redis.conf /etc/redis/6379.conf  
chkconfig --add redis  
chkconfig redis on  
  
service redis start  
service redis restart

Redis常用命令

Redis-value操作

此處Redis五種資料型別具體操作先不演示,只做一個簡單的key操作,具體操作等Python操作Redis再寫詳細,前面主要以運維為主

# 新增一個key[name]為youmen
127.0.0.1:6379> set name youmen
OK

# 獲取key
127.0.0.1:6379> get name
"youmen"

# 檢視當前資料庫裡面的key
127.0.0.1:6379> keys *
1) "name"

# 判斷key是否存在
127.0.0.1:6379> exists name
(integer) 1

# 刪除key值,也就刪除了這條資料
127.0.0.1:6379> del name
(integer) 1
# 查詢不到對應的key返回值就會使(integer)0
127.0.0.1:6379> exists name
(integer) 0



# 設定key的過期時間,過期後key自動刪除
127.0.0.1:6379> set name youmen
OK
127.0.0.1:6379> set name youmen ex 2
127.0.0.1:6379> expire name 10
(integer) 1
127.0.0.1:6379> ttl name
(integer) 6
127.0.0.1:6379> ttl name
(integer) 2
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> exists name
(integer) 0
持久化命令
# save:將資料同步儲存到磁碟
# bgsave:將資料非同步儲存到磁碟
# lastsave:返回上次成功將資料儲存到磁碟的Unix時戳
# shundown:將資料同步儲存到磁碟,然後關閉服務
遠端服務控制
# info:提供伺服器的資訊和統計
# info clients:  檢視客戶端資訊.
# monitor:實時轉儲收到的請求
# slaveof:改變複製策略設定
# config:在執行時配置Redis伺服器
# client list: 檢視連結的客戶端有哪些
# chient kill 127.0.0.1:50390:  殺掉客戶端連結
# config get dir: 檢視儲存檔案目錄
# config get *:  檢視所有配置
# config set requirepass 123 : 修改配置,即時生效
# config get bind :  檢視配置檔案中的監聽地址
Redis info引數介紹
# 叢集每個節點獲取的資訊不一樣
127.0.0.1:6379> info
# Server
redis_version:4.0.14
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:5ad4d17d599d7e92
redis_mode:standalone			# 執行模式,單機或叢集
os:Linux 3.10.0-1062.1.1.el7.x86_64 x86_64	# 伺服器的宿主作業系統
arch_bits:64					# 架構
multiplexing_api:epoll			# redis所使用的事件迴圈機制
atomicvar_api:atomic-builtin	# 原子處理api
gcc_version:4.8.5				# 編譯Redis時所使用的GCC版本
process_id:19955				# 伺服器程式的PID
run_id:3cd8b85e4c852fc93adbbb51eaee051a0a6a788d	# 標識redis server的隨機值
tcp_port:6379
uptime_in_seconds:4272			# redis server啟動的時間(單位s)
uptime_in_days:0				# redis server啟動的時間(單位d)
hz:10								
# hz:10  edis內部排程(進行關閉timeout的客戶端,刪除過期key等)頻率,程式規定serverCron每秒執行十次.
lru_clock:1935465						
# 自增的時鐘,用於LRU管理,該時鐘ms(hz=10,因此每1000ms/10=100ms執行一次定時任務)更新一次
executable:/usr/local/redis/src/redis-server	# 執行檔案
config_file:/etc/redis/6379.conf  # 配置檔案路徑
# Clients
connected_clients:1				# 已連線客戶端的數量(不包括通過從屬伺服器連線的客戶端)
client_longest_output_list:0			# 當前連線的客戶端中,最長的輸出列表
client_biggest_input_buf:0			# 當前連線的客戶端中,最大輸入快取
blocked_clients:0				# 正在等待阻塞命令(BLPOP、BRPOP、Brpoplpush)的客戶端的數量

# Memory
used_memory:849400				# 由Redis分配器分配的記憶體總量,以位元組(byte)為單位
used_memory_human:829.49K			# 以人類可讀的格式返回Redis分配的記憶體總量
used_memory_rss:8278016				
# 從作業系統的角度,返回Redis已分配的記憶體總量(俗稱常駐集大小),這個值和top,ps等命令輸出一致
used_memory_rss_human:7.89M			
# 以人類可讀的格式,從作業系統角度,返回Redis已分配的記憶體總量(俗稱常駐集大小),這個值和top,ps等命令輸出一致

used_memory_peak:849472				# redis的記憶體消耗峰值(以位元組為單位)
used_memory_peak_human:829.56K			# 以人類可讀的格式返回redis的記憶體消耗峰值
used_memory_peak_perc:99.99%			# (used_memory/ used_memory_peak) *100%

used_memory_overhead:836622
# Redis為了維護資料集的內部機制所需的記憶體開銷,
# 包括所有客戶端輸出緩衝區、查詢緩衝區、AOF重寫緩衝區和主從複製的backlog

used_memory_startup:786608			# Redis伺服器啟動時消耗的記憶體
used_memory_dataset:12778			# used_memory—used_memory_overhead
used_memory_dataset_perc:20.35%			
# 100%*(used_memory_dataset/(used_memory—used_memory_startup))

total_system_memory:1926860800			# 整個系統記憶體
total_system_memory_human:1.79G			# 以人類可讀格式,顯示整個系統記憶體
used_memory_lua:37888				# lua指令碼儲存佔用的記憶體
used_memory_lua_human:37.00K			# 以人類可讀的格式,顯示lua指令碼佔用的記憶體
maxmemory:0					# Redis例項的最大記憶體配置
maxmemory_human:0B				# 以人類可讀格式,顯示Redis例項的最大記憶體配置
maxmemory_policy:noeviction			# 當達到maxmemory時的淘汰策略
mem_fragmentation_ratio:9.74			# used_memory_rss/used_memory
mem_allocator:jemalloc-4.0.3			# 記憶體分配器
active_defrag_running:0				# 表示沒有活動的defrag任務正在執行(defrag: 表示記憶體碎片整理)
lazyfree_pending_objects:0			# 0表示不存在延遲釋放(也有資料翻譯末惰性刪除)的掛起物件
# Persistence
loading:0					# 伺服器是否正在載入持久化檔案
rdb_changes_since_last_save:0			
# 離最近一次成功生成rdb檔案,寫入命令的個數,即有多少個寫入命令沒有持久化

rdb_bgsave_in_progress:0			# 伺服器是否正在建立rdb檔案
rdb_last_save_time:1578992739			
# 離最近一次成功建立rdb檔案的時間戳,當前時間戳-rdb_last_save_time=多少秒未成功生成rdb檔案
rdb_last_bgsave_status:ok			# 最近一次rdb持久化是否成功
rdb_last_bgsave_time_sec:0			# 最近一次成功生成rdb檔案耗時總數
rdb_current_bgsave_time_sec:-1		
# 如果伺服器正在建立rdb檔案,那麼這個域記錄的就是當前建立操作已經耗費的秒數
rdb_last_cow_size:6537216			 
# RDB過程中父程式與子程式相比執行了多少修改(包括讀緩衝區,寫緩衝區,資料修改等)
aof_enabled:0					# 是否開啟了aof
aof_rewrite_in_progress:0			# 標識aof的rewrite操作是否正在進行中
aof_rewrite_scheduled:0			
# rewrite任務計劃,當客戶端傳送bgrewriteaof指令,如果當前rewrite子程式正在執行,
# 那麼將客戶端請求的bgrewriteaof變為計劃任務,待aof子程式結束後執行rewrite
aof_last_rewrite_time_sec:-1			# 最近一次aof rewrite耗費的時長
aof_current_rewrite_time_sec:-1			# 如果rewrite操作正在進行,則記錄所使用的時間,單位秒
aof_last_bgrewrite_status:ok			# 上次bgrewriteaof操作的狀態
aof_last_write_status:ok			# 上次aof寫入狀態
aof_last_cow_size:0				
# AOF過程中父程式與子程式相比執行了多少次修改(包括讀緩衝區,寫緩衝區,資料修改等)

# Stats
total_connections_received:4
# 新建立連線個數,如果新建立連線過多,過度地建立和銷燬連線對效能有影響,
# 說明短連線嚴重或連線池使用有問題,需調研程式碼的連線設定
total_commands_processed:87			# Redis處理的命令數
instantaneous_ops_per_sec:0			# redis當前的qps,redis內部較實時的每秒執行的命令數
total_net_input_bytes:2757			# redis網路入口流量位元組數
total_net_output_bytes:53214			# redis網路出口流量位元組數
instantaneous_input_kbps:0.00			# redis網路入口kps
instantaneous_output_kbps:0.00			# redis網路入口kps
rejected_connections:0				
# 拒絕的連線的個數,redis連線個數達到maxclients限制,拒絕新連線個數
sync_full:0					# 主從完全同步成功次數
sync_partial_ok:0				# 主從部分同步成功次數
sync_partial_err:0				# 主從部分失敗次數
expired_keys:1					# 執行以來過期的key的數量
expired_stale_perc:0.00				# 過期的比率
expired_time_cap_reached_count:0		# 過期計數
evicted_keys:0					# 執行以來剔除(超過maxmemory後)的key的數量
keyspace_hits:26				# 命中次數
keyspace_misses:10				# 未命中次數
pubsub_channels:0				# 當前使用中的頻道數量
pubsub_patterns:0				# 當前使用的模式的數量
latest_fork_usec:578				# 最近一次fork操作阻塞redis程式的耗時數,單位微妙
migrate_cached_sockets:0			# 是否已經快取到了改地址的連結
slave_expires_tracked_keys:0			# 從例項到期key的數量
active_defrag_hits:0				# 主動碎片整理命中次數
active_defrag_misses:0				# 主動碎片整理未命中次數
active_defrag_key_hits:0			# 主動碎片整理key命中次數
active_defrag_key_misses:0			# 主動碎片整理key未命中次數.
# Replication
role:master					# 例項的角色,是master or slave
connected_slaves:0				# 連線的slave例項個數
master_replid:54da017499c5257de9b00d168dd49c04b8bbe7ef	# 主例項啟動隨機字串
master_replid2:0000000000000000000000000000000000000000	# 主例項啟動隨機字串
master_repl_offset:0			
# 主從同步偏移量,此值如果和上面的offset相同說明主從一致沒延遲,與master_replid可被用來標識主例項複製流的位置.
second_repl_offset:-1 				# 主從同步偏移量2,此值如果和上面的offset相同說明主從一致沒延遲
repl_backlog_active:0				# 複製積壓緩衝區是否開啟
repl_backlog_size:1048576			# 複製積壓緩衝大小
repl_backlog_first_byte_offset:0		# 複製緩衝區偏移量的大小
repl_backlog_histlen:0
# 此值等於 master_repl_offset - repl_backlog_first_byte_offset,該值不會超過repl_backlog_size的大小

# CPU
used_cpu_sys:1.90				# 將所有redis主程式在核心態所佔用的CPU時求和累積起來
used_cpu_user:1.14				# 將所有redis主程式在使用者態所佔用CPU時求和累計起來
used_cpu_sys_children:0.01		# 將後臺程式在核心態所佔用的CPU時求和累計起來
used_cpu_user_children:0.00		# 將後臺程式在使用者態所佔用CPU時求和累計起來

# Cluster
cluster_enabled:0

# Keyspace
db0:keys=7,expires=0,avg_ttl=0

部署Go操作Redis客戶端

安裝

golang操作redis的客戶端包有多個比如redigo、go-redis,github上Star最多的莫屬redigo。

github地址:https://github.com/garyburd/redigo 目前已經遷移到:https://github.com/gomodule/redigo

文件:https://godoc.org/github.com/garyburd/redigo/redis

go get github.com/garyburd/redigo/redis
import "github.com/garyburd/redigo/redis"
連線

Conn介面是與Redis協作的主要介面,可以使用Dial,DialWithTimeout或者NewConn函式來建立連線,當任務完成時,應用程式必須呼叫Close函式來完成操作。

package main

import (
	"fmt"
	"github.com/garyburd/redigo/redis"
)

func main() {
	conn,err := redis.Dial("tcp","121.36.43.223:6379")
	if err != nil{
		fmt.Println("connect redis server:",err)
		return
	}
	fmt.Println(conn)
	defer conn.Close()
}

Go常用Redis命令操作

命令操作

通過使用Conn介面中的do方法執行redis命令,redis命令大全參考:http://doc.redisfans.com/

go中傳送與響應對應型別:

Do函式會必要時將引數轉化為二進位制字串

Go Type Conversion
[]byte Sent as is
string Sent as is
int, int64 strconv.FormatInt(v)
float64 strconv.FormatFloat(v, 'g', -1, 64)
bool true -> "1", false -> "0"
nil ""
all other types fmt.Print(v)

Redis命令響應會用一下Go型別表示

Redis type Go type
error redis.Error
integer int64
simple string string
bulk string []byte or nil if value not present.
array []interface{} or nil if value not present

可以使用GO的型別斷言或者reply輔助函式將返回的interface{}轉換為對應型別

SET,GET
package main

import (
	"fmt"
	"github.com/garyburd/redigo/redis"
)

func main()  {
	conn,err := redis.Dial("tcp","121.36.43.223:6379")
	if err != nil{
		fmt.Println("connect redis error:",err)
		return
	}
	defer conn.Close()

	_,err = conn.Do("SET","youmen","18")
	if err != nil{
		fmt.Println("redis set error:",err)
	}
	name, err := redis.String(conn.Do("GET","youmen"))
	if err != nil{
		fmt.Println("redis get error:",err)
	}else {
		fmt.Printf("Get name: %s \n",name)
	}
}
設定key過期時間
package main

import (
	"fmt"
	"github.com/garyburd/redigo/redis"
)

func main()  {
	conn,err := redis.Dial("tcp","121.36.43.223:6379")
	if err != nil{
		fmt.Println("connect redis error:",err)
		return
	}
	defer conn.Close()
	_, err = conn.Do("SET", "name", "youmen")
	if err != nil {
		fmt.Println("redis set error:", err)
	}
	_,err = conn.Do("expire","name",10)
	if err != nil{
		fmt.Println("set expire error:",err)
		return
	}
	name,err := redis.String(conn.Do("GET","name"))
	if err != nil{
		fmt.Println("redis get error:",err)
	} else {
		fmt.Printf("GET name: %s \n",name)
	}
}
批量獲取mget,批量設定mset
package main

import (
	"fmt"
	"github.com/garyburd/redigo/redis"
	"reflect"
)

func main()  {
	conn,err := redis.Dial("tcp","121.36.43.223:6379")
	if err != nil{
		fmt.Println("connect redis error:",err)
		return
	}
	defer conn.Close()

	_,err = conn.Do("MSET","name","youmen","age","22")
	if err != nil{
		fmt.Println("redis mset error:",err)
	}
	res,err := redis.Strings(conn.Do("MGET","name","age"))
	if err != nil{
		fmt.Println("redis get error",err)
	} else {
		res_type := reflect.TypeOf(recover())
		fmt.Printf("res type : %s \n", res_type)
		fmt.Printf("MGET name: %s \n", res)
		fmt.Println(len(res))
	}
}
列表操作
package main

import (
	"fmt"
	"github.com/garyburd/redigo/redis"
	"reflect"
)

func main()  {
	conn,err := redis.Dial("tcp","121.36.43.223:6379")
	if err != nil{
		fmt.Println("connect redis error:",err)
		return
	}
	defer conn.Close()
	_, err = conn.Do("LPUSH", "list1", "l1","l2","l3")
	if err != nil {
		fmt.Println("redis mset error:", err)
	}
	res, err := redis.String(conn.Do("LPOP", "list1"))
	if err != nil {
		fmt.Println("redis POP error:", err)
	} else {
		res_type := reflect.TypeOf(res)
		fmt.Printf("res type : %s \n", res_type)
		fmt.Printf("res  : %s \n", res)
	}
}
hash操作
package main

import (
	"fmt"
	"github.com/garyburd/redigo/redis"
	"reflect"
)

func main()  {
	conn,err := redis.Dial("tcp","121.36.43.223:6379")
	if err != nil{
		fmt.Println("connect redis error:",err)
		return
	}
	defer conn.Close()

	_, err = conn.Do("HSET", "student","name", "wd","age",22)
	if err != nil {
		fmt.Println("redis mset error:", err)
	}
	res, err := redis.Int64(conn.Do("HGET", "student","age"))
	if err != nil {
		fmt.Println("redis HGET error:", err)
	} else {
		res_type := reflect.TypeOf(res)
		fmt.Printf("res type : %s \n", res_type)
		fmt.Printf("res  : %d \n", res)
	}
}