阳历和阴历相互转化的工具类 golang版本

nosix 发表了文章 • 1 个评论 • 120 次浏览 • 3 天前 • 来自相关话题

github 地址 https://github.com/nosixtools/solarlunar

  1. 实现了阳历和阴历... 查看全部

github 地址 https://github.com/nosixtools/solarlunar



  1. 实现了阳历和阴历的相互转化,支持1900年到2049年。

  2. 支持节假日的计算


分享给你们


转化例子


package main 

import (
"github.com/nosixtools/solarlunar"
"fmt"
)

func main() {
solarDate := "1990-05-06"
fmt.Println(solarlunar.SolarToChineseLuanr(solarDate))
fmt.Println(solarlunar.SolarToSimpleLuanr(solarDate))

lunarDate := "1990-04-12"
fmt.Println(solarlunar.LunarToSolar(lunarDate, false))
}

节假日计算例子


package main

import (
"fmt"
"github.com/nosixtools/solarlunar/festival"
)

func main() {
festival := festival.NewFestival("./festival.json")
fmt.Println(festival.GetFestivals("2017-08-28"))
fmt.Println(festival.GetFestivals("2017-05-01"))
fmt.Println(festival.GetFestivals("2017-04-05"))
fmt.Println(festival.GetFestivals("2017-10-01"))
fmt.Println(festival.GetFestivals("2018-02-15"))
fmt.Println(festival.GetFestivals("2018-02-16"))
}

用Go实例学习Protobuf编码

ochapman 发表了文章 • 1 个评论 • 180 次浏览 • 6 天前 • 来自相关话题

Protobuf使用非常广泛,但是内部是怎么编码的呢,本文用Go的案例来学习protobuf编码规则。 https://slide.mua.io...

用喜欢和舒服的方式在Golang中使用锁、使用channel自定义锁

pathbox 发表了文章 • 4 个评论 • 214 次浏览 • 6 天前 • 来自相关话题

众所周知,我们能使用Golang轻松编写并发程序。Golang利用goroutine,让我们编写并发程序变得容易。并发程序中重要的问题之一就是如何正确的处理“竞争资源”或“共享资源”。Golang为我们提供了锁的机制。这篇文章,就简单介绍Golang中锁... 查看全部

众所周知,我们能使用Golang轻松编写并发程序。Golang利用goroutine,让我们编写并发程序变得容易。并发程序中重要的问题之一就是如何正确的处理“竞争资源”或“共享资源”。Golang为我们提供了锁的机制。这篇文章,就简单介绍Golang中锁的使用方法。并且进行错误的使用方法和正确的使用方法的代码示例对比。文章的所以代码示例在:https://github.com/pathbox/learning-go/tree/master/src/lock


原文链接


我们看第一个栗子:


package main

import (
"fmt"
"sync"
)

type Counter struct {
Value int
}

var wg sync.WaitGroup
var mutex sync.Mutex // 声明了一个全局锁
func main() {

wg.Add(1000)
counter := &Counter{Value: 0}

for i := 0; i < 1000; i++ {
go Count(counter, mutex)
}
wg.Wait()
fmt.Println("Count Value: ", counter.Value)
}

func Count(counter *Counter, mutex sync.Mutex) {
mutex.Lock()
defer mutex.Unlock()
counter.Value++
wg.Done()
}

/*
输出结果:
Count Value: 982
*/

这里声明了一个全局锁 sync.Mutex,然后将这个全局锁以参数的方式代入到方法中,这样并没有真正起到加锁的作用。


正确的方式是:


package main

import (
"fmt"
"sync"
)

type Counter struct {
Value int
}

var wg sync.WaitGroup
var mutex sync.Mutex // 声明了一个全局锁
func main() {

wg.Add(1000)
counter := &Counter{Value: 0}

for i := 0; i < 1000; i++ {
go Count(counter)
}
wg.Wait()
fmt.Println("Count Value: ", counter.Value)
}

func Count(counter *Counter) {
mutex.Lock()
defer mutex.Unlock()
counter.Value++
wg.Done()
}

/*
输出结果:
Count Value: 1000
*/

声明了一个全局锁后,其作用范围是全局。直接使用,而不是将其作为参数传递到方法中。


下一个栗子


package main

import (
"fmt"
"sync"
)

type Counter struct {
Value int
}

var wg sync.WaitGroup

func main() {
var mutex sync.Mutex // 声明了一个非全局锁
wg.Add(1000)
counter := &Counter{Value: 0}

for i := 0; i < 1000; i++ {
go Count(counter, mutex)
}
wg.Wait()
fmt.Println("Count Value: ", counter.Value)
}

func Count(counter *Counter, mutex sync.Mutex) {
mutex.Lock()
defer mutex.Unlock()
counter.Value++
wg.Done()
}

/*
输出结果:
Count Value: 954
*/

上面栗子中,声明的不是全局锁。然后将这个锁作为参数传入到Count()方法中,这样并没有真正起到加锁的作用。


正确的方式:


package main

import (
"fmt"
"sync"
)

type Counter struct {
Value int
}

var wg sync.WaitGroup

func main() {
mutex := &sync.Mutex{} // 定义了一个锁 mutex,赋值给mutex
wg.Add(1000)
counter := &Counter{Value: 0}

for i := 0; i < 1000; i++ {
go Count(counter, mutex)
}
wg.Wait()
fmt.Println("Count Value: ", counter.Value)
}

func Count(counter *Counter, mutex *sync.Mutex) {
mutex.Lock()
defer mutex.Unlock()
counter.Value++
wg.Done()
}

/*
输出结果:
Count Value: 1000
*/

这次通过 mutex := &sync.Mutex{},定义了mutex,然后作为参数传递到方法中,正确实现了加锁功能。


简单的说,在全局声明全局锁,之后这个全局锁就能在代码中的作用域范围内都能使用了。但是,也许你需要的不是全局锁。这和锁的粒度有关。
所以,你可以声明一个锁,在其作用域范围内使用,并且这个作用域范围是有并发执行的,别将锁当成参数传递。如果,需要将锁当成参数传递,那么你传的不是一个锁的声明,而是这个锁的指针。


下面,我们讨论一种更好的使用方式。通过阅读过很多”牛人“写的Go的程序或源码库,在锁的使用中。常常将锁放入对应的 struct 中定义,我觉得这是一种不错的方法。


package main

import (
"fmt"
"sync"
)

type Counter struct {
Value int
sync.Mutex
}

var wg sync.WaitGroup

func main() {

wg.Add(1000)
counter := &Counter{Value: 0}

for i := 0; i < 1000; i++ {
go Count(counter)
}
wg.Wait()
fmt.Println("Count Value: ", counter.Value)
}

func Count(counter *Counter) {
counter.Lock()
defer counter.Unlock()
counter.Value++
wg.Done()
}

