目录

在ChatGPT 帮助下我利用golang和redis 快速实现了一个缓存服务解决方案

Abstract: 在实际业务中经常用redis 作为二级缓存来提升系统的性能,尤其对一些耗 时但变化频率很低的操作会有巨大收益。同时,redis也可以很方便地将单机demo 快速转换 为分布式架构,在部署上也极其舒适。本文利用ChatGPT快速实现了一个简单的函数缓存功能。

需求介绍

最近需要对我的爬虫程序进行一些重构和分析,其中一个需求是为每次 request 分配并记 录相应的 User-Agent, Referer, Proxy 等信息,并且为了分布式架构的扩展性,最好可以 通过网络通讯进行。思考了一下这个需求在要求不高的情况下其实可以被等价为一个缓存服 务。考虑到未来缓存服务对我的数据库读写模块也意义重大,所以就决定通过缓存服务来实 现上述功能。

为什么重复造轮子

其实无论是我要的功能还是缓存服务,都能找到一些现成的包来实现。但是,我认为,在 ChatGPT 时代重复造轮子的意义已经不同了。

事实上,大多数开源的软件包都存在一些隐性的问题,往往需要使用过程中才能发现。如果 你习惯于python 调包,对这些可能感受不深反而还觉的方便。但当你的需求变得越来越定 制化的时候,就会发现调包会引入相应包的技术债务,并且这些问题往往不是一个习惯调包 的人可以很容易解决的。更重要的是,每个包的使用都有其学习成本。这些学习开销本可以 更低,因为你通常只需要一个包里的某几项功能,但却不得不学习全部。

ChatGPT 已经开始深刻影响程序员的开发工作流。对像我这样喜欢理解算法原理后自己动手 实现以避免一些技术债务并增加掌控力的人来说,一些简单的功能利用ChagGPT 仅仅通过编 程语言而非第三方包来实现,反而比我去读文档教程来的更快。这一过程中往往也能学到很 多。

使用ChatGPT

我正在构造和优化自己的ChatGPT 工作流。但这篇文章里不涉及这些,因为问题真的很简单, 仅仅对话就能够实现。下面是我跟 ChatGPT 的对话

GPT 给出的方案

可以看到在简单描述了问题后,GPT 几乎给出了一个可以直接用的版本。

/ox-hugo/cache1.png /ox-hugo/cache2.png /ox-hugo/cache3.png /ox-hugo/cache4.png /ox-hugo/cache5.png

修改和测试

我对代码进行了修改然后就可以正常跑了。准确来说,我真正修改的bug 只有一行!就是删 除了 import 中的 strconv. 因为 strconv 虽然被 import 但没有在程序中使用。

我们要知道,这个错误是因为 golang 编译时进行了严格的格式审查,熟悉python 的同学 一定明白这在python 中根本不算作错误。

下面是我修改后的版本,除了上面说的以外,我进行了一些参数的修改方便进行测试。

package main

import (
    "context"
    "fmt"
    "time"

    "github.com/go-redis/redis/v8"
)

var (
    redisClient *redis.Client
    ctx         = context.Background()
)

func init() {
    redisClient = redis.NewClient(&redis.Options{
        Addr:     "localhost:16379", // redis服务地址
        Password: "",               // redis连接密码
        DB:       0,                // redis数据库编号
    })
}

// GetRequestHeader 你需要缓存的函数
func GetRequestHeader(url, referer, userAgent string) string {
    // 这里是实现你需要缓存的函数的代码逻辑
    fmt.Println("The function is called...")
    time.Sleep(10 * time.Second)

    // 返回该函数的结果
    return fmt.Sprintf("url=%s, referer=%s, userAgent=%s", url, referer, userAgent)
}

// CacheFunc 基于redis缓存的函数执行结果
func CacheFunc(fn func(string, string, string) string, expire time.Duration) func(string, string, string) string {
    return func(url, referer, userAgent string) string {
        key := fmt.Sprintf("%s_%s_%s", url, referer, userAgent)
        res, err := redisClient.Get(ctx, key).Result()
        if err == redis.Nil {
            // 缓存中没有命中,则执行函数并缓存结果
            res = fn(url, referer, userAgent)
            if err := redisClient.Set(ctx, key, res, expire).Err(); err != nil {
                panic(err)
            }
        } else if err != nil {
            panic(err)
        }
        return res
    }
}

func main() {
    // 缓存有效期,可以根据实际情况进行调整
    expire := 5 * time.Second

    // 基于redis缓存函数执行结果
    cachedFunc := CacheFunc(GetRequestHeader, expire)

    // 调用函数
    url := "https://www.example.com"
    referer := "https://www.google.com"
    userAgent := "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"
    res1 := cachedFunc(url, referer, userAgent)
    fmt.Println("Result 1:", res1)

    // 等待一段时间,再次调用函数
    time.Sleep(0 * time.Second)
    res2 := cachedFunc(url, referer, userAgent)
    fmt.Println("Result 2:", res2)

    // 等待一段时间,再次调用函数
    time.Sleep(6 * time.Second)
    res3 := cachedFunc(url, referer, userAgent)
    fmt.Println("Result 3:", res3)
}

其运行结果是

The function is called...
Result 1: url=https://www.example.com, referer=https://www.google.com... (太长被我删除。。)
Result 2: url=https://www.example.com, referer=https://www.google.com...
The function is called...
Result 3: url=https://www.example.com, referer=https://www.google.com...

基于现有第三方库的实现

在简单调研之后(其实就是问ChatGPT。。。), 我找到了一个第三方包 gocache 可以实现类似 的功能。我问ChatGPT 是否可以基于 gocache 实现上面的功能,得到了如下结果。

这里我就不再继续进行解释了,因为这一版本我没有采用

/ox-hugo/cache6.png /ox-hugo/cache7.png /ox-hugo/cache8.png /ox-hugo/cache9.png

第三方还是自己实现?

上面提供的两个版本,各自的优缺点是什么?我直接问了ChatGPT.

ChatGPT 做了一些总结,答案看起来有一点似是而非了,质量相比代码生成有所不如。这也是 ChatGPT 的特点。

但其中有一点比较戳中我,让我选择第一版,就是基于 GoCatch 的版本会增加系统的复杂 性。这跟我一开始的初衷是吻合的。 /ox-hugo/cache10.png /ox-hugo/cache11.png

总结

其实整个程序,前后我花了大概5分钟就完成并且跑通了。整体程序也很简单,很容易理解 也容易作为组件整合到应用中。后续的文章中我会介绍如何将上面的程序模块化并部署为网 络服务,从而方便作为组件或者中间件集成到我的系统中。

我们看到,如果我使用第三方库,5分钟大概能让我找到两三个可能适用的库。之后我需要 花更多的时间去读文档并测试其符合我的需求。如果我对这一 cache 组件的要求很高的话, 第三方库可能是更好的选择,但如果我只需要基本功能,ChatGPT 可以帮我做得更快更好。

这是ChatGPT 时代给我的思考,技术的潮流影响会比很多人认为的还要巨大,未来的许多工 作流程和思路都需要做相应转变。你准备好了吗?