yum安装方法
1、安装
1 | yum install -y iozone |
2、参数
1 | iozone -l 1 -u 1 -r 16k -s 64g -F |
-l是最小进程数量lower
-u是最大进程数量upper
-r是读写的基本单位,16k作为读写的基本单位,根据模拟应用程序进行合理设置(目的是模拟真实应用)
-s指定默认读写的大小,建议不要指定的太小,一般指定的是内存的2倍
-F指定测试文件位置,可以是多个
编译安装方法
1 | yum install gcc |
1、安装
1 | yum install -y iozone |
2、参数
1 | iozone -l 1 -u 1 -r 16k -s 64g -F |
-l是最小进程数量lower
-u是最大进程数量upper
-r是读写的基本单位,16k作为读写的基本单位,根据模拟应用程序进行合理设置(目的是模拟真实应用)
-s指定默认读写的大小,建议不要指定的太小,一般指定的是内存的2倍
-F指定测试文件位置,可以是多个
1 | yum install gcc |
Go并发编程案例解析学习笔记
课程地址:https://www.imooc.com/video/17021
课程教师:麦可同学
1 | foo() //执行函数foo,程序等待函数foo返回 |
1 | c := make(chan string) //创建一个channel |
1 | select { |
定义:
可以把《实时读取》《解析》《写入》拆成多个模块,使用多个goroutine。那么这么多个goroutine是并行执行还是并发执行呢?换句话说,多个goroutine执行,是一个CPU核心通过不断的切换时间片,并发的执行?还是将goroutine分散到多核的CPU并行的执行?这个问题Golang为我们屏蔽掉了,编程人员不需要考虑这个问题。
Golang中没有类和对象的概念,但是支持
传统的面向对象中,继承、封装、多态都可以基于这两个特性来实现。
1 | // 结构体,baz相当于成员变量,可以看做是类 |
1 | // Foo结构体 |
1 | // 定义一个interface,可以看成是一组方法的集合,通过interface定义一些对象的行为 |
1 | package main |
1 | package main |
1 | package main |
1 | package main |
在URL中,db=mydb
指定database。使用逗号做分隔’,’,cpu_usage
表示measurement。host=server01,region=us-west value=0.64 1434055562000000000
表示points。host=server01,region=us-west
表示points中的tags,value=0.64
表示points中的fields,1434055562000000000
表示points中的time。
influxDB提供了Golang的客户端,可以使用这个客户端很方便的写入数据https://github.com/influxdata/influxdb/tree/master/client
,首先先引入包"github.com/influxdata/influxdb/client/v2"
1 | package main |
1、总处理日志行数
2、系统吞吐量
3、read channel长度
4、write channel长度
5、运行总时间
6、错误数
1 | package main |
import “flag”
Package flag实现了command-line flag解析
定义flags使用flag.String(), Bool(), Int()等。
以下声明了一个整型flag,名字是-flagname,存储在指针ip中,类型为*int。
1 | import "flag" |
如果你喜欢,你可以使用Var()函数将flag绑定到一个变量。
1 | var flagvar int |
或者,您可以创建满足Value接口(指针接收器)的自定义flags
1 | flag.Var(&flagVal, "name", "help message for flagname") |
对于这样的flags,默认值是变量的初始值。
所有flags定义后,调用:
1 | flag.Parse() |
将命令行解析为所定义的flags。
flags可以直接使用。如果你使用flags本身,它们都是指针;如果你绑定到变量,它们就是值。
1 | fmt.Println("ip has value ", *ip) |
解析后,flags后面的参数可作为slice,flag.Args()。或者独立的一个值,flag.Arg(i)。参数索引,从0到flag.NArg() - 1。
Command line flag语法:
1 | -flag |
可以使用一个或两个减号;它们是等价的。最后一种形式不允许用于boolean flags
1 | cmd -x * |
其中*是一个Unix shell通配符,如果有一个名为0或false等文件时它将会更改。您必须使用-flag=false形式来关闭boolean flag。
flag解析在第一个non-flag参数之前(”-“是一个non-flag参数)或终止符“–”之后停止。
整数flags接受1234,0664,0x1234并且可能是负数。Boolean flags可能是:
1 | 1, 0, t, f, T, F, true, false, TRUE, FALSE, True, False |
Duration flags接受任何有效的time.ParseDuration。
默认的一组command-line flags由顶层函数控制。FlagSet类型允许您定义独立的flags集,例如在command-line interface中实现子命令。FlagSet的方法类似于command-line flag集的顶层函数。
1 | // These examples demonstrate more intricate uses of the flag package. |
在近期项目中,想针对Prometheus的ceph_exporter做定制化修改,但是遇到了一些麻烦。
使用go get github.com/digitalocean/ceph_exporter
命令down下来的代码和GitHub上的master分支或tag中的代码都不一样,这是为什么呢?如果go get
指向目标Repository的master分支,那么master分支怎样保证任何时刻都是可用的呢?于是带着疑问开始寻找答案。
当你在代码中引用package时,通常使用看起来像URL的导入路径,例如github.com/jpoehls/gophermail。当使用go build构建代码时,Go工具使用此路径在GOPATH中查找这些软件package,如果找不到,则失败。
如何拉取这些package?
1 | 1、手动下载。你可以使用git clone这些package到你的GOPATH中。 |
如何管理GitHub中单个Repository中的多个Versions?
有一种解决方法,可以让你在同一个Repository中,保存软件package的多个版本,并使用branches/tags来区分它们。go get
支持自定义URL,你可以将版本号插入到package的导入路径中。这意味着需要编写一个代理服务,用于解析URL,并将请求代理到存储Repository的branch/tag上。
幸运的是,有人已经为我们做了这些工作。GoPkg.in完全符合上述内容。
例如:
使用此方法管理gophermail package。这意味着,不是使用github.com/jpoehls/gophermail导入软件package,而是使用gopkg.in/jpoehls/gophermail.v0。.v0是因为gophermail还没有达到1.0。当我发布1.0并声明稳定的API时,导入路径将更改为gopkg.in/jpoehls/gophermail.v1。
弄到这,好像和下面Prometheus的ceph_exporter输出没太大关系啊。。。。。。。。等我再研究研究。。。。
1 | [root@centos7 ~]# go version |
参考链接:
【1】http://zduck.com/2014/go-and-package-versioning
【2】https://stackoverflow.com/questions/24855081/how-do-i-import-a-specific-version-of-a-package-using-go-get
工欲善其事,必先利其器。这篇文章记录怎样在Linux上使用Vundle管理Vim插件,提高效率。
步骤很简单,大体如下:
1 | [root@centos7 ~]# yum install -y redhat-lsb |
1 | [root@centos7 ~]# yum install -y git vim |
1 | [root@centos7 ~]# git clone https://github.com/VundleVim/Vundle.vim.git ~/.vim/bundle/Vundle.vim |
1 | [root@centos7 ~]# vim ~/.vimrc |
打开~/.vimrc,并添加你要安装的Plugins
1 | [root@centos7 ~]# vim ~/.vimrc |
启动vim并运行:PluginInstall
1 | [root@centos7 ~]# vim ~/.vimrc |
配置已安装NERDTree插件
1 | [root@centos7 ~]# vim ~/.vimrc |
当然vim不止这一个插件,这里只是介绍一下怎样配置,其他好玩的插件请自己发掘。
1 | 1、首先安装Java JDK和一些工具包,这里使用的是java-1.7.0 |
1 | 1、准备测试目录 |
openflags
1 | 这个参数可以指定在SD, WD, FSD, FWD, RD中 |
sizes(注意这里有s)
1 | 这个参数可以指定在FSD中 |
应用场景为多个NFS Client挂在相同NFS Server的读写。
1 | hd=default,vdbench=/home/vdbench,user=root,shell=ssh |
参数解析:
有3台测试节点node1、node2、node3。每台测试节点的/home/vdbench/目录都存在可执行vdbench二进制文件(位置必须相同),使用root用户通过ssh方式连接(节点间需要做ssh免密),每台测试节点的测试目录为/mnt/test863,目录深度为1,最深层目录中的目录宽度为10,最深层每个目录中有10000个文件,每个文件大小20mb
关于shared=yes
随着Vdbench运行多个slaves和可选的多个hosts,slaves和hosts之间关于文件状态的通信变得困难。使所有这些slaves设备相互通信所涉及的开销变得过于昂贵。您不希望一个slave删除另一个slave当前正在读取或写入的文件。因此,Vdbench不允许您跨slaves和hosts共享FSD。
当然,在你开始使用庞大的文件系统之前,这一切听起来都很棒。 您刚刚填满了500 TB的磁盘文件,然后您决定要与一个或多个远程主机共享该数据。 从头开始重新创建整个文件结构需要很长时间。 该怎么办?
指定’shared = yes’时,Vdbench将允许您共享文件系统定义(FSD)。 它通过允许每个slave设备仅使用FSD文件结构中定义的每个“第n”文件来实现这一点,其中“n”等于slave数量。(It does this by allowing each slave to use only every ‘nth’ file as is defined in the FSD file structure, where ‘n’ equals the amount of slaves.)
这意味着不同的host不会互相踩到脚趾,但有一个例外:当您指定’format = yes’时,Vdbench首先删除已存在的文件结构。由于这可能是一个旧的文件结构,Vdbench无法传播文件删除周围,让每个slave删除他的’第n’文件。因此,每个slave设备都会尝试删除所有文件,但如果删除失败则不会生成错误消息(因为不同的slave设备只是删除了它)。这些失败的删除将被计算并报告在“Miscellaneous statistics”中的“FILE_DELETE_SHARED”计数器下。但是,“FILE_DELETES”计数器可以包含高于存在的文件数量的计数。我已经看到多个slaves设备能够同时删除同一个文件而没有操作系统将任何错误传递给Java的情况。
关于rdpct(Read Percentage)
此参数允许您混合读取和写入。 使用operation=read只允许你做read,operation=write只允许你做write。 但是,指定rdpct=,您将能够在同一个选定文件中混合读取和写入。请注意,对于sequential(顺序),这没有多大意义。您可以以读取块1,写入块2,读取块3等。对于随机I/O,这非常有意义。
关于format=
no
不需要任何格式,但现有文件结构必须与 FSD 定义的结构相匹配。
yes
Vdbench 将首先删除当前文件结构,然后再次创建文件结构。然后,它将执行您在 RD 中的请求。
restart
Vdbench将仅创建尚未创建的文件,并且还将扩展未达到其正确大小的文件。 (这是totalsize和workingsetsize可以发挥作用的地方)。
only
与’yes’相同,但Vdbench不会执行当前的RD。
dir(ectories)
与‘yes’相同,但它只会创建目录。
clean
Vdbench只会删除当前的文件结构,而不会执行当前的RD。
once
这将覆盖每个forxxx参数循环完成format的默认行为。
limited
format将在elapsed=seconds之后终止,而不是所有文件或为totalsize=选择的文件已格式化之后终止。
complete
只能与’format=no’一起使用,并且会告诉Vdbench format已经完成,但是Vdbench不应该尝试通过目录搜索来验证每个目录和文件的状态。 当然,如果一个或多个目录或文件丢失或文件未达到其预期大小,结果不可预测。在测试期间删除或创建目录或文件时非常危险。
RocksDB:A Persistent Key-Value Store for Flash and RAM Storage
RocksDB是一个嵌入式key-value store C ++库,其中keys和values是任意byte streams。RocksDB由Facebook Database Engineering Team开发和维护,基于LevelDB二次开发,并对LevelDB API提供向后兼容。它支持原子读取和写入。RocksDB借鉴了开源leveldb项目中的重要代码以及Apache HBase的重要思想。最初的代码是从开源的leveldb 1.5中分离出来的。它还建立在Facebook之前在RocksDB开发的代码和想法之上。
RocksDB针对Flash进行了优化,延迟极低。RocksDB使用Log Structured Database Engine进行存储,完全用C ++编写。一个名为RocksJava的Java版本目前正在开发中。
RocksDB具有高度灵活的配置选项,可以调整以运行在各种生产环境上的不同设备之上,包括pure memory,Flash,hard disks或HDFS。它支持各种压缩算法并且为production support和debugging提供良好的工具。
专门希望存储在本地Flash drives或RAM中,高达几TB数据的应用程序服务器而设计
针对fast storage设备,存储small到medium size key-values进行了优化( flash devices 或 in-memory )
与CPU数量线性扩展,以便在具有多核的处理器上运行良好
RocksDB引入了几十个新的主要features。不在LevelDB中的feature列表
RocksDB设计主要专注于fast storage和server workloads。它应该利用Flash或RAM子系统提供的高速率读/写全部潜力。它应该支持高效的point lookups以及range scans。它应该可配置为支持high random-read workloads,high update workloads或两者的组合(最优解)。其架构应易于调整Read Amplification, Write Amplification 和 Space Amplification。
ceph L版已经发布很久了,官方说默认使用BlueStore作为OSD的存储后端,在Cephalocon APAC 2018上也是讨论的焦点之一。
提到BlueStore,不得不说一说Ceph的STORAGE DEVICES。
Ceph守护进程将数据存储在磁盘上:
1 | Ceph OSDs ( Object Storage Daemons ) |
OSD可以通过两种方式管理存储的数据。从Luminous 12.2.z发行版开始,新的默认(推荐)后端是 BlueStore。在Luminous之前,默认(也是唯一的选择)是 FileStore。
BlueStore是专门用于Ceph OSD管理磁盘上的数据的专用存储后端。在过去十年间,受到了FileStore管理OSD经验的启发.
BlueStore的主要功能包括:
1 | 直接管理存储设备 ( Direct management of storage devices ) |
http://docs.ceph.com/docs/master/rados/configuration/bluestore-config-ref/
http://docs.ceph.com/docs/master/rados/operations/bluestore-migration/
FileStore是在Ceph中存储objects的传统方法。它依赖于标准文件系统(通常是XFS)和某个元数据的key/value数据库(传统上是LevelDB,现在是RocksDB)结合使用。
FileStore经过良好测试并广泛用于生产,但由于其整体设计和对传统文件系统存储object数据的依赖性,因此存在许多性能缺陷。
尽管FileStore通常能够在大多数与POSIX兼容的文件系统(包括btrfs和ext4)上运行,但我们只建议使用XFS。
btrfs和ext4都有已知的bug和缺陷,使用它们可能会导致数据丢失。默认情况下,所有的Ceph提供的工具都将使用XFS。
http://docs.ceph.com/docs/master/rados/configuration/filestore-config-ref/
在ceph L版代码结构改动比较大,增加了CEPH-MGR向外部监测和管理系统提供额外的监测接口,今天就用虚拟机搭建实验环境玩一玩。
1 | [root@cephL ~]# yum install -y redhat-lsb |
1 | [root@cephL ~]# curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py" |
1 | [root@cephL ~]# mkdir ceph-deploy && cd ceph-deploy |
1 | [root@cephL ceph-deploy]# ceph-deploy new --public-network 192.168.56.101/24 --cluster-network 192.168.56.101/24 cephL |
1 | [root@cephL ceph-deploy]# ceph-deploy mon create-initial |
bluestore方法
1 | # 在创建osd时,L版默认是bluestore |
遇到问题
1 | [root@cephL ceph-deploy]# ceph -s |
filestore方法
1 | # 如果是filestore则需要对data device和journal device先做GPT partition |
1 | # 使OSD进入out状态 |
1 | install netstat tool |
相关命令
1 | [root@cephL ceph-deploy]# ceph mgr module ls |
1 | [root@cephL ceph-deploy]# ceph-deploy mds create cephL |
Ceph文件系统至少需要两个RADOS pool,一个用于存储数据,一个用于存储元数据。
配置这些pool时,可以考虑:
对元数据pool使用更多的replication数量,因为该pool中的任何数据丢失都可能导致整个文件系统无法访问。
为元数据pool使用SSD等较低延迟的存储设备,因为这将直接影响客户端上文件系统操作的延迟。
1 | ceph osd pool create cephfs_data <pg_num> |
更改pool的副本数
1 | ceph osd pool set {poolname} size {num-replicas} |
一旦创建了pool,就可以使用fs new命令启用文件系统:
1 | ceph fs new <fs_name> <metadata> <data> |
一旦创建了文件系统,您的MDS将能够进入active状态。例如,在single MDS system中:
1 | [root@cephL ceph-deploy]# ceph mds stat |
一旦创建了文件系统并且MDS处于active状态,你就可以挂载文件系统了。如果您创建了多个文件系统,在挂载文件系统时,选择使用哪一个。
如果创建了多个文件系统,并且client在挂载时没有指定挂载哪个文件系统,你可以使用ceph fs set-default命令来设置client默认看到的文件系统。
挂载CEPH FS ( File System ) 有两种方式:
KERNEL DRIVER
要挂载Ceph文件系统,您可以在知道monitor主机IP地址的情况下使用mount命令,或使用mount.ceph utility将monitor主机名解析为IP地址。例如:
1 | sudo mkdir /mnt/mycephfs |
如果挂载Ceph文件系统时开启了cephx authentication,您必须指定user和secret。
1 | sudo mount -t ceph 192.168.0.1:6789:/ /mnt/mycephfs -o name=admin,secret=AQATSKdNGBnwLhAAnNDKnH65FmVKpXZJVasUeQ== |
上述用法在Bash history中留下了secret。更安全的方法是从文件中读取secret。 例如:
1 | sudo mount -t ceph 192.168.0.1:6789:/ /mnt/mycephfs -o name=admin,secretfile=/etc/ceph/admin.secret |
如果您有多个文件系统,请使用mds_namespace选项指定要挂载的文件系统,例如-o mds_namespace=myfs
要卸载Ceph文件系统,可以使用umount命令。 例如:
1 | sudo umount /mnt/mycephfs |
FUSE
在用户空间(FUSE)中挂载Ceph文件系统之前,请确保客户端主机具有Ceph配置文件的副本以及Ceph元数据服务器的CAPS keyring。
在您的客户端主机上,将Ceph配置文件从monitor主机复制到/etc/ceph目录。
1 | sudo mkdir -p /etc/ceph |
在您的客户端主机上,将monitor主机的Ceph keyring复制到/etc/ceph目录。
1 | sudo scp {user}@{server-machine}:/etc/ceph/ceph.keyring /etc/ceph/ceph.keyring |
确保Ceph配置文件和keyring在您的客户端机器上设置了适当的权限(例如,chmod 644)。
要将Ceph文件系统挂在为FUSE,可以使用ceph-fuse命令。 例如:
1 | sudo mkdir /home/usernname/cephfs |
如果您拥有多个文件系统,请使用 –client_mds_namespace 命令行参数指定要挂载哪一个文件系统,或者向ceph.conf中添加client_mds_namespace设置。
要自动挂载ceph-fuse,您可以在system fstab中添加一个条目。此外还可以使用ceph-fuse@.service和ceph-fuse.target systemd units。通常这些unit文件为ceph-fuse描述默认的dependencies和推荐的execution context。例如使用ceph-fuse挂载到/mnt:
1 | sudo systemctl start ceph-fuse@/mnt.service |
持久化挂载点可通过以下方式进行设置:
1 | sudo systemctl enable ceph-fuse@/mnt.service |
Ceph Object Gateway原来叫RADOS Gateway,它是构建在librados之上的对象存储接口,为应用程序提供了一个RESTful gateway,用户可以通过HTTP协议访问Ceph存储集群。
Ceph Object Storage支持两个接口:
S3-compatible:与Amazon S3 RESTful API中一些子集兼容的接口,提供对象存储功能。
Swift-compatible:与OpenStack Swift API中一些子集兼容的接口,提供对象存储功能。
Ceph Object Storage使用Ceph Object Gateway daemon (radosgw),它是一个HTTP server,用于与Ceph存储集群进行交互。由于它提供了与OpenStack Swift和Amazon S3兼容的接口,因此Ceph Object Gateway具有自己的用户管理。Ceph Object Gateway可以将数据存储在与Ceph Filesystem和Ceph Block Device相同的Ceph存储集群中。但是我相信在生产环境中不会这么做,如果数据量大的话会影响Ceph Filesystem和Ceph Block Device的性能,个人一般会独立出一个Ceph Object Gateway集群。S3和Swift API共享一个通用的namespace,因此您可以使用一个API编写数据并使用另一个API检索它。
1 | Note:Ceph Object Storage 不使用 Ceph Metadata Server |
1 | # 必须部署MGR,才能部署RGW |
在L版中,删除pool的操作做了强制限制。需要在/etc/ceph/ceph.conf中加入相关参数才允许删除pool。
1 | # 允许删除pool,需要添加 |
Go发行版包含了一个名为go的命令,实现Go包和命令的自动下载,编译,安装和测试。
Go从开始就明确目标:能够仅使用源代码本身的信息来构建Go代码,而不需要编写makefile或Makefile。
起初,在没有Go编译器的时候,为了达到自动化构建的目的,Go单独使用一个程序生成makefile。新的go命令的目的使我们回到这个理想。
该go命令要求代码遵循几个关键的惯例:
1 | 1、导入路径源自源码的URL。对于Bitbucket、GitHub、Google Code和Launchpad来说, 其代码仓库的根目录由该仓库的主URL确定,无需 http:// 前缀。 子目录名附加在该路径之后。 |
首先配置GOPATH,进入此目录后,我们就可以添加一些源码了。假设我们想要使用codesearch项目中的索引包, 以及一个左倾红黑树包。我们可以用“go get”子命令安装二者:
1 | $ go get code.google.com/p/codesearch/index |
这两个包现在已被下载并安装到我们的 $GOPATH 目录中了。该目录树现在包含了 src/code.google.com/p/codesearch/index/ 和 src/github.com/petar/GoLLRB/llrb/ 这两个目录,以及那些库和依赖的已编译包 (在 pkg/ 中)
“go list”子命令会列出其对应实参的导入路径,而模式”./…” 意为从当前目录(”./“)开始,查找该目录下的所有包(”…”):
1 | yujiangdeMBP-13:go yujiang$ go list ./... |
我们也可以测试这些包:
1 | go test ./... |
“go install”子命令会将包的最新副本安装到pkg目录中。由于 go 命令会分析依赖关系,因此,除非该包导入的其它包已过期,否则 “go install” 也会递归地根据依赖关系安装它们。
可以根据要求和实际情况从互联网上下载或更新指定的代码包及其依赖包,并对它们进行编译和安装。
1 | [root@centos7 ~]# go help get |
-d标志指示在下载软件包后停止,不安装软件包。
-f标志仅在设置-u时有效,该标记会让命令程序忽略掉对已下载代码包的导入路径的检查。如果下载并安装的代码包所属的项目是你从别人那里Fork过来的,那么这样做就尤为重要了。
-fix标志指示命令程序在下载代码包后先执行修正动作,而后再进行编译和安装。
-insecure标志允许命令程序使用非安全的scheme(如HTTP)去下载指定的代码包。如果你用的代码仓库(如公司内部的Gitlab)没有HTTPS支持,可以添加此标记。请在确定安全的情况下使用它。
-t标志指示让命令程序同时下载并安装指定的代码包中的测试源码文件中依赖的代码包。
-u标志指示让命令利用网络来更新已有代码包及其依赖包。默认情况下,该命令只会从网络上下载本地不存在的代码包,而不会更新已有的代码包。
-v标志启用详细的进度和调试输出。
-x标志可以看到go get
命令执行过程中所使用的所有命令
Get也接受build flags来控制安装。请参阅go help build
。
当检出一个新的包时,get创建目标目录GOPATH/src/<import-path>
。如果GOPATH包含多个条目,get使用第一个条目。欲了解更多详情,请参阅:go help gopath
。
在checkout或update软件包时,请查找与本地安装的Go版本相匹配的branch或tag。最重要的规则是,如果本地安装运行版本“go1”,则搜索名为“go1”的branch或tag。如果不存在这样的版本,它将检索软件包的默认branch。
当去checkout或update git repository时,它还会更新repository引用的任何git子模块。
永远不要checksout或update存储在vendor目录中的代码。
有关指定软件包的更多信息,请参阅go help packages
。
有关go get
如何查找要下载的源代码的更多信息,请参阅go help importpath
。
另请参见:go build
,go install
,go clean
。
【1】http://wiki.jikexueyuan.com/project/go-command-tutorial/0.3.html
1、首先根据你所使用的系统,下载对应的Golang包。
国内可以在这里下载:https://studygolang.com/dl
2、然后配置Golang的环境变量,配置方法类似于Java环境变量
持久化方法:编辑 ~/.bash_profile 文件,配置自己的目录,例如:
1 | GOPATH="/Users/yujiang/go" |
使配置生效source ~/.bash_profile
主要的环境变量有:
GOROOT:你所安装的go可执行文件的目录
GOROOT="/usr/local/go"
GOPATH:是自己的项目所在目录,也就是所谓的工作目录,可以配置多个目录,使用’;’分隔。
GOPATH如果配置多个目录,使用go install/go get
时,会默认匹配第一个目录,后面的忽略。
GOPATH="/Users/yujiang/go"
3、工作目录结构
通常在你的工作目录中,包含3个子目录
1 | bin 存放编译后的二进制文件 |