应该如何组织代码结构以将工具代码和业务代码分离?

有问必答astaxie 回复了问题 • 2 人关注 • 1 个回复 • 909 次浏览 • 2016-11-16 22:42 • 来自相关话题

golang 基础之 import 详解

文章分享xieyanke 发表了文章 • 0 个评论 • 662 次浏览 • 2016-11-16 16:55 • 来自相关话题

import

golang 中的模块是通过 package 进行组织管理的,通过 import 进行导入的

几种包的导入形式:

  1. 导入标准库... 查看全部

import



golang 中的模块是通过 package 进行组织管理的,通过 import 进行导入的



几种包的导入形式:



  1. 导入标准库

    • import "fmt",是最常用导入标准的形式

    • import f "fmt",为标准库起一个别名,而后调用 fmt.Println("") 可以使用 f.Println("")

    • import . "fmt",将 fmt 启用别名".",这样就可以直接使用其内容,而不用再加 fmt,例如fmt.Println("") 可以直接写成 Println("")


  2. 导入私人库

    • import "samples/util" 这里 samples 目录位于 $GOPATH/src 的目录下,import 之后就可以通过util.XXX 使用 util 包中提供的内容,包的名字并不是由其所在的目录定义的,而是由 package 关键字定义的,例如:util 目录下每个 *.go 文件中都定义 package tools,那么在 import "samples/util" 之后需要使用实际包的名称调用即: tools.XXX,同一个目录下只允许定义一个包名,推荐与所在目录名称相同。

    • import _ "samples/util",表示不使用该包,而是只是使用该包的 init 函数,即在 import 时执行 util 包中的 init 函数,如果 util 包中有多个 init 函数,不同 .go 文件之间的多个 init 函数的执行顺序按所属 .go 的文件名称的字典排序的顺序执行,同一 *.go 文件中的多个 init 函数之间的执行顺序按由上到下执行。


求http高并发日志处理包

有问必答mintzhao 回复了问题 • 5 人关注 • 3 个回复 • 1038 次浏览 • 2016-11-16 11:39 • 来自相关话题

beego 工程可执行文件与资源、模板等文件的路径问题

有问必答astaxie 回复了问题 • 4 人关注 • 1 个回复 • 1142 次浏览 • 2016-11-15 22:51 • 来自相关话题

有没有什么好用的golang版本的git客户端(类似python的gittle)

有问必答astaxie 回复了问题 • 4 人关注 • 2 个回复 • 831 次浏览 • 2016-11-15 21:53 • 来自相关话题

go channel问题

有问必答vr 回复了问题 • 9 人关注 • 8 个回复 • 1047 次浏览 • 2016-11-15 15:32 • 来自相关话题

北京游戏公司招聘go工程师

招聘应聘6wavesbeijing 发表了文章 • 3 个评论 • 594 次浏览 • 2016-11-15 15:28 • 来自相关话题

基本要求:

1.计算机或相关专业毕业,go/java/C++语言至少精通任意其一,了解数据结构,深入理解OOP、MVC编程思想, 3年以上工作经验; 2.有完整参与过至少一款上线MMORPG游戏或者SLG项目研发; 3.熟悉TCP/IP协议... 查看全部

基本要求:


1.计算机或相关专业毕业,go/java/C++语言至少精通任意其一,了解数据结构,深入理解OOP、MVC编程思想, 3年以上工作经验;
2.有完整参与过至少一款上线MMORPG游戏或者SLG项目研发;
3.熟悉TCP/IP协议,SOCKET编程;
4.熟练使用mySql、postgreSql、mongodb、oracle任意一种;
5.具主程序经验或框架搭建经验者优先


加分项:
1.参与过一款成功项目,并对线上维护有一定经验;
2.对SQL性能调优有一定的认识和经验;
3.有SLG游戏研发经验优先

[译]Go语言内存布局

文章分享astaxie 发表了文章 • 9 个评论 • 1895 次浏览 • 2016-11-15 14:44 • 来自相关话题

go语言内存布局

在本文中,我将尝试解释Go如何在内存中构建结构体,以及结构体在字节和比特位方面是什么样子。 希望我会成功,否则本文将是非常沉闷和混乱的。

想象一下,你有一个如下的结构体。

查看全部
					

go语言内存布局


在本文中,我将尝试解释Go如何在内存中构建结构体,以及结构体在字节和比特位方面是什么样子。 希望我会成功,否则本文将是非常沉闷和混乱的。


想象一下,你有一个如下的结构体。


type MyData struct {
aByte byte
aShort int16
anInt32 int32
aSlice []byte
}

那么这个结构体究竟是什么呢? 从根本上说,它描述了如何在内存中布局数据。 这是什么意思?编译器又是如何展现出来呢? 我们来看一下。 首先让我们使用反射来检查结构中的字段。


反射之上


下面是一些使用反射来找出字段大小及其偏移量(它们相对于结构的开始位于内存中的位置)的代码。 反射可以告诉我们编译器是怎么看待类型(包括结构)的。


// First ask Go to give us some information about the MyData type
typ := reflect.TypeOf(MyData{})
fmt.Printf("Struct is %d bytes long\n", typ.Size())
// We can run through the fields in the structure in order
n := typ.NumField()
for i := 0; i < n; i++ {
field := typ.Field(i)
fmt.Printf("%s at offset %v, size=%d, align=%d\n",
field.Name, field.Offset, field.Type.Size(),
field.Type.Align())
}

除了每个字段的偏移和大小,我还打印了每个字段的对齐方式,我稍后会解释。结果如下:


Struct is 32 bytes long
aByte at offset 0, size=1, align=1
aShort at offset 2, size=2, align=2
anInt32 at offset 4, size=4, align=4
aSlice at offset 8, size=24, align=8

aByte是我们结构体中的第一个字段,偏移量为0.它使用1字节的内存。


aShort是第二个字段。它使用2字节的内存。奇怪的是偏移量是2。这是为什么呢?答案是对齐, CPU更好地访问位于2字节(“2字节边界”)的倍数的地址处的2个字节,并访问位于4字节边界上的4个字节,直到CPU的自然整数大小,在现代CPU上是8字节(64位)。


