go 进阶 三方库之 go-redis

目录

  • 一. 基础
    • 初始化连接
    • 使用示例
      • 1. 常用操作与string
      • 2. 操作hash类型
      • 3. 操作list
      • 4. 操作set
      • 5. 操作zset
      • 6. 发布与订阅
      • 7. 事物操作
      • 8. 执行Lua脚本
  • 二. 基于redis实现分布式锁
    • 封装锁结构体
    • lua脚本
    • 加锁,释放锁
    • 运行测试
  • 三. bitmap使用示例
  • 四. go-redis 生产使用示例
    • 初始化连接
    • go-redis操作数据

一. 基础

  1. 拉取github.com/go-redis/redis包

注意有个v8版本,是有方式上不太一样

  1. 为什么使用go-redis: 与Go语言redis client库:redigo比,因为go-redis支持连接哨兵及集群模式的Redis

初始化连接

import (
	"context"
	"errors"
	"fmt"
	"github.com/go-redis/redis"
	"math/rand"
	"net"
	"os"
	"strconv"
	"testing"
	"time"
)

// 声明一个全局的rdb变量
var rdb *redis.Client

//1.初始化连接初始化连接
func initClient() (err error) {
	rdb = redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "", // no password set
		DB:       0,  // use default DB
	})

	_, err = rdb.Ping().Result()
	if err != nil {
		fmt.Println("连接redis失败")
		os.Exit(1)
	}
	return nil
}
  1. redisClient可配置参数详解
func redisClientInfo() {
	rdb = redis.NewClient(&redis.Options{
		Addr:     "127.0.0.1:6379",
		DB:       0,
		Password: "",
		Network:  "tcp",

		//连接池容量及闲置连接数量
		PoolSize:     15, // 连接池最大socket连接数,默认为4倍CPU数, 4 * runtime.NumCPU
		MinIdleConns: 10, //在启动阶段创建指定数量的Idle连接,并长期维持idle状态的连接数不少于指定数量;。

		//超时
		DialTimeout:  5 * time.Second, //连接建立超时时间,默认5秒。
		ReadTimeout:  3 * time.Second, //读超时,默认3秒, -1表示取消读超时
		WriteTimeout: 3 * time.Second, //写超时,默认等于读超时
		PoolTimeout:  4 * time.Second, //当所有连接都处在繁忙状态时,客户端等待可用连接的最大等待时长,默认为读超时+1秒

		//闲置连接检查包括IdleTimeout,MaxConnAge
		//闲置连接检查的周期,默认为1分钟,-1表示不做周期性检查,只在客户端获取连接时对闲置连接进行处理。
		IdleCheckFrequency: 60 * time.Second, 
		IdleTimeout:        5 * time.Minute,  //闲置超时,默认5分钟,-1表示取消闲置超时检查
		//连接存活时长,从创建开始计时,超过指定时长则关闭连接,默认为0,即不关闭存活时长较长的连接
		MaxConnAge:         0 * time.Second,  

		//命令执行失败时的重试策略
		MaxRetries:      0,                      // 命令执行失败时,最多重试多少次,默认为0即不重试
		MinRetryBackoff: 8 * time.Millisecond,   //每次计算重试间隔时间的下限,默认8毫秒,-1表示取消间隔
		MaxRetryBackoff: 512 * time.Millisecond, //每次计算重试间隔时间的上限,默认512毫秒,-1表示取消间隔

		//可自定义连接函数
		Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
			netDialer := &net.Dialer{
				Timeout:   5 * time.Second,
				KeepAlive: 5 * time.Minute,
			}
			return netDialer.Dial("tcp", "127.0.0.1:6379")
		},

		//钩子函数
		//仅当客户端执行命令时需要从连接池获取连接时,如果连接池需要新建连接时则会调用此钩子函数
		OnConnect: func(ctx context.Context, cn *redis.Conn) error {
			fmt.Printf("conn=%v\n", cn)
			return nil
		},
	})
}

使用示例

1. 常用操作与string

