golang使用redis分布式锁,缓存实现GetSet

golang使用分布式锁,缓存方法实现GetSet闭包实现

分布式场景

  1. 某个耗时的操作不允许并发情况
  2. 订单抢购
  3. 库存超卖
  4. 其他场景懒得想了……

初始化redis

redis.go

package initilize

import (
	"fmt"
	"log"
	"time"

	"xxxx/common/global"
	"xxxx/config"

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

/**
 * 初始化redis,并赋值给全局变量
 */

func InitilizeRedis() {
	// 创建redis连接池
	global.REDISPoll = GetRedisPool(global.CONFIG)
}

func GetRedisPool(config config.Config) *redis.Pool {
	return &redis.Pool{
		MaxIdle:     config.Redis.MaxIdle, // 最大空闲连接数
		MaxActive:   config.Redis.Active,  // 最大连接数
		IdleTimeout: time.Duration(config.Redis.Timeout) * time.Second,
		Wait:        true, // 超过连接数后是否等待
		Dial: func() (redis.Conn, error) {
			redisUri := fmt.Sprintf("%s:%s", config.Redis.Addr, config.Redis.Port)
			var redisConn redis.Conn
			var err error

			if config.Redis.Auth {
				redisConn, err = redis.Dial("tcp", redisUri, redis.DialPassword(config.Redis.Password))
			} else {
				redisConn, err = redis.Dial("tcp", redisUri)
			}

			if err != nil {
				log.Println("获取连接失败:" + err.Error())
				return nil, err
			}

			// 添加 Redis 前缀
			// if config.Redis.Prefix != "" {
			// 	redisConn = &PrefixedConn{Conn: redisConn, Prefix: config.Redis.Prefix}
			// }

			return redisConn, nil
		},
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			if time.Since(t) < time.Minute {
				return nil
			}
			_, err := c.Do("PING")
			return err
		},
	}
}

// PrefixedConn 是一个实现了 redis.Conn 接口的自定义结构体,它在键名前添加了前缀
type PrefixedConn struct {
	redis.Conn
	Prefix string
}

// Do 实现了 redis.Conn 接口的 Do 方法
func (c *PrefixedConn) Do(commandName string, args ...interface{}) (interface{}, error) {
	// todo

	return c.Conn.Do(commandName, args...)
}

golang简单的redis分布式锁代码实现,简单的GetSet实现

util.go

package util

import (
	"encoding/json"
	"fmt"

	"xxxx/task-flow-api/common/global"

	"github.com/garyburd/redigo/redis"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/spf13/cast"
)

// 简单分布式锁 -- 暂时使用在上传和下载
// 看门狗自动续时 暂不实现
// 分布式ID避免高并发时误释放 暂不实现
// lua保证原子性 暂不实现
// 保证这些特性可以去使用三方的库(github.com/go-redsync/redsync)

const DefaultRedisLockExpire = 300

func GetRedisLockKey(key string) string {
	return "lock:" + key
}

type RedisLock struct {
	key    string
	expire int
}

func NewRedisLock(key string, expire int) *RedisLock {
	return &RedisLock{
		key:    GetRedisLockKey(key),
		expire: expire,
	}
}

func NewUserRedisLock(ctx *gin.Context, key string, expire int) *RedisLock {
	return &RedisLock{
		key:    GetRedisLockKey(fmt.Sprintf("%d:%s", ctx.GetInt("user_id"), key)),
		expire: expire,
	}
}

func NewLesseeRedisLock(ctx *gin.Context, key string, expire int) *RedisLock {
	return &RedisLock{
		key:    GetRedisLockKey(fmt.Sprintf("%d:%s", ctx.GetInt("lessee_id"), key)),
		expire: expire,
	}
}

func (l *RedisLock) TryLock() bool {
	conn := global.REDISPoll.Get()
	defer conn.Close()
	txid := uuid.New().String()
	res, err := conn.Do("SET", l.key, txid, "NX", "EX", l.expire)
	if err != nil {
		return false
	}
	return cast.ToString(res) == "OK"
}

func (l *RedisLock) UnLock() error {
	conn := global.REDISPoll.Get()
	defer conn.Close()
	_, err := conn.Do("DEL", l.key)
	return err
}

const DefaultRedisItemEx = 300

type RedisItemInterface interface {
	ExpiresAfter(exp int)
}
type RedisItem struct {
	Exp int
}

func (r *RedisItem) ExpiresAfter(exp int) {
	r.Exp = exp
}

