10EnableRealTimeVideo = &Switch{Name: "xxx.real.time.video", On: true}

11

12)

13

14funcinit(){

15xxxSwitchManager.Register(AsyncProcedure,

16EnableRealTimeVideo)

17}

18

19

20// 具体实现结构和实现方法

21typeSwitch struct{

22Name string

23On bool

24listeners []ChangeListener

25}

26

27func(s *Switch)TurnOn(){

28s.On = true

29s.notifyListeners()

30}

31

32func(s *Switch)notifyListeners(){

33iflen(s.listeners) > 0{

34for_, l := ranges.listeners {

35l.OnChange(s.Name, s.On)

36}

37}

38}

39

40func(s *Switch)TurnOff(){

41s.On = false

42s.notifyListeners()

43}

44

45func(s *Switch)IsOn()bool{

46returns.On

47}

48

49func(s *Switch)IsOff()bool{

50return!s.On

51}

52

53func(s *Switch)AddChangeListener(l ChangeListener){

54ifl == nil{

55return

56}

57s.listeners = append(s.listeners, l)

58}

59

60typeSwitchManager struct{

61switches map[ string]*Switch

62}

63

64func(m SwitchManager)Register(switches ...*Switch){

65for_, s := rangeswitches {

66m.switches[s.Name] = s

67}

68}

69

70func(m SwitchManager)Unregister(name string){

71delete(m.switches, name)

72}

73

74func(m SwitchManager)TurnOn(name string)(bool, error){

75ifs, ok := m.switches[name]; ok {

76s.TurnOn()

77returntrue, nil

78} else{

79returnfalse, errors.New( "switch "+ name + " is not registered")

80}

81}

82

83func(m SwitchManager)TurnOff(name string)(bool, error){

84ifs, ok := m.switches[name]; ok {

85s.TurnOff()

86returntrue, nil

87} else{

88returnfalse, errors.New( "switch "+ name + " is not registered")

89}

90}

91

92func(m SwitchManager)IsOn(name string)(bool, error){

93ifs, ok := m.switches[name]; ok {

94returns.IsOn(), nil

95} else{

96returnfalse, errors.New( "switch "+ name + " is not registered")

97}

98}

99

100func(m SwitchManager)List()map[string]bool{

101switches := make( map[ string] bool)

102forname, switcher := rangem.switches {

103switches[name] = switcher.On

104}

105returnswitches

106}

107

108typeChangeListener interface{

109OnChange(name string, isOn bool)

110}

111

112

113// 这里开始调用

114ifswitches.AsyncProcedure.IsOn() {

115// do sth

116} else{

117// do other sth

118}

prometheus + grafana

prometheus + grafana 是业界常用的监控方案,prometheus进行数据采集,grafana进行图表展示。

Golang里面prometheus进行数据采集非常简单,有对应client库,应用程序只需暴露出http接口即可,这样,prometheus server端就可以定期采集数据,并且还可以根据这个接口来监控服务端是否异常【如挂掉的情况】。

1import"github.com/prometheus/client_golang/prometheus"

2

3engine.GET( "/metrics", gin.WrapH(prometheus.Handler()))

这样就实现了数据采集,但是具体采集什么样的数据,数据从哪里生成的,还需要进入下一步:

1packageprometheus

2

3import"github.com/prometheus/client_golang/prometheus"

4

5varDefaultBuckets = [] float64{ 10, 50, 100, 200, 500, 1000, 3000}

6

7varMySQLHistogramVec = prometheus.NewHistogramVec(

8prometheus.HistogramOpts{

9Namespace: "allen.wu",

10Subsystem: "xxx",

11Name: "mysql_op_milliseconds",

12Help: "The mysql database operation duration in milliseconds",

13Buckets: DefaultBuckets,

14},

15[] string{ "db"},

16)

17