/*
输出结果:
Count Value: 1000
*/

这样,我们声明的不是全局锁,并且这个需要加锁的竞争资源也正是 struct Counter 本身的Value属性,反映了这个锁的粒度。我觉得这是一种很舒服的使用方式(暂不知道这种方式会带来什么负面影响,如果有踩过坑的朋友,欢迎聊一聊这个坑),当然,如果你需要全局锁,那么请定义全局锁。


还可以有更多的使用方式:


// 1.
type Counter struct {
Value int
Mutex sync.Mutex
}

counter := &Counter{Value: 0}
counter.Mutex.Lock()
defer counter.Mutex.Unlock()

//2.
type Counter struct {
Value int
Mutex *sync.Mutex
}

counter := &Counter{Value: 0, Mutex: &sync.Mutex{}}
counter.Mutex.Lock()
defer counter.Mutex.Unlock()

Choose the way you like~


接下来,我们自己尝试创建一个互斥锁。


简单的说,简单的互斥锁锁的原理是:一个线程(进程)拿到了这个互斥锁,在这个时刻,只有这个线程(进程)能够进行互斥锁锁的范围中的"共享资源"的操作,主要是写操作。我们这里不讨论读锁的实现。锁的种类很多,有不同的实现场景和功能。这里我们讨论的是最简单的互斥锁。


我们能够利用Golang 的channel所具有特性,创建一个简单的互斥锁。


/locker/locker.go


package locker

// Mutext struct
type Mutex struct {
lock chan struct{}
}

// 创建一个互斥锁
func NewMutex() *Mutex {
return &Mutex{lock: make(chan struct{}, 1)}
}

// 锁操作
func (m *Mutex) Lock() {
m.lock <- struct{}{}
}

// 解锁操作
func (m *Mutex) Unlock() {
<-m.lock
}

main.go


package main

import (
"./locker"
"fmt"
"time"
)

type record struct {
lock *locker.Mutex
lock_count int
no_lock_count int
}

func newRecord() *record {
return &record{
lock: locker.NewMutex(),
lock_count: 0,
no_lock_count: 0,
}
}

func main() {
r := newRecord()

for i := 0; i < 1000; i++ {
go CountWithoutLock(r)
go CountWithLock(r)
}
time.Sleep(2 * time.Second)
fmt.Println("Record no_lock_count: ", r.no_lock_count)
fmt.Println("Record lock_count: ", r.lock_count)
}

func CountWithLock(r *record) {
r.lock.Lock()
defer r.lock.Unlock()
r.lock_count++
}

func CountWithoutLock(r *record) {
r.no_lock_count++
}

/* 输出结果
Record no_lock_count: 995
Record lock_count: 1000
*/

locker 就是通过使用channel的读操作和写操作会互相阻塞等待的这个同步性质。
可以简单的理解为,channel中传递的就是互斥锁。一个线程(进程)申请了一个互斥锁(struct{}{}),将这个互斥锁存放在channel中,
其他线程(进程)就没法申请互斥锁放入channel,而处于阻塞状态,等待channel恢复空闲空间。该线程(进程)进行操作”共享资源“,然后释放这个互斥锁(从channel中取走),channel这时候恢复了空闲的空间,其他线程(进程)
就能申请互斥锁并且放入channel。这样,在某一时刻,只会有一个线程(进程)拥有互斥锁,在操作"共享资源"。

官方依赖管理工具:dep

qiangmzsx 发表了文章 • 2 个评论 • 320 次浏览 • 2017-08-08 19:20 • 来自相关话题

在这里声明一下,百度或者google看到的godep不是我这篇博文说的dep,那它们是什么关系呢?按照查看全部

在这里声明一下,百度或者google看到的godep不是我这篇博文说的dep,那它们是什么关系呢?按照Peter Bourgon博文来说,它们的作者都有相同的人,但是一个是dep是官方版本,godep是第三方工具。

我今天介绍的是dep,之前也有介绍过glide,有兴趣的可以到Golang依赖管理工具:glide从入门到精通使用看看。
现在还有一个疑问是为什么官方现在要支持依赖管理了呢?我个人认为有如下原因(勿喷,如果不同或者遗漏欢迎留言补充):



  • 第三方依赖管理很多,虽然很好用,但是很少可以兼容的,结果--乱;

  • 官方的包管理为了增加社区的凝聚力,保持Go开箱即用的简单特性,不需要大家再安装各种第三方工具了,而且第三方工具都会过来兼容官方版的;

  • 还有一个官话,为了go更好的发展;

    dep的FAQ中有一段描述depgo get网址,也是侧面说了依赖管理工具和go get关系,
    一句话概括:依赖管理工具是为应用管理代码的,go get是为GOPATH管理代码的。


下面进入教程。


介绍


dep是一个原型依赖管理工具,需要在Go 1.7及更高的版本中使用,说明第三方工具近期还是有市场的。

PS:本博客的dep基于v0.3。


安装


环境准备。


//设置环境变量 使用vendor目录
GO15VENDOREXPERIMENT=1

安装dep


等到dep正式集成到Golang中时候,也许是Golang 1.10 ,广大吃瓜群众就可以直接使用go dep命令。现在还是需要自己安装的。


$ go get -u github.com/golang/dep/cmd/dep

验证安装


$ dep
dep is a tool for managing dependencies for Go projects

Usage: dep <command>

Commands:

init Initialize a new project with manifest and lock files
status Report the status of the project's dependencies
ensure Ensure a dependency is safely vendored in the project
prune Prune the vendor tree of unused packages

Examples:
dep init set up a new project
dep ensure install the project's dependencies
dep ensure -update update the locked versions of all dependencies
dep ensure -add github.com/pkg/errors add a dependency to the project

Use "dep help [command]" for more information about a command.

有一个很重要的选项ensure中文含义是确保;保证;担保,作者想传达的意思是确保所有本地状态-代码树、清单、锁和供应商彼此同步


看到这个说明已经安装好了。


使用


老规矩,篇幅有限,我只介绍经常使用到的。
先进入在GOPATH的一个项目中。


cd $GOPATH/src/foordep

初始化(dep init)


$ cd foordep/
$ dep init
$ ll
total 12
-rw-rw-r-- 1 qiangmzsx qiangmzsx 286 Aug 7 11:45 Gopkg.lock
-rw-rw-r-- 1 qiangmzsx qiangmzsx 535 Aug 7 11:45 Gopkg.toml
drwxrwxr-x 2 qiangmzsx qiangmzsx 4096 Aug 7 11:45 vendor

大家发现了,应用foordep目录下出现了两个文件(Gopkg.lock、Gopkg.toml)和一个目录(vendor)。
它们是什么关系呢?

关系

所以出现Gopkg.toml and Gopkg.lock are out of sync.时候最好执行一下dep ensure


下面看看它们的内容。


$ cat Gopkg.lock 
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.

[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "ab4fef131ee828e96ba67d31a7d690bd5f2f42040c6766b1b12fe856f87e0ff7"
solver-name = "gps-cdcl"
solver-version = 1
$ cat Gopkg.toml

# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/ ... ml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"

dep ensure


我们写一个Gopkg.toml看看效果。


# 必需包
required = ["github.com/astaxie/beego"]
# 忽略包
ignored = ["golang.org/x/crypto"]
# 项目元数据
[metadata]
homepage = "https://github.com/qiangmzsx"
license = "MIT"
owners_name_1 = "qiangmzsx"
owners_email_1 = "qiangmzsx@hotmail.com"
owners_homepage_1 = "https://github.com/qiangmzsx"

# 约束条件
[[constraint]]
name = "github.com/astaxie/beego"
# 可选:版本
version = "=1.8.0"
# 分支
#branch = "master"
# 修订
#revision = "beego 1.8.0"
# 可选:指定来源
source = "github.com/astaxie/beego"

但是怎么样执行?可以执行如下命令寻找帮助:


$ dep help ensure
Usage: dep ensure [-update | -add] [-no-vendor | -vendor-only] [-dry-run] [<spec>...]

Project spec:

<import path>[:alt source URL][@<constraint>]

Ensure gets a project into a complete, reproducible, and likely compilable state:

* All non-stdlib imports are fulfilled
* All rules in Gopkg.toml are respected
* Gopkg.lock records precise versions for all dependencies
* vendor/ is populated according to Gopkg.lock

Ensure has fast techniques to determine that some of these steps may be
unnecessary. If that determination is made, ensure may skip some steps. Flags
may be passed to bypass these checks; -vendor-only will allow an out-of-date
Gopkg.lock to populate vendor/, and -no-vendor will update Gopkg.lock (if
needed), but never touch vendor/.

The effect of passing project spec arguments varies slightly depending on the
combination of flags that are passed.

Examples:

dep ensure Populate vendor from existing Gopkg.toml and Gopkg.lock
dep ensure -add github.com/pkg/foo Introduce a named dependency at its newest version
dep ensure -add github.com/pkg/foo@^1.0.1 Introduce a named dependency with a particular constraint

For more detailed usage examples, see dep ensure -examples.

Flags:

-add add new dependencies, or populate Gopkg.toml with constraints for existing dependencies (default: false)
-dry-run only report the changes that would be made (default: false)
-examples print detailed usage examples (default: false)
-no-vendor update Gopkg.lock (if needed), but do not update vendor/ (default: false)
-update update the named dependencies (or all, if none are named) in Gopkg.lock to the latest allowed by Gopkg.toml (default: false)
-v enable verbose logging (default: false)
-vendor-only populate vendor/ from Gopkg.lock without updating it first (default: false)

执行一下


$ dep ensure
all dirs lacked any go code

错误了,这是因为foordep目录下没有任何的go代码,只能加上一个看看。


$ vim main.go
package main

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

func main() {
maxCPU := runtime.NumCPU()
runtime.GOMAXPROCS(maxCPU)
beego.Run()
}

再来试试看。


$ dep ensure
$ ll vendor/
total 4
drwxrwxr-x 3 qiangmzsx qiangmzsx 4096 Aug 7 16:29 github.com

看看Gopkg.lock的内容。


# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.

[[projects]]
name = "github.com/astaxie/beego"
packages = [".","config","context","grace","logs","session","toolbox","utils"]
revision = "323a1c4214101331a4b71922c23d19b7409ac71f"
source = "github.com/astaxie/beego"
version = "v1.8.0"

[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "6fb93334da1b165aaab11f170d871a92b40033575e66e2cf4289e77e0d64551d"
solver-name = "gps-cdcl"
solver-version = 1

现在需要解析json,我们试试使用命令行的方式导入github.com/bitly/go-simplejson包。


$ dep ensure -add github.com/bitly/go-simplejson
$ Gopkg.lock
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.

[[projects]]
name = "github.com/astaxie/beego"
packages = [".","config","context","grace","logs","session","toolbox","utils"]
revision = "323a1c4214101331a4b71922c23d19b7409ac71f"
source = "github.com/astaxie/beego"
version = "v1.8.0"

[[projects]]
name = "github.com/bitly/go-simplejson"
packages = ["."]
revision = "aabad6e819789e569bd6aabf444c935aa9ba1e44"
version = "v0.5.0"

[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "6fb93334da1b165aaab11f170d871a92b40033575e66e2cf4289e77e0d64551d"
solver-name = "gps-cdcl"
solver-version = 1

可以发现多了github.com/bitly/go-simplejson,但是Gopkg.toml并没有任何改变。
注意:执行dep ensure -add时候报错


$ dep ensure -add github.com/bitly/go-simplejson
Gopkg.toml and Gopkg.lock are out of sync. Run a plain dep ensure to resync them before attempting to -add

还可以指定依赖的版本:


$ dep ensure -add github.com/bitly/go-simplejson@=0.4.3

是因为Gopkg.tomlGopkg.lock不同步了,需要重新执行一下dep ensure即可。
重新整理一下Gopkg.toml


# 必需包
required = ["github.com/astaxie/beego"]
# 忽略包
ignored = ["golang.org/x/crypto"]
# 项目元数据
[metadata]
homepage = "https://github.com/qiangmzsx"
license = "MIT"
owners_name_1 = "qiangmzsx"
owners_email_1 = "qiangmzsx@hotmail.com"
owners_homepage_1 = "https://github.com/qiangmzsx"

# 约束条件
[[constraint]]
name = "github.com/astaxie/beego"
# 可选:版本
version = "=1.8.0"
# 分支
#branch = "master"
# 修订
#revision = "beego 1.8.0"
# 可选:指定来源
source = "github.com/astaxie/beego"

[[constraint]]
name = "github.com/bitly/go-simplejson"
branch = "master"
source = "https://github.com/bitly/go-simplejson.git"

Gopkg.tomlversion规则:
~=version使用的操作符规则,如果仅仅是指定version = "1.8.0",那么dep会自动加上^,表示最左边的非零位的版本加一


^1.2.3 意味 1.2.3 <= X < 2.0.0
^0.2.3 意味 0.2.3 <= X < 0.3.0
^0.0.3 意味 0.0.3 <= X < 0.1.0

如果执行dep ensure时候出现


$ dep  ensure 
error while parsing /home/users/qiangmzsx/golang/src/foordep/Gopkg.toml: multiple constraints specified for github.com/astaxie/beego, can only specify one

说明配置写错了,需要看看Gopkg.toml文件中是不是同时配置了versionbranchrevision


空配置


我们现在尝试着把foordep目录情况就留下main.go


package main

import (
"github.com/astaxie/beego"
"github.com/bitly/go-simplejson"
"runtime"
)

func main() {
maxCPU := runtime.NumCPU()
runtime.GOMAXPROCS(maxCPU)
strJson := `{"announcer": {"nickname": "非议讲史", "kind": "user", "created_at": 1494904539000, "updated_at": 1494983507000, "track_id": 38088960}}`
mapJson,_:=simplejson.NewJson([]byte(strJson))
println(mapJson)
beego.Run()
}

执行dep ensure 为了更好地看到过程,加上参数-v


$ dep init -v
Root project is "foordep"
1 transitively valid internal packages
2 external packages imported from 2 projects
(0) ✓ select (root)
(1) ? attempt github.com/bitly/go-simplejson with 1 pkgs; 5 versions to try
(1) try github.com/bitly/go-simplejson@v0.5.0
(1) ✓ select github.com/bitly/go-simplejson@v0.5.0 w/1 pkgs
(2) ? attempt github.com/astaxie/beego with 1 pkgs; 23 versions to try
(2) try github.com/astaxie/beego@v1.8.3
(2) ✓ select github.com/astaxie/beego@v1.8.3 w/9 pkgs
✓ found solution with 10 packages from 2 projects

Solver wall times by segment:
b-list-versions: 3.926086724s
b-list-pkgs: 285.209471ms
b-gmal: 266.828805ms
select-atom: 3.417834ms
satisfy: 3.126864ms
select-root: 428.276µs
new-atom: 234.106µs
other: 45.014µs
b-source-exists: 6.946µs
b-deduce-proj-root: 3.472µs

TOTAL: 4.485387512s

Using ^0.5.0 as constraint for direct dep github.com/bitly/go-simplejson
Locking in v0.5.0 (aabad6e) for direct dep github.com/bitly/go-simplejson
Using ^1.8.3 as constraint for direct dep github.com/astaxie/beego
Locking in v1.8.3 (cab8458) for direct dep github.com/astaxie/beego

此时再查看Gopkg.tomlGopkg.lock文件:


$ vim Gopkg.toml
[[constraint]]
name = "github.com/astaxie/beego"
version = "1.8.3"

[[constraint]]
name = "github.com/bitly/go-simplejson"
version = "0.5.0"

$ vim Gopkg.lock
[[projects]]
name = "github.com/astaxie/beego"
packages = [".","config","context","context/param","grace","logs","session","toolbox","utils"]
revision = "cab8458c1c4a5a3b4bf5192922be620e6dede15b"
version = "v1.8.3"

[[projects]]
name = "github.com/bitly/go-simplejson"
packages = ["."]
revision = "aabad6e819789e569bd6aabf444c935aa9ba1e44"
version = "v0.5.0"

[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "dc040fb12390d61768be6388ad2935bdd7f9cc93d4b1fdda421a284058277c80"
solver-name = "gps-cdcl"
solver-version = 1

glide一样,具有自举功能,不知道这个名词用得对不对。dep会自动根据代码生成Gopkg.tomlGopkg.lock配置文件。

PS:但是不建议使用,因为其拉取的依赖包都是最新的,可能出现不兼容,再者我国是一个被墙的地方。


dep cache


看到这里时候很多人都会有疑问?dep的依赖包每一次都是拉取新的还是优先使用本地cache呢?可以肯定的是
dep也是有本地缓存的,大家可以打开$GOPATH/pkg/dep/看看,是不是存在呢!

下面我们做两个测试看看。


$GOPATH/src不存在依赖包


环境准备,将原来的cache和vendor清空,别遗漏了$GOPATH/src中的github.com/bitly/go-simplejson


$ ll
total 4
-rwxr--r-- 1 qiangmzsx qiangmzsx 990 Aug 7 16:39 main.go
$ vim main.go
package main

import (
"github.com/astaxie/beego"
"github.com/bitly/go-simplejson"
"runtime"
)

func main() {
maxCPU := runtime.NumCPU()
runtime.GOMAXPROCS(maxCPU)
strJson := `{"announcer": {"nickname": "非议讲史", "kind": "user", "updated_at": 1494983507000, "track_id": 38088960}}`
mapJson,_:=simplejson.NewJson([]byte(strJson))
println(mapJson)
beego.Run()
}

执行dep init -gopath -v查看初始化过程。


$ ll
total 4
-rwxr--r-- 1 qiangmzsx qiangmzsx 382 Aug 8 12:47 main.go
$ dep init -gopath -v
Searching GOPATH for projects...
Following dependencies were not found in GOPATH. Dep will use the most recent versions of these projects.
github.com/bitly/go-simplejson
Root project is "foordep"
1 transitively valid internal packages
2 external packages imported from 2 projects
(0) ✓ select (root)
(1) ? attempt github.com/astaxie/beego with 1 pkgs; at least 1 versions to try
(1) try github.com/astaxie/beego@76ce912a30f9255d8d353d365ab99cb069fd29e0
(1) ✗ Unable to update checked out version: fatal: reference is not a tree: 76ce912a30f9255d8d353d365ab99cb069fd29e0
(1) try github.com/astaxie/beego@v1.8.3
(1) ✓ select github.com/astaxie/beego@v1.8.3 w/9 pkgs
(2) ? attempt github.com/bitly/go-simplejson with 1 pkgs; 5 versions to try
(2) try github.com/bitly/go-simplejson@v0.5.0
(2) ✓ select github.com/bitly/go-simplejson@v0.5.0 w/1 pkgs
✓ found solution with 10 packages from 2 projects

Solver wall times by segment:
b-list-pkgs: 320.955115ms
b-gmal: 274.950203ms
satisfy: 8.179966ms
select-atom: 2.62224ms
new-atom: 392.168µs
b-list-versions: 254.937µs
select-root: 209.152µs
b-deduce-proj-root: 40.45µs
other: 37.01µs
b-source-exists: 5.83µs

TOTAL: 607.647071ms

Using ^1.8.3 as constraint for direct dep github.com/astaxie/beego
Locking in v1.8.3 (cab8458) for direct dep github.com/astaxie/beego
Using ^0.5.0 as constraint for direct dep github.com/bitly/go-simplejson
Locking in v0.5.0 (aabad6e) for direct dep github.com/bitly/go-simplejson

日志显示,dep首先从$GOPATH查找github.com/bitly/go-simplejson,因为没有找到才从网络下载。


$GOPATH存在依赖包


环境准备,将原来的cache和vendor清空,注意$GOPATH/src中的github.com/bitly/go-simplejson存在。


$ ll
total 4
-rwxr--r-- 1 qiangmzsx qiangmzsx 382 Aug 8 12:47 main.go
$ dep init -gopath -v
Searching GOPATH for projects...
Using master as constraint for direct dep github.com/bitly/go-simplejson
Locking in master (da1a892) for direct dep github.com/bitly/go-simplejson
Root project is "foordep"
1 transitively valid internal packages
2 external packages imported from 2 projects
(0) ✓ select (root)
(1) ? attempt github.com/astaxie/beego with 1 pkgs; at least 1 versions to try
(1) try github.com/astaxie/beego@76ce912a30f9255d8d353d365ab99cb069fd29e0
(1) ✗ Unable to update checked out version: fatal: reference is not a tree: 76ce912a30f9255d8d353d365ab99cb069fd29e0
(1) try github.com/astaxie/beego@v1.8.3
(1) ✓ select github.com/astaxie/beego@v1.8.3 w/9 pkgs
(2) ? attempt github.com/bitly/go-simplejson with 1 pkgs; at least 1 versions to try
(2) try github.com/bitly/go-simplejson@master
(2) ✓ select github.com/bitly/go-simplejson@master w/1 pkgs
✓ found solution with 10 packages from 2 projects

Solver wall times by segment:
b-list-pkgs: 312.646734ms
b-gmal: 265.066047ms
satisfy: 6.488056ms
select-atom: 3.287416ms
new-atom: 397.837µs
select-root: 373.267µs
b-list-versions: 108.466µs
other: 47.43µs
b-source-exists: 7.71µs
b-deduce-proj-root: 6.568µs

TOTAL: 588.429531ms

Using ^1.8.3 as constraint for direct dep github.com/astaxie/beego
Locking in v1.8.3 (cab8458) for direct dep github.com/astaxie/beego

可以看到github.com/bitly/go-simplejson是优先从$GOPATH获取的。 好处我个人认为有两个:



  • 节省时间;

  • 本地类库的稳定性和兼容性已经经过用户验证了。


dep v0.1时候还不需要手动加上-gopath选项,dep工具会自动判断,但是dep v0.3后如果没有加上-gopath那么默认就是从网络下载。


更新配置 (dep ensure -update)


现在修改foordep项目的Gopkg.toml内容为:


$ vim  Gopkg.toml
[[constraint]]
name = "github.com/astaxie/beego"
# 约束为1.8.0
version = "=1.8.0"

[[constraint]]
name = "github.com/bitly/go-simplejson"
version = "0.5.0"
$ dep ensure -update
Gopkg.toml and Gopkg.lock are out of sync. Run a plain dep ensure to resync them before attempting to -update
$ dep ensure
$ dep ensure -update -v
Root project is "foordep"
1 transitively valid internal packages
2 external packages imported from 2 projects
(0) ✓ select (root)
(1) ? attempt github.com/astaxie/beego with 1 pkgs; 23 versions to try
(1) try github.com/astaxie/beego@v1.8.3
(2) ✗ github.com/astaxie/beego@v1.8.3 not allowed by constraint 1.8.0:
(2) 1.8.0 from (root)
(1) try github.com/astaxie/beego@v1.8.2
(2) ✗ github.com/astaxie/beego@v1.8.2 not allowed by constraint 1.8.0:
(2) 1.8.0 from (root)
(1) try github.com/astaxie/beego@v1.8.1
(2) ✗ github.com/astaxie/beego@v1.8.1 not allowed by constraint 1.8.0:
(2) 1.8.0 from (root)
(1) try github.com/astaxie/beego@v1.8.0
(1) ✓ select github.com/astaxie/beego@v1.8.0 w/8 pkgs
(2) ? attempt github.com/bitly/go-simplejson with 1 pkgs; 5 versions to try
(2) try github.com/bitly/go-simplejson@v0.5.0
(2) ✓ select github.com/bitly/go-simplejson@v0.5.0 w/1 pkgs
✓ found solution with 9 packages from 2 projects

Solver wall times by segment:
b-source-exists: 4.00794324s
b-list-pkgs: 2.545452669s
b-gmal: 276.070372ms
satisfy: 5.179016ms
select-atom: 4.337704ms
new-atom: 1.359055ms
b-list-versions: 467.799µs
b-matches: 371.239µs
select-root: 193.471µs
b-pair-rev: 127.992µs
other: 25.962µs
b-pair-version: 7.866µs

TOTAL: 6.841536385s

$ dep status
PROJECT CONSTRAINT VERSION REVISION LATEST PKGS USED
github.com/astaxie/beego 1.8.0 v1.8.0 323a1c4 323a1c4 8
github.com/bitly/go-simplejson ^0.5.0 v0.5.0 aabad6e aabad6e 1

后记


看到了这里,那么对dep已经可以进行基本的使用了,不过目前而言,dep还不够稳定,谁也不知道后续会怎么样更改,尝鲜可以,个人还不建议使用在线上。
如果大家喜欢这篇博文请点赞或者留言,有不同见解的也请留言讨论。

Golang高性能json包:easyjson

qiangmzsx 发表了文章 • 7 个评论 • 417 次浏览 • 2017-08-04 21:01 • 来自相关话题

简介

easyjson是什么呢? 根据官网介绍,easyjson是提供高效快速且易用的结构体structs<-->json转换... 查看全部

简介


easyjson是什么呢?
根据官网介绍,easyjson是提供高效快速且易用的结构体structs<-->json转换包。easyjson并没有使用反射方式实现,所以性能比其他的json包该4-5倍,比golang 自带的json包快2-3倍。
easyjson目标是维持生成去代码简单,以致于它可以轻松地进行优化或固定。


安装



go get -u github.com/mailru/easyjson/
go install github.com/mailru/easyjson/easyjson
or
go build -o easyjson github.com/mailru/easyjson/easyjson

验证是否安装成功。


$ easyjson
Usage of D:\Code\go\bin\easyjson.exe:
-all
generate marshaler/unmarshalers for all structs in a file
-build_tags string
build tags to add to generated file
-leave_temps
do not delete temporary files
-lower_camel_case
use lowerCamelCase names instead of CamelCase by default
-no_std_marshalers
don't generate MarshalJSON/UnmarshalJSON funcs
-noformat
do not run 'gofmt -w' on output file
-omit_empty
omit empty fields by default

string
specify the filename of the output
-pkg
process the whole package instead of just the given file
-snake_case
use snake_case names instead of CamelCase by default
-stubs
only generate stubs for marshaler/unmarshaler funcs

其中有几个选项需要注意:


-lower_camel_case:将结构体字段field首字母改为小写。如Name=>name。  
-build_tags string:将指定的string生成到生成的go文件头部。
-no_std_marshalers:不为结构体生成MarshalJSON/UnmarshalJSON函数。
-omit_empty:没有赋值的field可以不生成到json,否则field为该字段类型的默认值。
-output_filename:定义生成的文件名称。
-pkg:对包内指定有`//easyjson:json`结构体生成对应的easyjson配置。
-snke_case:可以下划线的field如`Name_Student`改为`name_student`。

使用


记得在需要使用easyjson的结构体上加上//easyjson:json
如下:


//easyjson:json
type School struct {
Name string `json:"name"`
Addr string `json:"addr"`
}

//easyjson:json
type Student struct {
Id int `json:"id"`
Name string `json:"s_name"`
School School `json:"s_chool"`
Birthday time.Time `json:"birthday"`
}

在结构体包下执行


easyjson  -all student.go

此时在该目录下出现一个新的文件。


// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.

package easyjson

import (
json "encoding/json"
easyjson "github.com/mailru/easyjson"
jlexer "github.com/mailru/easyjson/jlexer"
jwriter "github.com/mailru/easyjson/jwriter"
)

// suppress unused package warning
var (
_ *json.RawMessage
_ *jlexer.Lexer
_ *jwriter.Writer
_ easyjson.Marshaler
)

func easyjsonB83d7b77DecodeStudygoEasyjson(in *jlexer.Lexer, out *Student) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeString()
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "id":
out.Id = int(in.Int())
case "s_name":
out.Name = string(in.String())
case "s_chool":
easyjsonB83d7b77DecodeStudygoEasyjson1(in, &out.School)
case "birthday":
if data := in.Raw(); in.Ok() {
in.AddError((out.Birthday).UnmarshalJSON(data))
}
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjsonB83d7b77EncodeStudygoEasyjson(out *jwriter.Writer, in Student) {
out.RawByte('{')
first := true
_ = first
if !first {
out.RawByte(',')
}
first = false
out.RawString("\"id\":")
out.Int(int(in.Id))
if !first {
out.RawByte(',')
}
first = false
out.RawString("\"s_name\":")
out.String(string(in.Name))
if !first {
out.RawByte(',')
}
first = false
out.RawString("\"s_chool\":")
easyjsonB83d7b77EncodeStudygoEasyjson1(out, in.School)
if !first {
out.RawByte(',')
}
first = false
out.RawString("\"birthday\":")
out.Raw((in.Birthday).MarshalJSON())
out.RawByte('}')
}

// MarshalJSON supports json.Marshaler interface
func (v Student) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjsonB83d7b77EncodeStudygoEasyjson(&w, v)
return w.Buffer.BuildBytes(), w.Error
}

// MarshalEasyJSON supports easyjson.Marshaler interface
func (v Student) MarshalEasyJSON(w *jwriter.Writer) {
easyjsonB83d7b77EncodeStudygoEasyjson(w, v)
}

// UnmarshalJSON supports json.Unmarshaler interface
func (v *Student) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjsonB83d7b77DecodeStudygoEasyjson(&r, v)
return r.Error()
}

// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *Student) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjsonB83d7b77DecodeStudygoEasyjson(l, v)
}
func easyjsonB83d7b77DecodeStudygoEasyjson1(in *jlexer.Lexer, out *School) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeString()
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "name":
out.Name = string(in.String())
case "addr":
out.Addr = string(in.String())
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjsonB83d7b77EncodeStudygoEasyjson1(out *jwriter.Writer, in School) {
out.RawByte('{')
first := true
_ = first
if !first {
out.RawByte(',')
}
first = false
out.RawString("\"name\":")
out.String(string(in.Name))
if !first {
out.RawByte(',')
}
first = false
out.RawString("\"addr\":")
out.String(string(in.Addr))
out.RawByte('}')
}

现在可以写一个测试类啦。



package main

import (
"studygo/easyjson"
"time"
"fmt"
)

func main(){
s:=easyjson.Student{
Id: 11,
Name:"qq",
School:easyjson.School{
Name:"CUMT",
Addr:"xz",
},
Birthday:time.Now(),
}
bt,err:=s.MarshalJSON()
fmt.Println(string(bt),err)
json:=`{"id":11,"s_name":"qq","s_chool":{"name":"CUMT","addr":"xz"},"birthday":"2017-08-04T20:58:07.9894603+08:00"}`
ss:=easyjson.Student{}
ss.UnmarshalJSON([]byte(json))
fmt.Println(ss)
}

运行结果:


{"id":11,"s_name":"qq","s_chool":{"name":"CUMT","addr":"xz"},"birthday":"2017-08-04T20:58:07.9894603+08:00"} <nil>
{121 {CwwwwwwwUMT xzwwwww} 2017-08-04 20:52:03.4066002 +0800 CST}

[译]go styleguide

回复

Xargin 发起了问题 • 1 人关注 • 0 个回复 • 294 次浏览 • 2017-08-04 17:39 • 来自相关话题

golang面向对象分析

jeson 回复了问题 • 4 人关注 • 2 个回复 • 340 次浏览 • 2017-08-04 10:21 • 来自相关话题

Glide--Golang包版本管理工具

Julyqi 发表了文章 • 3 个评论 • 205 次浏览 • 2017-08-03 15:42 • 来自相关话题

Golang挺好用的,但是目前比较受到诟病的是其没有自带包管理工具(golang社区已经发起了一个dep的项目,不过要正式被golang接受还需要一段时间)。官方没有咋办,那就自己造轮子呗。所以 go的包管理工具就五花八门、层出不穷了。目前有Glide,... 查看全部

Golang挺好用的,但是目前比较受到诟病的是其没有自带包管理工具(golang社区已经发起了一个dep的项目,不过要正式被golang接受还需要一段时间)。官方没有咋办,那就自己造轮子呗。所以 go的包管理工具就五花八门、层出不穷了。目前有Glide, gopm, godep,gpm,gom等等。。。


这里介绍一个工具,使用比较简单: glide


说明原因:因为我也是分享给大家的,原文内容如果更新了,这个还是原来的内容不同步呢,而且大家如果真的感兴趣,点击查看也不会大家几分钟的。


下载说明

应届生求职

cloverstd 回复了问题 • 1 人关注 • 1 个回复 • 343 次浏览 • 2017-08-03 13:34 • 来自相关话题

Golang实现微型数学运算解释器

Julyqi 发表了文章 • 5 个评论 • 202 次浏览 • 2017-08-02 15:02 • 来自相关话题

对于一般的解释器来说,通常需要三个步骤:

词法分析 语法分析 指令执行

这篇文章所介绍的小型数学解释器则没有这么复杂的过程,原因在于语法设计及其简单。我们来看一下最终的使用效果, 源码在这儿:查看全部

对于一般的解释器来说,通常需要三个步骤:


词法分析
语法分析
指令执行


这篇文章所介绍的小型数学解释器则没有这么复杂的过程,原因在于语法设计及其简单。我们来看一下最终的使用效果, 源码在这儿:http://t.cn/R9JzbgY


大家也可以自己试试,遇到什么可以交流一下。

Golang bytes 包源码解析

qiangmzsx 回复了问题 • 2 人关注 • 1 个回复 • 276 次浏览 • 2017-08-01 20:48 • 来自相关话题

Golang优秀开源项目汇总(持续更新。。。)

Julyqi 发表了文章 • 1 个评论 • 435 次浏览 • 2017-07-31 14:52 • 来自相关话题

分享一些优秀的开源项目,能给我们开发者带来一些灵感,哪里不对的地方可以指出来哦。

项目列表如下 http://t.cn/R9cTHZu

也希望大家多多补充一些...

分享一些优秀的开源项目,能给我们开发者带来一些灵感,哪里不对的地方可以指出来哦。


项目列表如下 http://t.cn/R9cTHZu


也希望大家多多补充一些 哈

理解Golang并发编程

dasheng 发表了文章 • 2 个评论 • 1167 次浏览 • 2017-07-29 12:58 • 来自相关话题

点击查看原文章

concurrency vs parallelism

并发和并行是彼此相关的两个... 查看全部

点击查看原文章


concurrency vs parallelism


并发和并行是彼此相关的两个概念,并不能完全等价。在程序中,并发强调的是独立执行的程序的组合;并行强调的是同时执行计算任务[1]。

计算机核心的数量决定了并行计算的能力,大多数人类作为“单核”动物(老顽童小龙女除外),可以说自己在并发某些任务,如我在听歌写代码,但是不能说这两件事在并行,参考下图:
![concurrency vs parallelism](/images/currency vs parallelism.png)
Golang的并发模型源于Communicating Sequential Processes (CSP),通过提供goroutine和channel来实现并发编程模式。


Goroutine


Goroutine由Go运行时创建和管理,是用于调度CPU资源的“最小单元”,和OS的线程相比更轻量[2]:



  • 内存消耗更低只需2kB初始栈空间,而线程初始要1Mb的空间;

  • 由golang的运行时环境创建和销毁,更加廉价,不支持手动管理;

  • 切换效率更高等。
    Goroutine和线程的关系如下图所示:
    ![goroutine vs thread](/images/goroutine vs thread.png)


我们可以轻松地创建成百上千的goroutine,而不会降低程序的执行效率。

通过goroutine可以让一个函数和其他的函数并行执行。可以在函数调用前面加上go关键字,方便地创建一个goroutine。

main函数本身也是一个goroutine[3]。

举例如下:


package main

import "fmt"

func main() {
fmt.Println("begin main goroutine")
go hello()
fmt.Println("end main goroutine")
}

func hello() {
fmt.Println("begin hello goroutine")
}

输出:


begin main goroutine
end main goroutine

上面的例子中,并不会输出begin hello goroutine,这是因为,通过使用goroutine,我们不需要等待函数调用的返回结果,而会接着执行下面的代码。

可以在go hello()后面添加:


time.Sleep(1 * time.Second)

就可以正常输出begin hello goroutine


channel


Go提供了一种机制能够使goroutine之间进行通信和同步,它就是channel。

channel是一种类型,关键字chan和channel传输内容的类型共同定义了某一channel。

定义方式为:var c chan string = make(chan string),也可以简写为:var c = make(chan string)c := make(chan string)


通过左箭头<-操作符操作channel变量:



  • c <- "ping"向channel发送一个值为“ping”的字符串,

  • msg := <- c接收channel中的一个值,并赋給msg。


package main

import (
"fmt"
"strconv"
"time"
)

func main() {
c := make(chan string)
go ping(c)
go print(c)
var input string
fmt.Scanln(&input)
}

func ping(c chan string) {
for i := 0; ; i++ {
c <- strconv.Itoa(i)
}
}

func print(c chan string) {
for {
<-c
fmt.Println("reveving: " + <-c)
time.Sleep(1 * time.Second)
}
}

输出:


reveving: 1
reveving: 3
reveving: 5
reveving: 7
reveving: 9
...

按功能,可以将channel分为只发送或只接收channel,通过修改函数签名的channel形参类型来指定channel的“方向”:



  • 只允许发送: func ping(c chan<- string)

  • 只允许接收: func print(c <-chan string)

  • 任何对只发送channel的接收操作和只接收channel的发送操作都会产生编译错误。

  • 不指定方向的channel被称作“双向”channel,可以将“双向”channel最为参数,传递给接收单向channel的函数,反之,则不行。


unbuffered channel


非缓冲channel,也就是缓冲池大小为0的channel或者同步channel,上面的例子都是非缓冲channel,定义方式为:



  • ch := make(chan int)

  • ch := make(chan int, 0)


非缓冲channel在同步读时,如果channel的sendq中有就绪的goroutine,那么就取出(copy)数据并释放发送方goroutine;如果没有就绪的goroutine,那么将接收方goroutine挂起。

非缓冲channel在同步写时,如果channel的recvq中有就绪的goroutine,那么就取出(copy)数据到接收方goroutine,并使其就绪;如果没有,那么将发送发goroutine挂起。


buffered channel


缓冲channel只能容纳固定量的数据,当缓冲池满之后,发送发被阻塞,直到数据被接收释放缓冲池,定义如下:



  • ch := make(chan int)
    缓冲channel可以用来限制吞吐量,例子如下:


package main

import (
"fmt"
"time"
)

// Request struct
type Request struct {
}

var sem = make(chan int, 5) // Create a buffered channel witch capacity of 5

func main() {
queue := make(chan *Request)
go start(queue)
go serve(queue)
var input string
fmt.Scanln(&input)
}

func start(queue chan *Request) {
for {
queue <- &Request{}
}
}

func serve(queue chan *Request) {
for req := range queue {
sem <- 1 // Put on signal to channel
go handle(req) // Don't wait for handle to finish.
}
}

func handle(r *Request) {
process(r) // May take a long time.
<-sem // Done; enable next request to run.
}

func process(r *Request) {
fmt.Println("process")
time.Sleep(4 * time.Second)
}

每隔4秒钟,输出:


process
process
process
process
process

select


针对于channel,Golang提供了一个类似switch的功能,即select,使用如下:



  1. select选择第一个就绪的channel进行处理

  2. 如果有多个就绪的channel,则随机选择一个channel进行处理

  3. 如果没有就绪的channel,则等待直到某一channel就绪

  4. 如果有default,则在3情形中不会等待,而是立即执行default中的代码


package main

import (
"fmt"
"time"
)

func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go ping(ch1)
go pong(ch2)
go print(ch1, ch2)
var input string
fmt.Scanln(&input)
}

func ping(ch chan int) {
time.Sleep(2 * time.Second)
ch<-1
}

func pong(ch chan int) {
time.Sleep(3 * time.Second)
ch<-2
}

func print(ch1, ch2 chan int) {
select {
case msg := <-ch1:
fmt.Println(msg)
case msg := <-ch2:
fmt.Println(msg)
}
}

两秒钟之后,输出:1

在select语句中添加下面代码:


default:
fmt.Println("nothing received.")

输出: nothing received.


总结


Golang将线程抽象出来成为轻量级的goroutine,开发者不再需要过多地关注OS层面的逻辑,终于能够从并发编程中解放出来。

channel作为goroutine通信的媒介,安全高效的实现了goroutine之间的通信和共享内存。

用Effetive go中的一句话来总结[4]:



Do not communicate by sharing memory; instead, share memory by communicating.



Reference


[1] https://blog.golang.org/concurrency-is-not-parallelism

[2] http://blog.nindalf.com/how-goroutines-work/

[3] https://www.golang-book.com/books/intro/10

[4] https://golang.org/doc/effective_go.html

NoPoint Docker — 云时代的程序分发方式

Julyqi 发表了文章 • 0 个评论 • 195 次浏览 • 2017-07-28 16:17 • 来自相关话题

要说最近一年云计算业界有什么大事件?Google Compute Engine 的正式发布?Azure入华?还是AWS落地中国?留在每个人大脑中的印象可能各不相同,但要是让笔者来排名的话那么Docker绝对应该算是第一位的。如果你之前听说过它的话,那么也... 查看全部

要说最近一年云计算业界有什么大事件?Google Compute Engine 的正式发布?Azure入华?还是AWS落地中国?留在每个人大脑中的印象可能各不相同,但要是让笔者来排名的话那么Docker绝对应该算是第一位的。如果你之前听说过它的话,那么也许你会说“没错,就是它”,因为几乎世界各地的开发、运维都在谈论着Docker;如果你还没听说过Docker,那么我真的建议你花上5分钟来阅读本文。


https://community.clouderwork.com/article/view/59633469d009d.html


总之笔者认为Docker还是非常有趣的一个东西,值得大家花些时间体验一下,相信在各位的工作中多多少少都能用的上Docker。

记一次golang的gzip优化

thinkboy 发表了文章 • 4 个评论 • 638 次浏览 • 2017-07-26 21:11 • 来自相关话题

背景

近期使用Golang官方的"compress/gzip"包对数据压缩返回给App,此场景特性:数据不固定、高并发。在实际过程中发现一个简单逻辑的API服务,几百QPS的情况下CPU却很高达到几个核负载。查看全部
					

背景


近期使用Golang官方的"compress/gzip"包对数据压缩返回给App,此场景特性:数据不固定、高并发。在实际过程中发现一个简单逻辑的API服务,几百QPS的情况下CPU却很高达到几个核负载。

