两个方式管理基础服务连接

举报
码乐 发表于 2024/02/13 08:48:19 2024/02/13
【摘要】 2.0 管理数据连接,两个方式这里简单介绍管理连接的两个方式。我们建立这个缓存的链接管理对象,假如我们已经实现了客户端对象redis.client type Caches struct { Client *redis.Client } func (db *Caches) GetConn() (*redis.Conn, context.Conte...

2.0 管理数据连接,两个方式

这里简单介绍管理连接的两个方式。

我们建立这个缓存的链接管理对象,假如我们已经实现了客户端对象redis.client

    type Caches struct {
        Client       *redis.Client
    }

    func (db *Caches) GetConn() (*redis.Conn, context.Context) {
	   return db.Client.Conn(Ctx), Ctx
    }

当然,我们还可以把sql数据库也一并集中在一起, 全部数据库

	type DBMangers struct {
		DBPgsql *pgxpool.Pool
		DBRedis *Caches
	}

2.0.1 方式一 这里我们直接为缓存操作提供基本方法

	type RedisOp struct {
		*Caches
	}

	func NewRedisOp(nc *Caches) *RedisOp {
		newCash := RedisOp{nc}
		return &newCash
	}

然后我们提供方法应对基础指令 SET,GET,DEL

	func (db *RedisOp) Set(Ctx context.Context, key string, value string) error {

		err := db.Client.Set(Ctx, key, value, 0).Err()
		if err != nil {
			log.Panic(err) 
		}
		log.Println(key, value, " set success exist")
		return nil
	}

	func (db *RedisOp) Get(Ctx context.Context,key string) string {

		val, err := db.Client.Get(Ctx, key).Result()
		if err == redis.Nil {
			log.Println(key, " does not exist")
			log.Panic(err)
			return ""
		} else if err != nil {
			panic(err)
		} else {
			log.Println(key, " : ", val)
		}

		return fmt.Sprintf("%v", val)
	}

	func (db *RedisOp) Del(Ctx context.Context,key string) error {

		err := db.Client.Del(Ctx, key).Err()
		if err != nil {
			log.Panic(err) 
		}
		log.Println(key, " del success ")
		return nil
	}

这种方式是直接对redis 的操作进行的封装,它让用户提供指令和内容,最终执行,如果失败则直接退出。

也可以使用一个统一入口 Do,对任意的key操作进行分别操作,可以做某些优化。

	func (db *RedisOp) Do(Ctx context.Context,key string, value string) error {

		if key == "set" {
			return db.Set(Ctx,key  , value  )
		} else if key == "get" {
			return db.Get(Ctx,key  , value  )
		} else if key == "del" {
			return db.Del(Ctx,key  , value  )
		}
		 
		msg := fmt.Errorf( " key:%v not support.",key)
		return msg
	}

2.1 方式二 使用接口

一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口

interface 是一种类型,从它的定义可以看出来用了 type 关键字,更准确的说 interface 是一种具有一组方法的类型,这些方法定义了 interface 的行为

interface可以是一组method的集合,是duck-type programming的一种体现(不关心属性(数据),只关心行为(方法))。
我们可以自己定义interface类型的struct,并提供方法。

go 允许不带任何方法的 interface ,这种类型的 interface 叫 empty interface。

空接口 interface{} 本身可以表示任何类型,因此它其实就是一个全量的泛型,我们可以结合反射在运行时对实际传入的参数做类型检查,让泛型变得可控,从而确保程序的健壮性,否则很容易因为传递进来的参数类型不合法导致程序崩溃。

如果一个类型实现了一个 interface 中所有方法,必须是所有的方法,我们说类型实现了该 interface,所以所有类型都实现了 empty interface,因为任何一种类型至少实现了 0 个方法。

go 没有显式的关键字用来实现 interface,只需要实现 interface 包含的方法即可。

interface还可以作为返回值使用。 判断interface变量存储的是哪种类型。 比如下:

    type HashTable interface {
		Write(p []byte) (n int, err error)
		NewSum(b []byte) []byte
	}

	//使用基础类
	func NewHashTable() HashTable {
		return &Base{}
	}

	//使用子类
	func NewHashWriter() HashTable {
		return &ChWriter{}
	}

使用基类可以使用全部函数 和 组合的类的函数

    //使用Reader子类 全部方法
	func NewReader() Reader {
		return &ChReader{}
	}

	type error interface {
		Error() string
	}


	type ChReader struct{ Base }

	func (b *ChReader) Read(p []byte) (n int, err error) { return 2, nil }

	func (b *ChReader) ReportError() string { return "err" }

使用接口,如果方法没有封装入接口,则不能使用全部函数,如反馈错误

     //使用接口只有子类函数, 
     fmt.Println(r1.Read([]byte{}))
     //读者不能反馈问题
     //fmt.Println(r1.ReportError())

使用基类可以反应全部错误

 fmt.Println("struct of Reader:")
 //使用本类可以使用全部函数 和 组合的类的函数
 r2 := ChReader{}
 //读者反应有错误
 fmt.Println(r2.ReportError())
 fmt.Println(r2.NewSum([]byte{}))

2.1.1 使用接口提供对外的业务方法

日常中使用interface,有时候需要判断原来是什么类型的值转成了interface。一般如下方式:
这里的接口是一组方法定义,任意实现了这一组方法的结构体都可以称其为对其的实现。

      type HashTable interface {
                      Write(p []byte) (n int, err error)
                      NewSum(b []byte) []byte
               }

一个类型通常会满足许多接口,每个接口对应于它的方法一个子集
比如任何满足接口的类型HashTable也满足Writer接口

              type Writer interface {
                   Write(p []byte) (n int, err error)
                }

go的系统各个部分以交互方式随着它的增长而适应。
如下Reader接口,Write任何具有此方法的项目都可以与Reader接口互补一起使用:

               type Reader interface { 
                 Read(p []byte) (n int, err error) 
                 }

go 没有专门的错误处理语句 try except
需要预定义一个接口类型 error,它表示一个具有Error返回字符串的方法的值

                      type error interface {
                         Error() string
                       }

我们打算对业务逻辑进行部分保留和选择性的暴露,使得用户可以知道他们正在做什么事情。

     type cacheRepo struct {
		Client *Caches
	}
            
 var Ctx = context.Background()
     type CacheRepo interface {  
        //基础操作
        Set(key, value string, ttl time.Duration, options ...Option) error
        Get(key string, options ...Option) (string, error)
        MGet(keys []string, options ...Option) ([]interface{}, error)
        TTL(key string) (time.Duration, error)
        Expire(key string, ttl time.Duration) bool
        ExpireAt(key string, ttl time.Time) bool 

        //有序集操作 创建操作的流水线管道等
        ZaddArgs(k string, kv string, options ...Option) ([]string, error)
        PipelinedCreate(priKeys string, ex map[string]string, options ...Option) ([]any, error)
        ZRange(k string, start, end int64, options ...Option) ([]string, error)
        ZCard(k string, options ...Option) (int64, error)
        ZRangeScoreByindex(k string, start, end int64, options ...Option) ([]redis.Z, error)
	}

在具体函数调用执行时,我们跟踪错误并回溯它们

    if opt.Trace != nil {
				opt.Redis.Timestamp = timeutil.CSTLayoutString()
				opt.Redis.Handle = "set"
				opt.Redis.Key = key
				opt.Redis.Value = value
				opt.Redis.TTL = ttl.Minutes()
				opt.Redis.CostSeconds = time.Since(ts).Seconds()
				opt.Trace.AppendRedis(opt.Redis)
			}

然后调用内部方法去执行

   if err := c.Client.set(Ctx, key, value, ttl).Err(); err != nil {
			return errors.Wrapf(err, "redis set key: %s err", key)
		}
		...

如果从第一章开始看到现在的朋友,一定不会陌生这个方式.

3 小结

实际使用时有很多特殊问题需要处理,比如如何分离大键,如何模拟关系数据的数据管理方式。也有异常问题 如缓存值批量过期和查询数据不存在的问题。 实际解决方案如只保留单向通道,从db到缓存,使用不同的过期方式。

有兴趣的可以看到示例

  https://github.com/hahamx/examples/blob/main/tcps/3_with_tcp_service/with_interface.go
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。