分享
  1. 首页
  2. 文章

用Go实现异步的Web开发

mikespook · · 11362 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

不知道大家还记得不记得大约一年前,我的一个白日梦《关于Web编程异步模型的白日梦》,然后这个白日梦我又连续做了好几天《Web编程异步模型的PHP原生实现》、《Web编程异步模型的 Gearman 实现(残)》。

当时怎么也没相通,还死皮白赖的粘在PHP的异步实现上不肯放手。好吧,实现是繁琐的,应用是成功的,代码是容易写的,环境是要搭建的......

昨晚睡觉前突然觉得自己应该真正用Go实现一下异步的Web,哪怕是个例子也好啊。于是,边吃饭,边敲了一票代码搞了一个很简单的demo,分享给大家吧。在这里下载完整的代码:webdemo

包含以下文件:

  • async.go——异步Web
  • data.go——模拟数据
  • Makefile——不说了,你懂......
  • page.go——页面
  • sync.go——同步Web
  • timer.go——记录执行时间的日志
  • webdemo.go——主文件

编译并运行

make run

通过浏览器分别访问同步方式的http://127.0.0.1:8888/sync,和异步方式的http://127.0.0.1:8888/async。在控制台会输出请求处理的时长,实际上即便不看统计时长,两者之间的速度差异很直观的能体会出来。

mikespook@mikespook-desktop:~/Desktop/webdemo$ make run
8g -o _go_.8 timer.go data.go page.go async.go sync.go 
rm -f _obj/webdemo.a
gopack grc _obj/webdemo.a _go_.8 
cp _obj/webdemo.a "/home/mikespook/bin/go/pkg/linux_386/webdemo.a"
8g webdemo.go
8l -o webdemo webdemo.8
./webdemo
2011年03月25日 15:06:35 async:	228623000
2011年03月25日 15:06:57 sync:	20024992000

核心思路很简单,我在模拟数据请求的代码里用了 time.Sleep(),让每次数据请求都延迟 0.2 秒(一个优化得不太好的,很大的数据库表的一次很烂的查询所用时间):

// data.go
func GetContents(key string) string {
 time.Sleep(SLEEP) // 延迟
 return fmt.Sprintf("%s. The quick brown fox jumps over the lazy dog.", key)
}

对于同步数据请求来说,必须等上一次的数据请求完毕才能发起下一次数据请求(原始的PHP即是如此),那么如果100个0.2秒的数据请求,则最终耗时一定大于 20 秒。
下面是请求100次数据的处理:

// webdemo.go 同步数据请求
func syncHandler(w http.ResponseWriter, r *http.Request) {
 timer := webdemo.NewTimer("sync")
 defer timer.End()
 page := webdemo.NewSyncPage()
 for i := 0; i < 100; i ++ {
 key := strconv.Itoa(i)
 page.SetContents(key)
 }
 page.Render(w)
}

下面是获取数据到页面,并渲染页面输出到http.ResonseWriter的方法:

// sync.go
func (page *SyncPage) SetContents(key string) {
 page.contents[key] = GetContents(key)
}
func (page *SyncPage) Render(w http.ResponseWriter) {
 lines := ""
 for i := 0; i < len(page.contents); i++ {
 key := strconv.Itoa(i)
 lines += fmt.Sprintf(TEMPLATE_LINE, page.contents[key])
 }
 block := fmt.Sprintf(TEMPLATE_BLOCK, lines)
 fmt.Fprintf(w, TEMPLATE_PAGE, block)
}

对于异步来说,总时间长度略大于单条数据请求时间长度。下面是异步请求的 handler 代码,其实跟同步并无区别。

// webdemo.go 异步Handler
func asyncHandler(w http.ResponseWriter, r *http.Request) {
 timer := webdemo.NewTimer("async")
 defer timer.End()
 page := webdemo.NewAsyncPage()
 page.CountOut = 100
 for i:= 0; i < page.CountOut; i++ {
 key := strconv.Itoa(i)
 page.SetContents(key)
 }
 page.Render(w)
}

为了实现异步的数据获取,在 async.go 的代码中,使用 go 语言强大的 channel 来实现。所以在 AsyncPage 结构中定义了一个 contents 是 chan。

// async.go
// 页面内容
type AsyncContents struct {
 key, value string
}
// 页面
type AsyncPage struct {
 contents chan AsyncContents
 timeout chan bool
 CountOut int
 page Page
}

在异步页面的 SetContents 方法中,用 go 关键字建立一个 goroutines 向 chan 中输入内容。同时建立另一个 goroutines 作为 timeout(本例中不会出现 timeout 的情况,不过实际环境中这是必要的)。

// async.go
func (page *AsyncPage) SetContents(key string) {
 // 异步的数据获取
 go func() {
 page.contents <- AsyncContents{key, GetContents(key)}
 }()
 // 设置针对页面的超时
 go func() {
 time.Sleep(TIMEOUT)
 page.timeout <- true
 }()
}

页面的渲染也跟同步方式不同,通过 select 将数据从chan 中取出,并渲染到模板。

// async.go
func (page *AsyncPage) Render(w http.ResponseWriter) {
 lines := ""
 LOOP: for i := 0; i < page.CountOut; i++{
 select {
 case line := <-page.contents:
 lines = fmt.Sprintf("%s" + TEMPLATE_LINE, lines, line.value)
 // 每获取一个数据,就去掉一个超时
 go func() {<-page.timeout}()
 case <-page.timeout:
 lines = fmt.Sprintf("%s" + TEMPLATE_LINE, lines, "Time Out")
 break LOOP
 }
 }
 block := fmt.Sprintf(TEMPLATE_BLOCK, lines)
 fmt.Fprintf(w, TEMPLATE_PAGE, block)
}

为了演示异步的结构,我并没有使用模板来渲染页面。不过用模板来渲染也差不多:异步的获取模板上叫做%VARn的变量,然后将数据集合渲染至模板上......

当数据的获取有依赖关系的时候情况会比较复杂,不过如果把 web 页面当作一个树(DOM Tree?)的话,让异步数据从叶节点开始获取,逐层上推是个不错的办法。


有疑问加站长微信联系(非本文作者)

本文来自:mikespook 的博客

感谢作者:mikespook

查看原文:用Go实现异步的Web开发

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
11362 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