go中redis使用小結


最近做了個關於redis的項目,那么就整理下遇到和未遇到的問題

1、redis的簡介安裝

2、redis的數據結構

3、Redis基本使用

4、Redis的並發

5、Redis的落地

 

一、redis的簡介安裝

 

一、Redis 是什么

  Redis 是一款依據BSD開源協議發行的高性能Key-Value存儲系統(cache and store)。它通常被稱為數據結構服務器,因為值(value)可以是 字符串(String), 哈希(Map), 列表(list), 集合(sets) , 有序集合(sorted sets)和位圖(bitmaps)等類型。官方網站是 http://redis.io/

  Redis 和其它 NO SQL 的比較本文不做過多闡述。我覺得 Redis 最好的地方就是提供數據持久化功能(定時把內存中的數據寫入文件),從而不至於一旦宕機將造成數據丟失。而且相較於 Memcached ,它提供的值類型選擇更為寬泛。

 

二、Redis 下載安裝  

  打開 Redis 官網,我們發現 Redis 官方並不支持 Windows 平台,但 Microsoft Open Tech Group 卻改變了這一情況

  點擊 Learn more

  點擊 Download ZIP, 下載完后解壓,我們發現其並沒有提供現成的執行安裝文件,這就需要我們自行進行編譯。定位到目錄 Redis\redis2.8\msvs,打開文件 RedisServer.sln

 

  項目結構如下圖

  由於筆者的機器為64位,在編譯之前我們確認一下編譯 Platform, 同時我們可以看到對於此 project 將會編譯產生 redis-server.exe 文件

   其它項目類似

   編譯成功之后,我們到其 Debug 目錄下找到編譯產生的文件

  為了便於處理,我們新建目錄 Redis,並把這些文件拷貝過去

  其中的 redis.conf 來自如下目錄

  至此,我們已經得到所有需要的文件了,下面就可以使用了,打開 CMD, 定位到目錄 D:\Developer\Redis\Redis,然后執行如下命令

redis-server.exe redis.conf

  執行成功的截圖(可以看到端口為6379, 進程標識符 PID 為7696)

  執行過程中還會讀取配置,由於截圖太長,故這里放出文字

  View Code

  Server 端好了,現在我們開一個 Client 端來測試一下,新打開 CMD (之前打開的 CMD - Server 端不能關閉)

 

redis-cli.exe -h 10.7.15.172 -p 6379

   其中 10.7.15.172 為本機 IP

set hello helloworld

  設置一個值

get hello

  讀取這個值

  大概15分鍾之后我們發現 Server 端也有變化

  原來15分鍾自動把內存中的數據寫入 RDF 文件以防丟失。

  至於為什么是15分鍾,我們可以看到配置文件是這樣設置的(1個更改/900秒,10更改/300秒,10000更改/60秒),即更改的越多,數據寫入文件的時間間隔越短,這樣設計蠻合理的。

 

三、Redis Desktop Manager

   雖然通過上面的 CMD 我們也能看到 Redis 在內存中的數據,但方式太不友好了,這里介紹一個工具 Redis Desktop Manager

  下載完成后安裝,之后連接至 Server 即可

 

   點擊查看數據

 

四、Install Redis as Windows Service

   前面我們通過 CMD 方式安裝了Redis, 但是非常不方便,因為我們要一直保持窗口打開,而且如果機器重啟的話也需要重新打開。Redis 也可以以 Windows Service 的方式進行部署。在部署之前我們需要把配置文件找到

  然后拷貝到 Redis 目錄

  安裝服務

redis-server --service-install redis.windows.conf

  安裝成功提示

  查看服務

  啟動服務

redis-server --service-start

  停止服務

redis-server --service-stop

  卸載服務

redis-server --service-uninstall

 

  其實安裝成 Windows 服務還有一種方式,就是從 Github 上直接下載安裝文件,但是好像不是最新的版本

 

 