在一些较旧的RISC CPU访问错误对齐的数字引起一个故障:在一些UNIX系统上,这将是一个SIGBUS,它会停止你的程序(或内核)。一些系统能够处理这些错误并修复错误:您的代码将运行,但会缓慢的运行,因为额外的代码将由操作系统运行以修复错误。我相信英特尔和ARM的CPU也只是处理芯片上的任何不对齐:也许我们将在以后的文章中测试这一点,以及任何性能的影响。


无论如何,对齐是Go编译器跳过一个字节放置字段aShort以便它位于2字节边界的原因。因为这样,我们可以将另一个字段放进结构体中,而不使它占用更大内存。这里是我们的结构的新版本,在aByte之后立即有一个新字段anotherByte。


type MyData struct {
aByte byte
anotherByte byte
aShort int16
anInt32 int32
aSlice []byte
}

我们再次运行反射代码,可以看到anotherByte正好在aByte和aShort之间的空闲空间。 它坐落在偏移1,aShort仍然在偏移2.现在可能是时候注意我之前提到的那个神秘对齐字段。 它告诉我们和Go编译器,这个字段需要如何对齐。


Struct is 32 bytes long
aByte at offset 0, size=1, align=1
anotherByte at offset 1, size=1, align=1
aShort at offset 2, size=2, align=2
anInt32 at offset 4, size=4, align=4
aSlice at offset 8, size=24, align=8

让我看看内存


然而我们的结构体在内存中到底是什么样子? 让我们看看我们能不能找到答案。 首先让我们构建一个MyData实例,并填充一些值。我选择了应该容易在内存中找到的值。


data := MyData{
aByte: 0x1,
aShort: 0x0203,
anInt32: 0x04050607,
aSlice: []byte{
0x08, 0x09, 0x0a,
},
}

现在一些代码访问组成这个结构的字节。 我们想要获取这个结构的实例,在内存中找到它的地址,并打印出该内存中的字节。
我们使用unsafe包来帮助我们这样做。 这让我们绕过Go类型系统将指向我们的结构的指针转换为32字节数组,这个数组就是组成我们的结构体的内存数据。


dataBytes := (*[32]byte)(unsafe.Pointer(&data))
fmt.Printf("Bytes are %#v\n", dataBytes)

我们运行以上代码。 这是结果,第一个字段,aByte,从我们的结构中以粗体显示。 这是希望你期望的,单字节aByte = 0x01在偏移0。


Bytes are &[32]uint8{**0x1**, 0x0, 0x3, 0x2, 0x7, 0x6, 0x5, 0x4, 0x5a, 0x5, 0x1, 0x20, 0xc4, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}

接下来我们来看看AShort。 这是在偏移量2的位置并且长度为2.如果你记得,aShort = 0x0203,但数据显示的字节是倒序。 这是因为大多数现代CPU都是Little-Endian:该值的最低位字节首先出现在内存中。


Bytes are &[32]uint8{0x1, 0x0, **0x3, 0x2**, 0x7, 0x6, 0x5, 0x4, 0x5a, 0x5, 0x1, 0x20, 0xc4, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}

同样的事情发生在Int32 = 0x04050607。 最低位字节首先出现在内存中。


Bytes are &[32]uint8{0x1, 0x0, 0x3, 0x2, **0x7, 0x6, 0x5, 0x4**, 0x5a, 0x5, 0x1, 0x20, 0xc4, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}

神秘的插曲


现在我们看到什么? 这是aSlice = [] byte {0x08,0x09,0x0a},在偏移量8的24个字节。我没有看到我的序列0x08,0x09,0x0a的任何地方的任何符号。 这是怎么回事?


Bytes are &[32]uint8{0x1, 0x0, 0x3, 0x2, 0x7, 0x6, 0x5, 0x4, **0x5a, 0x5, 0x1, 0x20, 0xc4, 0x0, 0x0, 0x0, 0x3, 0x0**, **0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0**}

Go反射包里自有答案。 slice在Go语言中由以下结构体表示,该结构从指针数据开始,该数据指向保存切片中的数据的存储器; 然后是该存储器中的有用数据的长度Len,以及该存储器的大小Cap。


type SliceHeader struct {
Data uintptr
Len int
Cap int
}

如果把它提供给我们的代码,我们得到以下偏移和大小。 数据指针和两个长度各为8个字节,具有8个字节对齐。


Struct is 24 bytes long
Data at offset 0, size=8, align=8
Len at offset 8, size=8, align=8
Cap at offset 16, size=8, align=8

如果我们再看一下后面的内存结构,我们可以看到数据是在地址0x000000c42001055a。 之后,我们看到Len和Cap都是3,这是我们的数据的长度。


Bytes are &[32]uint8{0x1, 0x0, 0x3, 0x2, 0x7, 0x6, 0x5, 0x4, **0x5a, 0x5, 0x1, 0x20, 0xc4, 0x0, 0x0, 0x0**, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}

我们可以直接用以下代码访问这些数据字节。 首先让我们直接访问slice头,然后打印出数据指向的内存。


dataslice := *(*reflect.SliceHeader)(unsafe.Pointer(&data.aSlice))
fmt.Printf("Slice data is %#v\n",
(*[3]byte)(unsafe.Pointer(dataslice.Data)))

这是输出:


Slice data is &[3]uint8{0x8, 0x9, 0xa}

如何判断两个slice是相同的?

技术讨论insisthzr 回复了问题 • 9 人关注 • 6 个回复 • 1455 次浏览 • 2016-11-14 22:13 • 来自相关话题

练手项目,实现一个web框架

开源程序golang 发表了文章 • 4 个评论 • 1025 次浏览 • 2016-11-14 20:01 • 来自相关话题

github.com/zjj2wry/virgin,楼主也刚搞go半年多,写业务无聊了,所以想研究怎么实现一个简单的web框架,学习学习~~

github.com/zjj2wry/virgin,楼主也刚搞go半年多,写业务无聊了,所以想研究怎么实现一个简单的web框架,学习学习~~

来自沪江、滴滴、蘑菇街架构师的 Docker 实践分享

文章分享lalala 发表了文章 • 1 个评论 • 633 次浏览 • 2016-11-14 18:05 • 来自相关话题


架构师小组交流会是由国内知名公司架构师参与的技术交流会,每期选择一个时下最热门的技术话题进行实践经验分享。


