技术标签: java golang 编程语言 go 项目经验
作者作为校招新人入职,公司用Golang, 自己有Java、PHP的 Web 开经验,熟悉前端的Vue技术栈,在此之前没写过Golang项目,只是几个月前按照菜鸟教程看了一遍Golang的语法 ,了解其天生高并发的特性,知道其有协程、管道这两个重要的特性。
入职当天就要求自学做一个Golang的新手村任务,做一个分布式的在2000并发访问下能够做到1000QPS的登录注册,期限一周,要求细节不便透露,这里就不详细展开了,本文所分享的干货主要来自于这个新手村任务的积累。
Golang 语法入门很简单,但是根据我的入门经验,最容易对初学者造成困扰的是代码的组织形式,稍有语法基础的读者建议直接看 2.2 代码组织部分。
// 单行注释
/* 多行
注释 */
// go的IDE我推荐Vscode,Goland也不错,前者我自用,后者同同事都在用
// 用 go 开发时注释中也会经常见到文档注解,如 @Version 1.0.0
// 导入包的子句在每个源文件的开头。
// Main比较特殊,它用来声明可执行文件,而不是一个库。
package main
// Import语句声明了当前文件引用的包。
import (
"fmt" // Go语言标准库中的包
"net/http" // 一个web服务器包
"strconv" // 字符串转换
"strings" // 字符串标准库
)
// 等你写习惯了go不带分好,写Java会很变扭
// 函数声明:Main是程序执行的入口。
// 不管你喜欢还是不喜欢,反正Go就用了花括号来包住函数体,花括号配合严格的缩进,代码非常易读。
func main() {
// 往标准输出打印一行。
// 用包名fmt限制打印函数。
fmt.Println("Hello world!")
// 写项目时用 log.Println("Hello world!") 会更多
// 调用当前包的另一个函数。
beyondHello()
}
// 函数可以在括号里加参数。
// 如果没有参数的话,也需要一个空括号。
func beyondHello() {
var x int // 变量声明,变量必须在使用之前声明。
x = 3 // 变量赋值。
// 可以用:=来偷懒,它自动把变量类型、声明和赋值都搞定了,这个在项目中最常用。
y := 4
sum, prod := learnMultiple(x, y) // 返回多个变量的函数
fmt.Println("sum:", sum, "prod:", prod) // 简单输出
learnTypes() // 少于y分钟,学的更多!
}
// 多变量和多返回值的函数
func learnMultiple(x, y int) (sum, prod int) {
return x + y, x * y // 返回两个值
/*
// 因为这里返回值不仅有类型,还写了名称,因此函数体其实还可以这么写
sum = x + y // 注意这里不是 :=
prod = x * y
return
*/
}
// 内置变量类型和关键词
func learnTypes() {
// 短声明给你所想。
s := "Learn Go!" // String类型
s2 := `A "raw" string literal
can include line breaks.` // 同样是String类型
// 非ascii字符。Go使用UTF-8编码。
g := 'Σ' // rune类型,int32的别名,使用UTF-8编码
f := 3.14195 // float64类型,IEEE-754 64位浮点数
c := 3 + 4i // complex128类型,内部使用两个float64表示
// Var变量可以直接初始化。
var u uint = 7 // unsigned 无符号变量,但是实现依赖int型变量的长度
var pi float32 = 22. / 7
// 字符转换
n := byte('\n') // byte是uint8的别名
// 数组类型编译的时候大小固定。
var a4 [4] int // 有4个int变量的数组,初始为0
a3 := [...]int{
3, 1, 5} // 有3个int变量的数组,同时进行了初始化
// Slice 可以动态的增删。Array和Slice各有千秋,但是使用slice的地方更多些。
s3 := []int{
4, 5, 9} // 和a3相比,这里没有省略号
s4 := make([]int, 4) // 分配一个有4个int型变量的slice,全部被初始化为0
// 常见操作
str := "带加豪,偶稀渣渣辉"
bs := []byte(str) // 字符串转化为byte切片
str2 := string(bs) // byte切片转字符串
println(str, len(str), bs, len(bs), str2, strings.Count(str, "")-1) // println函数更像 Java的 sysout
fmt.Println(str, len(str), bs, len(bs), str2, strings.Count(str2, "渣")) // 这个效果更好
var d2 [][]float64 // 声明而已,什么都没有分配
bs := []byte("a slice") // 类型转换的语法
p, q := learnMemory() // 声明p,q为int型变量的指针
fmt.Println(*p, *q) // * 取值
// Map是动态可增长关联数组,和其他语言中的hash或者字典相似。
m := map[string]int{
"three": 3, "four": 4}
m["one"] = 1
// 在Go语言中未使用的变量在编译的时候会报错,而不是warning。
// 下划线 _ 可以使你“使用”一个变量,但是丢弃它的值。
_,_,_,_,_,_,_,_,_ = s2, g, f, u, pi, n, a3, s4, bs
// 输出变量, 从结果可知上面这种操作并不会清除变量
fmt.Println(s, c, a4, s3, d2, m)
learnFlowControl() // 回到流程控制
}
// Go全面支持垃圾回收。Go有指针,但是不支持指针运算。
// 你会因为空指针而犯错,但是不会因为增加指针而犯错。
func learnMemory() (p, q *int) {
// 返回int型变量指针p和q
p = new(int) // 内置函数new分配内存
// 自动将分配的int赋值0,p不再是空的了。
s := make([]int, 20) // 给20个int变量分配一块内存
s[3] = 7 // 赋值
r := -2 // 声明另一个局部变量
return &s[3], &r // & 取地址
}
func expensiveComputation() int {
return 1e6 // 1 * 10^6
}
func learnFlowControl() {
// If需要花括号,括号就免了
if true {
fmt.Println("told ya")
}
// 用go fmt 命令可以帮你格式化代码,所以不用怕被人吐槽代码风格了,
// 也不用容忍被人的代码风格。
if false {
// pout
} else {
// gloat
}
// 如果太多嵌套的if语句,推荐使用switch
x := 1
switch x {
case 0:
case 1:
// 隐式调用break语句,匹配上一个即停止
case 2:
// 不会运行
case 3:
// 如果非要匹配上一个不停止,也不是不可
fallthrough // 但是不建议使用
default:
// 都没匹配上执行
}
// 和if一样,for也不用括号
for x := 0; x < 3; x++ {
// ++ 自增
fmt.Println("iteration", x)
}
// x在这里还是1。为什么?
// for 是go里唯一的循环关键字,不过它有很多变种
for {
// 死循环
break // 骗你的
continue // 不会运行的
}
// 和for一样,if中的:=先给y赋值,然后再和x作比较。
if y := expensiveComputation(); y > x {
x = y
}
// 闭包函数
xBig := func() bool {
return x > 100 // x是上面声明的变量引用
}
fmt.Println("xBig:", xBig()) // true (上面把y赋给x了)
x /= 1e5 // x变成10
fmt.Println("xBig:", xBig()) // 现在是false
// 当你需要goto的时候,你会爱死它的!
goto love
love:
learnInterfaces() // 好东西来了!
}
// 定义Stringer为一个接口类型,有一个方法String
type Stringer interface {
String() string // 这里可以有多个方法声明
}
// 定义pair为一个结构体,有x和y两个int型变量。
// 这个pair相当于Java中的一个类
type pair struct {
x, y int
}
// 定义pair类型的成员方法,实现Stringer接口,这里不需要显示指定 Stringer
func (p pair) String() string {
// p被叫做“接收器”
// Sprintf是fmt包中的另一个公有函数。
// 用 . 调用p中的元素。
return fmt.Sprintf("(%d, %d)", p.x, p.y)
}
func learnInterfaces() {
// 花括号用来定义结构体变量,:=在这里将一个结构体变量赋值给p。
p := pair{
3, 4}
fmt.Println(p.String()) // 调用pair类型p的String方法
var i Stringer // 声明i为Stringer接口类型
i = p // 有效!因为p实现了Stringer接口(类似java中的塑型)
// 调用i的String方法,输出和上面一样
fmt.Println(i.String())
// fmt包中的Println函数向对象要它们的string输出,实现了String方法就可以这样使用了。
// (类似java中的序列化)
fmt.Println(p) // 输出和上面一样,自动调用String函数。
fmt.Println(i) // 输出和上面一样。
learnErrorHandling()
}
// 你可以定义别的结构体作为接收器来实现 String() string 方法,实现多态,
// 在实例化时以Stringer作为其类型 (var obj Stringer), 赋值赋不同的结构体
// 可详细参考 https://www.jianshu.com/p/1227c5145cd8
// 我认为 Golang勉强可以面向对象,但是没必要对Java那套太挂念
func learnErrorHandling() {
// 一开始不适应没 try catch,习惯了你会爱上她
// ", ok"用来判断有没有正常工作
m := map[int]string{
3: "three", 4: "four"}
if x, ok := m[1]; !ok {
// ok 为false,因为m中没有1
fmt.Println("no one there")
} else {
fmt.Print(x) // 如果x在map中的话,x就是那个值喽。
}
// 错误可不只是ok,它还可以给出关于问题的更多细节。
if _, err := strconv.Atoi("non-int"); err != nil {
// _ discards value
// 输出"strconv.ParseInt: parsing "non-int": invalid syntax"
fmt.Println(err)
}
// 待会再说接口吧。同时,
learnConcurrency()
}
// c是channel类型,一个并发安全的通信对象。
func inc(i int, c chan int) {
c <- i + 1 // <-把右边的发送到左边的channel。
}
// 我们将用inc函数来并发地增加一些数字。
func learnConcurrency() {
// 用make来声明一个slice,make会分配和初始化slice,map和channel。
c := make(chan int)
// 用go关键字开始三个并发的goroutine,如果机器支持的话,还可能是并行执行。
// 三个都被发送到同一个channel。
go inc(0, c) // go is a statement that starts a new goroutine.
go inc(10, c)
go inc(-805, c)
// 从channel中独处结果并打印。
// 打印出什么东西是不可预知的。
fmt.Println(<-c, <-c, <-c) // channel在右边的时候,<-是读操作。
cs := make(chan string) // 操作string的channel
cc := make(chan chan string) // 操作channel的channel
go func() {
c <- 84 }() // 开始一个goroutine来异步发送一个新的数字
go func() {
cs <- "wordy" }() // 发送给cs
// Select类似于switch,但是每个case包括一个channel操作。
// 它随机选择一个准备好通讯的case。
select {
case i := <-c: // 从channel接收的值可以赋给其他变量
fmt.Println("it's a", i)
case <-cs: // 或者直接丢弃
fmt.Println("it's a string")
case <-cc: // 空的,还没作好通讯的准备
fmt.Println("didn't happen.")
}
// 上面c或者cs的值被取到,其中一个goroutine结束,另外一个一直阻塞。
learnWebProgramming() // Go很适合web编程,我知道你也想学!
}
// 上述函数可是涉及到了 多线程,多线程通信,但是却简单明了
// http包中的一个简单的函数就可以开启web服务器。
func learnWebProgramming() {
// ListenAndServe第一个参数指定了监听端口,第二个参数是一个接口,特定是http.Handler。
err := http.ListenAndServe(":8080", pair{
})
fmt.Println(err) // 不要无视错误。
// 如何优雅的关闭 http server: https://www.cnblogs.com/oxspirt/p/7058812.html
}
// 使pair实现http.Handler接口的ServeHTTP方法。
func (p pair) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 使用http.ResponseWriter返回数据
w.Write([]byte("You learned Go in Y minutes!"))
defer fmt.Println("最后一个执行")
// defer 语句常用于处理 io的关闭,在函数内代码执行完之后依次执行
// 执行顺序遵循压栈出栈顺序
defer func(){
fmt.Println("倒数第二个执行")
}()
}
至此你已经掌握了多数 Go语言教程中的重点内容,但是下面的Golang代码组织篇却更为重要,是笔者早期驯服Go语言时的珍贵经验。
global、var、public、private
之类的作用域限定关键词,但是Golang不是这样的,Go 语言中的 大写字母开头的方法是Public的,小写字母开头的是private的,这就是为什么fmt.Println()
的方法名是大写字母开头的原因明白了这一点,作为 Javaer 的你再去看Go 的代码,是不是觉得眉目清秀起来了?
json.Marshal
序列化一个struct
时,序列化得到的字符串是没有成员变量的空对象{}
,直到我给成员变量名改为大写字母开头,问题才解决!package
和 import
, 跟Java
中的有着较大区别,Java 中的package是一个完整的路径字符串,和目录结构是一致的,Go中路径是路径,包名是包名,import
导入时导入的不是包名,而是包的路径,包名是在导入包的代码中使用时使用的,如fmt.Println()
。GOPATH
与 go mod
,GOPATH
是 Go语言中使用的一个环境变量,它使用绝对路径提供项目的工作目录,当我们的第三方包不是官方库时都要放在GOPATH/src
下才可以使用。该方案导致第三方库和自己的项目都在GOPATH/src
目录下,鱼龙混杂,如果GOPATH/src
下面不同的包依赖的同一第三方库的不同版本,那么就会出现版本冲突,总之就是对多项目支持不友好。于是在go1.11版本之后出现了go mod
,其于Java 的 Maven
比较类似,项目根目录的go.mod
依赖配置文件类比于Maven项目根目录的pom.xml
。解决了代码组织问题,你已经掌握了使用 使用Golang 生存的基本技能
众所周知,go语言早期使用C和汇编实现,在1.5版本,其实现自举( 使用Go实现Go编译器 ),这意味着弄懂了Golang语法,然后要看懂Golang源码是不困难的,看看源码,总会有一些意料之外的收货。
net/rpc
,另一个是 net/rpc/jsonrpc
server.go
的测试代码为server_test.go
, 可以看做非常高质量的官方Demopackage main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // 监听并在 0.0.0.0:8080 上启动服务
}
func main() {
router := gin.Default()
router.Static("/assets", "./assets")
router.StaticFS("/more_static", http.Dir("my_file_system"))
router.StaticFile("/favicon.ico", "./resources/favicon.ico")
// 监听并在 0.0.0.0:8080 上启动服务
router.Run(":8080")
}
1. 生命周期:
首先总体上分为三个阶段 :setup, ruuning, done
然后每个阶段可细分:
setup(thread) 每个线程执行一次
runing:
init(args) 每个线程执行一次
delay() 每个请求执行一次
request() 每个请求执行一次
response(status, headers, body) 每个请求执行一次
done(summary, latency, requests)
这里值得注意的是,周期函数外的语句会被执行 thread + 1 次,因此想要初始化一次的东西,应该使用计数器,判断为第一次时执行
2. 变量作用域
a. lua 脚本内, 生命周期函数外变量
b. setup 阶段函数内
c. runing 阶段的函数内
d. done 阶段的函数内
e. 其他自定义函数内
这里面
- abd作用域互通
- c的所有函数中作用域互通,但是每个线程单独持有
- d可以通过 thread 获取到每个线程的局部变量
issue: 如何在每个thread中获取全局变量,如测试数据集?
- 方法一: 在生命周期函数setup中给传入的thread传入数据,缺点,不适合大量数据
- 方法二: 引用传递(其可行性还需调查wrk内存调用模型以及lua如何跨进程引用传递)从报错情况来看,猜测init阶段使用堆内存,可以加载较多数据
samples = {
} -- lua 中函数传递table时引用传递
local counter = 1 -- 全局的变量
local threads = {
}
function setup(thread)
-- 执行 t 次
if counter == 1 then
loadSamples()
end
thread:set("id", counter)
math.randomseed(os.time())
-- 线程初始化时就完成用例设定,因此是固定用户
thread:set("sample", samples[math.random(0, #samples)])
counter = counter + 1
end
function init(args)
faild = 0
responses = 0
end
-- 加载数据
function loadSamples()
local j = 0
for line in io.lines("../pressure/loginbody4lua.txt")
do
samples[j] = line
end
-- print("总共加载了"..j.."个用例")
end
-- 单元测试,验证正确性,正式压测时不注释的话脚本性能上不来
-- function response(status, headers, body)
-- -- 属于线程,每个请求完成执行一次, 统计失败
-- if status == 200 then
-- if(not string.find(body,'"code":0')) then
-- faild = faild + 1
-- end
-- wrk.thread:stop()
-- else
-- faild = faild + 1
-- end
-- responses = responses + 1
-- end
wrk.method = "POST"
wrk.host = "127.0.0.1"
wrk.port = 8000
wrk.path = "/api/v1/login"
wrk.headers["Content-Type"] = "application/json"
wrk.headers["Connection"] = "keep-alive"
function request(args)
wrk.body = sample
return wrk.format()
end
-- 200并发请求
-- redis-cli flushall
-- redis-cli flushall & wrk -t200 -c200 -d60s -s login_fixuser.lua http://127.0.0.1:8000 --latency
-- wrk -t200 -c200 -d120s -s login_fixuser.lua http://127.0.0.1:8000 --latency
-- 2000并发请求
-- redis-cli flushall
-- wrk -t2000 -c2000 -d30s -s login_fixuser.lua http://127.0.0.1:8000 --latency
-- wrk -t2000 -c2000 -d60s -s login_fixuser.lua http://127.0.0.1:8000 --latency
-- wrk -t2000 -c2000 -d120s -s login_fixuser.lua http://127.0.0.1:8000 --latency
文章浏览阅读1.2w次,点赞3次,收藏32次。——参考自:SIMATIC HMI设备 Smart 700 IE、Smart 1000 IE 操作说明HMI 设备一旦探测到操作员控件被触摸就会立即返回一个反馈。该反馈是独立的,与 PLC 的通讯无关。 因此,其并不指示相关动作是否已真正执行。设计Smart Panel 700 IE是Smart Panel 700的升级版。连接HMI与PC连接组态PC与SMART PANEL有两种连接方式,一种是通过 RS485/422 接口连接,另一种是通过以太网接口连接,后者接线图如下图所示:HM_wincc flexible 2008
文章浏览阅读570次。转自https://blog.csdn.net/guet_kite/article/details/78368928设备驱动模型概述Linux早期时候,一个驱动对应一个设备,也就对应一个硬件地址,那当有两个一样的设备的时候,就要写两个驱动,显然是不合理的。应该是从Linux2.5开始,就引入了device-bus-driver模型。其中设备驱动模型主要结构分为kset、kobject、kty..._kobject kset ktype
文章浏览阅读1.9k次。对于CSS的white-space属性,我想大部分人应该和我差不多,最常用的就是nowrap属性,最多用来做超长省略号显示的时候会用到【hiahiahia~】起因是这样的:产品doggie策划了一个元旦活动,活动主页最下边需要显示配置的活动规则,注意,是配置的活动规则,所以,免不了运营小妹要在后台配置一个活动规则,肯定不指望她们能配置html文本的呀,对吧,于是她们配置的是这样的: 1、封垫苏菲房间都是克拉夫; 2、对方萨芬的刷分放大; 3、粉打发打发打发这样色的,于..._nowarp也会换行
文章浏览阅读353次。BRVAH一.简介二.使用一.简介BRVAH是一个强大的RecyclerAdapter框架(什么是RecyclerView?),它能节约开发者大量的开发时间,集成了大部分列表常用需求解决方案。二.使用在使用时,首先要项目的build.gradle导入allprojects { repositories { google() jcenter() ..._andriod brvah适配器万能官网
文章浏览阅读91次。计算机文化基础PPT课件2019-03-15计算机文化基础PPT课件 第1章http://wenku.baidu.com/view/e7ef8b6925c52cc58bd6be97.html计算机文化基础PPT课件 第2章 Windows 2000操作系统http://wenku.baidu.com/view/e85671f5f61fb7360b4c6594.html计算机文化基础PPT课件 ..._计算机文化基础ppt
文章浏览阅读3k次。来源python123获得迭代器最后一个元素问题尝试使用 * 迭代器展开运算,返回 range(0, 1000, 4) 的最后一个元素。print([x for x in range(0,1000,4)][-1])输出:996..._处理迭代器最后一个元素
文章浏览阅读5.2k次,点赞3次,收藏25次。第七章 PX4-Mavlink解析首先我们是还是来说一说mavlink吧。Mavlink协议是无人机的一种开源通信协议。可以理解就是按照一定的格式来发送数据。这一章节涉及到了消息的打包发送和接收解析。 首先我们还是找到入口函数然后回到脚本启动中找到mavlink的启动,这个找到应该不难吧,前面几章都有这个。这里有一个需要提一下,很多_px4 mavlink 波特率
文章浏览阅读1.6k次,点赞4次,收藏8次。1. pyOpenSSL 生成RSA密钥对, 效率比较高2. rsa 加解密方法简单3. 代码import rsaimport base64from OpenSSL.crypto import PKeyfrom OpenSSL.crypto import TYPE_RSA, FILETYPE_PEM, FILETYPE_ASN1from OpenSSL.crypto import dum..._import base64 import rsa from openssl.crypto import pkey from openssl.crypto
文章浏览阅读1.8k次,点赞2次,收藏3次。同系列传送门Android 架构设计(一):设计模式分析_赵星海的博客-程序员宅基地Android 架构设计(二):分包和文件结构_赵星海的博客-程序员宅基地_android 分包结构Android 架构设计(三):技术选型_赵星海的博客-程序员宅基地关于组件化,我这边分三步与大家分享:1定义,2需求,3优劣,4改造步骤(含框架推荐);1、组件化的定义:各个业务模块可单独运行,模块相互联系只可以使用唯一的入口。如图:2、当前项目是否需要采用组件化?首先看项目大小,.._android 组件化 去除相关组件
文章浏览阅读701次。添加RecyclerView分隔线_rv_list.additemdecoration(new recyclerviewdivider距离左侧
文章浏览阅读282次。第一章 计算机系统漫游 计算机系统是由硬件和系统软件组成的。所有计算机系统都是由相似的硬件和软件组成,它们又执行着相似的功能。 以hello程序为例。 1.1信息就是位+上下文 hello程序的生命是从源程序(源文件)开始的。源程序是程序员编写的,hello.c。源程序是 0和1 的比特位,8个一组。ASCII标准来表示文本字符。 这样的文件称为文本文件,所有其他_深入理解计算机系统 jeancheng
文章浏览阅读1.4k次。方法1:读取文件中的json字符串,再用json.loads转为python字典import jsonstr_file = ‘./960x540/config.json‘with open(str_file, ‘r‘) as f:print("Load str file from {}".format(str_file))str1 = f.read()r = json.loads(str1)pri..._python 提取json元素 获取两个字段的值组成字典