func GetSetAny[T any](key string, fun func(item RedisItemInterface) (T, error)) (result T, err error) {
	// 获取缓存
	conn := global.REDISPoll.Get()
	defer conn.Close()
	r, err := conn.Do("GET", key)
	if err != nil {
		return result, err
	}
	if r != nil {
		// 返回
		err = json.Unmarshal([]byte(cast.ToString(r)), &result)
		return
	}
	// 创建 RedisItem 实例
	redisItem := &RedisItem{}
	result, err = fun(redisItem)
	if err != nil {
		return result, err
	}
	// 存储缓存
	resStr, err := json.Marshal(result)
	if err != nil {
		return result, err
	}
	if redisItem.Exp == -1 {
		_, err = conn.Do("SET", key, resStr)
	} else {
		_, err = conn.Do("SET", key, resStr, "EX", redisItem.Exp)
	}
	return
}

func GetSetString(key string, fun func(item RedisItemInterface) (string, error)) (result string, err error) {
	// 获取缓存
	conn := global.REDISPoll.Get()
	defer conn.Close()
	r, err := conn.Do("GET", key)
	if err != nil {
		return "", err
	}
	if r != nil {
		// 返回
		result = cast.ToString(r)
		return
	}
	// 创建 RedisItem 实例
	redisItem := &RedisItem{}
	result, err = fun(redisItem)
	if err != nil {
		return "", err
	}
	// 存储缓存
	if redisItem.Exp == -1 {
		_, err = conn.Do("SET", key, result)
	} else {
		_, err = conn.Do("SET", key, result, "EX", redisItem.Exp)
	}
	return
}

func GetSetInt(key string, fun func(item RedisItemInterface) (int, error)) (result int, err error) {
	// 获取缓存
	conn := global.REDISPoll.Get()
	defer conn.Close()
	r, err := conn.Do("GET", key)
	if err != nil {
		return 0, err
	}
	if r != nil {
		// 返回
		result, err = redis.Int(r, err)
		return
	}
	// 创建 RedisItem 实例
	redisItem := &RedisItem{}
	result, err = fun(redisItem)
	if err != nil {
		return
	}
	// 存储缓存
	if redisItem.Exp == -1 {
		_, err = conn.Do("SET", key, result)
	} else {
		_, err = conn.Do("SET", key, result, "EX", redisItem.Exp)
	}
	return
}

func GetSetInt64(key string, fun func(item RedisItemInterface) (int64, error)) (result int64, err error) {
	// 获取缓存
	conn := global.REDISPoll.Get()
	defer conn.Close()
	r, err := conn.Do("GET", key)
	if err != nil {
		return 0, err
	}
	if r != nil {
		// 返回
		result, err = redis.Int64(r, err)
		return
	}
	// 创建 RedisItem 实例
	redisItem := &RedisItem{}
	result, err = fun(redisItem)
	if err != nil {
		return
	}
	// 存储缓存
	if redisItem.Exp == -1 {
		_, err = conn.Do("SET", key, result)
	} else {
		_, err = conn.Do("SET", key, result, "EX", redisItem.Exp)
	}
	return
}


TryLock测试


func TestRedisLock(t *testing.T) {
	// 从连接池取出一个连接
	conn := global.REDISPoll.Get()
	defer conn.Close() // 关闭连接池,一旦关闭就不能取数据了

	// res, err := conn.Do("SET", "sssss2", 1, "NX", "EX", 300)
	// fmt.Println(res, err)

	lock := util.NewUserRedisLock(&gin.Context{}, "test14", util.DefaultRedisLockExpire)
	fmt.Println("上锁:", lock.TryLock())
	time.Sleep(time.Second * 3)
	defer lock.UnLock()
}

GetSet方法测试

  1. 缓存存在的情况下获取缓存,不存在则调用回调方法存储缓存
    redis_test.go
package util

import (
	"fmt"
	_ "xxxx/task-flow-api/initialize"
	"testing"
)


func TestGetSetInt(t *testing.T) {
	res, err := GetSetInt("test", func(item RedisItemInterface) (int, error) {
		item.ExpiresAfter(60)
		return 1232, nil
	})

	if err != nil {
		fmt.Println("报错,", err)
	}
	fmt.Println("返回:", res)
}

func TestGetSetAny(t *testing.T) {
	type TestAny struct {
		A int
		B int
	}
	res, err := GetSetAny[TestAny]("TestAny", func(item RedisItemInterface) (TestAny, error) {
		item.ExpiresAfter(60)
		return TestAny{
			A: 1,
			B: 2,
		}, nil
	})

	if err != nil {
		fmt.Println("报错,", err)
	}
	fmt.Printf("%#v\n", res)
}


golang使用redis分布式锁,缓存实现GetSet_第1张图片

golang使用redis分布式锁,缓存实现GetSet_第2张图片

你可能感兴趣的:(缓存,golang,redis,分布式,后端)