18varRedisHistogramVec = prometheus.NewHistogramVec(

19prometheus.HistogramOpts{

20Namespace: "allen.wu",

21Subsystem: "xxx",

22Name: "redis_op_milliseconds",

23Help: "The redis operation duration in milliseconds",

24Buckets: DefaultBuckets,

25},

26[] string{ "redis"},

27)

28

29funcinit(){

30prometheus.MustRegister(MySQLHistogramVec)

31prometheus.MustRegister(RedisHistogramVec)

32...

33}

34

35

36// 使用,在对应的位置调用prometheus接口生成数据

37instanceOpts := []redis.Option{

38redis.Shards(shards...),

39redis.Password(viper.GetString(conf.RedisPrefix + name + ".password")),

40redis.ClusterName(name),

41redis.LatencyObserver( func(name string, latency time.Duration){

42prometheus.RedisHistogramVec.WithLabelValues(name).Observe( float64(latency.Nanoseconds()) * 1e-6)

43}),

44}

捕获异常 和 错误处理panic 异常

捕获异常是否有存在的必要,根据各自不同的项目自行决定,但是一般出现panic,如果没有异常,那么服务就会直接挂掉,如果能够捕获异常,那么出现panic的时候,服务不会挂掉,只是当前导致panic的某个功能,无法正常使用,个人建议还是在某些有必要的条件和入口处进行异常捕获。

常见抛出异常的情况:数组越界、空指针空对象,类型断言失败等;Golang里面捕获异常通过 defer + recover来实现

C++有try。。。catch来进行代码片段的异常捕获,Golang里面有recover来进行异常捕获,这个是Golang语言的基本功,是一个比较简单的功能,不多说,看代码

1funcconsumeSingle(kafkaMsg *sarama.ConsumerMessage){

2varerr error

3deferfunc(){

4ifr := recover(); r != nil{

5ife, ok := r.(error); ok {

6// 异常捕获的处理

7}

8}

9}()

10}

在请求来源入口处的函数或者某个方法里面实现这么一段代码进行捕获,这样,只要通过这个入口出现的异常都能被捕获,并打印详细日志

error 错误

error错误,可以自定义返回,一般工程应用中的做法,会在方法的返回值上增加一个error返回值,Golang允许每个函数返回多个返回值,增加一个error的作用在于,获取函数返回值的时候,根据error参数进行判断,如果是nil表示没有错误,正常处理,否则处理错误逻辑。这样减少代码出现异常情况

panic 抛出的堆栈信息排查

如果某些情况下,没有捕获异常,程序在运行过程中出现panic,一般都会有一些堆栈信息,我们如何根据这些堆栈信息快速定位并解决呢 ?

一般信息里面都会表明是哪种类似的panic,如是空指针异常还是数组越界,还是xxx;

然后会打印一堆信息出来包括出现异常的代码调用块及其文件位置,需要定位到最后的位置然后反推上去

分析示例如下

