golang

golang

单个 goroutine 的意义何在?已取消问题

回复

有问必答qingfeng 发起了问题 • 1 人关注 • 0 个回复 • 101 次浏览 • 1 天前 • 来自相关话题

大家都用什么web框架

技术讨论songtianyi 回复了问题 • 20 人关注 • 20 个回复 • 2900 次浏览 • 2 天前 • 来自相关话题

golang版本的curl请求库

开源程序mikemintang 发表了文章 • 0 个评论 • 255 次浏览 • 2017-09-15 07:55 • 来自相关话题

Github地址


https://github.com/mikemintang/go-curl


安装


go get github.com/mikemintang/go-curl

使用


package main

import (
"fmt"
"github.com/mikemintang/go-curl"
)

func main() {

url := "http://php.dev/api.php"

headers := map[string]string{
"User-Agent": "Sublime",
"Authorization": "Bearer access_token",
"Content-Type": "application/json",
}

cookies := map[string]string{
"userId": "12",
"loginTime": "15045682199",
}

queries := map[string]string{
"page": "2",
"act": "update",
}

postData := map[string]interface{}{
"name": "mike",
"age": 24,
"interests": []string{"basketball", "reading", "coding"},
"isAdmin": true,
}

// 链式操作
req := curl.NewRequest()
resp, err := req.
SetUrl(url).
SetHeaders(headers).
SetCookies(cookies).
SetQueries(queries).
SetPostData(postData).
Post()

if err != nil {
fmt.Println(err)
} else {
if resp.IsOk() {
fmt.Println(resp.Body)
} else {
fmt.Println(resp.Raw)
}
}

}

接收请求的api.php


<?php  

//echo json_encode($_GET); // 获取url地址中的查询参数
//echo json_encode(getallheaders()); // 获取请求头
//echo json_encode($_COOKIE); // 获取cookies
echo file_get_contents("php://input"); // 获取post提交的数据

function getallheaders() {
$headers = [];
foreach ($_SERVER as $name => $value) {
if (substr($name, 0, 5) == 'HTTP_') {
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
}
}
return $headers;
}

可导出的成员变量和方法



TodoList



  • [x] 以链式操作的方式发起请求

  • [ ] 以函数回调的方式发起请求

  • [ ] 以类Jquery Ajax的方式发起请求

  • [x] 发起GET/POST请求

  • [ ] 发起PUT/PATCH/DELETE/OPTIONS操作

  • [x] 以application/x-www-form-urlencoded形式提交post数据

  • [x] 以application/json形式提交post数据

  • [ ] 以multipart/form-data形式提交post数据

  • [ ] proxy代理设置

求助:有没有一种方式根据类型的名称获得类型的reflect.Value或reflect.Type

有问必答傅小黑 回复了问题 • 10 人关注 • 8 个回复 • 398 次浏览 • 2017-09-08 16:52 • 来自相关话题

goroutine 奇怪的输出顺序

有问必答trigged 回复了问题 • 3 人关注 • 3 个回复 • 266 次浏览 • 2017-09-06 23:47 • 来自相关话题

使用什么docker image来运行Go程序

有问必答myonlyzzy 回复了问题 • 24 人关注 • 22 个回复 • 3178 次浏览 • 2017-09-01 10:15 • 来自相关话题

Go 语言在命令行以表格的形式输出结构体切片

开源程序modood 发表了文章 • 3 个评论 • 239 次浏览 • 2017-08-29 15:32 • 来自相关话题

最近写的小工具,可以在命令行以表格的形式输出结构体切片

  • 没有第三方依赖
  • 支持中文汉字
  • 表格每列自动对齐
  • 支持自动适应列宽
  • 结构体的字段支持所有数据类型(字符... 查看全部

最近写的小工具,可以在命令行以表格的形式输出结构体切片



  • 没有第三方依赖

  • 支持中文汉字

  • 表格每列自动对齐

  • 支持自动适应列宽

  • 结构体的字段支持所有数据类型(字符串,切片,映射等)


例如可以很方便清晰地将数据库查询结果列表(结构体切片)在命令行以表格的形式输出。


项目 Github 主页:https://github.com/modood/table


对你有用的话,给个 star 支持一下吧~


package main

import (
"fmt"

"github.com/modood/table"
)

type House struct {
Name string
Sigil string
Motto string
}

func main() {
s := []House{
{"Stark", "direwolf", "Winter is coming"},
{"Targaryen", "dragon", "Fire and Blood"},
{"Lannister", "lion", "Hear Me Roar"},
}

table.Output(s)
}

输出结果:


┌───────────┬──────────┬──────────────────┐
│ Name │ Sigil │ Motto │
├───────────┼──────────┼──────────────────┤
│ Stark │ direwolf │ Winter is coming │
│ Targaryen │ dragon │ Fire and Blood │
│ Lannister │ lion │ Hear Me Roar │
└───────────┴──────────┴──────────────────┘

golang 如何动态创建struct

有问必答Lampo 回复了问题 • 19 人关注 • 9 个回复 • 3451 次浏览 • 2017-08-25 10:35 • 来自相关话题

想用golang写个分布式的监控,大神给点建议

有问必答zhipj 回复了问题 • 18 人关注 • 13 个回复 • 3289 次浏览 • 2017-08-23 03:35 • 来自相关话题

Golang API 业务监控项目求大神指点

开源程序haoweishow 回复了问题 • 3 人关注 • 2 个回复 • 854 次浏览 • 2017-08-22 09:20 • 来自相关话题

【上海】换手率-来自小米腾讯联合投资的互联网金融短线炒股APP-跪求Golang

招聘应聘catty 发表了文章 • 2 个评论 • 351 次浏览 • 2017-08-21 15:02 • 来自相关话题

关于我们:https://www.huanshoulv.com/

硅谷式氛围,简单可依赖。我们是一个充满激情和梦想的团队,请无情地向我砸简历。

查看全部

关于我们:https://www.huanshoulv.com/


硅谷式氛围,简单可依赖。我们是一个充满激情和梦想的团队,请无情地向我砸简历。




岗位职责:



  1. 上交所、深交所股票数据处理;

  2. 高性能API接口开发;




任职要求:



  1. 熟悉Linux环境;

  2. 熟悉Go语言;

  3. 2年以上工作经验;

  4. 有高并发开发经验;

  5. 熟悉MongDB、Redis;

  6. 沟通能力良好,有较强的抗压能力;




优先条件:



  1. 熟悉MQTT协议优先;

  2. 熟悉git,有github优先 请在简历中写明github地址;

  3. 熟悉c/c++/nodejs/python优先;

  4. 股票用户优先;




岗位待遇:20k-40k


简历投递邮箱:catty@huanshoulv.com 简历标题:姓名+岗位+来源

请问有没有比较好的分布式系统监控项目?

开源程序fiisio 回复了问题 • 10 人关注 • 4 个回复 • 1019 次浏览 • 2017-08-21 10:41 • 来自相关话题

Go有哪些好的视频教程

有问必答故城 回复了问题 • 15 人关注 • 3 个回复 • 1813 次浏览 • 2017-08-18 13:56 • 来自相关话题

golang有没有好的开源游戏框架

技术讨论cye 回复了问题 • 22 人关注 • 12 个回复 • 6413 次浏览 • 2017-08-16 17:23 • 来自相关话题

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

文章分享nosix 发表了文章 • 1 个评论 • 252 次浏览 • 2017-08-15 15:47 • 来自相关话题

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"))
}
条新动态, 点击查看
astaxie

astaxie 回答了问题 • 2016-10-10 18:35 • 26 个回复 不感兴趣

大家推荐哪种golang包管理方式?

赞同来自:

我们目前项目中使用的是godep,但是我最近尝试迁移到glide里面来,两个的功能都差不多,但是glide更强大一点,而且是Go1.5 vendor目录支持之后出来的,所以我还是比较推荐用这个。

这里列出来一些目前支持vendor的工具

* [manul... 显示全部 »
我们目前项目中使用的是godep,但是我最近尝试迁移到glide里面来,两个的功能都差不多,但是glide更强大一点,而且是Go1.5 vendor目录支持之后出来的,所以我还是比较推荐用这个。

这里列出来一些目前支持vendor的工具

* [manul](https://github.com/kovetskiy/manul) - Vendor packages using git submodules.
* [Godep](https://github.com/tools/godep)
* [Govendor](https://github.com/kardianos/govendor)
* [godm](https://github.com/hectorj/godm)
* [vexp](https://github.com/kr/vexp)
* [gv](https://github.com/forestgiant/gv)
* [gvt](https://github.com/FiloSottile/gvt) - Recursively retrieve and vendor packages.
* [govend](https://github.com/govend/govend)
* [Glide](https://github.com/Masterminds/glide) - Manage packages like composer, npm, bundler, or other languages.
* [Vendetta](https://github.com/dpw/vendetta)
* [trash](https://github.com/rancher/trash)
* [gsv](https://github.com/toxeus/gsv)
* [gom](https://github.com/mattn/gom)
astaxie

astaxie 回答了问题 • 2016-10-11 21:41 • 9 个回复 不感兴趣

golang 的channel是否适合做消息队列?

赞同来自:

我猜你看到的文章的担心是万一程序挂了怎么办,在缓冲channel里面的数据就可能丢失了,如果这个是可以忍受的话其实是非常适合做消息队列的
我猜你看到的文章的担心是万一程序挂了怎么办,在缓冲channel里面的数据就可能丢失了,如果这个是可以忍受的话其实是非常适合做消息队列的
name5566

name5566 回答了问题 • 2016-10-12 11:36 • 12 个回复 不感兴趣

golang有没有好的开源游戏框架

赞同来自:

> 使用 Leaf 已知的上线项目:
> * 2014 年,某手游(棋牌)项目上线
> * 2016 年,某 H5 手游项目上线
> * 2016 年,某卡牌手游项目上线
> 正在研发项目 N 个,已知情况 N >= 4

... 显示全部 »
> 使用 Leaf 已知的上线项目:
> * 2014 年,某手游(棋牌)项目上线
> * 2016 年,某 H5 手游项目上线
> * 2016 年,某卡牌手游项目上线
> 正在研发项目 N 个,已知情况 N >= 4

来自:https://github.com/name5566/leaf/wiki
leoliu

leoliu 回答了问题 • 2016-10-12 13:44 • 24 个回复 不感兴趣

求一些golang的教程,书籍也可以

赞同来自:

《The Golang Programming Language》
《Golang 学习笔记》
《The Golang Programming Language》
《Golang 学习笔记》
yougg

yougg 回答了问题 • 2016-10-14 10:01 • 78 个回复 不感兴趣

大家说说看都用啥写Go

赞同来自:

# IDEA大法好
# 天灭vscode 退软保平安
# 人在做,天在看 中文乱码留祸患
# 界面卡顿天地灭 赶紧卸载保平安
# 诚心诚念IDEA好 JetBrains大法平安保
# 众生皆为IDEA来 现世险恶忘前缘
# 开源为你说真相 教你脱险莫拒绝
# ... 显示全部 »
# IDEA大法好
# 天灭vscode 退软保平安
# 人在做,天在看 中文乱码留祸患
# 界面卡顿天地灭 赶紧卸载保平安
# 诚心诚念IDEA好 JetBrains大法平安保
# 众生皆为IDEA来 现世险恶忘前缘
# 开源为你说真相 教你脱险莫拒绝
# 早日不做软粉,早日获得新生
# 上网搜索“九评纳德拉”
# 有 真 相
sryan

sryan 回答了问题 • 2016-10-13 11:44 • 9 个回复 不感兴趣

golang 如何动态创建struct

赞同来自:

静态语言貌似不能直接实现
可以自己实现个map[string]func(string)interface{}
将要动态生成的结构体的函数注册上去
通过string来调用相应的函数来获取对应的结构体
静态语言貌似不能直接实现
可以自己实现个map[string]func(string)interface{}
将要动态生成的结构体的函数注册上去
通过string来调用相应的函数来获取对应的结构体
astaxie

astaxie 回答了问题 • 2016-10-13 22:04 • 13 个回复 不感兴趣

想用golang写个分布式的监控,大神给点建议

赞同来自:

这个问题很有意思,很多场景设计都会来考虑拉和推两种方案,我分别对拉和推两种的优缺点对比以下,你自己权衡一下,欢迎大家继续补充

## 拉的方案(不写agent)
优点:
- 不需要agent,不需要再部署新的程序

缺点:
- 网络中断的情况下,就无法监控机器... 显示全部 »
这个问题很有意思,很多场景设计都会来考虑拉和推两种方案,我分别对拉和推两种的优缺点对比以下,你自己权衡一下,欢迎大家继续补充

## 拉的方案(不写agent)
优点:
- 不需要agent,不需要再部署新的程序

缺点:
- 网络中断的情况下,就无法监控机器的信息

## 推的方案(agent)
优点:
- 本地运行,在和中控机失去网络连接的时候还是可以继续保存监控数据

缺点:
- 需要部署agent,如果机器多得话将来升级也是比较麻烦

拉取和推送其实大家可以考虑,微博的follow逻辑,直播流里面也有同样的问题,很多场景都会遇到

至于说第二种方案走什么协议,这种程序我建议走tcp协议,HTTP的话相对重了一点。
jinzhu

jinzhu 回答了问题 • 2016-10-19 15:38 • 14 个回复 不感兴趣

2016 年 10 月,当前好用的 ORM 是哪个?

赞同来自:

作为作者,推荐 GORM ;)
作为作者,推荐 GORM ;)
sheepbao

sheepbao 回答了问题 • 2016-10-30 20:16 • 15 个回复 不感兴趣

字符串连接哪一种方式最高效

赞同来自:

```go
package main

import (
"bytes"
"fmt"
"strings"
"time"
)

var way map[int]string
... 显示全部 »
```go
package main

import (
"bytes"
"fmt"
"strings"
"time"
)

var way map[int]string

func benchmarkStringFunction(n int, index int) (d time.Duration) {
v := "ni shuo wo shi bu shi tai wu liao le a?"
var s string
var buf bytes.Buffer

t0 := time.Now()
for i := 0; i < n; i++ {
switch index {
case 0: // fmt.Sprintf
s = fmt.Sprintf("%s[%s]", s, v)
case 1: // string +
s = s + "[" + v + "]"
case 2: // strings.Join
s = strings.Join([]string{s, "[", v, "]"}, "")
case 3: // stable bytes.Buffer
buf.WriteString("[")
buf.WriteString(v)
buf.WriteString("]")
}

}
d = time.Since(t0)
if index == 3 {
s = buf.String()
}
fmt.Printf("string len: %d\t", len(s))
fmt.Printf("time of [%s]=\t %v\n", way[index], d)
return d
}

func main() {
way = make(map[int]string, 5)
way[0] = "fmt.Sprintf"
way[1] = "+"
way[2] = "strings.Join"
way[3] = "bytes.Buffer"

k := 4
d := [5]time.Duration{}
for i := 0; i < k; i++ {
d[i] = benchmarkStringFunction(10000, i)
}
}

```
结果:
```
string len: 410000 time of [fmt.Sprintf]= 426.001476ms
string len: 410000 time of [+]= 307.044147ms
string len: 410000 time of [strings.Join]= 738.44362ms
string len: 410000 time of [bytes.Buffer]= 742.248µs
```
* strings.Join 最慢
* fmt.Sprintf 和 string + 差不多
* bytes.Buffer又比上者快约500倍
CodyGuo

CodyGuo 回答了问题 • 2016-10-30 12:57 • 4 个回复 不感兴趣

Go里面如何写多行的字符串吗?

赞同来自:

```go
`line 1
line 2
line 3`
```
```go
`line 1
line 2
line 3`
```
philosophia14

philosophia14 回答了问题 • 2016-11-05 00:30 • 3 个回复 不感兴趣

新版本Go将会对database/sql进行大量改进

赞同来自:

但愿不要多改..
但愿不要多改..
zdreamx

zdreamx 回答了问题 • 2016-11-08 11:27 • 26 个回复 不感兴趣

beego1.8版本功能征集

赞同来自:

orm有没有考虑分表分区分库的优化或支持
orm有没有考虑分表分区分库的优化或支持
println 是把结果输出到 standard error

fmt.Println 是把结果输出到 standard output
println 是把结果输出到 standard error

fmt.Println 是把结果输出到 standard output
傅小黑

傅小黑 回答了问题 • 2017-06-06 17:18 • 5 个回复 不感兴趣

Go指针复制问题

赞同来自:

```go
for k, r := range *rr {
fmt.Printf("%dth r, id: %d, cpu: %f, mem: %f\n", k, r.ID, r.CPU, r.MEM)
rs = append(rs, ... 显示全部 »
```go
for k, r := range *rr {
fmt.Printf("%dth r, id: %d, cpu: %f, mem: %f\n", k, r.ID, r.CPU, r.MEM)
rs = append(rs, &r)
}
```

这里的 r 一直是同一个地址的值的,for 循环的每次是覆盖旧的 r,你要用 *rr[k]
voidint

voidint 回答了问题 • 2017-07-25 09:20 • 7 个回复 不感兴趣

有必要设置多个gopath吗?

赞同来自:

我会设置起码2个`GOPATH`。因为`go get`会把代码拉倒第一个`GOPATH`的缘故,我会把第一个`GOPATH`用于存放第三方库,之后的`GOPATH`才是自己的项目,这样会更清晰。
我会设置起码2个`GOPATH`。因为`go get`会把代码拉倒第一个`GOPATH`的缘故,我会把第一个`GOPATH`用于存放第三方库,之后的`GOPATH`才是自己的项目,这样会更清晰。

大家是如何处理 golang web 应用静态资源的?

技术讨论astaxie 回复了问题 • 5 人关注 • 1 个回复 • 1249 次浏览 • 2016-10-14 13:08 • 来自相关话题

Python 程序员的 Golang 学习指南(II): 开发环境搭建

文章分享Cloudinsight 发表了文章 • 0 个评论 • 1559 次浏览 • 2016-10-12 15:44 • 来自相关话题

Authors: startover

Authors: startover





上一篇文章我们已经对 Golang 有了初步的了解,这篇主要介绍如何在 Ubuntu 14.04 上搭建 Golang 开发环境。


安装 Golang


这里就按照官方文档进行安装即可,如下:



  • 下载并解压安装包到指定目录


$ wget https://storage.googleapis.com ... ar.gz
$ tar -C /usr/local -xzf go1.6.3.linux-amd64.tar.gz


  • 设置 PATH


$ echo "export PATH=$PATH:/usr/local/go/bin" >> ~/.bashrc
$ source ~/.bashrc


  • 验证安装


$ go version
go version go1.6.3 linux/amd64

环境变量设置


$ echo "export GOROOT=/usr/local/go" >> ~/.bashrc
$ echo "export GOPATH=$HOME/go" >> ~/.bashrc
$ source ~/.bashrc

其中,GOROOT 为 Golang 的安装目录,只有当 Golang 安装到除 /usr/local 之外的路径时需要设置,反之则不用设置,GOPATH 是 Golang 的开发目录,详细可参考官方文档


开发工具


工欲善其事,必先利其器,作为一名伪 VIMer,这里主要介绍下如何在 Vim 下配置 Golang 开发环境。


由于之前一直使用 k-vim 作为 Python 开发环境,而 k-vim 已经集成了当前使用最为广泛的用于搭建 Golang 开发环境的 vim 插件 vim-go,只是默认没有开启,需要我们手动进行相关设置。


k-vim 中开启 Golang 语言的支持,非常简单,如下:



  • 修改 ~/.vimrc.bundles(开启 golang 支持,并修改 vim-go 的默认配置,增加快捷键配置等)。


let g:bundle_groups=['python', 'javascript', 'markdown', 'html', 'css', 'tmux', 'beta', 'json', 'golang']

" vimgo {{{
let g:go_highlight_functions = 1
let g:go_highlight_methods = 1
let g:go_highlight_structs = 1
let g:go_highlight_operators = 1
let g:go_highlight_build_constraints = 1

let g:go_fmt_fail_silently = 1
let g:go_fmt_command = "goimports"
let g:syntastic_go_checkers = ['golint', 'govet', 'errcheck']

" vim-go custom mappings
au FileType go nmap <Leader>s <Plug>(go-implements)
au FileType go nmap <Leader>i <Plug>(go-info)
au FileType go nmap <Leader>gd <Plug>(go-doc)
au FileType go nmap <Leader>gv <Plug>(go-doc-vertical)
au FileType go nmap <leader>r <Plug>(go-run)
au FileType go nmap <leader>b <Plug>(go-build)
au FileType go nmap <leader>t <Plug>(go-test)
au FileType go nmap <leader>c <Plug>(go-coverage)
au FileType go nmap <Leader>ds <Plug>(go-def-split)
au FileType go nmap <Leader>dv <Plug>(go-def-vertical)
au FileType go nmap <Leader>dt <Plug>(go-def-tab)
au FileType go nmap <Leader>e <Plug>(go-rename)
au FileType go nnoremap <leader>gr :GoRun %<CR>
" }}}



  • 在 Vim 内执行 :PlugInstall,安装 vim-go




  • 在 Vim 内执行 :GoInstallBinaries,下载并安装 vim-go 依赖的二进制工具,goimportsgolint 等。



  • 安装 gotags,使 tagbar 配置生效。


$ go get -u github.com/jstemmer/gotags

我们来看一下最终效果:


Image of Golang Environment in Vim


编写第一个程序


进入工作目录,新建文件 hello.go,如下:


$ cd $GOPATH
$ vim hello.go
package main

import "fmt"

func main() {
fmt.Println("Hello, World!")
}

运行程序:


$ go run hello.go
Hello, World!



本文章为 Cloudinsight 技术团队工程师原创,更多技术文章可访问 Cloudinsight 技术博客Cloudinsight 为可视化系统监控工具,涵盖 Windows、Linux 操作系统,用 Golang 开发的 Cloudinsight Agent 正式开源了,欢迎 fork,Github:https://github.com/cloudinsight/cloudinsight-agent


golang-for-pythonistas 系列持续更新中,欢迎关注~

Python 程序员的 Golang 学习指南(I): Go 之初体验

文章分享Cloudinsight 发表了文章 • 3 个评论 • 1523 次浏览 • 2016-10-12 15:27 • 来自相关话题

Authors: startover

Authors: startover





Go 语言简介


Go,又称 golang,是 Google 开发的一种静态强类型,编译型,并发型,并具有垃圾回收功能的编程语言。


Go 语言于2009年11月正式宣布推出,自2012年发布1.0,最新稳定版1.7。目前,Go的相关工具和生态已逐渐趋于完善,也不乏重量级项目,如 Docker, Kubernetes, Etcd, InfluxDB 等。


Go 语言能解决什么样的问题


同绝大多数通用型编程语言相比,Go 语言更多的是为了解决我们在构建大型服务器软件过程中所遇到的软件工程方面的问题而设计的。乍看上去,这么讲可能会让人感觉 Go 非常无趣且工业化,但实际上,在设计过程中就着重于清晰和简洁,以及较高的可组合性,最后得到的反而会是一门使用起来效率高而且很有趣的编程语言,很多程序员都会发现,它有极强的表达力而且功能非常强大。


总结为以下几点:



  • 清晰的依赖关系

  • 清晰的语法

  • 清晰的语义

  • 偏向组合而不是继承

  • 提供简单的编程模型(垃圾回收、并发)

  • 强大的内置工具(gofmt、godoc、gofix等)


建议有兴趣的同学看看 Go在谷歌:以软件工程为目的的语言设计


Go 语言相对 Python 有哪些优势


这里引用一段知乎上某大牛的回答,如下:




  • 部署简单。Go 编译生成的是一个静态可执行文件,除了 glibc 外没有其他外部依赖。这让部署变得异常方便:目标机器上只需要一个基础的系统和必要的管理、监控工具,完全不需要操心应用所需的各种包、库的依赖关系,大大减轻了维护的负担。这和 Python 有着巨大的区别。由于历史的原因,Python 的部署工具生态相当混乱【比如 setuptools, distutils, pip, buildout 的不同适用场合以及兼容性问题】。官方 PyPI 源又经常出问题,需要搭建私有镜像,而维护这个镜像又要花费不少时间和精力。




  • 并发性好。Goroutine 和 channel 使得编写高并发的服务端软件变得相当容易,很多情况下完全不需要考虑锁机制以及由此带来的各种问题。单个 Go 应用也能有效的利用多个 CPU 核,并行执行的性能好。这和 Python 也是天壤之比。多线程和多进程的服务端程序编写起来并不简单,而且由于全局锁 GIL 的原因,多线程的 Python 程序并不能有效利用多核,只能用多进程的方式部署;如果用标准库里的 multiprocessing 包又会对监控和管理造成不少的挑战【我们用的 supervisor 管理进程,对 fork 支持不好】。部署 Python 应用的时候通常是每个 CPU 核部署一个应用,这会造成不少资源的浪费,比如假设某个 Python 应用启动后需要占用 100MB 内存,而服务器有 32 个 CPU 核,那么留一个核给系统、运行 31 个应用副本就要浪费 3GB 的内存资源。




  • 良好的语言设计。从学术的角度讲 Go 语言其实非常平庸,不支持许多高级的语言特性;但从工程的角度讲,Go 的设计是非常优秀的:规范足够简单灵活,有其他语言基础的程序员都能迅速上手。更重要的是 Go 自带完善的工具链,大大提高了团队协作的一致性。比如 gofmt 自动排版 Go 代码,很大程度上杜绝了不同人写的代码排版风格不一致的问题。把编辑器配置成在编辑存档的时候自动运行 gofmt,这样在编写代码的时候可以随意摆放位置,存档的时候自动变成正确排版的代码。此外还有 gofix, govet 等非常有用的工具。



  • 执行性能好。虽然不如 C 和 Java,但通常比原生 Python 应用还是高一个数量级的,适合编写一些瓶颈业务。内存占用也非常省。


从个人对 Golang 的初步使用来说,体验还是相当不错的,但是也有下面几点需要注意:




  • 驼峰式命名风格(依据首字母大小写来决定其是否能被其他包引用),但我更喜欢 Python 的小写字母加下划线命名风格。




  • 没有好用的包管理器,Golang 官方也没有推荐最佳的包管理方案,目前公认的比较好用的有 Godeps, Govendor 及 Glide,而 Python 的包管理器 pip 已形成自己的一套标准。




  • 多行字符串的变量声明需要用反引号(`),Python 里是三个双引号("""),参考http://stackoverflow.com/questions/7933460/how-do-you-write-multiline-strings-in-go




  • Golang 中的类型匹配是很严格的,不同的类型之间通常需要手动转换,所以在字符串拼接时往往需要对整型进行显式转换,如 fmt.Println("num: " + strconv.Itoa(1))



  • Golang 语言语法里的语法糖并不多,如在 Python 中很流行的 map, reduce, range 等,在 Golang 里都没有得到支持。


另外,推荐阅读 Golang 新手开发者要注意的陷阱和常见错误


学习资料推荐


建议先把 Go 的官方文档过一遍,主要有以下几项:



官方文档看完后,基本也算入门了,这时候可以看看 Go 的示例代码,或者去 Project Euler 刷刷题。


当然也可以去知乎看看大牛们都是如何学习的,链接 https://www.zhihu.com/question/23486344


总结


虽然 Go 有很多被诟病的地方,比如 GC 和对错误的处理方式,但没有任何语言是完美的,从实用角度来讲,Go 有着不输于 Python 的开发效率,完善的第三方工具,以及强大的社区支持,这些就足够了。


相关链接:

https://golang.org/doc/

https://talks.golang.org/2012/splash.article

https://www.zhihu.com/question/21409296

https://www.zhihu.com/question/23486344

http://stackoverflow.com/questions/7933460/how-do-you-write-multiline-strings-in-go

http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/

http://www.oschina.net/translate/go-at-google-language-design-in-the-service-of-software-engineering




本文章为 Cloudinsight 技术团队工程师原创,更多技术文章可访问 Cloudinsight 技术博客Cloudinsight 为可视化系统监控工具,涵盖 Windows、Linux 操作系统,用 Golang 开发的 Cloudinsight Agent 正式开源了,欢迎 fork,Github:https://github.com/cloudinsight/cloudinsight-agent


golang-for-pythonistas 系列持续更新中,欢迎关注~

求一些golang的教程,书籍也可以

有问必答飞雪无情 回复了问题 • 43 人关注 • 24 个回复 • 3880 次浏览 • 2017-07-03 20:55 • 来自相关话题

golang有没有好的开源游戏框架

技术讨论cye 回复了问题 • 22 人关注 • 12 个回复 • 6413 次浏览 • 2017-08-16 17:23 • 来自相关话题

为什么Go里面大多数的接口返回的是int类型

有问必答negronihe 回复了问题 • 4 人关注 • 3 个回复 • 1787 次浏览 • 2016-10-16 13:06 • 来自相关话题

大家推荐哪种golang包管理方式?

有问必答topgrd 回复了问题 • 32 人关注 • 26 个回复 • 6356 次浏览 • 2017-08-04 17:12 • 来自相关话题

单个 goroutine 的意义何在?已取消问题

回复

有问必答qingfeng 发起了问题 • 1 人关注 • 0 个回复 • 101 次浏览 • 1 天前 • 来自相关话题

大家都用什么web框架

回复

技术讨论songtianyi 回复了问题 • 20 人关注 • 20 个回复 • 2900 次浏览 • 2 天前 • 来自相关话题

求助:有没有一种方式根据类型的名称获得类型的reflect.Value或reflect.Type

回复

有问必答傅小黑 回复了问题 • 10 人关注 • 8 个回复 • 398 次浏览 • 2017-09-08 16:52 • 来自相关话题

goroutine 奇怪的输出顺序

回复

有问必答trigged 回复了问题 • 3 人关注 • 3 个回复 • 266 次浏览 • 2017-09-06 23:47 • 来自相关话题

使用什么docker image来运行Go程序

回复

有问必答myonlyzzy 回复了问题 • 24 人关注 • 22 个回复 • 3178 次浏览 • 2017-09-01 10:15 • 来自相关话题

golang 如何动态创建struct

回复

有问必答Lampo 回复了问题 • 19 人关注 • 9 个回复 • 3451 次浏览 • 2017-08-25 10:35 • 来自相关话题

想用golang写个分布式的监控,大神给点建议

回复

有问必答zhipj 回复了问题 • 18 人关注 • 13 个回复 • 3289 次浏览 • 2017-08-23 03:35 • 来自相关话题

Golang API 业务监控项目求大神指点

回复

开源程序haoweishow 回复了问题 • 3 人关注 • 2 个回复 • 854 次浏览 • 2017-08-22 09:20 • 来自相关话题

请问有没有比较好的分布式系统监控项目?

回复

开源程序fiisio 回复了问题 • 10 人关注 • 4 个回复 • 1019 次浏览 • 2017-08-21 10:41 • 来自相关话题

Go有哪些好的视频教程

回复

有问必答故城 回复了问题 • 15 人关注 • 3 个回复 • 1813 次浏览 • 2017-08-18 13:56 • 来自相关话题

golang有没有好的开源游戏框架

回复

技术讨论cye 回复了问题 • 22 人关注 • 12 个回复 • 6413 次浏览 • 2017-08-16 17:23 • 来自相关话题

go的beego框架在不使用orm的情况下怎么操作数据库?

回复

技术讨论jsharkc 回复了问题 • 4 人关注 • 3 个回复 • 331 次浏览 • 2017-08-14 10:53 • 来自相关话题

大家推荐哪种golang包管理方式?

回复

有问必答topgrd 回复了问题 • 32 人关注 • 26 个回复 • 6356 次浏览 • 2017-08-04 17:12 • 来自相关话题

都说vscode好,请教一下大家的配置方式

回复

有问必答plain 回复了问题 • 5 人关注 • 4 个回复 • 1427 次浏览 • 2017-07-28 10:46 • 来自相关话题

Go指针复制问题

回复

有问必答lys86_1205 回复了问题 • 4 人关注 • 5 个回复 • 667 次浏览 • 2017-07-27 14:40 • 来自相关话题

golang版本的curl请求库

开源程序mikemintang 发表了文章 • 0 个评论 • 255 次浏览 • 2017-09-15 07:55 • 来自相关话题

Github地址


https://github.com/mikemintang/go-curl


安装


go get github.com/mikemintang/go-curl

使用


package main

import (
"fmt"
"github.com/mikemintang/go-curl"
)

func main() {

url := "http://php.dev/api.php"

headers := map[string]string{
"User-Agent": "Sublime",
"Authorization": "Bearer access_token",
"Content-Type": "application/json",
}

cookies := map[string]string{
"userId": "12",
"loginTime": "15045682199",
}

queries := map[string]string{
"page": "2",
"act": "update",
}

postData := map[string]interface{}{
"name": "mike",
"age": 24,
"interests": []string{"basketball", "reading", "coding"},
"isAdmin": true,
}

// 链式操作
req := curl.NewRequest()
resp, err := req.
SetUrl(url).
SetHeaders(headers).
SetCookies(cookies).
SetQueries(queries).
SetPostData(postData).
Post()

if err != nil {
fmt.Println(err)
} else {
if resp.IsOk() {
fmt.Println(resp.Body)
} else {
fmt.Println(resp.Raw)
}
}

}

接收请求的api.php


<?php  

//echo json_encode($_GET); // 获取url地址中的查询参数
//echo json_encode(getallheaders()); // 获取请求头
//echo json_encode($_COOKIE); // 获取cookies
echo file_get_contents("php://input"); // 获取post提交的数据

function getallheaders() {
$headers = [];
foreach ($_SERVER as $name => $value) {
if (substr($name, 0, 5) == 'HTTP_') {
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
}
}
return $headers;
}

可导出的成员变量和方法



TodoList



  • [x] 以链式操作的方式发起请求

  • [ ] 以函数回调的方式发起请求

  • [ ] 以类Jquery Ajax的方式发起请求

  • [x] 发起GET/POST请求

  • [ ] 发起PUT/PATCH/DELETE/OPTIONS操作

  • [x] 以application/x-www-form-urlencoded形式提交post数据

  • [x] 以application/json形式提交post数据

  • [ ] 以multipart/form-data形式提交post数据

  • [ ] proxy代理设置

Go 语言在命令行以表格的形式输出结构体切片

开源程序modood 发表了文章 • 3 个评论 • 239 次浏览 • 2017-08-29 15:32 • 来自相关话题

最近写的小工具,可以在命令行以表格的形式输出结构体切片

  • 没有第三方依赖
  • 支持中文汉字
  • 表格每列自动对齐
  • 支持自动适应列宽
  • 结构体的字段支持所有数据类型(字符... 查看全部

最近写的小工具,可以在命令行以表格的形式输出结构体切片



  • 没有第三方依赖

  • 支持中文汉字

  • 表格每列自动对齐

  • 支持自动适应列宽

  • 结构体的字段支持所有数据类型(字符串,切片,映射等)


例如可以很方便清晰地将数据库查询结果列表(结构体切片)在命令行以表格的形式输出。


项目 Github 主页:https://github.com/modood/table


对你有用的话,给个 star 支持一下吧~


package main

import (
"fmt"

"github.com/modood/table"
)

type House struct {
Name string
Sigil string
Motto string
}

func main() {
s := []House{
{"Stark", "direwolf", "Winter is coming"},
{"Targaryen", "dragon", "Fire and Blood"},
{"Lannister", "lion", "Hear Me Roar"},
}

table.Output(s)
}

输出结果:


┌───────────┬──────────┬──────────────────┐
│ Name │ Sigil │ Motto │
├───────────┼──────────┼──────────────────┤
│ Stark │ direwolf │ Winter is coming │
│ Targaryen │ dragon │ Fire and Blood │
│ Lannister │ lion │ Hear Me Roar │
└───────────┴──────────┴──────────────────┘

【上海】换手率-来自小米腾讯联合投资的互联网金融短线炒股APP-跪求Golang

招聘应聘catty 发表了文章 • 2 个评论 • 351 次浏览 • 2017-08-21 15:02 • 来自相关话题

关于我们:https://www.huanshoulv.com/

硅谷式氛围,简单可依赖。我们是一个充满激情和梦想的团队,请无情地向我砸简历。

查看全部

关于我们:https://www.huanshoulv.com/


硅谷式氛围,简单可依赖。我们是一个充满激情和梦想的团队,请无情地向我砸简历。




岗位职责:



  1. 上交所、深交所股票数据处理;

  2. 高性能API接口开发;




任职要求:



  1. 熟悉Linux环境;

  2. 熟悉Go语言;

  3. 2年以上工作经验;

  4. 有高并发开发经验;

  5. 熟悉MongDB、Redis;

  6. 沟通能力良好,有较强的抗压能力;




优先条件:



  1. 熟悉MQTT协议优先;

  2. 熟悉git,有github优先 请在简历中写明github地址;

  3. 熟悉c/c++/nodejs/python优先;

  4. 股票用户优先;




岗位待遇:20k-40k


简历投递邮箱:catty@huanshoulv.com 简历标题:姓名+岗位+来源

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

文章分享nosix 发表了文章 • 1 个评论 • 252 次浏览 • 2017-08-15 15:47 • 来自相关话题

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"))
}

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

文章分享pathbox 发表了文章 • 4 个评论 • 381 次浏览 • 2017-08-12 18:52 • 来自相关话题

众所周知,我们能使用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。这样,在某一时刻,只会有一个线程(进程)拥有互斥锁,在操作"共享资源"。

GOLANG中time.After释放的问题

技术讨论winlin 发表了文章 • 7 个评论 • 1067 次浏览 • 2017-07-29 11:58 • 来自相关话题

在谢大群里看到有同学在讨论time.After泄漏的问题,就算时间到了也不会释放,瞬间就惊呆了,忍不住做了试验,结果发现应该没有这么的恐怖的,是有泄漏的风险不过不算是泄漏,先看API的说明:

查看全部
					

在谢大群里看到有同学在讨论time.After泄漏的问题,就算时间到了也不会释放,瞬间就惊呆了,忍不住做了试验,结果发现应该没有这么的恐怖的,是有泄漏的风险不过不算是泄漏,先看API的说明:


// After waits for the duration to elapse and then sends the current time
// on the returned channel.
// It is equivalent to NewTimer(d).C.
// The underlying Timer is not recovered by the garbage collector
// until the timer fires. If efficiency is a concern, use NewTimer
// instead and call Timer.Stop if the timer is no longer needed.
func After(d Duration) <-chan Time {
return NewTimer(d).C
}

提到了一句The underlying Timer is not recovered by the garbage collector,这句挺吓人不会被GC回收,不过后面还有条件until the timer fires,说明fire后是会被回收的,所谓fire就是到时间了,写个例子证明下压压惊:


package main

import "time"

func main() {
for {
<- time.After(10 * time.Nanosecond)
}
}

显示内存稳定在5.3MB,CPU为161%,肯定被GC回收了的。当然如果放在goroutine也是没有问题的,一样会回收:


package main

import "time"

func main() {
for i := 0; i < 100; i++ {
go func(){
for {
<- time.After(10 * time.Nanosecond)
}
}()
}
time.Sleep(1 * time.Hour)
}

只是资源消耗会多一点,CPU为422%,内存占用6.4MB。因此:



Remark: time.After(d)在d时间之后就会fire,然后被GC回收,不会造成资源泄漏的。



那么API所说的If efficieny is a concern, user NewTimer instead and call Timer.Stop是什么意思呢?这是因为一般time.After会在select中使用,如果另外的分支跑得更快,那么timer是不会立马释放的(到期后才会释放),比如这种:


select {
case time.After(3*time.Second):
return errTimeout
case packet := packetChannel:
// process packet.
}

如果packet非常多,那么总是会走到下面的分支,上面的timer不会立刻释放而是在3秒后才能释放,和下面代码一样:


package main

import "time"

func main() {
for {
select {
case <-time.After(3 * time.Second):
default:
}
}
}

这个时候,就相当于会堆积了3秒的timer没有释放而已,会不断的新建和释放timer,内存会稳定在2.8GB,这个当然就不是最好的了,可以主动释放:


package main

import "time"

func main() {
for {
t := time.NewTimer(3*time.Second)

select {
case <- t.C:
default:
t.Stop()
}
}
}

这样就不会占用2.8GB内存了,只有5MB左右。因此,总结下这个After的说明:



  1. GC肯定会回收time.After的,就在d之后就回收。一般情况下让系统自己回收就好了。

  2. 如果有效率问题,应该使用Timer在不需要时主动Stop。大部分时候都不用考虑这个问题的。


交作业。

【深圳(福田)-klook.com】-Golang开发工程师(招聘)

招聘应聘poiumn12345 发表了文章 • 1 个评论 • 536 次浏览 • 2017-07-12 11:01 • 来自相关话题

简历投递:jobs@klook.com 地址:福田天安科技创业园A座

职位描述:

  1. 负责Klook各线产品的开发、测试和上线
  2. ... 查看全部

简历投递:jobs@klook.com
地址:福田天安科技创业园A座


职位描述:



  1. 负责Klook各线产品的开发、测试和上线

  2. 负责管理自身项目和需求的优先级、按时高质量交付


职位要求:



  1. 熟练掌握Golang语言,具有扎实的计算机基础和编程能力,熟悉常见的算法与数据结构

  2. 熟悉Restful接口设计,有过互联网业务系统或相关技术产品开发经验

  3. 有较强的工作责任心和良好的沟通协调能力,能在压力下独立解决问题

  4. 熟练掌握MySQL数据库,擅长SQL优化者优先

  5. 有熟悉一门静态(C/C++/Java)语言者优先

  6. 开源贡献者优先,GitHub源码者优先,技术博客者优先


【 Klook客路团队 】



  • Klook客路旅行已于2016年完成B轮融资,在香港、深圳、台北、新加坡、吉隆坡、首尔、马尼拉、曼谷开设办公室

  • 国际化团队,半数成员来自海外近十个国家及地区


【 关于Klook客路旅行 】



  • KLOOK.com是全球千万人使用的旅行体验探索及预订平台。无论是在尼泊尔与秃鹰齐飞的滑翔伞历奇,还是潜入冲绳的蔚蓝海洞,或者全家同游迪士尼乐园,甚至在新加坡的高空缆车上享受浪漫晚宴……客路帮助旅行者们体验地道个性玩法、预订优惠景点门票、以特价观看演出、享受景点快速通道。

  • 全球超过300位各具专长的Klook旅行体验师们深入目的地,挖掘日韩/港澳台/东南亚/澳新等地区80+个城市、超过一万种独具特色的旅行活动。

  • Klook APP荣获App Store 2015 年度精选;

  • Klook APP荣获Google Play “Best of 2015”年度最佳应用;

  • Klook客路旅行官网 http://www.klook.com/

  • 官方公众号:“KLOOK旅行灵感指南”

  • 知乎专栏:“一群旅行体验师”,全站最具影响力的旅行专栏


【Klook客路福利】



  1. 舒适、健康、人性化的办公环境

  2. 扁平化的团队管理,业务飞速发展,你的能力和价值很快会被大家看到

  3. 开放和充满激情的国际化团队,男女高颜值并且比例协调,每个人都有那么一点传奇

  4. 独特的创业团队氛围会让你在完成工作之余,还有自己的时间学习、成长

  5. 体验Klook各类活动,全球城市旅行的机会

  6. 水果、零食,团队活动

GOLANG使用Context实现传值、超时和取消

技术讨论winlin 发表了文章 • 0 个评论 • 600 次浏览 • 2017-06-28 16:24 • 来自相关话题

GO1.7之后,新增了context.Context这个package,实现goroutine的管理。

Context基本的用法参考GOL... 查看全部

GO1.7之后,新增了context.Context这个package,实现goroutine的管理。


Context基本的用法参考GOLANG使用Context管理关联goroutine


实际上,Context还有个非常重要的作用,就是设置超时。比如,如果我们有个API是这样设计的:


type Packet interface {
encoding.BinaryMarshaler
encoding.BinaryUnmarshaler
}

type Stack struct {
}
func (v *Stack) Read(ctx context.Context) (pkt Packet, err error) {
return
}

一般使用是这样使用,创建context然后调用接口:


ctx,cancel := context.WithCancel(context.Background())
stack := &Stack{}
pkt,err := stack.Read(ctx)

那么,它本身就可以支持取消和超时,也就是用户如果需要取消,比如发送了SIGINT信号,程序需要退出,可以在收到信号后调用cancel


sc := make(chan os.Signal, 0)
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM)
go func() {
for range sc {
cancel()
}
}()

如果需要超时,这个API也不用改,只需要调用前设置超时时间:


ctx,cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
pkt,err := stack.Read(ctx)

如果一个程序在运行,比如Read在等待,那么在没有人工干预的情况下,那就应该自己运行就好了。而人工干预,也就是需要取消,比如要升级程序了,或者需要停止服务了,都属于这种取消操作。而超时,一般是系统的策略,因为不能一直等下去,就需要在一定时间没有反应时终止服务。实际上context这两个都能支持得很好,而且还不影响Read本身的逻辑,在Read中只需要关注context是否Done:


func (v *Stack) Read(ctx context.Context) (pkt Packet, err error) {
select {
// case <- dataChannel: // Parse packet from data channel.
case <- ctx.Done():
return nil,ctx.Err()
}
return
}

这是为何context被接纳成为标准库的包的缘故了吧,非常之强大和好用,而又非常简单。一行context,深藏功与名。


另外,Context还可以传递上下文的Key-Value对象,比如我们希望日志中,相关的goroutine都打印一个简化的CID,那么就可以用context.WithValue,参考go-oryx-lib/logger

udp编程的那些事与golang udp的实践

文章分享sheepbao 发表了文章 • 0 个评论 • 1152 次浏览 • 2017-06-26 10:30 • 来自相关话题

udp编程的那些事与golang udp的实践

tcp/ip大协议中,tcp编程大家应该比较熟,应用的场景也很多,但是udp在现实中,应用也不少,而在大部分博文中,都很少对udp的编程进行研究,最近研究了一下udp编程,正好做个记录。<... 查看全部

udp编程的那些事与golang udp的实践


tcp/ip大协议中,tcp编程大家应该比较熟,应用的场景也很多,但是udp在现实中,应用也不少,而在大部分博文中,都很少对udp的编程进行研究,最近研究了一下udp编程,正好做个记录。

sheepbao 2017.06.15


tcp Vs udp


tcp和udp都是著名的传输协议,他们都是基于ip协议,都在OSI模型中传输层。tcp我们都很清楚,它提供了可靠的数据传输,而udp我们也知道,它不提供数据传输的可靠性,只是尽力传输。
他们的特性决定了它们很大的不同,tcp提供可靠性传输,有三次握手,4次分手,相当于具有逻辑上的连接,可以知道这个tcp连接的状态,所以我们都说tcp是面向连接的socket,而udp没有握手,没有分手,也不存在逻辑上的连接,所以我们也都说udp是非面向连接的socket。

我们都畏惧不知道状态的东西,所以即使tcp的协议比udp复杂很多,但对于系统应用层的编程来说,tcp编程其实比udp编程容易。而udp相对比较灵活,所以对于udp编程反而没那么容易,但其实掌握后udp编程也并不难。


udp协议


udp的首部


        2               2       (byte)
+---+---+---+---+---+---+---+---+ -
| src port | dst port | |
+---+---+---+---+---+---+---+---+ 8(bytes)
| length | check sum | |
+---+---+---+---+---+---+---+---+ -
| |
+ data +
| |
+---+---+---+---+---+---+---+---+

udp的首部真的很简单,头2个字节表示的是原端口,后2个字节表示的是目的端口,端口是系统层区分进程的标识。接着是udp长度,最后就是校验和,这个其实很重要,现在的系统都是默认开启udp校验和的,所以我们才能确保udp消息传输的完整性。如果这个校验和关闭了,那会让我们绝对会很忧伤,因为udp不仅不能保证数据一定到达,还不能保证即使数据到了,这个数据是否是正确的。比如:我在发送端发送了“hello”,而接收端却接收到了“hell”。如果真的是这样,我们就必须自己去校验数据的正确性。还好udp默认开发了校验,我们可以保证udp的数据完整性。


udp数据的封装


                                    +---------+
| 应用数据 |
+---------+
| |
v v
+---------+---------+
| udp首部 | 应用数据 |
+---------+---------+
| |
v UDP数据报 v
+---------+---------+---------+
| ip首部 | udp首部 | 应用数据 |
+---------+---------+---------+
| |
v IP数据报 v
+---------+---------+---------+---------+---------+
|以太网首部 | ip首部 | udp首部 | 应用数据 |以太网尾部 |
+---------+---------+---------+---------+---------+
| 14 20 8 4 |
| -> 以太网帧 <- |

数据的封装和tcp是一样,应用层的数据加上udp首部,构成udp数据报,再加上ip首部构成ip数据报,最后加上以太网首部和尾部构成以太网帧,经过网卡发送出去。


Golang udp实践


实践出真知,编程就需要多实践,才能体会其中的奥妙。


echo客户端和服务端


echo服务,实现数据包的回显,这是很多人网络编程起点,因为这个服务足够简单,但又把网络的数据流都过了一遍,这里也用go udp实现一个echo服务。

实现客户端发送一个“hello”,服务端接收消息并原封不动的返回给客户度。


server.go


package main

import (
"flag"
"fmt"
"log"
"net"
)

var addr = flag.String("addr", ":10000", "udp server bing address")

func init() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
flag.Parse()
}

func main() {
//Resolving address
udpAddr, err := net.ResolveUDPAddr("udp", *addr)
if err != nil {
log.Fatalln("Error: ", err)
}

// Build listining connections
conn, err := net.ListenUDP("udp", udpAddr)
if err != nil {
log.Fatalln("Error: ", err)
}
defer conn.Close()

// Interacting with one client at a time
recvBuff := make([]byte, 1024)
for {
log.Println("Ready to receive packets!")
// Receiving a message
rn, rmAddr, err := conn.ReadFromUDP(recvBuff)
if err != nil {
log.Println("Error:", err)
return
}

fmt.Printf("<<< Packet received from: %s, data: %s\n", rmAddr.String(), string(recvBuff[:rn]))
// Sending the same message back to current client
_, err = conn.WriteToUDP(recvBuff[:rn], rmAddr)
if err != nil {
log.Println("Error:", err)
return
}
fmt.Println(">>> Sent packet to: ", rmAddr.String())
}
}

client1.go


package main

import (
"flag"
"fmt"
"log"
"net"
)

var raddr = flag.String("raddr", "127.0.0.1:10000", "remote server address")

func init() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
flag.Parse()
}

func main() {
// Resolving Address
remoteAddr, err := net.ResolveUDPAddr("udp", *raddr)
if err != nil {
log.Fatalln("Error: ", err)
}

// Make a connection
tmpAddr := &net.UDPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 0,
}

conn, err := net.DialUDP("udp", tmpAddr, remoteAddr)
// Exit if some error occured
if err != nil {
log.Fatalln("Error: ", err)
}
defer conn.Close()

// write a message to server
_, err = conn.Write([]byte("hello"))
if err != nil {
log.Println(err)
} else {
fmt.Println(">>> Packet sent to: ", *raddr)
}

// Receive response from server
buf := make([]byte, 1024)
rn, rmAddr, err := conn.ReadFromUDP(buf)
if err != nil {
log.Println(err)
} else {
fmt.Printf("<<< %d bytes received from: %v, data: %s\n", rn, rmAddr, string(buf[:rn]))
}
}

client2.go


package main

import (
"flag"
"fmt"
"log"
"net"
)

var (
laddr = flag.String("laddr", "127.0.0.1:9000", "local server address")
raddr = flag.String("raddr", "127.0.0.1:10000", "remote server address")
)

func init() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
flag.Parse()
}

func main() {
// Resolving Address
localAddr, err := net.ResolveUDPAddr("udp", *laddr)
if err != nil {
log.Fatalln("Error: ", err)
}

remoteAddr, err := net.ResolveUDPAddr("udp", *raddr)
if err != nil {
log.Fatalln("Error: ", err)
}

// Build listening connections
conn, err := net.ListenUDP("udp", localAddr)
// Exit if some error occured
if err != nil {
log.Fatalln("Error: ", err)
}
defer conn.Close()

// write a message to server
_, err = conn.WriteToUDP([]byte("hello"), remoteAddr)
if err != nil {
log.Println(err)
} else {
fmt.Println(">>> Packet sent to: ", *raddr)
}

// Receive response from server
buf := make([]byte, 1024)
rn, remAddr, err := conn.ReadFromUDP(buf)
if err != nil {
log.Println(err)
} else {
fmt.Printf("<<< %d bytes received from: %v, data: %s\n", rn, remAddr, string(buf[:rn]))
}
}

这里实现echo的服务端和客户端,和tcp的差不多,但是有一些小细节需要注意。

对于server端,先net.ListenUDP建立udp一个监听,返回一个udp连接,这里需要注意udp不像tcp,建立tcp监听后返回的是一个Listener,然后阻塞等待接收一个新的连接,这样区别是因为udp一个非面向连接的协议,它没有会话管理。同时也因为udp是非面向连接的协议,当接收到消息后,想把消息返回给当前的客户端时,是不能像tcp一样,直接往conn里写的,而是需要指定远端地址。

对于client端,类似tcp先Dial,返回一个连接,对于发送消息用Write,接收消息用Read,当然udp也可以用ReadFromUDP,这样可以知道从哪得到的消息。但其实client也可以用另一种方式写,如client2.go程序,先建立一个监听,返回一个连接,用这个连接发送消息给服务端和从服务器接收消息,这种方式和tcp倒是有很大的不同。


参考


golang pkg

GOLANG如何避免字符串转义

技术讨论winlin 发表了文章 • 0 个评论 • 446 次浏览 • 2017-06-23 17:41 • 来自相关话题

避免转义字符,例如造个json:

json.Unmarshal(`{"code":0, "data":{"server":"127.0.0.1:8080"}}`)查看全部
					

避免转义字符,例如造个json:


json.Unmarshal(`{"code":0, "data":{"server":"127.0.0.1:8080"}}`)

是不是太简单了点,但是我好像并不总是记得。

GOLANG宽泛接口在测试中的大用处

技术讨论winlin 发表了文章 • 1 个评论 • 270 次浏览 • 2017-06-23 17:26 • 来自相关话题

考虑测试一个函数:

func request(ctx context.Context, hc *http.Client, api string) (err error... 			查看全部
					

考虑测试一个函数:


func request(ctx context.Context, hc *http.Client, api string) (err error) {
var hreq *http.Request
if hreq, err = http.NewRequest("GET", api, nil); err != nil {
return nil, errors.Wrap(err, "create request")
}
var hres *http.Response
if hres, err = hc.Do(hreq.WithContext(ctx)); err != nil {
return nil, errors.Wrap(err, "do request")
}
defer hres.Body.Close()

var body []byte
if body, err = ioutil.ReadAll(hres.Body); err != nil {
return nil, errors.Wrap(err, "read body")
}

// ......
return nil
}

这个函数的参数是一个*http.Client,而不是接口,这个该如何测试?内嵌一个http.Client像这样吗?


type mockHttpClient struct {
http.Client
}

但是,问题是这样总是很恶心不是吗?就像如果是C++中,我们只能写一个mock类从要测试的类继承,但是我们只需要重写Do这个方法啊。



注意:对于C++而言,这是为何要求构造函数只是初始化,而不能包含逻辑,想象一个类在构造函数就访问了数据库,请问如何MOCK它?是做不到的,因此只能在构造函数初始化数据库的IP和账号等信息,提供connect这种函数连接数据库。


备注:上面只是拿数据库连接打个比方,实际上从MOCK角度来说,构造函数只能初始化内存对象,其他的应该啥也不干。



在GOLANG中,有个非常牛逼的方法,就是创建一个私有的接口,使用时用接口:


type httpDoer interface {
Do(req *http.Request) (*http.Response, error)
}
func request(ctx context.Context, hc httpDoer, api string) (err error) {
// ......

可以发现,很神奇的是,调用者也可以给*http.Client,对这个改动一无所知,这难道不是极其巧妙的设计吗?我们在mock中只需要mock这个方法就可以了。


一行代码处,深藏功与名~

GOLANG测试必须用带堆栈的errors

技术讨论winlin 发表了文章 • 2 个评论 • 344 次浏览 • 2017-06-22 18:15 • 来自相关话题

GOLANG测试时,可以用匿名对象填充测试序列,但是如果没有带堆栈的errors,那么会造成出现错误时无法排查。

GOLANG初始化匿名结构的方式,可以很方便的建立测试集合。

先看测试序列的填充,参考查看全部

GOLANG测试时,可以用匿名对象填充测试序列,但是如果没有带堆栈的errors,那么会造成出现错误时无法排查。


GOLANG初始化匿名结构的方式,可以很方便的建立测试集合。


先看测试序列的填充,参考tricks-2015这个文章,怕你翻不了墙,我把内容粘贴过来就是:


Anonymous structs: test cases (1/2)


These properties enable a nice way to express test cases:


func TestIndex(t *testing.T) {
var tests = []struct {
s string
sep string
out int
}{
{"", "", 0},
{"", "a", -1},
{"fo", "foo", -1},
{"foo", "foo", 0},
{"oofofoofooo", "f", 2},
// etc
}
for _, test := range tests {
actual := strings.Index(test.s, test.sep)
if actual != test.out {
t.Errorf("Index(%q,%q) = %v; want %v", test.s, test.sep, actual, test.out)
}
}
}

是的,看起来很方便,出错时也知道哪里的问题,但是实际上如果序列中有函数,那就悲剧了。让我们来试试,考虑一个包头的定义,它就是两个字段,然后序列化成[]byte


type MyHeader struct {
Version uint8
Size uint16
}

func (v MyHeader) MarshalBinary() ([]byte, error) {
return []byte{byte(v.Version),0,0},nil // Failed.
}

为了测试设置不同的值,得到不同的字节,我们用两个函数来填充测试序列:


func TestMyHeader_MarshalBinary(t *testing.T) {
mhs := []struct {
set func(h *MyHeader)
compare func(p []byte) error
}{
{func(h *MyHeader) { h.Size = 1 }, func(p []byte) error {
if p[1] != 0x01 {
return fmt.Errorf("p[1] is %v", p[1]) // line 194
}
return nil
}},
{func(h *MyHeader) { h.Size = 2 }, func(p []byte) error {
if p[1] != 0x02 {
return fmt.Errorf("p[1] is %v", p[1]) // line 200
}
return nil
}},
}
for _, mh := range mhs {
h := &MyHeader{}
mh.set(h)
if b, err := h.MarshalBinary(); err != nil {
t.Errorf("error is %+v", err)
} else if err = mh.compare(b); err != nil {
t.Errorf("invalid data, err is %+v", err) // line 211
}
}
}

结果我们就懵逼了,出现的错误行数都是在error那个地方211行是不够的,还需要知道是194还是200出问题了:


--- FAIL: TestMyHeader_MarshalBinary (0.00s)
iprouter_test.go:211: invalid data, err is p[1] is 0
iprouter_test.go:211: invalid data, err is p[1] is 0

怎么办呢?把堆栈信息带上,参考错误最佳实践,改成这样:


import oe "github.com/ossrs/go-oryx-lib/errors"

创建error时用这个package:


            if p[1] != 0x01 {
return oe.Errorf("p[1] is %v", p[1]) // line 194
}
if p[1] != 0x02 {
return oe.Errorf("p[1] is %v", p[1]) // line 200
}

结果可以看到详细的堆栈:


    iprouter_test.go:211: invalid data, err is p[1] is 0
_/Users/winlin/git/test/src/core.TestMyHeader_MarshalBinary.func4
/Users/winlin/git/test/src/core_test.go:200
_/Users/winlin/git/test/src/core.TestMyHeader_MarshalBinary
/Users/winlin/git/test/src/core_test.go:210
testing.tRunner
/usr/local/Cellar/go/1.8.1/libexec/src/testing/testing.go:657
runtime.goexit
/usr/local/Cellar/go/1.8.1/libexec/src/runtime/asm_amd64.s:2197

这样可以嵌套非常多的函数做测试了。

重庆 诚聘golang工程师 12k-25k

招聘应聘酒酒 发表了文章 • 3 个评论 • 504 次浏览 • 2017-06-22 15:33 • 来自相关话题

岗位职责:

1.协助架构师完成即时通信系统的技术架构设计与实施; 2.完成服务端通信层和业务层程序的编写。 任职要求:

1.1年golang以上开发经验,熟悉Linux环境及常用命令; 2.熟悉基于TCP/IP、Rpc、Htt... 查看全部

岗位职责:


1.协助架构师完成即时通信系统的技术架构设计与实施;
2.完成服务端通信层和业务层程序的编写。
任职要求:


1.1年golang以上开发经验,熟悉Linux环境及常用命令;
2.熟悉基于TCP/IP、Rpc、Http、Socket等通信编程优先考虑;
3.熟悉C/C++/Java/ python任意一种语言开发经验优先;
4.熟练缓存和nosql数据库使用经验优先考虑;
5.具有一定的架构设计能力优先考虑;
6.有分布式系统开发设计经验,参与开发并成功运维过高并发大据项目的优先考虑;
7.有IM,XMPP,ejabberd开发经验优先。 上班时间:灵活上班时间+双休
地址:重庆市渝北区洪湖西路24号B栋20楼


联系方式:qq:983092900 联系电话:15683298185 或直接将简历发送至983092900@qq.com

wechat_pusher : 基于Golang开发的微信消息定时推送框架

开源程序HundredLee 发表了文章 • 2 个评论 • 443 次浏览 • 2017-06-14 12:55 • 来自相关话题

wechat_pusher


Github



  • https://github.com/hundredlee/wechat_pusher

  • 欢迎star && fork && watch

  • 学Golang不久,写一个开源练练手。希望大家提提建议。谢谢

    功能列表


  • 消息推送

    • 模板消息推送

      • model -> message.go

      • task -> template_task.go


    • 图片推送(TODO)

    • 文字推送(TODO)

    • 图文推送(TODO)


  • 日志存储

  • 计划任务


如何开始?


第一步:当然是go get




  • go get github.com/hundredlee/wechat_pusher.git



  • 项目结构如下:


├── README.md
├── config
│   └── config.go
├── config.conf
├── config.conf.example
├── enum
│   └── task_type.go
├── glide.lock
├── glide.yaml
├── hlog
│   ├── filelog.go
│   ├── filelog_test.go
│   └── hlog.go
├── main.go
├── main.go.example
├── models
│   ├── message.go
│   └── token.go
├── redis
│   ├── redis.go
│   └── redis_test.go
├── statics
│   └── global.go
├── task
│   ├── task.go
│   └── template_task.go
├── utils
│   ├── access_token.go
│   ├── crontab.go
│   └── push.go
└── vendor
└── github.com

第二步:创建一个项目


创建配置文件



  • 项目根目录有一个config.conf.example,重命名为config.conf即可

  • 内容如下:


[WeChat]
APPID=
SECRET=
TOKEN=

[Redis]
POOL_SIZE=
TIMEOUT=
HOST=
PASS=
DB=

[Log]
LOG_PATH=


  • WeChat部分

    • APPID && SECRET && TOKEN 这些是微信开发者必须了解的东西。不细讲


  • Redis部分

    • POOL_SIZE 连接池大小 ,整型 int

    • TIMEOUT 连接超时时间 ,整型 int

    • HOST 连接的IP 字符串 string

    • PASS 密码 字符串 string

    • DB 数据库选择 整型 int



  • Log部分



    • LOG_PATH 日志存放文件夹,例如值为wechat_log,那么完整的目录应该是 GOPATH/wechat_log



  • 调用的时候这么写:



conf := config.Instance()
//例如wechat 的 appid
appId := conf.ConMap["WeChat.APPID"]

模板怎么配置



  • 以模板消息作为例子说明:

  • message.go 是模板消息的结构

  • template_task.go 是将一个模板消息封装成任务(template_task.go 是实现了接口task.go的)


mess := models.Message{
ToUser: "openid",
TemplateId: "templateid",
Url: "url",
Data: models.Data{
First: models.Raw{"xxx", "#173177"},
Subject: models.Raw{"xxx", "#173177"},
Sender: models.Raw{"xxx", "#173177"},
Remark: models.Raw{"xxx", "#173177"}}}

//封装成一个任务,TemplateTask表示模板消息任务
task := task.TemplateTask{}
task.SetTask(mess)


  • 以上代码是模板消息的配置,这个微信开发者应该都能看懂。


如何创建一个任务



  • 例如我们要创建一个模板消息定时推送任务

    • 第一步,封装任务

    • 第二步,添加任务,并设置任务类型、并发执行的个数、失败尝试次数等。

    • 第三步,启动任务


  • 我们用示例代码演示整个完整的过程


package main

import (
"github.com/hundredlee/wechat_pusher/enum"
"github.com/hundredlee/wechat_pusher/models"
"github.com/hundredlee/wechat_pusher/task"
"github.com/hundredlee/wechat_pusher/utils"
"runtime"
)

func main() {

runtime.GOMAXPROCS(runtime.NumCPU())
var tasks []task.Task
tasks = make([]task.Task, 100)
mess := models.Message{
ToUser: "oBv9cuLU5zyI27CtzI4VhV6Xabms",
TemplateId: "UXb6s5dahNC5Zt-xQIxbLJG1BdP8mP73LGLhNXl68J8",
Url: "http://baidu.com",
Data: models.Data{
First: models.Raw{"xxx", "#173177"},
Subject: models.Raw{"xxx", "#173177"},
Sender: models.Raw{"xxx", "#173177"},
Remark: models.Raw{"xxx", "#173177"}}}
task := task.TemplateTask{}
task.SetTask(mess)

for i := 0; i < 100; i++ {
tasks[i] = &task
}

utils.NewPush(tasks).SetTaskType(enum.TASK_TYPE_TEMPLATE).SetRetries(4).SetBufferNum(10).Add("45 * * * * *")
utils.StartCron()

}


Run



  • 很简单,当你组装好所有的task以后,直接运行一句话就可以了。


  • utils.NewPush(tasks).SetTaskType(enum.TASK_TYPE_TEMPLATE).SetRetries(4).SetBufferNum(10).Add("45 * * * * *")



  • utils.StartCron()


Contributor


kcp-go源码解析

文章分享sheepbao 发表了文章 • 0 个评论 • 1718 次浏览 • 2017-06-12 15:24 • 来自相关话题

kcp-go源码解析

对kcp-go的源码解析,有错误之处,请一定告之。
sheepbao 2017.0612

概念

ARQ:自动重传请求(Automatic Repeat-reQuest,... 查看全部

kcp-go源码解析


对kcp-go的源码解析,有错误之处,请一定告之。

sheepbao 2017.0612


概念


ARQ:自动重传请求(Automatic Repeat-reQuest,ARQ)是OSI模型中数据链路层的错误纠正协议之一.

RTO:Retransmission TimeOut

FEC:Forward Error Correction


kcp简介


kcp是一个基于udp实现快速、可靠、向前纠错的的协议,能以比TCP浪费10%-20%的带宽的代价,换取平均延迟降低30%-40%,且最大延迟降低三倍的传输效果。纯算法实现,并不负责底层协议(如UDP)的收发。查看官方文档kcp


kcp-go是用go实现了kcp协议的一个库,其实kcp类似tcp,协议的实现也很多参考tcp协议的实现,滑动窗口,快速重传,选择性重传,慢启动等。

kcp和tcp一样,也分客户端和监听端。


    +-+-+-+-+-+            +-+-+-+-+-+
| Client | | Server |
+-+-+-+-+-+ +-+-+-+-+-+
|------ kcp data ------>|
|<----- kcp data -------|

kcp协议


layer model


+----------------------+
| Session |
+----------------------+
| KCP(ARQ) |
+----------------------+
| FEC(OPTIONAL) |
+----------------------+
| CRYPTO(OPTIONAL)|
+----------------------+
| UDP(Packet) |
+----------------------+

KCP header


KCP Header Format


      4           1   1     2 (Byte)
+---+---+---+---+---+---+---+---+
| conv |cmd|frg| wnd |
+---+---+---+---+---+---+---+---+
| ts | sn |
+---+---+---+---+---+---+---+---+
| una | len |
+---+---+---+---+---+---+---+---+
| |
+ DATA +
| |
+---+---+---+---+---+---+---+---+

代码结构


src/vendor/github.com/xtaci/kcp-go/
├── LICENSE
├── README.md
├── crypt.go 加解密实现
├── crypt_test.go
├── donate.png
├── fec.go 向前纠错实现
├── frame.png
├── kcp-go.png
├── kcp.go kcp协议实现
├── kcp_test.go
├── sess.go 会话管理实现
├── sess_test.go
├── snmp.go 数据统计实现
├── updater.go 任务调度实现
├── xor.go xor封装
└── xor_test.go

着重研究两个文件kcp.gosess.go


kcp浅析


kcp是基于udp实现的,所有udp的实现这里不做介绍,kcp做的事情就是怎么封装udp的数据和怎么解析udp的数据,再加各种处理机制,为了重传,拥塞控制,纠错等。下面介绍kcp客户端和服务端整体实现的流程,只是大概介绍一下函数流,不做详细解析,详细解析看后面数据流的解析。


kcp client整体函数流


和tcp一样,kcp要连接服务端需要先拨号,但是和tcp有个很大的不同是,即使服务端没有启动,客户端一样可以拨号成功,因为实际上这里的拨号没有发送任何信息,而tcp在这里需要三次握手。


DialWithOptions(raddr string, block BlockCrypt, dataShards, parityShards int)
V
net.DialUDP("udp", nil, udpaddr)
V
NewConn()
V
newUDPSession() {初始化UDPSession}
V
NewKCP() {初始化kcp}
V
updater.addSession(sess) {管理session会话,任务管理,根据用户设置的internal参数间隔来轮流唤醒任务}
V
go sess.readLoop()
V
go s.receiver(chPacket)
V
s.kcpInput(data)
V
s.fecDecoder.decodeBytes(data)
V
s.kcp.Input(data, true, s.ackNoDelay)
V
kcp.parse_data(seg) {将分段好的数据插入kcp.rcv_buf缓冲}
V
notifyReadEvent()

客户端大体的流程如上面所示,先Dial,建立udp连接,将这个连接封装成一个会话,然后启动一个go程,接收udp的消息。


kcp server整体函数流


ListenWithOptions() 
V
net.ListenUDP()
V
ServerConn()
V
newFECDecoder()
V
go l.monitor() {从chPacket接收udp数据,写入kcp}
V
go l.receiver(chPacket) {从upd接收数据,并入队列}
V
newUDPSession()
V
updater.addSession(sess) {管理session会话,任务管理,根据用户设置的internal参数间隔来轮流唤醒任务}
V
s.kcpInput(data)`
V
s.fecDecoder.decodeBytes(data)
V
s.kcp.Input(data, true, s.ackNoDelay)
V
kcp.parse_data(seg) {将分段好的数据插入kcp.rcv_buf缓冲}
V
notifyReadEvent()

服务端的大体流程如上图所示,先Listen,启动udp监听,接着用一个go程监控udp的数据包,负责将不同session的数据写入不同的udp连接,然后解析封装将数据交给上层。


kcp 数据流详细解析


不管是kcp的客户端还是服务端,他们都有io行为,就是读与写,我们只分析一个就好了,因为它们读写的实现是一样的,这里分析客户端的读与写。


kcp client 发送消息


s.Write(b []byte) 
V
s.kcp.WaitSnd() {}
V
s.kcp.Send(b) {将数据根据mss分段,并存在kcp.snd_queue}
V
s.kcp.flush(false) [flush data to output] {
if writeDelay==true {
flush
}else{
每隔`interval`时间flush一次
}
}
V
kcp.output(buffer, size)
V
s.output(buf)
V
s.conn.WriteTo(ext, s.remote)
V
s.conn..Conn.WriteTo(buf)

读写都是在sess.go文件中实现的,Write方法:


// Write implements net.Conn
func (s *UDPSession) Write(b []byte) (n int, err error) {
for {
...

// api flow control
if s.kcp.WaitSnd() < int(s.kcp.snd_wnd) {
n = len(b)
for {
if len(b) <= int(s.kcp.mss) {
s.kcp.Send(b)
break
} else {
s.kcp.Send(b[:s.kcp.mss])
b = b[s.kcp.mss:]
}
}

if !s.writeDelay {
s.kcp.flush(false)
}
s.mu.Unlock()
atomic.AddUint64(&DefaultSnmp.BytesSent, uint64(n))
return n, nil
}

...
// wait for write event or timeout
select {
case <-s.chWriteEvent:
case <-c:
case <-s.die:
}

if timeout != nil {
timeout.Stop()
}
}
}

假设发送一个hello消息,Write方法会先判断发送窗口是否已满,满的话该函数阻塞,不满则kcp.Send("hello"),而Send函数实现根据mss的值对数据分段,当然这里的发送的hello,长度太短,只分了一个段,并把它们插入发送的队列里。


func (kcp *KCP) Send(buffer []byte) int {
...
for i := 0; i < count; i++ {
var size int
if len(buffer) > int(kcp.mss) {
size = int(kcp.mss)
} else {
size = len(buffer)
}
seg := kcp.newSegment(size)
copy(seg.data, buffer[:size])
if kcp.stream == 0 { // message mode
seg.frg = uint8(count - i - 1)
} else { // stream mode
seg.frg = 0
}
kcp.snd_queue = append(kcp.snd_queue, seg)
buffer = buffer[size:]
}
return 0
}

接着判断参数writeDelay,如果参数设置为false,则立马发送消息,否则需要任务调度后才会触发发送,发送消息是由flush函数实现的。


// flush pending data
func (kcp *KCP) flush(ackOnly bool) {
var seg Segment
seg.conv = kcp.conv
seg.cmd = IKCP_CMD_ACK
seg.wnd = kcp.wnd_unused()
seg.una = kcp.rcv_nxt

buffer := kcp.buffer
// flush acknowledges
ptr := buffer
for i, ack := range kcp.acklist {
size := len(buffer) - len(ptr)
if size+IKCP_OVERHEAD > int(kcp.mtu) {
kcp.output(buffer, size)
ptr = buffer
}
// filter jitters caused by bufferbloat
if ack.sn >= kcp.rcv_nxt || len(kcp.acklist)-1 == i {
seg.sn, seg.ts = ack.sn, ack.ts
ptr = seg.encode(ptr)

}
}
kcp.acklist = kcp.acklist[0:0]

if ackOnly { // flash remain ack segments
size := len(buffer) - len(ptr)
if size > 0 {
kcp.output(buffer, size)
}
return
}

// probe window size (if remote window size equals zero)
if kcp.rmt_wnd == 0 {
current := currentMs()
if kcp.probe_wait == 0 {
kcp.probe_wait = IKCP_PROBE_INIT
kcp.ts_probe = current + kcp.probe_wait
} else {
if _itimediff(current, kcp.ts_probe) >= 0 {
if kcp.probe_wait < IKCP_PROBE_INIT {
kcp.probe_wait = IKCP_PROBE_INIT
}
kcp.probe_wait += kcp.probe_wait / 2
if kcp.probe_wait > IKCP_PROBE_LIMIT {
kcp.probe_wait = IKCP_PROBE_LIMIT
}
kcp.ts_probe = current + kcp.probe_wait
kcp.probe |= IKCP_ASK_SEND
}
}
} else {
kcp.ts_probe = 0
kcp.probe_wait = 0
}

// flush window probing commands
if (kcp.probe & IKCP_ASK_SEND) != 0 {
seg.cmd = IKCP_CMD_WASK
size := len(buffer) - len(ptr)
if size+IKCP_OVERHEAD > int(kcp.mtu) {
kcp.output(buffer, size)
ptr = buffer
}
ptr = seg.encode(ptr)
}

// flush window probing commands
if (kcp.probe & IKCP_ASK_TELL) != 0 {
seg.cmd = IKCP_CMD_WINS
size := len(buffer) - len(ptr)
if size+IKCP_OVERHEAD > int(kcp.mtu) {
kcp.output(buffer, size)
ptr = buffer
}
ptr = seg.encode(ptr)
}

kcp.probe = 0

// calculate window size
cwnd := _imin_(kcp.snd_wnd, kcp.rmt_wnd)
if kcp.nocwnd == 0 {
cwnd = _imin_(kcp.cwnd, cwnd)
}

// sliding window, controlled by snd_nxt && sna_una+cwnd
newSegsCount := 0
for k := range kcp.snd_queue {
if _itimediff(kcp.snd_nxt, kcp.snd_una+cwnd) >= 0 {
break
}
newseg := kcp.snd_queue[k]
newseg.conv = kcp.conv
newseg.cmd = IKCP_CMD_PUSH
newseg.sn = kcp.snd_nxt
kcp.snd_buf = append(kcp.snd_buf, newseg)
kcp.snd_nxt++
newSegsCount++
kcp.snd_queue[k].data = nil
}
if newSegsCount > 0 {
kcp.snd_queue = kcp.remove_front(kcp.snd_queue, newSegsCount)
}

// calculate resent
resent := uint32(kcp.fastresend)
if kcp.fastresend <= 0 {
resent = 0xffffffff
}

// check for retransmissions
current := currentMs()
var change, lost, lostSegs, fastRetransSegs, earlyRetransSegs uint64
for k := range kcp.snd_buf {
segment := &kcp.snd_buf[k]
needsend := false
if segment.xmit == 0 { // initial transmit
needsend = true
segment.rto = kcp.rx_rto
segment.resendts = current + segment.rto
} else if _itimediff(current, segment.resendts) >= 0 { // RTO
needsend = true
if kcp.nodelay == 0 {
segment.rto += kcp.rx_rto
} else {
segment.rto += kcp.rx_rto / 2
}
segment.resendts = current + segment.rto
lost++
lostSegs++
} else if segment.fastack >= resent { // fast retransmit
needsend = true
segment.fastack = 0
segment.rto = kcp.rx_rto
segment.resendts = current + segment.rto
change++
fastRetransSegs++
} else if segment.fastack > 0 && newSegsCount == 0 { // early retransmit
needsend = true
segment.fastack = 0
segment.rto = kcp.rx_rto
segment.resendts = current + segment.rto
change++
earlyRetransSegs++
}

if needsend {
segment.xmit++
segment.ts = current
segment.wnd = seg.wnd
segment.una = seg.una

size := len(buffer) - len(ptr)
need := IKCP_OVERHEAD + len(segment.data)

if size+need > int(kcp.mtu) {
kcp.output(buffer, size)
current = currentMs() // time update for a blocking call
ptr = buffer
}

ptr = segment.encode(ptr)
copy(ptr, segment.data)
ptr = ptr[len(segment.data):]

if segment.xmit >= kcp.dead_link {
kcp.state = 0xFFFFFFFF
}
}
}

// flash remain segments
size := len(buffer) - len(ptr)
if size > 0 {
kcp.output(buffer, size)
}

// counter updates
sum := lostSegs
if lostSegs > 0 {
atomic.AddUint64(&DefaultSnmp.LostSegs, lostSegs)
}
if fastRetransSegs > 0 {
atomic.AddUint64(&DefaultSnmp.FastRetransSegs, fastRetransSegs)
sum += fastRetransSegs
}
if earlyRetransSegs > 0 {
atomic.AddUint64(&DefaultSnmp.EarlyRetransSegs, earlyRetransSegs)
sum += earlyRetransSegs
}
if sum > 0 {
atomic.AddUint64(&DefaultSnmp.RetransSegs, sum)
}

// update ssthresh
// rate halving, https://tools.ietf.org/html/rfc6937
if change > 0 {
inflight := kcp.snd_nxt - kcp.snd_una
kcp.ssthresh = inflight / 2
if kcp.ssthresh < IKCP_THRESH_MIN {
kcp.ssthresh = IKCP_THRESH_MIN
}
kcp.cwnd = kcp.ssthresh + resent
kcp.incr = kcp.cwnd * kcp.mss
}

// congestion control, https://tools.ietf.org/html/rfc5681
if lost > 0 {
kcp.ssthresh = cwnd / 2
if kcp.ssthresh < IKCP_THRESH_MIN {
kcp.ssthresh = IKCP_THRESH_MIN
}
kcp.cwnd = 1
kcp.incr = kcp.mss
}

if kcp.cwnd < 1 {
kcp.cwnd = 1
kcp.incr = kcp.mss
}
}

flush函数非常的重要,kcp的重要参数都是在调节这个函数的行为,这个函数只有一个参数ackOnly,意思就是只发送ack,如果ackOnly为true的话,该函数只遍历ack列表,然后发送,就完事了。
如果不是,也会发送真实数据。
在发送数据前先进行windSize探测,如果开启了拥塞控制nc=0,则每次发送前检测服务端的winsize,如果服务端的winsize变小了,自身的winsize也要更着变小,来避免拥塞。如果没有开启拥塞控制,就按设置的winsize进行数据发送。

接着循环每个段数据,并判断每个段数据的是否该重发,还有什么时候重发:



  1. 如果这个段数据首次发送,则直接发送数据。

  2. 如果这个段数据的当前时间大于它自身重发的时间,也就是RTO,则重传消息。

  3. 如果这个段数据的ack丢失累计超过resent次数,则重传,也就是快速重传机制。这个resent参数由resend参数决定。

  4. 如果这个段数据的ack有丢失且没有新的数据段,则触发ER,ER相关信息ER


最后通过kcp.output发送消息hello,output是个回调函数,函数的实体是sess.go的:


func (s *UDPSession) output(buf []byte) {
var ecc [][]byte

// extend buf's header space
ext := buf
if s.headerSize > 0 {
ext = s.ext[:s.headerSize+len(buf)]
copy(ext[s.headerSize:], buf)
}

// FEC stage
if s.fecEncoder != nil {
ecc = s.fecEncoder.Encode(ext)
}

// encryption stage
if s.block != nil {
io.ReadFull(rand.Reader, ext[:nonceSize])
checksum := crc32.ChecksumIEEE(ext[cryptHeaderSize:])
binary.LittleEndian.PutUint32(ext[nonceSize:], checksum)
s.block.Encrypt(ext, ext)

if ecc != nil {
for k := range ecc {
io.ReadFull(rand.Reader, ecc[k][:nonceSize])
checksum := crc32.ChecksumIEEE(ecc[k][cryptHeaderSize:])
binary.LittleEndian.PutUint32(ecc[k][nonceSize:], checksum)
s.block.Encrypt(ecc[k], ecc[k])
}
}
}

// WriteTo kernel
nbytes := 0
npkts := 0
// if mrand.Intn(100) < 50 {
for i := 0; i < s.dup+1; i++ {
if n, err := s.conn.WriteTo(ext, s.remote); err == nil {
nbytes += n
npkts++
}
}
// }

if ecc != nil {
for k := range ecc {
if n, err := s.conn.WriteTo(ecc[k], s.remote); err == nil {
nbytes += n
npkts++
}
}
}
atomic.AddUint64(&DefaultSnmp.OutPkts, uint64(npkts))
atomic.AddUint64(&DefaultSnmp.OutBytes, uint64(nbytes))
}

output函数才是真正的将数据写入内核中,在写入之前先进行了fec编码,fec编码器的实现是用了一个开源库github.com/klauspost/reedsolomon,编码以后的hello就不是和原来的hello一样了,至少多了几个字节。
fec编码器有两个重要的参数reedsolomon.New(dataShards, parityShards, reedsolomon.WithMaxGoroutines(1)),dataShardsparityShards,这两个参数决定了fec的冗余度,冗余度越大抗丢包性就越强。


kcp的任务调度器


其实这里任务调度器是一个很简单的实现,用一个全局变量updater来管理session,代码文件为updater.go。其中最主要的函数


func (h *updateHeap) updateTask() {
var timer <-chan time.Time
for {
select {
case <-timer:
case <-h.chWakeUp:
}

h.mu.Lock()
hlen := h.Len()
now := time.Now()
if hlen > 0 && now.After(h.entries[0].ts) {
for i := 0; i < hlen; i++ {
entry := heap.Pop(h).(entry)
if now.After(entry.ts) {
entry.ts = now.Add(entry.s.update())
heap.Push(h, entry)
} else {
heap.Push(h, entry)
break
}
}
}
if hlen > 0 {
timer = time.After(h.entries[0].ts.Sub(now))
}
h.mu.Unlock()
}
}

任务调度器实现了一个堆结构,每当有新的连接,session都会插入到这个堆里,接着for循环每隔interval时间,遍历这个堆,得到entry然后执行entry.s.update()。而entry.s.update()会执行s.kcp.flush(false)来发送数据。


总结


这里简单介绍了kcp的整体流程,详细介绍了发送数据的流程,但未介绍kcp接收数据的流程,其实在客户端发送数据后,服务端是需要返回ack的,而客户端也需要根据返回的ack来判断数据段是否需要重传还是在队列里清除该数据段。处理返回来的ack是在函数kcp.Input()函数实现的。具体详细流程下次再介绍。