24 小时内学会 Go 并做了一个项目发布到 Google 首页
雷纳尔多·阿吉亚尔(Reinaldo Aguiar)
2011年12月13日
介绍
本文由Google搜索团队的软件工程师Reinaldo Aguiar撰写。他分享了自己开发第一个Go程序并将其在一天之内发布给数百万观众的经验!
最近,我有机会合作进行一个小型但引人注目的“ 20%项目”:感恩节2011 Google Doodle。涂鸦的特色是火鸡,它是通过随机组合不同样式的头,翅膀,羽毛和腿而制成的。用户可以通过单击火鸡的不同部分来自定义它。这种交互性是通过JavaScript,CSS和HTML的结合在浏览器中实现的,从而动态地创建了火鸡。
一旦用户创建了个性化的火鸡,就可以通过发布到Google+与亲朋好友共享。点击“共享”按钮(此处未显示)在用户的Google+流中创建包含火鸡快照的帖子。快照是与用户创建的火鸡匹配的单个图像。
火鸡的8个部分(头部,成对的腿,不同的羽毛等)中的每一个都有13个替代项,可以生成超过8亿个可能的快照图像。预先计算所有这些显然是不可行的。相反,我们必须动态生成快照。将该问题与即时可扩展性和高可用性的需求结合在一起,显而易见的平台选择是:Google App Engine!
我们需要决定的下一件事是要使用哪个App Engine运行时。图像处理任务受CPU限制,因此在这种情况下,性能是决定因素。
为了做出明智的决定,我们进行了测试。我们很快为新的Python 2.7运行时 (提供了PIL,基于C的映像库)和Go运行时。每个应用程序都会生成由几个小图像组成的图像,将图像编码为JPEG,然后将JPEG数据作为HTTP响应发送。 Python 2.7应用程序处理请求的中值延迟为65毫秒,而Go应用程序运行的中值延迟仅为32毫秒。
因此,该问题似乎是尝试实验性Go运行时的绝佳机会。
我以前没有使用Go的经验,时间紧迫:两天就可以准备生产了。这令人生畏,但我认为这是从另一个经常被忽视的角度测试Go的机会:开发速度。没有go经验的人能多快地拾起并构建出可以执行和扩展的对象?
设计
该方法是在URL中对火鸡的状态进行编码,即时绘制和编码快照。
每个涂鸦的基础是背景:
有效的请求URL可能如下所示:google-turkey.appspot.com/thumb/203...
“/thumb/”后面的字母数字字符串(以十六进制表示)为每个布局元素绘制哪个选择,如下图所示:
程序的请求处理程序解析URL,以确定为每个组件选择了哪个元素,在背景图像的顶部绘制适当的图像,并将结果用作JPEG。
如果发生错误,则会提供默认图像。为错误页面提供服务毫无意义,因为用户将永远看不到它-浏览器几乎可以肯定地将此URL加载到图像标签中。
实现
在包装范围中,我们声明了一些数据结构来描述火鸡的元素,相应图像的位置以及应在背景图像上绘制的位置。
var(
// dirs将每个布局元素映射到其在磁盘上的位置。
dirs = map [string] string {
“ h”:“ img / heads”,
“ b”:“ img / eyes_beak”,
“ i”:“ img / index_feathers”,
“ m”:“ img / middle_feathers”,
“ r”:“ img / ring_feathers”,
“ p”:“ img / pinky_feathers”,
“ f”:“ img / feet”,
“ w”:“ img / wing”,
}
// urlMap将每个URL字符位置映射到
//其对应的布局元素。
urlMap = [...] string {“ b”,“ h”,“ i”,“ m”,“ r”,“ p”,“ f”,“ w”}
// layoutMap将每个布局元素映射到其位置
//在背景图片上。
layoutMap = map [string] image.Rectangle {
“ h”:{image.Pt(109,50),image.Pt(166,152)},
“ i”:{image.Pt(136,21),image.Pt(180,131)},
“ m”:{image.Pt(159,7),image.Pt(201,126)},
“ r”:{image.Pt(188,20),image.Pt(230,125)},
“ p”:{image.Pt(216,48),image.Pt(258,134)},
“ f”:{image.Pt(155,176),image.Pt(243,213)},
“ w”:{image.Pt(169,118),image.Pt(250,197)},
“ b”:{image.Pt(105,104),image.Pt(145,148)},
}
)
通过测量图像中每个布局元素的实际位置和大小,可以计算出以上各点的几何形状。
在每个请求上从磁盘加载图像都是重复的浪费,因此在收到第一个请求后,我们会将所有106个图像(13 * 8个元素+ 1个背景+ 1个默认值)加载到全局变量中。
var(
//元素会将每个布局元素映射到其图像。
elements = make(map [string] [] * image.RGBA)
// backgroundImage包含背景图像数据。
backgroundImage * image.RGBA
// defaultImage是发生错误时提供的图像。
defaultImage * image.RGBA
// loadOnce仅用于在第一个请求上调用load函数。
loadOnce同步一次
)
// load从磁盘读取各种PNG图像并将其存储在它们的
//对应的全局变量。
func load(){
defaultImage = loadPNG(defaultImageFile)
backgroundImage = loadPNG(backgroundImageFile)
for dirKey,dir:= range dirs {
path,错误:= filepath.Glob(dir +“ /*.png”)
如果err!= nil {
恐慌
}
对于_,p:=范围路径{
elements [dirKey] = append(elements [dirKey],loadPNG(p))
}
}
}
请求以简单的顺序处理:
-解析请求网址,解码路径中每个字符的十进制值。
-制作背景图像的副本作为最终图像的基础。
-使用layoutMap将每个图像元素绘制到背景图像上,以确定应在何处绘制它们。
-将图像编码为JPEG
-通过将JPEG直接写入HTTP响应编写器,将图像返回给用户。
如果发生任何错误,我们会将defaultImage提供给用户,并将错误记录到App Engine仪表板中以供以后分析。
这是带有解释性注释的请求处理程序的代码:
func handler(w http.ResponseWriter,r * http.Request){
// [[https://learnku.com/docs/go-blog/2010/08/defer-panic-and-recover.html][Defer]]从任何紧急情况中恢复的功能。
//从紧急状态恢复时,将错误情况记录到
// App Engine信息中心,然后将默认图片发送给用户。
defer func(){
if err:= recovery(); err!= nil {
c:= appengine.NewContext(r)
c.Errorf(“%s”,err)
c.Errorf(“%s”,“Traceback:%s”,r.RawURL)
if defaultImage!= nil {
w.Header()。Set(“ Content-type”,“ image / jpeg”)
jpeg.Encode(w,defaultImage,&imageQuality)
}
}
}()
//在第一个请求时从磁盘加载映像。
loadOnce.Do(load)
//复制要绘制的背景。
bgRect:= backgroundImage.Bounds()
m:= image.NewRGBA(bgRect.Dx(),bgRect.Dy())
draw.Draw(m,m.Bounds(),backgroundImage,image.ZP,draw.Over)
//处理请求字符串的每个字符。
code:= strings.ToLower(r.URL.Path [len(prefix):])
for i,p := range code{
//将十六进制字符p原地解码
if p <'a'{
//是数字
p = p-'0'
} else {
//是字母
p = p-'a'+ 10
}
t:= urlMap [i] //元素类型按索引
em:= elements [t] //元素图片类型
if p >= len(em){
panic(fmt.Sprintf("element index out of range %s:" +
“%d> =%d”,t,p,len(em)))
}
//将元素绘制到m
//使用layoutMap指定其位置。
draw.Draw(m,layoutMap [t],em [p],image.ZP,draw.Over)
}
//编码JPEG图像并将其作为响应写入。
w.Header()。Set(“ Content-type”,“ image / jpeg”)
w.Header()。Set(“ Cache-control”,“ public,max-age = 259200”)
jpeg.Encode(w,m,&imageQuality)
}
为简便起见,我从这些代码清单中省略了一些帮助器函数。有关完整的信息,请参见源代码。
性能
该图表直接从App Engine仪表板获取,显示了启动期间的平均请求延迟。如您所见,即使在负载下,它也不会超过60毫秒,中值延迟为32毫秒。考虑到我们的请求处理程序正在动态地进行图像处理和编码,这实在太快了。
结论
我发现Go的语法直观,简单,简洁。过去,我曾使用解释语言进行过很多工作,尽管Go是一种静态类型化和编译的语言,但编写此应用程序更像是使用动态解释语言。
带有SDK的开发服务器会在进行任何更改后迅速重新编译程序,因此我可以像使用解释语言一样快地进行迭代。这也非常简单-设置我的开发环境不到一分钟。
Go的出色文档也帮助我快速完成了这些工作。这些文档是从源代码生成的,因此每个函数的文档都直接链接到关联的源代码。这不仅使开发人员可以非常快速地了解特定功能的作用,还可以鼓励开发人员深入研究包的实现,从而更容易学习良好的样式和约定。
在编写此应用程序时,我仅使用了三种资源:App Engine的Hello World Go示例,[Go软件包文档](https :///golang.org/pkg/)和展示Draw软件包的博客文章。由于开发服务器和语言本身实现了快速迭代,因此我能够在不到24小时的时间内选择该语言并构建了一个超级快速,可投入生产的Doodle生成器。
在Google Code项目下载完整的应用程序源代码(包括图像)。
特别感谢设计了Doodle的Guillermo Real和Ryan Germick。
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: