分享一个质量很高的IT行业项目资源渠道平台

回复

huixinyunit 发起了问题 • 1 人关注 • 0 个回复 • 25 次浏览 • 2 小时前 • 来自相关话题

nxlog4go Simplest: Using for Testing Environment

ccpaging 发表了文章 • 0 个评论 • 76 次浏览 • 4 天前 • 来自相关话题

nxlog4go is very simple to use without any configuring, setting. For example:

查看全部
					

nxlog4go is very simple to use without any configuring, setting. For example:



package main

import (
"time"
log "github.com/ccpaging/nxlog4go"
)

func main() {
log.Fine("This should be omitted as default.")
log.Debug("The time is now: %s", time.Now().Format("15:04:05 MST 2006/01/02"))
log.Info("The time is now: %s", time.Now().Format("15:04:05 MST 2006/01/02"))
log.Warn("The time is now: %s", time.Now().Format("15:04:05 MST 2006/01/02"))
log.Critical("The time is now: %s", time.Now().Format("15:04:05 MST 2006/01/02"))
}

Compatibility with go log


The most programmer always use go log for testing. nxlog4go aimed the replacement of go log.
































Log Function Level After
Crash, Crashf CRITICAL, panic panic
Exit, Exitf ERROR, exit exit
Stderr, Stderrf ERROR
Stdout, Stdoutf INFO

New log functions




















































Log Function Level Default
Finest(...) 0 Omit
Fine(...) 1 Omit
Debug(...) 2
Trace(...) 3
Info(...) 4
Warn(...) 5
Error(...) 6
Critical(...) 7

Set display level


For example:


    log.GetLogger().SetLevel(log.FINE)
log.Fine("This should be not omitted now.")

log.GetLogger() return the point of default Global Logger.


SetLevel(log.FINE) sets level to log.FINE.

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

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

Go 语言内置类型研读


编程语言底层那些事儿


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

Go 内嵌静态文件工具 packr

xfstart07 发表了文章 • 0 个评论 • 437 次浏览 • 2018-02-09 18:38 • 来自相关话题

[Go] Go 内嵌静态文件工具 packr

介绍一个简单实用的 Go 内嵌静态文件工具 packr。

安装

$ go get -... 			查看全部
					

[Go] Go 内嵌静态文件工具 packr


介绍一个简单实用的 Go 内嵌静态文件工具 packr。


安装


$ go get -u github.com/gobuffalo/packr/...

使用


使用 packr 打包静态文件非常简单,通过创建一个 box.


// templates 是相对路径,例子是在同一个目录下
box := packr.NewBox("./templates")

// 以字符串的形式获取静态文件
html := box.String("index.html")
fmt.Println("String获取:", html)

html, err := box.MustString("index.html")
if err != nil {
log.Fatal(err)
}
fmt.Println("MustString获取", html)

// 以字节数组的形式获取静态文件
htmlByte := box.Bytes("index.html")
fmt.Println("Bytes: ", htmlByte)
// 对应的还有 MustBytes 方法

packr 在查找文件时的解析规则:



  • 在二进制文件中,在内存中查找文件

  • 开发时,在本地查找文件


在 HTTP 中使用


因为 box 实现了 http.FileSystem 接口,所有可以直接用来提供静态文件访问


package main

import (
"net/http"

"github.com/gobuffalo/packr"
)

func main() {
box := packr.NewBox("./templates")

http.Handle("/", http.FileServer(box))
http.ListenAndServe(":3000", nil)
}

使用 gorilla/mux 作为路由库,mux 也有提供静态文件访问的方式


r := mux.NewRouter()

box := packr.NewBox("./css")
r.PathPrefix("/css").Handler(http.StripPrefix("/css", http.FileServer(box)))

在渲染库(render)中使用


使用 unrolled/render 库来渲染资源,在初始化渲染选项中,将静态文件加入到 Asset 中。


var boxTemp = packr.NewBox("../templates")
var ren = render.New(render.Options{
Directory: "templates",
Asset: func(name string) ([]byte, error) {
// 返回指定路径名称的文件资源
return boxTemp.Bytes("index.html"), nil
},
AssetNames: func() []string {
// 静态文件的路径名称
return []string{"templates/index.html"}
},
Extensions: []string{".html"},
})

使用


ren.HTML(w, http.StatusOK, "index.html", "")

打包命令


使用命令建立二进制文件


packr build 包括了 go build


packr install 包括了 go install


还可以使用 go generate 命令来生成静态资源文件(.go),


package main

// 打包静态文件命令,生成 packr.go 文件
//go:generate packr

func main() {
Run()
}

然后运行命令


go generate && go build

资源


https://github.com/gobuffalo/packr


https://github.com/unrolled/render


https://github.com/gorilla/mux

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

jjjjerk 发表了文章 • 0 个评论 • 196 次浏览 • 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 restful 框架之 go-swagger

hatlonely 发表了文章 • 0 个评论 • 245 次浏览 • 2018-02-09 02:53 • 来自相关话题

restful 是这些年的高频词汇了,各大互联网公司也都纷纷推出了自己的 restful api,其实 restful 和 thrift,grpc 类似,就是一种协议,但是这种协议有点特殊的就是使用 http 接口,返回的对象一般是 json 格式,这样... 查看全部