问题追踪


通过golang自带工具pprof抓图分析CPU,如下图(由于有业务代码,所以部分信息遮盖了):
http://img-hxy021.didistatic.com/static/way/do1_In6jEmpzPXAca5KnRNUD


通过此图可以看出,整个工程里有两个CPU消耗大头:1)GC高 2)大部分CPU耗在Gzip上.看方法属于New操作,再加上GC高,很容易往一个方向上去想,就是对象创建过多造成。


于是google搜了一些资料发现有人尝试优化gzip,地址:https://github.com/klauspost/compress/tree/master/gzip,但经过测试虽然速度提升20~30%,但是并不兼容原生Gzip,似乎并不是一个很通用的方案


分析源码


1.首先看下demo里原生的使用方式


demo地址:https://github.com/thinkboy/gzip-benchmark


func OldGzip(wr http.ResponseWriter, r *http.Request) {
buf := new(bytes.Buffer)
w := gzip.NewWriter(buf)

leng, err := w.Write(originBuff)
if err != nil || leng == 0 {
return
}
err = w.Flush()
if err != nil {
return
}
err = w.Close()
if err != nil {
return
}
b := buf.Bytes()
wr.Write(b)

// 查看是否兼容go官方gzip
/*gr, _ := gzip.NewReader(buf)
defer gr.Close()
rBuf, err := ioutil.ReadAll(gr)
if err != nil {
panic(err)
}
fmt.Println(string(rBuf))*/
}

