1、学习曲线
在营口等地区,都构建了全面的区域性战略布局,加强发展的系统性、市场前瞻性、产品创新能力,以专注、极致的服务理念,为客户提供做网站、网站制作 网站设计制作定制设计,公司网站建设,企业网站建设,品牌网站制作,营销型网站建设,外贸营销网站建设,营口网站建设费用合理。
它包含了类C语法、GC内置和工程工具。这一点非常重要,因为Go语言容易学习,所以一个普通的大学生花一个星期就能写出来可以上手的、高性能的应用。在国内大家都追求快,这也是为什么国内Go流行的原因之一。
2、效率
Go拥有接近C的运行效率和接近PHP的开发效率,这就很有利的支撑了上面大家追求快速的需求。
3、出身名门、血统纯正
之所以说Go语言出身名门,是因为我们知道Go语言出自Google公司,这个公司在业界的知名度和实力自然不用多说。Google公司聚集了一批牛人,在各种编程语言称雄争霸的局面下推出新的编程语言,自然有它的战略考虑。而且从Go语言的发展态势来看,Google对它这个新的宠儿还是很看重的,Go自然有一个良好的发展前途。我们看看Go语言的主要创造者,血统纯正这点就可见端倪了。
4、组合的思想、无侵入式的接口
Go语言可以说是开发效率和运行效率二者的完美融合,天生的并发编程支持。Go语言支持当前所有的编程范式,包括过程式编程、面向对象编程以及函数式编程。
5、强大的标准库
这包括互联网应用、系统编程和网络编程。Go里面的标准库基本上已经是非常稳定,特别是我这里提到的三个,网络层、系统层的库非常实用。
6、部署方便
我相信这一点是很多人选择Go的最大理由,因为部署太方便,所以现在也有很多人用Go开发运维程序。
7、简单的并发
它包含降低心智的并发和简易的数据同步,我觉得这是Go最大的特色。之所以写正确的并发、容错和可扩展的程序如此之难,是因为我们用了错误的工具和错误的抽象,Go可以说这一块做的相当简单。
8、稳定性
Go拥有强大的编译检查、严格的编码规范和完整的软件生命周期工具,具有很强的稳定性,稳定压倒一切。那么为什么Go相比于其他程序会更稳定呢?这是因为Go提供了软件生命周期的各个环节的工具,如go
tool、gofmt、go test。
这个问题说来话长,我先表达一下我的观点,Go语言从语法层面提供区分错误和异常的机制是很好的做法,比自己用单个返回值做值判断要方便很多。
上面看到很多知乎大牛把异常和错误混在一起说,有认为Go没有异常机制的,有认为Go纯粹只有异常机制的,我觉得这些观点都太片面了。
具体对于错误和异常的讨论,我转发一下前阵子写的一篇日志抛砖引玉吧。
============================
最近连续遇到朋友问我项目里错误和异常管理的事情,之前也多次跟团队强调过错误和异常管理的一些概念,所以趁今天有动力就赶紧写一篇Go语言项目错误和异常管理的经验分享。
首先我们要理清:什么是错误、什么是异常、为什么需要管理。然后才是怎样管理。
错误和异常从语言机制上面讲,就是error和panic的区别,放到别的语言也一样,别的语言没有error类型,但是有错误码之类的,没有panic,但是有throw之类的。
在语言层面它们是两种概念,导致的是两种不同的结果。如果程序遇到错误不处理,那么可能进一步的产生业务上的错误,比如给用户多扣钱了,或者进一步产生了异常;如果程序遇到异常不处理,那么结果就是进程异常退出。
在项目里面是不是应该处理所有的错误情况和捕捉所有的异常呢?我只能说,你可以这么做,但是估计效果不会太好。我的理由是:
如果所有东西都处理和记录,那么重要信息可能被淹没在信息的海洋里。
不应该处理的错误被处理了,很容易导出BUG暴露不出来,直到出现更严重错误的时候才暴露出问题,到时候排查就很困难了,因为已经不是错误的第一现场。
所以错误和异常最好能按一定的规则进行分类和管理,在第一时间能暴露错误和还原现场。
对于错误处理,Erlang有一个很好的概念叫速错,就是有错误第一时间暴露它。我们的项目从Erlang到Go一直是沿用这一设计原则。但是应用这个原则的前提是先得区分错误和异常这两个概念。
错误和异常上面已经提到了,从语言机制层面比较容易区分它们,但是语言取决于人为,什么情况下用错误表达,什么情况下用异常表达,就得有一套规则,否则很容易出现全部靠异常来做错误处理的情况,似乎Java项目特别容易出现这样的设计。
这里我先假想有这样一个业务:游戏玩家通过购买按钮,用铜钱购买宝石。
在实现这个业务的时候,程序逻辑会进一步分化成客户端逻辑和服务端逻辑,客户端逻辑又进一步因为设计方式的不同分化成两种结构:胖客户端结构、瘦客户端结构。
胖客户端结构,有更多的本地数据和懂得更多的业务逻辑,所以在胖客户端结构的应用中,以上的业务会实现成这样:客户端检查缓存中的铜钱数量,铜钱数量足够的时候购买按钮为可用的亮起状态,用户点击购买按钮后客户端发送购买请求到服务端;服务端收到请求后校验用户的铜钱数量,如果铜钱数量不足就抛出异常,终止请求过程并断开客户端的连接,如果铜钱数量足够就进一步完成宝石购买过程,这里不继续描述正常过程。
因为正常的客户端是有一步数据校验的过程的,所以当服务端收到不合理的请求(铜钱不足以购买宝石)时,抛出异常比返回错误更为合理,因为这个请求只可能来自两种客户端:外挂或者有BUG的客户端。如果不通过抛出异常来终止业务过程和断开客户端连接,那么程序的错误就很难被第一时间发现,攻击行为也很难被发现。
我们再回头看瘦客户端结构的设计,瘦客户端不会存有太多状态数据和用户数据也不清楚业务逻辑,所以客户端的设计会是这样:用户点击购买按钮,客户端发送购买请求;服务端收到请求后检查铜钱数量,数量不足就返回数量不足的错误码,数量足够就继续完成业务并返回成功信息;客户端收到服务端的处理结果后,在界面上做出反映。
在这种结构下,铜钱不足就变成了业务逻辑范围内的一种失败情况,但不能提升为异常,否则铜钱不足的用户一点购买按钮都会出错掉线。
所以,异常和错误在不同程序结构下是互相转换的,我们没办法一句话的给所有类型所有结构的程序一个统一的异常和错误分类规则。
但是,异常和错误的分类是有迹可循的。比如上面提到的痩客户端结构,铜钱不足是业务逻辑范围内的一种失败情况,它属于业务错误,再比如程序逻辑上尝试请求某个URL,最多三次,重试三次的过程中请求失败是错误,重试到第三次,失败就被提升为异常了。
所以我们可以这样来归类异常和错误:不会终止程序逻辑运行的归类为错误,会终止程序逻辑运行的归类为异常。
因为错误不会终止逻辑运行,所以错误是逻辑的一部分,比如上面提到的瘦客户端结构,铜钱不足的错误就是业务逻辑处理过程中需要考虑和处理的一个逻辑分支。而异常就是那些不应该出现在业务逻辑中的东西,比如上面提到的胖客户端结构,铜钱不足已经不是业务逻辑需要考虑的一部分了,所以它应该是一个异常。
错误和异常的分类需要通过一定的思维训练来强化分类能力,就类似于面向对象的设计方式一样的,技术实现就摆在那边,但是要用好需要不断的思维训练不断的归类和总结,以上提到的归类方式希望可以作为一个参考,期待大家能发现更多更有效的归类方式。
接下来我们讲一下速错和Go语言里面怎么做到速错。
速错我最早接触是在做的时候就体验到的,当然跟Erlang的速错不完全一致,那时候也没有那么高大上的一个名字,但是对待异常的理念是一样的。
在.NET项目开发的时候,有经验的程序员都应该知道,不能随便re-throw,就是catch错误再抛出,原因是异常的第一现场会被破坏,堆栈跟踪信息会丢失,因为外部最后拿到异常的堆栈跟踪信息,是最后那次throw的异常的堆栈跟踪信息;其次,不能随便try catch,随便catch很容易导出异常暴露不出来,升级为更严重的业务漏洞。
到了Erlang时期,大家学到了速错概念,简单来讲就是:让它挂。只有挂了你才会第一时间知道错误,但是Erlang的挂,只是Erlang进程的异常退出,不会导致整个Erlang节点退出,所以它挂的影响层面比较低。
在Go语言项目中,虽然有类似Erlang进程的Goroutine,但是Goroutine如果panic了,并且没有recover,那么整个Go进程就会异常退出。所以我们在Go语言项目中要应用速错的设计理念,就要对Goroutine做一定的管理。
在我们的游戏服务端项目中,我把Goroutine按挂掉后的结果分为两类:1、挂掉后不影响其他业务或功能的;2、挂掉后业务就无法正常进行的。
第一类Goroutine典型的有:处理各个玩家请求的Goroutine,因为每个玩家连接各自有一个Goroutine,所以挂掉了只会影响单个玩家,不会影响整体业务进行。
第二类Goroutine典型的有:数据库同步用的Goroutine,如果它挂了,数据就无法同步到数据库,游戏如果继续运行下去只会导致数据回档,还不如让整个游戏都异常退出。
这样一分类,就可以比较清楚哪些Goroutine该做recover处理,哪些不该做recover处理了。
那么在做recover处理时,要怎样才能尽量保留第一现场来帮组开发者排查问题原因呢?我们项目中通常是会在最外层的recover中把错误和堆栈跟踪信息记进日志,同时把关键的业务信息,比如:用户ID、来源IP、请求数据等也一起记录进去。
为此,我们还特地设计了一个库,用来格式化输出堆栈跟踪信息和对象信息,项目地址:funny/debug · GitHub
通篇写下来发现比我预期的长很多,所以这里我做一下归纳总结,帮组大家理解这篇文章所要表达的:
错误和异常需要分类和管理,不能一概而论
错误和异常的分类可以以是否终止业务过程作为标准
错误是业务过程的一部分,异常不是
不要随便捕获异常,更不要随便捕获再重新抛出异常
Go语言项目需要把Goroutine分为两类,区别处理异常
在捕获到异常时,需要尽可能的保留第一现场的关键数据
以上仅为一家之言,抛砖引玉,希望对大家有所帮助。
应用于搭建 Web 服务器,存储集群或类似用途的巨型中央服务器的系统编程语言。
Go 是谷歌的编程语言,而不是社区的。在这位博主看来,虽然 Go 语言拥有一个贡献者社区,但是它并不是社区的项目,只是谷歌的一个项目。所以只要是谷歌反对的东西,没有人可以把这个东西加到 Go 语言中。
InfoQ 记者也第一时间联系了《Go 并发编程实战》作者、前轻松筹大数据负责人郝林,他的观点是:Go 语言是大家的,只有伪爱好者才会谈舍弃。在郝林看来,Go 语言官方团队在谷歌内部实属一个很小的团队,但其成员几乎个个都是技术大神。
很多社区成员为 Go 语言贡献了很多重要并且有价值的东西,这些从贡献者和提交者的多样性就可以看出来。但谷歌作为整个 Go 社区的守门人,它独自决定什么东西可以被 Go 语言接受,什么不能被接受。
在 Go 语言模块系统上发生的一件事情,谷歌 Go 语言核心团队的一名成员放弃了由外部 Go 社区开发的一个模块系统,因为它使用了另一种不同的模型。Go 语言拥有一个贡献者社区,但是它并不是一个社区项目。
三次握手:
1. 主动发起连接请求端(客户端),发送 SYN 标志位,携带数据包、包号
2. 被动接收连接请求端(服务器),接收 SYN,回复 ACK,携带应答序列号。同时,发送SYN标志位,携带数据包、包号
3. 主动发起连接请求端(客户端),接收SYN 标志位,回复 ACK。
被动端(服务器)接收 ACK —— 标志着 三次握手建立完成( Accept()/Dial() 返回 )
四次挥手:
1. 主动请求断开连接端(客户端), 发送 FIN标志,携带数据包
2. 被动接受断开连接端(服务器), 发送 ACK标志,携带应答序列号。 —— 半关闭完成。
3. 被动接受断开连接端(服务器), 发送 FIN标志,携带数据包
4. 主动请求断开连接端(客户端), 发送 最后一个 ACK标志,携带应答序列号。—— 发送完成,客户端不会直接退出,等 2MSL时长。
等 2MSL待目的:确保服务器 收到最后一个ACK
滑动窗口:
通知对端本地存储数据的 缓冲区容量。—— write 函数在对端 缓冲区满时,有可能阻塞。
TCP状态转换:
1. 主动发起连接请求端:
CLOSED —— 发送SYN —— SYN_SENT(了解) —— 接收ACK、SYN,回发 ACK —— ESTABLISHED (数据通信)
2. 主动关闭连接请求端:
ESTABLISHED —— 发送FIN —— FIN_WAIT_1 —— 接收ACK —— FIN_WAIT_2 (半关闭、主动端)
—— 接收FIN、回复ACK —— TIME_WAIT (主动端) —— 等 2MSL 时长 —— CLOSED
3. 被动建立连接请求端:
CLOSED —— LISTEN —— 接收SYN、发送ACK、SYN —— SYN_RCVD —— 接收 ACK —— ESTABLISHED (数据通信)
4. 被动断开连接请求端:
ESTABLISHED —— 接收 FIN、发送 ACK —— CLOSE_WAIT —— 发送 FIN —— LAST_ACK —— 接收ACK —— CLOSED
windows下查看TCP状态转换:
netstat -an | findstr 端口号
Linux下查看TCP状态转换:
netstat -an | grep 端口号
TCP和UDP对比:
TCP: 面向连接的可靠的数据包传递。 针对不稳定的 网络层,完全弥补。ACK
UDP:无连接不可靠的报文传输。 针对不稳定的 网络层,完全不弥补。还原网络真实状态。
优点 缺点
TCP: 可靠、顺序、稳定 系统资源消耗大,程序实现繁复、速度慢
UDP:系统资源消耗小,程序实现简单、速度快 不可靠、无序、不稳定
使用场景:
TCP:大文件、可靠数据传输。 对数据的 稳定性、准确性、一致性要求较高的场合。
UDP:应用于对数据时效性要求较高的场合。 网络直播、电话会议、视频直播、网络游戏。
UDP-CS-Server实现流程:
1. 创建 udp地址结构 ResolveUDPAddr(“协议”, “IP:port”) —— udpAddr 本质 struct{IP、port}
2. 创建用于 数据通信的 socket ListenUDP(“协议”, udpAddr ) —— udpConn (socket)
3. 从客户端读取数据,获取对端的地址 udpConn.ReadFromUDP() —— 返回:n,clientAddr, err
4. 发送数据包给 客户端 udpConn.WriteToUDP("数据", clientAddr)
UDP-CS-Client实现流程:
1. 创建用于通信的 socket。 net.Dial("udp", "服务器IP:port") —— udpConn (socket)
2. 以后流程参见 TCP客户端实现源码。
UDPserver默认就支持并发!
------------------------------------
命令行参数: 在main函数启动时,向整个程序传参。 【重点】
语法: go run xxx.go argv1 argv2 argv3 argv4 。。。
xxx.exe: 第 0 个参数。
argv1 :第 1 个参数。
argv2 :第 2 个参数。
argv3 :第 3 个参数。
argv4 :第 4 个参数。
使用: list := os.Args 提取所有命令行参数。
获取文件属性函数:
os.stat(文件访问绝对路径) —— fileInfo 接口
fileInfo 包含 两个接口。
Name() 获取文件名。 不带访问路径
Size() 获取文件大小。
网络文件传输 —— 发送端(客户端)
1. 获取命令行参数,得到文件名(带路径)filePath list := os.Args
2. 使用 os.stat() 获取 文件名(不带路径)fileName
3. 创建 用于数据传输的 socket net.Dial("tcp", “服务器IP+port”) —— conn
4. 发送文件名(不带路径) 给接收端, conn.write()
5. 读取 接收端回发“ok”,判断无误。封装函数 sendFile(filePath, conn) 发送文件内容
6. 实现 sendFile(filePath, conn)
1) 只读打开文件 os.Open(filePath)
for {
2) 从文件中读数据 f.Read(buf)
3) 将读到的数据写到socket中 conn.write(buf[:n])
4)判断读取文件的 结尾。 io.EOF. 跳出循环
}
网络文件传输 —— 接收端(服务器)
1. 创建用于监听的 socket net.Listen() —— listener
2. 借助listener 创建用于 通信的 socket listener.Accpet() —— conn
3. 读取 conn.read() 发送端的 文件名, 保存至本地。
4. 回发 “ok”应答 发送端。
5. 封装函数,接收文件内容 recvFile(文件路径)
1) f = os.Create(带有路径的文件名)
for {
2)从 socket中读取发送端发送的 文件内容 。 conn.read(buf)
3) 将读到的数据 保存至本地文件 f.Write(buf[:n])
4) 判断 读取conn 结束, 代表文件传输完成。 n == 0 break
}
Go语言于2009年11月正式宣布推出,成为开放源代码项目,并在Linux及Mac OS X平台上进行了实现,后追加Windows系统下的实现。
谷歌资深软件工程师罗布·派克(Rob Pike)表示,“Go让我体验到了从未有过的开发效率。”派克表示,和今天的C++或C一样,Go是一种系统语言。他解释道,“使用它可以进行快速开发,同时它还是一个真正的编译语言,我们之所以现在将其开源,原因是我们认为它已经非常有用和强大。”
2007年,谷歌把Go作为一个20%项目开始研发,即让员工抽出本职工作之外时间的20%,投入在该项目上。除了派克外,该项目的成员还有其它一些谷歌工程师。
派克表示,编译后Go代码的运行速度与C语言非常接近,而且编译速度非常快,就像在使用一个交互式语言。
现有编程语言均未专门对多核处理器进行优化。派克表示,Go就是谷歌工程师为这类程序编写的一种语言。它不是针对编程初学者设计的,但学习使用它也不是非常困难。Go支持面向对象,而且具有真正的封装(closures)和反射(reflection)等功能。
在学习曲线方面,派克认为Go与Java类似,对于Java开发者来说,应该能够轻松学会Go。
之所以将Go作为一个开源项目发布,目的是让开源社区有机会创建更好的工具来使用该语言,例如Eclipse IDE中的插件。目前还没有支持Go的IDE。
在目前谷歌公开发布的所有网络应用中,均没有使用Go。但是谷歌已经使用该语言开发了几个内部项目。
派克表示,Go是否会对谷歌即将推出的Chrome OS产生影响,现在还言之尚早,不过Go的确可以和Native Client配合使用。他表示,“Go可以让应用完美的运行在浏览器内。”例如,使用Go可以更高效的实现Wave,无论是在前端还是后台。
Go语言是一种新的语言,一种并发的、带垃圾回收的、快速编译的语言。它具有以下特点:
1.它可以在一台计算机上用几秒钟的时间编译一个大型的Go程序。
2.Go语言为软件构造提供了一种模型,它使依赖分析更加容易,且避免了大部分C风格include文件与库的开头。
3.Go语言是静态类型的语言,它的类型系统没有层级。因此用户不需要在定义类型之间的关系上花费时间,这样感觉起来比典型的面向对象语言更轻量级。
4.Go语言完全是垃圾回收型的语言,并为并发执行与通信提供了基本的支持。
按照其设计,Go打算为多核机器上系统软件的构造提供一种方法。
Go语言是一种编译型语言,它结合了解释型语言的游刃有余,动态类型语言的开发效率,以及静态类型的安全性。它也打算成为现代的,支持网络与多核计算的语言。要满足这些目标,需要解决一些语言上的问题:一个富有表达能力但轻量级的类型系统,并发与垃圾回收机制,严格的依赖规范等等。这些无法通过库或工具解决好,因此Go也就应运而生了。