restful 是这些年的高频词汇了,各大互联网公司也都纷纷推出了自己的 restful api,其实 restful 和 thrift,grpc 类似,就是一种协议,但是这种协议有点特殊的就是使用 http 接口,返回的对象一般是 json 格式,这样有个好处,就是可以供前端的 js 直接调用,使用非常方便,但 http 本身并不是一个高效的协议,后端的内部通信还是使用 grpc 或者 thrift 可以获得更高的性能


其实如果只是要用 http 返回 json 本身并不是一件很难的事情,不用任何框架,golang 本身也能很方便做到,但是当你有很多 api 的时候,这些 api 的维护和管理就会变得很复杂,你自己都无法记住这些 api 应该填什么参数,返回什么,当然你可以花很多时间去维护一份接口文档,这样不仅耗时而且很难保证文档的即时性,准确性以及一致性


swagger 有一整套规范来定义一个接口文件,类似于 thrift 和 proto 文件,定义了服务的请求内容和返回内容,同样也有工具可以生成各种不同语言的框架代码,在 golang 里面我们使用 go-swagger 这个工具,这个工具还提供了额外的功能,可以可视化显示这个接口,方便阅读


下面通过一个例子来简单介绍一下这个框架的使用,还是之前的点赞评论系统:https://github.com/hatlonely/microservices


go-swagger 使用方法


api 定义文件


首先需要写一个 api 定义文件,这里我只展示其中一个接口 countlike,请求中带有某篇文章,返回点赞的次数


paths:
/countlike:
get:
tags:
- like
summary: 有多少赞
description: ''
operationId: countLike
consumes:
- application/json
produces:
- application/json
parameters:
- name: title
in: query
description: 文章标题
required: true
type: string
responses:
'200':
description: 成功
schema:
$ref: '#/definitions/CountLikeModel'
'500':
description: 内部错误
schema:
$ref: '#/definitions/ErrorModel'
definitions:
CountLikeModel:
type: object
properties:
count:
type: integer
title:
type: string
example: golang json 性能分析
ErrorModel:
type: object
properties:
message:
type: string
example: error message
code:
type: integer
example: 400

这个是 yaml 语法,有点像去掉了括号的 json


这里完整地定义了请求方法、请求参数、正常返回接口、异常返回结果,有了这个文件只需要执行下面命令就能生成框架代码了


swagger generate server -f api/comment_like/comment_like.yaml

还可以下面这个命令可视化查看这个接口文件


swagger serve api/comment_like/comment_like.yaml

这个命令依赖 swagger 工具,可以通过下面命令获取


Mac


brew tap go-swagger/go-swagger
brew install go-swagger

Linux


go get -u github.com/go-swagger/go-swagger/cmd/swagger
export PATH=$GOPATH/bin:$PATH

执行完了之后,你发现多了几个文件夹,其中 cmd 目录里面包含 main 函数,是整个程序的入口,restapi 文件夹下面包含协议相关代码,其中 configure_xxx.go 是需要特别关注的,你需要在这个文件里面实现你具体的业务逻辑


现在你就其实已经可以运行程序了,go run cmd/comment-like-server/main.go,在浏览器里面访问一下你的 api,会返回一个错误信息,告诉你 api 还没有实现,下面就来实现一下吧


业务逻辑实现


api.LikeCountLikeHandler = like.CountLikeHandlerFunc(func(params like.CountLikeParams) middleware.Responder {
count, err := comment_like.CountLike(params.Title)
if err != nil {
return like.NewCountLikeInternalServerError().WithPayload(&models.ErrorModel{
Code: http.StatusInternalServerError,
Message: err.Error(),
})
}
return like.NewCountLikeOK().WithPayload(&models.CountLikeModel{
Count: count,
Title: params.Title,
})
})

你只需要在这些 handler 里面实现自己的业务逻辑即可,这里对协议的封装非常好,除了业务逻辑以及打包返回,没有多余的逻辑


再次运行,现在返回已经正常了


统一处理


如果你对请求有一些操作需要统一处理,比如输出统一的日志之类的,可以重写这个函数,也在 configure_xxx.go 这个文件中


func setupGlobalMiddleware(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
handler.ServeHTTP(w, r)
})
}

这里我统一设置了一下头部,解决跨域访问问题


参考链接




转载请注明出处
本文链接:http://hatlonely.github.io/2018/02/08/golang-restful-%E6%A1%86%E6%9E%B6%E4%B9%8B-go-swagger/


Traefik 一个反向代理的新工具

wangxingge 发表了文章 • 0 个评论 • 252 次浏览 • 2018-02-08 14:57 • 来自相关话题

Traefik

由于工作需要最近试用了几个反向路由的开源项目,Traefik就是其中之一。

一,Traefik 是干什么用的

简单来说它就是用来作反向代理和负载均衡的,比较适用于微服务化的场景,支持多种分... 查看全部

Traefik


由于工作需要最近试用了几个反向路由的开源项目,Traefik就是其中之一。


一,Traefik 是干什么用的


简单来说它就是用来作反向代理和负载均衡的,比较适用于微服务化的场景,支持多种分布式的Key-Value存储系统,支持容器技术,下面这个图诠释了它的工作。

image




二,Traefik 的特性


这些特性都是官方自己说的,再加上个人的一点通俗唱法解释。



  • 我很快 --- 经过测试吧,我感觉是速度一般般,但对于新产品来说速度做的还不错。

  • 安装简单 --- 不需要任何依赖,只是一个单独的可执行文件。(安装Traefik真的是简单,这操作给满分。)

  • 镜像很小 --- 有一个很小的官方 Docker Image。(可执行文件43MB,镜像只有45MB,评什么扣分。)

  • RestApi --- 支持Rest-API。(中规中矩。)

  • 配置热更新 --- 无需重启即可应用最新的配置。(其实这里说的配置只是动态的路由配置。)

  • 熔断机制 --- 对于后端服务的保护上支持熔断和重试机制。

  • 负载均衡 --- 负载策略内置两种,Weighted Round-Robin(wrr)和Dynamic Round-Robin(wrr)。

  • 支持Docker --- Docker, Swarm, Kubernetes, Marathon, Mesos。

  • 支持统计 --- Rest, Prometheus, Datadog, Statd。

  • 支持KV --- 支持多种分布式K-V系统,Zookeeper, Consul, Etcd, ECS等。

  • 多种通信协议 --- HTTP/1.1, HTTP/2, Websocket, GRPC等。

  • 支持ACME和HA --- 个人感觉Traefik的HA也就在ACME的场景下才有用。

  • Web-UI --- 有个AngularJS的Web-UI




三,Traefik 的基础组件


就两个组件,就这么简单,支持自己写 middle-ware。



  • Traefik

    Traefik 的主程序,启动时可以指定配置文件,
    ## 启动方式
    ## 默认的文件名是traefik.toml
    ## 寻址文件优先级是1. /etc/traefik, 2. $HOME/.traefik/ -->, 3. working directory.
    traefik --configFile=foo/bar/myconfigfile.toml

  • Dashboard

    一个简单的Dashboard, 可以看当前的路由规则,和转发的结果统计。


四, 配置文件如何使用


Traefik 的配置分为静态配置动态配置两大类。



  1. 动态配置:用来控制路由和负载均衡策略,动态配置不需要重起Traefik就可以生效。

  2. 静态配置:简单的说吧除了动态配置的其他均为静态配置范畴,静态配置需要重启Traefik才能生效。


配置详细说明我就不写了,到官网上找你需要的配置是最明智的(我是明智的官网)。

但是在后面的的练习中会说明部分配置的意义。


注意点



  1. 动态配置可以和静态配置一起在同一个文件里,动态配置写在文件的最后。

  2. 如果想用配置文件来指定路由规则的话,需要将动态配置和静态配置文件分开,如下
    ## 在  "静态配置的最后面"  加入下面信息来指定动态配置文件
    [file]
    watch = true
    filename = "rules.toml"





五,实战


业务需求


  1. 公司有多条产品线,但是每条产品线的负载压力不同,压力由高到低排序为, Mobile--> Web --> PC。

  2. 公司控制成本考虑,要求使用实体服务器,虚拟机或者容器技术来混合部署服务。

  3. 要求可以秒级的动态增加服务和减少服务来应对业务的压力。


画个简单的图来表示

image




基础环境


回顾一下上面的缩略图,发现我们需要下列基本的元素。

我们需要有一个Docker环境,需要有一套分布式K-V系统,K-V系统选择etcd。



  • [x] Real-Machine,用虚拟机来作RealMachine。

  • [x] Docker

  • [x] Etcd-Cluster

  • [x] Traefik

  • [x] Demo-Service

    Question



    1. 手上没有docker和etcd-cluster怎么办?

      Answer: 伸手过来我教你

    2. 不想用etcd,因为公司项目没用这个K-V.

      Answer: 你可以更换到任何一款主流的K-V系统,因为Traefik支持很多种K-V。




DemoService

直接从docker-hub上 pull 已经写好的Image就好了。


docker pull wangxingge/simple-web

Traefik

直接从docker-hub 上 pull下来。


docker pull traefik

货全了,开始搭建配置环境。




Traefik启动与配置文件



  • 创建docker-compose.yml 文件。docker-compose读取yml文件来启动镜像。


#docker-compose.yml

traefik:
image: traefik
command: --logLevel=DEBUG
ports:
- "80"
- "8080"
- "443"
## 端口由docker 自由分配,但是需要指定容器需要多少端口,如果要指定宿主机端口的话就写成
## 80 默认的http端口,443 默认的https端口,8080默认的后台管理页面端口
## ports:
## - "80:80"
## - "8080:8080"
## - "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /opt/traefik/traefik.toml:/traefik.toml
- /opt/traefik/rules.toml:/rules.toml
## 磁盘映射,主要是为了映射配置文件和让traefik读取docker的网络配置
## traefik.toml 是traefik的静态配置文件
## rules.toml 是traefik的动态配置文件,也是路由配置


  • 创建traefik.toml配置文件。

    由于需求是秒级增加服务节点,所以路由配置信息我们不使用 rules.toml的形式提供,而使用分布式K-V来提供。

    在K-V 配置中设置exposebydefault = false 目的是为了不让启动容器时自动添加路由。


#traefik.toml

################################################################
# Common configuration
################################################################
debug=true
logLevel="DEBUG"
[web]
address = ":8080"
ProvidersThrottleDuration = 100000000

################################################################
# Docker configuration backend
################################################################
[docker]
domain = "docker.local"
watch = true
exposedbydefault = false

