标签 - Go语言四十二章经

Go语言四十二章经    2021-01-27 10:09:44    32    0    0

《Go语言四十二章经》第四十二章 WEB框架(Gin)

作者:李骁

42.1 有关于Gin

Gin是Go语言写的一个web框架,API性能超强,运行速度号称较httprouter要快40x。开源网址:https://github.com/gin-gonic/gin

下载安装gin包:

  1. go get -u github.com/gin-gonic/gin

一个简单的例子:

  1. package main
  2. import "github.com/gin-gonic/gin"
  3. func main() {
  4. r := gin.Default()
  5. r.GET("/ping", func(c *gin.Context) {
  6. c.Json(200, gin.H{
  7. "message": "pong",
  8. })
  9. })
  10. r.Run() // listen and serve on 0.0.0.0:8080
  11. }

编译运行程序,打开浏览器,访问 http://localhost:8080/ping
页面显示:

  1. {"message":"pong"}

以Json格式输出了数据。

gin的功能不只是简单输出Json数据。它是一个轻量级的WEB框架,支持RestFull风格API,支持GET,POST,PUT,PATCH,DELETE,OPTIONS 等http方法,支持文件上传,分组路由,Multipart/Urlencoded FORM,以及支持JsonP,参数处理等等功能,这些都和WEB紧密相关,通过提供这些功能,使开发人员更方便地处理WEB业务。

42.2 Gin实际应用

接下来使用Gin作为框架来搭建一个拥有静态资源站点,动态WEB站点,以及RESTFull API接口站点(可专门作为手机APP应用提供服务使用)组成的,亦可根据情况分拆这套系统,每种功能独立出来单独提供服务。

下面按照一套系统但采用分站点来说明,首先是整个系统的目录结构,website目录下面static是资源类文件,为静态资源站点专用;photo目录是UGC上传图片目录,tpl是动态站点的模板。

Go语言四十二章经    2021-01-27 10:09:44    38    0    0

《Go语言四十二章经》第四十一章 网络爬虫

作者:李骁

41.1 go-colly

go-colly是用Go实现的网络爬虫框架。go-colly快速优雅,在单核上每秒可以发起1K以上请求;以回调函数的形式提供了一组接口,可以实现任意类型的爬虫。

Colly 特性:

清晰的API
快速(单个内核上的请求数大于1k)
管理每个域的请求延迟和最大并发数
自动cookie 和会话处理
同步/异步/并行抓取
高速缓存
自动处理非Unicode的编码
Robots.txt 支持

下面是官方提供的抓取例子:

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gocolly/colly"
  5. )
  6. func main() {
  7. c := colly.NewCollector()
  8. // Find and visit all links
  9. c.OnHTML("a[href]", func(e *colly.HTMLElement) {
  10. e.Request.Visit(e.Attr("href"))
  11. })
  12. c.OnRequest(func(r *colly.Request) {
  13. fmt.Println("Visiting", r.URL)
  14. })
  15. c.Visit("http://go-colly.org/")
  16. }

程序输出:

  1. Visiting http://go-colly.org/
  2. Visiting http://go-colly.org/docs/
  3. Visiting http://go-colly.org/articles/
  4. Visiting http://go-colly.org/services/
  5. Visiting http://go-colly.org/datasets/
  6. ......

Colly大致的使用说明:

在代码中导入包:

  1. import "github.com/gocolly/colly"

colly的主体是Collector对象,管理网络通信和负责在作业运行时执行附加的回掉函数。使用colly需要先初始化Colle

Go语言四十二章经    2021-01-27 10:09:44    42    0    0

《Go语言四十二章经》第四十章 LevelDB与BoltDB

作者:李骁

LevelDB 和 BoltDB 都是k/v数据库。

但LevelDB没有事务,LevelDB实现了一个日志结构化的merge tree。它将有序的key/value存储在不同文件的之中,通过db, _ := leveldb.OpenFile("db", nil),在db目录下有很多数据文件,并通过“层级”把它们分开,并且周期性地将小的文件merge为更大的文件。这让其在随机写的时候会很快,但是读的时候却很慢。

这也让LevelDB的性能不可预知:但数据量很小的时候,它可能性能很好,但是当随着数据量的增加,性能只会越来越糟糕。而且做merge的线程也会在服务器上出现问题。

LSM树而且通过批量存储技术规避磁盘随机写入问题。 LSM树的设计思想非常朴素,它的原理是把一颗大树拆分成N棵小树, 它首先写入到内存中(内存没有寻道速度的问题,随机写的性能得到大幅提升),在内存中构建一颗有序小树,随着小树越来越大,内存的小树会flush到磁盘上。磁盘中的树定期可以做merge操作,合并成一棵大树,以优化读性能。

BoltDB会在数据文件上获得一个文件锁,所以多个进程不能同时打开同一个数据库。BoltDB使用一个单独的内存映射的文件(.db),实现一个写入时拷贝的B+树,这能让读取更快。而且,BoltDB的载入时间很快,特别是在从crash恢复的时候,因为它不需要去通过读log去找到上次成功的事务,它仅仅从两个B+树的根节点读取ID。

BoltDB支持完全可序列化的ACID事务,让应用程序可以更简单的处理复杂操作。

BoltDB设计源于LMDB,具有以下特点:

  • 直接使用API存取数据,没有查询语句;
  • 支持完全可序列化的ACID事务,这个特性比LevelDB强;
  • 数据保存在内存映射的文件里。没有wal、线程压缩和垃圾回收;
  • 通过COW技术,可实现无锁的读写并发,但是无法实现无锁的写写并发,这就注定了读性能超高,但写性能一般,适合与读多写少的场景。
  • 最后,BoltDB使用Golang开发,而且被应用于influxDB项目作为底层存储。

LMDB的全称是Lightning

Go语言四十二章经    2021-01-27 10:09:44    37    0    0

《Go语言四十二章经》第三十九章 Mysql数据库

作者:李骁

39.1 database/sql包

Go 提供了database/sql包用于对SQL数据库的访问,作为操作数据库的入口对象sql.DB,主要为我们提供了两个重要的功能:

  • sql.DB 通过数据库驱动为我们提供管理底层数据库连接的打开和关闭操作.
  • sql.DB 为我们管理数据库连接池

需要注意的是,sql.DB表示操作数据库的抽象访问接口, 而非一个数据库连接对象;它可以根据driver打开关闭数据库连接,管理连接池。正在使用的连接被标记为繁忙,用完后回到连接池等待下次使用。所以,如果你没有把连接释放回连接池,会导致过多连接使系统资源耗尽。

导入mysql数据库驱动

  1. import (
  2. "database/sql"
  3. _ "github.com/go-sql-driver/mysql"
  4. )

通常来说,不应该直接使用驱动所提供的方法,而是应该使用 sql.DB,因此在导入 mysql 驱动时,这里使用了匿名导入的方式(在包路径前添加 _),当导入了一个数据库驱动后,此驱动会自行初始化并注册自己到Go的database/sql上下文中,因此我们就可以通过 database/sql 包提供的方法访问数据库了。

39.2 Mysql数据库操作

我们先建立表结构:

  1. CREATE TABLE t_article_cate (
  2. `cid` int(10) NOT NULL AUTO_INCREMENT,
  3. `cname` varchar(60) NOT NULL,
  4. `ename` varchar(100),
  5. `cateimg` varchar(255),
  6. `addtime` int(10) unsigned NOT NULL DEFAULT '0',
  7. `publishtime` int(10) unsigned NOT NULL DEFAULT '0',
  8. `scope` int(10) unsigned NOT NULL DEFAULT '10000',
  9. `status` tinyint(1) unsigned NOT NULL
Go语言四十二章经    2021-01-27 10:09:44    24    0    0

《Go语言四十二章经》第三十八章 Json数据格式

作者:李骁

38.1 序列化与反序列化

数据结构要在网络中传输或保存到文件,就必须对其编码和解码;目前存在很多编码格式:Json,XML,Gob,Google 缓冲协议等等。Go 语言支持所有这些编码格式。

结构可能包含二进制数据,如果将其作为文本打印,那么可读性是很差的。另外结构内部可能包含匿名字段,而不清楚数据的用意。

通过把数据转换成纯文本,使用命名的字段来标注,让其具有可读性。这样的数据格式可以通过网络传输,而且是与平台无关的,任何类型的应用都能够读取和输出,不与操作系统和编程语言的类型相关。

下面是一些术语说明:

  • 数据结构 --> 指定格式 = 序列化 或 编码(传输之前)
  • 指定格式 --> 数据格式 = 反序列化 或 解码(传输之后)

序列化是在内存中把数据转换成指定格式(data -> string),反之亦然(string -> data structure)

编码也是一样的,只是输出一个数据流(实现了 io.Writer 接口);解码是从一个数据流(实现了 io.Reader)输出到一个数据结构。

  1. Json.Marshal() 的函数签名是 func Marshal(v interface{}) ([]byte, error)

出于安全考虑,在 web 应用中最好使用 Json.MarshalforHTML() 函数,其对数据执行HTML转码,所以文本可以被安全地嵌在 HTML

Go语言四十二章经    2021-01-27 10:09:44    19    0    0

《Go语言四十二章经》第三十七章 context包

作者:李骁

37.1 context包

在Go中,每个请求的request在单独的goroutine中进行,处理一个request也可能涉及多个goroutine之间的交互。一个请求衍生出的各个 goroutine 之间需要满足一定的约束关系,以实现一些诸如有效期,中止routine树,传递请求全局变量之类的功能。于是Go为我们提供一个解决方案,标准context包。使用context可以使开发者方便的在这些goroutine之间传递request相关的数据、取消goroutine的signal或截止时间等。

每个goroutine在执行之前,都要先知道程序当前的执行状态,通常将这些执行状态封装在一个Context变量中,传递给要执行的goroutine中。上下文则几乎已经成为传递与请求同生存周期变量的标准方法。在网络编程下,当接收到一个网络请求Request,处理Request时,我们可能需要开启不同的goroutine来获取数据与逻辑处理,即一个请求Request,会在多个goroutine中处理。而这些goroutine可能需要共享Request的一些信息;同时当Request被取消或者超时的时候,所有从这个Request创建的所有goroutine也应该被结束。

context包不仅实现了在程序单元之间共享状态变量的方法,同时能通过简单的方法,使我们在被调用程序单元的外部,通过设置ctx变量值,将过期或撤销这些信号传递给被调用的程序单元。若存在A调用B的API,B再调用C的API,若A调用B取消,那也要取消B调用C,通过在A, B, C的API调用之间传递Context,以及判断其状态。

Context结构

  1. // Context包含过期,取消信号,request值传递等,方法在多个goroutine中协程安全
  2. type Context interface {
  3. // Done 方法在context被取消或者超时返回一个close的channel
  4. Done() <-chan struct{}
  5. Err() error
  6. // Deadline 返回con
Go语言四十二章经    2021-01-27 10:09:44    65    0    0

《Go语言四十二章经》第三十六章 net/http包

作者:李骁

在Go中,搭建一个http server简单到令人难以置信。只需要引入net/http包,写几行代码,一个http服务器就可以正常运行并接受访问请求。

下面就是Go最简单的http服务器:

  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. )
  6. func myfunc(w http.ResponseWriter, r *http.Request) {
  7. fmt.Fprintf(w, "hi")
  8. }
  9. func main() {
  10. http.HandleFunc("/", myfunc)
  11. http.ListenAndServe(":8080", nil)
  12. }

访问 http://localhost:8080/ , 我们可以看到网页输出"hi" !

下面我们通过分析net/http的源代码,来深入理解这个包的实现原理。在net/http源代码中,我们可以深深体会到Go语言的接口设计哲学,这个包主要有4个文件,分别是:

client.go

server.go

request.go

response.go

我们知道有http Request 请求和 http Response 响应,以及client和server,我们先从这四个方面讲讲net/http包:

36.1 Request

http Request请求是由客户端发出的消息, 用来使服务器执行动作.发出的消息包括起始行, Headers, Body。

在net/http包中,request.go文件定义了结构:

  1. type Request struct
  2. 代表客户端给服务器端发送的一个请求或者是服务端收到的一个请求,但是服务器端和客户端使用Request时语义区别很大。
  3. // 利用指定的method, url以及可选的body返回一个新的请求.如果body参数实现了
  4. // io.Closer接口,Request返回值的Body 字段会被设置为body,并会被Client
  5. // 类型的Do、Post和PostForm方法以及Transport.RoundTrip
Go语言四十二章经    2021-01-27 10:09:44    11    0    0

《Go语言四十二章经》第三十五章 模板

作者:李骁

Printf也可以做到输出格式化,当然,对于简单的例子来说足够了,但是我们有时候还是需要复杂的输出格式,甚至需要将格式化代码分离开来。这时,可以使用text/template和html/template。

Go 官方库提供了两个模板库: text/template 和 html/template 。这两个库类似,当需要输出html格式的代码时需要使用 html/template。

35.1 text/template

所谓模板引擎,则将模板和数据进行渲染的输出格式化后的字符程序。对于Go,执行这个流程大概需要三步。

  • 创建模板对象
  • 加载模板
  • 执行渲染模板

其中最后一步就是把加载的字符和数据进行格式化。

  1. package main
  2. import (
  3. "log"
  4. "os"
  5. "text/template"
  6. )
  7. const templ = `
  8. {{range .}}----------------------------------------
  9. Name: {{.Name}}
  10. Price: {{.Price | printf "%4s"}}
  11. {{end}}`
  12. var report = template.Must(template.New("report").Parse(templ))
  13. type Book struct {
  14. Name string
  15. Price float64
  16. }
  17. func main() {
  18. Data := []Book{{"《三国演义》", 19.82}, {"《儒林外史》", 99.09}, {"《史记》", 26.89}}
  19. if err := report.Execute(os.Stdout, Data); err != nil {
  20. log.Fatal(err)
  21. }
  22. }
  1. 程序输出:
  2. ----------------------------------------
  3. Name: 《三国演义》
  4. Price: %!s(float64=19.82)
  5. -------------------------------------
Go语言四十二章经    2021-01-27 10:09:44    10    0    0

《Go语言四十二章经》第三十四章 命令行 flag 包

作者:李骁

34.1 命令行

写命令行程序时需要对命令参数进行解析,这时我们可以使用os库。os可以通过变量Args来获取命令参数,os.Args返回一个字符串数组,其中第一个参数就是执行文件本身。

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. )
  6. func main() {
  7. fmt.Println(os.Args)
  8. }

