关于我的图床

我的图床网址是 i.imoe.xyz ,在优化前每日下载流量大约为20G左右,优化后下载流量大约5G~6G,为了节省流量成本同时节约存储空间,咱就开始了对图床的优化~

一些方案

首先我希望可以根据用户访问图片时携带的Accept头信息判断用户的浏览器可以展示什么样子的图片,例如 image/webp, image/avif 等,对于新版本浏览器,这个header应该类似于这样

1
image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8

可以看到avif和webp都是支持的,这个时候只需要在服务端简单判断一下是否包含 image/avifimage/webp 就足够了,这个时候只需要服务端对不同accept的访问请求返回不同的图片格式即可

实际使用过程中会发现对于大图片来说webp编码或许可以在几秒钟内完成,但是avif可能会需要数十秒,这就是下面会讲到的内容了

这里需要额外注意一下的是CDN可能没办法很好的根据Accept请求头做缓存,所以这里我的方案是使用302跳转,图片原始链接不会被CDN缓存,转码跳转后的图片才会被CDN缓存

离线转码

对于avif这一类转码时间可能会比较长的格式来说,可以使用离线任务的方式进行处理,具体方法如下

在用户访问的时候尝试同时转换出avif和webp格式,若avif转码超过指定时常则直接返回webp格式(若webp转码太久也可以直接返回原图),这个时候就可以直接把avif/webp的转码任务放进队列中,进行离线处理

同时对于用户新上传的图片,可以直接提前转码为webp格式,webp格式的兼容性已经可以适配大部分设备/浏览器了,可以通过这种方式加快访问速度的同时节约很大一部分硬盘空间

限流

由于大部分转码任务都会出现在图片被访问的时候,这个时候如果同时出现了一大堆访问,会导致服务器CPU爆满,所以这里需要对请求进行限流,咱这里使用的redis配合lua同时实现了锁和限流,锁的作用是对于同一个文件只允许出现一个转码任务,限流则是保护服务器不被冲爆

加锁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
local count = 0
-- 计算锁的数量
for _, key in ipairs(redis.call("keys", "fileserver:lock:*")) do
count = count + 1
end

-- 大于5则拿不到锁
if count > 5 then
return nil
end

-- 拿锁
if redis.call("exists", KEYS[1]) == 0 then
redis.call("set", KEYS[1], 1, "EX", ARGV[1])
return 1
end

return nil

释放锁:

1
2
3
4
5
if redis.call("exists", KEYS[1]) == 1 then
redis.call("del", KEYS[1])
return 1
end
return nil

结尾

最后就是hmm,欢迎大家来用我的图床(?)

大概做的优化还挺多的w