Docker 作为当前最具颠覆性的开源技术之一,其轻量虚拟化、可移植性是
CI/CD、DevOps、微服务的重要实现技术。但目前技术还不够成熟,在生产实践中还存在很多问题。对此,沪江黄凯、滴滴田智伟、蘑菇街张振华、蘑菇街向靖、扇贝丁彦以及七牛云袁晓沛在本期交流会上分享了各自的经验。本文是对此次交流的整理,欢迎探讨。





自由交流


沪江黄凯


大家好,我是来自沪江的 Java 架构师,我叫黄凯。在加入沪江之前,曾在 HP 和 IBM 的云计算部门担任核心开发和架构职位。对 IaaS、PaaS、SaaS,尤其是云存储有较深入的了解。2015 年加入沪江,担任架构师职位,主导的产品有:课件云存储,云转码等等。在这些项目中,我们使用 Mesos 和 Marathon 做 Docker 的编排工具,并开发了一个 Mesos Framework 做云转码的核心框架。


那么我们为什么要使用 Docker,也是机缘巧合。由于我们的服务开始的时候不是特别多,采用的就是一种普通的架构,后来随着服务的增多,发现部署和运维花的时间太长,我们想使用一些新的方式。开始的时候研究过 Openstack,后来觉得 Openstack 慢慢没落,于是我们就选中现在使用的 Docker。我们并不把 Docker 当成 VM 在用,而是使用它的原生的,在 Baremetal 上直接安装 Docker,这样运行效率比在 VM 运行 Docker 要来的快。课件云是由很多微服务组成,不光是一些存储,这种微服务是使用 Docker 部署,就相当于编排,把这些微服务部署上去。转码这一块是使用了 Mesos 框架,和 Docker 没有特别大的关系,但是转码的应用程序,比如说我们现在应用 FFmpeg,这个程序是运行在 Docker 里面的。


为什么要选择 Marathon?第一,我觉得 Mesos+Marathon 非常的容易理解。我们也研究过 Kubernetes 和其他的一些方法,发现从运维和研究的方面来说的话,Kubernetes 实在是太重而且太复杂,后来选择了Marathon。我们现在是内部服务使用,两个部门在使用转码集群,大概是 Baremetal 有 20 台的物理机。除去我们 API 的一些服务,还有一些第三方组件的服务的话,大概是有 400 多个 Docker 容器在跑。


滴滴田智伟


大家好,我是滴滴代驾事业部架构师,代驾事业部是公司最早尝试 Docker 虚拟化的事业部。目前主要方向是业务系统及部分中间件的 Docker 化,我们做的时间也不太长,半年多的时间。线上是因为我们有老的一套发布系统,集成涉及的部门比较多,所以我们基于原来的发布系统完成了预发布环境 Docker 的部署。线下环境基于 Docker+K8s 开发内部的自动化持续交付系统及开发测试环境管理。我们在做开发和测试环境的自动化,另一方面也是做环境管理的,两套环境。对于项目并行的时候发现原来很多不够用,原来很多配置是基于端口绑死的情况。现在基于开发 Kubernetes 的话,网络隔离用了一部分,然后主要是用环境变量这一部分,主要考虑是解决一个配置可以应用到在多个环境的情况,基于这个需求才用它。开发 Kubernetes 基于 Namespace,同一个服务在不同的 Namespace 下,它其实环境变量名可以是相同的,但是IP不同,而这一部分 IP 其实是由开发 Kubernetes 自己去管理的。基于环境变量获取一些配置的话,比如 IP 地址这种,就可以做到拿一份配置可以打出多套环境。


考虑业务的安全性和稳定性,线上基于纯 Docker 的方式在做。我们是基于裸的 Docker 来工作,主要是用资源隔离,没有借助调度框架,也没有自动伸缩。我们是两步走,一步是验证 Docker,其次是做开发 Kubernetes 线下使用和预研。为什么没有考虑 Mesos?刚才跟沪江的同学,我们的考虑是相反的。Mesos 侧重点更专一一点,首先不会有模块的划分,比如 Kubernetes 有 Replication controller ,Namespace 这种概念,而 Mesos 下几乎没有这种概念。我们拿 Kubernetes 主要是做一些编排的功能,而正好开发 Kubernetes 在整个发布和编排上,体系更全面一点。Mesos 最早是做资源管理,基于 Docker 做一个 Framework 接进来的话,它不是专门为编排而生。Kubernetes 首先解决我们的问题是,我们可能不需要加多份配置就可以搭多套不同的环境,它就是基于 Namespace 做一个多租户的概念,会对 Service 做一层隔离,对于动态配置,扩容这一部分暂时我们没用到,确实用到的一些场景比较少。主要是做不同环境的隔离,并没有太多使用编排细节上的东西,动态伸缩之类的目前线下没有太大必要,线上可能会用到。


蘑菇街向靖


大家好,我是向靖,来自蘑菇街的运维架构师。我们接下来会做一个 PaaS 平台,想做 Docker 和结合虚拟机以及我们用到公有云产品,做成一个混合云的架构平台。我们现在 Docker 也在用,更多的是当虚拟机用,后面我们想基于 Docker 原生的方式去用,可能会涉及资源调度,服务发现的问题。除了 Docker,我们还会用到公有云,公有云更多是虚拟机的方式提供。出于混合云,想在资源层做一个抽象,对于上层业务来讲它没有关系,它是跑在 Docker 上,还是云主机上,还是 KVM 虚拟机上,那么我想在这上面做一个抽象。另外还有,刚才我也是提问滴滴架构师的问题,配置怎样和代码做隔离,这个也是我考虑的问题。因为我看 Docker 用了环境变量,通过环境变量做一些配置的参数的传递,但是在虚拟机上,特别是在物理机上,通过环境变量的方式,我还在考虑有没有安全的风险,Docker 可能是一个只读的,不会被修改的,但是对于虚拟机以及物理机来说,可能会存在被修改的风险。


蘑菇街张振华


大家好,我叫张振华,花名郭嘉,我是 14 年从思科加入蘑菇街。我们算是国内用 Docker 比较早的,我们一开始用 Docker 是 1.3.2 的版本,当时我们采用集群管理工具还是 Openstack,因为当时 Kubernetes 还不是很成熟。当时也走了一些弯路,比如我们把 Docker 当成虚拟机来用,曾经在线上的规模也达到几百台虚拟机几千个容器,但是我们逐步发现不能把 Docker 当成虚拟机来使用,因此我们做了一个转型,从去年开始研究 Kubernetes,现在 Kubernetes 加 Docker 的版本开发完成了,准备逐步上线。