2.其次看下官方gzip的实现,如下图:
http://img-hxy021.didistatic.com/static/way/do1_TIZjHEQ3BarilNCNC3Z8
http://img-hxy021.didistatic.com/static/way/do1_QvSBEc6YCpXRafIxvK9C


跟踪代码寻找几处与Pprof图相关的有New操作的地方,首先第一张图每次都会New一个Writer,然后在第二张图里的Write的时候,每次又都会为新创建的Writer分配一个压缩器。对于对象的反复创建有一个通用的思路,使用对象池。


3.尝试使用对象池


http://img-hxy021.didistatic.com/static/way/do1_HQTmIbkd8zBFJVKPmPwr


通过上图我们发现gzip的Writer有个Reset()方法,该方法调用的init()里的实现是如果已经存在压缩器,就复用并且Reset()。也就是说其实官方已经提供了一种方式让用户不再反复New Writer。然后我们可以这样改造下实现代码:


func MyGzip(wr http.ResponseWriter, r *http.Request) {
buf := spBuffer.Get().(*bytes.Buffer)
w := spWriter.Get().(*gzip.Writer)
w.Reset(buf)
defer func() {
// 归还buff
buf.Reset()
spBuffer.Put(buf)
// 归还Writer
spWriter.Put(w)
}()

leng, err := w.Write(originBuff)
if err != nil || leng == 0 {
return
}
err = w.Flush()
if err != nil {
return
}
err = w.Close()
if err != nil {
return
}
b := buf.Bytes()
wr.Write(b)

// 查看是否兼容go官方gzip
/*gr, _ := gzip.NewReader(buf)
defer gr.Close()
rBuf, err := ioutil.ReadAll(gr)
if err != nil {
panic(err)
}
fmt.Println(string(rBuf))*/
}

