之前一直在用qiniu的存储服务,生成图片的缩略图,模糊图,视频的webp,现在需要把存储移到s3上,那么这些图片,视频处理就要自己动手写了,本文梳理一下大致的思路。
分析需求
先看一下qiniu的接口是如何处理图片的,例如先截取视频第一秒的图片,再把图片缩略,最后存储到一个新的key,命令可以这么写 vframe/jpg/offset/1|imageMogr2/thumbnail/400x|saveas/xxx, 可以看到三个操作之间用 | 符号分割,类似unix 的 pipe 操作。
上面的操作算作一个cmd, 一次API请求可以同时处理多个cmd,cmd之间用分号分割, 处理完毕后,在回调中把处理结果返回,例如
{ "id": "xxxxx", "pipeline": "xxx", "code": 0, "desc": "The fop was completed successfully", "reqid": "xTsAAFnxUbR5J10U", "inputBucket": "xxx", "inputKey": "xxxxx", "items": [ { "cmd": "vframe/jpg/offset/1|imageMogr2/thumbnail/400x|saveas/ZmFtZS1wcml2YXRlOm1vbWVudC9jb3Zlci9zbmFwL3ZpZGVvL2M5YzdjZjQ5LTU3NGQtNGZjMS1iZDFkLTRkYjZkMzlkZWY1Ni8wLzA=", "code": 0, "desc": "The fop was completed successfully", "hash": "FhdN6V8EI4vW4XJGALSfxutvMEIv", "key": "xx", "returnOld": 0 }, { "cmd": "vframe/jpg/offset/1|imageMogr2/thumbnail/400x|imageMogr2/blur/45x8|saveas/ZmFtZS1wcml2YXRlOm1vbWVudC9jb3Zlci9zbmFwL3ZpZGVvL2M5YzdjZjQ5LTU3NGQtNGZjMS1iZDFkLTRkYjZkMzlkZWY1Ni8wLzBfYmx1cg==", "code": 0, "desc": "The fop was completed successfully", "hash": "FgNiRzrCsa7TZx1xVSb_4d5TiaK3", "key": "xxx", "returnOld": 0 } ] }
分解需求
这个程序大致需要这么几个部分:
一个http接口,接受任务,接受后把任务扔到队列,返回一个job ID。 worker异步处理任务,worker的个数 和 每个worker 并行的处理的个数 能够配置,worker有重试机制。
从 job payload 中解析出需要做的任务,解析出每个cmd, 最好能并行执行每一个 cmd, 记录每一个cmd的结果
每个cmd中有多个 operation, 并且用 pipe 连接,前一个operaion的输出是后一个operation的输入
可以把 1 和 2,3 分开来看,1 比较独立,之前写过一个worker的模型,参考的是这篇文章 Handling 1 Million Requests per Minute with Go,比较详细,是用 go channel 作为queue的,我加了一个 beanstalk 作为 queue的 providor。还有一点改进是,文章中只提供了worker数量的设置,我再加了一个参数,设定每个worker可以并行执行的协程数。所以下面主要讲讲3, 2的解决办法
Pipe
可以参考这个库 pipe, 用法如下:
p := pipe.Line( pipe.ReadFile("test.png"), resize(300, 300), blur(0.5), )output, err := pipe.CombinedOutput(p) if err != nil { fmt.Printf("%v\n", err) }
buf := bytes.NewBuffer(output) img, _ := imaging.Decode(buf)
imaging.Save(img, "test_a.png")
还是比较方便的,建一个 Cmd struct, 利用正则匹配一下每个 Operation 的参数,放入一个 []Op slice, 最后执行,struct和方法如下:
type Cmd struct { cmd string saveas string ops []Op err error }type Op interface { getPipe() pipe.Pipe }
type ResizeOp struct { width, height int }
func (c ResizeOp) getPipe() pipe.Pipe { return resize(c.width, c.height) }
//使用方法 cmdStr := `file/test.png|thumbnail/x300|blur/20x8` cmd := Cmd{cmdStr, "test_b.png", nil, nil}
cmd.parse() cmd.doOps() sync.WaitGroup
单个cmd处理解决后,就是多个cmd的并行问题,没啥好想的,直接用 sync.WaitGroup 就可以完美解决。一步一步来,我们先看看这个struct的使用方法:
func main() { cmds := []string{} for i := 0; i < 10000; i++ { cmds = append(cmds, fmt.Sprintf("cmd-%d", i)) }results := handleCmds(cmds)
fmt.Println(len(results)) // 10000 }
func doCmd(cmd string) string { return fmt.Sprintf("cmd=%s", cmd) }
func handleCmds(cmds []string) (results []string) { fmt.Println(len(cmds)) //10000 var count uint64
group := sync.WaitGroup{} lock := sync.Mutex{} for _, item := range cmds { // 计数加一 group.Add(1) go func(cmd string) { result := doCmd(cmd) atomic.AddUint64(&count, 1)
lock.Lock() results = append(results, result) lock.Unlock() // 计数减一 group.Done() }(item) }
// 阻塞 group.Wait()
fmt.Printf("count=%d \n", count) // 10000 return }
group本质大概是一个计数器,计数 > 0时, group.Wait() 会阻塞,直到 计数 == 0. 这里还有一点要注意,就是 results = append(results, result) 的操作是线程不安全的,清楚这里 results 是共享的,需要加锁来保证同步,否则最后 len(results) 不为 10000。
我们建一个BenchCmd, 来存放 cmds. 如下:
type BenchCmd struct { cmds []Cmd waitGroup sync.WaitGroup errs []error lock sync.Mutex }func (b *BenchCmd) doCmds() { for _, item := range b.cmds { b.waitGroup.Add(1)
go func(cmd Cmd) { cmd.parse() err := cmd.doOps()
b.lock.Lock() b.errs = append(b.errs, err) b.lock.Unlock()
b.waitGroup.Done() }(item) }
b.waitGroup.Wait() }
最后的调用就像这样:
var cmds []Cmd cmd_a := Cmd{`file/test.png|thumbnail/x300|blur/20x8`, "test_a.png", nil, nil} cmd_b := Cmd{`file/test.png|thumbnail/500x1000|blur/20x108`, "test_b.png", nil, nil} cmd_c := Cmd{`file/test.png|thumbnail/300x300`, "test_c.png", nil, nil}cmds = append(cmds, cmd_a) cmds = append(cmds, cmd_b) cmds = append(cmds, cmd_c)
bench := BenchCmd{ cmds: cmds, waitGroup: sync.WaitGroup{}, lock: sync.Mutex{}, }
bench.doCmds()
fmt.Println(bench.errs)
这只是一个初级的实验,思考还不够全面,并且只是模仿API,qiniu应该不是这么做的,耦合更低,可能各个Cmd都有各自处理的集群,那pipe这个库就暂时没法解决了,目前的局限在于 每个Cmd必须都在一个进程中。
七牛提供了强大的图片处理功能,ThinkCMF内部提供了七牛良好的支持,只要在后台"文件存储"里进行简单的设置就可以把全站的图片上传七牛了,前台使用七牛的强大api就可以对图片进行各种处理,如放大缩小,缩略图,加水印等. ThinkCMF内部保存的文件路径是相对路径,假如你在七牛空间有个图片访问地址是http://78re52.com1.z0.glb.clouddn.com/resource/go
本文向大家介绍使用Javascript简单实现图片无缝滚动,包括了使用Javascript简单实现图片无缝滚动的使用技巧和注意事项,需要的朋友参考一下 js无缝滚动效果几乎在任何网页上都能看到它的身影,有的可能是使用插件,其实使用原始的javascript比较简单。 主要的是使用js位置知识。 1.innerHTML:设置或获取元素的html标签 2.scrollLeft:设置或获取位于对象左边界
本文向大家介绍php实现的通用图片处理类,包括了php实现的通用图片处理类的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了php实现的通用图片处理类。分享给大家供大家参考。具体如下: 该图片处理函数功能:缩放、剪切、相框、水印、锐化、旋转、翻转、透明度、反色,处理并保存历史记录的思路:当有图片有改动时自动生成一张新图片,命名方式可以考虑在原图片的基础上加上步骤,例如:图片名称+__第几步。
本文向大家介绍C++ HLSL实现简单的图像处理功能,包括了C++ HLSL实现简单的图像处理功能的使用技巧和注意事项,需要的朋友参考一下 由于对于dxva2解码得到的数据不宜copy回内存给CPU处理,所以最好的办法是在GPU上直接进行处理。D3D的像素着色器能够对像素直接进行操作,实现点运算极其简单方便,简单的卷积运算效果也非常好。但D3D9的限制也很多,对于过于复杂的图像处理则显得有些不能胜
本文向大家介绍javascript简单实现图片预加载,包括了javascript简单实现图片预加载的使用技巧和注意事项,需要的朋友参考一下 简单的图片预加载 reloader.js 具体用法如上,小伙伴们可以发挥你的想象力,自由扩展。
本文向大家介绍PHP图片处理之使用imagecopyresampled函数实现图片缩放例子,包括了PHP图片处理之使用imagecopyresampled函数实现图片缩放例子的使用技巧和注意事项,需要的朋友参考一下 网站优化不能只定在代码上,内容也是网站最需要优化的对象之一,而图像又是网站中最主要的内容。图像的优化最需要处理的就是将所有上传到网站中的大图片自动缩放称小图(在网页中大小够用就行),以
本文向大家介绍使用wxpython实现的一个简单图片浏览器实例,包括了使用wxpython实现的一个简单图片浏览器实例的使用技巧和注意事项,需要的朋友参考一下 上次我爬了n多图片,但是浏览的时候有一个问题。 图片浏览器的浏览一般都是按名称排的,而我对图片的命名是按照数字递增的。比如3总是会排在10后面,也就无法快速地浏览图片了。 所以,出于方便自己查阅图片,也出于学习,决定做一个自己的图片浏览器。
本文向大家介绍jQuery实现简单的图片查看器,包括了jQuery实现简单的图片查看器的使用技巧和注意事项,需要的朋友参考一下 项目中自己diy了一个图片查看器。因为初始代码不是自己的,只是在上面改了一下也没有弄的很漂亮。等以后有时间了在重写一下样式和封装,作为备用的只是积累吧。如果有童鞋有用到,完全可以在此基础上改,比较容易,代码也比较简单 图片查看器主要有几个功能: 1.显示图片和图片信息(