123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535 |
- package redis
- import (
- "context"
- "encoding/json"
- "errors"
- "hash/crc32"
- "log"
- "math"
- "regexp"
- "strconv"
- "strings"
- "time"
- "github.com/go-redis/redis/v8"
- )
- //goredis 工具类对象
- type GoRedis struct {
- //Password string
- Code string
- DB int //默认库
- DBS []int //数据库列表
- Nodes []int //节点列表
- HashDb int //是否是多个数据库 0单库 1单节点多库 2多节点单库 3多节点多库
- Ctx context.Context
- CMap map[int]map[int]*redis.Client
- BakDb bool //是否有备用节点
- Bak *GoRedis //备用节点连接
- }
- //code并未实现,目前不支持多个code使用
- //初始化单个"[other=]127.0.0.1:2203[|#]127.0.0.1:2204[/0]=0-1=1-10=300" [代码]、地址[|备用地址[/代表默认库][#多节点地址,默认库都为0]、库、最大池、空闲时间默认300秒
- /*
- 示例
- 127.0.0.1:2203
- 127.0.0.1:2203=2=15 默认库为2,最大连接为15,最小连接是0.2*15为3
- other=127.0.0.1:2203 带code配置
- other=127.0.0.1:2203=0=10
- other=127.0.0.1:2203=0=10=300 空闲时间300秒
- other=127.0.0.1:2203=0-8=10=300 带code配置,使用多个库即0 1 2 3 4 5 6 7 8,最大连接为10,最小连接是0.2*10为2,空闲时间300秒
- other=127.0.0.1:2203=0-8=1-10=300 带code配置,使用多个库即0 1 2 3 4 5 6 7 8,最大连接为10,最小连接1,空闲时间300秒
- other=127.0.0.1:2203|127.0.0.1:2204=0-8=10=300 带code配置,使用备用节点(仅供取数据时使用),使用多个库即0 1 2 3 4 5 6 7 8,最大连接为10,最小连接1,空闲时间300秒
- other=127.0.0.1:2203|127.0.0.1:2204/0=0-8=10=300 带code配置,使用备用节点(仅供取数据时使用,备用节点默认库为0),使用多个库即0 1 2 3 4 5 6 7 8,最大连接为10,最小连接1,空闲时间300秒
- other=127.0.0.1:2203#127.0.0.1:2204#127.0.0.1:2205#127.0.0.1:2206=0=10=300 带code配置,使用多节点模式(多节点所有默认库为0),最大连接为10,最小连接2,空闲时间300秒
- 注意:
- 1、当使用多节点时,用正则获取keys时,再用Get取模获取数据时,一定要保障放入和取出是同一个hashcode,否则数据永远取不到
- */
- //解析配置并初始化,opt为string或map
- func (r *GoRedis) Init(opt interface{}) {
- check := false
- //代码 地址 []库范围 []池配置 空闲时间
- code, addr, dbs, pool, idle := "", "", []int{0}, []int{2, 30}, 300
- if so, ok := opt.(string); ok {
- arr := strings.Split(so, "=")
- regAddr := regexp.MustCompile("[0-9.a-zA-Z/]+:[0-9]+.*")
- if len(arr) == 1 { //只是一个串
- if regAddr.MatchString(arr[0]) {
- check = true
- addr = arr[0]
- }
- } else if len(arr) > 1 {
- index := 0
- if regAddr.MatchString(arr[0]) { //第1个是地址
- index = 1
- addr = arr[0]
- check = true
- } else if regAddr.MatchString(arr[1]) { //第二个是地址
- check = true
- addr = arr[1]
- code = arr[0]
- }
- //解析库配置
- if len(arr) > 2-index { //dbs配置
- dbs1 := strings.Split(arr[2-index], "-") //库范围的配置
- if len(dbs1) == 1 || len(dbs1) == 2 {
- check = true
- dbs[0], _ = strconv.Atoi(dbs1[0])
- if len(dbs1) == 2 {
- tmp, _ := strconv.Atoi(dbs1[1])
- dbs = append(dbs, tmp)
- }
- } else {
- check = false
- }
- //解析连接池配置
- if len(arr) > 3-index {
- pool1 := strings.Split(arr[3-index], "-")
- if len(pool1) == 1 || len(pool1) == 2 {
- check = true
- if len(pool1) == 1 {
- pool[1], _ = strconv.Atoi(pool1[0])
- pool[0] = int(math.Ceil(float64(pool[1]) * 0.2))
- } else {
- pool[0], _ = strconv.Atoi(pool1[0])
- pool[1], _ = strconv.Atoi(pool1[1])
- }
- } else {
- check = false
- }
- //解析最大空闲时间配置
- if len(arr) > 4-index {
- idle, _ = strconv.Atoi(arr[4-index])
- if idle == 0 {
- idle = 300
- }
- }
- }
- }
- }
- } else if _, ok := opt.(map[string]interface{}); ok {
- }
- if check {
- log.Println("代码 地址 []库范围 []池配置 空闲时间", code, addr, dbs, pool, idle)
- addrs := strings.Split(addr, "|") //备用节点模式
- if len(addrs) == 2 { //备用节点的模式 2选1
- r.init(addrs[0], code, dbs, pool[1], pool[0], idle)
- r.BakDb = true
- //有备用节点,集群先不考虑,支持指定的库
- addr1 := strings.Split(addrs[1], "/")
- r.Bak = &GoRedis{}
- if len(addr1) == 2 { //没有指定库 根据/区分
- i, _ := strconv.Atoi(addr1[1])
- dbs = []int{i}
- }
- r.Bak.init(addr1[0], code, dbs, pool[1], pool[0], idle)
- } else if len(addrs) == 1 {
- addr1 := strings.Split(addrs[0], "#") //是多节点模式,所有库默认为0
- if len(addr1) == 1 {
- r.init(addr1[0], code, dbs, pool[1], pool[0], idle)
- } else { //多节点模式
- r.Code = code
- r.HashDb = 1 + len(dbs)
- if len(dbs) > 1 {
- r.DBS = []int{}
- for i := dbs[0]; i <= dbs[1]; i++ {
- r.DBS = append(r.DBS, i)
- }
- } else {
- r.DBS = dbs
- }
- r.DB = dbs[0]
- r.Ctx = context.Background()
- r.CMap = map[int]map[int]*redis.Client{}
- r.Nodes = []int{}
- for i := 0; i < len(addr1); i++ {
- r.Nodes = append(r.Nodes, i)
- r.CMap[i] = map[int]*redis.Client{}
- for k, v := range r.DBS { //按节点的每个库初始化
- r.CMap[i][k] = redis.NewClient(&redis.Options{
- Addr: addr1[i],
- DB: v,
- PoolSize: pool[1],
- MinIdleConns: pool[0],
- IdleTimeout: time.Duration(idle) * time.Second,
- })
- }
- }
- }
- }
- }
- log.Println(check, code, addr, dbs, pool, idle, r.DBS)
- }
- //初始化连接
- func (r *GoRedis) init(addr, code string, dbs []int, poolSize, minIdleConns, idleTimeOut int) {
- r.Code = code
- r.DB = dbs[0]
- r.Ctx = context.Background()
- r.CMap = map[int]map[int]*redis.Client{}
- r.CMap[0] = map[int]*redis.Client{}
- if len(dbs) > 1 {
- r.HashDb = len(dbs) - 1
- r.DBS = []int{}
- for i := dbs[0]; i <= dbs[1]; i++ {
- r.DBS = append(r.DBS, i)
- }
- } else {
- r.DBS = dbs
- }
- for k, v := range r.DBS { //按节点的每个库初始化
- r.CMap[0][k] = redis.NewClient(&redis.Options{
- Addr: addr,
- DB: v,
- PoolSize: poolSize,
- MinIdleConns: minIdleConns,
- IdleTimeout: time.Duration(idleTimeOut) * time.Second,
- })
- }
- }
- //int转timeDuration
- func D(t int) time.Duration {
- return time.Duration(t) * time.Second
- }
- //取db
- func (r *GoRedis) GetDB(key string) (int, int) {
- switch r.HashDb {
- case 0: //单节点单库
- return 0, 0
- case 1: //单节点多库
- return 0, hashCode(key) % len(r.DBS)
- case 2: //多节点单库
- return hashCode(key[len(key)/2:]) % len(r.Nodes), 0
- case 3: ////多节点多库
- return hashCode(key[len(key)/2:]) % len(r.Nodes), hashCode(key) % len(r.DBS)
- }
- // if r.HashDb {
- // //return int(key[len(key)-1]) % len(r.DBS)
- // return hashCode(key) % len(r.DBS)
- // } else {
- // return r.DB
- // }
- return 0, 0
- }
- //根据key取hash
- func hashCode(key string) int {
- v := int(crc32.ChecksumIEEE([]byte(key)))
- if v < 0 {
- v = -v
- }
- return v
- }
- func (r *GoRedis) Client(key string) *redis.Client {
- _i, _k := r.GetDB(key)
- m := r.CMap[_i]
- if m != nil {
- c := m[_k]
- if c != nil {
- return c
- }
- }
- return &redis.Client{}
- }
- //-----具体方法
- //简单的Put方法
- func (r *GoRedis) Put(key string, val interface{}) (string, error) {
- stutsCmd := r.Client(key).Set(r.Ctx, key, val, 0)
- str, err := stutsCmd.Result()
- return str, err
- }
- //存key,加过期时间
- func (r *GoRedis) Set(key string, val interface{}, timeout int) (string, error) {
- stutsCmd := r.Client(key).Set(r.Ctx, key, val, D(timeout))
- //cmd := r.CMap[r.GetDB(key)].Do(r.Ctx, "setex", key, timeout, val)
- str, err := stutsCmd.Result()
- return str, err
- }
- //简单的Get方法,无结果返回空串,err: redis: nil
- func (r *GoRedis) Get(key string) (string, error) {
- stutsCmd := r.Client(key).Get(r.Ctx, key)
- str, err := stutsCmd.Result()
- // log.Println("1", str, "-", err)
- if err != nil {
- //有备用节点
- if r.BakDb {
- str, err = r.Bak.Get(key)
- }
- }
- return str, err
- }
- //根据正则取key,未考虑负载、多库
- func (r *GoRedis) GetByPattern(key string) (res []string, err error) {
- serr := ""
- for _, v1 := range r.CMap {
- for _, v := range v1 {
- strSlice := v.Keys(r.Ctx, key)
- arr, err1 := strSlice.Result()
- if len(arr) > 0 {
- res = append(res, arr...)
- } else if err1 != nil {
- serr += err1.Error()
- }
- }
- }
- if serr != "" {
- err = errors.New(serr)
- }
- return
- }
- //批量保存数据
- // 不支持- MSet("key1", "value1", "key2", "value2")
- // 不支持- MSet([]string{"key1", "value1", "key2", "value2"})
- // []interface{[]interface{k1,v1},[]interface{k2,v2},[]interface{k3,v3} }
- // - MSet(map[string]interface{}{"key1": "value1", "key2": "value2"})
- func (r *GoRedis) BulkPut(timeout int, obj interface{}) {
- if r.HashDb > 0 {
- if timeout < 1 {
- timeout = 0
- }
- timeEx := time.Duration(timeout) * time.Second
- switch obj.(type) {
- case []interface{}:
- if objs, ok := obj.([]interface{}); ok {
- for _, _tmp := range objs {
- tmp, ok := _tmp.([]interface{})
- if ok && len(tmp) == 2 {
- key, ok1 := tmp[0].(string)
- if ok1 {
- r.Client(key).Set(r.Ctx, key, tmp[1], timeEx)
- }
- }
- }
- }
- case map[string]interface{}:
- if objs, ok := obj.(map[string]interface{}); ok {
- for k, v := range objs {
- r.Client(k).Set(r.Ctx, k, v, timeEx)
- }
- }
- }
- } else {
- //单库直接保存
- if timeout < 1 {
- stutsCmd := r.CMap[0][0].MSet(r.Ctx, obj)
- str, err := stutsCmd.Result()
- log.Println(str, err)
- } else {
- timeEx := time.Duration(timeout) * time.Second
- //设置超时
- lenth := 0
- cmds, err := r.CMap[0][0].Pipelined(r.Ctx, func(pipe redis.Pipeliner) error {
- if objs, ok := obj.([]interface{}); ok {
- lenth = len(objs)
- for _, _tmp := range objs {
- tmp, ok := _tmp.([]interface{})
- if ok && len(tmp) == 2 {
- key, ok1 := tmp[0].(string)
- if ok1 {
- pipe.Set(r.Ctx, key, tmp[1], timeEx)
- }
- }
- }
- } else if objs, ok := obj.(map[string]interface{}); ok {
- lenth = len(objs)
- for k, v := range objs {
- pipe.Set(r.Ctx, k, v, timeEx)
- }
- } else {
- log.Println("bulkPut type error")
- }
- return nil
- })
- if err != nil {
- log.Println("bulkPut error", err.Error())
- }
- if len(cmds) == lenth { //数据相等
- for _, v := range cmds {
- log.Println("cmd", v.Args(), v.String())
- }
- }
- }
- }
- }
- //设置超时时间,单位秒
- func (r *GoRedis) SetExpire(key string, expire int) error {
- boolCmd := r.Client(key).Expire(r.Ctx, key, D(expire))
- return boolCmd.Err()
- }
- //判断一个key是否存在
- func (r *GoRedis) Exists(key string) bool {
- intCmd := r.Client(key).Exists(r.Ctx, key)
- i, err := intCmd.Result()
- if err != nil {
- log.Println("redisutil-exists", key, err)
- }
- return i == 1
- }
- //直接返回字节流
- func (r *GoRedis) GetBytes(key string) (ret *[]byte, err error) {
- cmd := r.Client(key).Do(r.Ctx, "GET", key)
- res, err := cmd.Result()
- log.Println(res)
- if err != nil {
- log.Println("redisutil-GetBytesError", err)
- } else {
- if tmp, ok := res.([]byte); ok {
- ret = &tmp
- } else if tmp, ok := res.(string); ok {
- bs := []byte(tmp)
- ret = &bs
- } else {
- err = errors.New("redis返回数据格式不对")
- }
- }
- return
- }
- //支持删除多个key
- func (r *GoRedis) Del(key ...string) (b bool) {
- i := 0
- if r.HashDb > 0 {
- for _, k := range key {
- cmd := r.Client(k).Del(r.Ctx, k)
- i1, _ := cmd.Result()
- i += int(i1)
- }
- } else {
- intCmd := r.CMap[0][0].Del(r.Ctx, key...)
- i1, _ := intCmd.Result()
- i = int(i1)
- }
- return i == len(key)
- }
- //根据代码和前辍key删除多个
- func (r *GoRedis) DelByPattern(key string) {
- res, _ := r.GetByPattern(key)
- if len(res) > 0 {
- r.Del(res...)
- }
- }
- //自增计数器
- func (r *GoRedis) Incr(key string) (int64, error) {
- intCmd := r.Client(key).Incr(r.Ctx, key)
- i, err := intCmd.Result()
- return i, err
- }
- //自减
- func (r *GoRedis) Decrby(key string, val int) (int64, error) {
- intCmd := r.Client(key).DecrBy(r.Ctx, key, int64(val))
- i, err := intCmd.Result()
- return i, err
- }
- //批量取多个key
- func (r *GoRedis) Mget(key []string) []interface{} {
- if r.HashDb > 0 {
- mdb := map[int]map[int][]string{}
- for _, v := range key { //分组
- _i, _k := r.GetDB(v)
- mdb[_i] = map[int][]string{}
- arr := mdb[_i][_k]
- if arr == nil {
- arr = []string{v}
- } else {
- arr = append(arr, v)
- }
- mdb[_i][_k] = arr
- }
- res := []interface{}{}
- for k, v := range mdb {
- for k1, v1 := range v {
- sliceCmd := r.CMap[k][k1].MGet(r.Ctx, v1...)
- res1, err := sliceCmd.Result()
- if err != nil {
- log.Println("Mget error", err)
- }
- res = append(res, res1...)
- }
- }
- return res
- } else {
- sliceCmd := r.CMap[0][0].MGet(r.Ctx, key...)
- res, err := sliceCmd.Result()
- if err != nil {
- log.Println("Mget error", err)
- }
- return res
- }
- }
- //取出并删除Key
- func (r *GoRedis) Pop(key string) (result interface{}) {
- c := r.Client(key)
- strCmd := c.Get(r.Ctx, key)
- b, err := strCmd.Bytes()
- if err != nil {
- log.Println("Poperr bytes", err)
- return
- }
- err1 := json.Unmarshal(b, &result)
- if err1 != nil {
- log.Println("Poperr json ", err)
- return
- } else {
- go c.Del(r.Ctx, key)
- }
- return
- }
- //list操作
- func (r *GoRedis) LPOP(list string) (result interface{}) {
- strCmd := r.Client(list).LPop(r.Ctx, list)
- b, err := strCmd.Bytes()
- if err != nil {
- log.Println("LPOP bytes", err)
- return
- }
- err1 := json.Unmarshal(b, &result)
- if err1 != nil {
- log.Println("LPOP json ", err)
- return
- }
- return
- }
- //将一个或多个值插入到列表的尾部
- func (r *GoRedis) RPUSH(list string, val ...interface{}) bool {
- intCmd := r.Client(list).RPush(r.Ctx, list, val...)
- i, err := intCmd.Result()
- if err != nil {
- log.Println("RPUSH bytes", err)
- return false
- }
- return i == 1
- }
- //获取列表长度
- func (r *GoRedis) LLEN(list string) int64 {
- intCmd := r.Client(list).LLen(r.Ctx, list)
- i, err := intCmd.Result()
- if err != nil {
- log.Println("RPUSH bytes", err)
- }
- return i
- }
|