func TestRedis1(t *testing.T) {
	//1.设置key和value,以及key的过期时间expiration
	err := rdb.Set("key", "val", 0).Err()
	if nil != err {
		fmt.Println("插入失败")
		return
	}

	//2.获取key的值
	valStr, err := rdb.Get("key").Result()
	if nil != err {
		if redis.Nil == err {
			fmt.Println("不存在")
			return
		}
		fmt.Println("插入数据异常err:" + err.Error())
		return
	}
	fmt.Println(valStr)

	//3.设置一个key的值,并且返回这个key的旧值
	rdb.GetSet("key", "val").Result()

	//4.key不存在,则设置这个key,存在则失败, 成功返回1,失败返回0
	rdb.SetNX("", "", 0).Err()

	//5.批量设置key的值
	rdb.MSet("").Err()

	//6.批量查询key值
	rdb.MGet("").Result()

	//7.删除单个或多个key
	rdb.Del("key1", "key2").Err()

	//8.设置指定key的过期时间
	rdb.Expire("", 0).Err()

	//9.针对一个key进行递增操作
	rdb.Incr("key").Result()

	//10.针对一个key进行递减操作
	rdb.Decr("key").Result()

	//11.查看键的类型
	str, err := rdb.Type("key").Result()
	if err == nil {
		fmt.Println(str)
	}

	//12.查看指定key的过期时间
	time, err := rdb.TTL("key").Result()
	if err == nil {
		fmt.Println(time)
	}

	//13.执行自定义命令
	rdb.Do("set", "key", 10, "EX", 3600).Err()

	//14.根据前缀获取Key
	rdb.Keys("key*")

	//15.通过游标迭代器查询,非阻塞式查询key,与keys命令的区别:
	// > scan时间复杂度虽然也是O(N),但是分次进行的,不会阻塞线程。
	// > scan命令提供了limit参数,可以控制每次返回结果的最大条数。

	//当Scan方法需要的cursor也就是游标为0时,表示一个新的完整迭代
	//当执行完Scan方法后,返回的数据实际包含两部分一个是游标,一个是元素值
	//如果下次想再此处继续迭代,则将cursor设置为上次返回的

	//count: 指定返回结果的最大条数
	iter := rdb.Scan(0, "k*", 0).Iterator()
	for iter.Next() {
		err := rdb.Del(iter.Val()).Err()
		if err != nil {
			panic(err)
		}
	}
}

2. 操作hash类型

func TestRedisHash(t *testing.T) {
	//1.添加
	err := rdb.HSet("key", "field", "val").Err()
	if nil != err {
		fmt.Println("插入失败")
		return
	}

	//2.获取指定key的指定field值
	rdb.HGet("key", "field").Result()

	//3.批量设置
	fieldMap := make(map[string]interface{})
	fieldMap["field1"] = "val1"
	fieldMap["field2"] = "val2"
	rdb.HMSet("key", fieldMap)

	//4.获取key下所有field值,或者获取key下一个或多个field值
	rdb.HMGet("key", "field1", "field2")

	//5.获取指定key下所有field与对应的值
	rdb.HGetAll("key")

	//6.在指定key的指定field上进行累加
	rdb.HIncrBy("key", "field", 1)

	//7.获取指定key下的所有field
	rdb.HKeys("key")

	//8.获取指定key下字段数量
	rdb.HLen("key")

	//9.向指定key中添加field如果不存在添加成功返回1
	//如果存在添加无效返回0
	rdb.HSetNX("key", "field", "val")

	//10.删除指定key,或删除key下的指定一个或多个field
	rdb.HDel("key", "field1", "field2")

	//11.判断指定key,或key下的指定field是否存在
	rdb.HExists("", "")

	//12.获取指定key下所有value
	rdb.HVals("key")
}

3. 操作list

