golang

golang

Go语言基础,想要了解的可以阅读下

文章分享liyongjing 发表了文章 • 0 个评论 • 206 次浏览 • 2018-02-13 18:53 • 来自相关话题

Go 语言内置类型研读


编程语言底层那些事儿


内容太干,想要了解的可以阅读下

golang80行代码钉钉群机器人订阅百度新闻

文章分享jjjjerk 发表了文章 • 0 个评论 • 195 次浏览 • 2018-02-09 17:54 • 来自相关话题

1. 资料


1.1.第三方包


1.2.接口


2. 初始化项目变量


package main

import (
"fmt"
"log"
"github.com/PuerkitoBio/goquery"
"github.com/go-redis/redis"
"net/http"
"bytes"
"github.com/astaxie/beego/toolbox"
)

var (
redisClient *redis.Client //redis 缓存
//钉钉群机器人webhook地址
dingdingURL = "https://oapi.dingtalk.com/robot/send?access_token=dingding_talk_group_bot_webhook_token"
//百度新闻搜索关键字URL
baiduNewsUrlWithSearchKeyword = "http://news.baidu.com/ns?cl=2&rn=20&tn=news&word=%E7%89%A9%E8%81%94%E7%BD%91"
)

const (
newsFeed = "news_feed"//爬取到的百度新闻redis key
newsPost = "news_post"//已发送的百度新闻redis key
newsList = "iot_news" //储存了的百度新闻redis key
)
//实例化redis缓存
func init() {
redisClient = redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Password: "ddfrfgtre4353252", // redis password
DB: 0, // redis 数据库ID
})
}

在机器人管理页面选择“自定义”机器人,输入机器人名字并选择要发送消息的群。如果需要的话,可以为机器人设置一个头像。点击“完成添加”。


点击“复制”按钮,即可获得这个机器人对应的Webhook地址,赋值给 dingdingURl


3 func newsBot


3.1 使用goquery和网页元素选择器语法提取有用信息

func newsBot() error {
// 获取html doc
doc, err := goquery.NewDocument(baiduNewsUrlWithSearchKeyword)
if err != nil {
return nil
}
//使用redis pipeline 减少redis连接数
pipe := redisClient.Pipeline()
// 使用selector xpath 语法获取有用信息
// 储存新闻到redis中 newsList
// 储存新闻ur到redis-set 建newfeed 为以后是用sdiff 找出没有发送的新闻

doc.Find("div.result").Each(func(i int, s *goquery.Selection) {
// For each item found, get the band and title
URL, _ := s.Find("h3 > a").Attr("href")
Source := s.Find("p.c-author").Text()
Title := s.Find("h3 > a").Text()
markdown := fmt.Sprintf("- [%s](%s) _%s_", Title, URL, Source)
pipe.HSet(newsList, URL, markdown)
pipe.SAdd(newsFeed, URL)
})
//执行redis pipeline
pipe.Exec()

3.2 排除以发送的新闻,拼接markdown字符串

        //使用redis sdiff找出没有发送的新闻url
unSendNewsUrls := redisClient.SDiff(newsFeed, newsPost).Val()
//新闻按dingding文档markdonw 规范拼接

content := ""
for _, url := range unSendNewsUrls {
md := redisClient.HGet(newsList, url).Val()
content = content + " \n " + md
//记录已发送新闻的url地址
pipe.SAdd(newsPost, url)
}
pipe.Exec()

3.3 调用钉钉群机器人接口

        //如果有未发送新闻 请求钉钉webhook
if content != "" {
formt := `
{
"msgtype": "markdown",
"markdown": {
"title":"IOT每日新闻",
"text": "%s"
}
}`
body := fmt.Sprintf(formt, content)
jsonValue := []byte(body)
//发送消息到钉钉群使用webhook
//钉钉文档 https://open-doc.dingtalk.com/ ... e%3D1
resp, err := http.Post(dingdingURL, "application/json", bytes.NewBuffer(jsonValue))
if (err != nil) {
return err
}
log.Println(resp)
}
return nil
}

func newsBot函数完成


4. 设置定时任务


func main() {
//销毁redisClient
defer redisClient.Close()

//创建定时任务
//每天 8点 13点 18点 自动执行爬虫和机器人
//
dingdingNewsBot := toolbox.NewTask("dingding-news-bot", "0 0 8,13,18 * * *", newsBot)
//dingdingNewsBot := toolbox.NewTask("dingding-news-bot", "0 40 */1 * * *", newsBot)
//err := dingdingNewsBot.Run()
//检测定时任务
// if err != nil {
// log.Fatal(err)
// }
//添加定时任务
toolbox.AddTask("dingding-news-bot", dingdingNewsBot)
//启动定时任务
toolbox.StartTask()
defer toolbox.StopTask()
select {}
}


spec 格式是参照



5 最终代码



go build main.go
nohup ./main &

最终效果
dingding-webhook-bot


7 最后


reflect在实际项目中都有哪儿些应用

有问必答elvin5 回复了问题 • 5 人关注 • 5 个回复 • 358 次浏览 • 2018-02-08 10:38 • 来自相关话题

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

有问必答csxuejin 回复了问题 • 50 人关注 • 30 个回复 • 6095 次浏览 • 2018-02-06 11:42 • 来自相关话题

请问 macOS 怎么优雅简单的升级Golang

有问必答shimron 回复了问题 • 6 人关注 • 5 个回复 • 435 次浏览 • 2018-02-02 18:48 • 来自相关话题

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

有问必答lichao2018 回复了问题 • 32 人关注 • 18 个回复 • 12319 次浏览 • 2018-02-02 13:45 • 来自相关话题

用Go来开发类似QT的跨平台的桌面软件框架是不是会更方便使用?

有问必答tupunco 回复了问题 • 3 人关注 • 2 个回复 • 353 次浏览 • 2018-02-02 08:38 • 来自相关话题

go的compiler是如何支持append函数确定其slice类型的?

回复

有问必答cbsheng 发起了问题 • 1 人关注 • 0 个回复 • 181 次浏览 • 2018-02-01 10:46 • 来自相关话题

【北京望京】【云账户】跪求高级golang工程师~~ 薪资open!!!

招聘应聘lynn 发表了文章 • 2 个评论 • 643 次浏览 • 2018-01-23 16:25 • 来自相关话题

职责:

1、负责云账户后台系统的设计、开发、测试和维护

要求:

1、2年以上工作经验,有分布式后台开发经验优先

2、有Golang开发经验优先,或熟悉掌握一门常用语言(C/C++, JAVA, P... 查看全部

职责:


1、负责云账户后台系统的设计、开发、测试和维护


要求:


1、2年以上工作经验,有分布式后台开发经验优先


2、有Golang开发经验优先,或熟悉掌握一门常用语言(C/C++, JAVA, PHP, PYTHON等),愿意转向Golang开发


3、熟悉关系型数据库(MySQL,PostgreSQL等)应用开发及常用性能优化技术


4、具备一定运维经验,能够开发运维友好的系统


5、熟悉一门脚本语言(Python, Perl或者Shell等)优先


6、具有较强责任心及执行力


公司福利:14薪;住房离公司近补贴;带薪年假;有竞争力的薪酬待遇;牛人共事;技术驱动;工程师文化;各种零食和茶歇活动;办公环境和设备好;各种节日活动福利;经常团建;年度体检;弹性工作时间;快乐工作氛围;桌游电竞休闲等等等等等。


坐标:朝阳区望京soho ~
有兴趣的伙伴们欢迎砸我的邮箱 ying.li@yunzhanghu.com

滴滴招golang开发工程师(长期有效20k-50k)

回复

招聘应聘doopymc 发起了问题 • 1 人关注 • 0 个回复 • 442 次浏览 • 2018-01-22 15:09 • 来自相关话题

golang for语句完全指南

文章分享sheepbao 发表了文章 • 4 个评论 • 261 次浏览 • 2018-01-15 20:54 • 来自相关话题

以下所有观点都是个人愚见,有不同建议或补充的的欢迎emialaboutme
查看全部

以下所有观点都是个人愚见,有不同建议或补充的的欢迎emialaboutme

原文章地址


关于for语句的疑问

for语句的规范

for语句的内部实现-array

问题解答


关于for语句的疑问


我们都知道在golang中,循环语句只有for这一个,在代码中写一个循环都一般都需要用到for(当然你用goto也是可以的),
虽然golang的for语句很方便,但不少初学者一样对for语句持有不少疑问,如:



  1. for语句一共有多少种表达式格式?

  2. for语句中临时变量是怎么回事?(为什么有时遍历赋值后,所有的值都等于最后一个元素)

  3. range后面支持的数据类型有哪些?

  4. range string类型为何得到的是rune类型?

  5. 遍历slice的时候增加或删除数据会怎么样?

  6. 遍历map的时候增加或删除数据会怎么样?


其实这里的很多疑问都可以看golang编程语言规范,
有兴趣的同学完全可以自己看,然后根据自己的理解来解答这些问题。


for语句的规范


for语句的功能用来指定重复执行的语句块,for语句中的表达式有三种:

官方的规范: ForStmt = "for" [ Condition | ForClause | RangeClause ] Block .



  • Condition = Expression .

  • ForClause = [ InitStmt ] ";" [ Condition ] ";" [ PostStmt ] .

  • RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .


单个条件判断


形式:


for a < b {
f(doThing)
}
// or 省略表达式,等价于true
for { // for true {
f(doThing)
}

这种格式,只有单个逻辑表达式, 逻辑表达式的值为true,则继续执行,否则停止循环。


for语句中两个分号


形式:


for i:=0; i < 10; i++ {
f(doThing)
}
// or
for i:=0; i < 10; {
i++
f(doThing)
}
// or
var i int
for ; i < 10; {
i++
f(doThing)
}

这种格式,语气被两个分号分割为3个表达式,第一个表示为初始化(只会在第一次条件表达式之计算一次),第二个表达式为条件判断表达式,
第三个表达式一般为自增或自减,但这个表达式可以任何符合语法的表达式。而且这三个表达式,
只有第二个表达式是必须有的,其他表达式可以为空。


for和range结合的语句


形式:


for k,v := range []int{1,2,3} {
f(doThing)
}
// or
for k := range []int{1,2,3} {
f(doThing)
}
// or
for range []int{1,2,3} {
f(doThing)
}

用range来迭代数据是最常用的一种for语句,range右边的表达式叫范围表达式,
范围表达式可以是数组,数组指针,slice,字符串,map和channel。因为要赋值,
所以左侧的操作数(也就是迭代变量)必须要可寻址的,或者是map下标的表达式。
如果迭代变量是一个channel,那么只允许一个迭代变量,除此之外迭代变量可以有一个或者两个。


范围表达式在开始循环之前只进行一次求值,只有一个例外:如果范围表达式是数组或指向数组的指针,
至多有一个迭代变量存在,只对范围表达式的长度进行求值;如果长度为常数,范围表达式本身将不被求值。


每迭代一次,左边的函数调用求值。对于每个迭代,如果相应的迭代变量存在,则迭代值如下所示生成:


Range expression                          1st value          2nd value

array or slice a [n]E, *[n]E, or []E index i int a[i] E
string s string type index i int see below rune
map m map[K]V key k K m[k] V
channel c chan E, <-chan E element e E


  1. 对于数组、数组指针或是分片值a来说,下标迭代值升序生成,从0开始。有一种特殊场景,只有一个迭代参数存在的情况下,
    range循环生成0到len(a)的迭代值,而不是索引到数组或是分片。对于一个nil分片,迭代的数量为0。

  2. 对于字符串类型,range子句迭代字符串中每一个Unicode代码点,从下标0开始。在连续迭代中,下标值会是下一个utf-8代码点的
    第一个字节的下标,而第二个值类型是rune,会是对应的代码点。如果迭代遇到了一个非法的Unicode序列,那么第二个值是0xFFFD,
    也就是Unicode的替换字符,然后下一次迭代只会前进一个字节。

  3. map中的迭代顺序是没有指定的,也不保证两次迭代是一样的。如果map元素在迭代过程中被删掉了,那么对应的迭代值不会再产生。
    如果map元素在迭代中插入了,则该元素可能在迭代过程中产生,也可能被跳过,但是每个元素的迭代值顶多出现一次。如果map是nil,那么迭代次数为0。

  4. 对于管道,迭代值就是下一个send到管道中的值,除非管道被关闭了。如果管道是nil,范围表达式永远阻塞。


迭代值会赋值给相应的迭代变量,就像是赋值语句。

迭代变量可以使用短变量声明(:=)。这种情况,它们的类型设置为相应迭代值的类型,它们的域是到for语句的结尾,它们在每一次迭代中复用。
如果迭代变量是在for语句外声明的,那么执行之后它们的值是最后一次迭代的值。


var testdata *struct {
a *[7]int
}
for i, _ := range testdata.a {
// testdata.a is never evaluated; len(testdata.a) is constant
// i ranges from 0 to 6
f(i)
}

var a [10]string
for i, s := range a {
// type of i is int
// type of s is string
// s == a[i]
g(i, s)
}

var key string
var val interface {} // value type of m is assignable to val
m := map[string]int{"mon":0, "tue":1, "wed":2, "thu":3, "fri":4, "sat":5, "sun":6}
for key, val = range m {
h(key, val)
}
// key == last map key encountered in iteration
// val == map[key]

var ch chan Work = producer()
for w := range ch {
doWork(w)
}

// empty a channel
for range ch {}

for语句的内部实现-array


golang的for语句,对于不同的格式会被编译器编译成不同的形式,如果要弄明白需要看
golang的编译器和相关数据结构的源码,
数据结构源码还好,但是编译器是用C++写的,本人C++是个弱鸡,这里只讲array内部实现


// The loop we generate:
// len_temp := len(range)
// range_temp := range
// for index_temp = 0; index_temp < len_temp; index_temp++ {
// value_temp = range_temp[index_temp]
// index = index_temp
// value = value_temp
// original body
// }

// 例如代码:
array := [2]int{1,2}
for k,v := range array {
f(k,v)
}

// 会被编译成:
len_temp := len(array)
range_temp := array
for index_temp = 0; index_temp < len_temp; index_temp++ {
value_temp = range_temp[index_temp]
k = index_temp
v = value_temp
f(k,v)
}

所以像遍历一个数组,最后生成的代码很像C语言中的遍历,而且有两个临时变量index_temp,value_temp,
在整个遍历中一直复用这两个变量。所以会导致开头问题2的问题(详细解答会在后边)。


问题解答




  1. for语句一共有多少种表达式格式?

    这个问题应该很简单了,上面的规范中就有答案了,一共有3种:


    Condition = Expression .
    ForClause = [ InitStmt ] ";" [ Condition ] ";" [ PostStmt ] .
    RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .



  2. for语句中临时变量是怎么回事?(为什么有时遍历赋值后,所有的值都等于最后一个元素)

    先看这个例子:


    var a = make([]*int, 3)
    for k, v := range []int{1, 2, 3} {
    a[k] = &v
    }
    for i := range a {
    fmt.Println(*a[i])
    }
    // result:
    // 3
    // 3
    // 3

    for语句的内部实现-array可以知道,即使是短声明的变量,在for循环中也是复用的,这里的v一直
    都是同一个零时变量,所以&v得到的地址一直都是相同的,如果不信,你可以打印该地址,且该地址最后存的变量等于最后一次循环得到的变量,
    所以结果都是3。




  3. range后面支持的数据类型有哪些?

    共5个,分别是数组,数组指针,slice,字符串,map和channel




  4. range string类型为何得到的是rune类型?

    这个问题在for规范中也有解答,对于字符串类型,在连续迭代中,下标值会是下一个utf-8代码点的第一个字节的下标,而第二个值类型是rune。
    如果迭代遇到了一个非法的Unicode序列,那么第二个值是0xFFFD,也就是Unicode的替换字符,然后下一次迭代只会前进一个字节。


    其实看完这句话,我没理解,当然这句话告诉我们了遍历string得到的第二个值类型是rune,但是为什么是rune类型,而不是string或者其他类型?
    后来在看了Rob Pike写的blogStrings, bytes, runes and characters in Go
    才明白点,首先需要知道runeint32的别名,且go语言中的字符串字面量始终保存有效的UTF-8序列。而UTF-8就是用4字节来表示Unicode字符集。
    所以go的设计者用rune表示单个字符的编码,则可以完成容纳所表示Unicode字符。举个例子:


    s := `汉语ab`
    fmt.Println("len of s:", len(s))
    for index, runeValue := range s {
    fmt.Printf("%#U starts at byte position %d\n", runeValue, index)
    }
    // result
    // len of s: 8
    // U+6C49 '汉' starts at byte position 0
    // U+8BED '语' starts at byte position 3
    // U+0061 'a' starts at byte position 6
    // U+0062 'b' starts at byte position 7

    根据结果得知,s的长度是为8字节,一个汉子占用了3个字节,一个英文字母占用一个字节,而程序go程序是怎么知道汉子占3个字节,而
    英文字母占用一个字节,就需要知道utf-8代码点的概念,这里就不深究了,知道go是根据utf-8代码点来知道该字符占了多少字节就ok了。




  5. 遍历slice的时候增加或删除数据会怎么样?

    for语句的内部实现-array可以知道,获取slice的长度只在循环外执行了一次,
    该长度决定了遍历的次数,不管在循环里你怎么改。但是对索引求值是在每次的迭代中求值的,如果更改了某个元素且
    该元素还未遍历到,那么最终遍历得到的值是更改后的。删除元素也是属于更改元素的一种情况。


    在slice中增加元素,会更改slice含有的元素,但不会更改遍历次数。


    a2 := []int{0, 1, 2, 3, 4}
    for i, v := range a2 {
    fmt.Println(i, v)
    if i == 0 {
    a2 = append(a2, 6)
    }
    }
    // result
    // 0 0
    // 1 1
    // 2 2
    // 3 3
    // 4 4

    在slice中删除元素,能删除该元素,但不会更改遍历次数。


    // 只删除该元素1,不更改slice长度
    a2 := []int{0, 1, 2, 3, 4}
    for i, v := range a2 {
    fmt.Println(i, v)
    if i == 0 {
    copy(a2[1:], a2[2:])
    }
    }
    // result
    // 0 0
    // 1 2
    // 2 3
    // 3 4
    // 4 4

    // 删除该元素1,并更改slice长度
    a2 := []int{0, 1, 2, 3, 4}
    for i, v := range a2 {
    fmt.Println(i, v)
    if i == 0 {
    copy(a2[1:], a2[2:])
    a2 = a2[:len(a2)-2] //将a2的len设置为3,但并不会影响临时slice-range_temp
    }
    }
    // result
    // 0 0
    // 1 2
    // 2 3
    // 3 4
    // 4 4



  6. 遍历map的时候增加或删除数据会怎么样?

    规范中也有答案,map元素在迭代过程中被删掉了,那么对应的迭代值不会再产生。
    如果map元素在迭代中插入了,则该元素可能在迭代过程中产生,也可能被跳过。


    在遍历中删除元素


    m := map[int]int{1: 1, 2: 2, 3: 3, 4: 4, 5: 5}
    del := false
    for k, v := range m {
    fmt.Println(k, v)
    if !del {
    delete(m, 2)
    del = true
    }
    }
    // result
    // 4 4
    // 5 5
    // 1 1
    // 3 3

    在遍历中增加元素,多执行几次看结果


    m := map[int]int{1: 1, 2: 2, 3: 3, 4: 4, 5: 5}
    add := false
    for k, v := range m {
    fmt.Println(k, v)
    if !add {
    m[6] = 6
    m[7] = 7
    add = true
    }
    }
    // result1
    // 1 1
    // 2 2
    // 3 3
    // 4 4
    // 5 5
    // 6 6

    // result2
    // 1 1
    // 2 2
    // 3 3
    // 4 4
    // 5 5
    // 6 6
    // 7 7

    在map遍历中删除元素,将会删除该元素,且影响遍历次数,在遍历中增加元素则会有不可控的现象出现,有时能遍历到新增的元素,
    有时不能。具体原因下次分析。




参考


https://golang.org/ref/spec#For_statements

https://github.com/golang/go/wiki/Range

https://blog.golang.org/strings

electron 怎么和golang结合?

有问必答raindylong 回复了问题 • 5 人关注 • 5 个回复 • 611 次浏览 • 2018-01-11 21:39 • 来自相关话题

基于go+vue实现的golang每日新闻数据浏览与检索平台

开源程序mikemintang 发表了文章 • 6 个评论 • 1087 次浏览 • 2018-01-09 16:58 • 来自相关话题

介绍

gonews是基于go+vue实现的golang每日新闻浏览与检索平台

介绍


gonews是基于go+vue实现的golang每日新闻浏览与检索平台



项目截图


gonews


部署



  • 获取新闻数据


git clone https://github.com/gocn/news /data/news


  • 获取源码


go get -u github.com/mikemintang/gonews


  • 解析数据


nohup gonews -d /data/news > /data/log/gonews.log 2>&1 


  • 启动Api


nohup gonews -a api -p 8017 > /data/log/gonews.log 2>&1 &


  • 前端部署


cd $GOPATH/src/github.com/mikemintang/gonews/web
npm install
npm run build


  • Nginx配置


server {
listen 80;
server_name gonews.idoubi.cc;
index index.html index.htm index.php;
root /data/go/src/mikemintang/gonews/web;

location /api {
rewrite ^.+api/?(.*)$ /$1 break;
proxy_pass http://127.0.0.1:8017;
}
}


  • Shell脚本


#!/bin/sh

cd /data/news
git pull origin master
nohup gonews -d /data/news/ > /data/log/gonews.log 2>&1


  • 定时任务


crontab -e
*/10 * * * * /bin/sh /data/shell/cache_news.sh

用到的技术


golang包



  • github.com/go-redis/redis

  • encoding/json

  • flag

  • net/http

  • net/url

  • strconv

  • sync

  • crypto/md5

  • fmt

  • io

  • io/ioutil

  • net/url

  • os

  • path/filepath

  • regexp

  • strconv

  • strings

  • time


npm包



  • vue

  • vuex

  • vue-router

  • axios

  • moment

  • mockjs


欢迎提交Pull Request

Go能写桌面程序吗?比如winform 这样的

有问必答jicg 回复了问题 • 17 人关注 • 15 个回复 • 3625 次浏览 • 2018-01-07 19:59 • 来自相关话题

Redis 集群搭建及使用Golang示例

文章分享pathbox 发表了文章 • 5 个评论 • 397 次浏览 • 2018-01-05 11:55 • 来自相关话题

Redis 在3.x版本之后,自身支持了集群模式。Redis的集群主要是 master-slave的形式。集群定义了 16384个hash slot。这些hash slot分布在所有master上。查看全部

Redis 在3.x版本之后,自身支持了集群模式。Redis的集群主要是 master-slave的形式。集群定义了
16384个hash slot。这些hash slot分布在所有master上。we simply take the CRC16 of the key modulo 16384 将key计算得到对应的hash slot的值,然后看这个hash slot在哪个redis服务上,这个key就会保存在对应的这个redis服务。和Elasticsearch、MongoDB不一样,redis通过这种方式进行sharding。不能不说这是一种简单,但对redis来说是有效的一种集群方式。能够最大限度的保留原有redis的属性。
[Document]


下面是部署过程的例子。


有三台服务器 A、B、C。外网ip分别为 A_IP,B_IP,C_IP。


三台服务器先卸载原有redis版本,安装最新的redis版本


$ wget http://download.redis.io/relea ... ar.gz
$ tar xzf redis-4.0.6.tar.gz
$ cd redis-4.0.6
$ make

cd /src
cp redis-server /usr/bin/
cp redis-cli /usr/bin/
cp redis-trib.rb /usr/bin/

mkdir redis-cluster
cd redis-cluster
mkdir 7000
mkdir 7100

进入7000,新建redis.conf 文件,编写配置文件


cd 7000
vim redis.conf

配置内容为


port 7000                      # 绑定的端口
bind A_IP # 绑定的IP,如果你要开放出去,需要使用外网IP
cluster-enabled yes # 是否启用集群模式
cluster-config-file nodes.conf # 节点配置文件
cluster-node-timeout 5000 # 超时设置
appendonly yes # appendonly 配置 持久化设置
cluster-require-full-coverage no

cluster-require-full-coverage默认是yes。如果某个redis挂了,没有对应的slave升级为master,这时候,整个redis集群不可用。


所以建议设为no, 这样某个redis挂了,只是影响这一部分的hash slot查询有问题,不影响集群的其他redis的读写。


同理完成 7100的redis.conf配置文件内容。将三台机器都安装上述进行安装配置。


在A、B、C机器上


cd 7000
redis-server redis.conf
cd 7001
redis-server redis.conf

到A机器上, 执行


redis-trib.rb create --replicas 1 A_IP:7000 B_IP:7000 \
C_IP:7000 A_IP:7100 B_IP:7100 C_IP:7100

之后会提示输入yes,执行完后。


redis-cli -p 7000 -h A_IP cluster nodes

查看集群状态,会得到类似这样的信息。


aedabb4bd830978905d68ab8d88a94a031b515fe A_IP:7000@17000 master - 0 1513840545000 2 connected 5461-10922
861ff3beac7a6a3023424a64446f1101f8193d9d A_IP:7100@17100 slave 3d9e37599f6547387503e165d21838556435bac4 0 1513840544011 4 connected
3d9e37599f6547387503e165d21838556435bac4 B_IP:7000@17000 myself,master - 0 1513840545000 1 connected 0-5460
3ef82f387086931dbdedbd24b379671b8bd03289 C_IP:7000@17000 master - 0 1513840544411 3 connected 10923-16383
27764a4a521e4e5bd8e2e108c472ef6ed51645cd B_IP:7100@17100 slave aedabb4bd830978905d68ab8d88a94a031b515fe 0 1513840543912 5 connected
7f6ef6f05968ee869359c0771d42d768243e4ca1 C_IP:7100@17100 slave 3ef82f387086931dbdedbd24b379671b8bd03289 0 1513840545414 6 connected

Bingo~ redis集群服务就跑起来了。 三台机器,每台机器一个master,一个slave


redis集群增加节点或Resharding操作是使用redis-trib脚本命令,这个脚本是Ruby写的。每个node有一个node ID,比如: aedabb4bd830978905d68ab8d88a94a031b515fe


Golang 代码示例


package redis

import (
"time"

"github.com/go-redis/redis"
)

// redis数据超时时间
var Timeout = 5 * time.Hour

var Client = GetClusterClient()

func GetClusterClient() *redis.ClusterClient {
var client *redis.ClusterClient
client = redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{"A_IP:7000", "B_IP:7000", "C_IP:7000","A_IP:7100", "B_IP:7100", "C_IP:7100"},
})
err := client.Ping().Err()
if err == nil {
log.Info("Redis cluster OK")
} else {
log.Error("Redis cluster wrong")
}
return client
}

redis.Client.Set("word", "Hello World", redis.Timeout)
str, _ := rediscluster.Client.Get("word").Result()
fmt.Println(str) // => "Hello World"
条新动态, 点击查看
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 • 30 个回复 不感兴趣

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

赞同来自:

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

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

大家说说看都用啥写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 • 14 个回复 不感兴趣

想用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 • 18 个回复 不感兴趣

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

赞同来自:

```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 个回复 • 2020 次浏览 • 2016-10-14 13:08 • 来自相关话题

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

文章分享Cloudinsight 发表了文章 • 0 个评论 • 3166 次浏览 • 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 个评论 • 2145 次浏览 • 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的教程,书籍也可以

有问必答csxuejin 回复了问题 • 50 人关注 • 30 个回复 • 6095 次浏览 • 2018-02-06 11:42 • 来自相关话题

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

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

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

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

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

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

reflect在实际项目中都有哪儿些应用

回复

有问必答elvin5 回复了问题 • 5 人关注 • 5 个回复 • 358 次浏览 • 2018-02-08 10:38 • 来自相关话题

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

回复

有问必答csxuejin 回复了问题 • 50 人关注 • 30 个回复 • 6095 次浏览 • 2018-02-06 11:42 • 来自相关话题

请问 macOS 怎么优雅简单的升级Golang

回复

有问必答shimron 回复了问题 • 6 人关注 • 5 个回复 • 435 次浏览 • 2018-02-02 18:48 • 来自相关话题

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

回复

有问必答lichao2018 回复了问题 • 32 人关注 • 18 个回复 • 12319 次浏览 • 2018-02-02 13:45 • 来自相关话题

用Go来开发类似QT的跨平台的桌面软件框架是不是会更方便使用?

回复

有问必答tupunco 回复了问题 • 3 人关注 • 2 个回复 • 353 次浏览 • 2018-02-02 08:38 • 来自相关话题

go的compiler是如何支持append函数确定其slice类型的?

回复

有问必答cbsheng 发起了问题 • 1 人关注 • 0 个回复 • 181 次浏览 • 2018-02-01 10:46 • 来自相关话题

滴滴招golang开发工程师(长期有效20k-50k)

回复

招聘应聘doopymc 发起了问题 • 1 人关注 • 0 个回复 • 442 次浏览 • 2018-01-22 15:09 • 来自相关话题

electron 怎么和golang结合?

回复

有问必答raindylong 回复了问题 • 5 人关注 • 5 个回复 • 611 次浏览 • 2018-01-11 21:39 • 来自相关话题

Go能写桌面程序吗?比如winform 这样的

回复

有问必答jicg 回复了问题 • 17 人关注 • 15 个回复 • 3625 次浏览 • 2018-01-07 19:59 • 来自相关话题

golang下载minio上的zip文件无法解压?

回复

有问必答tupunco 回复了问题 • 2 人关注 • 1 个回复 • 282 次浏览 • 2017-12-24 16:15 • 来自相关话题

并发map或者类似的结构

回复

有问必答SaltySailor 回复了问题 • 5 人关注 • 6 个回复 • 552 次浏览 • 2017-12-20 14:30 • 来自相关话题

有没有好的 sql 数据库 driver 推荐, 最好同时支持 mysql 跟 pg?

回复

有问必答gogoing 回复了问题 • 5 人关注 • 5 个回复 • 663 次浏览 • 2017-12-14 17:43 • 来自相关话题

(开源)基于vue, react, node.js, go开发的微商城(含微信小程序)

回复

开源程序zhaoyun4122 回复了问题 • 8 人关注 • 5 个回复 • 2453 次浏览 • 2017-12-07 09:22 • 来自相关话题

golang兼职讲师 imooc 20K-45K

回复

招聘应聘gigiyuan 发起了问题 • 1 人关注 • 0 个回复 • 541 次浏览 • 2017-12-01 14:06 • 来自相关话题

大家说说看都用啥写Go

回复

技术讨论MarkYang 回复了问题 • 68 人关注 • 84 个回复 • 6582 次浏览 • 2017-11-30 16:33 • 来自相关话题

Go语言基础,想要了解的可以阅读下

文章分享liyongjing 发表了文章 • 0 个评论 • 206 次浏览 • 2018-02-13 18:53 • 来自相关话题

Go 语言内置类型研读


编程语言底层那些事儿


内容太干,想要了解的可以阅读下

golang80行代码钉钉群机器人订阅百度新闻

文章分享jjjjerk 发表了文章 • 0 个评论 • 195 次浏览 • 2018-02-09 17:54 • 来自相关话题

1. 资料


1.1.第三方包


1.2.接口


2. 初始化项目变量


package main

import (
"fmt"
"log"
"github.com/PuerkitoBio/goquery"
"github.com/go-redis/redis"
"net/http"
"bytes"
"github.com/astaxie/beego/toolbox"
)

var (
redisClient *redis.Client //redis 缓存
//钉钉群机器人webhook地址
dingdingURL = "https://oapi.dingtalk.com/robot/send?access_token=dingding_talk_group_bot_webhook_token"
//百度新闻搜索关键字URL
baiduNewsUrlWithSearchKeyword = "http://news.baidu.com/ns?cl=2&rn=20&tn=news&word=%E7%89%A9%E8%81%94%E7%BD%91"
)

const (
newsFeed = "news_feed"//爬取到的百度新闻redis key
newsPost = "news_post"//已发送的百度新闻redis key
newsList = "iot_news" //储存了的百度新闻redis key
)
//实例化redis缓存
func init() {
redisClient = redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Password: "ddfrfgtre4353252", // redis password
DB: 0, // redis 数据库ID
})
}

在机器人管理页面选择“自定义”机器人,输入机器人名字并选择要发送消息的群。如果需要的话,可以为机器人设置一个头像。点击“完成添加”。


点击“复制”按钮,即可获得这个机器人对应的Webhook地址,赋值给 dingdingURl


3 func newsBot


3.1 使用goquery和网页元素选择器语法提取有用信息

func newsBot() error {
// 获取html doc
doc, err := goquery.NewDocument(baiduNewsUrlWithSearchKeyword)
if err != nil {
return nil
}
//使用redis pipeline 减少redis连接数
pipe := redisClient.Pipeline()
// 使用selector xpath 语法获取有用信息
// 储存新闻到redis中 newsList
// 储存新闻ur到redis-set 建newfeed 为以后是用sdiff 找出没有发送的新闻

doc.Find("div.result").Each(func(i int, s *goquery.Selection) {
// For each item found, get the band and title
URL, _ := s.Find("h3 > a").Attr("href")
Source := s.Find("p.c-author").Text()
Title := s.Find("h3 > a").Text()
markdown := fmt.Sprintf("- [%s](%s) _%s_", Title, URL, Source)
pipe.HSet(newsList, URL, markdown)
pipe.SAdd(newsFeed, URL)
})
//执行redis pipeline
pipe.Exec()

3.2 排除以发送的新闻,拼接markdown字符串

        //使用redis sdiff找出没有发送的新闻url
unSendNewsUrls := redisClient.SDiff(newsFeed, newsPost).Val()
//新闻按dingding文档markdonw 规范拼接

content := ""
for _, url := range unSendNewsUrls {
md := redisClient.HGet(newsList, url).Val()
content = content + " \n " + md
//记录已发送新闻的url地址
pipe.SAdd(newsPost, url)
}
pipe.Exec()

3.3 调用钉钉群机器人接口

        //如果有未发送新闻 请求钉钉webhook
if content != "" {
formt := `
{
"msgtype": "markdown",
"markdown": {
"title":"IOT每日新闻",
"text": "%s"
}
}`
body := fmt.Sprintf(formt, content)
jsonValue := []byte(body)
//发送消息到钉钉群使用webhook
//钉钉文档 https://open-doc.dingtalk.com/ ... e%3D1
resp, err := http.Post(dingdingURL, "application/json", bytes.NewBuffer(jsonValue))
if (err != nil) {
return err
}
log.Println(resp)
}
return nil
}

func newsBot函数完成


4. 设置定时任务


func main() {
//销毁redisClient
defer redisClient.Close()

//创建定时任务
//每天 8点 13点 18点 自动执行爬虫和机器人
//
dingdingNewsBot := toolbox.NewTask("dingding-news-bot", "0 0 8,13,18 * * *", newsBot)
//dingdingNewsBot := toolbox.NewTask("dingding-news-bot", "0 40 */1 * * *", newsBot)
//err := dingdingNewsBot.Run()
//检测定时任务
// if err != nil {
// log.Fatal(err)
// }
//添加定时任务
toolbox.AddTask("dingding-news-bot", dingdingNewsBot)
//启动定时任务
toolbox.StartTask()
defer toolbox.StopTask()
select {}
}


spec 格式是参照



5 最终代码



go build main.go
nohup ./main &

最终效果
dingding-webhook-bot


7 最后


【北京望京】【云账户】跪求高级golang工程师~~ 薪资open!!!

招聘应聘lynn 发表了文章 • 2 个评论 • 643 次浏览 • 2018-01-23 16:25 • 来自相关话题

职责:

1、负责云账户后台系统的设计、开发、测试和维护

要求:

1、2年以上工作经验,有分布式后台开发经验优先

2、有Golang开发经验优先,或熟悉掌握一门常用语言(C/C++, JAVA, P... 查看全部

职责:


1、负责云账户后台系统的设计、开发、测试和维护


要求:


1、2年以上工作经验,有分布式后台开发经验优先


2、有Golang开发经验优先,或熟悉掌握一门常用语言(C/C++, JAVA, PHP, PYTHON等),愿意转向Golang开发


3、熟悉关系型数据库(MySQL,PostgreSQL等)应用开发及常用性能优化技术


4、具备一定运维经验,能够开发运维友好的系统


5、熟悉一门脚本语言(Python, Perl或者Shell等)优先


6、具有较强责任心及执行力


公司福利:14薪;住房离公司近补贴;带薪年假;有竞争力的薪酬待遇;牛人共事;技术驱动;工程师文化;各种零食和茶歇活动;办公环境和设备好;各种节日活动福利;经常团建;年度体检;弹性工作时间;快乐工作氛围;桌游电竞休闲等等等等等。


坐标:朝阳区望京soho ~
有兴趣的伙伴们欢迎砸我的邮箱 ying.li@yunzhanghu.com

golang for语句完全指南

文章分享sheepbao 发表了文章 • 4 个评论 • 261 次浏览 • 2018-01-15 20:54 • 来自相关话题

以下所有观点都是个人愚见,有不同建议或补充的的欢迎emialaboutme
查看全部

以下所有观点都是个人愚见,有不同建议或补充的的欢迎emialaboutme

原文章地址


关于for语句的疑问

for语句的规范

for语句的内部实现-array

问题解答


关于for语句的疑问


我们都知道在golang中,循环语句只有for这一个,在代码中写一个循环都一般都需要用到for(当然你用goto也是可以的),
虽然golang的for语句很方便,但不少初学者一样对for语句持有不少疑问,如:



  1. for语句一共有多少种表达式格式?

  2. for语句中临时变量是怎么回事?(为什么有时遍历赋值后,所有的值都等于最后一个元素)

  3. range后面支持的数据类型有哪些?

  4. range string类型为何得到的是rune类型?

  5. 遍历slice的时候增加或删除数据会怎么样?

  6. 遍历map的时候增加或删除数据会怎么样?


其实这里的很多疑问都可以看golang编程语言规范,
有兴趣的同学完全可以自己看,然后根据自己的理解来解答这些问题。


for语句的规范


for语句的功能用来指定重复执行的语句块,for语句中的表达式有三种:

官方的规范: ForStmt = "for" [ Condition | ForClause | RangeClause ] Block .



  • Condition = Expression .

  • ForClause = [ InitStmt ] ";" [ Condition ] ";" [ PostStmt ] .

  • RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .


单个条件判断


形式:


for a < b {
f(doThing)
}
// or 省略表达式,等价于true
for { // for true {
f(doThing)
}

这种格式,只有单个逻辑表达式, 逻辑表达式的值为true,则继续执行,否则停止循环。


for语句中两个分号


形式:


for i:=0; i < 10; i++ {
f(doThing)
}
// or
for i:=0; i < 10; {
i++
f(doThing)
}
// or
var i int
for ; i < 10; {
i++
f(doThing)
}

这种格式,语气被两个分号分割为3个表达式,第一个表示为初始化(只会在第一次条件表达式之计算一次),第二个表达式为条件判断表达式,
第三个表达式一般为自增或自减,但这个表达式可以任何符合语法的表达式。而且这三个表达式,
只有第二个表达式是必须有的,其他表达式可以为空。


for和range结合的语句


形式:


for k,v := range []int{1,2,3} {
f(doThing)
}
// or
for k := range []int{1,2,3} {
f(doThing)
}
// or
for range []int{1,2,3} {
f(doThing)
}

用range来迭代数据是最常用的一种for语句,range右边的表达式叫范围表达式,
范围表达式可以是数组,数组指针,slice,字符串,map和channel。因为要赋值,
所以左侧的操作数(也就是迭代变量)必须要可寻址的,或者是map下标的表达式。
如果迭代变量是一个channel,那么只允许一个迭代变量,除此之外迭代变量可以有一个或者两个。


范围表达式在开始循环之前只进行一次求值,只有一个例外:如果范围表达式是数组或指向数组的指针,
至多有一个迭代变量存在,只对范围表达式的长度进行求值;如果长度为常数,范围表达式本身将不被求值。


每迭代一次,左边的函数调用求值。对于每个迭代,如果相应的迭代变量存在,则迭代值如下所示生成:


Range expression                          1st value          2nd value

array or slice a [n]E, *[n]E, or []E index i int a[i] E
string s string type index i int see below rune
map m map[K]V key k K m[k] V
channel c chan E, <-chan E element e E


  1. 对于数组、数组指针或是分片值a来说,下标迭代值升序生成,从0开始。有一种特殊场景,只有一个迭代参数存在的情况下,
    range循环生成0到len(a)的迭代值,而不是索引到数组或是分片。对于一个nil分片,迭代的数量为0。

  2. 对于字符串类型,range子句迭代字符串中每一个Unicode代码点,从下标0开始。在连续迭代中,下标值会是下一个utf-8代码点的
    第一个字节的下标,而第二个值类型是rune,会是对应的代码点。如果迭代遇到了一个非法的Unicode序列,那么第二个值是0xFFFD,
    也就是Unicode的替换字符,然后下一次迭代只会前进一个字节。

  3. map中的迭代顺序是没有指定的,也不保证两次迭代是一样的。如果map元素在迭代过程中被删掉了,那么对应的迭代值不会再产生。
    如果map元素在迭代中插入了,则该元素可能在迭代过程中产生,也可能被跳过,但是每个元素的迭代值顶多出现一次。如果map是nil,那么迭代次数为0。

  4. 对于管道,迭代值就是下一个send到管道中的值,除非管道被关闭了。如果管道是nil,范围表达式永远阻塞。


迭代值会赋值给相应的迭代变量,就像是赋值语句。

迭代变量可以使用短变量声明(:=)。这种情况,它们的类型设置为相应迭代值的类型,它们的域是到for语句的结尾,它们在每一次迭代中复用。
如果迭代变量是在for语句外声明的,那么执行之后它们的值是最后一次迭代的值。


var testdata *struct {
a *[7]int
}
for i, _ := range testdata.a {
// testdata.a is never evaluated; len(testdata.a) is constant
// i ranges from 0 to 6
f(i)
}

var a [10]string
for i, s := range a {
// type of i is int
// type of s is string
// s == a[i]
g(i, s)
}

var key string
var val interface {} // value type of m is assignable to val
m := map[string]int{"mon":0, "tue":1, "wed":2, "thu":3, "fri":4, "sat":5, "sun":6}
for key, val = range m {
h(key, val)
}
// key == last map key encountered in iteration
// val == map[key]

var ch chan Work = producer()
for w := range ch {
doWork(w)
}

// empty a channel
for range ch {}

for语句的内部实现-array


golang的for语句,对于不同的格式会被编译器编译成不同的形式,如果要弄明白需要看
golang的编译器和相关数据结构的源码,
数据结构源码还好,但是编译器是用C++写的,本人C++是个弱鸡,这里只讲array内部实现


// The loop we generate:
// len_temp := len(range)
// range_temp := range
// for index_temp = 0; index_temp < len_temp; index_temp++ {
// value_temp = range_temp[index_temp]
// index = index_temp
// value = value_temp
// original body
// }

// 例如代码:
array := [2]int{1,2}
for k,v := range array {
f(k,v)
}

// 会被编译成:
len_temp := len(array)
range_temp := array
for index_temp = 0; index_temp < len_temp; index_temp++ {
value_temp = range_temp[index_temp]
k = index_temp
v = value_temp
f(k,v)
}

所以像遍历一个数组,最后生成的代码很像C语言中的遍历,而且有两个临时变量index_temp,value_temp,
在整个遍历中一直复用这两个变量。所以会导致开头问题2的问题(详细解答会在后边)。


问题解答




  1. for语句一共有多少种表达式格式?

    这个问题应该很简单了,上面的规范中就有答案了,一共有3种:


    Condition = Expression .
    ForClause = [ InitStmt ] ";" [ Condition ] ";" [ PostStmt ] .
    RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .



  2. for语句中临时变量是怎么回事?(为什么有时遍历赋值后,所有的值都等于最后一个元素)

    先看这个例子:


    var a = make([]*int, 3)
    for k, v := range []int{1, 2, 3} {
    a[k] = &v
    }
    for i := range a {
    fmt.Println(*a[i])
    }
    // result:
    // 3
    // 3
    // 3

    for语句的内部实现-array可以知道,即使是短声明的变量,在for循环中也是复用的,这里的v一直
    都是同一个零时变量,所以&v得到的地址一直都是相同的,如果不信,你可以打印该地址,且该地址最后存的变量等于最后一次循环得到的变量,
    所以结果都是3。




  3. range后面支持的数据类型有哪些?

    共5个,分别是数组,数组指针,slice,字符串,map和channel




  4. range string类型为何得到的是rune类型?

    这个问题在for规范中也有解答,对于字符串类型,在连续迭代中,下标值会是下一个utf-8代码点的第一个字节的下标,而第二个值类型是rune。
    如果迭代遇到了一个非法的Unicode序列,那么第二个值是0xFFFD,也就是Unicode的替换字符,然后下一次迭代只会前进一个字节。


    其实看完这句话,我没理解,当然这句话告诉我们了遍历string得到的第二个值类型是rune,但是为什么是rune类型,而不是string或者其他类型?
    后来在看了Rob Pike写的blogStrings, bytes, runes and characters in Go
    才明白点,首先需要知道runeint32的别名,且go语言中的字符串字面量始终保存有效的UTF-8序列。而UTF-8就是用4字节来表示Unicode字符集。
    所以go的设计者用rune表示单个字符的编码,则可以完成容纳所表示Unicode字符。举个例子:


    s := `汉语ab`
    fmt.Println("len of s:", len(s))
    for index, runeValue := range s {
    fmt.Printf("%#U starts at byte position %d\n", runeValue, index)
    }
    // result
    // len of s: 8
    // U+6C49 '汉' starts at byte position 0
    // U+8BED '语' starts at byte position 3
    // U+0061 'a' starts at byte position 6
    // U+0062 'b' starts at byte position 7

    根据结果得知,s的长度是为8字节,一个汉子占用了3个字节,一个英文字母占用一个字节,而程序go程序是怎么知道汉子占3个字节,而
    英文字母占用一个字节,就需要知道utf-8代码点的概念,这里就不深究了,知道go是根据utf-8代码点来知道该字符占了多少字节就ok了。




  5. 遍历slice的时候增加或删除数据会怎么样?

    for语句的内部实现-array可以知道,获取slice的长度只在循环外执行了一次,
    该长度决定了遍历的次数,不管在循环里你怎么改。但是对索引求值是在每次的迭代中求值的,如果更改了某个元素且
    该元素还未遍历到,那么最终遍历得到的值是更改后的。删除元素也是属于更改元素的一种情况。


    在slice中增加元素,会更改slice含有的元素,但不会更改遍历次数。


    a2 := []int{0, 1, 2, 3, 4}
    for i, v := range a2 {
    fmt.Println(i, v)
    if i == 0 {
    a2 = append(a2, 6)
    }
    }
    // result
    // 0 0
    // 1 1
    // 2 2
    // 3 3
    // 4 4

    在slice中删除元素,能删除该元素,但不会更改遍历次数。


    // 只删除该元素1,不更改slice长度
    a2 := []int{0, 1, 2, 3, 4}
    for i, v := range a2 {
    fmt.Println(i, v)
    if i == 0 {
    copy(a2[1:], a2[2:])
    }
    }
    // result
    // 0 0
    // 1 2
    // 2 3
    // 3 4
    // 4 4

    // 删除该元素1,并更改slice长度
    a2 := []int{0, 1, 2, 3, 4}
    for i, v := range a2 {
    fmt.Println(i, v)
    if i == 0 {
    copy(a2[1:], a2[2:])
    a2 = a2[:len(a2)-2] //将a2的len设置为3,但并不会影响临时slice-range_temp
    }
    }
    // result
    // 0 0
    // 1 2
    // 2 3
    // 3 4
    // 4 4



  6. 遍历map的时候增加或删除数据会怎么样?

    规范中也有答案,map元素在迭代过程中被删掉了,那么对应的迭代值不会再产生。
    如果map元素在迭代中插入了,则该元素可能在迭代过程中产生,也可能被跳过。


    在遍历中删除元素


    m := map[int]int{1: 1, 2: 2, 3: 3, 4: 4, 5: 5}
    del := false
    for k, v := range m {
    fmt.Println(k, v)
    if !del {
    delete(m, 2)
    del = true
    }
    }
    // result
    // 4 4
    // 5 5
    // 1 1
    // 3 3

    在遍历中增加元素,多执行几次看结果


    m := map[int]int{1: 1, 2: 2, 3: 3, 4: 4, 5: 5}
    add := false
    for k, v := range m {
    fmt.Println(k, v)
    if !add {
    m[6] = 6
    m[7] = 7
    add = true
    }
    }
    // result1
    // 1 1
    // 2 2
    // 3 3
    // 4 4
    // 5 5
    // 6 6

    // result2
    // 1 1
    // 2 2
    // 3 3
    // 4 4
    // 5 5
    // 6 6
    // 7 7

    在map遍历中删除元素,将会删除该元素,且影响遍历次数,在遍历中增加元素则会有不可控的现象出现,有时能遍历到新增的元素,
    有时不能。具体原因下次分析。




参考


https://golang.org/ref/spec#For_statements

https://github.com/golang/go/wiki/Range

https://blog.golang.org/strings

基于go+vue实现的golang每日新闻数据浏览与检索平台

开源程序mikemintang 发表了文章 • 6 个评论 • 1087 次浏览 • 2018-01-09 16:58 • 来自相关话题

介绍

gonews是基于go+vue实现的golang每日新闻浏览与检索平台

介绍


gonews是基于go+vue实现的golang每日新闻浏览与检索平台



项目截图


gonews


部署



  • 获取新闻数据


git clone https://github.com/gocn/news /data/news


  • 获取源码


go get -u github.com/mikemintang/gonews


  • 解析数据


nohup gonews -d /data/news > /data/log/gonews.log 2>&1 


  • 启动Api


nohup gonews -a api -p 8017 > /data/log/gonews.log 2>&1 &


  • 前端部署


cd $GOPATH/src/github.com/mikemintang/gonews/web
npm install
npm run build


  • Nginx配置


server {
listen 80;
server_name gonews.idoubi.cc;
index index.html index.htm index.php;
root /data/go/src/mikemintang/gonews/web;

location /api {
rewrite ^.+api/?(.*)$ /$1 break;
proxy_pass http://127.0.0.1:8017;
}
}


  • Shell脚本


#!/bin/sh

cd /data/news
git pull origin master
nohup gonews -d /data/news/ > /data/log/gonews.log 2>&1


  • 定时任务


crontab -e
*/10 * * * * /bin/sh /data/shell/cache_news.sh

用到的技术


golang包



  • github.com/go-redis/redis

  • encoding/json

  • flag

  • net/http

  • net/url

  • strconv

  • sync

  • crypto/md5

  • fmt

  • io

  • io/ioutil

  • net/url

  • os

  • path/filepath

  • regexp

  • strconv

  • strings

  • time


npm包



  • vue

  • vuex

  • vue-router

  • axios

  • moment

  • mockjs


欢迎提交Pull Request

Redis 集群搭建及使用Golang示例

文章分享pathbox 发表了文章 • 5 个评论 • 397 次浏览 • 2018-01-05 11:55 • 来自相关话题

Redis 在3.x版本之后,自身支持了集群模式。Redis的集群主要是 master-slave的形式。集群定义了 16384个hash slot。这些hash slot分布在所有master上。查看全部

Redis 在3.x版本之后,自身支持了集群模式。Redis的集群主要是 master-slave的形式。集群定义了
16384个hash slot。这些hash slot分布在所有master上。we simply take the CRC16 of the key modulo 16384 将key计算得到对应的hash slot的值,然后看这个hash slot在哪个redis服务上,这个key就会保存在对应的这个redis服务。和Elasticsearch、MongoDB不一样,redis通过这种方式进行sharding。不能不说这是一种简单,但对redis来说是有效的一种集群方式。能够最大限度的保留原有redis的属性。
[Document]


下面是部署过程的例子。


有三台服务器 A、B、C。外网ip分别为 A_IP,B_IP,C_IP。


三台服务器先卸载原有redis版本,安装最新的redis版本


$ wget http://download.redis.io/relea ... ar.gz
$ tar xzf redis-4.0.6.tar.gz
$ cd redis-4.0.6
$ make

cd /src
cp redis-server /usr/bin/
cp redis-cli /usr/bin/
cp redis-trib.rb /usr/bin/

mkdir redis-cluster
cd redis-cluster
mkdir 7000
mkdir 7100

进入7000,新建redis.conf 文件,编写配置文件


cd 7000
vim redis.conf

配置内容为


port 7000                      # 绑定的端口
bind A_IP # 绑定的IP,如果你要开放出去,需要使用外网IP
cluster-enabled yes # 是否启用集群模式
cluster-config-file nodes.conf # 节点配置文件
cluster-node-timeout 5000 # 超时设置
appendonly yes # appendonly 配置 持久化设置
cluster-require-full-coverage no

cluster-require-full-coverage默认是yes。如果某个redis挂了,没有对应的slave升级为master,这时候,整个redis集群不可用。


所以建议设为no, 这样某个redis挂了,只是影响这一部分的hash slot查询有问题,不影响集群的其他redis的读写。


同理完成 7100的redis.conf配置文件内容。将三台机器都安装上述进行安装配置。


在A、B、C机器上


cd 7000
redis-server redis.conf
cd 7001
redis-server redis.conf

到A机器上, 执行


redis-trib.rb create --replicas 1 A_IP:7000 B_IP:7000 \
C_IP:7000 A_IP:7100 B_IP:7100 C_IP:7100

之后会提示输入yes,执行完后。


redis-cli -p 7000 -h A_IP cluster nodes

查看集群状态,会得到类似这样的信息。


aedabb4bd830978905d68ab8d88a94a031b515fe A_IP:7000@17000 master - 0 1513840545000 2 connected 5461-10922
861ff3beac7a6a3023424a64446f1101f8193d9d A_IP:7100@17100 slave 3d9e37599f6547387503e165d21838556435bac4 0 1513840544011 4 connected
3d9e37599f6547387503e165d21838556435bac4 B_IP:7000@17000 myself,master - 0 1513840545000 1 connected 0-5460
3ef82f387086931dbdedbd24b379671b8bd03289 C_IP:7000@17000 master - 0 1513840544411 3 connected 10923-16383
27764a4a521e4e5bd8e2e108c472ef6ed51645cd B_IP:7100@17100 slave aedabb4bd830978905d68ab8d88a94a031b515fe 0 1513840543912 5 connected
7f6ef6f05968ee869359c0771d42d768243e4ca1 C_IP:7100@17100 slave 3ef82f387086931dbdedbd24b379671b8bd03289 0 1513840545414 6 connected

Bingo~ redis集群服务就跑起来了。 三台机器,每台机器一个master,一个slave


redis集群增加节点或Resharding操作是使用redis-trib脚本命令,这个脚本是Ruby写的。每个node有一个node ID,比如: aedabb4bd830978905d68ab8d88a94a031b515fe


Golang 代码示例


package redis

import (
"time"

"github.com/go-redis/redis"
)

// redis数据超时时间
var Timeout = 5 * time.Hour

var Client = GetClusterClient()

func GetClusterClient() *redis.ClusterClient {
var client *redis.ClusterClient
client = redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{"A_IP:7000", "B_IP:7000", "C_IP:7000","A_IP:7100", "B_IP:7100", "C_IP:7100"},
})
err := client.Ping().Err()
if err == nil {
log.Info("Redis cluster OK")
} else {
log.Error("Redis cluster wrong")
}
return client
}

redis.Client.Set("word", "Hello World", redis.Timeout)
str, _ := rediscluster.Client.Get("word").Result()
fmt.Println(str) // => "Hello World"

分享一个带 TTL 以及失效回调的 LRU 库. https://github.com/Akagi201/kvcache

开源程序Akagi201 发表了文章 • 0 个评论 • 281 次浏览 • 2017-12-06 15:04 • 来自相关话题

https://github.com/Akagi201/kvcache

包含一个完整功能版本(每一个 TTL 的 key 用一个 gorou... 查看全部

https://github.com/Akagi201/kvcache


包含一个完整功能版本(每一个 TTL 的 key 用一个 goroutine 进行维护), 以及轻量级版本 (不使用 goroutine, 这样 TTL 时间计算不是很准确, 具体看代码逻辑)


完美达到我的业务需要的效果. 大家有同样的需求可以测试测试.


有问题一定向我反馈. akagi201@gmail.com

tsdump-用于导出数据库表结构的工具(支持导出为text、markdown、csv、json)

开源程序voidint 发表了文章 • 0 个评论 • 316 次浏览 • 2017-12-03 20:32 • 来自相关话题

项目地址: https://github.com/voidint/tsdump

特性

  • 支持将数据库(当前仅支持查看全部

项目地址: https://github.com/voidint/tsdump


特性



  • 支持将数据库(当前仅支持MySQL)及其表结构的元数据以textmarkdownjsoncsv形式输出。


安装


$ go get -u github.com/voidint/tsdump

基本使用




  • 全局选项


    GLOBAL OPTIONS:
    -H value, --host value Connect to host. (default: "127.0.0.1")
    -P value, --port value Port number to use for connection. (default: 3306)
    -u value, --user value User for login if not current user. (default: "voidint")
    -p value, --password value Password to use when connecting to server.
    -d value, --db value Database name.
    -V value, --viewer value Output viewer. Optional values: txt|csv|json|md (default: "txt")
    -o value, --output value Write to a file, instead of STDOUT.
    -D, --debug Enable debug mode.
    --help, -h show help
    --version, -v print the version



  • 使用root用户创建一个名为mydb的数据库实例,以及一张student的表。


    CREATE DATABASE IF NOT EXISTS `mydb` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

    USE `mydb`;

    CREATE TABLE `student` (
    `sno` char(8) NOT NULL COMMENT '学号',
    `sname` varchar(255) NOT NULL COMMENT '姓名',
    `gender` char(2) DEFAULT NULL COMMENT '性别',
    `native` char(20) DEFAULT NULL COMMENT '籍贯',
    `birthday` datetime DEFAULT NULL COMMENT '出生日期',
    `dno` char(6) DEFAULT NULL COMMENT '所在院系',
    `spno` char(8) DEFAULT NULL COMMENT '专业代码',
    `classno` char(4) DEFAULT NULL COMMENT '班级号',
    `entime` date DEFAULT NULL COMMENT '入校时间',
    `home` varchar(40) DEFAULT NULL COMMENT '家庭住址',
    `tell` varchar(40) DEFAULT NULL COMMENT '联系电话',
    PRIMARY KEY (`sno`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生信息表';



  • 将数据库及其表结构数据以表格形式输出到console


    $ tsdump -H 127.0.0.1 -P 3307 -u root -p "mypassword" --db mydb
    |----------|---------------|--------------------|
    | DATABASE | CHARACTER SET | COLLATION |
    |----------|---------------|--------------------|
    | mydb | utf8mb4 | utf8mb4_general_ci |
    |----------|---------------|--------------------|

    TABLE: student 学生信息表
    |----------|----------|--------------|---------------|--------------------|----------|
    | COLUMN | NULLABLE | DATA TYPE | CHARACTER SET | COLLATION | COMMENT |
    |----------|----------|--------------|---------------|--------------------|----------|
    | sno | NO | char(8) | utf8mb4 | utf8mb4_general_ci | 学号 |
    | sname | NO | varchar(255) | utf8mb4 | utf8mb4_general_ci | 姓名 |
    | gender | YES | char(2) | utf8mb4 | utf8mb4_general_ci | 性别 |
    | native | YES | char(20) | utf8mb4 | utf8mb4_general_ci | 籍贯 |
    | birthday | YES | datetime | | | 出生日期 |
    | dno | YES | char(6) | utf8mb4 | utf8mb4_general_ci | 所在院系 |
    | spno | YES | char(8) | utf8mb4 | utf8mb4_general_ci | 专业代码 |
    | classno | YES | char(4) | utf8mb4 | utf8mb4_general_ci | 班级号 |
    | entime | YES | date | | | 入校时间 |
    | home | YES | varchar(40) | utf8mb4 | utf8mb4_general_ci | 家庭住址 |
    | tell | YES | varchar(40) | utf8mb4 | utf8mb4_general_ci | 联系电话 |
    |----------|----------|--------------|---------------|--------------------|----------|



  • 将数据库及其表结构数据输出到markdown文件


    $ tsdump -H 127.0.0.1 -P 3307 -u root -p "mypassword" --db mydb -V md > ./mydb.md

    output:


    student


    学生信息表







































































































    COLUMN NULLABLE DATA TYPE CHARACTER SET COLLATION COMMENT
    sno NO char(8) utf8mb4 utf8mb4_general_ci 学号
    sname NO varchar(255) utf8mb4 utf8mb4_general_ci 姓名
    gender YES char(2) utf8mb4 utf8mb4_general_ci 性别
    native YES char(20) utf8mb4 utf8mb4_general_ci 籍贯
    birthday YES datetime 出生日期
    dno YES char(6) utf8mb4 utf8mb4_general_ci 所在院系
    spno YES char(8) utf8mb4 utf8mb4_general_ci 专业代码
    classno YES char(4) utf8mb4 utf8mb4_general_ci 班级号
    entime YES date 入校时间
    home YES varchar(40) utf8mb4 utf8mb4_general_ci 家庭住址
    tell YES varchar(40) utf8mb4 utf8mb4_general_ci 联系电话



  • 将数据库及其表结构数据输出到csv文件


    $ tsdump -H 127.0.0.1 -P 3307 -u root -p "mypassword" --db mydb -V csv -o ./mydb.csv


  • 将数据库及其表结构数据输出到JSON文件
    $ tsdump -H 127.0.0.1 -P 3307 -u root -p "mypassword" --db mydb -V json -o ./mydb.json

搭建轻量级的 Docker 容器云管理平台

开源程序bobliu0909 发表了文章 • 0 个评论 • 482 次浏览 • 2017-12-02 11:09 • 来自相关话题

什么是 Humpback?

Humpback 可以帮助企业快速搭建轻量级的 Docker 容器云管理平台,若将你的 Docker 主机接入到 Humpback 平台中,就能够为你带来更快捷稳定的容器操作体验。

查看全部

什么是 Humpback?


Humpback 可以帮助企业快速搭建轻量级的 Docker 容器云管理平台,若将你的 Docker 主机接入到 Humpback 平台中,就能够为你带来更快捷稳定的容器操作体验。


humpback架构


Humpback 功能特点


-Web操作,简单易用

-权限分组隔离

-容器升级与克隆

-容器监控

-容器日志

-集群容器调度

-集群弹性伸缩

-私有仓库


Humpback 模式介绍


-Single Mode 单一模式,对单组主机实现容器管理,提供容器创建,容器操作,容器重命名,容器升级与克隆,容器监控,容器日志输出等功能。

-Cluster Mode 容器集群模式,实现按实例数批量创建容器,容器调度,批量操作容器,升级和迁移等功能。


平台采用分组方式(Group)来管理多主机,多组之间权限操作隔离,同时也可以将一台主机加入到多个分组中交叉管理。


系统登录


Single Mode

SingleMode


Cluster Mode

ClusterMode


Container Monitor

ContainerMonitor


Container Logs

Container Logs


Container Detail

ContainerDetail


ContainerDetail


项目地址:https://humpback.github.io/humpback

授权协议:Apache

开发语言:TypeScript、Golang

操作系统:垮平台

GOLANG探测HTTP连接断开

技术讨论winlin 发表了文章 • 1 个评论 • 558 次浏览 • 2017-11-22 12:22 • 来自相关话题

考虑基于HTTP的RPC,或者HTTP服务器主动通知客户端的机制,就是HTTP Long-Polling,意思... 查看全部

考虑基于HTTP的RPC,或者HTTP服务器主动通知客户端的机制,就是HTTP Long-Polling,意思就是客户端发起一个长连接,服务器阻塞忍住不响应直到:



  1. 超时,比如5秒后,我们给客户端响应一个keepalive,意思是现在还没有啥事,请继续polling。

  2. 拿到结果,这个可能是任何时候,比如300毫秒、1100毫秒、2300毫秒拿到一个事件,响应给客户端,实现了有事件异步通知。


这样客户端和服务器之间RPC的效率就非常高,只有在有事件时才会通知。但是,实际上还有一种情况需要处理:



  1. 当客户端断开连接,比如客户端设置了3秒钟TCP请求超时,或者因为客户端Crash时OS回收了FD等等,这个时候服务器应该要终止polling事务,停止获取事件。因为如果这个时候获取了事件,那么如何处理这个事件?只能丢弃,如果客户端再次发起请求,就拿不到这个事件了。


问题就来了,如何在HTTP Handler中探测客户端断开?例如:


var incoming chan []byte
http.HandleFunc("/polling", func(w http.ResponseWriter, r *http.Request) {
select {
case b := <- incoming:
w.Write(b)
case <-time.After(5 * time.Second):
w.Write("keepalive")
// how to detect TCP disconnect event?
}
})

可能有以下方式:



  1. 读取r.Body,如果发现断开应该会有错误。

  2. 有朋友用reflect或hijack取到底层的TCPConn,然后Peek。

  3. w转换成http.CloseNotifier,在TCP连接关闭时拿到事件。


r.Body Read


这种方式是不靠谱的,假设没有Body内容,直接读取检测是否有error:


nn,err := io.Copy(ioutil.Discard, r.Body)

实际上返回的是nn=0err=nil,也就是没有Body,没有错误。因为这个读取的含义是指Request结束。


如果读取完Body后再读呢?收到的是io.EOF,在没有发送Response之前,Request已经结束了,所以就是io.EOF,并不能检测到底层TCP断开。


Peek TcpConn


使用reflect获取底层的TCPConn对象,是知道w http.ResponseWriter实际上是http.response


// A response represents the server side of an HTTP response.
type response struct {
conn *conn

它有个Field就是conn,再转成TCPConn就可以Peek。


这样做的风险就是,不同的GOLANG版本,可能会对底层实现进行变更,在升级时会有风险。


Reflect方式始终不是最好的。


另外,还有一种方式,就是用http hijack方式,这种方式虽然是http库提供的接口,但是很多地方注释都说hijack需要特殊处理,因此也不是最好的方式。参考When to use hijack


Close Notifier


在GO1.1提供了http.CloseNotifier接口,参考Close Notifier,但是也注意会有一些问题,参考net/http: CloseNotifier fails to fire when underlying connection is gone。用法如下:


var incoming chan []byte
http.HandleFunc("/polling", func(w http.ResponseWriter, r *http.Request) {
select {
case <- w.(http.CloseNotifier).CloseNotify():
fmt.Println("connection closed")
}
})

实际上,超时机制始终是需要的,加上之前的逻辑,考虑context.Context取消事件,http-long polling的完整实现应该是:


func polling(ctx context.Context, incoming chan []byte) {
http.HandleFunc("/polling", func(w http.ResponseWriter, r *http.Request) {
select {
case <- ctx.Done():
fmt.Println("system quit")
case b := <- incoming:
w.Write(b)
case <-time.After(5 * time.Second):
w.Write("keepalive")
case <- w.(http.CloseNotifier).CloseNotify():
fmt.Println("connection closed")
}
})
}

Gopher 深圳 meetup

线下活动mai_yang 发表了文章 • 1 个评论 • 422 次浏览 • 2017-11-16 09:13 • 来自相关话题

Gopher 深圳 meetup 开始报名啦,由小恩爱、PingCAP、随手记、史蒂夫软件的Go语言专家给大家带来 Go 语言实战经验和技巧分享,还有丰厚的奖品等你来拿,名额有限哦。

活动议程

13:00-13:30 签... 查看全部

Gopher 深圳 meetup 开始报名啦,由小恩爱、PingCAP、随手记、史蒂夫软件的Go语言专家给大家带来 Go 语言实战经验和技巧分享,还有丰厚的奖品等你来拿,名额有限哦。


活动议程


13:00-13:30 签到,微信墙交流


13:30-14:00 开场


14:00-14:55 《Go TCP Socket编程之teleport框架是怎样炼成的?》


14:55-15:50 《Go 开发游戏踩过的那些坑》


15:50-16:10 茶歇


16:10-17:05 《随手记 Go 实践:高并发积分系统改造》


17:05-18:00 《Go in TiDB》


18:00-18:10 抽奖交流,合影留念


时间地点


2017/12/16 13:30-18:20


深圳市南山区科发路1号富利臻大厦3F


分享话题




  • 《Go TCP Socket编程之teleport框架是怎样炼成的?》


    李亚川,Pholcus、Faygo、Teleport等Go语言开源项目作者。自2014年起全面转向Go语言的服务端开发领域。现就职于小恩爱,负责服务器底层架构与基础服务的研发工作。Github:https://github.com/henrylee2cn


    内容介绍:通过回顾开源项目teleport(通用、高效、灵活的TCP Socket框架)的开发历程,分享Go Socket的开发实战经验。




  • 《Go 开发游戏踩过的那些坑》


    庾俊:现任史蒂夫软件技术经理,原美国 Kabam 公司亚特兰蒂斯之龙族崛起游戏后端主程,该游戏三年收入超过一亿美金。


    内容介绍:带着大家一步一步的回顾我在创业三年以来,用 Go 开发游戏服务器过程中艰苦历程,并分享给你可能你从未踩过或者听过的一些坑,以及一些个人经验。




  • 《随手记 Go 实践:高并发积分系统改造》


    曾龙:随手记高级开发工程师


    内容介绍:随手记积分系统基于Go语言进行了改造,以支撑高并发、高性能的业务需求。本次分享主要介绍在改造过程中的实战经验、踩过的坑以及改造前后的对比提升,当然还有提升的经验。




  • 《Go in TiDB》


    姚维: PingCAP 资深数据库技术专家,华南区GM, 开源数据库中间件 360 Atlas 作者. mysql-sniffer作者.


    内容介绍:介绍 TiDB 如何用Go实现一个分布式的数据库SQL引擎, 以及在 TiDB 中利用的一些黑科技。




报名请前往:https://www.bagevent.com/event/979909

GoReporter第三版

开源程序fiisio 发表了文章 • 0 个评论 • 471 次浏览 • 2017-10-18 15:08 • 来自相关话题

GoReporter第三版重构了展示页面,分类更清晰,展示模型更多。可以作为白盒测试,CodeReview助手或者最佳实践评估工具。

欢迎大家使用和提出改进建议或者帮助完善功能! 查看全部

GoReporter第三版重构了展示页面,分类更清晰,展示模型更多。可以作为白盒测试,CodeReview助手或者最佳实践评估工具。


欢迎大家使用和提出改进建议或者帮助完善功能!
https://github.com/360EntSecGroup-Skylar/goreporter

链表以及golang介入式链表的实现

文章分享sheepbao 发表了文章 • 4 个评论 • 677 次浏览 • 2017-10-14 21:47 • 来自相关话题

链表以及golang介入式链表的实现

今天看tcp/ip协议栈的代码时看到一个双向链表,链表吗?听过它的顶顶大名,知道它是由节点构成的,每个节点还有个指针指向下一个节点,但是从来没自己实现过一个,没有实践就不能深刻理解,遂有此文。查看全部

链表以及golang介入式链表的实现


今天看tcp/ip协议栈的代码时看到一个双向链表,链表吗?听过它的顶顶大名,知道它是由节点构成的,每个节点还有个指针指向下一个节点,但是从来没自己实现过一个,没有实践就不能深刻理解,遂有此文。

以下所有观点都是个人愚见,有不同建议或补充的的欢迎emial我aboutme


何为链表?


链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)。

简单的说链表是一个具有逻辑顺序的线性表,每一个节点里存到下一个节点的指针。


图示


单链表


list1


双向链表


list2


链表有啥用?


因为链表插入很快,而且具有动态性,想添加几个元素就添加几个(内存空间足够),不像数组一样那么死板,正因为链表的灵活性,所有链表的用处是大大的有啊。

链表最适合用于频繁更新变化的数据,比如一个需要异步执行并且不可丢失的命令序列、一个需要进行实时加载与卸载的驱动,无序且数量未知,这个时候就需要链表结构来协助完成数据的管理。如果不需要过度关注数据的顺序,还可以用链表方便快捷地在任意一个地方插入或删除一个元素,并且不会影响到其它的元素。

又或者我在今天看tcp/ip源码中,链表用来构造队列,作为数据段的队列。我想链表用于队列应该是最多的。如果你看过linux内核源码,应该会发现linux内核中多处使用链表这种结构。


go标准库的双向链表


golang的标准库中实现了一个双向链表,该链表可以存储任何数据,先看看使用标准库链表的例子:


package list_test

import (
"container/list"
"fmt"
"testing"
)

func TestList(t *testing.T) {
// Create a new list and put some numbers in it.
l := list.New()
e4 := l.PushBack(4)
e1 := l.PushFront(1)
l.InsertBefore(3, e4)
l.InsertAfter(2, e1)

// Iterate through list and print its contents.
for e := l.Front(); e != nil; e = e.Next() {
fmt.Println(e.Value)
}
}
// output
// 1
// 2
// 3
// 4

该链表实现了链表所有的功能,链表的增删查改。


实现该链表的数据结构


// List represents a doubly linked list.
// The zero value for List is an empty list ready to use.
type List struct {
root Element // sentinel list element, only &root, root.prev, and root.next are used
len int // current list length excluding (this) sentinel element
}

// Element is an element of a linked list.
type Element struct {
// Next and previous pointers in the doubly-linked list of elements.
// To simplify the implementation, internally a list l is implemented
// as a ring, such that &l.root is both the next element of the last
// list element (l.Back()) and the previous element of the first list
// element (l.Front()).
next, prev *Element

// The list to which this element belongs.
list *List

// The value stored with this element.
Value interface{}
}

可以看到Element结构体看到了链表的结构,next,prev分别指向下一个和前一个元素的指针。Value就是链表中的数据段,可以理解为上图中的object。


介入式链表(intrusive list)


前面的链表都是普通链表,记得<<c语言程序设计>>上讲的链表也是一样,就是链表的节点指针和数据段是放在同一个struct,每实现一个不同的struct就得重新实现一遍链表的功能,这对于“懒惰”的程序员来说是不可忍受的,所以就出来了介入式链表,将数据段和链表的功能区别开来。最经典的例子莫过于linux内核的list_head,详情请看链接klist or Linked List in Linux Kernel,linux中是用c实现的,我想用go实现一个介入式链表。


实现代码


package list

type Intrusive interface {
Next() Intrusive
Prev() Intrusive
AddNext(Intrusive)
AddPrev(Intrusive)
}

// List provides the implementation of intrusive linked lists
type List struct {
prev Intrusive
next Intrusive
}

func (l *List) Next() Intrusive {
return l.next
}

func (l *List) Prev() Intrusive {
return l.prev
}

func (l *List) AddNext(i Intrusive) {
l.next = i
}

func (l *List) AddPrev(i Intrusive) {
l.prev = i
}

func (l *List) Reset() {
l.prev = nil
l.next = nil
}

func (l *List) Empty() bool {
return l.prev == nil
}

// Front returns the first element of list l or nil.
func (l *List) Front() Intrusive {
return l.prev
}

// Back returns the last element of list l or nil.
func (l *List) Back() Intrusive {
return l.next
}

// PushFront inserts the element e at the front of list l.
func (l *List) PushFront(e Intrusive) {
e.AddPrev(nil)
e.AddNext(l.prev)

if l.prev != nil {
l.prev.AddPrev(e)
} else {
l.next = e
}
l.prev = e
}

// PushBack inserts the element e at the back of list l.
func (l *List) PushBack(e Intrusive) {
e.AddNext(nil)
e.AddPrev(l.next)

if l.next != nil {
l.next.AddNext(e)
} else {
l.prev = e
}
l.next = e
}

// InsertAfter inserts e after b.
func (l *List) InsertAfter(e, b Intrusive) {
a := b.Next()
e.AddNext(a)
e.AddPrev(b)
b.AddNext(e)

if a != nil {
a.AddPrev(e)
} else {
l.next = e
}
}

// InsertBefore inserts e before a.
func (l *List) InsertBefore(e, a Intrusive) {
b := a.Prev()
e.AddNext(a)
e.AddPrev(b)
a.AddPrev(e)

if b != nil {
b.AddNext(e)
} else {
l.prev = e
}
}

// Remove removes e from l.
func (l *List) Remove(e Intrusive) {
prev := e.Prev()
next := e.Next()

if prev != nil {
prev.AddNext(next)
} else {
l.prev = next
}

if next != nil {
next.AddPrev(prev)
} else {
l.next = prev
}
}

我们这里用List表示实现了Intrusive接口,也实现了链表的基本功能,所以任何实现了Intrusive接口的对象都是可以作为链表的节点,利用这个介入式链表就很简单了,只要在现有的struct嵌入List这个结构体即可,在举个例子:


package list

import (
"container/list"
"fmt"
"testing"
)

func TestIntrusiveList(t *testing.T) {
type E struct {
List
data int
}
// Create a new list and put some numbers in it.
l := List{}
e4 := &E{data: 4}
e1 := &E{data: 1}
l.PushBack(e4)
l.PushFront(e1)
l.InsertBefore(&E{data: 3}, e4)
l.InsertAfter(&E{data: 2}, e1)

for e := l.Front(); e != nil; e = e.Next() {
fmt.Printf("e: %#v\n", e)
fmt.Printf("data: %#v\n", e.(*E).data)
}
}

E里嵌入List即可作为链表的节点,是不是很方便,其实当我写完介入式链表的栗子后,发现其实标准库的链表更方便,哈哈。。因为golang有interface{}


参考


https://blog.goquxiao.com/posts/2013/07/06/intrusive-list/

http://blog.nlogn.cn/linked-list-in-linux-kernel/

GOLANG实现的HTTP转HTTPS的代理

技术讨论winlin 发表了文章 • 0 个评论 • 584 次浏览 • 2017-10-13 12:17 • 来自相关话题

有时候需要将后端的HTTP服务,转成HTTPS,可以用一个代理。

Reamark: 如果是GOLANG的后端服务,可以直接用库go-oryx-lib/https

查看全部

有时候需要将后端的HTTP服务,转成HTTPS,可以用一个代理。



Reamark: 如果是GOLANG的后端服务,可以直接用库go-oryx-lib/https



这个代理支持自签名的证书,也支持letsencrypt的证书。



Remark: Letsencrypt只支持少量域名的情况,比如自己的网站,它会有请求次数限制,另外CA是letsencrypt的,商业用户不适合用。



我们有个HTTP API, SRS Version:


{
"code": 0,
"server": 12504,
"data": {
"major": 2,
"minor": 0,
"revision": 243,
"version": "2.0.243"
}
}

下面演示实现HTTPS的代理。


Self-sign Certificate


自签名证书可以用在测试中,先生成私钥server.key和证书server.crt


openssl genrsa -out server.key 2048 &&
openssl req -new -x509 -key server.key -out server.crt -days 365


Remark: 生成证书时会有很多提问,直接回车就好了。还可以参考openssl的文档,直接在命令行设置这些参数。



生成私钥和证书后,下载HTTPS代理:


go get github.com/ossrs/go-oryx/httpx-static


Remark: GOLANG的设置请参考GO环境配置


Note: 详细参数可以直接运行httpx-static程序不带参数,会显示help。



启动服务,代理到SRS Version:


sudo $GOPATH/bin/httpx-static -http 80 -https 443 \
-proxy http://ossrs.net:1985/api/v1/versions \
-ssc server.crt -ssk server.key

访问本机HTTP和HTTPS就可以:



  1. HTTP: http://localhost/api/v1/versions

  2. HTTPS: https://localhost/api/v1/versions



Remark: 浏览器访问自签名证书时,可能会提示不安全,选择高级然后继续浏览就可以了。



LetsEncrypt Certificate


可以使用letsencrypt签名的证书,在浏览器中会显示合法的绿色,不会提示有错误。参考:ossrs.net


ossrs.net也是使用httpx-static,参数如下:


sudo $GOPATH/bin/httpx-static -http 80 -https 443 \
-lets=true -domains ossrs.net


Remark: 注意在局域网的机器无法使用,因为ACME会有反向验证,也就是你的服务器得能在公网访问到。



Advance Proxy


如果需要代理所有的API怎么办呢?直接指定父目录就好,如果指定/则代理所有的请求。例如:


下面的命令,代理所有的/api请求:


sudo $GOPATH/bin/httpx-static -http 80 -https 443 \
-proxy http://ossrs.net:1985/api \
-ssc server.crt -ssk server.key

下面的命令,代理所有的请求,相当于做了镜像:


sudo $GOPATH/bin/httpx-static -http 80 -https 443 \
-proxy http://ossrs.net/ \
-ssc server.crt -ssk server.key

其他的参数请参考httpx-static的参数。

golang string和[]byte的对比

文章分享sheepbao 发表了文章 • 2 个评论 • 932 次浏览 • 2017-09-30 16:42 • 来自相关话题

golang string和[]byte的对比

为啥string和[]byte类型转换需要一定的代价?
为啥内置函数copy会有一种特殊情况copy(dst []byte, src string) int查看全部

golang string和[]byte的对比


为啥string和[]byte类型转换需要一定的代价?

为啥内置函数copy会有一种特殊情况copy(dst []byte, src string) int?

string和[]byte,底层都是数组,但为什么[]byte比string灵活,拼接性能也更高(动态字符串拼接性能对比)?

今天看了源码探究了一下。

以下所有观点都是个人愚见,有不同建议或补充的的欢迎emial我aboutme


何为string?


什么是字符串?标准库builtin的解释:


type string

string is the set of all strings of 8-bit bytes, conventionally but not necessarily representing UTF-8-encoded text. A string may be empty, but not nil. Values of string type are immutable.

简单的来说字符串是一系列8位字节的集合,通常但不一定代表UTF-8编码的文本。字符串可以为空,但不能为nil。而且字符串的值是不能改变的。

不同的语言字符串有不同的实现,在go的源码中src/runtime/string.go,string的定义如下:


type stringStruct struct {
str unsafe.Pointer
len int
}

可以看到str其实是个指针,指向某个数组的首地址,另一个字段是len长度。那到这个数组是什么呢?
在实例化这个stringStruct的时候:


func gostringnocopy(str *byte) string {
ss := stringStruct{str: unsafe.Pointer(str), len: findnull(str)}
s := *(*string)(unsafe.Pointer(&ss))
return s
}

哈哈,其实就是byte数组,而且要注意string其实就是个struct。


何为[]byte?


首先在go里面,byte是uint8的别名。而slice结构在go的源码中src/runtime/slice.go定义:


type slice struct {
array unsafe.Pointer
len int
cap int
}

array是数组的指针,len表示长度,cap表示容量。除了cap,其他看起来和string的结构很像。

但其实他们差别真的很大。


区别


字符串的值是不能改变


在前面说到了字符串的值是不能改变的,这句话其实不完整,应该说字符串的值不能被更改,但可以被替换。 还是以string的结构体来解释吧,所有的string在底层都是这样的一个结构体stringStruct{str: str_point, len: str_len},string结构体的str指针指向的是一个字符常量的地址,
这个地址里面的内容是不可以被改变的,因为它是只读的,但是这个指针可以指向不同的地址,我们来对比一下string、[]byte类型重新赋值的区别:


s := "A1" // 分配存储"A1"的内存空间,s结构体里的str指针指向这快内存
s = "A2" // 重新给"A2"的分配内存空间,s结构体里的str指针指向这快内存

其实[]byte和string的差别是更改变量的时候array的内容可以被更改。


s := []byte{1} // 分配存储1数组的内存空间,s结构体的array指针指向这个数组。
s = []byte{2} // 将array的内容改为2

因为string的指针指向的内容是不可以更改的,所以每更改一次字符串,就得重新分配一次内存,之前分配空间的还得由gc回收,这是导致string操作低效的根本原因。


string和[]byte的相互转换


将string转为[]byte,语法[]byte(string)源码如下:


func stringtoslicebyte(buf *tmpBuf, s string) []byte {
var b []byte
if buf != nil && len(s) <= len(buf) {
*buf = tmpBuf{}
b = buf[:len(s)]
} else {
b = rawbyteslice(len(s))
}
copy(b, s)
return b
}

func rawstring(size int) (s string, b []byte) {
p := mallocgc(uintptr(size), nil, false)

stringStructOf(&s).str = p
stringStructOf(&s).len = size

*(*slice)(unsafe.Pointer(&b)) = slice{p, size, size}

return
}

可以看到b是新分配的,然后再将s复制给b,至于为啥copy函数可以直接把string复制给[]byte,那是因为go源码单独实现了一个slicestringcopy函数来实现,具体可以看src/runtime/slice.go


将[]byte转为string,语法string([]byte)源码如下:


func slicebytetostring(buf *tmpBuf, b []byte) string {
l := len(b)
if l == 0 {
// Turns out to be a relatively common case.
// Consider that you want to parse out data between parens in "foo()bar",
// you find the indices and convert the subslice to string.
return ""
}
if raceenabled && l > 0 {
racereadrangepc(unsafe.Pointer(&b[0]),
uintptr(l),
getcallerpc(unsafe.Pointer(&buf)),
funcPC(slicebytetostring))
}
if msanenabled && l > 0 {
msanread(unsafe.Pointer(&b[0]), uintptr(l))
}
s, c := rawstringtmp(buf, l)
copy(c, b)
return s
}

func rawstringtmp(buf *tmpBuf, l int) (s string, b []byte) {
if buf != nil && l <= len(buf) {
b = buf[:l]
s = slicebytetostringtmp(b)
} else {
s, b = rawstring(l)
}
return
}

依然可以看到s是新分配的,然后再将b复制给s。

正因为string和[]byte相互转换都会有新的内存分配,才导致其代价不小,但读者千万不要误会,对于现在的机器来说这些代价其实不值一提。
但如果想要频繁string和[]byte相互转换(仅假设),又不会有新的内存分配,能有办法吗?答案是有的。


package string_slicebyte_test

import (
"log"
"reflect"
"testing"
"unsafe"
)

func stringtoslicebyte(s string) []byte {
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
bh := reflect.SliceHeader{
Data: sh.Data,
Len: sh.Len,
Cap: sh.Len,
}
return *(*[]byte)(unsafe.Pointer(&bh))
}

func slicebytetostring(b []byte) string {
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
sh := reflect.StringHeader{
Data: bh.Data,
Len: bh.Len,
}
return *(*string)(unsafe.Pointer(&sh))
}

func TestStringSliceByte(t *testing.T) {
s1 := "abc"
b1 := []byte("def")
copy(b1, s1)
log.Println(s1, b1)

s := "hello"
b2 := stringtoslicebyte(s)
log.Println(b2)
// b2[0] = byte(99) unexpected fault address

b3 := []byte("test")
s3 := slicebytetostring(b3)
log.Println(s3)
}

答案虽然有,但强烈推荐不要使用这种方法来转换类型,因为如果通过stringtoslicebyte将string转为[]byte的时候,共用的时同一块内存,原先的string内存区域是只读的,一但更改将会导致整个进程down掉,而且这个错误是runtime没法恢复的。


如何取舍?


既然string就是一系列字节,而[]byte也可以表达一系列字节,那么实际运用中应当如何取舍?



  • string可以直接比较,而[]byte不可以,所以[]byte不可以当map的key值。

  • 因为无法修改string中的某个字符,需要粒度小到操作一个字符时,用[]byte。

  • string值不可为nil,所以如果你想要通过返回nil表达额外的含义,就用[]byte。

  • []byte切片这么灵活,想要用切片的特性就用[]byte。

  • 需要大量字符串处理的时候用[]byte,性能好很多。


最后脱离场景谈性能都是耍流氓,需要根据实际场景来抉择。