1. 程式人生 > >Go redis 入門操作

Go redis 入門操作

我使用的是 https://github.com/go-redis/redis 這個 golang 客戶端, 因此安裝方式如下:

go get gopkg.in/redis.v4
接著在程式碼中匯入此包即可:

import "gopkg.in/redis.v4"


基本操作
建立客戶端

通過 redis.NewClient 函式即可建立一個 redis 客戶端, 這個方法接收一個 redis.Options 物件引數, 通過這個引數, 我們可以配置 redis 相關的屬性, 例如 redis 伺服器地址, 資料庫名, 資料庫密碼等. 
下面是一個連線的例子:

// 建立 redis 客戶端
func createClient() *redis.Client {
    client := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "",
        DB:       0,
    })

    // 通過 cient.Ping() 來檢查是否成功連線到了 redis 伺服器
    pong, err := client.Ping().Result()
    fmt.Println( pong, err )

    return client
}



String 操作
redis 的 String 操作有:

set( key, value ):給資料庫中名稱為 key 的 string 賦予值 value
get( key ):返回資料庫中名稱為 key 的 string 的 value
getset( key, value ):給名稱為 key 的 string 賦予上一次的 value
mget( key1, key2,…, key N ):返回庫中多個 string 的 value
setnx( key, value ):新增 string,名稱為 key,值為 value
setex( key, time, value ):向庫中新增 string,設定過期時間 time
mset( key N, value N ):批量設定多個 string 的值
msetnx( key N, value N ):如果所有名稱為 key 的 string 都不存在
incr( key ):名稱為 key 的 string 增 1 操作
incrby( key, integer ):名稱為 key 的 string 增加 integer
decr( key ):名稱為 key 的 string 減 1 操作
decrby( key, integer ):名稱為 key 的 string 減少 integer
append( key, value ):名稱為 key 的 string 的值附加 value
substr( key, start, end ):返回名稱為 key 的 string 的 value 的子串
在 go-redis 中, 我們可以直接找到對應的操作方法, 直接上程式碼:
 

//String 操作
func stringOperation( client *redis.Client ) {
    // 第三個引數是過期時間, 如果是0, 則表示沒有過期時間.
    err := client.Set( "name", "xys", 0 ).Err()
    if err != nil {
        panic(err)
    }

    val, err := client.Get( "name" ).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println( "name", val )

    // 這裡設定過期時間.
    err = client.Set( "age", "20", 1 * time.Second ).Err()
    if err != nil {
        panic(err)
    }

    client.Incr( "age" ) // 自增
    client.Incr( "age" ) // 自增
    client.Decr( "age" ) // 自減

    val, err = client.Get( "age" ).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println( "age", val ) // age 的值為21

    // 因為 key "age" 的過期時間是一秒鐘, 因此當一秒後, 此 key 會自動被刪除了.
    time.Sleep(1 * time.Second)
    val, err = client.Get( "age" ).Result()
    if err != nil {
        // 因為 key "age" 已經過期了, 因此會有一個 redis: nil 的錯誤.
        fmt.Printf( "error: %v\n", err )
    }
    fmt.Println( "age", val )
}


list 操作
redis 的 list 操作有:

rpush( key, value ):在名稱為 key 的 list 尾新增一個值為 value 的元素
lpush( key, value ):在名稱為 key 的 list 頭新增一個值為 value 的元素
llen( key ):返回名稱為 key 的 list 的長度
lrange( key, start, end ):返回名稱為 key 的 list 中 start 至 end 之間的元素
ltrim( key, start, end ):擷取名稱為 key 的 list
lindex( key, index ):返回名稱為 key 的 list 中 index 位置的元素
lset( key, index, value ):給名稱為 key 的 list 中 index 位置的元素賦值
lrem( key, count, value ):刪除 count 個 key 的 list 中值為 value 的元素
lpop( key ):返回並刪除名稱為 key 的 list 中的首元素
rpop( key ):返回並刪除名稱為 key 的 list 中的尾元素
blpop( key1, key2,… key N, timeout ):lpop 命令的 block 版本。
brpop( key1, key2,… key N, timeout ):rpop 的 block 版本。
rpoplpush( srckey, dstkey ):返回並刪除名稱為 srckey 的 list 的尾元素,並將該元素新增到名稱為 dstkey 的 list 的頭部
同樣地, 在 go-redis 中也可以找到對應的方法, 下面是一個簡單的示例:

//list 操作
func listOperation( client *redis.Client ) {
    client.RPush( "fruit", "apple" ) // 在名稱為 fruit 的list尾新增一個值為value的元素
    client.LPush( "fruit", "banana" ) // 在名稱為 fruit 的list頭新增一個值為value的 元素
    length, err := client.LLen( "fruit" ).Result() // 返回名稱為 fruit 的list的長度
    if err != nil {
        panic(err)
    }
    fmt.Println( "length: ", length ) // 長度為2

    value, err := client.LPop( "fruit" ).Result() // 返回並刪除名稱為 fruit 的list中的首元素
    if err != nil {
        panic(err)
    }
    fmt.Println( "fruit: ", value )

    value, err = client.RPop( "fruit" ).Result() // 返回並刪除名稱為 fruit 的list中的尾元素
    if err != nil {
        panic(err)
    }
    fmt.Println( "fruit: ", value )
}



set 操作
redis 的 set 操作:

sadd( key, member ):向名稱為 key 的 set 中新增元素 member
srem( key, member ):刪除名稱為 key 的 set 中的元素 member
spop( key ):隨機返回並刪除名稱為 key 的 set 中一個元素
smove( srckey, dstkey, member ):移到集合元素
scard( key ):返回名稱為 key 的 set 的基數
sismember( key, member ):member 是否是名稱為 key 的 set 的元素
sinter( key1, key2,…key N ):求交集
sinterstore( dstkey, ( keys ))`:求交集並將交集儲存到 dstkey 的集合
sunion( key1, ( keys ))`:求並集
sunionstore( dstkey, ( keys ))`:求並集並將並集儲存到 dstkey 的集合
sdiff( key1, ( keys ))`:求差集
sdiffstore( dstkey, ( keys ))`:求差集並將差集儲存到 dstkey 的集合
smembers( key ):返回名稱為 key 的 set 的所有元素
srandmember( key ):隨機返回名稱為 key 的 set 的一個元素
接下來是 go-redis 的 set 操作:

//set操作
func setOperation( client *redis.Client ) {
    client.SAdd( "blacklist", "Obama" ) // 向 blacklist 中新增元素
    client.SAdd( "blacklist", "Hillary" ) // 再次新增
    client.SAdd( "blacklist", "the Elder" ) // 新增新元素

    client.SAdd( "whitelist", "the Elder" ) // 向 whitelist 新增元素

    // 判斷元素是否在集合中
    isMember, err := client.SIsMember( "blacklist", "Bush" ).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println( "Is Bush in blacklist: ", isMember )


    // 求交集, 即既在黑名單中, 又在白名單中的元素
    names, err := client.SInter( "blacklist", "whitelist" ).Result()
    if err != nil {
        panic(err)
    }
    // 獲取到的元素是 "the Elder"
    fmt.Println( "Inter result: ", names )


    // 獲取指定集合的所有元素
    all, err := client.SMembers( "blacklist" ).Result()
    if err != nil {
        panic( err )
    }
    fmt.Println( "All member: ", all )
}



hash 操作
redis 的 hash 操作:

hset( key, field, value ):向名稱為 key 的 hash 中新增元素 field
hget( key, field ):返回名稱為 key 的 hash 中 field 對應的 value
hmget( key, ( fields ) ):返回名稱為 key 的 hash 中 field 對應的 value
hmset( key, ( fields ) ):向名稱為 key 的 hash 中新增元素 field
hincrby( key, field, integer ):將名稱為 key 的 hash 中 field 的 value 增加 integer
hexists( key, field ):名稱為 key 的 hash 中是否存在鍵為 field 的域
hdel( key, field ):刪除名稱為 key 的 hash 中鍵為 field 的域
hlen( key ):返回名稱為 key 的 hash 中元素個數
hkeys( key ):返回名稱為 key 的 hash 中所有鍵
hvals( key ):返回名稱為 key 的 hash 中所有鍵對應的 value
hgetall( key ):返回名稱為 key 的 hash 中所有的鍵( field )及其對應的 value
go-redis 中的 hash 操作:

// hash 操作
func hashOperation( client *redis.Client ) {
    client.HSet( "user_xys", "name", "xys" ); // 向名稱為 user_xys 的 hash 中新增元素 name
    client.HSet( "user_xys", "age", "18" ); // 向名稱為 user_xys 的 hash 中新增元素 age

    // 批量地向名稱為 user_test 的 hash 中新增元素 name 和 age
    client.HMSet( "user_test", map[string]string{"name": "test", "age":"20"} )
    // 批量獲取名為 user_test 的 hash 中的指定欄位的值.
    fields, err := client.HMGet( "user_test", "name", "age" ).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println( "fields in user_test: ", fields )


    // 獲取名為 user_xys 的 hash 中的欄位個數
    length, err := client.HLen( "user_xys" ).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println( "field count in user_xys: ", length ) // 欄位個數為2

    // 刪除名為 user_test 的 age 欄位
    client.HDel( "user_test", "age" )
    age, err := client.HGet("user_test", "age" ).Result()
    if err != nil {
        fmt.Printf( "Get user_test age error: %v\n", err )
    } else {
        fmt.Println( "user_test age is: ", age ) // 欄位個數為2
    }
}


關於連線池
redis.v4 包實現了 redis 的連線池管理, 因此我們就不需要自己手動管理 redis 的連線了. 
預設情況下, redis.v4 的 redis 連線池大小是10, 不過我們可以在初始化 redis 客戶端時自行設定連線池的大小, 例如:

client := redis.NewClient( &redis.Options{
    Addr:     "localhost:6379",
    Password: "",
    DB:       0,
    PoolSize: 5,
} )


通過 redis.Options 的 PoolSize 屬性, 我們設定了 redis 連線池的大小為 5. 
那麼接下來我們來看一下這個設定有什麼效果吧:

// redis.v4 的連線池管理
func connectPool( client *redis.Client ) {
    wg := sync.WaitGroup{}
    wg.Add(10)

    for i := 0; i < 10; i++ {
        go func() {
            defer wg.Done()

            for j := 0; j < 100; j++ {
                client.Set( fmt.Sprintf( "name%d", j ), fmt.Sprintf( "xys%d", j ), 0 ).Err()
                client.Get( fmt.Sprintf( "name%d", j ) ).Result()
            }

            fmt.Printf( "PoolStats, TotalConns: %d, FreeConns: %d\n", client.PoolStats().TotalConns, client.PoolStats().FreeConns );
        }()
    }

    wg.Wait()
}


上面的例子啟動了 10 個 routine 來不斷向 redis 讀寫資料, 然後我們通過 client.PoolStats() 獲取連線池的資訊. 執行這個例子, 輸出如下:

PoolStats, TotalConns: 5, FreeConns: 1
PoolStats, TotalConns: 5, FreeConns: 1
PoolStats, TotalConns: 5, FreeConns: 1
PoolStats, TotalConns: 5, FreeConns: 1
PoolStats, TotalConns: 5, FreeConns: 1
PoolStats, TotalConns: 5, FreeConns: 2
PoolStats, TotalConns: 5, FreeConns: 2
PoolStats, TotalConns: 5, FreeConns: 3
PoolStats, TotalConns: 5, FreeConns: 4
PoolStats, TotalConns: 5, FreeConns: 5
通過輸出可以看到, 此時最大的連線池數量確實是 5 了, 並且一開始時, 因為 coroutine 的數量大於 5, 會造成 redis 連線不足的情況(反映在 FreeConns 上就是前幾次的輸出 FreeConns 一直是 1 ), 當某個 coroutine 結束後, 會釋放此 redis 連線, 因此 FreeConns 會增加.