func TestRedisList(t *testing.T) {
	//1.添加
	rdb.LPush("name", "marry").Err()
	rdb.LPush("name", "tom").Err()

	//2.将一个或多个值插入到列表头部,如果key不存在,创建一个空列表并执行LPUSH操作
	rdb.LPush("key", "val").Err()
	//将一个值插入到已存在的列表头部,列表不存在时操作无效
	rdb.LPushX("key", "val")

	//3.将一个或多个值插入到列表的尾部(最右边)如果列表不存在,创建一个空列表并执行RPUSH操作
	rdb.RPush("", "")
	//将一个值插入到已存在的列表尾部,列表不存在时操作无效
	rdb.RPushX("", "")

	//4.移除并返回列表的第一个元素
	rdb.LPop("key").Result()

	//5.移除并返回列表的最后一个元素
	rdb.RPop("key").Result()

	//6.返回列表的长度,如果列表key不存在,则key被解释为一个空列表,返回 0
	rdb.LLen("")

	//7.返回列表中指定区间内的元素
	// 0表示第一个,1表示第二个,-1表示最后一个,-2表示倒数第二个
	rdb.LRange("key", 0, 2)

	//8.根据参数 COUNT 的值,移除列表中与参数 VALUE 相等的元素
	//count>0: 从表头开始向表尾搜索,移除与 VALUE 相等的元素,数量为COUNT
	//count<0: 从表尾开始向表头搜索,移除与 VALUE 相等的元素,数量为COUNT的绝对值
	//count=0 : 移除表中所有与 VALUE 相等的值
	rdb.LRem("key ", 22, "")

	//9.通过索引获取列表中的元素,其中 -1表示最后一个,-2表示倒数第二个
	rdb.LIndex("key", 3)

	//10.在列表的元素前或者后插入元素,当指定元素不存在时,返回 -1
	rdb.LInsert("key", "BEFORD|AFTER", "元素", "插入val")
}

4. 操作set

func TestRedisSet(t *testing.T) {
	//1.添加
	rdb.SAdd("name", "xh")
	//2.获取key下元素个数
	rdb.SCard("key")
	//3.判断集合中是否有该值
	rdb.SIsMember("key", "val")
	//4.获取
	rdb.SMembers("key")
	//5.删除key或删除该key下的指定元素
	rdb.SRem("key", "val")
	//6.随机弹出key下的一个元素
	rdb.SPop("key")
	//7.随机弹出key下的指定个数元素
	rdb.SPopN("key", 2)
}

5. 操作zset

func TestRedisZSet(t *testing.T) {
	//1.添加
	z := redis.Z{
		Score:  2,
		Member: "kevin",
	}
	rdb.ZAdd("key", z)

	//2.获取集合中元素个数
	rdb.ZCard("key")

	//3.获取集合中指定分数区间的个数
	rdb.ZCount("key", "1", "20")

	//4.对集合中指定成员的分数加上增量 increment
	rdb.ZIncrBy("key", 20, "val")

	//5.获取指定区间内的成员,返回时按分数从小到大排序,相同则按照元素排序
	rdb.ZRange("key", 1, 10)
	//6.获取指定区间内的成员,返回时按分数从大到小排序,相同则按照元素排序
	rdb.ZRevRange("key", 1, 10)

	//7.获取指定分数区间的元素,返回时按分数从小到大排序
	zRange := redis.ZRangeBy{
		Min: "0",
		Max: "3",
	}
	rdb.ZRangeByScore("key", zRange)
	//8.获取指定分数区间的元素,返回时按分数从大到小排序
	rdb.ZRevRangeByScore("key", zRange)
	//9.返回在分数之类的所有的成员以及分数??
	rdb.ZRangeByScoreWithScores("", zRange)

	//10.删除指定key,或key下的指定一个或多个元素
	rdb.ZRem("key", "val1", "")

	//11.移除有序集中,指定排名(rank)区间内的所有成员
	rdb.ZRemRangeByRank("key", 10, 11)

	//12.移除有序集中,指定分数区间内的所有成员
	rdb.ZRemRangeByScore("key", "1", "10")

	//13.返回指定元素分数
	rdb.ZScore("key", "val")

	//14.返回元素在集合中的排名
	rdb.ZRank("key", "val")
}

6. 发布与订阅

func TestRedisPublishAndSubscribe(t *testing.T) {
	//发布
	rdb.Publish("频道1", "数据")
	//订阅,监听指定频道获取数据
	pubsub := rdb.Subscribe("频道")
	// 第一种接收消息方法
	// ch := pubsub.Channel()
	// for msg := range ch
	// 	fmt.Println(msg.Channel, msg.Payload)

	// 第二种接收消息方法
	for {
		msg, err := pubsub.ReceiveMessage()
		if err != nil {
			panic(err)
		}
		fmt.Println(msg.Channel, msg.Payload)
	}

	//匹配模式订阅
	pubsub = rdb.PSubscribe("mychannel*")
	defer pubsub.Close()

	//取消订阅
	pubsub.Unsubscribe("mychannel1", "mychannel2")
}

