Go简单实践
实战案例一:猜谜游戏
1.
1 | maxNum := 100 |
使用**math/rand/**库:rand.Seed(time.Now().UnixNano())
实现一个纳秒级更新的随机种子库;然后使用rand.Intn()
函数实现随机数的生成。
rand.Intn(Number)函数是在一个左开右闭的区间内生成,rand.Int(Number)函数在一个全闭范围内生成。
2.
1 | reader := bufio.NewReader(os.Stdin) |
使用bufio库:reader=NewReader(os.Stdin)
创建一个新的对象,用于从标准输入端读入用户数据,即为reader;使用readString函数将输入放入变量Input中,碰到换行符就停止读入。如果存在错误信息(err不等于nil,相当于none),就打印错误提示并完成程序。
最后,在Input变量中只用strings.trim
函数去除换行符和回车符,并且将输入字符串转化为整型变量guess。
3.
1 | if guess > secretNumber { |
加上判断,完成一次程序。需要多次输入,我们需要for循环。
该程序所用到的go库为:bufio,fmt,math/rand,os(os.Stdin),strconv,strings,time
实战案例二:在线词典
http通信包括:请求方法-请求头-请求体-响应-响应状态码-读取响应体-错误处理
- 首先在需要连接的网页中获取cURL并编写http请求代码如下(使用curlconverter.com)进行代码生成.
- 由于请求体需要的数据不局限于以上代码,则需要使用json序列化来实现请求体的变化。
- 通过oktools.net生成结构体代码解析response
- 输出相应的音标,解释等。将在线词典功能封装为一个函数;在主函数中读取命令行中的参数,载入word,运行程序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
)
//通过json序列化 实现请求体的变化
type DictRequest struct{
Transtype string `json:"trans_type"`
Source string `json:"source"`
}
//解析response
type DictResponse struct {
Rc int `json:"rc"`
Wiki struct {
} `json:"wiki"`
Dictionary struct {
Prons struct {
EnUs string `json:"en-us"`
En string `json:"en"`
} `json:"prons"`
Explanations []string `json:"explanations"`
Synonym []string `json:"synonym"`
Antonym []string `json:"antonym"`
WqxExample [][]string `json:"wqx_example"`
Entry string `json:"entry"`
Type string `json:"type"`
Related []interface{} `json:"related"`
Source string `json:"source"`
} `json:"dictionary"`
}
func query(word string){
client := &http.Client{} //创建一个http客户端,用于发送给http请求
request:=DictRequest{Transtype:"en2zh",Source:word}
buf,err:=json.Marshal(request)
if err!=nil{
log.Fatal(err)
}
var data=bytes.NewReader(buf)
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data) //创建一个http post请求实例,被发送至中间该地址
if err != nil {
log.Fatal(err)
}
//各式各样的请求头
req.Header.Set("authority", "api.interpreter.caiyunai.com")
req.Header.Set("accept", "application/json, text/plain, */*")
req.Header.Set("accept-language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
req.Header.Set("app-name", "xy")
req.Header.Set("content-type", "application/json;charset=UTF-8")
req.Header.Set("device-id", "6434127fee6f4eed253afdc2e6dc8683")
req.Header.Set("origin", "https://fanyi.caiyunapp.com")
req.Header.Set("os-type", "web")
req.Header.Set("os-version", "")
req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
req.Header.Set("sec-ch-ua", `"Not/A)Brand";v="99", "Microsoft Edge";v="115", "Chromium";v="115"`)
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("sec-ch-ua-platform", `"Windows"`)
req.Header.Set("sec-fetch-dest", "empty")
req.Header.Set("sec-fetch-mode", "cors")
req.Header.Set("sec-fetch-site", "cross-site")
req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.188")
req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
resp, err := client.Do(req) //发送http请求并等待响应
if err != nil {
log.Fatal(err)
} //出现错误则打印错误结果并终止程序
defer resp.Body.Close() //延迟关闭响应的主体
bodyText, err := ioutil.ReadAll(resp.Body) //读取相应主体的内容
if err != nil {
log.Fatal(err)
} //错误则
var dictResponse DictResponse
err=json.Unmarshal(bodyText,&dictResponse)
if err!=nil{
log.Fatal(err)
}
fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)
for _,item:=range dictResponse.Dictionary.Explanations{
fmt.Println(item)
}
} //遍历切片,忽略下标值,输出每一个解释
func main() {
if len(os.Args)!=2{ //`os.Args`是一个字符串切片,检查命令行参数的数量是否等于2
fmt.Fprintf(os.Stderr, `usage: simpleDict WORD
example:simpleDict hello
`) //如果命令行参数数量不等於2,将说明信息打印到标准错误输出(stderr),`os.stderr`表示标准错误输出流
os.Exit(1) //终止程序执行,返回1(非零的状态码表示程序异常终止)
}
word:=os.Args[1] //读取命令行的第二个参数(下标为1)
query(word)
}
我真的要疯了.jpg之实战案例三:socks5代理,完全听不懂,大哥。
Go语言进阶与依赖管理
并发(多线程单核√)与并行(多线程多核)
- Goroutine:协程(√)/线程
- CSP:通过通信实现并发处理,而不共享数据
- Channel
- 并发安全 Lock
- waitgroup
依赖管理
- 依赖管理演进:GOPATH-Go Vender-Go Module
- 依赖管理三要素
- 配置文件,描述依赖(go.mod)
- 依赖管理基本单元,原生库,单元依赖
- version:语义化/commit伪版本
- indirect间接依赖
- incompatible
- 依赖分发-Proxy
- 本地工具 go mod:init,download,tidy
- 配置文件,描述依赖(go.mod)
Go语言工程实践之测试
- 单元测试
- _test.go结尾
- func TestXxx(*testing.T)
- 初始化逻辑放到TestMain中:测试前准备工作;测试后的收尾工作
- testify/assert包可实现结果的equal/notequal
- 代码覆盖率
- 依赖
- mock
基准测试
设置一个简单的社区话题页面
需求:
- 展示话题(标题,文字描述)和回帖列表
- 实现一个本地web服务
- 话题和回帖数据用文件存储
ER图
分层结构
File-数据层-逻辑层-视图层-Client
高质量编程及编码规范
编码规范
- 简单性、可读性、生产力
- 编码规范
- 代码格式:gofmt自动格式化代码/goimprts=gofmt+依赖包管理
- 注释:代码作用、如何做的、实现原因、什么情况会出错
· 公共符号始终要注释 - 命名规范:
- variable
- 缩略词全大写,位于变量开头且不需要导出时,使用全小写
- 全局变量需要在名字中提供更多上下文信息
function - 不携带包名的上下文信息
- 当名为foo的包某个函数返回类型Foo时,可以省略类型信息而不出现歧义
- 当名为foo的包某个函数返回类型T时,可以在函数名中加入类型信息
- package
- 只有小写字母组成
- 不与标准库同名
- 不适用常用变量名作为包名
- 使用单数而不是复数
- 谨慎使用缩写
- 控制流程
- 避免嵌套
- 错误和异常处理
- 简单错误:优先使用errors.New(string)创建匿名变量直接表示简单错误;如果有格式化要求,使用fmt.Errorf
- 错误的Wrap(包装)和Unwrap(解包装):提供一个error嵌套另一个error,生成error的跟踪链;在fmt.Errorf中使用%w来将一个错误关联至错误链中
- 错误判定:判定一个错误是否为特定错误,使用error.ls,可以判断错误链上的所有错误是否含有特定错误;使用errors.As在错误链上获取特定种类的错误
- panic:不建议
- recover:只能在被defer的函数中使用;嵌套无法生效;只在当前goroutine生效;defer的语句是后进先出。如果需要更多上下文信息,可以recover后在log中记录当前的调用栈
性能优化建议
- Benchmark
go test -bench=. -benchmen
- Slice预分配内存;大内存未释放(copy代替re-slice)
- Map预分配内存
- 字符串处理:使用strings.Builder/strings.Buffer做字符串拼接
- 空结构体:节省内存
- atomic包:效率比锁高;sync.Mutex应该用来保护一段逻辑而不是保护一个变量;对于非数值操作,而可以使用atomic.Value
- Benchmark
性能调优
- 性能分析工具pprof:可视化和分析性能分析数据,可以知道程序在什么地方耗费了多少CPU、Memory
https://github.com/wolfogre/go-pprof-practice1
2
3
4
5go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10" //进入pprof
top //查看占用资源最多的函数
list 关键字 //查找代码行
web //调用关系可视化
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap" //处理堆内存问题
- 性能分析工具pprof:可视化和分析性能分析数据,可以知道程序在什么地方耗费了多少CPU、Memory
性能调优案例
- 业务服务优化
- 建立服务性能评估手段
- 服务性能评估方式
- 请求流量构造
- 压测范围
- 单机器压测
- 集群压测
- 性能数据采集
- 单机性能数据
- 集群性能数据
- 分析性能数据(使用库不规范、高并发场景优化不足…)
- 重点优化项改造(diff)
- 优化效果验证
- 重复压测验证
- 上限评估优化结果:关注服务监控、逐步放量、收集性能数据
- 进一步优化,服务整体链路分析
- 规范上有服务调用接口
- 分析链路,提升服务性能
- 基础库优化
- AB实验SDK优化
- 设计完善改造方案、数据按需获取、数据序列协议优化
- 内部压测验证
- 对光业务服务落地验证
- Go语言优化
- 编译器优化:内存分配、代码编译流程、内部压测验证、推广业务服务落地验证