Practique desde lo simple hasta la encapsulación de Redis con patrones de diseño

antecedentes

Al aprender el desarrollo de Golang, se debe introducir el uso de Redis para algunos escenarios. A continuación, se describe principalmente el proceso paso a paso para usar Redis. Comience simplemente creando un cliente de caché y luego optimice y encapsule un cliente de caché personalizado.

Presentamos Redis

Primero, obtenga el paquete de terceros redis go-redis, que admite la conexión al modo centinela y clúster Redis

go get -u github.com/go-redis/redis
复制代码

Defina un cliente Redis global RedisDB e inicialice el cliente de acuerdo con el ejemplo proporcionado por go-redis

package plugins
var RedisDB *redis.Client
func InitRedis() {
     RedisDB = redis.NewClient(&redis.Options{
     Addr: "localhost:6379",
     Password: ""
     DB: 1,
 })
    pong, err := RedisDB.Ping().Result()
    if err != nil {
        fmt.Println(pong, err)
    }
    fmt.Println("redis 连接成功")
}

// main.go 中使用
package main
func main(){
 plugins.InitRedis()
 plugins.RedisDB.set("key001","Hello World", time.Second * 60)
}
复制代码

Si se utiliza en un pequeño proyecto desarrollado por él mismo, no hay problema, pero si es un servicio de un producto, todavía hay mucho margen de optimización.

Encapsular operaciones de Redis

El conjunto de herramientas existente proporciona muchos métodos de almacenamiento en caché, pero es posible que no necesite demasiados, y algunos métodos pueden tener otras funciones que deben agregarse. Por lo tanto, puede encapsular un redis con solo sus propios métodos requeridos basados ​​​​en go-redis.

package cache
// 定义 MyRedis 结构体
type MyRedis struct {
    Client *redis.Client
}

var redisClient *MyRedis

// 封装 redis 实例,提供获取
func GetInstance() *MyRedis {
    return redisClient
}

// 使用单例模式进行封装
var once sync.Once

// 创建 redis 客户端,并给封装的Redis的客户端进行初始化
func NewMyRedis() *redis.Client {
    myRedis := redis.NewClient(&redis.Options{
        Addr:     "http://localhost",
        Password: "",
        DB:       1,
    })

    once.Do(func() {
        redisClient.Client = myRedis
    })

    return myRedis

}

// 自定义 Exist 方法
func (mr *MyRedis) Exist(key string) bool {
    if mr.Client.Get(key) != nil {
        return true
    }
    return false
}

// set 方法
func (mr *MyRedis) Set(key string, value interface{}, duration time.Duration) bool {
    result := mr.Client.Set(key, value, duration)
    if result.Err() != nil {
        return false
    }
    return true
}
复制代码

1. Defina una estructura MyRedis que contenga el cliente redis. MyRedis implementa su propio cliente redis basado en go-redis y vincula su propio método de operación redis a la estructura

2. Cree la función NewMyRedis para crear una instancia de redis. La instancia creada se usa para inicializar MyRedis, que se encapsula con sync.Once para evitar la concurrencia.

3. Cree una función GetInstance para obtener la instancia personalizada de MyRedis

Los pasos anteriores completan la encapsulación de su propia operación Redis. Al usarlo, debe establecer una conexión Redis para completar el trabajo de inicialización de Redis, y luego puede obtener una instancia de MyRedis durante la aplicación y realizar operaciones relacionadas directamente. La implementación detallada es la siguiente:

// main.go  使用封装后的 redis
package main
func main(){
    // 项目启动时初始化 redis 
    cache.NewMyRedis()
    pong, err := rclient.Ping().Result()
    if err != nil {
        fmt.Println(pong, err)
    }
    fmt.Println("redis 连接成功")
}

// 其他 package 中使用
package user
func getUser() {
    result := cache.GetInstance().Exist("user_001")
    if !result {
        fmt.Println("不存在该数据")
    }
}
复制代码

El modo proxy encapsula el cliente de caché

Cuando cambien los requisitos, espero admitir una variedad de cachés, como Redis, memcached, etc., espero admitir una variedad de productos de caché. El uso del modo proxy se puede optimizar aún más para mejorar aún más la escalabilidad del código.

2022-07-14-08-03-58-image.png

modo proxy

Modo proxy: proxy el control y acceso de clases concretas definiendo una clase proxy

Composición del modo proxy:

  • Clase de sujeto abstracto: define el método que implementará un objeto de sujeto específico a través de una interfaz o clase abstracta
  • 具体主题类:实现了抽象主题类的具体方法,是最终代理类要引用的具体类
  • 代理类:提供对外的代理类,内部引用具体的类,它可以访问、控制或扩展真实主题的功能
// 定义缓存接口,提供自定义的 get、set 方法
type ICache interface {
    Set(key string, value interface{}, ttl time.Duration)
    Get(key string) interface{}
}

type CacheRedis struct {
    Client *redis.Client
}

var credis *CacheRedis

func NewCacheRedis() *redis.Client {
    myRedis := redis.NewClient(&redis.Options{
        Addr:     "http://localhost",
        Password: "",
        DB:       1,
    })
    once.Do(func() {
        credis.Client = myRedis
    })

    return myRedis
}

func (credis *CacheRedis) Set(key string, value interface{}, ttl time.Duration) {
    credis.Client.Set(key, value, ttl)
}
func (credis CacheRedis) Get(key string) interface{} {
    return credis.Client.Get(key)
}

type CacheLocal struct {
    Client *redis.Client
}

func (credis *CacheLocal) Set(key string, value interface{}, ttl time.Duration) {
    credis.Client.Set(key, value, ttl)
}
func (credis CacheLocal) Get(key string) interface{} {
    return credis.Client.Get(key)
}
复制代码
  1. 定义了一个 ICache 接口 ,其中定义了缓存客户端需要进行哪些操作

  2. 分别定义了2种具体的缓存类,实现了 ICache 接口。同时也需要提供初始化这2种缓存Client 的函数

  3. 创建一个对外的代理类 CacheClient。其中增加了一个 InitInstance 函数 和 getDriver方法。

    • InitInstance 函数主要用于根据类型进行初始化缓存实例。这里进行了升级,和普通的代理模式不同,这里通过类型进行判断进行不同的缓存实例,从而实现根据不同的类型使用不同的缓存实例
    • getDriver 方法,通过 getDriver 实现动态代理,代理的过程由该方法来判断使用何种缓存,继而调用其真正的缓存方法。
type CLIENT_TYPE string

const (
    CACHE_REDIS CLIENT_TYPE = "CACHE_REDIS"
    CACHE_LOCAL             = "CACHE_LOCAL"
)

type CacheClient struct {
    ClientType CLIENT_TYPE
    CRedis     *CacheRedis
    CLocal     *CacheLocal
}

var CacheCli *CacheClient
var ccache *CacheRedis
var lcache *CacheLocal

// 初始化缓存 cli 的函数,用于项目程序启动时使用
func InitInstance(clientType CLIENT_TYPE) {
    CacheCli.ClientType = clientType
    switch clientType {
    case CACHE_LOCAL:
        CacheCli.CRedis.Client = NewCacheRedis()
    case CACHE_REDIS:
        CacheCli.CLocal.Client = NewCacheRedis()
    default:
        break
    }
}

func (client *CacheClient) getDriver() ICache {
    if client.CLocal.Client != nil {
        return client.CLocal
    }
    return client.CRedis
}

func (credis *CacheClient) Set(key string, value interface{}, ttl time.Duration) {
    credis.getDriver().Set(key, value, ttl)
}
func (credis *CacheClient) Get(key string) interface{} {
    return credis.getDriver().Get(key)
}
复制代码

以上是使用过程中定义的源代码,具体的在main 函数中使用如下:

在项目启动时初始化缓存 Cli,然后就可以使用结构体为 CacheClient 的代理缓存 Cli:CacheCli 进行redis 操作。

func main(){
   // 根据缓存类型初始化缓存 Cli
  cache.InitInstance(cache.CACHE_REDIS)

  // 使用结构体为 CacheClient 的代理缓存 Cli:CacheCli 进行redis 操作
  data := cache.CacheCli.Get("hhh001_11") 
  fmt.Println(data)
}
复制代码

代理模式的优势:

  • 主要起到一个中介作用,对外屏蔽具体目标对象的实现。
  • 在一定程度上降低了系统的耦合度,具体目标对象内部方法业务变更不影响调用者
  • 增加程序的可扩展性,当需要扩展多个目标对象时可以增加目标类型,增加目标类型所对应的对象的实现,然后代理类只需要在初始化时通过不同类型初始化不同的目标对象。

扩展思考

1、思考点:代理模式是否还可以继续优化?

应该还有需要优化的点。暂未想到,欢迎留言指点。

2、思考点:当作一种设计模版,使用于其他的中间件,比如数据库,消息队列,存储服务?

代理模式这种方式适用于多个目标对象的,然后根据不同需求动态切换不同的目标对象。比如多种数据库的时候,可以通过 config 配置数据库类型,动态的初始化对应的数据库。也可以代码上控制不同的类型动态切换不同的数据源。

3、思考点:其他方式实现?

暂时没有想到其他的方式封装 Redis 。如果你有很好的建议可以留言,让我学习学习。

参考资料:

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿

Supongo que te gusta

Origin juejin.im/post/7120107593854353416
Recomendado
Clasificación