7. 事物操作

func TestRedisTx(t *testing.T) {
	//1.执行TxPipeline()开启事物,底层会执行MULTI命令开启事物块
	pipe := rdb.TxPipeline()

	//2.执行业务操作
	incr := pipe.Incr("tx_pipeline_counter")
	pipe.Expire("tx_pipeline_counter", time.Hour)

	//3.如果业务正常执行完毕,调用Exec()提交事物,底层会执行"EXEC"执行事物块中的所有命令
	_, err := pipe.Exec()
	fmt.Println(incr.Val(), err)

	//4.如果中间有异常可以执行Discard(),底层会执行"DISCARD"取消事务,放弃执行事务块内的所有命令
	pipe.Discard()

	//5.有些场景下需要配合WATCH
	//使用WATCH命令监视某个键,事物执行EXEC命令的这段时间内
	//如果有其他用户抢先对被监视的键进行了替换、更新、删除等操作,
	//可以选择提交或放弃事物
	key := "watch_count"
	err = rdb.Watch(func(tx *redis.Tx) error {
		n, err := tx.Get(key).Int()
		if err != nil && err != redis.Nil {
			return err
		}
		_, err = tx.Pipelined(func(pipe redis.Pipeliner) error {
			pipe.Set(key, n+1, 0)
			return nil
		})
		return err
	}, key)
}

8. 执行Lua脚本

  1. 从Redis2.6开始,内置了 Lua 解释器,可以使用EVAL命令对lua脚本进行求值。
  2. EVAL示例 EVAL script numkeys key [key …] arg [arg …]
  1. eval: 代表执行lua语言的命令
  2. script:一段可以在redis服务器运行的 lua5.1的脚本
  3. numkeys:用于指定健名参数的个数
  4. key [key …]:从 EVAL 的第三个参数开始算起,表示在脚本中所用到的那些 Redis 键(key)。这些键可以通过lua的全局变量KEYS数组获取,用 1 访问形式 KEYS[1],2 是 KEYS[2]
  5. arg [arg …]:附加参数,可以在lua中用全局变量 ARGV 数组访问,访问形式 ARGV[1],ARGC[2] 等
//示例: luaStr:= eval "return KEYS[1],KEYS[2],ARGV[1],ARGV[2]" 2 key1 key2 first second
// eval lua脚本 参数个数(假设参数个数=2) 参数1 参数2 参数1值 参数2值
var incrBy = redis.NewScript(`
local key = KEYS[1]
local change = ARGV[1]
local value = redis.call("GET", key)
if not value then
  value = 0
end
value = value + change
redis.call("SET", key, value)
return value
`)

var sum = redis.NewScript(`
local key = KEYS[1]
local sum = redis.call("GET", key)
if not sum then
  sum = 0
end
local num_arg = #ARGV
for i = 1, num_arg do
  sum = sum + ARGV[i]
end
redis.call("SET", key, sum)
return sum
`)

func TestRedisLua(t *testing.T) {
	fmt.Printf("# INCR BY\n")
	for _, change := range []int{+1, +5, 0} {
		num, err := incrBy.Run(rdb, []string{"my_counter"}, change).Int()
		if err != nil {
			panic(err)
		}
		fmt.Printf("incr by %d: %d\n", change, num)
	}

	sum, err := sum.Run(rdb, []string{"my_sum"}, 1, 2, 3).Int()
	if err != nil {
		panic(err)
	}
	fmt.Printf("sum is: %d\n", sum)
}

二. 基于redis实现分布式锁

  1. 为什么不使用setNx: setNx命令是向redis存储一个key,如果key存在则会存储失败返回0,这种情况下,就不能实现锁的重入了,并且不使用lua脚本,无法实现原子性判断锁是否超时问题
  2. 实现思路:
  1. 基于redis实现分布式锁,其原理是依赖redis单线程,原子性存储一个key
  2. 封装一个代表redis锁核心的上下文结构体,内部添加一个context.Context,通过context.Context解决锁超时问题
  3. 考虑锁重入,与释放错乱问题,锁key 对应的value是代表每一把锁得唯一编号
  4. 为保证原子性提供获取锁与释放锁的lua脚本

