> 2021年6月19日信息消化 ### 每天学点Golang #### Build Web Application with Golang [build-web-application-with-golang](https://github.com/astaxie/build-web-application-with-golang) ##### [Go环境配置 Go env configuration](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/01.0.md) Go install [How to install and setup Golang development under WSL 2](https://medium.com/@benzbraunstein/how-to-install-and-setup-golang-development-under-wsl-2-4b8ca7720374) ###### Go Module, GOPATH 与工作空间 从 Go1.11 开始, Go 官方加入 Go Module 支持, Go1.12 成为默认支持; 从此告别源码必须放在 Gopath 中 以及 Gopath 对初学者造成的困扰. ###### GOMODULE 常用命令: ``` go mod init # 初始化 go.mod go mod tidy # 更新依赖文件 go mod download # 下载依赖文件 go mod vendor # 将依赖转移至本地的 vendor 文件 go mod edit # 手动修改依赖文件 go mod graph # 打印依赖图 go mod verify # 校验依赖 ``` ###### godoc ```bash go get golang.org/x/tools/cmd/godoc ``` 通过命令在命令行执行 godoc -http=:端口号 比如`godoc -http=:8080`。然后在浏览器中打开`127.0.0.1:8080`,你将会看到一个golang.org的本地copy版本,通过它你可以查询pkg文档等其它内容。如果你设置了GOPATH,在pkg分类下,不但会列出标准包的文档,还会列出你本地`GOPATH`中所有项目的相关文档,这对于经常被墙的用户来说是一个不错的选择。 ##### [Go语言基础 Go basic knowledge](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/02.0.md) ###### Struct, Interface, OOP ```go type Human struct { name string age int phone string } type Student struct { Human //匿名字段 school string loan float32 } type Student struct { Human //匿名字段 school string loan float32 } //Human实现SayHi方法 func (h Human) SayHi() { fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) } //Employee重载Human的SayHi方法 func (e Employee) SayHi() { fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, e.company, e.phone) } // Interface Men被Human,Student和Employee实现 // 因为这三个类型都实现了这两个方法 type Men interface { SayHi() Sing(lyrics string) } func main() { mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00} paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100} sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000} tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000} //定义Men类型的变量i var i Men //i能存储Student i = mike fmt.Println("This is Mike, a Student:") i.SayHi() i.Sing("November rain") //i也能存储Employee i = tom fmt.Println("This is tom, an Employee:") i.SayHi() i.Sing("Born to be wild") //定义了slice Men fmt.Println("Let's use a slice of Men and see what happens") x := make([]Men, 3) //这三个都是不同类型的元素,但是他们实现了interface同一个接口 x[0], x[1], x[2] = paul, sam, mike for _, value := range x{ value.SayHi() } } ``` ###### Go 并发 ```go package main import "fmt" func sum(a []int, c chan int) { total := 0 for _, v := range a { total += v } c <- total // send total to c } func main() { a := []int{7, 2, 8, -9, 4, 0} c := make(chan int) go sum(a[:len(a)/2], c) go sum(a[len(a)/2:], c) x, y := <-c, <-c // receive from c fmt.Println(x, y, x + y) } ``` 这一章我们主要介绍了Go语言的一些语法,通过语法我们可以发现Go是多么的简单,只有二十五个关键字。让我们再来回顾一下这些关键字都是用来干什么的。 ```go break default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var ``` - var和const参考2.2Go语言基础里面的变量和常量申明 - package和import已经有过短暂的接触 - func 用于定义函数和方法 - return 用于从函数返回 - defer 用于类似析构函数 - go 用于并发 - select 用于选择不同类型的通讯 - interface 用于定义接口,参考2.6小节 - struct 用于定义抽象数据类型,参考2.5小节 - break、case、continue、for、fallthrough、else、if、switch、goto、default这些参考2.3流程介绍里面 - chan用于channel通讯 - type用于声明自定义类型 - map用于声明map类型数据 - range用于读取slice、map、channel数据 ##### [Web基础 Web Foundation](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/03.0.md)  Web服务器的工作原理可以简单地归纳为: - 客户机通过TCP/IP协议建立到服务器的TCP连接 - 客户端向服务器发送HTTP协议请求包,请求服务器里的资源文档 - 服务器向客户机发送HTTP协议应答包,如果请求的资源包含有动态语言的内容,那么服务器会调用动态语言的解释引擎负责处理“动态内容”,并将处理得到的数据返回给客户端 - 客户机与服务器断开。由客户端解释HTML文档,在客户端屏幕上渲染图形结果 DNS解析的整个流程 所谓 `递归查询过程` 就是 “查询的递交者” 更替, 而 `迭代查询过程` 则是 **“查询的递交者”不变。** 从HTTP/1.1起,默认都开启了Keep-Alive保持连接特性,简单地说,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的TCP连接。 Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同服务器软件(如Apache)中设置这个时间。 ###### http包建立Web服务器 ```go package main import ( "fmt" "net/http" "strings" "log" ) func sayhelloName(w http.ResponseWriter, r *http.Request) { r.ParseForm() //解析参数,默认是不会解析的 fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息 fmt.Println("path", r.URL.Path) fmt.Println("scheme", r.URL.Scheme) fmt.Println(r.Form["url_long"]) for k, v := range r.Form { fmt.Println("key:", k) fmt.Println("val:", strings.Join(v, "")) } fmt.Fprintf(w, "Hello astaxie!") //这个写入到w的是输出到客户端的 } func main() { http.HandleFunc("/", sayhelloName) //设置访问的路由 err := http.ListenAndServe(":9090", nil) //设置监听的端口 if err != nil { log.Fatal("ListenAndServe: ", err) } } ``` ###### 分析http包运行机制  图3.9 http包执行流程 1. 创建Listen Socket, 监听指定的端口, 等待客户端请求到来。 2. Listen Socket接受客户端的请求, 得到Client Socket, 接下来通过Client Socket与客户端通信。 3. 处理客户端的请求, 首先从Client Socket读取HTTP请求的协议头, 如果是POST方法, 还可能要读取客户端提交的数据, 然后交给相应的handler处理请求, handler处理完毕准备好客户端需要的数据, 通过Client Socket写给客户端。 监控之后如何接收客户端的请求呢?上面代码执行监控端口之后,调用了`srv.Serve(net.Listener)`函数,这个函数就是处理接收客户端的请求信息。这个函数里面起了一个`for{}`,首先通过Listener接收请求,其次创建一个Conn,最后单独开了一个goroutine,把这个请求的数据当做参数扔给这个conn去服务:`go c.serve()`。这个就是高并发体现了,用户的每一次请求都是在一个新的goroutine去服务,相互不影响。 ```mermaid graph TD; ListenAndServe["监听端口addr net.Listen('tcp', addr) "]; Conn["接受用户请求并创建连接Conn srv.Serve(I net.Listener)"] ListenAndServe --接到请求并转交--> Conn; subgraph 2 Conn --"for{}"--> createconn["rw := I.Accept() c:=srv.NewConn() go c.serve"]; createconn--> createconn; end createconn --> Deal["处理连接 c.readRequest() handler = DefualtServeMux handler.ServeHTTP(resp,req)"] ``` ###### Go的http包详解 ```go c, err := srv.newConn(rw) if err != nil { continue } go c.serve() ``` 这里我们可以看到客户端的每次请求都会创建一个Conn,这个Conn里面保存了该次请求的信息,然后再传递到对应的handler,该handler中便可以读取到相应的header信息,这样保证了每个请求的独立性。 ###### 路由器 ```go type ServeMux struct { mu sync.RWMutex //锁,由于请求涉及到并发处理,因此这里需要一个锁机制 m map[string]muxEntry // 路由规则,一个string对应一个mux实体,这里的string就是注册的路由表达式 hosts bool // 是否在任意的规则中带有host信息 } ``` Handler是一个接口 ```go type Handler interface { ServeHTTP(ResponseWriter, *Request) // 路由实现器 } type HandlerFunc func(ResponseWriter, *Request) // 调用了HandlerFunc(f),强制类型转换f成为HandlerFunc类型,这样f就拥有了ServeHTTP方法。 // ServeHTTP calls f(w, r). func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) } ``` ##### [表单 User form](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/04.0.md) 表单内容: `login.gtpl` ```go package main import ( "fmt" "html/template" "log" "net/http" "strings" ) func login(w http.ResponseWriter, r *http.Request) { fmt.Println("method:", r.Method) //获取请求的方法 if r.Method == "GET" { t, _ := template.ParseFiles("login.gtpl") log.Println(t.Execute(w, nil)) } else { //请求的是登录数据,那么执行登录的逻辑判断 fmt.Println("username:", r.Form["username"]) fmt.Println("password:", r.Form["password"]) } } ``` ###### 防止多次递交表单 我们在模版里面增加了一个隐藏字段`token`,这个值我们通过MD5(时间戳)来获取唯一值,然后我们把这个值存储到服务器端(session来控制,我们将在第六章讲解如何保存),以方便表单提交时比对判定。 ###### 4.5 处理文件上传 https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/04.5.md ```go http.HandleFunc("/upload", upload) // 处理/upload 逻辑 func upload(w http.ResponseWriter, r *http.Request) { fmt.Println("method:", r.Method) //获取请求的方法 if r.Method == "GET" { crutime := time.Now().Unix() h := md5.New() io.WriteString(h, strconv.FormatInt(crutime, 10)) token := fmt.Sprintf("%x", h.Sum(nil)) t, _ := template.ParseFiles("upload.gtpl") t.Execute(w, token) } else { r.ParseMultipartForm(32 << 20) file, handler, err := r.FormFile("uploadfile") if err != nil { fmt.Println(err) return } defer file.Close() fmt.Fprintf(w, "%v", handler.Header) f, err := os.OpenFile("./test/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666) // 此处假设当前目录下已存在test目录 if err != nil { fmt.Println(err) return } defer f.Close() io.Copy(f, file) } } ``` ##### [访问数据库 Database](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/05.0.md) RDBMS: SQlite, MySQL, Postgres NoSQL: Redis, mongoDB ##### [session和数据存储 Data storage and session](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/06.0.md) ##### [文本文件处理 Text files](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/07.0.md) ##### [8 Web服务 Web services](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/08.0.md) ###### 什么是Socket? Socket起源于Unix,而Unix基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现,网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用:Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。 常用的Socket类型有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。 ###### TCP server ```go package main import ( "fmt" "net" "os" "time" ) func main() { service := ":1200" tcpAddr, err := net.ResolveTCPAddr("tcp4", service) checkError(err) listener, err := net.ListenTCP("tcp", tcpAddr) checkError(err) for { conn, err := listener.Accept() if err != nil { continue } go handleClient(conn) } } func handleClient(conn net.Conn) { defer conn.Close() daytime := time.Now().String() conn.Write([]byte(daytime)) // don't care about return value // we're finished with this client } func checkError(err error) { if err != nil { fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) os.Exit(1) } } ``` ###### 8.2 WebSocket WebSocket是HTML5的重要特性,它实现了基于浏览器的远程socket,它使浏览器和服务器可以进行全双工通信,许多浏览器(Firefox、Google Chrome和Safari)都已对此做了支持。 相比传统HTTP有如下好处: - 一个Web客户端只建立一个TCP连接 - Websocket服务端可以推送(push)数据到web客户端. - 有更加轻量级的头,减少数据传送量 ```go package main import ( "golang.org/x/net/websocket" "fmt" "log" "net/http" ) func Echo(ws *websocket.Conn) { var err error for { var reply string if err = websocket.Message.Receive(ws, &reply); err != nil { fmt.Println("Can't receive") break } fmt.Println("Received back from client: " + reply) msg := "Received: " + reply fmt.Println("Sending to client: " + msg) if err = websocket.Message.Send(ws, msg); err != nil { fmt.Println("Can't send") break } } } func main() { http.Handle("/", websocket.Handler(Echo)) if err := http.ListenAndServe(":1234", nil); err != nil { log.Fatal("ListenAndServe:", err) } } ``` ###### 8.4 RPC 通过学习我们了解了Socket和HTTP采用的是类似"信息交换"模式,即客户端发送一条信息到服务端,然后(一般来说)服务器端都会返回一定的信息以表示响应。客户端和服务端之间约定了交互信息的格式,以便双方都能够解析交互所产生的信息。但是很多独立的应用并没有采用这种模式,而是采用类似常规的函数调用的方式来完成想要的功能。 RPC就是想实现函数调用模式的网络化。客户端就像调用本地函数一样,然后客户端把这些参数打包之后通过网络传递到服务端,服务端解包到处理过程中执行,然后执行的结果反馈给客户端。 RPC(Remote Procedure Call Protocol)——远程过程调用协议,是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。 ##### [安全与加密 Security and encryption](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/09.0.md) ##### [国际化和本地化 Internationalization and localization](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/10.0.md) 国际化与本地化(Internationalization and localization,通常用i18n和L10N表示),国际化是将针对某个地区设计的程序进行重构,以使它能够在更多地区使用,本地化是指在一个面向国际化的程序中增加对新地区的支持。 我们可以通过下面的代码来实现域名的对应locale: ```go if r.Host == "www.asta.com" { i18n.SetLocale("en") } else if r.Host == "www.asta.cn" { i18n.SetLocale("zh-CN") } else if r.Host == "www.asta.tw" { i18n.SetLocale("zh-TW") } ``` [www.asta.com/en/books](http://www.asta.com/en/books) URL地址可以通过router来获取locale ```go mux.Get("/:locale/books", listbook) ``` ###### 本地化文本消息 ```go func main() { locales = make(map[string]map[string]string, 2) en := make(map[string]string, 10) cn := make(map[string]string, 10) en["how old"] ="I am %d years old" cn["how old"] ="我今年%d岁了" locales["en"] = en locales["zh-CN"] = cn lang := "zh-CN" fmt.Printf(msg(lang, "how old"), 30)} ``` ```go var locales map[string]map[string]stringfunc msg(locale, key string) string { if v, ok := locales[locale]; ok { if v2, ok := v[key]; ok { return v2 } } return ""} ``` 上面的示例代码仅用以演示内部的实现方案,而实际数据是存储在JSON里面的,所以我们可以通过`json.Unmarshal`来为相应的map填充数据。 ###### 本地化视图和资源 ```text views |--en //英文模板 |--images //存储图片信息 |--js //存储JS文件 |--css //存储css文件 index.tpl //用户首页 login.tpl //登陆首页 |--zh-CN //中文模板 |--images |--js |--css index.tpl login.tpl ``` ###### 管理多个本地包 ```json # zh.json { "zh": { "submit": "提交", "create": "创建" } } # en.json { "en": { "submit": "Submit", "create": "Create" } } ``` 为了支持国际化,在此我们使用了一个国际化相关的包——[go-i18n](https://github.com/astaxie/go-i18n),首先我们向go-i18n包注册config/locales这个目录,以加载所有的locale文件 ```go Tr:=i18n.NewLocale() Tr.LoadPath("config/locales") // 这个包使用起来很简单,你可以通过下面的方式进行测试: fmt.Println(Tr.Translate("submit")) //输出Submit Tr.SetLocale("zh") fmt.Println(Tr.Translate("submit")) //输出“提交” ``` ##### [11 错误处理,调试和测试 Error Handling, debugging and testing](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/11.0.md) 在程序设计中,容错是相当重要的一部分工作,在Go中它是通过错误处理来实现的,error虽然只是一个接口,但是其变化却可以有很多,我们可以根据自己的需求来实现不同的处理 错误信息定义的更加友好,调试的时候也方便定位问题,我们可以自定义返回的错误类型: ```go type appError struct { Error error Message string Code int } ``` 这样我们的自定义路由器可以改成如下方式: ```go type appHandler func(http.ResponseWriter, *http.Request) *appError func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if e := fn(w, r); e != nil { // e is *appError, not os.Error. c := appengine.NewContext(r) c.Errorf("%v", e.Error) http.Error(w, e.Message, e.Code) } } ``` 这样修改完自定义错误之后,我们的逻辑处理可以改成如下方式: ```go func viewRecord(w http.ResponseWriter, r *http.Request) *appError { c := appengine.NewContext(r) key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil) record := new(Record) if err := datastore.Get(c, key, record); err != nil { return &appError{err, "Record not found", 404} } if err := viewTemplate.Execute(w, record); err != nil { return &appError{err, "Can't display record", 500} } return nil} ``` ###### Debug Go内部已经内置支持了GDB,所以,我们可以通过GDB来进行调试 GDB是FSF(自由软件基金会)发布的一个强大的类UNIX系统下的程序调试工具。使用GDB可以做如下事情: 1. 启动程序,可以按照开发者的自定义要求运行程序。 2. 可让被调试的程序在开发者设定的调置的断点处停住。(断点可以是条件表达式) 3. 当程序被停住时,可以检查此时程序中所发生的事。 4. 动态的改变当前程序的执行环境。 另外建议纯go代码使用[delve](https://github.com/derekparker/delve)可以很好的进行Go代码调试 ###### 通过gdb命令启动调试: ```bash # 编译文件,生成可执行文件gdbfile# -ldflags "-s",忽略debug的打印信息# -gcflags "-N -l" 忽略Go内部做的一些优化,聚合变量和函数等优化go build -gcflags "-N -l" gdbfile.gogdb gdbfile>> (gdb) runStarting program: /home/xiemengjun/gdbfile Starting maincount: 0# 在第23行设置断点>> (gdb) b 23Breakpoint 1 at 0x400d8d: file /home/xiemengjun/gdbfile.go, line 23.(gdb) run# 打印变量(gdb) info locals# 继续往下执行(gdb) c# 查看goroutine(gdb) info goroutines(gdb) goroutine 1 bt ``` ###### 11.3 Go怎么写测试用例 [gotests](https://github.com/cweill/gotests)插件自动生成测试代码: ```go go get -u -v github.com/cweill/gotests/... ``` 单元测试 压力测试 ```go package gotest import ( "testing" ) func Benchmark_Division(b *testing.B) { for i := 0; i < b.N; i++ { //use b.N for looping Division(4, 5) } } func Benchmark_TimeConsumingFunction(b *testing.B) { b.StopTimer() //调用该函数停止压力测试的时间计数 //做一些初始化的工作,例如读取文件数据,数据库连接之类的, //这样这些时间不影响我们测试函数本身的性能 b.StartTimer() //重新开始时间 for i := 0; i < b.N; i++ { Division(4, 5) } } ``` 我们执行命令`go test webbench_test.go -test.bench=".*"`,可以看到如下结果: ```go Benchmark_Division-4 500000000 7.76 ns/op 456 B/op 14 allocs/op Benchmark_TimeConsumingFunction-4 500000000 7.80 ns/op 224 B/op 4 allocs/op PASS ok gotest 9.364s ``` 第一条显示了`Benchmark_Division`执行了500000000次,每次的执行平均时间是7.76纳秒,第二条显示了`Benchmark_TimeConsumingFunction`执行了500000000,每次的平均执行时间是7.80纳秒。最后一条显示总共的执行时间。 通过上面对单元测试和压力测试的学习,我们可以看到`testing`包很轻量,编写单元测试和压力测试用例非常简单,配合内置的`go test`命令就可以非常方便的进行测试,这样在我们每次修改完代码,执行一下go test就可以简单的完成回归测试了。 ##### [部署与维护 Deployment and maintenance](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/12.0.md) 使用第三方开发的日志系统:[logrus](https://github.com/sirupsen/logrus)和[seelog](https://github.com/cihub/seelog),它们实现了很强大的日志功能,可以结合自己项目选择。 ###### 12.2 网站错误处理 我们的Web应用一旦上线之后,那么各种错误出现的概率都有,Web应用日常运行中可能出现多种错误,具体如下所示: - 数据库错误:指与访问数据库服务器或数据相关的错误。例如,以下可能出现的一些数据库错误。 - 连接错误:这一类错误可能是数据库服务器网络断开、用户名密码不正确、或者数据库不存在。 - 查询错误:使用的SQL非法导致错误,这样子SQL错误如果程序经过严格的测试应该可以避免。 - 数据错误:数据库中的约束冲突,例如一个唯一字段中插入一条重复主键的值就会报错,但是如果你的应用程序在上线之前经过了严格的测试也是可以避免这类问题。 - 应用运行时错误:这类错误范围很广,涵盖了代码中出现的几乎所有错误。可能的应用错误的情况如下: - 文件系统和权限:应用读取不存在的文件,或者读取没有权限的文件、或者写入一个不允许写入的文件,这些都会导致一个错误。应用读取的文件如果格式不正确也会报错,例如配置文件应该是ini的配置格式,而设置成了json格式就会报错。 - 第三方应用:如果我们的应用程序耦合了其他第三方接口程序,例如应用程序发表文章之后自动调用接发微博的接口,所以这个接口必须正常运行才能完成我们发表一篇文章的功能。 - HTTP错误:这些错误是根据用户的请求出现的错误,最常见的就是404错误。虽然可能会出现很多不同的错误,但其中比较常见的错误还有401未授权错误(需要认证才能访问的资源)、403禁止错误(不允许用户访问的资源)和503错误(程序内部出错)。 - 操作系统出错:这类错误都是由于应用程序上的操作系统出现错误引起的,主要有操作系统的资源被分配完了,导致死机,还有操作系统的磁盘满了,导致无法写入,这样就会引起很多错误。 - 网络出错:指两方面的错误,一方面是用户请求应用程序的时候出现网络断开,这样就导致连接中断,这种错误不会造成应用程序的崩溃,但是会影响用户访问的效果;另一方面是应用程序读取其他网络上的数据,其他网络断开会导致读取失败,这种需要对应用程序做有效的测试,能够避免这类问题出现的情况下程序崩溃。 ###### 如何处理异常 panic其实就是异常处理。如下代码,我们期望通过uid来获取User中的username信息,但是如果uid越界了就会抛出异常,这个时候如果我们没有recover机制,进程就会被杀死,从而导致程序不可服务。因此为了程序的健壮性,在一些地方需要建立recover机制。 ```go func GetUser(uid int) (username string) { defer func() { if x := recover(); x != nil { username = "" } }() username = User[uid] return } ``` 规则很简单:如果你定义的函数有可能失败,它就应该返回一个错误。当我调用其他package的函数时,如果这个函数实现的很好,我不需要担心它会panic,除非有真正的异常情况发生,即使那样也不应该是我去处理它。而panic和recover是针对自己开发package里面实现的逻辑,针对一些特殊情况来设计。 ##### 应用部署 因为Go程序编译之后是一个可执行文件,编写过C程序的读者一定知道采用daemon就可以完美的实现程序后台持续运行,但是目前Go还无法完美的实现daemon,因此,针对Go的应用程序部署,我们可以利用第三方工具来管理。 ```bash sudo easy_install supervisor ``` Supervisord默认的配置文件路径为/etc/supervisord.conf,通过文本编辑器修改这个文件,下面是一个示例的配置文件: ```conf ;/etc/supervisord.conf [unix_http_server] file = /var/run/supervisord.sock chmod = 0777 chown= root:root [inet_http_server] # Web管理界面设定 port=9001 username = admin password = yourpassword [supervisorctl] ; 必须和'unix_http_server'里面的设定匹配 serverurl = unix:///var/run/supervisord.sock [supervisord] logfile=/var/log/supervisord/supervisord.log ; (main log file;default $CWD/supervisord.log) logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB) logfile_backups=10 ; (num of main logfile rotation backups;default 10) loglevel=info ; (log level;default info; others: debug,warn,trace) pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid) nodaemon=true ; (start in foreground if true;default false) minfds=1024 ; (min. avail startup file descriptors;default 1024) minprocs=200 ; (min. avail process descriptors;default 200) user=root ; (default is current user, required if root) childlogdir=/var/log/supervisord/ ; ('AUTO' child log dir, default $TEMP) [rpcinterface:supervisor] supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface ; 管理的单个进程的配置,可以添加多个program [program:blogdemon] command=/data/blog/blogdemon autostart = true startsecs = 5 user = root redirect_stderr = true stdout_logfile = /var/log/supervisord/blogdemon.log ``` ###### Supervisord管理 Supervisord安装完成后有两个可用的命令行supervisor和supervisorctl,命令使用解释如下: - supervisord,初始启动Supervisord,启动、管理配置中设置的进程。 - supervisorctl stop programxxx,停止某一个进程(programxxx),programxxx为[program:blogdemon]里配置的值,这个示例就是blogdemon。 - supervisorctl start programxxx,启动某个进程 - supervisorctl restart programxxx,重启某个进程 - supervisorctl stop all,停止全部进程,注:start、restart、stop都不会载入最新的配置文件。 - supervisorctl reload,载入最新的配置文件,并按新的配置启动、管理所有进程。 ###### 12.4 备份和恢复 如果我们没有采用云储存的情况下,如何做到网站的备份呢?这里我们介绍一个文件同步工具rsync:rsync能够实现网站的备份,不同系统的文件的同步,如果是windows的话,需要windows版本cwrsync。 ###### rsync配置 rsync主要有以下三个配置文件rsyncd.conf(主配置文件)、rsyncd.secrets(密码文件)、rsyncd.motd(rysnc服务器信息)。 服务端开启: ``` #/usr/bin/rsync --daemon --config=/etc/rsyncd.conf ``` --daemon参数方式,是让rsync以服务器模式运行。把rsync加入开机启动 ``` echo 'rsync --daemon' >> /etc/rc.d/rc.local ``` ###### MySQL备份 应用数据库目前还是MySQL为主流,目前MySQL的备份有两种方式:热备份和冷备份,热备份目前主要是采用master/slave方式(master/slave方式的同步目前主要用于数据库读写分离,也可以用于热备份数据) 冷备份一般使用shell脚本来实现定时备份数据库,然后通过上面介绍rsync同步非本地机房的一台服务器。 下面这个是定时备份mysql的备份脚本,我们使用了mysqldump程序,这个命令可以把数据库导出到一个文件中。 ※完整脚本见snippets ```bash mysqldump -h$host -P$port -u$user -p$password $dbname --default-character-set=$charset | gzip > $backup_dir/$dbname-$backup_time.sql.gz ``` 修改shell脚本的属性: ```bash chmod 600 /root/mysql_backup.sh chmod +x /root/mysql_backup.sh ``` 设置好属性之后,把命令加入crontab,我们设置了每天00:00定时自动备份,然后把备份的脚本目录/var/www/mysql设置为rsync同步目录。 ```bash 00 00 * * * /root/mysql_backup.sh ``` ###### MySQL恢复 ```bash mysql -u username -p databse < backup.sql ``` ###### redis备份 redis是目前我们使用最多的NoSQL,它的备份也分为两种:热备份和冷备份,redis也支持master/slave模式,所以我们的热备份可以通过这种方式实现,相应的配置大家可以参考官方的文档配置,相当的简单。我们这里介绍冷备份的方式:redis其实会定时的把内存里面的缓存数据保存到数据库文件里面,我们备份只要备份相应的文件就可以,就是利用前面介绍的rsync备份到非本地机房就可以实现。 ###### redis恢复 redis的恢复分为热备份恢复和冷备份恢复,热备份恢复的目的和方法同MySQL的恢复一样,只要修改应用的相应的数据库连接即可。 但是有时候我们需要根据冷备份来恢复数据,redis的冷备份恢复其实就是只要把保存的数据库文件copy到redis的工作目录,然后启动redis就可以了,redis在启动的时候会自动加载数据库文件到内存中,启动的速度根据数据库的文件大小来决定。 ### 每天学点AI #### OpenAI GPT-3: Beginners Tutorial 原文:[OpenAI GPT-3: Beginners Tutorial](https://www.youtube.com/watch?v=9g66yO0Jues) #### GPT-3 Introduction OpenAI API Reference https://beta.openai.com/docs/api-reference/introduction ```bash pip install openai ``` ##### Engines OpenAI’s API provides access to several different engines - Ada, Babbage, Curie and Davinci. While Davinci is generally the most capable engine, the other engines can perform certain tasks extremely well and in some cases significantly faster. The other engines have cost advantages. For example, Curie can perform many of the same tasks as Davinci, but faster and for 1/10th the cost. We encourage developers to experiment with using the other models and try to find the one that’s the most efficient for your application. 虽然Davinci通常是能力最强的引擎,但其他引擎可以非常好地完成某些任务,在某些情况下,速度明显更快。其他引擎有成本优势。例如,Curie可以执行许多与Davinci相同的任务,但速度更快,而且成本是Davinci的1/10。我们鼓励开发者尝试使用其他模型,并尝试找到对你的应用最有效的模型。 Davinci: Complex intent, cause and effect, summarization for audience Curie: Language translation, complex classification, text sentiment, summarization Babbage: Moderate classification, semantic search classification Ada: Parsing text, simple classification, address correction, keywords ##### Completions Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position. ##### Searches Given a query and a set of documents or labels, the model ranks each document based on its semantic similarity to the provided query. ##### Classifications Given a query and a set of labeled examples, the model will predict the most likely label for the query. Useful as a drop-in replacement for any ML classification or text-to-label task. ##### Answers Given a question, a set of documents, and some examples, the API generates an answer to the question based on the information in the set of documents. This is useful for question-answering applications on sources of truth, like company documentation or a knowledge base. ##### GPT-3 temperature The temperature controls how much randomness is in the output. In general, the lower the temperature, the more likely GPT-3 will choose words with a higher probability of occurrence. It is particularly useful when we want GPT-3 to complete something, where there is only one answer and vice versa, if you want to generate ideas or complete a story, higher temperature will bring us more variety. 温度控制了输出中的随机性的程度。 一般来说,温度越低,GPT-3就越有可能选择出现概率较高的词。当我们想让GPT-3完成一些事情时,它特别有用,因为那里只有一个答案,反之亦然,如果你想产生想法或完成一个故事,更高的温度将带给我们更多的变化。 #### GPT-3 Demo 代码片段见: gpt.py ```python import openai openai.api_key = "" gpt = GPT(engine="davinci", temperature=0.5, max_tokens=100) ``` ##### Adding Examples for GPT Model ```python gpt.add_example(Example('Fetch unique values of DEPARTMENT from Worker table.', 'Select distinct DEPARTMENT from Worker;')) gpt.add_example(Example('Print the first three characters of FIRST_NAME from Worker table.', 'Select substring(FIRST_NAME,1,3) from Worker;')) gpt.add_example(Example("Find the position of the alphabet ('a') in the first name column 'Amitabh' from Worker table.", "Select INSTR(FIRST_NAME, BINARY'a') from Worker where FIRST_NAME = 'Amitabh';")) gpt.add_example(Example("Print the FIRST_NAME from Worker table after replacing 'a' with 'A'.", "Select CONCAT(FIRST_NAME, ' ', LAST_NAME) AS 'COMPLETE_NAME' from Worker;")) gpt.add_example(Example("Display the second highest salary from the Worker table.", "Select max(Salary) from Worker where Salary not in (Select max(Salary) from Worker);")) gpt.add_example(Example("Display the highest salary from the Worker table.", "Select max(Salary) from Worker;")) gpt.add_example(Example("Fetch the count of employees working in the department Admin.", "SELECT COUNT(*) FROM worker WHERE DEPARTMENT = 'Admin';")) gpt.add_example(Example("Get all details of the Workers whose SALARY lies between 100000 and 500000.", "Select * from Worker where SALARY between 100000 and 500000;")) gpt.add_example(Example("Get Salary details of the Workers", "Select Salary from Worker")) ``` ##### Prompt ```python prompt = "Display the lowest salary from the Worker table." output = gpt.submit_request(prompt) print(output.choices[0].text) ``` #### GPT3 free tools I tried recently for fun. https://djoann.medium.com/gpt3-free-tools-i-tried-recently-for-fun-f10ebea21d9 **Also Read:** [Top Free Resources To Learn GPT-3](https://analyticsindiamag.com/top-free-resources-to-learn-gpt-3/) 1| Create Mails With OthersideAI the tool helps users create well-written, concise, convincing emails 4x faster than before. Along with that, the tool also provides intelligent summarisation, insights, and a whole suite of other tools to manage the inbox. 2| Write Job Description With Dover 3| Ask Questions With Philosopher AI 4| Build Apps With Debuild 5| Learn Anything From Anyone ### A Definitive Guide to Completing Your First Game Jam https://blog.tarynmcmillan.com/a-definitive-guide-to-completing-your-first-game-jam?source=newsletter #### What is a game jam? **Game jams can be anywhere from a few days to a month long, but most average out at about a week to ten days**. Every game jam has a theme. Sometimes it is a total secret, but sometimes the theme is voted on by the community before the jam actually begins. Depending on the jam, there may be different rules regarding the use of assets from places like the Unity store but the general rule of thumb is that free assets are OK, paid assets are not. And of course, attribution is necessary when you’re using assets that are not your own! **The best place to look is on [Itch.io](http://itch.io/), which is a free game-hosting platform**. Of course, if you're considering a career in game development, then you'll probably want to spend the time learning one of the two main game engines: Unity (C#) or Unreal (C++).  ### 一点收获 - Micro-SaaS - ???? **Problem** Large SaaS apps solve lots of problems for lots of people. - ???? **Solution** Micro-SaaS apps solve a specific problem for a specific group of people. ### 附录:snippets - gpt.py example ```python """Creates the Example and GPT classes for a user to interface with the OpenAI API.""" import openai def set_openai_key(key): """Sets OpenAI key.""" openai.api_key = key class Example(): """Stores an input, output pair and formats it to prime the model.""" def __init__(self, inp, out): self.input = inp self.output = out def get_input(self): """Returns the input of the example.""" return self.input def get_output(self): """Returns the intended output of the example.""" return self.output def format(self): """Formats the input, output pair.""" return f"input: {self.input}\noutput: {self.output}\n" class GPT: """The main class for a user to interface with the OpenAI API. A user can add examples and set parameters of the API request.""" def __init__(self, engine='davinci', temperature=0.5, max_tokens=100): self.examples = [] self.engine = engine self.temperature = temperature self.max_tokens = max_tokens def add_example(self, ex): """Adds an example to the object. Example must be an instance of the Example class.""" assert isinstance(ex, Example), "Please create an Example object." self.examples.append(ex.format()) def get_prime_text(self): """Formats all examples to prime the model.""" return '\n'.join(self.examples) + '\n' def get_engine(self): """Returns the engine specified for the API.""" return self.engine def get_temperature(self): """Returns the temperature specified for the API.""" return self.temperature def get_max_tokens(self): """Returns the max tokens specified for the API.""" return self.max_tokens def craft_query(self, prompt): """Creates the query for the API request.""" return self.get_prime_text() + "input: " + prompt + "\n" def submit_request(self, prompt): """Calls the OpenAI API with the specified parameters.""" response = openai.Completion.create(engine=self.get_engine(), prompt=self.craft_query(prompt), max_tokens=self.get_max_tokens(), temperature=self.get_temperature(), top_p=1, n=1, stream=False, stop="\ninput:") return response def get_top_reply(self, prompt): """Obtains the best result as returned by the API.""" response = self.submit_request(prompt) return response['choices'][0]['text'] ``` - mysql backup ```bash #!/bin/bash# 以下配置信息请自己修改mysql_user="USER" #MySQL备份用户mysql_password="PASSWORD" #MySQL备份用户的密码mysql_host="localhost"mysql_port="3306"mysql_charset="utf8" #MySQL编码backup_db_arr=("db1" "db2") #要备份的数据库名称,多个用空格分开隔开 如("db1" "db2" "db3")backup_location=/var/www/mysql #备份数据存放位置,末尾请不要带"/",此项可以保持默认,程序会自动创建文件夹expire_backup_delete="ON" #是否开启过期备份删除 ON为开启 OFF为关闭expire_days=3 #过期时间天数 默认为三天,此项只有在expire_backup_delete开启时有效# 本行开始以下不需要修改backup_time=`date +%Y%m%d%H%M` #定义备份详细时间backup_Ymd=`date +%Y-%m-%d` #定义备份目录中的年月日时间backup_3ago=`date -d '3 days ago' +%Y-%m-%d` #3天之前的日期backup_dir=$backup_location/$backup_Ymd #备份文件夹全路径welcome_msg="Welcome to use MySQL backup tools!" #欢迎语# 判断MYSQL是否启动,mysql没有启动则备份退出mysql_ps=`ps -ef |grep mysql |wc -l`mysql_listen=`netstat -an |grep LISTEN |grep $mysql_port|wc -l`if [ [$mysql_ps == 0] -o [$mysql_listen == 0] ]; then echo "ERROR:MySQL is not running! backup stop!" exitelse echo $welcome_msgfi# 连接到mysql数据库,无法连接则备份退出mysql -h$mysql_host -P$mysql_port -u$mysql_user -p$mysql_password < $backup_dir/$dbname-$backup_time.sql.gz` flag=`echo $?` if [ $flag == "0" ];then echo "database $dbname success backup to $backup_dir/$dbname-$backup_time.sql.gz" else echo "database $dbname backup fail!" fi done else echo "ERROR:No database to backup! backup stop" exit fi # 如果开启了删除过期备份,则进行删除操作 if [ "$expire_backup_delete" == "ON" -a "$backup_location" != "" ];then #`find $backup_location/ -type d -o -type f -ctime +$expire_days -exec rm -rf {} \;` `find $backup_location/ -type d -mtime +$expire_days | xargs rm -rf` echo "Expired backup data delete complete!" fi echo "All database backup success! Thank you!" exitfi ```