我们为什么选用 Kubernetes?编排工具的选择我们也是做过一番调研的,它们没有谁好谁不好这一说,只能说谁更贴切你的需求。对于我们蘑菇街来说,我们需要解决是资源利用率的问题,和运维的对接,我们需要有预发和线上环境的持续集成持续部署的过程,还有我们需要有对资源的隔离,对部署的快速迭代,包括集群管理,这些方面,我们觉得 Kubernetes 更加适合于我们。


在网络方面,我们研究过现在在开源界比较常用的一些方案,但是我们都觉得不太适合,比较 Fannel,Caico 等等,他们一般用的技术都是 VXLAN,或者是用 BGP。因为我们之前对 Openstack 的网络是比较有经验的,然后我们发现有一个项目,具体名字不记得,Neutron 和 Kubernetes 做一个对接,我们在这个项目的基础上做了 VLAN 的方案,我们的网络没有用 VXLAN 来做,而是选择 VLAN 来做,这样的话一个 Docker 它可以获得跟一个物理理同一个网络平面的 IP,我们的应用程序可以直接对外访问,因为我们内部业务有这个需求选择这个方案。虽然 Docker 内部网络和外部网络是通的,但 Docker 还是独立的一个网段,不需要一层 NAT 的转换。我们直接走二层的,是在交换机走 Chunk,本来物理机交换机的 Access 口,这样的话,一台物理机上面允许跑多个 VLAN 的容器,比如说 A 业务和 B 业务要走隔离的话,通过网络的 VLAN 走隔离,它们的数据之间不会有干扰。


Load Balance 我们还没有涉及到这一块,Load Balance 我们应该会在 nginx 上做一层。因为据我了解,现在 Kubernetes 这一块 Proxy 还不是很成熟,这上面还存在一些问题,因此还不敢用 Kubernetes 现有提供的服务。服务发现和注册这一块我们还在做开发,这块会和配置管理中心打通。我们内部也有其他团队在做这些功能,所以我们会和内部的中间件团队合作。


七牛云袁晓沛


大家好,我是七牛云数据处理技术总监袁晓沛。我们的数据处理业务包括了图片和视频的实时在线及异步处理。数据处理的业务量比较大,日均请求量达到百亿级。平台采用容器技术的原因是借助容器技术快速部署,启动的特性,数据处理程序可以根据数据处理量快速地弹性伸缩。借助容器技术内核级别的资源隔离和访问控制,每个数据处理程序可以运行在一个私有的环境,不被其它程序所干扰,保证其上运行数据是安全可靠的。而且容器技术是轻量的,它以最小的资源损耗提供资源隔离和访问控制,而资源特别是计算资源在数据处理中是非常宝贵的。


我们在资源调度上采用的是 Mesos,而二层的业务调度框架则是自己自研的。七牛自身拥有近千台的物理机,容器是直接运行的物理机上,可以减少虚拟层对资源的消耗,提高资源的利用率。


在网络上,对于七牛的自定义数据处理服务直接使用的是 Host 模式,而对第三方数据处理服务则使用的是 Bridge 模式,因为这些程序是用户自己部署运行的,并不知道用户是否有开启其他的端口使用,所以使用的是 Bridge 模式,需要对外使用端口的都需要通过 NAT 进行暴露,这样服务内部使用了什么端口并不会对外界环境造成影响,对平台环境做了非常好的安全隔离。我们是使用 Consul 做注册中心,支持跨数据中心的服务发现。我们为什么自研的调度框架,而不用 Marathon。因为 Marathon 不支持跨数据中心的内部服务或外部服务的发现,而七牛有多个数据中心,影响整体的调度,其次如果选用 Marathon 的话,根据我们业务的特点,还是要再做一层对 Marathon 的包装才能作为 Dora 的调度服务,这样模块就会变多,部署运维会复杂。


扇贝丁彦


大家好,我是扇贝的技术总监丁彦,之前在暴走漫画,先后在暴走漫画和扇贝设计和主导了基于 Docker 的微服务架构系统,以及数据收集和分析系统。去年来到扇贝,这里是 Python 的开发环境。后来发现业务增长快,水平扩展一些机器,出现问题需要换个机器等等,都需要非常熟悉业务的少数开发去做。另外公司对预算控制严格,机器基本都是满负荷运作,平时也不可能多开空置的机器,已有的机器还要根据负载情况调整服务分布情况,所以这种切换服务,增删服务的操作还是比较频繁的。因此,我们用了 2-3 个月的时间将所有的运行环境都切换到 Docker上,这大大提高了我们的运维能力。


Docker 包装有几个好处。


第一个好处是,环境升级非常方便。因为只要pull 一下最新的镜像,启动一个 Container,环境就升级了。而如果直接基于公有云的镜像升级的话就很难,因为一台机器上跑哪些服务其实不一定是固定的,并且之前做的镜像只要有一台机器是还基于它的话,就删除不掉的,镜像数量又有上限。所以 Docker 非常好地解决了我们的问题。


其次是环境的颗粒度会更小,一台机器上配好几个应用的话,往往配着配着,到最后你就不太能精确地记得上面装的程序或者库是给哪个应用服务的,应用之间如果依赖有版本的冲突也很难调和。你想做些服务的迁移,把负载比较小的放一起,把负载比较大的抽出来,这个时候就非常痛苦,但你如果用 Docker 包装后就非常简单,只要在不同的机器上起不同的 Container,就可以实现这一点。