封装锁结构体

type RedisLock struct {
	ctx       context.Context //用来解决锁超时问题
	timeoutMs int //锁超时时间
	key       string //锁key,也就是redis存储时的key
	id        string //锁编号,也就是redis存储时的val
}

lua脚本

1.提供获取锁脚本, 释放锁脚本,

const (
	//加锁时执行lua脚本:
	//考虑锁的可重入性: 加锁时除了保存锁key,还需要保存代表每一把锁的id
	//在加锁时先get获取,如果该锁key以存在,说明是锁重入刷新ttl,
	//没有则set, 这种是可重入锁,防止在同一线程中多次获取锁而导致死锁发生
	
	//KEYS[1]: 表示锁key
	//ARGV[1]: 表示value
	//ARGV[2]: 表示过期时间
	lockCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
	redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2])
	return "OK"
else
	--注意:这里执行的是setnx,如果已存在插入失败返回0
	return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
end`

	//解锁时执行lua脚本
	//也就是删除指定锁key,必须先匹配id值,防止A超时后,B马上获取到锁,A的解锁把B的锁删了
	delCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
	return redis.call("DEL", KEYS[1])
else
	return 0
end`
)

加锁,释放锁

var (
	// 默认超时时间
	defaultTimeout = 500 * time.Millisecond
	// 重试间隔
	retryInterval = 10 * time.Millisecond
	// 上下文取消
	ErrContextCancel = errors.New("context cancel")
)

//1.初始化锁
func NewRedisLock(ctx context.Context, key string) *RedisLock {
	timeout := defaultTimeout
	if deadline, ok := ctx.Deadline(); ok {
		timeout = deadline.Sub(time.Now())
	}
	rl := &RedisLock{
		ctx:       ctx,
		timeoutMs: int(timeout.Milliseconds()),
		key:       key,
		id:        randomStr(),
	}
	return rl
}

//2.尝试加锁方法
func (rl *RedisLock) TryLock() (bool, error) {
	t := strconv.Itoa(rl.timeoutMs)
	resp, err := rdb.Eval(lockCommand, []string{rl.key}, []string{rl.id, t}).Result()
	if err != nil || resp == nil {
		return false, nil
	}
	reply, ok := resp.(string)
	return ok && reply == "OK", nil
}

//3.加锁方法
func (rl *RedisLock) Lock() error {
	for {
		select {
		case <-rl.ctx.Done():
			return ErrContextCancel
		default:
			//续时
			b, err := rl.TryLock()
			if err != nil {
				return err
			}
			if b {
				return nil
			}
			time.Sleep(retryInterval)
		}
	}
}

//4.是否锁方法
func (rl *RedisLock) Unlock() {
	rdb.Eval(delCommand, []string{rl.key}, []string{rl.id}).Result()
}

//5.生成锁编号
func randomStr() string {
	letters := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
	b := make([]byte, 10)
	for i := range b {
		b[i] = letters[rand.Intn(len(letters))]
	}
	return string(b)
}

运行测试

func BenchmarkRedisLock1(t *testing.B) {
	x := t.N
	wg := sync.WaitGroup{}
	wg.Add(x)
	now := time.Now()

	var n int64
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
	defer cancel()
	for i := 0; i < x; i++ {
		go func() {
			defer wg.Done()
			rl := NewRedisLock(ctx, "testLock")
			if err := rl.Lock(); err != nil {
				cancel() // 这里cancel,可以在第一个超时或者发生错误后,后边的不再尝试加锁。
				return
			}
			defer rl.Unlock()
			atomic.AddInt64(&n, 1)
		}()
	}
	wg.Wait()
	fmt.Printf("测试数:%d\t成功数:%d\t结果:%t\t耗时: %d \n", x, n, x == int(n), time.Since(now).Milliseconds())
}

三. bitmap使用示例

import (
	"fmt"
	"github.com/go-redis/redis"
	"os"
	"testing"
	"time"
)

// 声明一个全局的rdb变量
var rdb *redis.Client

func initClient() *redis.Client {
	rdb = redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "", // no password set
		DB:       0,  // use default DB
	})

	_, err := rdb.Ping().Result()
	if err != nil {
		fmt.Println("连接redis失败")
		os.Exit(1)
	}
	return rdb
}