我们给压缩过程中用到的Buffer以及Writer定义对象池spBuffer、spWriter,然后每次api请求都从对象池里去取,然后Reset,从而绕过New操作。


这里容易产生一个疑问:对象池其实本身就是一个“全局大锁”,高并发场景下这把全局大锁影响有多大?(其实有一种深度优化的方式就是拆锁,比如依据某个ID进行取余取不同的对象池。这里就拿一把大锁来实验).


下面看一下此次改造后的压测结果(QPS: 3000):


不使用对象池(CPU 使用28个核左右):


http://img-hxy021.didistatic.com/static/way/do1_gmYIJXtQ7KNJtts9itST


使用对象池(CPU 使用22个核左右):


http://img-hxy021.didistatic.com/static/way/do1_mJGJQGHiQNYvumIZiqTx


通过CPU使用来看有消耗降低22%左右,由于QPS并不是很高,所以这里对象池的“全局大锁”的影响暂且可以忽略。


结论


针对官方Gzip的压缩可以使用对象池来改善。


klauspost所提供的方案也列举在demo中了,虽然属于自己改了压缩算法不兼容Golang官方包,但亲测对压缩速度也提升了很大百分比。使用该库+对象池的方式可能会达到更显著优化效果。


demo地址:https://github.com/thinkboy/gzip-benchmark