Skip to content

实现一个简单的图片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、测试一体化协作平台。

image

本地idea运行

语言编译时间启动时间
Java3.274 s1.873 s
Go1.439s504.9µs

Java接口结果

image

Go接口测试结果

image

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 推送镜像,在服务器部署
语言镜像构建耗时镜像大小运行内存
Java03:19 min 第二次41.948 s319MB270MB
Go9.8s9.09MB13MB

go构建镜像耗时

image

go镜像运行

image

java镜像运行

sorry,图片丢失了

​​

最后更新: