Golang

go-micro v2弃用了consul作为默认的服务发现

月盾

很遗憾,go-micro v2版本不再使用consul作为服务发现中间件,官方文档也没有consul相关的文档,而是默认改用了mdns,生产推荐etcd

问题:I can’t set registry with consul

解答:《Deprecating Consul in favour of Etcd

超过4年的时间,Consul一直是Micro的默认服务发现系统之一,为我们提供了良好的服务。实际上,从一开始,它就是用于注册表的默认机制以及入门所需的唯一基础依赖项。

从那时起,世界在不断发展,原生云技术也在不断发展。我们发现了许多与使用Consul的方式有关的问题。这不是对Consul的打击,而是对我们的用例的反思,以及对继续前进的需求。

例如,我们将元数据和服务端点信息进行二进制编码,压缩和base64编码,然后再将它们存储为Consul标签,因为没有其他方法可以这样做。我们还非常严重地滥用Consul的分布式属性,这导致了许多关于raft共识的问题。

不幸的是,我们发现现在该继续前进了。

自2014年以来,Kubernetes真正成为了容器编排和基础服务平台中的一支计算力。因此,etcd成为了他们选择的键值存储的一种,它是基于raft共识构建的分布式键值存储。它已经发展到可以满足kubernetes的规模需求,并且已经以其他开源项目所没有的方式经过了实战测试。

Etcd还是用于二进制数据的非常标准的Get / Put / Delete存储,这意味着我们可以轻松地编码和存储服务元数据,而不会出现零问题。它对所存储数据的格式没有意见。

过去一周中,我们已将etcd迁移为Micro中的默认服务发现机制之一,并将在未来几周内弃用Consul。这是什么意思?好吧,我们将领事移交给我们社区维护的go-plugins存储库,并专注于支持etcd。

我们知道许多用户正在使用Consul,这可能会导致中断。对我们来说,这是通往v2的重大突破,因此我们的下一个发行版将被标记为v2。您可以放心,您的v1发行版将继续按原样运行,但希望我们发布的下一个发行版是micro v2.0.0。

参考项目:micro-service

beego httplib库使用方法

月盾

beego是一个优秀的api,web框架,不只是其丰富的功能特性,更是因为其功能的独立性,可以根据自身需要单独添加使用。 常用的模块有以下这些:

  • session 模块
  • cache 模块
  • logs 模块
  • httplib 模块
  • context 模块
  • toolbox 模块
  • config 模块
  • i18n 模块

本文要讲解的是httplib客户端请求的使用。

日常开发中不只是要接收请求,还会发起http请求,go本身提供了http库可以实现http请求,不过使用起来略微复杂一些。如果使用的框架是beego的话,那推荐使用httplib

基本使用方法

import (
    "github.com/astaxie/beego/httplib"
)

然后初始化请求方法,返回对象

req := httplib.Get("http://beego.me/")

然后我们就可以获取数据了

str, err := req.String()
if err != nil {
    t.Fatal(err)
}
fmt.Println(str)

以上是最基本的使用方法,更多文档可以查看httplib文档,本文不再做一次搬运工。 下面提供一些使用实例以供参考:

获取body信息

func RequestByAjax3(region, language string) {
    req := httplib.Get(fmt.Sprintf("https://m.lagou.com/search.json?city=%s&positionName=%s&pageNo=1&pageSize=1", url.QueryEscape(region), language))
    req.Header("Referer", "https://m.lagou.com/search.html")
    req.Header("Cookie", "JSESSIONID=ABAAAECAAHHAAFD8DC17DEB3DE2DF3C5FCAE8C3D4423759; user_trace_token=20200117101405-234d1d57-b8c1-4d66-956e-c49f35f28f75; LGSID=20200117101406-09c6fa83-38cf-11ea-b2e7-525400f775ce;  PRE_LAND=https%3A%2F%2Fm.lagou.com%2Fsearch.html; LGUID=20200117101406-09c6fc06-38cf-11ea-b2e7-525400f775ce; X_HTTP_TOKEN=8e6e6bd15763030e425822975149ec77fc62d73ec7;")
    req.Header("Host", "m.lagou.com")
    req.Header("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/56.0.2924.75 Mobile/14E5239e Safari/602.1")
    var resBody wapResBody//TODO 根据需要自己定义
    req.ToJSON(&resBody)
    logs.Debug(">>>>>>%+v", resBody)
    if resBody.State != 1 {
        logs.Error("获取"+language+"数据为空!", fmt.Sprint("%+v", resBody))
    }
    logs.Debug(resBody)
}

上面代码中的resBody就是接收到的body内容,其核心是req.ToJSON(&resBody)

部署golang到服务器

月盾

说起将开发好的程序部署到服务上,常用的有两种方式:

  1. 本地编译打包,上传到服务器
  2. git push到远程仓库,在服务器上拉取(编译-打包)

无论以怎样的方式发布,都只有熟悉流程才能得心应手。今天我要说的是golang的部署流程。

如果是在公司内,自然有专人负责发布事宜,也有公司暂无运维人员,这时还是由开发人员负责服务器发布工作,当然,CI/CD这类工具一般也没有搭建起来。但这并不影响我们快速发布。 得益于go的编译速度,整个发布过程可能也就2分钟,接下来说明一下我个人的发布流程:

  1. 在项目目录下执行go打包命令
GOOS=linux GOARCH=amd64 go build 

由于是要部署到Linux服务器上,所以加上GOOS=linux GOARCH=amd64就可以打包出对应系统的二进制可执行文件。可以将该命令写成脚本文件。

  1. 推送代码到git仓库,这一步并不是必须,之所以需要这一步,是因为go只打包*.go文件,并不会打包静态文件,所以还需要把相关静态文件推送的git仓库以便拉取。

  2. 上传打包好的二进制可执行文件到服务器的项目目录下。为什么是项目目录?因为还有静态文件需要使用,所以服务器上也要有同样的项目结构。可借助一些工具来上传,我使用了rz命令来上传。

  3. git pull代码,主要是拉取静态文件。

  4. 重启应用。

整个过程比较耗时的操作是上传文件和推拉代码,打包和重启应用反而很快,基本是两三秒完成。 golang相对于其他语言,在服务,器上不需要安装运行时,不像Java和nodejs都需要安装正确的运行时版本,go只需要把打包好的二进制可执行文件扔上去就可以执行。

go语言开发grpc之安装grpc

月盾

一、安装gRPC

$ go get -u google.golang.org/grpc  
package google.golang.org/grpc: unrecognized import path "google.golang.org/grpc" (https fetch: Get https://google.golang.org/grpc?go-get=1: dial tcp 216.239.37.1:443: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.)

grpc的源码库迁移到了github上,所以需要手动下载了。grpc-go 正常情况下按照以下方式就可安装完成

git clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpc

git clone https://github.com/golang/net.git $GOPATH/src/golang.org/x/net

git clone https://github.com/golang/text.git $GOPATH/src/golang.org/x/text

go get -u github.com/golang/protobuf/{proto,protoc-gen-go}

git clone https://github.com/google/go-genproto.git $GOPATH/src/google.golang.org/genproto

cd $GOPATH/src/

go install google.golang.org/grpc

但是在某些情况可能连git clone都不行。就像下面这样的:

go实现文件下载

月盾

go可以很容易实现一个文件服务器,只需要使用函数 func ServeFile(w ResponseWriter, r *Request, name string)即可。

package main

import (
	"log"
	"net/http"
	"fmt"
)

func helloHandler(res http.ResponseWriter, req *http.Request) {
	http.ServeFile(res, req, "E:/go-work/src/go-learning/foo.xlsx")
}
func main() {
	fmt.Println("web服务启动成功,可在浏览器中访问:localhost:8081")
	http.HandleFunc("/file", helloHandler)
	http.Handle("/", http.FileServer(http.Dir("E:/go-work/src/go-learning/")))
	err := http.ListenAndServe(":8081", nil)
	if err != nil {
		log.Fatal("ListenAndServe:", err.Error())
	}
}

查看go文档除了翻墙访问https://golang.org

还可以访问国内镜像:https://golang.google.cn/

最简单快速的访问,直接在本地起服务:

godoc -http=:8082

beego注解路由404

月盾

beego注解路由匹配不到,返回404页面 router.go使用了两种方式注册路由:

ns := beego.NewNamespace("/admin",
	beego.NSRouter("/", &controllers.UserController{}, "get:Welcome"),
	beego.NSInclude(
		&controllers.UserController{},
	),

controller中的路由注解设置:

// @router /admin/user/get-all-user [get]
func (c *UserGroupController) GetAllUser() {
	user := new(User)
	users, err := user.GetUserList()
	if nil != err {
		c.Data["json"] = ErrorMsg(err)
	}
	c.Data["json"] = users
	c.ServeJSON()
}

使用上面的方式注册路由后结果是nomatch

最终结果显示上面的注解路由时错误的,下面是正确的注册方式: 问题在于controller的注解写法,如果该路由在namespace下,则不能在注解中拼接命名空间前缀,框架会自动拼接。 即/admin为命名空间,注解中只需写/user/get-all-user,不能这样写/admin/user/get-all-user

// @router /user/get-all-user [get]
func (c *UserGroupController) GetAllUser() {
	user := new(User)
	users, err := user.GetUserList()
	if nil != err {
		c.Data["json"] = ErrorMsg(err)
	}
	c.Data["json"] = SuccessData(users)
	c.ServeJSON()
}

当然,两种路由注册的方式可以同时

完整项目:https://github.com/yuedun/metal

go并发获取数据

月盾

go语言可以很轻松的实现并发获取数据,就算是新手也可以按部就班的套用现成的并发模式来实现并发。以下是一个简单的测试程序,其中有串行,并行。

package main

import (
	"sync"
	"time"
	"fmt"
)
func main() {
	syncFunc()
	fmt.Println(">>>>>>>>>>>>>>>")
	asyncFunc()
	fmt.Println(">>>>>>>>>>>>>>>")
	asyncChanFunc()
}
// 串行执行
func syncFunc() {
	var n,m,x int
	start := time.Now()
	fmt.Println("syncFunc start:",start)
	func () {
		time.Sleep(time.Second*1)
		n = 1
	}()
	func () {
		time.Sleep(time.Second*2)
		m = 2
	}()
	func () {
		time.Sleep(time.Second*3)
		x  =3
	}()
	t := time.Now()
	fmt.Println(t)
	elapsed := t.Sub(start)
	fmt.Println("syncFunc end:", elapsed, n, m, x)
}
// 并行执行
func asyncFunc() {
	var n,m,x int
	var wg sync.WaitGroup
	wg.Add(3)
	start := time.Now()
	fmt.Println("asyncFunc start:", start)
	go func () {
		defer wg.Done()
		time.Sleep(time.Second*1)
		n = 1
	}()
	go func () {
		defer wg.Done()
		time.Sleep(time.Second*2)
		m = 2
	}()
	go func () {
		defer wg.Done()
		time.Sleep(time.Second*3)
		x = 3
	}()
	wg.Wait()
	t := time.Now()
	fmt.Println(t)
	elapsed := t.Sub(start)
	fmt.Println("asyncFunc end:", elapsed, n, m, x)
}

// 并行执行
func asyncChanFunc() {
	var n, m, x =make(chan int),make(chan int),make(chan int)
	start := time.Now()
	fmt.Println("asyncChanFunc start:",start)
	go func () {
		time.Sleep(time.Second*1)
		n <- 1
	}()
	go func () {
		time.Sleep(time.Second*2)
		m <- 2
	}()
	go func () {
		time.Sleep(time.Second*3)
		x <- 3
	}()

	fmt.Printf("n:%d, m:%d, x:%d\n",<-n, <-m, <-x)
	t := time.Now()
	fmt.Println(t)
	elapsed := t.Sub(start)
	fmt.Println("asyncChanFunc end:", elapsed)
}

测试结果:

go测试函数的编写及运行

月盾

go test命令是一个按照一定的约定和组织的测试代码的驱动程序。在包目录内,所有以_test.go为后缀名的源文件并不是go build构建包的一部分,它们是go test测试的一部分。 在\*_test.go文件中,有三种类型的函数:测试函数、基准测试函数、示例函数。一个测试函数是以Test为函数名前缀的函数,用于测试程序的一些逻辑行为是否正确; go test命令会调用这些测试函数并报告测试结果是PASS或FAIL。基准测试函数是以Benchmark为函数名前缀的函数,它们用于衡量一些函数的性能;go test命令会多次运行基准函数以计算一个平均的执行时间。示例函数是以Example为函数名前缀的函数,提供一个由编译器保证正确性的示例文档。

测试函数

每个测试函数必须导入testing包。测试函数有如下的签名:

func TestName(t *testing.T) {
// ...
}

测试函数的名字必须以Test开头,可选的后缀名必须以大写字母开头:

func TestSin(t *testing.T) { /* ... */ }
func TestCos(t *testing.T) { /* ... */ }
func TestLog(t *testing.T) { /* ... */ }

其中t参数用于报告测试失败和附加的日志信息。让我们定义一个实例包gopl.io/ch11/word1,其中只有一个函数IsPalindrome用于检查一个字符串是否从前向后和从后向前读都是一样的。(下面这个实现对于一个字符串是否是回文字符串前后重复测试了两次;我们稍后会再讨论这个问题。)

// gopl.io/ch11/word1
// Package word provides utilities for word games.
package word

// IsPalindrome reports whether s reads the same forward and backward.
// (Our first attempt.)
func IsPalindrome(s string) bool {
	for i := range s {
		if s[i] != s[len(s)-1-i] {
			return false
		}
	}
	return true
}

在相同的目录下,word_test.go测试文件中包含了TestPalindrome和TestNonPalindrome两个测试函数。每一个都是测试IsPalindrome是否给出正确的结果,并使用t.Error报告失败信息

Golang- import 导入包的语法

月盾

一、 包的导入语法 在写Go代码的时候经常用到import这个命令用来导入包文件,看到的方式参考如下:

import(
	"fmt"
)

然后在代码里面可以通过如下的方式调用

fmt.Println("hello world")

上面这个fmt是Go语言的标准库,他其实是去GOROOT下去加载该模块,当然Go的import还支持如下两种方式来加载自己写的模块:

相对路径

import   "./model"  //当前文件同一目录的model目录,但是不建议这种方式import`

绝对路径

import   "shorturl/model"  //加载GOPATH/src/shorturl/model模块`

上面展示了一些import常用的几种方式,但是还有一些特殊的import,让很多新手很费解,下面是三种导入包的使用方法。

  1. 点操作

有时候会看到如下的方式导入包

import( 
    . "fmt" 
)

这个点操作的含义就是这个包导入之后在你调用这个包的函数时,你可以省略前缀的包名,也就是前面你调用的

fmt.Println("hello world")

可以省略的写成

Println("hello world")
  1. 别名操作

别名操作顾名思义可以把包命名成另一个用起来容易记忆的名字

import(
  f "fmt"
)

别名操作调用包函数时前缀变成了重命名的前缀,即

f.Println("hello world")
  1. _操作

这个操作经常是让很多人费解的一个操作符,请看下面这个import

import ( 
  "database/sql" 
  _ "github.com/ziutek/mymysql/godrv" 
)

_操作其实只是引入该包。当导入一个包时,它所有的init()函数就会被执行,但有些时候并非真的需要使用这些包,仅仅是希望它的init()函数被执行而已。这个时候就可以使用_操作引用该包了。即使用_操作引用包是无法通过包名来调用包中的导出函数,而是只是为了简单的调用其init函数()。

二、 包的导入过程说明 程序的初始化和执行都起始于main包。如果main包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到fmt包,但它只会被导入一次,因为没有必要导入多次)。当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数(如果有的话),依次类推。等所有被导入的包都加载完毕了,就会开始对main包中的包级常量和变量进行初始化,然后执行main包中的init函数(如果存在的话),最后执行main函数。下图详细地解释了整个执行过程:

go包导入过程

通过上面的介绍我们了解了import的时候其实是执行了该包里面的init函数,初始化了里面的变量,_操作只是说该包引入了,只初始化里面的init函数和一些变量,不能通过包名来调用其它的函数,这有什么用呢?往往这些init函数里面是注册自己包里面的引擎,让外部可以方便的使用,就很多实现database/sql的引起,在init函数里面都是调用了sql.Register(name string, driver driver.Driver)注册自己,然后外部就可以使用了。

原文地址:http://blog.csdn.net/zhangzhebjut/article/details/25564457

go语言实现继承,重写

月盾

以实际遇到过得情况为例,用户的数据结构中有类型为日期类型time.Time的createdAt属性,经过反复的格式化处理,在页面上输出的还是2017-05-31 06:49:09 +0800 CST这种格式,所以猜想日期类型是不能直接输出2017-05-31 06:49:09格式的,只能输出格式化后的字符串类型。于是利用go的继承将User的数据结构继承都UserPojo里,再单独对createdAt进行修改,重写为string类型。

package main

import "fmt"
import "time"

type User struct {
    name string
    age int
    createdAt time.Time
}

type UserPojo struct {
    User
    createdAt string
}

func (user *User) getName(){
    fmt.Println("获取用户名:", user.name)
}

func main()  {
    user := new(User)
    user.name="张三"
    user.age=26
    user.createdAt=time.Now()
    fmt.Println("user.createdAt",user.createdAt)

    userpj := new(UserPojo)
    userpj.User = *user
    userpj.createdAt = user.createdAt.Format("2006-01-02 15:04:05")
    fmt.Println("userpj.createdAt",userpj.createdAt)
}

//输出
//user.createdAt 2017-06-17 10:39:29.5294 +0800 CST
//userpj.createdAt 2017-06-17 10:39:29

在go的继承中有一点需要注意,使用结构体struct字面量赋值会出现找不到属性的问题:

https://hopefully-img.yuedun.wang/go_extends_20180328182436.pn

# command-line-arguments
.\test.go:17:7: unknown field 'Name' in struct literal of type Cat
.\test.go:18:8: unknown field 'Color' in struct literal of type Cat

换一种方式还是有问题: