1. 程式人生 > >From zero to Go: launching on the Google homepage in 24 hours

From zero to Go: launching on the Google homepage in 24 hours

13 December 2011

Introduction

This article was written by Reinaldo Aguiar, a software engineer from the Search team at Google. He shares his experience developing his first Go program and launching it to an audience of millions - all in one day!

I was recently given the opportunity to collaborate on a small but highly visible "20% project": the

Thanksgiving 2011 Google Doodle. The doodle features a turkey produced by randomly combining different styles of head, wings, feathers and legs. The user can customize it by clicking on the different parts of the turkey. This interactivity is implemented in the browser by a combination of JavaScript, CSS and of course HTML, creating turkeys on the fly.

Once the user has created a personalized turkey it can be shared with friends and family by posting to Google+. Clicking a "Share" button (not pictured here) creates in the user's Google+ stream a post containing a snapshot of the turkey. The snapshot is a single image that matches the turkey the user created.

With 13 alternatives for each of 8 parts of the turkey (heads, pairs of legs, distinct feathers, etc.) there are more than than 800 million possible snapshot images that could be generated. To pre-compute them all is clearly infeasible. Instead, we must generate the snapshots on the fly. Combining that problem with a need for immediate scalability and high availability, the choice of platform is obvious: Google App Engine!

The next thing we needed to decide was which App Engine runtime to use. Image manipulation tasks are CPU-bound, so performance is the deciding factor in this case.

To make an informed decision we ran a test. We quickly prepared a couple of equivalent demo apps for the new Python 2.7 runtime (which provides PIL, a C-based imaging library) and the Go runtime. Each app generates an image composed of several small images, encodes the image as a JPEG, and sends the JPEG data as the HTTP response. The Python 2.7 app served requests with a median latency of 65 milliseconds, while the Go app ran with a median latency of just 32 milliseconds.

This problem therefore seemed the perfect opportunity to try the experimental Go runtime.

I had no previous experience with Go and the timeline was tight: two days to be production ready. This was intimidating, but I saw it as an opportunity to test Go from a different, often overlooked angle: development velocity. How fast can a person with no Go experience pick it up and build something that performs and scales?

Design

The approach was to encode the state of the turkey in the URL, drawing and encoding the snapshot on the fly.

The base for every doodle is the background:

A valid request URL might look like this: http://google-turkey.appspot.com/thumb/20332620][http://google-turkey.appspot.com/thumb/20332620

The alphanumeric string that follows "/thumb/" indicates (in hexadecimal) which choice to draw for each layout element, as illustrated by this image:

The program's request handler parses the URL to determine which element is selected for each component, draws the appropriate images on top of the background image, and serves the result as a JPEG.

If an error occurs, a default image is served. There's no point serving an error page because the user will never see it - the browser is almost certainly loading this URL into an image tag.

Implementation

In the package scope we declare some data structures to describe the elements of the turkey, the location of the corresponding images, and where they should be drawn on the background image.

var (
    // dirs maps each layout element to its location on disk.
    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 maps each URL character position to
    // its corresponding layout element.
    urlMap = [...]string{"b", "h", "i", "m", "r", "p", "f", "w"}

    // layoutMap maps each layout element to its position
    // on the background image.
    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)},
    }
)

The geometry of the points above was calculated by measuring the actual location and size of each layout element within the image.

Loading the images from disk on each request would be wasteful repetition, so we load all 106 images (13 * 8 elements + 1 background + 1 default) into global variables upon receipt of the first request.

var (
    // elements maps each layout element to its images.
    elements = make(map[string][]*image.RGBA)

    // backgroundImage contains the background image data.
    backgroundImage *image.RGBA

    // defaultImage is the image that is served if an error occurs. 
    defaultImage *image.RGBA

    // loadOnce is used to call the load function only on the first request.
    loadOnce sync.Once
)

// load reads the various PNG images from disk and stores them in their
// corresponding global variables.
func load() {
    defaultImage = loadPNG(defaultImageFile)
    backgroundImage = loadPNG(backgroundImageFile)
    for dirKey, dir := range dirs {
        paths, err := filepath.Glob(dir + "/*.png")
        if err != nil {
            panic(err)
        }
        for _, p := range paths {
            elements[dirKey] = append(elements[dirKey], loadPNG(p))
        }
    }
}

Requests are handled in a straightforward sequence:

  • Parse the request URL, decoding the decimal value of each character in the path.
  • Make a copy of the background image as the base for the final image.
  • Draw each image element onto the background image using the layoutMap to determine where they should be drawn.
  • Encode the image as a JPEG
  • Return the image to user by writing the JPEG directly to the HTTP response writer.

Should any error occur, we serve the defaultImage to the user and log the error to the App Engine dashboard for later analysis.

Here's the code for the request handler with explanatory comments:

func handler(w http.ResponseWriter, r *http.Request) {
    // [[https://blog.golang.org/2010/08/defer-panic-and-recover.html][Defer]] a function to recover from any panics.
    // When recovering from a panic, log the error condition to
    // the App Engine dashboard and send the default image to the user.
    defer func() {
        if err := recover(); 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)
            }
        }
    }()

    // Load images from disk on the first request.
    loadOnce.Do(load)

    // Make a copy of the background to draw into.
    bgRect := backgroundImage.Bounds()
    m := image.NewRGBA(bgRect.Dx(), bgRect.Dy())
    draw.Draw(m, m.Bounds(), backgroundImage, image.ZP, draw.Over)

    // Process each character of the request string.
    code := strings.ToLower(r.URL.Path[len(prefix):])
    for i, p := range code {
        // Decode hex character p in place.
        if p < 'a' {
            // it's a digit
            p = p - '0'
        } else {
            // it's a letter
            p = p - 'a' + 10
        }

        t := urlMap[i]    // element type by index
        em := elements[t] // element images by type
        if p >= len(em) {
            panic(fmt.Sprintf("element index out of range %s: "+
                "%d >= %d", t, p, len(em)))
        }

        // Draw the element to m,
        // using the layoutMap to specify its position.
        draw.Draw(m, layoutMap[t], em[p], image.ZP, draw.Over)
    }

    // Encode JPEG image and write it as the response.
    w.Header().Set("Content-type", "image/jpeg")
    w.Header().Set("Cache-control", "public, max-age=259200")
    jpeg.Encode(w, m, &imageQuality)
}

For brevity, I've omitted several helper functions from these code listings. See the source code for the full scoop.

Performance

This chart - taken directly from the App Engine dashboard - shows average request latency during launch. As you can see, even under load it never exceeds 60 ms, with a median latency of 32 milliseconds. This is wicked fast, considering that our request handler is doing image manipulation and encoding on the fly.

Conclusions

I found Go's syntax to be intuitive, simple and clean. I have worked a lot with interpreted languages in the past, and although Go is instead a statically typed and compiled language, writing this app felt more like working with a dynamic, interpreted language.

The development server provided with the SDK quickly recompiles the program after any change, so I could iterate as fast as I would with an interpreted language. It's dead simple, too - it took less than a minute to set up my development environment.

Go's great documentation also helped me put this together fast. The docs are generated from the source code, so each function's documentation links directly to the associated source code. This not only allows the developer to understand very quickly what a particular function does but also encourages the developer to dig into the package implementation, making it easier to learn good style and conventions.

In writing this application I used just three resources: App Engine's Hello World Go example, the Go packages documentation, and a blog post showcasing the Draw package. Thanks to the rapid iteration made possible by the development server and the language itself, I was able to pick up the language and build a super fast, production ready, doodle generator in less than 24 hours.

Download the full app source code (including images) at the Google Code project.

Special thanks go to Guillermo Real and Ryan Germick who designed the doodle.

相關推薦

From zero to Go: launching on the Google homepage in 24 hours

13 December 2011 Introduction This article was written by Reinaldo Aguiar, a software engineer from the Search t

Learning Go — from zero to hero

Learning Go — from zero to hero https://medium.freecodecamp.org/learning-go-from-zero-to-hero-d2a3223b3d86 Pic: Gopher mascot and old 

