Redis之並發寫入

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

首先需要澄清一個事實:redis服務端是單線程處理用戶端請求,也就是說用戶端請求在服務端是序列化執行的,因此對服務端來說,並不存在並發問題。但業務方卻存在並行作業redis中的同一個key的情況。所以如何讓A用戶端知道B用戶端正在操作它想操作的 key,就成了必須要討論的問題。

那麼開始總結下方案吧:

1. SETNX key value      //key存在就不做任何操作,返回0;不存在操作成功返回1

這種方式通過對需要操作的key加鎖來保證並行作業的序列化。這裡我們以Golang代碼為例來舉例說明該操作。先看多個協程寫同一個key的情況。代碼如下:

package mainimport (    "fmt"    "github.com/garyburd/redigo/redis"    "runtime"    "sync"    "time")var w sync.WaitGroupfunc newRdsPool(server, auth string) *redis.Pool {    return &redis.Pool{        MaxIdle:     100,        MaxActive:   30,        IdleTimeout: 60 * time.Second,        Dial: func() (redis.Conn, error) {            c, err := redis.Dial("tcp", server)            if err != nil {                return nil, err            }            if auth == "" {                return c, err            }            if _, err := c.Do("AUTH", auth); err != nil {                c.Close()                return nil, err            }            return c, err        },        TestOnBorrow: func(c redis.Conn, t time.Time) error {            _, err := c.Do("PING")            return err        },    }}func g1(r redis.Conn) {    for i := 0; i < 2; i++ {        if _, err := redis.String(r.Do("set", "hello", "1")); err != nil {            fmt.Println(err)        }        time.Sleep(10 * time.Millisecond)    }    w.Done()}func g2(r redis.Conn) {    for i := 0; i < 2; i++ {        if _, err := redis.String(r.Do("set", "hello", "2")); err != nil {            fmt.Println(err)        }        time.Sleep(10 * time.Millisecond)    }    w.Done()}func main() {    w.Add(2)    runtime.GOMAXPROCS(runtime.NumCPU())    var rc1 redis.Conn = newRdsPool(`127.0.0.1:6379`, ``).Get()    var rc2 redis.Conn = newRdsPool(`127.0.0.1:6379`, ``).Get()    defer rc1.Close()    defer rc2.Close()    go g1(rc1)    go g2(rc2)    w.Wait()}

執行上面的代碼之後,hello的值在1和2之間徘徊。希望出現的是如果協程1在操作時候,協程2就放棄操作,也即讓操作序列化。這樣就需要有一個鎖來保證不能同時讓兩個協程進去臨界區。setnx = set if not exists 不存在返回1,存在返回0。通過這個機制可以判斷當前的lock是否已經被設定了。lock必須給一個到期時間,因為很有可能goroutine1do work的時候出現panic,這樣就導致goroutine2一直在嘗試擷取鎖。

package mainimport (    "fmt"    "github.com/garyburd/redigo/redis"    "runtime"    "sync"    "time")var w sync.WaitGroupfunc newRdsPool(server, auth string) *redis.Pool {    return &redis.Pool{        MaxIdle:     100,        MaxActive:   30,        IdleTimeout: 60 * time.Second,        Dial: func() (redis.Conn, error) {            c, err := redis.Dial("tcp", server)            if err != nil {                return nil, err            }            if auth == "" {                return c, err            }            if _, err := c.Do("AUTH", auth); err != nil {                c.Close()                return nil, err            }            return c, err        },        TestOnBorrow: func(c redis.Conn, t time.Time) error {            _, err := c.Do("PING")            return err        },    }}func g1(r redis.Conn) {    var lock int64    var lock_timeout int64 = 2    var lock_time int64    var now int64    for lock != 1 {        now = time.Now().Unix()        lock_time = now + lock_timeout        lock, err1 := redis.Int64(r.Do("setnx", "foo", lock_time))        lockValue1, err2 := redis.Int64(r.Do("get", "foo"))        if lock == 1 && err1 == nil {            break        } else {            if now > lockValue1 && err2 == nil {                lockValue2, err3 := redis.Int64(r.Do("getset", "foo", lock_time))                if err3 == nil && now > lockValue2 {                    break                } else {                    fmt.Println(`g1 not get lock`)                    time.Sleep(1000 * time.Millisecond)                }            } else {                fmt.Println(`g1 not get lock`)                time.Sleep(1000 * time.Millisecond)            }        }    }    for i := 0; i < 5; i++ {        if _, err := redis.String(r.Do("set", "hello", "1")); err != nil {            fmt.Println(err)        }        fmt.Println(`g1 now work... `)        time.Sleep(1 * time.Second)    }    if time.Now().Unix() < lock_time {        if _, err4 := redis.Int64(r.Do("del", "foo")); err4 != nil {            fmt.Println(err4)        }    }    w.Done()}func g2(r redis.Conn) {    var lock int64    var lock_timeout int64 = 2    var lock_time int64    var now int64    for lock != 1 {        now = time.Now().Unix()        lock_time = now + lock_timeout        lock, err1 := redis.Int64(r.Do("setnx", "foo", lock_time))        lockValue1, err2 := redis.Int64(r.Do("get", "foo"))        if lock == 1 && err1 == nil {            break        } else {            if now > lockValue1 && err2 == nil {                lockValue2, err3 := redis.Int64(r.Do("getset", "foo", lock_time))                if err3 == nil && now > lockValue2 {                    break                } else {                    fmt.Println(`g2 not get lock`)                    time.Sleep(1000 * time.Millisecond)                }            } else {                fmt.Println(`g2 not get lock`)                time.Sleep(1000 * time.Millisecond)            }        }    }    for i := 0; i < 5; i++ {        if _, err := redis.String(r.Do("set", "hello", "2")); err != nil {            fmt.Println(err)        }        fmt.Println(`g2 now work... `)        time.Sleep(1 * time.Second)    }    if time.Now().Unix() < lock_time {        if _, err4 := redis.Int64(r.Do("del", "foo")); err4 != nil {            fmt.Println(err4)        }    }    w.Done()}func main() {    w.Add(2)    runtime.GOMAXPROCS(runtime.NumCPU())    var rc1 redis.Conn = newRdsPool(`127.0.0.1:6379`, ``).Get()    var rc2 redis.Conn = newRdsPool(`127.0.0.1:6379`, ``).Get()    defer rc1.Close()    defer rc2.Close()    go g1(rc1)    go g2(rc2)    w.Wait()}

上面的代碼給出了兩個goroutine通過鎖達到序列化操作同一個key的效果。

2.  MULTI、DISCARD、 EXEC、WATCH    // redis事務 
3. 將對key的操作的值都放到一個list裡面

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.