编译执行后执行

  1. $ ./cmd -user="root"
  2. [./cmd -user=root]

这种方式操作起来要自己封装,比较费时费劲。

34.2 flag包

Go提供了flag库,可以很方便的操作命名行参数,下面介绍下flag的用法。

几个概念:

1)命令行参数(或参数):是指运行程序提供的参数

2)已定义命令行参数:是指程序中通过flag.Xxx等这种形式定义了的参数

3)非flag(non-flag)命令行参数(或保留的命令行参数):先可以简单理解为flag包不能解析的参数

  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. "os"
  6. )
  7. var (
  8. h, H bool
  9. v bool
  10. q *bool
  11. D string
  12. Conf string
  13. )
  14. func init() {
  15. flag.BoolVar(&h, "h", false, "帮助信息")
  16. flag.BoolVar(&h, "H", false, "帮助信息")
  17. flag.BoolVar(&v, "v", false, "显示版本号")
  18. //
  19. flag.StringVar(&D, "D", "deamon", "set descripton ")
  20. flag.StringVar(&Conf, "Conf", "/dev/conf/cli.conf", "set Conf filename ")
  21. // 另一种绑定方式
  22. q = flag.Bool("q", false, "退出程序")
  23. // 像flag.Xxx函数格式都是一样的
Go语言四十二章经    2021-01-27 10:09:44    16    0    0

《Go语言四十二章经》第三十三章 Socket网络

作者:李骁

33.1 Socket基础知识

tcp/udp、ip构成了网络通信的基石,tcp/ip是面向连接的通信协议,要求建立连接时进行3次握手确保连接已被建立,关闭连接时需要4次通信来保证客户端和服务端都已经关闭,也就是我们常说的三次握手,四次挥手。在通信过程中还有保证数据不丢失,在连接不畅通时还需要进行超时重试等等。

socket就是封装了这一套基于tcp/udp/ip协议细节,提供了一系列套接字接口进行通信。

我们知道Socket有两种:TCP Socket和UDP Socket,TCP和UDP是协议,而要确定一个进程的需要三元组,还需要IP地址和端口。

  • IPv4地址

目前的全球因特网所采用的协议族是TCP/IP协议。IP是TCP/IP协议中网络层的协议,是TCP/IP协议族的核心协议。目前主要采用的IP协议的版本号是4(简称为IPv4),IPv4的地址位数为32位,也就是最多有2的32次方的网络设备可以联到Internet上。

地址格式类似这样:127.0.0.1

  • IPv6地址

IPv6是新一版本的互联网协议,也可以说是新一代互联网的协议,它是为了解决IPv4在实施过程中遇到的各种问题而被提出的,IPv6采用128位地址长度,几乎可以不受限制地提供地址。在IPv6的设计过程中除了一劳永逸地解决了地址短缺问题以外,还考虑了在IPv4中解决不好的其它问题,主要有端到端IP连接、服务质量(QoS)、安全性、多播、移动性、即插即用等。

地址格式类似这样:2002:c0e8:82e7:0:0:0:c0e8:82e7

33.2 TCP 与 UDP

Go是自带runtime的跨平台编程语言,Go中暴露给语言使用者的tcp socket api是建立OS原生tcp socket接口之上的,所以在使用上相对简单。

TCP Socket

建立网络连接过程:TCP连接的建立需要经历客户端和服务端的三次握手的过程。Go 语言net包封装了系列API,在TCP连接中,服务端是一个标准的Listen + Accept的结构,而在客户端Go语言使用net.Dial或

1/5