二、Redis中的數據類型

redis是鍵值對的數據庫,有5中主要數據類型

字符串類型(string),散列類型(hash),列表類型(list),集合類型(set),有序集合類型(zset)

 

幾個基本的命令:

KEYS * 獲得當前數據庫的所有鍵

EXISTS key [key ...]  判斷鍵是否存在,返回個數,如果key有一樣的也是疊加數

DEL key [key ...]       刪除鍵,返回刪除的個數

TYPE key                  獲取減值的數據類型(string,hash,list,set,zset)

FLUSHALL                清空所有數據庫

CONFIG [get、set]    redis配置

 

-inf 負無窮

+inf正無窮

一:字符串類型string

字符串類型是Redis的最基本類型,它可以存儲任何形式的字符串。其它的四種類型都是字符串類型的不同形式。

最基本的命令:GET、SET         語法:GET key,SET key value   value如果有空格需要雙引號以示區分

整數遞增:INCR                      語法:INCR key    默認值為0,所以首先執行命令得到 1 ,不是整型提示錯誤

增加指定的整數:INCRBY          語法:INCRBY key increment

整數遞減:DECR                     語法:DECR key   默認值為0,所以首先執行命令得到 -1,不是整型提示錯誤

減少指定的整數:DECRBY         語法:DECRBY key increment

增加指定浮點數:INCRBYFLOAT 語法:INCRBYFLOAT key increment  與INCR命令類似,只不過可以遞增一個雙精度浮點數

向尾部追加值:APPEND             語法:APPEND key value   redis客戶端並不是輸出追加后的字符串,而是輸出字符串總長度

獲取字符串長度:STRLEN          語法:STRLEN key  如果鍵不存在返回0,注意如果有中文時,一個中文長度是3,redis是使用UTF-8編碼中文的

獲取多個鍵值:MGET                語法:MGET key [key ...]  例如:MGET key1 key2 

設置多個鍵值:MSET                語法:MSET key value [key value ...]  例如:MSET key1 1 key2 "hello redis"

二進制指定位置值:GETBIT        語法:GETBIT key offset   例如:GETBIT key1 2 ,key1為hello 返回 1,返回的值只有0或1,

                   當key不存在或超出實際長度時為0

設置二進制位置值:SETBIT       語法:SETBIT key offset value ,返回該位置的舊值

二進制是1的個數:BITCOUNT    語法:BITCOUNT key [start end] ,start 、end為開始和結束字節

位運算:BITOP                       語法:BITOP operation destkey key [key ...]  ,operation支持AND、OR、XOR、NOT

偏移:BITPOS                        語法:BITPOS key bit [start] [end]

 

二:散列類型hash

設置單個:HSET                      語法:HSET key field value,不存在時返回1,存在時返回0,沒有更新和插入之分

設置多個:HMSET                    語法:HMSET key field value [field value ...]

讀取單個:HGET                      語法:HGET key field,不存在是返回nil

讀取多個:HMGET                    語法:HMGET key field [field ...]

讀取全部:HGETALL                 語法:HGETALL key,返回時字段和字段值的列表

判斷字段是否存在:HEXISTS      語法:HEXISTS key field,存在返回1 ,不存在返回0

字段不存在時賦值:HSETNX       語法:HSETNX key field value,與hset命令不同,hsetnx是鍵不存在時設置值

增加數字:HINCRBY                 語法:HINCRBY key field increment ,返回增加后的數,不是整數時會提示錯誤

刪除字段:HDEL                      語法:HDEL key field [field ...] ,返回被刪除字段的個數

只獲取字段名:HKEYS               語法:HKEYS key ,返回鍵的所有字段名

只獲取字段值:HVALS              語法:HVALS key  ,返回鍵的所有字段值

字段數量:HLEN                      語法:HLEN key ,返回字段總數

 

三:列表類型(list)

內部使用雙向鏈表實現,所以獲取越接近兩端的元素速度越快,但通過索引訪問時會比較慢

添加左邊元素:LPUSH               語法:LPUSH key value [value ...]  ,返回添加后的列表元素的總個數

添加右邊元素:RPUSH              語法:RPUSH key value [value ...]  ,返回添加后的列表元素的總個數

移除左邊第一個元素:LPOP        語法:LPOP key  ,返回被移除的元素值

移除右邊第一個元素:RPOP        語法:RPOP key ,返回被移除的元素值 

列表元素個數:LLEN                語法:LLEN key, 不存在時返回0,redis是直接讀取現成的值,並不是統計個數

獲取列表片段:LRANGE           語法:LRANGE key start stop,如果start比stop靠后時返回空列表,0 -1 返回整個列表

                                                    正數時:start 開始索引值,stop結束索引值(索引從0開始)

                                                    負數時:例如 lrange num -2 -1,-2表示最右邊第二個,-1表示最右邊第一個,

刪除指定值:LREM                  語法:LREM key count value,返回被刪除的個數

                                                   count>0,從左邊開始刪除前count個值為value的元素

                                                   count<0,從右邊開始刪除前|count|個值為value的元素

                                                   count=0,刪除所有值為value的元素

索引元素值:LINDEX               語法:LINDEX key index ,返回索引的元素值,-1表示從最右邊的第一位

設置元素值:LSET                  語法:LSET key index value

保留列表片段:LTRIM              語法:LTRIM key start stop,start、top 參考lrange命令

一個列表轉移另一個列表:RPOPLPUSH      語法:RPOPLPUSH source desctination ,從source列表轉移到desctination列表,

                                                                 該命令分兩步看,首先source列表RPOP右移除,再desctination列表LPUSH

 

四:集合類型(set)

集合類型值具有唯一性,常用操作是向集合添加、刪除、判斷某個值是否存在,集合內部是使用值為空的散列表實現的。

添加元素:SADD                    語法:SADD key member [member ...] ,向一個集合添加一個或多個元素,因為集合的唯一性,所以添加相同值時會被忽略。

                        返回成功添加元素的數量。

刪除元素:SREM                    語法:SREM key member [member ...] 刪除集合中一個或多個元素,返回成功刪除的個數。

獲取全部元素:SMEMBERS      語法:SMEMBERS key ,返回集合全部元素

值是否存在:SISMEMBER        語法:SISMEMBER key member ,如果存在返回1,不存在返回0

差運算:SDIFF                      語法:SDIFF key [key ...] ,例如:集合A和集合B,差集表示A-B,在A里有的元素B里沒有,返回差集合;多個集合(A-B)-C

交運算:SINTER                語法:SINTER key [key ...],返回交集集合,每個集合都有的元素

並運算:SUNION        語法:SUNION key [key ...],返回並集集合,所有集合的元素

集合元素個數:SCARD           語法:SCARD key ,返回集合元素個數

集合運算后存儲結果                語法:SDIFFSTROE destination key [key ...] ,差運算並存儲到destination新集合中

                   SINTERSTROE destination key [key ...],交運算並存儲到destination新集合中

                                                  SUNIONSTROE destination key [key ...],並運算並存儲到destination新集合中

隨機獲取元素:SRANDMEMGER 語法:SRANDMEMBER key [count],根據count不同有不同結果,count大於元素總數時返回全部元素

                  count>0 ,返回集合中count不重復的元素

                  count<0,返回集合中count的絕對值個元素,但元素可能會重復

彈出元素:SPOP                     語法:SPOP key [count] ,因為集合是無序的,所以spop會隨機彈出一個元素

 

五:有序集合類型

添加集合元素:ZADD              語法:ZADD key [NX|XX] [CH] [INCR] score member [score member ...],不存在添加,存在更新。

獲取元素分數:ZSCORE          語法:ZSCORE key member ,返回元素成員的score 分數

元素小到大:ZRANGE             語法:ZRANGE key start top [WITHSCORES] ,參考LRANGE ,加上withscores 返回帶元素,即元素,分數

                                                  當分數一樣時,按元素排序

元素大到小:ZREVRANGE       語法:ZREVRANGE key start [WITHSCORES] ,與zrange區別在於zrevrange是從大到小排序

指定分數范圍元素:ZRANGEBYSCORE   語法:ZRANGEBYSCORE key min max [WITHSCORE] [LIMIT offest count]

                返回從小到大的在min和max之間的元素,( 符號表示不包含,例如:80-100,(80 100,

                  withscore返回帶分數

                  limit offest count 向左偏移offest個元素,並獲取前count個元素

指定分數范圍元素:ZREVRANGESCORE   語法:ZREVRANGEBYSCORE key max  min [WITHSCORE] [LIMIT offest count]

                與zrangebyscore類似,只不過該命令是從大到小排序的。

增加分數:ZINCRBY                語法:ZINCRBY key increment member ,注意是增加分數,返回增加后的分數;如果成員不存在,則添加一個為0的成員。

 

 

三、GO中Redis簡使用

 

連接

import "github.com/garyburd/redigo/redis"

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

 

set & get

        _, err = c.Do("Set", "name", "nick")
    if err != nil {
        fmt.Println(err)
        return
    }

    r, err := redis.String(c.Do("Get", "name"))
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(r)

 

mset & mget

批量設置

        _, err = c.Do("MSet", "name", "nick", "age", "18")
    if err != nil {
        fmt.Println("MSet error: ", err)
        return
    }

    r2, err := redis.Strings(c.Do("MGet", "name", "age"))
    if err != nil {
        fmt.Println("MGet error: ", err)
        return
    }
    fmt.Println(r2)

 

hset & hget

hash操作

    _, err = c.Do("HSet", "names", "nick", "suoning")
    if err != nil {
        fmt.Println("hset error: ", err)
        return
    }

    r, err = redis.String(c.Do("HGet", "names", "nick"))
    if err != nil {
        fmt.Println("hget error: ", err)
        return
    }
    fmt.Println(r)

 

expire

設置過期時間

    _, err = c.Do("expire", "names", 5)
    if err != nil {
        fmt.Println("expire error: ", err)
        return
    }

 

lpush & lpop & llen

隊列

    // 隊列
    _, err = c.Do("lpush", "Queue", "nick", "dawn", 9)
    if err != nil {
        fmt.Println("lpush error: ", err)
        return
    }
    for {
        r, err = redis.String(c.Do("lpop", "Queue"))
        if err != nil {
            fmt.Println("lpop error: ", err)
            break
        }
        fmt.Println(r)
    }
    r3, err := redis.Int(c.Do("llen", "Queue"))
    if err != nil {
        fmt.Println("llen error: ", err)
        return
    }

 

連接池

各參數的解釋如下:

MaxIdle:最大的空閑連接數,表示即使沒有redis連接時依然可以保持N個空閑的連接,而不被清除,隨時處於待命狀態。

MaxActive:最大的激活連接數,表示同時最多有N個連接

IdleTimeout:最大的空閑連接等待時間,超過此時間后,空閑連接將被關閉

    pool := &redis.Pool{
        MaxIdle:     16,
        MaxActive:   1024,
        IdleTimeout: 300,
        Dial: func() (redis.Conn, error) {
            return redis.Dial("tcp", "localhost:6379")
        },
    }

 

連接池栗子

package main

import (
    "fmt"

    "github.com/garyburd/redigo/redis"
)

var pool *redis.Pool

func init() {
    pool = &redis.Pool{
        MaxIdle:     16,
        MaxActive:   1024,
        IdleTimeout: 300,
        Dial: func() (redis.Conn, error) {
            return redis.Dial("tcp", "localhost:6379")
        },
    }
}

func main() {
    c := pool.Get()
    defer c.Close()

    _, err := c.Do("Set", "name", "nick")
    if err != nil {
        fmt.Println(err)
        return
    }

    r, err := redis.String(c.Do("Get", "name"))
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(r)
}

 

管道操作

請求/響應服務可以實現持續處理新請求,客戶端可以發送多個命令到服務器而無需等待響應,最后在一次讀取多個響應。

使用Send(),Flush(),Receive()方法支持管道化操作

Send向連接的輸出緩沖中寫入命令。

Flush將連接的輸出緩沖清空並寫入服務器端。

Recevie按照FIFO順序依次讀取服務器的響應。

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

    c.Send("SET", "name1", "sss1")
    c.Send("SET", "name2", "sss2")

    c.Flush()

    v, err := c.Receive()
    fmt.Printf("v:%v,err:%v\n", v, err)
    v, err = c.Receive()
    fmt.Printf("v:%v,err:%v\n", v, err)

    v, err = c.Receive()    // 夯住,一直等待
    fmt.Printf("v:%v,err:%v\n", v, err)

 

四、Redis的並發

在日常的開發中,有時我們會遇到這樣的場景:多個人對同一個數據進行修改操作,導致並發問題發生。這個問題可以通過悲觀鎖來解決,但是悲觀鎖也是有限制的,在某些場景中是不適應的,因為和數據的耦合度太高了,可能會影響到其他業務的操作。而使用redis來解決這一問題是很好的選擇。

原理介紹

redis的存儲指令中有一個setnx方法,這個方法有一個特性,就是當鍵不存在的時候,會將這條數據插入,並且返回1,如果這個鍵已經存在了,那么就不會插入這條數據,並且返回0。

 

功能實現

明白了這個實現的原理之后,要實現這個功能就很簡單了。

  1. 在事務開啟的時候,我們就去redis中setnx一條數據,這條數據的鍵要和你當前操作的數據有關,這樣就只會鎖定一條數據,而不影響其他數據的業務,例如:做訂單審核的時候,將訂單號+業務簡寫作為鍵。
  2. 判斷上面插入操作的返回值,如果返回1,就繼續執行,如果返回0,直接return.
  3. 在事務結束之后,將redis中的這條數據刪除。直接使用del(String key)就可以了。

 go操作Redis不得不提就是Pipelining(管道)

管道操作可以理解為並發操作,並通過Send(),Flush(),Receive()三個方法實現。客戶端可以使用send()方法一次性向服務器發送一個或多個命令,命令發送完畢時,使用flush()方法將緩沖區的命令輸入一次性發送到服務器,客戶端再使用Receive()方法依次按照先進先出的順序讀取所有命令操作結果。

Send(commandName string, args ...interface{}) error
Flush() error
Receive() (reply interface{}, err error)
  • Send:發送命令至緩沖區
  • Flush:清空緩沖區,將命令一次性發送至服務器
  • Recevie:依次讀取服務器響應結果,當讀取的命令未響應時,該操作會阻塞。

示例:

package main

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


func main()  {
    conn,err := redis.Dial("tcp","10.1.210.69:6379")
    if err != nil {
        fmt.Println("connect redis error :",err)
        return
    }
    defer conn.Close()
    conn.Send("HSET", "student","name", "wd","age","22")
    conn.Send("HSET", "student","Score","100")
    conn.Send("HGET", "student","age")
    conn.Flush()

    res1, err := conn.Receive()
    fmt.Printf("Receive res1:%v \n", res1)
    res2, err := conn.Receive()
    fmt.Printf("Receive res2:%v\n",res2)
    res3, err := conn.Receive()
    fmt.Printf("Receive res3:%s\n",res3)
}
//Receive res1:0 
//Receive res2:0
//Receive res3:22

  

事務操作

MULTI, EXEC,DISCARD和WATCH是構成Redis事務的基礎,當然我們使用go語言對redis進行事務操作的時候本質也是使用這些命令。

MULTI:開啟事務

EXEC:執行事務

DISCARD:取消事務

WATCH:監視事務中的鍵變化,一旦有改變則取消事務。

示例:

package main

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


func main()  {
    conn,err := redis.Dial("tcp","10.1.210.69:6379")
    if err != nil {
        fmt.Println("connect redis error :",err)
        return
    }
    defer conn.Close()
    conn.Send("MULTI")
    conn.Send("INCR", "foo")
    conn.Send("INCR", "bar")
    r, err := conn.Do("EXEC")
    fmt.Println(r)
}
//[1, 1]

 

四、Redis的落地

 

Redis 的落地策略其實就是持久化(Persistence),主要有以下2種策略:

  1. RDB: 定時快照方式(snapshot)
  2. AOF: 基於語句追加文件的方式

RDB

RDB 文件非常緊湊,它保存了 Redis 某個時間點上的數據集。RDB 恢復大數據集時速度要比 AOF 快。但是 RDB 不適合那些對時效性要求很高的業務,因為它只保存了快照,在進行恢復時會導致一些時間內的數據丟失。實際在進行備份時,Redis 主要依靠 rdbSave() 函數,然后有兩個命令會調用這個函數 SAVE 和 BGSAVE,前者會同步調用,阻塞主進程導致會有短暫的 Redis-server 停止工作,后者會 fork 出子進程異步處理。

在調用 SAVE 或者 BGSAVE 時,只有發布和訂閱功能的命令可以正常執行,因為這個模塊和服務器的其他模塊是隔離的。
下面的命令表示: “60 秒內有至少有 1000 個鍵被改動”時進行RDB文件備份。

redis-server> SAVE 60 1000

RDB 文件的結構

開頭的REDIS表示這是一個 RDB 文件,然后緊跟着 redis 的版本號,SELECT-DB 和 KEY-VALUES-PAIRS 構成了對一個數據庫中的所有數據記錄,其中 KEY-VALUES-PAIRS具體結構如下,后面兩個就不用說了。

其中對於不同的類型,RDB文件中有不同的 layout,具體就不寫出來了。

AOF

AOF 可以通過設置的 fsync 策略配置,如果未設置 fsync ,AOF 的默認策略為每秒鍾 fsync 一次,在這種配置下, fsync 會在后台線程執行,所以主線程不會受到打擾。但是像 AOF 這種策略會導致追加的文件非常大,而且在恢復大數據時非常緩慢,因為要把所有會導致寫數據庫的命令都重新執行一遍。AOF文件中實際存儲的是 Redis 協議下的命令記錄,因此非常易讀。

當然 Redis 考慮到了 AOF 文件過大的問題,因此引入了 BGREWRITEAOF 命令進行重建 AOF 文件,保證可以減少大量無用的重復寫操作。重建命令並不會去分析已有的 AOF 文件,而是將當前數據庫的快照保存。

在 AOF 文件重寫時,Redis 的具體邏輯如下:

  1. Redis 首先 fork 出一個子進程,子進程將新 AOF 文件的內容寫入到臨時文件。
  2. 對於所有新執行的寫入命令,父進程一邊將它們累積到一個緩存中,一邊將這些改動追加到現有 AOF 文件的末尾: 這樣即使在重寫的中途發生停機,現有的 AOF 文件也還是安全的。
  3. 當子進程完成重寫工作時,它給父進程發送一個信號,父進程在接收到信號之后,將緩存中的所有數據追加到新 AOF 文件的末尾。
  4. 現在 Redis 原子地用新文件替換舊文件,之后所有命令都會直接追加到新 AOF 文件的末尾。

Redis 會維持一個默認的AOF重寫策略,當當前的AOF文件比上次重寫之后的文件大小增大了一倍時,就會自動在后台重寫AOF。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM