实现一个简单的图片API,Go vs Java
缘由
需求:做一个随机图片API,图片来源于Minio,使用S3协议访问。
原来是使用熟悉的Java写的,工程化的代码(因为后面想加上管理功能),尝试了一下打包docker镜像,几百兆。
之后就研究了一下如何精简,后来突然意识到,这么简单的功能,为什么要这么折腾呢。然后就撸起了Go。
顺便做一下对比!
对比
场景:连接minio,获取图片列表,每次访问随机返回随机一个可访问图片的url。
Java:工程化,Java17,Spring-boot,minio官方包,hutool-core
代码就不放了,一个接口,一个controller,service,dao(操作Minio),
引入依赖如下:
java
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.9</version>
</dependency>
</dependencies>
Go:单文件,gin,minio官方包,Go一些系统库,依赖如下:
go
import (
"context"
"fmt"
"github.com/minio/minio-go/v7/pkg/credentials"
"io/ioutil"
"log"
"math/rand"
"net/http"
"os"
"sync"
"time"
"github.com/gin-gonic/gin"
"github.com/minio/minio-go/v7"
)
go完整代码如下:代码大部分为AI生成,因为我还不熟悉Go语言
go
package main
import (
"context"
"fmt"
"github.com/minio/minio-go/v7/pkg/credentials"
"log"
"math/rand"
"net/http"
"os"
"sync"
"time"
"github.com/gin-gonic/gin"
"github.com/minio/minio-go/v7"
)
// S3Config 用于存储S3服务的配置信息
type S3Config struct {
Endpoint string
AccessKeyID string
SecretAccessKey string
BucketName string
}
var s3Config S3Config
var minioClient *minio.Client
var cache = NewCache(5*time.Minute, loadData)
type Cache struct {
mu sync.Mutex
data []string // 假设我们缓存的数据是字符串列表
expiry time.Time
timeout time.Duration
loadFunc func() []string
}
// 加载配置
func init() {
s3Config.Endpoint = os.Getenv("SHE_API_S3_ENDPOINT")
s3Config.AccessKeyID = os.Getenv("SHE_API_S3_ACCESS_KEY_ID")
s3Config.SecretAccessKey = os.Getenv("SHE_API_S3_SECRET_ACCESS_KEY")
s3Config.BucketName = os.Getenv("SHE_API_S3_BUCKET_NAME")
// 初始化MinIO客户端
var err error
minioClient, err = minio.New(s3Config.Endpoint, &minio.Options{
Creds: credentials.NewStaticV4(s3Config.AccessKeyID, s3Config.SecretAccessKey, ""),
Secure: true,
})
if err != nil {
log.Fatalln(err)
}
fmt.Println("MinIO Client has been initialized successfully.")
// 列出所有存储桶
buckets, err := minioClient.ListBuckets(context.Background())
if err != nil {
log.Fatalln(err)
}
for _, bucket := range buckets {
fmt.Println(bucket.Name)
}
loadData()
}
// NewCache 创建一个新的缓存实例
func NewCache(timeout time.Duration, loadFunc func() []string) *Cache {
return &Cache{
timeout: timeout,
loadFunc: loadFunc,
}
}
// 随机从字符串切片中获取一个元素
func getRandomImgUrl() string {
slice := cache.GetData()
randomIndex := rand.Intn(len(slice))
path := slice[randomIndex]
url := fmt.Sprintf("https://%s/%s/%s", s3Config.Endpoint, s3Config.BucketName, path)
return url
}
func main() {
startTime := time.Now() // 记录启动时刻
r := gin.Default()
// 获取随机图片路由
r.GET("/ir", func(c *gin.Context) {
url := getRandomImgUrl()
c.Redirect(http.StatusFound, url)
})
r.GET("/i", func(c *gin.Context) {
// 从请求中获取图片的URL
imageURL := getRandomImgUrl()
// 使用HTTP GET请求下载图片
resp, err := http.Get(imageURL)
if err != nil {
c.String(http.StatusInternalServerError, fmt.Sprintf("Failed to download image: %s", err))
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
c.String(http.StatusInternalServerError, fmt.Sprintf("Failed to download image, status code: %d", resp.StatusCode))
return
}
// 设置正确的Content-Type
contentType := resp.Header.Get("Content-Type")
if contentType == "" {
contentType = "application/octet-stream"
}
// 直接将响应流传递给客户端
c.DataFromReader(http.StatusOK, resp.ContentLength, contentType, resp.Body, nil)
})
fmt.Println("启动耗时:", time.Since(startTime))
// 启动服务器
if err := r.Run("127.0.0.1:8081"); err != nil {
log.Fatalf("Failed to run server: %v", err)
}
}
// GetData 获取缓存的数据,如果数据过期或不存在,则重新加载
func (c *Cache) GetData() []string {
c.mu.Lock()
defer c.mu.Unlock()
// 检查数据是否过期
if time.Now().After(c.expiry) {
c.data = c.loadFunc() // 重新调用加载函数
c.expiry = time.Now().Add(c.timeout) // 更新过期时间
}
return c.data
}
// 加兹安数据
func loadData() []string {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
var objects []string
objectCh := minioClient.ListObjects(ctx, s3Config.BucketName, minio.ListObjectsOptions{Prefix: "img", Recursive: true})
for object := range objectCh {
if object.Err != nil {
log.Printf("Error listing objects: %v", object.Err)
break
}
objects = append(objects, object.Key)
}
return objects
}
测试方法
使用Apifox进行测试。Apifox - API 文档、调试、Mock、测试一体化协作平台。
本地idea运行
语言 | 编译时间 | 启动时间 |
---|---|---|
Java | 3.274 s | 1.873 s |
Go | 1.439s | 504.9µs |
Java接口结果
Go接口测试结果
docker镜像运行
Java构建镜像方法:使用Spring boot带的Maven插件,Spring-boot:build-image,默认参数,仅修改了镜像名
Go:使用go-zero的生成工具goctl,zeromicro/go-zero: A cloud-native Go microservices framework with cli tool for productivity. (github.com)
pgsql
# go安装goctl
go install github.com/zeromicro/go-zero/tools/goctl@latest
# 生成dockerfile
goctl docker --go main.go
# 生成镜像
docker build -t xieken/she-api:1.0 .
# 之后在docker desktop 推送镜像,在服务器部署
语言 | 镜像构建耗时 | 镜像大小 | 运行内存 |
---|---|---|---|
Java | 03:19 min 第二次41.948 s | 319MB | 270MB |
Go | 9.8s | 9.09MB | 13MB |
go构建镜像耗时
go镜像运行
java镜像运行
sorry,图片丢失了