示例
Java程序猿对Go应该很眼熟
Main.java
1
2
3
4
5
|
public class Main {
public static void main(String[] args) {
System.out.println( "Hello, world!" );
}
}
|
hello.go
1
2
3
4
5
|
package main
import "fmt"
func main() {
fmt.Println( "Hello, 世界!" )
}
|
Hello, web server(你好,web服务)
package main
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc( "/hello" , handleHello)
fmt.Println( "serving on http://localhost:7777/hello" )
log .Fatal(http.ListenAndServe( "localhost:7777" , nil))
}
func handleHello(w http.ResponseWriter, req *http.Request) {
log .Println( "serving" , req.URL)
fmt.Fprintln(w, "Hello, 世界!" )
}
|
(访问权限)类型根据变量名来声明。
公共变量名首字大写,私有变量首字母小写。
示例:Google搜索前端
1
2
3
4
5
6
7
8
|
func main() {
http.HandleFunc( "/search" , handleSearch)
fmt.Println( "serving on http://localhost:8080/search" )
log .Fatal(http.ListenAndServe( "localhost:8080" , nil))
}
func handleSearch(w http.ResponseWriter, req *http.Request) {
|
请求验证
1
2
3
4
5
6
7
8
|
func handleSearch(w http.ResponseWriter, req *http.Request) {
log .Println( "serving" , req.URL)
query := req.FormValue( "q" )
if query == "" {
http.Error(w, missing "q" URL parameter , http.StatusBadRequest)
return
}
|
FormValueis 是 *http.Request 的一个方法:
1
2
3
|
package http
type Request struct {...}
func (r *Request) FormValue(key string) string {...}
|
query := req.FormValue("q")初始化变量query,其变量类型是右边表达式的结果,这里是string类型.
取搜索结果
1
2
3
4
5
6
7
8
|
start := time .Now()
results, err := Search(query)
elapsed := time .Since(start)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
|
Search方法有两个返回值,分别为结果results和错误error.
1
|
func Search(query string) ([]Result, error) {...}
|
当error的值为nil时,results有效。
1
2
3
|
type error interface {
Error() string
}
|
Error类型可能包含额外的信息,可通过断言访问。
渲染搜索结果
1
2
3
4
5
6
7
8
9
10
11
12
|
type templateData struct {
Results []Result
Elapsed time .Duration
}
if err := resultsTemplate.Execute(w, templateData{
Results: results,
Elapsed: elapsed,
}); err != nil {
log .Print(err)
return
}
|
结果results使用Template.Execute生成HTML,并存入一个io.Writer:
1
2
3
|
type Writer interface {
Write(p []byte) (n int , err error)
}
|
http.ResponseWriter实现了io.Writer接口。
Go变量操作HTML模板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
type Result struct {
Title, URL string
}
var resultsTemplate = template .Must( template .New( "results" ).Parse(
<html>
<head/>
<body>
<ol>
{{range .Results}}
<li>{{.Title}} - <a href= "{{.URL}}" >{{.URL}}</a></li>
{{end}}
</ol>
<p>{{len .Results}} results in {{.Elapsed}}</p>
</body>
</html>
))
|
请求Google搜索API
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
func Search(query string) ([]Result, error) {
u, err := url.Parse( "https://ajax.googleapis.com/ajax/services/search/web?v=1.0" )
if err != nil {
return nil, err
}
q := u.Query()
q.Set( "q" , query)
u.RawQuery = q.Encode()
resp, err := http.Get(u.String())
if err != nil {
return nil, err
}
defer resp.Body.Close()
|
defer声明使resp.Body.Close运行在Search方法返回时。
解析返回的JSON数据到Go struct类型
developers.google.com/web-search/docs/#fonje
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
var jsonResponse struct {
ResponseData struct {
Results [] struct {
TitleNoFormatting, URL string
}
}
}
if err := json.NewDecoder(resp.Body).Decode(&jsonResponse); err != nil {
return nil, err
}
var results []Result
for _, r := range jsonResponse.ResponseData.Results {
results = append(results, Result{Title: r.TitleNoFormatting, URL: r.URL})
}
return results, nil
}
|
这就是它的前端
所有引用的包都来自标准库:
1
2
3
4
5
6
7
8
9
|
import (
"encoding/json"
"fmt"
"html/template"
"log"
"net/http"
"net/url"
"time"
)
|
Go服务器规模:每一个请求都运行在自己的goroutine里。
让我们谈谈并发。
通信顺序进程(Hoare,1978)
并发程序作为独立进程,通过信息交流的顺序执行。
顺序执行很容易理解,异步则不是。
“不要为共亨内存通信,为通信共享内存。”
Go原理: goroutines, channels, 和 select声明.
Goroutines
Goroutines 就像轻量级线程。
它们通过小栈(tiny stacks)和按需调整运行。
Go 程序可以拥有成千上万个(goroutines)实例
使用go声明启动一个goroutines:
Go运行时把goroutines放进OS线程里。
不要使用线程堵塞goroutines。
Channels
Channels被定义是为了与goroutines之间通信。
1
2
3
4
5
6
7
8
|
c := make (chan string)
// goroutine 1
c <- "hello!"
// goroutine 2
s := <-c
fmt .Println(s) // "hello!"
|
Select
select声明一个语句块来判断执行。
1
2
3
4
5
6
|
select {
case n := <-in:
fmt.Println( "received" , n)
case out <- v:
fmt.Println( "sent" , v)
}
|
只有条件成立的case块会运行。
示例:Google搜索(后端)
问: Google搜索能做些什么?
答: 提出一个问题,它可以返回一个搜索结果的页面(和一些广告)。
问: 我们怎么得到这些搜索结果?
答: 发送一个问题到网页搜索、图片搜索、YouTube(视频)、地图、新闻,稍等然后检索出结果。
我们该怎么实现它?
Google搜索 : 一个假的框架
We can simulate a Search function with a random timeout up to 100ms.
我们要模拟一个搜索函数,让它随机超时0到100毫秒。
1
2
3
4
5
6
7
8
9
10
11
12
|
var (
Web = fakeSearch( "web" )
Image = fakeSearch( "image" )
Video = fakeSearch( "video" )
)
type Search func(query string) Result
func fakeSearch(kind string) Search {
return func(query string) Result {
time .Sleep( time .Duration( rand .Intn(100)) * time .Millisecond)
return Result(fmt.Sprintf( "%s result for %q\n" , kind, query))
}
}
|
Google搜索: 测试框架
1
2
3
4
5
6
7
|
func main() {
start := time .Now()
results := Google( "golang" )
elapsed := time .Since(start)
fmt.Println(results)
fmt.Println(elapsed)
}
|
Google搜索 (串行)
Google函数获取一个查询,然后返回一个的结果集 (不一定是字符串).
Google按顺序调用Web(网页)、Image(图片)、Video(视频)并将返回加入到结果集中。
1
2
3
4
5
6
|
func Google(query string) (results []Result) {
results = append(results, Web(query))
results = append(results, Image(query))
results = append(results, Video(query))
return
}
|
Google搜索(并行)
同时执行 Web,、Image、 和Video搜索,并等待所有结果。
func方法是在query和c的地方关闭的。
1
2
3
4
5
6
7
8
9
10
11
|
func Google(query string) (results []Result) {
c := make(chan Result)
go func() { c <- Web(query) }()
go func() { c <- Image(query) }()
go func() { c <- Video(query) }()
for i := 0; i < 3; i++ {
result := <-c
results = append(results, result)
}
return
}
|
Google搜索 (超时)
等待慢的服务器。
没有锁,没有条件变量,没有返回值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
c := make(chan Result, 3)
go func() { c <- Web(query) }()
go func() { c <- Image(query) }()
go func() { c <- Video(query) }()
timeout := time .After(80 * time .Millisecond)
for i := 0; i < 3; i++ {
select {
case result := <-c:
results = append(results, result)
case <-timeout:
fmt.Println( "timed out" )
return
}
}
return
|
防止超时
问: 如何防止丢掉慢的服务的结果?
答: 复制这个服务,然后发送请求到多个复制的服务,并使用第一个响应的结果。
1
2
3
4
5
6
7
8
|
func First(query string, replicas ...Search) Result {
c := make(chan Result, len(replicas))
searchReplica := func(i int ) { c <- replicas[i](query) }
for i := range replicas {
go searchReplica(i)
}
return <-c
}
|
使用First函数
1
2
3
4
5
6
7
8
9
|
func main() {
start := time .Now()
result := First( "golang" ,
fakeSearch( "replica 1" ),
fakeSearch( "replica 2" ))
elapsed := time .Since(start)
fmt.Println(result)
fmt.Println(elapsed)
}
|
Google搜索 (复制)
使用复制的服务以减少多余延迟。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
c := make(chan Result, 3)
go func() { c <- First(query, Web1, Web2) }()
go func() { c <- First(query, Image1, Image2) }()
go func() { c <- First(query, Video1, Video2) }()
timeout := time .After(80 * time .Millisecond)
for i := 0; i < 3; i++ {
select {
case result := <-c:
results = append(results, result)
case <-timeout:
fmt.Println( "timed out" )
return
}
}
return
|