Practice from simple to encapsulating Redis with design patterns

background

When learning Golang development, the use of Redis needs to be introduced for some scenarios. The following mainly describes the step-by-step process for using Redis. Start by simply creating a cache client, and then further optimize and encapsulate a custom cache client.

Introducing Redis

First get the redis third-party package go-redis, which supports connection to sentinel and cluster mode Redis

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

Define a global Redis client RedisDB, and initialize the client according to the example provided by 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)
}
复制代码

If it is used in a small project developed by itself, there is no problem, but if it is a service of a product, there is still a lot of room for optimization.

Encapsulate Redis operations

The existing toolkit provides a lot of caching methods, but I may not need too many, and some methods may have other functions that need to be added. So you can encapsulate a redis with only its own required methods based on 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. Define a MyRedis structure that contains the redis client. MyRedis implements its own redis client based on go-redis, and binds its own redis operation method to the structure

2. Create the NewMyRedis function to create a redis instance. The created instance is used to initialize MyRedis, which is encapsulated with sync.Once to avoid concurrency.

3. Create a GetInstance function to get the custom MyRedis instance

The above steps complete the encapsulation of your own Redis operation. When using it, you need to establish a redis connection to complete the work of initializing redis, and then you can obtain an instance of MyRedis during application and perform related operations directly. The detailed implementation is as follows:

// 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("不存在该数据")
    }
}
复制代码

Proxy mode encapsulates the cache client

When the requirements change, I hope to support a variety of caches, such as Redis, memcached, etc., I hope to support a variety of cache products. The use of proxy mode can be further optimized to further enhance the scalability of the code.

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

proxy mode

Proxy mode: proxy the control and access of concrete classes by defining a proxy class

Proxy mode composition:

  • Abstract subject class: Define the method to be implemented by a specific subject object through an interface or abstract class
  • 具体主题类:实现了抽象主题类的具体方法,是最终代理类要引用的具体类
  • 代理类:提供对外的代理类,内部引用具体的类,它可以访问、控制或扩展真实主题的功能
// 定义缓存接口,提供自定义的 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 。如果你有很好的建议可以留言,让我学习学习。

参考资料:

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

Guess you like

Origin juejin.im/post/7120107593854353416