第三,我们不光用了 Docker,还加入了服务发现,刚刚讨论配置管理这些,我们一并做了。Docker 启动时候,我们自己写了一些工具,可以自定义 Docker 启动参数,包括配置参数,比如说,一些程序要运行的参数,我们主要用两种方式,一种方式是通过环境变量灌进去,还有一种方式让程序的启动脚本支持参数,然后拼接不同的参数灌进去,最终都是落实到 Docker 的启动命令上。服务发现是基于 Consul,Docker 的启动命令是从 Consul 里取的。首先 Consul有 HTTP 的 API,我们是自己写的 pip 包,只要 Include 一下这个包就可以了,Docker 的服务启动后会自动注册到 Consul。比如要在负载后加一个服务,只需要找到一台机器,启动对应的 container,剩下的事情它自己会到 Consul,注册它的参数地址一系列东西,自动把它加进去。所以这些都是自动化的,如果检测那台机器/服务挂了,Health Check 也会到 Consul 里面更新。该增加机器就增加机器,该下线就下线。总体来说,我们的生产环境全部跑在 Docker 上面的,然后区分有状态和无状态两种,有状态的定死在机器上,无状态的灵活的自由切换。还有一点,如果是有状态的容器要定死在机器上的时候,我们一般来说都会采取冗余的结构,至少保证有两个在运行,一个挂了,保证整体的服务在运行。其次基于 Docker,我们还做了一套数据搜集以及分析的机制。数据搜集是基于日志来搜集的,利用 Docker 的 Log driver,把日志打到 Filter,把结果存在存储服务上。同时监控也是基于日志做的。第三部分非生产环境,比如开发环境跟测试环境都是 Docker 做的,因为我们每一个服务都做了 Image、镜像,用容器方式跑的。通过参数来决定启动方式的,我们可以在开发环境以及测试环境采用不同的参数来启动容器。 通过 Consul 来隔离的,因为 Consul 的服务发现,开发、生产、测试环境在不同的自动发现框架里不会相互影响到。目前机器在 120 台左右,基于云服务。有些基础的东西不需要依赖于 Docker,比如说申请云主机,申请的时候就可以指定它的 CPU 和内存这些服务器资源的配置。所以这部分东西还是属于 Human schedule,不是完全让编排的系统自己决定该怎么样。


编排工具我们现在在研究进一步,我刚来这工作的时候,所有的服务没有一个跑在 Docker 上面的,我现在把它迁进来。现在数据增长,已经有一些编排的瓶颈,现在在做调研,可能基于 Swarm,做自动编排的设计。




指定话题交流



主持人:容器多的情况下 Kubernetes 存在性能问题,各位在这方面有没有好的经验?



扇贝丁彦:我们其实也遇到了这个问题,找不到办法所以放弃了 Kubernetes。我们也是用公有云,网络直接依赖公有云的网络,有可能是因为公有云造成的,我没有试过在祼机上试过。


沪江黄凯: Kuberneters 的 Fannel 有一种模式是 VXLAN,它的封装折包是做内核里做的,效率会高一点。容器多就会效率会低是因为,在 Kubernetes 1.2 的时候,走这样的一种模式,数据先到内核态中,然后把数据拉回到用户态,用 Proxy 的方式分发给各个容器当中的。其实在 Kubernetes 1.3 以后,它直接在iptables里设规则,相当于用户数据不用跑到用户态,在内核直接分发出去了,这种效率会非常高。所以可以研究一下 Kubernetes 新版本。


扇贝丁彦:我们碰到过网络方面的问题。默认的 Docker engine 的启动参数里面有个 iptables,不知道大家有没有定制化过,如果不定制化这个参数,它默认会帮你建 iptables 的转发规则,并会开启内核的网络追踪的模块。一开始我们没有注意这件事情,当我们的 Nginx 迁到 Docker 的时候,Nginx 服务瞬间会挂。后来查原因,是因为这些参数会开启网络追踪模块。因为我们的 Nginx 流量非常大,当时只有 3 台 Linux 云主机,分发 http 请求的,然后会导致 3 台Linux宿主机,内存会被刷破,网络会出现堵塞。所以我们关掉了 iptables 参数,并采用 Host 的网络模型。所以它的容器拿到的 IP 就是 Host 的 IP。我们一开始也想上一些 Kubernetes 这些东西,然后发现简单跑个模型根本跑不起来,所以一开始就放弃了这一套东西,直接搞了个裸的 Docker。



主持人:关于跨数据中心容器集群的使用,大家有经验么?



沪江黄凯:我们跨数据中心主要是IP分配上的问题,我们现在也在尝试使用 Calico,如果 Host 网络是通的话,那么它的内部网络也就通了,可以自由划 VLAN,这样你就可以解决跨 Data center 的问题。还有一个问题就在跨 Data center 时,服务注册与发现的问题。这个问题也困扰我们很久了,我们现在使用 Consul 做服务注册与发现。虽然 Consul 它是官方支持跨 Data center,但是我们在使用当中的话会发现注册的 IP,在另外一个注册中心,它会发现的比较慢,甚至有时候出现 IP 冲突的时候。


我们的做法是把 Host 的 IP 地址直接用 Environment 的形式注到 Docker 镜像内部,接下 来 Docker 镜像要注册,它就会读取 App 的 IP,然后发送给 Consul,只要保证 Host 的 IP 和 Docker内部容器的 IP 能够互通的就行了。如果不能通的话,比如说完全和 Host IP 隔离,那么起码有几台机器要暴露出去,又比如说,Consul 它本身自己要暴露出去才能访问到。Host 的 IP 是容器启动之后注进去的,启动命令中把 Host 的 IP 地址加在 -e 的后面,容器在启动之后,它的环境就会有这么一个 IP。我们用 Mesos 就没这个问题,但是用 Kubernetes 就有这个问题。Mesos 会自动帮你把这些东西注入容器中去。


滴滴田智伟:其实 Kubernetes 本身也是可以解决这个问题,我们现在在做线下持续交付的时候。定义完 Service 之后,容器会同一个 Namespace 默认加一个系统环境变量。


沪江黄凯:我们试过,在 Pod 启动之后,Pod 里容器想访问 host 的 IP 地址,是没有办法做到的。


蘑菇街张振华:因为我们之前也遇到这个问题,然后我们业务方,他们可能有一些程序会获取本机 IP 地址,如果是内部的 IP 地址,他们程序可能会出现问题,于是我们当时没有用 Docker 默认的网络,而是采用 VLAN。



主持人:我们提到好多 Mesos、Kubernetes、网络,发现没有提自动伸缩,有没有项目涉及到容器的自动伸缩?



沪江黄凯:我们沪江是基于 Mesos+Marathon 做了自己的一个服务,它这个服务是干嘛的呢,就是监测,不停的监测每一个 Docker 的 CPU 和内存的利用率,一旦超过百分之多少以后,就向 Marathon 发一个命令,说我要扩容,它还可以支持时间点,比如 15 分钟监测一次,如果在 15 分钟发现它超过阈值了,就马上扩容出来,但是缩的话,不是适用于频繁监控,如果小于 20% 的话就会缩,一旦缩的话会影响线上用户的请求。怎么办呢?我们在缩的时候可以规定它的时间点,比如半夜里 2-3 点,访问量少于多少点时候把它缩掉。我们监测的是 Docker 内部的 CPU 的使用率。就是监测一个服务,它可以监控所有同一服务的 Container,比如一个服务有 100 个容器,那么这一百多个 CPU 利用率加起来除于一百,相当于平均的利用率。如果平均利用率超过 80%了,那说明这个集群到了扩展程度了,它会以一种比例来扩展。针对单个容器,可以设置内存的限制。我们给每一个容器呢,比如它只能用 4 个 CPU,只能用 8G 的内存,或者更小一点的内存,这些都设好,设好之后它自动扩展相同规格的容器。这么做是因为 Cgroup 有个问题,当利用率到达了启动的限制,Cgroup 会把这个容器 kill 掉。这个是不可理喻的问题,所以我们想到用 Load scale 来扩容,不让他直接死掉。


滴滴田志伟:关于自动扩容,我们线下的时候遇到一个问题,我们最早的时候是用腾讯的公有云,它限制了 NET 的模块,导致我们最初用 Cgroup 的方案去做,绑定端口。内部使用所有应用,端口是要做分配的,要不然出现端口冲突。然后遇到问题是,在这种情况下,如果要做动态扩容的话,它每次先创建一个,再杀掉一个,导致每次起来的时候就起不来了,因为端口的问题。服务启动的时候端口是随机,会出现冲突问题,因为用的是 Host 的模式。



主持人:关于自动伸缩为什么没有考虑到请求数?因为如果内存占用率如果超过一定预支,那么请求数也可能超过一定预支了。把单个容器所处理的请求数给限定了,那么它内存自然不会超,然后也不会被干掉。



沪江黄凯:我个人认为,第一,请求数很难测,你不知道请求数到多少时要扩容,还不如根据 CPU 到 80%,或者 90% 来的直观。我们的 API 也是根据 CPU 来算的。你真正是高并发的 API 的话,我也测过,最后我们能够监测到的,其实还是 CPU 和内存。


扇贝丁彦:我们扩容是根据响应时间,跟请求数类似,请求数定指标不太好定,我们是根据响应时间,比如平时的响应时间是 50 毫秒,当响应时间是 300 毫秒的时候就要扩容了。



主持人:关于自动伸缩为什么没有考虑到请求数?因为如果内存占用率如果超过一定预支,那么请求数也可能超过一定预支了。把单个容器所处理的请求数给限定了,那么它内存自然不会超,然后也不会被干掉。



沪江黄凯:关于存储,我们是有一些研究的。现在容器存储问题分为两种,Kubernetes 官方支持一种理念,任何一种存储都是一个 Volume。Volume 先于 Docker 存在的,而不是 Docker 启动之后再挂载 Volume。不管是网络存储还是本地存储,全部以卷的形式,挂载在 Pod 里面或者是宿主机上,以 Driver mapper 来驱动这个 Volume,来读到你所要的内容。


还有一种情况,就是 Docker 公司主导的存储模型,任何的存储都是一种驱动。如果你想用 NFS 或者如 Ceph 这样分布式存储的话,让 Ceph 开发 Docker 的驱动,Docker run 的时候指定存储的驱动,Docker storage driver 这种方式,外部的存储在容器内部它展现形式可以是目录,也可以是挂载卷、块的形式。如果用块挂载到容器中,这个容器自己格式化它,或直接读取它都是可以的。它只不过它是相当于用了一个 Driver 的形式,把你的容器和分布式存储建立一个连接而已。对于容器,如果原本绑定块或 Volume,容器出现故障的话,直接把容器杀掉,再启动挂在同样一个 块或Volume 就解决了。优点是直接读取,而不是通过再转一层,效率比较高一点。所有存储都是 Volume 的形式理解度比较高一点,所以我们还是赞同于用 Volume 的形式。


有状态的容器。我知道 k8s 的新的计划,如果你没有用 Kubernetes 最新版本的话,一般来说我们都是容器启动在固定 Host 上,下次启动还是在这台 Host 上,它的存储它的内存,包括一些 log,全部是在这台 Host 上。还有一种是用最新的版本,有个 PetSet 的新 kind,Kubernetes 它自己会记录 Pod 在什么 Host 上启动过,不用自己去指定一定要在某一台 Host 上启动,这种方法比较智能化,但是不是特别稳定的一种方法,因为它是刚刚开发出来的新功能。



主持人:关于自动伸缩为什么没有考虑到请求数?因为如果内存占用率如果超过一定预支,那么请求数也可能超过一定预支了。把单个容器所处理的请求数给限定了,那么它内存自然不会超,然后也不会被干掉。



沪江黄凯:我个人认为还是在同一台机器上起一个新的实例,不要让它做数据迁移,因为数据迁移会占用很多资源。而且如果你的想法是说,所有的分布式的存储只是以 Volume 的形式挂载在宿主同上,这也就没什么问题了。因为存储和 Docker 是完全分开来的。如果只有一个 Volume,存储的可靠性会得不到保障,所以在 Kubernetes 新版本当中,它会建立一个 Volume 的 kind,也相当于建立 RC kind一样,是一个 Pod,那这样由 Kubernetes 来保障这个 Volume 的高可用。

建议

站点反馈lifesohard 发表了文章 • 0 个评论 • 396 次浏览 • 2016-11-14 13:53 • 来自相关话题

建议创建一个精华文章系列

建议创建一个精华文章系列

beego如何指定应用的根名称 默认情况下都是/能否用/oa/作为根路径?

有问必答astaxie 回复了问题 • 2 人关注 • 1 个回复 • 824 次浏览 • 2016-11-14 12:31 • 来自相关话题

golang vendor路径问题

有问必答passinger 回复了问题 • 1 人关注 • 3 个回复 • 3009 次浏览 • 2016-11-14 10:22 • 来自相关话题

Golang中的unsafe标准库包

文章分享tttlll 发表了文章 • 2 个评论 • 1072 次浏览 • 2016-11-13 19:22 • 来自相关话题

2016/10/22, 老獏

unsafe标准库包是Golang中一个比较特殊的包,为什么这么说?本问将详细解释其中缘由。

Go官方文档中的警告

unsafe包的文档查看全部

2016/10/22, 老獏


unsafe标准库包是Golang中一个比较特殊的包,为什么这么说?本问将详细解释其中缘由。


Go官方文档中的警告


unsafe包的文档这么说:



Packages that import unsafe may be non-portable and are not protected by the Go 1 compatibility guidelines.



Go 1 兼容性指南这么说:



Packages that import unsafe may depend on internal properties of the Go implementation. We reserve the right to make changes to the implementation that may break such programs.



两段话大同小异,简而言之就是使用unsafe包是危险的,Golang不保证此包的跨平台和跨版本兼容性。听上去挺慎人,但是此包究竟有多危险?先让我们看看此包在Golang中发挥的角色是什么。


unsafe包的角色


到目前为止(Go1.7),unsafe包包含以下资源:



  • 3个函数:

    • func Alignof(variable ArbitraryType) uintptr

    • func Offsetof(selector ArbitraryType) uintptr

    • func Sizeof(variable ArbitraryType) uintptr


  • 和一个类型


这里ArbitraryType不是一个真正的类型,它仅仅是一个占位符。


不像绝大多数的函数,在Golang中,以上3个函数的调用将在编译时而不是运行时被估值,因此这3个函数的返回值可以被赋给常量。换句话说,这3个函数是为编译器服务的。


除了这3个函数,unsafe包中唯一的类型unsafe.Pointer也是为编译器服务的。


由于安全原因,Golang不允许下列类型的值互相进行转换:



  • 两个不同指针类型,比如*int64 and *float64

  • 任何普通指针类型*Tuintptr


但是在unsafe.Pointer的帮助下,我们可以打破Golang类型系统和内存管理的安全机制,从而使得以上的转换成为可能。这是怎么办到的呢?让我们看看unsafe包中列出的和unsafe.Pointer有关的规则



  • 任何类型的指针都可以被转换为unsafe.Pointer类型;

  • 一个unsafe.Pointer值都可以被转换为任何指针类型;

  • 一个uintptr值可以被转换为unsafe.Pointer类型;

  • 一个unsafe.Pointer值可以被转换为uintptr类型。


这些规则和Go白皮书是一致的:



Any pointer or value of underlying type uintptr can be converted to a Pointer type and vice versa.



这些规则表明unsafe.Pointer如同c语言中的void*。是的,c语言中的void*很危险!


按照这些规则,对两个不同的类型T1T2,指针类型*T1的值可以被转换为unsafe.Pointer类型,然后转换后的unsafe.Pointer类型的值可以继续被转换为*T2类型(或者uintptr类型);反之也是可以的。通过这种方式,Golang类型系统和内存管理的安全机制被绕过了。显然,滥用这种方式是危险的。


看个例子:


package main

import (
"fmt"
"unsafe"
)
func main() {
var n int64 = 5
var pn = &n
var pf = (*float64)(unsafe.Pointer(pn))
// 到这里,pn和pf指向同一个内存地址。
fmt.Println(*pf) // 2.5e-323
*pf = 3.14159
fmt.Println(n) // 4614256650576692846
}

此例中的转换或许并没有太大实际意义,但此转换是安全和合法的。至于为什么安全,见下文。


更多关于unsafe.Pointer和uintptr的一些事实


下面是关于unsafe.Pointeruintptr的一些事实:



  • uintptr 是一个整数类型,

    • 即使一个uintptr值依然被程序使用中,此uintptr值所表示的指针所指的数据可能将被垃圾回收。


  • unsafe.Pointer是一个指针类型,

    • 但是unsafe.Pointer值不能被解引用;

    • 和普通指针一样,如果一个unsafe.Pointer 值依然被程序使用中,此unsafe.Pointer 值所表示的指针所指的数据将确保不会被垃圾回收。


  • *unsafe.Pointer是一个普通指针,和*int类似。


既然uintptr是一个整数类型,那么当然可以对uintptr值进行算术运算。利用这一点,我们可以绕开Golang中指针不能进行偏移运算的限制:


package main

import (
"fmt"
"unsafe"
)

func main() {
a := [4]int{0, 1, 2, 3}
p1 := unsafe.Pointer(&a[1])
p3 := unsafe.Pointer(uintptr(p1) + 2 * unsafe.Sizeof(a[0]))
*(*int)(p3) = 6
fmt.Println("a =", a) // a = [0 1 2 6]

// ...

type Person struct {
name string
age int
gender bool
}

who := Person{"John", 30, true}
pp := unsafe.Pointer(&who)
pname := (*string)(unsafe.Pointer(uintptr(pp) + unsafe.Offsetof(who.name)))
page := (*int)(unsafe.Pointer(uintptr(pp) + unsafe.Offsetof(who.age)))
pgender := (*bool)(unsafe.Pointer(uintptr(pp) + unsafe.Offsetof(who.gender)))
*pname = "Alice"
*page = 28
*pgender = false
fmt.Println(who) // {Alice 28 false}
}

unsafe包究竟有多危险?


关于unsafe包,Go团队的主力开发之一Ian已经确认:



  • unsafe包中的函数原型今后保证不变;

  • 类型unsafe.Pointer将永远存在。


所以,看上去unsafe包中的函数并不怎么危险。Go团队的甚至想把它们放到别的包中。使用这些函数唯一的不安全性是这些函数的调用在今后的版本中可能会返回不同的结果值。但这种不安全性很难说是一种危险。


这样我们可以的得出结论:使用unsafe包的危险都是和使用unsafe.Pointer类型密切相关的。unsafe包的文档列出了一些合法和非法使用unsafe.Pointer类型的例子,这里仅仅列出部分非法的情形:


package main

import (
"fmt"
"unsafe"
)

// 情形A:unsafe.Pointer和uintptr之间的来回转换
// 未在同一个表达式中完成。
func illegalUseA() {
fmt.Println("===================== illegalUseA")

pa := new([4]int)

// 将下面这个合法的使用
// p1 := unsafe.Pointer(uintptr(unsafe.Pointer(pa)) + unsafe.Sizeof(pa[0]))
// 分成两个表达式(不合法的使用):
ptr := uintptr(unsafe.Pointer(pa))
p1 := unsafe.Pointer(ptr + unsafe.Sizeof(pa[0]))
// "go vet"将对上一行给出一个警告:
// possible misuse of unsafe.Pointer

// unsafe包的文档https://golang.org/pkg/unsafe/#Pointer,
// 认为上面分成两行是非法的,
// 但是目前的Go编译器和运行时(1.7.3)
// 没有侦测到这个非法使用。
//
// 然而,为了保证你的Go程序绝对安全的运行,
// 最好还是请遵守unsafe包的文档中定的规则。

*(*int)(p1) = 123
fmt.Println("*(*int)(p1) :", *(*int)(p1))
}

// 情形B:指针指向了非法地址
func illegalUseB() {
fmt.Println("===================== illegalUseB")

a := [4]int{0, 1, 2, 3}
p := unsafe.Pointer(&a)
p = unsafe.Pointer(uintptr(p) + uintptr(len(a)) * unsafe.Sizeof(a[0]))
// 现在p指向了a值内存块的结尾,
// 因为我们不清楚a值内存块的结尾存出的是什么,
// 所以p的当前值是非法的,虽然到目前为止也没什么危险。
// 但是如果我们改变了p所指的值,程序有可能运行错乱。
*(*int)(p) = 123
fmt.Println("*(*int)(p) :", *(*int)(p)) // 123 or not 123
// 当前的Go编译器和运行时(1.7.3)以及"go vet"
// 都没有检测到上面这个非法操作。

// 但是,当前的Go运行时(1.7.3)将侦测到下面的
// 非法操作(其实和上面的非法操作是一样的)并panic。
p = unsafe.Pointer(&a)
for i := 0; i <= len(a); i++ {
*(*int)(p) = 123 // Go运行时(1.7.3)不会在此panic

fmt.Println(i, ":", *(*int)(p))
// 当i==4的时候,Go运行时(1.7.3)将在上一行panic
// runtime error: invalid memory address or nil pointer dereference

p = unsafe.Pointer(uintptr(p) + unsafe.Sizeof(a[0]))
}
}

func main() {
illegalUseA()
illegalUseB()
}

编译器很难检测到非法的unsafe.Pointer使用。虽然"go vet"能发现一些潜在的非法的unsafe.Pointer使用,但也不能检测到所有的非法使用。同样,Go运行时也不能检测到所有的非法unsafe.Pointer使用。非法unsafe.Pointer使用可能会是程序崩溃,或者使程序运行诡异(比如有时候表现正常,有时候却很反常)。这就是为什么说使用unsafe包是危险的。


*T1*T2之间的转换


对于将*T1值转换为unsafe.Pointer类型,继而再转换为*T2类型,unsafe包是这么说的:



Provided that T2 is no larger than T1 and that the two share an equivalent memory layout, this conversion allows reinterpreting data of one type as data of another type.



直译过来:如果类型T2的大小不比T1大并且这两个类型有着等价的内存布局,这种转换允许其中一个类型的数据用另一个类型的数据来表示。(反正我是没看太明白)


这里对equivalent memory layout(内存布局)的定义很含糊,而且好像Go团队故意将其定义得很含糊。 这使得使用unsafe包更加的危险。


既然Go团队不愿意定义一个精确的规则,本文也不会做这个尝试。这里仅仅列出部分合法的例子。


合法使用1:在切片[]T[]MyT之间转换


在下例中,我们使用int类型表示T


type MyInt int

在Golang的类型系统中,类型[]int[]MyInt底层类型均为它们自身。Golang中,底层类型不一样的两个非接口类型之间是不支持转换的。但是在unsafe.Pointer的帮助下,这种转换成为可能:


package main

import (
"fmt"
"unsafe"
)

func main() {
type MyInt int

a := []MyInt{0, 1, 2}
// b := ([]int)(a) // error: cannot convert a (type []MyInt) to type []int
b := *(*[]int)(unsafe.Pointer(&a))

b[0]= 3

fmt.Println("a =", a) // a = [3 1 2]
fmt.Println("b =", b) // b = [3 1 2]

a[2] = 9

fmt.Println("a =", a) // a = [3 1 9]
fmt.Println("b =", b) // b = [3 1 9]
}

合法使用2:调用sync/atomic包中指针相关的函数


sync/atomic包中和指针相关的函数的参数和返回值类型大多都是unsafe.Pointer或者 *unsafe.Pointer



  • func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)

  • func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)

  • func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)

  • func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)


为了调用这些函数,必须引入unsafe包。


注意:*unsafe.Pointer属于普通类型,所以*unsafe.Pointer值可以被转换为unsafe.Pointer类型,反之亦然。


package main

import (
"fmt"
"log"
"time"
"unsafe"
"sync/atomic"
"sync"
"math/rand"
)

var data *string

// get data atomically
func Data() string {
p := (*string)(atomic.LoadPointer(
(*unsafe.Pointer)(unsafe.Pointer(&data)),
))
if p == nil {
return ""
} else {
return *p
}
}

// set data atomically
func SetData(d string) {
atomic.StorePointer(
(*unsafe.Pointer)(unsafe.Pointer(&data)),
unsafe.Pointer(&d),
)
}

func main() {
var wg sync.WaitGroup
wg.Add(200)

for range [100]struct{}{} {
go func() {
time.Sleep(time.Second * time.Duration(rand.Intn(1000)) / 1000)

log.Println(Data())
wg.Done()
}()
}

for i := range [100]struct{}{} {
go func(i int) {
time.Sleep(time.Second * time.Duration(rand.Intn(1000)) / 1000)
s := fmt.Sprint("#", i)
log.Println("====", s)

SetData(s)
wg.Done()
}(i)
}

wg.Wait()

fmt.Println("final data = ", *data)
}

结论



  • unsafe包是为编译器而不是运行时服务的;

  • 使用unsafe做为包名是为了让程序员小心使用此包;

  • 使用unsafe包并非总是不推荐的,有时我们必须使用它;

  • Golang的类型系统设计同时兼顾安全性和效率,但安全性要优先于效率。所以有时候为了普遍安全性将牺牲一些效率。unsafe包使得经验丰富的程序员在某些时候不影响安全性的同时绕开Golang的安全机制,从而避免一些正常使用中的效率牺牲。

  • 当然,再重申一遍,unsafe包很容易被滥用,因此使用之是危险的。




原文:http://www.tapirgames.com/blog/golang-unsafe