Rumah  >  Artikel  >  pangkalan data  >  Cara melaksanakan kunci teragih dalam Go digabungkan dengan Redis

Cara melaksanakan kunci teragih dalam Go digabungkan dengan Redis

PHPz
PHPzke hadapan
2023-05-27 21:55:241193semak imbas

    Senario contoh Redis Tunggal

    Jika anda biasa dengan arahan Redis, anda boleh segera memikirkan untuk menggunakan set Redis jika tidak wujud operasi untuk melaksanakannya, dan ia kini standard Pelaksanaannya ialah siri arahan SET resource_name my_random_value NX PX 30000, di mana:

    • resource_name mewakili sumber yang akan dikunci

    • NX mewakili jika ia tidak wujud Kemudian tetapkan

    • PX 30000 untuk menunjukkan masa tamat tempoh ialah 30000 milisaat, iaitu 30 saat

    • my_random_value mestilah unik di kalangan semua pelanggan , semua pemeroleh (pesaing) kunci yang sama tidak boleh mempunyai nilai yang sama.

    Nilai nilai mestilah nombor rawak terutamanya untuk melepaskan kunci dengan lebih selamat Apabila melepaskan kunci, gunakan skrip untuk memberitahu Redis: hanya kunci yang wujud dan nilai yang disimpan sama seperti nilai yang saya nyatakan Barulah saya boleh diberitahu bahawa pemadaman itu berjaya. Ini boleh dicapai melalui skrip Lua berikut:

    if redis.call("get",KEYS[1]) == ARGV[1] then
        return redis.call("del",KEYS[1])
    else
        return 0
    end

    Contohnya: Klien A memperoleh kunci sumber, tetapi disekat dengan serta-merta oleh operasi lain Apabila Klien A mahu melepaskan kunci selepas menjalankan operasi lain, ia bertukar bahawa Kunci telah tamat masa dan telah dikeluarkan secara automatik oleh Redis, dan dalam tempoh ini kunci sumber telah diperoleh semula oleh klien B.

    Skrip Lua digunakan kerana penghakiman dan pemadaman adalah dua operasi, jadi ada kemungkinan A akan melepaskan kunci secara automatik selepas ia tamat tempoh sebaik sahaja ia menilainya, dan kemudian B memperoleh kunci itu, dan kemudian A memanggil Del, menyebabkan B ke Kunci dilepaskan.

    Contoh mengunci dan membuka kunci

    package main
    
    import (
       "context"
       "errors"
       "fmt"
       "github.com/brianvoe/gofakeit/v6"
       "github.com/go-redis/redis/v8"
       "sync"
       "time"
    )
    
    var client *redis.Client
    
    const unlockScript = `
    if redis.call("get",KEYS[1]) == ARGV[1] then
        return redis.call("del",KEYS[1])
    else
        return 0
    end`
    
    func lottery(ctx context.Context) error {
       // 加锁
       myRandomValue := gofakeit.UUID()
       resourceName := "resource_name"
       ok, err := client.SetNX(ctx, resourceName, myRandomValue, time.Second*30).Result()
       if err != nil {
          return err
       }
       if !ok {
          return errors.New("系统繁忙,请重试")
       }
       // 解锁
       defer func() {
          script := redis.NewScript(unlockScript)
          script.Run(ctx, client, []string{resourceName}, myRandomValue)
       }()
    
       // 业务处理
       time.Sleep(time.Second)
       return nil
    }
    
    func main() {
       client = redis.NewClient(&redis.Options{
          Addr: "127.0.0.1:6379",
       })
       var wg sync.WaitGroup
       wg.Add(2)
       go func() {
          defer wg.Done()
          ctx, _ := context.WithTimeout(context.Background(), time.Second*3)
          err := lottery(ctx)
          if err != nil {
             fmt.Println(err)
          }
       }()
       go func() {
          defer wg.Done()
          ctx, _ := context.WithTimeout(context.Background(), time.Second*3)
          err := lottery(ctx)
          if err != nil {
             fmt.Println(err)
          }
       }()
       wg.Wait()
    }

    Mari kita lihat dahulu fungsi loteri(), yang menyerupai operasi loteri Apabila memasuki fungsi, mula-mula gunakan SET resource_name my_random_value NX PX 30000 untuk mengunci, di sini gunakan UUID sebagai nilai Rawak Jika operasi gagal, ia akan kembali secara langsung dan membolehkan pengguna mencuba lagi Jika logik buka kunci berjaya dilaksanakan dalam penangguhan, logik buka kunci adalah untuk melaksanakan skrip Lua yang disebutkan di atas dan kemudian melakukan pemprosesan perniagaan.

    Kami melaksanakan dua goroutine dalam fungsi main() untuk memanggil fungsi lottery() secara serentak Salah satu operasi akan gagal secara langsung kerana kunci tidak boleh diperolehi.

    Ringkasan

    • Jana nilai rawak

    • Gunakan SET resource_name my_random_value NX PX 30000 untuk mengunci

    • Jika kunci gagal, kembali terus ke
    • tunda untuk menambah logik buka kunci untuk memastikan
    • akan dilaksanakan apabila fungsi keluar Logik perniagaan
    • Senario contoh Redis Berbilang

    Dalam kes satu contoh, jika tika ini hang, semua permintaan akan gagal kerana kunci tidak boleh diperolehi, jadi kami Anda memerlukan berbilang kejadian Redis yang diedarkan pada mesin yang berbeza, dan anda perlu mendapatkan kunci kebanyakan nod untuk berjaya mengunci Ini ialah algoritma RedLock. Kita perlu memperoleh kunci pada berbilang kejadian Redis pada masa yang sama, tetapi ia sebenarnya berdasarkan algoritma kejadian tunggal.

    Contoh Tambah dan Buka Kunci

    package main
    
    import (
       "context"
       "errors"
       "fmt"
       "github.com/brianvoe/gofakeit/v6"
       "github.com/go-redis/redis/v8"
       "sync"
       "time"
    )
    
    var clients []*redis.Client
    
    const unlockScript = `
    if redis.call("get",KEYS[1]) == ARGV[1] then
        return redis.call("del",KEYS[1])
    else
        return 0
    end`
    
    func lottery(ctx context.Context) error {
       // 加锁
       myRandomValue := gofakeit.UUID()
       resourceName := "resource_name"
       var wg sync.WaitGroup
       wg.Add(len(clients))
       // 这里主要是确保不要加锁太久,这样会导致业务处理的时间变少
       lockCtx, _ := context.WithTimeout(ctx, time.Millisecond*5)
       // 成功获得锁的Redis实例的客户端
       successClients := make(chan *redis.Client, len(clients))
       for _, client := range clients {
          go func(client *redis.Client) {
             defer wg.Done()
             ok, err := client.SetNX(lockCtx, resourceName, myRandomValue, time.Second*30).Result()
             if err != nil {
                return
             }
             if !ok {
                return
             }
             successClients <- client
          }(client)
       }
       wg.Wait() // 等待所有获取锁操作完成
       close(successClients)
       // 解锁,不管加锁是否成功,最后都要把已经获得的锁给释放掉
       defer func() {
          script := redis.NewScript(unlockScript)
          for client := range successClients {
             go func(client *redis.Client) {
                script.Run(ctx, client, []string{resourceName}, myRandomValue)
             }(client)
          }
       }()
       // 如果成功加锁得客户端少于客户端数量的一半+1,表示加锁失败
       if len(successClients) < len(clients)/2+1 {
          return errors.New("系统繁忙,请重试")
       }
    
       // 业务处理
       time.Sleep(time.Second)
       return nil
    }
    
    func main() {
       clients = append(clients, redis.NewClient(&redis.Options{
          Addr: "127.0.0.1:6379",
          DB:   0,
       }), redis.NewClient(&redis.Options{
          Addr: "127.0.0.1:6379",
          DB:   1,
       }), redis.NewClient(&redis.Options{
          Addr: "127.0.0.1:6379",
          DB:   2,
       }), redis.NewClient(&redis.Options{
          Addr: "127.0.0.1:6379",
          DB:   3,
       }), redis.NewClient(&redis.Options{
          Addr: "127.0.0.1:6379",
          DB:   4,
       }))
       var wg sync.WaitGroup
       wg.Add(2)
       go func() {
          defer wg.Done()
          ctx, _ := context.WithTimeout(context.Background(), time.Second*3)
          err := lottery(ctx)
          if err != nil {
             fmt.Println(err)
          }
       }()
       go func() {
          defer wg.Done()
          ctx, _ := context.WithTimeout(context.Background(), time.Second*3)
          err := lottery(ctx)
          if err != nil {
             fmt.Println(err)
          }
       }()
       wg.Wait()
       time.Sleep(time.Second) 
    }

    Dalam kod di atas, kami menggunakan pangkalan data berbilang Redis untuk mensimulasikan berbilang kejadian induk Redis Secara amnya, kami akan memilih 5 kejadian Redis contoh harus Mereka diedarkan pada mesin yang berbeza untuk mengelakkan kegagalan serentak.

    Dalam logik penguncian, kami terutamanya melaksanakan SET resource_name my_random_value NX PX 30000 pada setiap kejadian Redis untuk mendapatkan kunci, dan kemudian meletakkan klien yang berjaya mendapatkan kunci ke dalam saluran (mungkin terdapat isu konkurensi apabila menggunakan slice di sini) . Pada masa yang sama, gunakan sync.WaitGroup untuk menunggu operasi pemerolehan kunci tamat.

    Kemudian tambah tangguh untuk melepaskan logik kunci Logik pelepas kunci adalah sangat mudah, cuma lepaskan kunci yang berjaya diperolehi.
    Akhir sekali, nilai sama ada bilangan kunci yang berjaya diperoleh adalah lebih daripada separuh Jika lebih daripada separuh kunci tidak diperoleh, ini bermakna penguncian gagal.
    Jika penguncian berjaya, langkah seterusnya ialah melakukan pemprosesan perniagaan.

    Ringkasan

      Jana nilai rawak
    • dan hantar ke setiap contoh Redis menggunakan
    • Kunci

      SET resource_name my_random_value NX PX 30000

    • Tunggu sehingga semua operasi pemerolehan kunci selesai
    • tangguh menambah logik buka kunci untuk memastikan ia akan dilaksanakan apabila fungsi keluar Di sini, tangguh dahulu dan kemudian menilai kerana mungkin untuk mendapatkan Kunci pada beberapa contoh Redis telah diperolehi, tetapi kerana ia tidak melebihi separuh, ia masih akan dinilai sebagai kegagalan kunci Kembali terus ke
    • untuk melaksanakan logik perniagaan

    Atas ialah kandungan terperinci Cara melaksanakan kunci teragih dalam Go digabungkan dengan Redis. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

    Kenyataan:
    Artikel ini dikembalikan pada:yisu.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam