這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
首先需要澄清一個事實: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必須給一個到期時間,因為很有可能goroutine1在do 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裡面