How to edit codes on the server which runs jupyter notebook using your pc's bwroser

repl lan HA sin AR make you 技術 des If you don‘t use jupyter, you may have to edit your source codes on your pc and upload them to your se

If you want to allow applications containing errors to be published on the server

serve enable errors websphere ebsp ror HERE The win If you want to allow applications containing errors to be published on the server, en

Learning Python: From Zero to Hero

Learning Python: From Zero to Hero https://medium.freecodecamp.org/learning-python-from-zero-to-hero-120ea540b567 First of all, what is Py

FROM ZERO TO HERO

刷題記錄 第一階段 //1000:A + B Problem #include <bits/stdc++.h> using namespace std; int main(void) { int a,b; while(scanf(

Android Developers Blog: Exclusive new organic acquisition insights on the Google Play Console

Posted by Tom Grinsted, Product Manager, Google Play We've updated the Play Console acquisition reports to give new insights into what users do on th

Ask HN: How to get everyone on the same page (dashboards/data/analytics)?

Hi HN,Has anyone here worked at a company where everyone was on the same page regarding which dashboards/analytics tools to go to when looking for dashboar

More than 4 billion birds stream overhead during fall migration: Scientists use radar to shed light on the massive numbers of mi

"We've discovered that each autumn, an average of 4 billion birds move south from Canada into the U.S. At the same time, another 4.7 billion birds leave t

Self-driving cars will likely lead to more sex on the road, study says

Ford Motor Company is partnering with Walmart and Postmates to study how consumers would handle self-driving cars making deliveries to their doors. Self-dr

Direct Traffic to Resources Based on the Query's Geographic Location

Amazon Web Services is Hiring. Amazon Web Services (AWS) is a dynamic, growing business unit within Amazon.com. We are currently hiring So

From Zero to Hero – 從1000個程式碼案例中學習人工智慧和大資料》課程

 https://mp.weixin.qq.com/s/3C0s_v1XhylrYjfKnhtOpQ王家林近1300頁的新書即將舉行新書釋出會---《大資料商業實戰三部曲》。 01.福利所有2017年11月30前報名最新《From Zero to Hero – 從1000個程

今天最是折磨我的問題。提示Unable to start debugging on the web server ……

最後是如此解決從“開始”選單中,依次選擇“程式”和“管理工具”,然後單擊“Internet 服務管理器”(Windows 2000) 或“Internet 資訊服務”(Windows XP)。 在“Internet 服務管理器”或“Internet 資訊服務”對話方塊中,單擊

How To Handle Network On Main Thread Error In Android

NetworkOnMainThreadException is a pretty popular exception among those new to android development. This exception is mainly thrown whenever an applicati

[Android] How to open the Google Play Store directly from my Android application?

如果是在Android app 裡寫code, 可以使用: final String appPackageName = getPackageName(); // getPackageName() from Context or Activity object try { startActivi

How to go from a Blockchain Tourist to a Blockchain Citizen: Beyond the Hype

How to go from a Blockchain Tourist to a Blockchain Citizen: Beyond the HypeHow to go from a Blockchain Tourist to a Blockchain CitizenBlockchain tourism a

Google Assistant makes it easier to go clothes shopping on Asos

Looking at clothes to buy online can be therapeutic, but sometimes you just want to quickly find something to wear for an impromptu event without having to

The Guardian’s Migration from MongoDB to PostgreSQL on Amazon RDS

轉載一片mongodb 遷移pg 資料庫的文章 原文: https://www.infoq.com/news/2019/01/guardian-mongodb-postgresql The Guardian migrated their CMS's datastor

Google Testing:Testing on the Toilet: What Makes a Good End-to-End Test?

by Adam BenderThis article was adapted from a GoogleTesting on the Toilet (TotT) episode. You can download a printer-friendlyvers

Warning: date(): It is not safe to rely on the system's timezone settings.

bsp ron notice zone asi 警告 family one str PHP調試的時候出現了警告: It is not safe to rely on the system解決方法,其實就是時區設置不正確造成的,本文提供了3種方法來解決這個問題。 實際上,