24 小时内学会 Go 并做了一个项目发布到 Google 首页

未匹配的标注

本文为官方 Go Blog 的中文翻译,详见 翻译说明

雷纳尔多·阿吉亚尔(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(10950),image.Pt(166152)},
        “ i”:{image.Pt(13621),image.Pt(180131)},
        “ m”:{image.Pt(1597),image.Pt(201126)},
        “ r”:{image.Pt(18820),image.Pt(230125)},
        “ p”:{image.Pt(21648),image.Pt(258134)},
        “ f”:{image.Pt(155176),image.Pt(243213)},
        “ w”:{image.Pt(169118),image.Pt(250197)},
        “ b”:{image.Pt(105104),image.Pt(145148)}}
)

通过测量图像中每个布局元素的实际位置和大小,可以计算出以上各点的几何形状。

在每个请求上从磁盘加载图像都是重复的浪费,因此在收到第一个请求后,我们会将所有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。

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/go-blog/from-ze...

译文地址:https://learnku.com/docs/go-blog/from-ze...

上一篇 下一篇
Summer
贡献者:4
讨论数量: 0
发起讨论 只看当前版本


暂无话题~