1{ "date": "2017-11-22 19:33:20.921", "pid":17, "level": "ERROR", "file": "recovery.go", "line":16, "func": "1", "msg": "panic in /Message.MessageService/Proces

2s: runtime error: invalid memory address or nil pointer dereference

3github.com.xxx/demo/biz/vendor/github.com.xxx/demo/commons/interceptor.newUnaryServerRecoveryInterceptor.func1.1

4/www/jenkins_home/.jenkins/jobs/demo/jobs/demo--biz/workspace/src/github.com.xxx/demo/biz/vendor/github.com.xxx/demo/commons/

5interceptor/recovery.go:17

6runtime.call64

7/www/jenkins_home/.jenkins/tools/org.jenkinsci.plugins.golang.GolangInstallation/go1.9/go/src/runtime/asm_amd64.s:510

8runtime.gopanic

9/www/jenkins_home/.jenkins/tools/org.jenkinsci.plugins.golang.GolangInstallation/go1.9/go/src/runtime/panic.go:491

10runtime.panicmem

11/www/jenkins_home/.jenkins/tools/org.jenkinsci.plugins.golang.GolangInstallation/go1.9/go/src/runtime/panic.go:63

12runtime.sigpanic

13/www/jenkins_home/.jenkins/tools/org.jenkinsci.plugins.golang.GolangInstallation/go1.9/go/src/runtime/signal_unix.go:367

14github.com.xxx/demo/biz/vendor/github.com.xxx/demo/mtrace-middleware-go/grpc.OpenTracingClientInterceptor.func1

15/www/jenkins_home/.jenkins/jobs/demo/jobs/demo--biz/workspace/src/github.com.xxx/demo/biz/vendor/github.com.xxx/demo/m

16trace-middleware-go/grpc/client.go:49

17github.com.xxx/demo/biz/vendor/github.com/grpc-ecosystem/go-grpc-middleware.ChainUnaryClient.func2.1.1

18/www/jenkins_home/.jenkins/jobs/demo/jobs/demo--biz/workspace/src/github.com.xxx/demo/biz/vendor/github.com/grpc-ecosystem/go-gr

19pc-middleware/chain.go:90

20github.com.xxx/demo/biz/vendor/github.com/grpc-ecosystem/go-grpc-middleware/retry.UnaryClientInterceptor.func1

21

问题分析

通过报错的堆栈信息,可以看到具体错误是“runtime error: invalid memory address or nil pointer dereference”,也就是空指针异常,然后逐步定位日志,可以发现最终导致出现异常的函数在这个,如下:

1github.com.xxx/demo/biz/vendor/github.com.xxx/demo/mtrace-middleware- go/grpc.OpenTracingClientInterceptor.func1

2

3/www/jenkins_home/.jenkins/jobs/demo/jobs/demo--biz/workspace/src/github.com.xxx/demo/biz/vendor/github.com.xxx/demo/m

4trace-middleware- go/grpc/client. go: 49

一般panic,都会有上述错误日志,然后通过日志,可以追踪到具体函数,然后看到OpenTracingClientInterceptor后,是在client.go的49行,然后开始反推,通过代码可以看到,可能是trace指针为空。然后一步一步看是从哪里开始调用的

最终发现代码如下:

1ucConn, err:= grpcclient.NewClientConn(conf.Discovery.UserCenter, newBalancer, time. Second* 3, conf.Tracer)

2iferr!= nil {

3logger.Fatalf(nil, "init user center client connection failed: %v", err)

4return

5}

6UserCenterClient = pb.NewUserCenterServiceClient(ucConn)

那么开始排查,conf.Tracer是不是可能为空,在哪里初始化,初始化有没有错,然后发现这个函数是在init里面,然后conf.Tracer确实在main函数里面显示调用的,main函数里面会引用或者间接引用所有包,那么init就一定在main之前执行。

这样的话,init执行的时候,conf.Tracer还没有被赋值,因此就是nil,就会导致panic了

项目工程级别接口

项目中如果能够有一些调试debug接口,有一些pprof性能分析接口,有探测、健康检查接口的话,会给整个项目在线上稳定运行带来很大的作用。 除了pprof性能分析接口属于Golang特有,其他的接口在任何语言都有,这里只是表明在一个工程中,需要有这类型的接口

上下线接口

我们的工程是通过etcd进行服务发现和注册的,同时还提供http服务,那么就需要有个机制来上下线,这样上线过程中,如果服务本身还没有全部启动完成准备就绪,那么就暂时不要在etcd里面注册,不要上线,以免有请求过来,等到就绪后再注册;下线过程中,先从etcd里面移除,这样流量不再导入过来,然后再等待一段时间用来处理还未完成的任务

我们的做法是,start 和 stop 服务的时候,调用API接口,然后再在服务的API接口里面注册和反注册到etcd

1varOnlineHook = func()error{

2returnnil

3}

4

5varOfflineHook = func()error{

6returnnil

7}

8

9

10// 初始化两个函数,注册和反注册到etcd的函数

11api.OnlineHook = func()error{

12returnregistry.Register(conf.Discovery.RegisterAddress)

13}

14

15api.OfflineHook = func()error{

16returnregistry.Deregister()

17}

18

19

20// 设置在线的函数里面分别调用上述两个函数,用来上下线

21funcSetOnline(isOnline bool)(err error){

22ifconf.Discovery.RegisterEnabled {

23if!isServerOnline && isOnline {

24err = OnlineHook()

25} elseifisServerOnline && !isOnline {

26err = OfflineHook()

27}

28}

29

30iferr != nil{

31return

32}

33

34isServerOnline = isOnline

35return

36}

37

38

39SetOnline 为Http API接口调用的函数

nginx 探测接口,健康检查接口

对于http的服务,一般访问都通过域名访问,nginx配置代理,这样保证服务可以随意扩缩容,但是nginx既然配置了代码,后端节点的情况,就必须要能够有接口可以探测,这样才能保证流量导入到的节点一定的在健康运行中的节点;为此,服务必须要提供健康检测的接口,这样才能方便nginx代理能够实时更新节点。

这个接口如何实现?nginx代理一般通过http code来处理,如果返回code=200,认为节点正常,如果是非200,认为节点异常,如果连续采样多次都返回异常,那么nginx将节点下掉

如提供一个/devops/status 的接口,用来检测,接口对应的具体实现为:

1funcCheckHealth(c *gin.Context){

2// 首先状态码设置为非200,如503

3httpStatus := http.StatusServiceUnavailable

4// 如果当前服务正常,并服务没有下线,则更新code

5ifisServerOnline {

6httpStatus = http.StatusOK

7}

8

9// 否则返回code为503

10c.IndentedJSON(httpStatus, gin.H{

11onlineParameter: isServerOnline,

12})

13}

PProf性能排查接口

1// PProf

2profGroup := debugGroup.Group( "/pprof")

3profGroup.GET( "/", func(c *gin.Context){

4pprof.Index(c.Writer, c.Request)

5})

6profGroup.GET( "/goroutine", gin.WrapH(pprof.Handler( "goroutine")))

7profGroup.GET( "/block", gin.WrapH(pprof.Handler( "block")))

8profGroup.GET( "/heap", gin.WrapH(pprof.Handler( "heap")))

9profGroup.GET( "/threadcreate", gin.WrapH(pprof.Handler( "threadcreate")))

10

11profGroup.GET( "/cmdline", func(c *gin.Context){

12pprof.Cmdline(c.Writer, c.Request)

13})

14profGroup.GET( "/profile", func(c *gin.Context){

15pprof.Profile(c.Writer, c.Request)

16})

17profGroup.GET( "/symbol", func(c *gin.Context){

18pprof.Symbol(c.Writer, c.Request)

19})

20profGroup.GET( "/trace", func(c *gin.Context){

21pprof.Trace(c.Writer, c.Request)

22})

debug调试接口

1// Debug

2debugGroup := engine.Group( "/debug")

3debugGroup.GET( "/requests", func(c *gin.Context){

4c.Writer.Header().Set( "Content-Type", "text/html; charset=utf-8")

5trace.Render(c.Writer, c.Request, true)

6})

7debugGroup.GET( "/events", func(c *gin.Context){

8c.Writer.Header().Set( "Content-Type", "text/html; charset=utf-8")

9trace.RenderEvents(c.Writer, c.Request, true)

10})

开关【降级】实时调整接口

前面有讲到过,在代码里面需要有开关和降级机制,并讲了实现示例,那么如果需要能够实时改变开关状态,并且实时生效,我们就可以提供一下http的API接口,供运维人员或者开发人员使用。

1// Switch

2console:= engine.Group( "/switch")

3{

4console.GET( "/list", httpsrv.MakeHandler(ListSwitches))

5console.GET( "/status", httpsrv.MakeHandler(CheckSwitchStatus))

6console.POST( "/turnOn", httpsrv.MakeHandler(TurnSwitchOn))

7console.POST( "/turnOff", httpsrv.MakeHandler(TurnSwitchOff))

8}

go test 单元测试用例

单元测试用例是必须,是自测的一个必要手段,Golang里面单元测试非常简单,import testing 包,然后执行go test,就能够测试某个模块代码

如,在某个user文件夹下有个user包,包文件为user.go,里面有个Func UpdateThemesCounts,如果想要进行test,那么在同级目录下,建立一个user_test.go的文件,包含testing包,编写test用例,然后调用go test即可

一般的规范有:

  • 每个测试函数必须导入testing包
  • 测试函数的名字必须以Test开头,可选的后缀名必须以大写字母开头
  • 将测试文件和源码放在相同目录下,并将名字命名为{source_filename}_test.go
  • 通常情况下,将测试文件和源码放在同一个包内。

如下:

1// user.go

2funcUpdateThemesCounts(ctx context.Context, themes []int, count int)error{

3redisClient := model.GetRedisClusterForTheme(ctx)

4key := themeKeyPattern

5for_, theme := rangethemes {

6ifredisClient == nil{

7returnerrors.New( "now redis client")

8}

9

10total, err := redisClient.HIncrBy(ctx, key, theme, count)

11iferr != nil{

12logger.Errorf(ctx, "add key:%v for theme:%v count:%v failed:%v", key, theme, count, err)

13returnerr

14} else{

15logger.Infof(ctx, "now key:%v theme:%v total:%v", key, theme, total)

16}

17}

18

19returnnil

20}

21

22

23//user_test.go

24packageuser

25

26import(

27"fmt"

28"testing"

29"Golang.org/x/net/context"

30)

31

32funcTestUpdateThemeCount(t *testing.T){

33ctx := context.Background()

34theme := 1

35count := 123

36total, err := UpdateThemeCount(ctx, theme, count)

37fmt.Printf( "update theme:%v counts:%v err:%v n", theme, total, err)

38}

39

40在此目录下执行 gotest即可出结果

测试单个文件 or 测试单个包

通常,一个包里面会有多个方法,多个文件,因此也有多个test用例,假如我们只想测试某一个方法的时候,那么我们需要指定某个文件的某个方案

如下:

1allen.wu@allen.wudeMacBook-Pro- 4:~/Documents/work_allen.wu/goDev/Applications/src/github.com.xxx/avatar/app_server/service/centralhub$tree .

2.

3├── msghub.go

4├── msghub_test.go

5├── pushhub.go

6├── rtvhub.go

7├── rtvhub_test.go

8├── userhub.go

9└── userhub_test.go

10

110directories, 7files

总共有7个文件,其中有三个test文件,假如我们只想要测试rtvhub.go里面的某个方法,如果直接运行go test,就会测试所有test.go文件了。

因此我们需要在go test 后面再指定我们需要测试的test.go 文件和 它的源文件,如下:

1gotest -v msghub. gomsghub_test. go

测试单个文件下的单个方法

在测试单个文件之下,假如我们单个文件下,有多个方法,我们还想只是测试单个文件下的单个方法,要如何实现?我们需要再在此基础上,用 -run 参数指定具体方法或者使用正则表达式。

假如test文件如下:

1packagecentralhub

2

3import(

4" context"

5" testing"

6)

7

8funcTestSendTimerInviteToServer( t* testing.T) {

9ctx := context. Background()

10

11err := sendTimerInviteToServer(ctx, 1461410596, 1561445452, 2)

12if err != nil {

13t. Errorf( "send to server friendship build failed. %v", err)

14}

15}

16

17funcTestSendTimerInvite( t* testing.T) {

18ctx := context. Background()

19err := sendTimerInvite(ctx, "test", 1461410596, 1561445452)

20if err != nil {

21t. Errorf( "send timeinvite to client failed:%v", err)

22}

23}

1gotest -v msghub. gomsghub_test. go-run TestSendTimerInvite

2

3gotest -v msghub. gomsghub_test. go-run "SendTimerInvite"

测试所有方法

指定目录即可

go test

测试覆盖度

go test工具给我们提供了测试覆盖度的参数,

go test -v -cover

go test -cover -coverprofile=cover.out -covermode=count

go tool cover -func=cover.out

goalng GC 、编译运行

服务端开发者如果在mac上开发,那么Golang工程的代码可以直接在mac上编译运行,然后如果需要部署在Linux系统的时候,在编译参数里面指定GOOS即可,这样可以本地调试ok后再部署到Linux服务器。

如果要部署到Linux服务,编译参数的指定为

1ldflags= "

2-X ${repo}/version.version=${version}

3-X ${repo}/version.branch=${branch}

4-X ${repo}/version.goVersion=${go_version}

5-X ${repo}/version.buildTime=${build_time}

6-X ${repo}/version.buildUser=${build_user}

7"

8

9CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "${ldflags}"-o $binary_dir/ $binary_name${repo}/

对于GC,我们要收集起来,记录到日志文件中,这样方便后续排查和定位,启动的时候指定一下即可执行gc,收集gc日志可以重定向

1exportGIN_MODE=release

2GODEBUG=gctrace=1 $SERVER_ENTRY1>/dev/null 2> $LOGDIR/gc.log.`date "+%Y%m%d%H%M%S"` &

3

4Golang包管理 目录代码管理

Golang包管理 目录代码管理目录代码管理

整个项目包括两大类,一个是自己编写的代码模块,一个是依赖的代码,依赖包需要有进行包管理,自己的编写的代码工程需要有一个合适的目录进行管理

main.go :入口

doc : 文档

conf : 配置相关

ops : 运维操作相关【http接口】

api : API接口【http交互接口】

daemon : 后台daemon相关

model : model模块,操作底层资源

service : model的service

grpcclient : rpc client

registry : etcd 注册

processor : 异步kafka消费

1.

2├── README.md

3├── api

4├── conf

5├── daemon

6├── dist

7├── doc

8├── grpcclient

9├── main.go

10├── misc

11├── model

12├── ops

13├── processor

14├── registry

15├── service

16├── tools

17├── vendor

18└── version

包管理

go允许import不同代码库的代码,例如github.com, golang.org等等;对于需要import的代码,可以使用 go get 命令取下来放到GOPATH对应的目录中去。

对于go来说,其实并不care你的代码是内部还是外部的,总之都在GOPATH里,任何import包的路径都是从GOPATH开始的;唯一的区别,就是内部依赖的包是开发者自己写的,外部依赖的包是go get下来的。

依赖GOPATH来解决go import有个很严重的问题:如果项目依赖的包做了修改,或者干脆删掉了,会影响到其他现有的项目。为了解决这个问题,go在1.5版本引入了vendor属性(默认关闭,需要设置go环境变量GO15VENDOREXPERIMENT=1),并在1.6版本之后都默认开启了vendor属性。 这样的话,所有的依赖包都在项目工程的vendor中了,每个项目都有各自的vendor,互不影响;但是vendor里面的包没有版本信息,不方便进行版本管理。

目前市场上常用的包管理工具主要有godep、glide、dep

godep

godep的使用者众多,如docker,kubernetes, coreos等go项目很多都是使用godep来管理其依赖,当然原因可能是早期也没的工具可选,早期我们也是使用godep进行包管理。

使用比较简单,godep save;godep restore;godep update;

但是后面随着我们使用和项目的进一步加强,我们发现godep有诸多痛点,目前已经逐步开始弃用godep,新项目都开始采用dep进行管理了。

godep的痛点:

  • godep如果遇到依赖项目里有vendor的时候就可能会导致编译不过,vendor下再嵌套vendor,就会导致编译的时候出现版本不一致的错误,会提示某个方法接口不对,全部放在当前项目的vendor下
  • godep锁定版本太麻烦了,在项目进一步发展过程中,我们依赖的项目(包)可能是早期的,后面由于升级更新,某些API接口可能有变;但是我们项目如果已经上线稳定运行,我们不想用新版,那么就需要锁定某个特定版本。但是这个对于godep而言,操作着实不方便。
  • godep的时候,经常会有一些包需要特定版本,然后包依赖编译不过,尤其是在多人协作的时候,本地gopath和vendor下的版本不一样,然后本地gopath和别人的gopath的版本不一样,导致编译会遇到各种依赖导致的问题

glide

glide也是在vendor之后出来的。glide的依赖包信息在glide.yaml和glide.lock中,前者记录了所有依赖的包,后者记录了依赖包的版本信息

glide create # 创建glide工程,生成glide.yaml

glide install # 生成glide.lock,并拷贝依赖包

glide update # 更新依赖包信息,更新glide.lock

因为glide官方说我们不更新功能了,只bugfix,请大家开始使用dep吧,所以鉴于此,我们在选择中就放弃了。同时,glide如果遇到依赖项目里有vendor的时候就直接跪了,dep的话,就会滤掉,不会再vendor下出现嵌套的,全部放在当前项目的vendor下

dep

golang官方出品,dep最近的版本已经做好了从其他依赖工具的vendor迁移过来的功能,功能很强大,是我们目前的最佳选择。不过目前还没有release1.0 ,但是已经可以用在生成环境中,对于新项目,我建议采用dep进行管理,不会有历史问题,而且当新项目上线的时候,dep也会进一步优化并且可能先于你的项目上线。

dep默认从github上拉取最新代码,如果想优先使用本地gopath,那么3.x版本的dep需要显式参数注明,如下

1depinit -gopath -v

总结

  • godep是最初使用最多的,能够满足大部分需求,也比较稳定,但是有一些不太好的体验;
  • glide 有版本管理,相对强大,但是官方表示不再进行开发;
  • dep是官方出品,目前没有release,功能同样强大,是目前最佳选择;

看官方的对比

Golang容易出现的问题包引用缺少导致panic

go vendor 缺失导致import多次导致panic

本工程下没有vendor目录,然而,引入了这个包“github.com.xxx/demo/biz/model/impl/hash”, 这个biz包里面包含了vendor目录。

这样,编译此工程的时候,会导致一部分import是从oracle下的vendor,另一部分是从gopath,这样就会出现一个包被两种不同方式import,导致出现重复注册而panic

并发 导致 panic

fatal error: concurrent map read and map write

并发编程中最容易出现资源竞争,以前玩C++的时候,资源出现竞争只会导致数据异常,不会导致程序异常panic,Golang里面会直接抛错,这个是比较好的做法,因为异常数据最终导致用户的数据异常,影响很大,甚至无法恢复,直接抛错后交给开发者去修复代码bug,一般在测试过程中或者代码review过程中就能够发现并发问题。

并发的处理方案有二:

Golang不允许包直接相互import,会导致编译不过。但是有个项目里面,A同学负责A模块,B同学负责B模块,由于产品需求导致,A模块要调用B模块中提供的方法,B模块要调用A模块中提供的方法,这样就导致了相互引用了

我们的解决方案是: 将其中一个相互引用的模块中的方法提炼出来,独立为另外一个模块,也就是另外一个包,这样就不至于相互引用

Golang json类型转换异常

Golang进行json转换的时候,常用做法是一个定义struct,成员变量使用tag标签,然后通过自带的json包进行处理,容易出现的问题主要有:

Golang 总结

golang使用一年多以来,个人认为golang有如下优点:

  • 学习入门快;让开发者开发更为简洁
  • 不用关心内存分配和释放,gc会帮我们处理;
  • 效率性能高;
  • 不用自己去实现一些基础数据结构,官方或者开源库可以直接import引用;
  • struct 和 interface 可以实现类、继承等面向对象的操作模式;
  • 初始化和赋值变量简洁;
  • 并发编程goroutine非常容易,结合chann可以很好的实现;
  • Context能够自我控制开始、停止,传递上下文信息

ID:Golangweb