//基于redis的bitMap,记录用户一个月内访问uv(既一天访问一次统计)
func TestRedisSetBitMap(t *testing.T) {
	//获取redis句柄
	redisDb := initClient()

	//1.组装key: 酒店编码:时间:用户id
	key := "IHOTEL:2023-03-27:111222AAASSS"

	now := time.Now()

	//2.存储为bitmap类型,key,所在月的天数为偏移量,value为1
	err := redisDb.Do("setbit", key, now.Day(), 1).Err()
	if nil != err {
		fmt.Printf("插入失败 err:%v", err)
	}

	//3.获取当天开始时间戳与本月最后一天的结束时间戳,计算实现时间
	startTime, _ := GetTimeStartAndEnd(now)
	lastTime := now.AddDate(0, 1, -1)
	_, endTime := GetTimeStartAndEnd(lastTime)
	var duration_Seconds time.Duration = time.Duration(endTime-startTime) * time.Second

	//4.设置失效时间
	err = redisDb.Expire(key, duration_Seconds).Err()
	if nil != err {
		fmt.Printf("设置失效时间异常 err: %v", err)
	}

	//5.统计指定key的访问量(bitcount也就是统计指定key的valu为1的数量)
	r, err := redisDb.Do("bitcount", key).Result()
	if nil != err {
		fmt.Printf("查询失败 err:%v", err)
		return
	}
	fmt.Printf("查询结果为 result:%v", r)
}

//获取指定time的开始时间戳与结束时间戳
func GetTimeStartAndEnd(NowTime time.Time) (int64, int64) {
	var startTime time.Time
	//NowTime := time.Date(2022, 9, 15, 0, 0, 0, 0, time.Local)
	if NowTime.Hour() == 0 && NowTime.Minute() == 0 && NowTime.Second() == 0 {
		startTime = time.Unix(NowTime.Unix()-86399, 0) //当天的最后一秒
	} else {
		startTime = time.Unix(NowTime.Unix()-86400, 0)
	}
	currentYear := startTime.Year()
	currentMonth := startTime.Month()
	currentDay := startTime.Day()
	yesterdayStartTime := time.Date(currentYear, currentMonth, currentDay, 0, 0, 0, 0, time.Local).Unix()
	yesterdayEndTime := time.Date(currentYear, currentMonth, currentDay, 23, 59, 59, 0, time.Local).Unix()
	return yesterdayStartTime, yesterdayEndTime
}

四. go-redis 生产使用示例

初始化连接

  1. 初始化连接
  1. 执行go get 命令拉取go-redis
  2. 读取配置参数调用NewClient()获取redis连接
import (
	"lmcd_siteserver/config"
	"lmcd_siteserver/log"
	"os"
	"github.com/go-redis/redis"
)

var (
	rClient = New()
)

func New() *redis.Client {

	log.TraceLog("Redis", "connect redis ...")
	address, err := config.Conf.GetValue("redis", "address")
	if err != nil {
		log.ErrorLog("Redis", "Load redis Error: read config value failed  redis address ")
		return nil
	}
	password, err := config.Conf.GetValue("redis", "password")
	if err != nil {
		log.ErrorLog("Redis", "Load redis Error: read config value failed  redis password ")
		return nil
	}
	database, err := config.Conf.Int("redis", "database")
	if err != nil {
		log.ErrorLog("Redis", "Load redis Error: read config value failed  redis password ")
		return nil
	}
	client := redis.NewClient(&redis.Options{
		Addr:     address,
		Password: password,
		DB:       database,
	})

	if client == nil {
		log.ErrorLog("Redis", "open redis failed %s ", address)
		os.Exit(0)
	}

	pong, err := client.Ping().Result()
	if pong == "PONG" {
		log.TraceLog("Redis", "connect redis OK")
	} else {
		log.ErrorLog("Redis", "connect redis  failed :"+err.Error())
		//os.Exit(0)
		return nil
	}
	return client
}

go-redis操作数据

import (
	"lmcd_siteserver/lmerror"
	"time"
)