################################################################
# kv store
################################################################
[etcd]
endpoint = "192.168.196.88:12379"
watch = true
prefix = "traefik"



  • 启动Traefik


    # 使用docker-compose 目的是方便起停批量的镜像
    docker-compose up -d


  • 验证Traefik启动成功,查看docker镜像和后面管理页面是否可用
    [root@centos7 traefik]# docker-compose ps
    Name Command State Ports
    ------------------------------------------------------------------------------------------------------------------------------
    traefik_traefik_1 /traefik --logLevel=DEBUG Up 0.0.0.0:32772->443/tcp, 0.0.0.0:32773->80/tcp, 0.0.0.0:32771->8080/tcp


Traefik后台页面


配置路由信息


前面说明了要使用分布式K-V来进行路由信息的配置,我们选择etcd-cluster。

Traefik的路由配置一共分为2大部分,FrontEnd和 BackEnd。


- Frontend

主要控制访问的路由规则,有三个主要控制方式:Header, Host, Path,都支持指定单个规则和按正则匹配。

匹配方式为某些规则的请求转发到某个Backend上。


- Backend

进行某个Backend匹配某一组服务,控制方式有:轮询控制,断路器控制(熔断),压力控制和健康检查。



  • 实战我们模拟的Frontend和Backend要求为。

  • Frontend: 分别根据Host和Url区分路由。

  • Backend: 三组服务分别是 mobile, web, pc, 轮询方式为wrr,加入健康检查,熔断控制使用最大数量限制。

  • 具体配置如下: 只需要定义frontend,backend不需要定义服务启动时会自动注册Backend.




































































































































Alias Path Value
1 /traefik_configurations/1/frontends/front_web/priority 3
1 /traefik_configurations/1/frontends/front_web/passHostHeader true
1 /traefik_configurations/1/frontends/front_web/backend backend_web
1 /traefik_configurations/1/frontends/front_web/routes/test_1/rule Host:web.eastmoney.com
1 /traefik_configurations/1/frontends/front_mobile/priority 3
1 /traefik_configurations/1/frontends/front_mobile/passHostHeader true
1 /traefik_configurations/1/frontends/front_mobile/backend backend_mobile
1 /traefik_configurations/1/frontends/front_mobile/routes/test_1/rule Host:mobile.eastmoney.com
1 /traefik_configurations/1/frontends/front_pc/priority 3
1 /traefik_configurations/1/frontends/front_pc/passHostHeader true
1 /traefik_configurations/1/frontends/front_pc/backend backend_pc
1 /traefik_configurations/1/frontends/front_pc/routes/test_1/rule Host:pc.eastmoney.com
2 /traefik_configurations/2/frontends/front_web/priority 3
2 /traefik_configurations/2/frontends/front_web/passHostHeader true
2 /traefik_configurations/2/frontends/front_web/backend backend_web
2 /traefik_configurations/2/frontends/front_web/routes/test_1/rule Path:/web_root/web/{subdomain:[a-z]+}
2 /traefik_configurations/2/frontends/front_mobile/priority 3
2 /traefik_configurations/2/frontends/front_mobile/passHostHeader true
2 /traefik_configurations/2/frontends/front_mobile/backend backend_mobile
2 /traefik_configurations/2/frontends/front_mobile/routes/test_1/rule Path:/web_root/web/{subdomain:[a-z]+}
2 /traefik_configurations/2/frontends/front_pc/priority 3
2 /traefik_configurations/2/frontends/front_pc/passHostHeader true
2 /traefik_configurations/2/frontends/front_pc/backend backend_pc
2 /traefik_configurations/2/frontends/front_pc/routes/test_1/rule Path:/web_root/web/{subdomain:[a-z]+}

启动后台服务


根据目前的需要后台服务的运行环境有两种:测试过程我们使用Docker的环境方便一些。






















宿主 启动方式 优缺点
Docker 使用docker-compose 再用labels 指定路由信息 方便动态调整服务数量,不需要修改路由配置。
实体机 直接启动直接读取K-V的路由信息 需要服务启动时到K-V中进行注册之后路由才会生效,一个服务使用一台服务器产生资源浪费。


  • 启动后台服务
    业务说明了有三条主要业务线:mobile, pc, web,为了方便那我们分别为每个业务线创建一个docker-compose要用的配置文件。

    web:
    image: wangxingge/simple-web:latest
    command: /opt/simple-web --kvaddr="http://192.168.196.88:12379" --kv=true --backend="backend_web" -watch="/traefik/alias"
    ports:
    - "80"


mobile:
image: wangxingge/simple-web:latest
command: /opt/simple-web --kvaddr="http://192.168.196.88:12379" --kv=true --backend="backend_mobile" -watch="/traefik/alias"
ports:



  • "80"


pc:
image: wangxingge/simple-web:latest
command: /opt/simple-web --kvaddr="http://192.168.196.88:12379" --kv=true --backend="backend_pc" -watch="/traefik/alias"
ports:



  • "80"

    /opt/simple-web 启动程序


    kvaddr: K-V系统的地址


    kv: 是否使用K-V系统


    backend: 当前程序使用哪些backend, backend_web 表示当前服务为WEB业务提供服务。


    watch: 把traefik.toml中的K-V项的prefix再加上/alias就可以了,目的是为了配合traefik进行动态注册


    根据当前的线上压力情况,我们启动5个Mobile, 3个Web, 2个PC的后台服务。

    docker-compose up -d
    docker-compose scale web=5
    docker-compose scale mobile=3
    docker-compose scale pc=2



    启动和配置成功以后观查一下Traefik的Web页面
    ![image](http://ogmbad78f.bkt.clouddn.c ... _1.png)


测试路由设置是否可用,测试步骤如下



  1. 在使用1号配置的时候按照Host进行路由。

  2. 使用Postman并设置相应的Host值进行请求,并观察返回结果。

  3. 切换至2号配置。

  4. 使用Postman并根据想应的URL进行请求,并观察返回结果。

  5. 动态添加和缩减容器数量,docker-compose scale serviceName=serviceCount

  6. 观察Traefik的后台管理页面,是否已经更新相应的路由。

  7. 使用Postman请求,观查是否后端相应的服务器数量是否已更新。

从零开始学golang之Bellman

回复

freedbg 发起了问题 • 1 人关注 • 0 个回复 • 267 次浏览 • 2018-02-05 23:59 • 来自相关话题

golang 网络框架之 grpc

hatlonely 发表了文章 • 0 个评论 • 318 次浏览 • 2018-02-03 21:59 • 来自相关话题

grpc 是 google 开源的一款网络框架,具有极好的性能,可能是目前性能最好的网络框架,支持流式 rpc,可以很方便地构建消息订阅发布系统,支持几乎所有主流的语言,使用上面也很简单,公司很多服务基于 grpc 框架构建,运行非常稳定

... 查看全部

grpc 是 google 开源的一款网络框架,具有极好的性能,可能是目前性能最好的网络框架,支持流式 rpc,可以很方便地构建消息订阅发布系统,支持几乎所有主流的语言,使用上面也很简单,公司很多服务基于 grpc 框架构建,运行非常稳定


开始之前首先你要知道网络框架为你做了哪些事情:




  1. 网络协议序列化与反序列化

  2. 网络底层通信

  3. 并发管理



以及需要你做哪些事情:




  1. 定义通信的内容(通过协议文件)

  2. 实现通信的方法(实现协议接口)



以下面两个例子来分别说明两种 rpc 服务的简单用法


下面使用的完整代码下列地址:
实现文件:https://github.com/hatlonely/hellogolang/tree/master/cmd/grpc
协议文件:https://github.com/hatlonely/hellogolang/tree/master/api


简单 echo 服务


要实现的这个服务很简单,功能和 echo 命令类似,用一个字符串请求服务器,返回相同的字符串


获取 grpc


go get google.golang.org/grpc
go get google.golang.org/genproto/

go get 上面两个库就可以了。可能被墙了,需要 vpn;如果没有 vpn,可以找一台能下载的服务器下载下来再传到本地;如果也没有服务器,可以点击这里下载,解压后放到 vendor/ 目录下即可,不过可能不是最新版本


定义协议文件


首先要定义通信的协议,grpc 使用的是 proto3 序列化协议,这是一个高效的协议,关于这个协议的跟多内容可以参考下面链接:https://developers.google.com/protocol-buffers/docs/proto3


syntax = "proto3";

package echo;

message EchoReq {
string msg = 1;
}

message EchoRes {
string msg = 1;
}

service Echo {
rpc echo (EchoReq) returns (EchoRes);
}

执行如下命令会自动生成 echo.pb.go 文件,这个过程其实是把上面这个协议翻译成 golang:


protoc --go_out=plugins=grpc:. echo.proto

实际项目中可以把这个命令放到一个 Makefile 文件中,执行 make 命令即可生成代码:


上面命令依赖 protoc 工具,以及 golang 插件 protoc-gen-go,可以通过如下命令获取


Mac


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

Linux


wget https://github.com/google/prot ... ar.gz
tar -xzvf protobuf-cpp-3.2.0.tar.gz
cd protobuf-3.2.0
./configure --prefix=${output}
make -j8
[sudo] make install
go get -u github.com/golang/protobuf/{proto,protoc-gen-go}

实现协议接口


type EchoServerImp struct {

}

func (e *EchoServerImp) Echo(ctx context.Context, req *echo.EchoReq) (*echo.EchoRes, error) {
fmt.Printf("message from client: %v\n", req.GetMsg())

res := &echo.EchoRes{
Msg: req.GetMsg(),
}

return res, nil
}

首先要定义一个接口的实现类 EchoServerImp,接口的的定义可以在上面生成的文件 echo.pb.go 中找到,这个类里面也可以有一些和业务逻辑相关的成员变量,这里我们的需求比较简单,没有其他的成员


然后需要在接口函数里面实现我们具体的业务逻辑,这里仅仅把请求里面的内容读出来,再写回到响应里面


你还可以为这个类增加其他的函数,比如初始化之类的,根据你具体的业务需求就好


实现服务端


func main() {
server := grpc.NewServer()
echo.RegisterEchoServer(server, &EchoServerImp{})

address, err := net.Listen("tcp", ":3000")
if err != nil {
panic(err)
}

if err := server.Serve(address); err != nil {
panic(err)
}
}

把我们刚刚实现的类实例注册到 grpc 里,再绑定到本地的一个端口上就可以了,现在可以启动服务了 go run echo_server.go


实现客户端


func main() {
conn, err := grpc.Dial("127.0.0.1:3000", grpc.WithInsecure())
if err != nil {
fmt.Errorf("dial failed. err: [%v]\n", err)
return
}

client := echo.NewEchoClient(conn)
res, err := client.Echo(context.Background(), &echo.EchoReq{
Msg: strings.Join(os.Args[1:], " "),
})

if err != nil {
fmt.Errorf("client echo failed. err: [%v]", err)
return
}

fmt.Printf("message from server: %v", res.GetMsg())
}

创建一个 client 之后,就可以像访问本地方法一样访问我们的服务了,go run echo_client.go hellogrpc


流式 rpc 服务


实现一个 counter 服务,客户端传过来一个数字,服务端从这个数字开始,不停地向下计数返回


定义协议文件


syntax = "proto3";

package counter;

message CountReq {
int64 start = 1;
}

message CountRes {
int64 num = 1;
}

service Counter {
rpc count (CountReq) returns (stream CountRes);
}

定义一个流式的 rpc 只需要在返回的字段前加一个 stream 关键字就可以


实现服务端


type CounterServerImp struct {

}

func (c *CounterServerImp) Count(req *counter.CountReq, stream counter.Counter_CountServer) error {
fmt.Printf("request from client. start: [%v]\n", req.GetStart())

i := req.GetStart()
for {
i++
stream.Send(&counter.CountRes{
Num: i,
})
time.Sleep(time.Duration(500) * time.Millisecond)
}

return nil
}

func main() {
server := grpc.NewServer()
counter.RegisterCounterServer(server, &CounterServerImp{})

address, err := net.Listen("tcp", ":3000")
if err != nil {
panic(err)
}

if err := server.Serve(address); err != nil {
panic(err)
}
}

接口实现上需要写一个死循环,不停地调用 Send 函数返回结果即可


实现客户端


func main() {
start, _ := strconv.ParseInt(os.Args[1], 10, 64)

conn, err := grpc.Dial("127.0.0.1:3000", grpc.WithInsecure())
if err != nil {
fmt.Errorf("dial failed. err: [%v]\n", err)
return
}
client := counter.NewCounterClient(conn)

stream, err := client.Count(context.Background(), &counter.CountReq{
Start: start,
})
if err != nil {
fmt.Errorf("count failed. err: [%v]\n", err)
return
}

for {
res, err := stream.Recv()
if err != nil {
fmt.Errorf("client count failed. err: [%v]", err)
return
}

fmt.Printf("server count: %v\n", res.GetNum())
}
}

客户端的 Count 接口返回的是一个 stream,不断地调用这个 streamRecv 方法,可以不断地获取来自服务端的返回


参考链接




转载请注明出处
本文链接:http://hatlonely.github.io/2018/02/03/golang-%E7%BD%91%E7%BB%9C%E6%A1%86%E6%9E%B6%E4%B9%8B-grpc/


GopherChina 大会早鸟票最后一天

astaxie 发表了文章 • 0 个评论 • 654 次浏览 • 2018-01-31 09:44 • 来自相关话题

今年我们针对Gopher们的要求,早鸟票延长了两周,回馈给更多的Gopher,今天是最后一天,如果你是个人,那么抓紧时间报名,如果是公司报销的话,无所谓早晚了,后面也不着急。哈哈。

报名地址:查看全部

今年我们针对Gopher们的要求,早鸟票延长了两周,回馈给更多的Gopher,今天是最后一天,如果你是个人,那么抓紧时间报名,如果是公司报销的话,无所谓早晚了,后面也不着急。哈哈。


报名地址:https://www.bagevent.com/event/1086224

从零开始学golang之 Prim

freedbg 发表了文章 • 0 个评论 • 212 次浏览 • 2018-01-27 00:05 • 来自相关话题

package main

import (
    "container/list"
    "fmt"
)

const MAX_SIZ... 			查看全部
					
package main

import (
"container/list"
"fmt"
)

const MAX_SIZE int = 5

//为了看上去 好一些
const MAX_VALUE int = 9

func main() {
fmt.Println("Prim")
var gg Graph
var vexs = []string{"B", "A", "C", "D", "E"}
gg.vexnum = 5
gg.vexs = vexs

for i := 0; i < len(vexs); i++ {
for j := 0; j < len(vexs); j++ {
gg.matrix[i][j] = MAX_VALUE
}
}
initGG(&gg)
fmt.Println(gg.vexs)
fBFS(&gg)
fDFS(&gg)

//listgg := list.New()
prim(&gg, 0)
PrintG(gg, len(vexs))
}

func PrintG(gg Graph, l int) {
for i := 0; i < l; i++ {
fmt.Println(gg.matrix[i])
}
}

type Graph struct {
vexs []string //定点集合
vexnum int //定点数量
edgnum int //边数量
matrix [MAX_SIZE][MAX_SIZE]int //邻接矩阵
}

func initGG(gg *Graph) {
gg.matrix[0][1] = 5
gg.matrix[0][2] = 3

gg.matrix[1][0] = 5
gg.matrix[1][3] = 7
gg.matrix[1][4] = 4

gg.matrix[2][0] = 3
gg.matrix[2][3] = 6

gg.matrix[3][1] = 7
gg.matrix[3][2] = 6
gg.matrix[3][4] = 1

gg.matrix[4][1] = 4
gg.matrix[4][3] = 1

gg.edgnum = 12 / 2
}

//深度遍历
func DFS(gg *Graph, visit *[]bool, i int) {

fmt.Println(gg.vexs[i])
for j := 0; j < gg.vexnum; j++ {
if gg.matrix[i][j] != MAX_VALUE && !(*visit)[j] {
(*visit)[j] = true
DFS(gg, visit, j)
}
}
}

func fDFS(gg *Graph) {
visit := make([]bool, 10, 10)
fmt.Println(visit)
visit[0] = true
DFS(gg, &visit, 0)
}

//广度遍历
func fBFS(gg *Graph) {
listq := list.New()
visit := make([]bool, 10, 10)

//first push
visit[0] = true
listq.PushBack(0)

for listq.Len() > 0 {
index := listq.Front()
fmt.Println(gg.vexs[index.Value.(int)])
for i := 0; i < gg.vexnum; i++ {
if !visit[i] && gg.matrix[index.Value.(int)][i] != MAX_VALUE {
visit[i] = true
listq.PushBack(i)
}
}
listq.Remove(index)
}
}

func prim(gg *Graph, start int) {
index := 0
sum := 0
prims := make([]string, 10, 10)
var weights [5][2]int //[[0 0] [0 5] [0 3] [0 9] [0 9]]

prims[index] = gg.vexs[start]
index++

//next vex
for i := 0; i < gg.vexnum; i++ {
weights[i][0] = start //k
weights[i][1] = gg.matrix[start][i] //v
}

//delete vex
weights[start][1] = 0

for i := 0; i < gg.vexnum; i++ {
//fmt.Println(weights)
if start == i {
continue
}

min := MAX_VALUE
next := 0
for j := 0; j < gg.vexnum; j++ {
if weights[j][1] != 0 && weights[j][1] < min {
min = weights[j][1]
next = j
}
}

fmt.Println(gg.vexs[weights[next][0]], gg.vexs[next], "权重", weights[next][1])
sum += weights[next][1]
prims[index] = gg.vexs[next]
index++

//delete vex
weights[next][1] = 0

//update
for j := 0; j < gg.vexnum; j++ {
if weights[j][1] != 0 && gg.matrix[next][j] < weights[j][1] {
weights[j][1] = gg.matrix[next][j]
weights[j][0] = next
}
}
}

fmt.Println("sum:", sum)
fmt.Println(prims)
}

func get_position(gg *Graph, ch string) int {
for i := 0; i < gg.vexnum; i++ {
if gg.vexs[i] == ch {
return i
}
}
return -1
}

所有代码地址


https://github.com/godla/golang-sort-data-structures-study.git


每天撸一点gopher

golang slice性能分析

hatlonely 发表了文章 • 0 个评论 • 238 次浏览 • 2018-01-26 22:20 • 来自相关话题

golang在gc这块的做得比较弱,频繁地申请和释放内存会消耗很多的资源。另外slice使用数组实现,有一个容量和长度的问题,当slice的容量用完再继续添加元素时需要扩容,而这个扩容会把申请新的空间,把老的内容复制到新的空间,这是一个非常耗时的操作。有... 查看全部

golang在gc这块的做得比较弱,频繁地申请和释放内存会消耗很多的资源。另外slice使用数组实现,有一个容量和长度的问题,当slice的容量用完再继续添加元素时需要扩容,而这个扩容会把申请新的空间,把老的内容复制到新的空间,这是一个非常耗时的操作。有两种方式可以减少这个问题带来的性能开销:



  1. 在slice初始化的时候设置capacity(但更多的时候我们可能并不知道capacity的大小)

  2. 复用slice


下面就针对这两个优化设计了如下的benchmark,代码在: https://github.com/hatlonely/hellogolang/blob/master/internal/buildin/slice_test.go


BenchmarkAppendWithoutCapacity-8                     100      21442390 ns/op
BenchmarkAppendWithCapLessLen10th-8 100 18579700 ns/op
BenchmarkAppendWithCapLessLen3th-8 100 13867060 ns/op
BenchmarkAppendWithCapEqualLen-8 200 6287940 ns/op
BenchmarkAppendWithCapGreaterLen10th-8 100 18692880 ns/op
BenchmarkAppendWithoutCapacityReuse-8 300 5014320 ns/op
BenchmarkAppendWithCapEqualLenReuse-8 300 4821420 ns/op
BenchmarkAppendWithCapGreaterLen10thReuse-8 300 4903230 ns/op

主要结论:



  1. 在已知 capacity 的情况下,直接设置 capacity 减少内存的重新分配,有效提高性能

  2. capacity < length,capacity越接近length,性能越好

  3. capacity > lenght,如果太大,反而会造成性能下降,这里当capacity > 10 * length时,与不设置capacity的性能差不太多

  4. 多次使用复用同一块内存能有效提高性能

golang 几种字符串的连接方式

hatlonely 发表了文章 • 0 个评论 • 215 次浏览 • 2018-01-26 22:19 • 来自相关话题


title: golang 几种字符串的连接方式 date: 2018-01-24 13:45:55 tags: [golang, string] thumbnail: /img/blog/genome.jpg

最近在做性... 查看全部



title: golang 几种字符串的连接方式
date: 2018-01-24 13:45:55
tags: [golang, string]
thumbnail: /img/blog/genome.jpg


最近在做性能优化,有个函数里面的耗时特别长,看里面的操作大多是一些字符串拼接的操作,而字符串拼接在 golang 里面其实有很多种实现。


实现方法


1. 直接使用运算符


func BenchmarkAddStringWithOperator(b *testing.B) {
hello := "hello"
world := "world"
for i := 0; i < b.N; i++ {
_ = hello + "," + world
}
}

golang 里面的字符串都是不可变的,每次运算都会产生一个新的字符串,所以会产生很多临时的无用的字符串,不仅没有用,还会给 gc 带来额外的负担,所以性能比较差


2. fmt.Sprintf()


func BenchmarkAddStringWithSprintf(b *testing.B) {
hello := "hello"
world := "world"
for i := 0; i < b.N; i++ {
_ = fmt.Sprintf("%s,%s", hello, world)
}
}

内部使用 []byte 实现,不像直接运算符这种会产生很多临时的字符串,但是内部的逻辑比较复杂,有很多额外的判断,还用到了 interface,所以性能也不是很好


3. strings.Join()


func BenchmarkAddStringWithJoin(b *testing.B) {
hello := "hello"
world := "world"
for i := 0; i < b.N; i++ {
_ = strings.Join([]string{hello, world}, ",")
}
}

join会先根据字符串数组的内容,计算出一个拼接之后的长度,然后申请对应大小的内存,一个一个字符串填入,在已有一个数组的情况下,这种效率会很高,但是本来没有,去构造这个数据的代价也不小


4. buffer.WriteString()


func BenchmarkAddStringWithBuffer(b *testing.B) {
hello := "hello"
world := "world"
for i := 0; i < 1000; i++ {
var buffer bytes.Buffer
buffer.WriteString(hello)
buffer.WriteString(",")
buffer.WriteString(world)
_ = buffer.String()
}
}

这个比较理想,可以当成可变字符使用,对内存的增长也有优化,如果能预估字符串的长度,还可以用 buffer.Grow() 接口来设置 capacity


测试结果


BenchmarkAddStringWithOperator-8            50000000             30.3 ns/op
BenchmarkAddStringWithSprintf-8 5000000 261 ns/op
BenchmarkAddStringWithJoin-8 30000000 58.7 ns/op
BenchmarkAddStringWithBuffer-8 2000000000 0.00 ns/op

这个是在我的自己 Mac 上面跑的结果,go 版本 go version go1.8 darwin/amd64,这个结果仅供参考,还是要以实际生产环境的值为准,代码在:https://github.com/hatlonely/hellogolang/blob/master/internal/buildin/string_test.go


主要结论



  1. 在已有字符串数组的场合,使用 strings.Join() 能有比较好的性能

  2. 在一些性能要求较高的场合,尽量使用 buffer.WriteString() 以获得更好的性能

  3. 性能要求不太高的场合,直接使用运算符,代码更简短清晰,能获得比较好的可读性

  4. 如果需要拼接的不仅仅是字符串,还有数字之类的其他需求的话,可以考虑 fmt.Sprintf


参考链接


go语言字符串拼接性能分析: http://herman.asia/efficient-string-concatenation-in-go

golang开发目录结构

hatlonely 发表了文章 • 0 个评论 • 363 次浏览 • 2018-01-25 19:04 • 来自相关话题

在实际的项目中发现大家的目录结构都比较凌乱,基本每个人都有每个人的风格,一个项目在不断地变大,一些新的文件或目录又不断地被添加进来,从这里面去找到自己需要的信息的成本越来越高,一个统一的通用的目录结构非常有必要。

以下内容来自于github... 查看全部

在实际的项目中发现大家的目录结构都比较凌乱,基本每个人都有每个人的风格,一个项目在不断地变大,一些新的文件或目录又不断地被添加进来,从这里面去找到自己需要的信息的成本越来越高,一个统一的通用的目录结构非常有必要。


以下内容来自于github上的这个项目(https://github.com/golang-standards/project-layout


/cmd


main函数文件(比如 /cmd/myapp.go)目录,这个目录下面,每个文件在编译之后都会生成一个可执行的文件。


不要把很多的代码放到这个目录下面,这里面的代码尽可能简单。


/internal


应用程序的封装的代码,某个应用私有的代码放到 /internal/myapp/ 目录下,多个应用通用的公共的代码,放到 /internal/common 之类的目录。


/pkg


一些通用的可以被其他项目所使用的代码,放到这个目录下面


/vendor


项目依赖的其他第三方库,使用 glide 工具来管理依赖


/api


协议文件,Swagger/thrift/protobuf


/web


web服务所需要的静态文件


/configs


配置文件


/init


服务启停脚本


/scripts


其他一些脚本,编译、安装、测试、分析等等


/build


持续集成目录


云 (AMI), 容器 (Docker), 操作系统 (deb, rpm, pkg)等的包配置和脚本放到 /build/package/ 目录


/deployments


部署相关的配置文件和模板


/test


其他测试目录,功能测试,性能测试等


/docs


设计文档


/tools


常用的工具和脚本,可以引用 /internal 或者 /pkg 里面的库


/examples


应用程序或者公共库使用的一些例子


/assets


其他一些依赖的静态资源

Ubuntu下编译golang的GXUI

回复

jinheking 发起了问题 • 1 人关注 • 0 个回复 • 238 次浏览 • 2018-01-25 11:00 • 来自相关话题