func Set(key, value string, timeout int64) (lmerr *lmerror.LmError) {
	client := rClient
	if client == nil {
		return lmerror.NewLmError(lmerror.REDIS_CONNECT_ERROR, "redisdatabase:Set Error "+key+" | "+value)
	}
	var duration_Seconds time.Duration = time.Duration(timeout) * time.Second
	err := client.Set(key, value, duration_Seconds).Err()
	if err != nil {
		return lmerror.NewLmError(lmerror.REDIS_SET_ERROR, "redisdatabase:Set Error "+key+" | "+value+" | "+err.Error())
	}
	return nil
}

func Get(key string) (value string, lmerr *lmerror.LmError) {
	client := rClient
	if client == nil {
		return "", lmerror.NewLmError(lmerror.REDIS_CONNECT_ERROR, "redisdatabase:Set Error "+key)
	}

	searchvalue, err := client.Get(key).Result()
	if err != nil {
		return "", lmerror.NewLmError(lmerror.REDIS_GET_ERROR, "redisdatabase:Get Error "+key+" | "+err.Error())
	}
	return searchvalue, nil
}

func HGet(key string, index string) (value string, lmerr *lmerror.LmError) {
	client := rClient
	if client == nil {
		return "", lmerror.NewLmError(lmerror.REDIS_CONNECT_ERROR, "redisdatabase:Set Error "+key)
	}
	result, err := client.HGet(key, index).Result()
	if err != nil {
		return "", lmerror.NewLmError(lmerror.REDIS_HGET_ERROR, "redisdatabase:Get Error "+key+" | "+err.Error())
	}
	return result, nil
}

func HSet(key string, index string, value string, timeout int) (ret int, lmerr *lmerror.LmError) {
	client := rClient
	if client == nil {
		return 0, lmerror.NewLmError(lmerror.REDIS_CONNECT_ERROR, "redisdatabase:HSet Error "+key)
	}

	n, err := client.Do("hset", key, index, value).Result()
	if err != nil {
		return 0, lmerror.NewLmError(lmerror.REDIS_HGET_ERROR, "redisdatabase:HSet Error "+key+" | "+err.Error())
	}

	var duration_Seconds time.Duration = time.Duration(timeout) * time.Second
	err = client.Expire(key, duration_Seconds).Err()
	if err != nil {
		return 0, lmerror.NewLmError(lmerror.REDIS_SETTIME_ERROR, "redisdatabase:Expire Error "+key)
	}
	return int(n.(int64)), nil
}

func HGetAll(key string) (ret map[string]string, lmerr *lmerror.LmError) {
	client := rClient
	var result map[string]string
	if client == nil {
		return result, lmerror.NewLmError(lmerror.REDIS_CONNECT_ERROR, "redisdatabase:Set Error "+key)
	}
	result, err := client.HGetAll(key).Result()
	if err != nil {
		return result, lmerror.NewLmError(lmerror.REDIS_HGET_ERROR, "redisdatabase:Get Error "+key+" | "+err.Error())
	}
	return result, nil
}

func HMSet(key string, data map[string]interface{}, timeout int) (lmerr *lmerror.LmError) {
	client := rClient
	if client == nil {
		return lmerror.NewLmError(lmerror.REDIS_CONNECT_ERROR, "redisdatabase:Set Error "+key)
	}
	err := client.HMSet(key, data).Err()
	if err != nil {
		return lmerror.NewLmError(lmerror.REDIS_HSET_ERROR, "redisdatabase:Get Error "+key+" | "+err.Error())
	}

	var duration_Seconds time.Duration = time.Duration(timeout) * time.Second
	err = client.Expire(key, duration_Seconds).Err()
	if err != nil {
		return lmerror.NewLmError(lmerror.REDIS_SETTIME_ERROR, "redisdatabase:Expire Error "+key)
	}
	return nil
}

func Incr(key string) (int, *lmerror.LmError) {
	client := rClient
	if client == nil {
		return 0, lmerror.NewLmError(lmerror.REDIS_CONNECT_ERROR, "redisdatabase:Incr Error "+key)
	}

	ret, err := client.Incr(key).Result()
	if err != nil {
		client.Set(key, 10000, 0)
		return 0, nil
	}
	return int(ret), nil
}

你可能感兴趣的:(#,十一.,Go,常用三方库与常用工具,redis,golang,数据库)