golang

golang

关于规范招聘信息发帖说明

招聘应聘 回复了问题 • 9 人关注 • 4 个回复 • 5235 次浏览 • 2019-10-04 10:59 • 来自相关话题

Excelize 发布 2.0.2 版本, Go 语言 Excel 文档基础库

Go开源项目xuri 发表了文章 • 0 个评论 • 234 次浏览 • 4 天前 • 来自相关话题


Excelize 是 Go 语言编写的用于操作 Office Excel 文档类库,基于 ECMA-376 Office Open XML 标准。可以使用它来读取、写入由 Microsoft Excel™ 2007 及以上版本创建的 XLSX 文档。相比较其他的开源类库,Excelize 支持写入原本带有图片(表)、透视表和切片器等复杂样式的文档,还支持向 Excel 文档中插入图片与图表,并且在保存后不会丢失文档原有样式,可以应用于各类报表系统中。入选 2018 开源中国码云 Gitee 最有价值开源项目 GVP,目前已成为 Go 语言最受欢迎的 Excel 文档基础库。

开源代码

GitHub: github.com/xuri/excelize
Gitee: gitee.com/xurime/excelize
中文文档: xuri.me/excelize/zh-hans

Excelize 知名用户


2019年10月9日,社区正式发布了 2.0.2 版本,该版本包含了多项新增功能、错误修复和兼容性提升优化。下面是有关该版本更新内容的摘要,完整的更改列表可查看 change log

有关更改的摘要,请参阅 Release Notes。完整的更改列表可查看 change log

Release Notes

此版本中最显著的变化包括:

兼容性提示

升级至该版本需要您的 Go 语言版本高于 1.10。

新增功能

问题修复

  • 修复部分情况下读取批注内容文本不完整的问题,解决 issue #434
  • 修复由于内部合并单元格偏移量计算错误导致的部分情况下使用 RemoveRow() 删除行出现下标越界问题,解决 issue #437
  • 修复部分情况下数据验证下拉菜单中的公式失效问题
  • 修复在循环迭代中调用 Save() 方法保存导致的文档损坏问题,解决 issue #443
  • 提升文档内部 workbook.xml.rels 中相对路径格式解析的兼容性,解决 issue #442
  • 修复部分情况下,删除带有合并单元格的文档所导致的文件损坏问题
  • 修复部分情况下设置保护工作表属性失效的情况,解决 issue #454
  • 修复部分情况下 GetSheetName 获取工作表名称为空的问题, 解决 issue #457
  • 增加单元格内多行文本解析的支持, 相关 issue #464
  • 修复 32 位操作系统环境下数字溢出问题,相关 issue #386
  • 修复 go module 依赖版本不匹配问题, 相关 issue #466 和 issue #480
  • 修复部分情况下调用 SetSheetPrOptions() 所致的文档损坏问题,解决 issue #483

性能表现

  • 性能优化,减少读取文档时的内存开销和耗时,相关 issue #439

其他

  • 完善 SetSheetRow() 函数中的异常处理
  • 代码精简优化, 合并了下列内部函数:

将函数 workBookRelsWriterdrawingRelsWriter 合并为 relsWriter;
将函数 drawingRelsReaderworkbookRelsReaderworkSheetRelsReader 合并为 relsReader;
将函数 addDrawingRelationshipsaddSheetRelationships 合并为 addRels

GoCN每日新闻(2019-10-07)

回复

每日新闻smallfish1 发起了问题 • 1 人关注 • 0 个回复 • 6688 次浏览 • 2019-10-07 15:42 • 来自相关话题

为什么gRPC客户端不提供连接池?

技术讨论tsingson 回复了问题 • 9 人关注 • 6 个回复 • 15787 次浏览 • 2019-10-03 02:58 • 来自相关话题

【杭州】河象网络科技 招聘 Golang 工程师 5 名

招聘应聘airylinus 发表了文章 • 0 个评论 • 374 次浏览 • 2019-09-29 21:02 • 来自相关话题

我们是谁?我们做什么?       杭州河象网络科技有限公司,成立于 2017 年 5 月,以“为孩 ...查看全部

我们是谁?我们做什么?


       杭州河象网络科技有限公司,成立于 2017 年 5 月,以“为孩子提供有效、有趣的普惠教育”为使命,是一家专注3-12岁少儿素质教育的在线教育公司。“河小象” 是公司旗下品牌,采用“AI+教育”模式,融合图像识别、语音测评、智能匹配、人机交互及大数据分析等创新科技,自主研发课程。以“大语文综合素养”为核心,同时推出了写字、书法、美术等明星产品,致力于帮助孩子提高综合素养。

       公司拥有浙江大学、北京师范大学等知名院校教授、博士组成的教研团队,并由原阿里巴巴顶级技术团队负责研发,回归教育本质,持续不断地升级学习体验。服务超过两百万学员,覆盖北京、上海、广州、杭州等270余个城市。同时,河小象已完成 2 亿元人民币 B 轮融资,由创新工场、贝塔斯曼、好未来及上一轮投资方元璟、亦联、金沙江、志拙资本等投资。

我们提供的什么待遇和福利


- 公司提供五险一金,签署劳动合同,办理社保。
- 办公地点在 A 级写字楼:余杭区未来科技城创鑫时代广场。
- 配备办公桌、饮水间,技术开发配备 Macbook 作为办公设备。
- 享受国家法定节假日,中秋、端午会有礼品福利。
- 在招的 Golang 开发工程师岗位的薪资在 10000 - 25000,若技术水平超过岗位标准可另议。

我们对候选人的要求


- **1** 年以上 Golang 开发经验,熟悉语言特性,基础扎实。
- 有良好的编码风格,能进行测试驱动开发、集成测试,能主动改进开发过程和提高交付质量。
- 熟悉常用关系数据库( MySQL,PostgreSQL )的使用经验,能有意识的改进查询性能。
- 熟悉 Linux 操作系统,熟练使用 Docker 相关技术,了解常用运维管理操作。
- 有良好的自学能力,对新技术能持续学习、保持好奇心,不断挖掘自身潜力。

以下是加分项目:

- 能使用 Linux、MacOS 作为日常工作系统。
- 乐于分享,热衷于参与开源社区,曾参与过开源项目的开发。
- 良好的英文阅读能力,善于使用 Google、stackoverflow 等来解决技术问题

我们的联系方式


简历发送到电邮 : `[]rune{120, 122, 109}@hexiaoxiang.com`

我在这家公司的身份和角色


我是 Golang 团队的负责人,希望大家能成为战友。






[gev] 一个轻量、快速的基于 Reactor 模式的非阻塞 TCP 网络库

Go开源项目惜朝 发表了文章 • 0 个评论 • 313 次浏览 • 2019-09-25 16:45 • 来自相关话题

`gev` 是一个 ...查看全部

`gev` 是一个轻量、快速的基于 Reactor 模式的非阻塞 TCP 网络库。

➡️➡️ https://github.com/Allenxuxu/gev

特点

- 基于 epoll 和 kqueue 实现的高性能事件循环
- 支持多核多线程
- 动态扩容 Ring Buffer 实现的读写缓冲区
- 异步读写
- SO_REUSEPORT 端口重用支持

网络模型

`gev` 只使用极少的 goroutine, 一个 goroutine 负责监听客户端连接,其他 goroutine (work 协程)负责处理已连接客户端的读写事件,work 协程数量可以配置,默认与运行主机 CPU 数量相同。

性能测试

> 测试环境 Ubuntu18.04 | 4 Virtual CPUs | 4.0 GiB

吞吐量测试

限制 GOMAXPROCS=1(单线程),1 个 work 协程


限制 GOMAXPROCS=4,4 个 work 协程


其他测试

速度测试


和同类库的简单性能比较, 压测方式与 evio 项目相同。
- gnet
- eviop
- evio
- net (标准库)

限制 GOMAXPROCS=1,1 个 work 协程


限制 GOMAXPROCS=1,4 个 work 协程


限制 GOMAXPROCS=4,4 个 work 协程


安装 gev


```bash
go get -u github.com/Allenxuxu/gev
```

快速入门


```go
package main

import (
"log"

"github.com/Allenxuxu/gev"
"github.com/Allenxuxu/gev/connection"
"github.com/Allenxuxu/ringbuffer"
)

type example struct{}

func (s *example) OnConnect(c *connection.Connection) {
log.Println(" OnConnect : ", c.PeerAddr())
}

func (s *example) OnMessage(c *connection.Connection, buffer *ringbuffer.RingBuffer) (out []byte) {
log.Println("OnMessage")
first, end := buffer.PeekAll()
out = first
if len(end) > 0 {
out = append(out, end...)
}
buffer.RetrieveAll()
return
}

func (s *example) OnClose(c *connection.Connection) {
log.Println("OnClose")
}

func main() {
handler := new(example)

s, err := gev.NewServer(handler,
gev.Address(":1833"),
gev.NumLoops(2),
gev.ReusePort(true))
if err != nil {
panic(err)
}

s.Start()
}
```

Handler 是一个接口,我们的程序必须实现它。

```go
type Handler interface {
OnConnect(c *connection.Connection)
OnMessage(c *connection.Connection, buffer *ringbuffer.RingBuffer) []byte
OnClose(c *connection.Connection)
}

func NewServer(handler Handler, opts ...Option) (server *Server, err error) {
```

在消息到来时,gev 会回调 OnMessage ,在这个函数中可以通过返回一个切片来发送数据给客户端。

```go
func (s *example) OnMessage(c *connection.Connection, buffer *ringbuffer.RingBuffer) (out []byte)
```

Connection 还提供 Send 方法来发送数据。Send 并不会立刻发送数据,而是先添加到 event loop 的任务队列中,然后唤醒 event loop 去发送。

更详细的使用方式可以参考示例:[服务端定时推送]

```go
func (c *Connection) Send(buffer []byte) error
```

Connection ShutdownWrite 会关闭写端,从而断开连接。

更详细的使用方式可以参考示例:[限制最大连接数]

```go
func (c *Connection) ShutdownWrite() error
```


➡️➡️ https://github.com/Allenxuxu/gev



Go实现双向链表

文章分享link1st 发表了文章 • 1 个评论 • 704 次浏览 • 2019-09-20 09:30 • 来自相关话题

本文介绍什么是链表,常见的链表有哪些,然后介绍链表这种数据结构会在哪些地方可以用到,以及 Redis 队列是底层的实现,通过一个小实例来演示 Redis 队列有哪些功能,最后通过 Go 实现一个双向链表。 ...查看全部

本文介绍什么是链表,常见的链表有哪些,然后介绍链表这种数据结构会在哪些地方可以用到,以及 Redis 队列是底层的实现,通过一个小实例来演示 Redis 队列有哪些功能,最后通过 Go 实现一个双向链表。

链表

目录

1、链表

1.1 说明

链表

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

链表有很多种不同的类型:单向链表,双向链表以及循环链表。

  • 优势:

可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。链表允许插入和移除表上任意位置上的节点。

  • 劣势:

由于链表增加了节点指针,空间开销比较大。链表一般查找数据的时候需要从第一个节点开始每次访问下一个节点,直到访问到需要的位置,查找数据比较慢。

  • 用途:

常用于组织检索较少,而删除、添加、遍历较多的数据。

如:文件系统、LRU cache、Redis 列表、内存管理等。

1.2 单向链表

链表中最简单的一种是单向链表,

一个单向链表的节点被分成两个部分。它包含两个域,一个信息域和一个指针域。第一个部分保存或者显示关于节点的信息,第二个部分存储下一个节点的地址,而最后一个节点则指向一个空值。单向链表只可向一个方向遍历。

单链表有一个头节点head,指向链表在内存的首地址。链表中的每一个节点的数据类型为结构体类型,节点有两个成员:整型成员(实际需要保存的数据)和指向下一个结构体类型节点的指针即下一个节点的地址(事实上,此单链表是用于存放整型数据的动态数组)。链表按此结构对各节点的访问需从链表的头找起,后续节点的地址由当前节点给出。无论在表中访问哪个节点,都需要从链表的头开始,顺序向后查找。链表的尾节点由于无后续节点,其指针域为空,写作为NULL。

1.3 循环链表

循环链表是与单向链表一样,是一种链式的存储结构,所不同的是,循环链表的最后一个结点的指针是指向该循环链表的第一个结点或者表头结点,从而构成一个环形的链。

循环链表的运算与单链表的运算基本一致。所不同的有以下几点:

1、在建立一个循环链表时,必须使其最后一个结点的指针指向表头结点,而不是像单链表那样置为NULL。

2、在判断是否到表尾时,是判断该结点链域的值是否是表头结点,当链域的值等于表头指针时,说明已到表尾。而非象单链表那样判断链域的值是否为NULL。

1.4 双向链表

双向链表

双向链表其实是单链表的改进,当我们对单链表进行操作时,有时你要对某个结点的直接前驱进行操作时,又必须从表头开始查找。这是由单链表结点的结构所限制的。因为单链表每个结点只有一个存储直接后继结点地址的链域,那么能不能定义一个既有存储直接后继结点地址的链域,又有存储直接前驱结点地址的链域的这样一个双链域结点结构呢?这就是双向链表。

在双向链表中,结点除含有数据域外,还有两个链域,一个存储直接后继结点地址,一般称之为右链域(当此“连接”为最后一个“连接”时,指向空值或者空列表);一个存储直接前驱结点地址,一般称之为左链域(当此“连接”为第一个“连接”时,指向空值或者空列表)。

2、redis队列

2.1 说明

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)

Redis 列表使用两种数据结构作为底层实现:双端列表(linkedlist)、压缩列表(ziplist)

通过配置文件中(list-max-ziplist-entries、list-max-ziplist-value)来选择是哪种实现方式

在数据量比较少的时候,使用双端链表和压缩列表性能差异不大,但是使用压缩列表更能节约内存空间

redis 链表的实现源码 redis src/adlist.h

2.2 应用场景

消息队列,秒杀项目

秒杀项目:

提前将需要的商品码信息存入 Redis 队列,在抢购的时候每个用户都从 Redis 队列中取商品码,由于 Redis 是单线程的,同时只能有一个商品码被取出,取到商品码的用户为购买成功,而且 Redis 性能比较高,能抗住较大的用户压力。

2.3 演示

如何通过 Redis 队列中防止并发情况下商品超卖的情况。

假设:

网站有三件商品需要卖,我们将数据存入 Redis 队列中

1、 将三个商品码(10001、10002、10003)存入 Redis 队列中

# 存入商品
RPUSH commodity:queue 10001 10002 10003

2、 存入以后,查询数据是否符合预期

# 查看全部元素
LRANGE commodity:queue 0 -1

# 查看队列的长度
LLEN commodity:queue

3、 抢购开始,获取商品码,抢到商品码的用户则可以购买(由于 Redis 是单线程的,同一个商品码只能被取一次 )

# 出队
LPOP commodity:queue

这里了解到 Redis 列表是怎么使用的,下面就用 Go 语言实现一个双向链表来实现这些功能。

3、Go双向链表

3.1 说明

这里只是用 Go 语言实现一个双向链表,实现:查询链表的长度、链表右端插入数据、左端取数据、取指定区间的节点等功能( 类似于 Redis 列表的中的 RPUSH、LRANGE、LPOP、LLEN功能 )。

3.2 实现

golang 双向链表

  • 节点定义

双向链表有两个指针,分别指向前一个节点和后一个节点

链表表头 prev 的指针为空,链表表尾 next 的指针为空

// 链表的一个节点
type ListNode struct {
prev *ListNode // 前一个节点
next *ListNode // 后一个节点
value string // 数据
}

// 创建一个节点
func NewListNode(value string) (listNode *ListNode) {
listNode = &ListNode{
value: value,
}

return
}

// 当前节点的前一个节点
func (n *ListNode) Prev() (prev *ListNode) {
prev = n.prev

return
}

// 当前节点的前一个节点
func (n *ListNode) Next() (next *ListNode) {
next = n.next

return
}

// 获取节点的值
func (n *ListNode) GetValue() (value string) {
if n == nil {

return
}
value = n.value

return
}
  • 定义一个链表

链表为了方便操作,定义一个结构体,可以直接从表头、表尾进行访问,定义了一个属性 len ,直接可以返回链表的长度,直接查询链表的长度就不用遍历时间复杂度从 O(n) 到 O(1)。

// 链表
type List struct {
head *ListNode // 表头节点
tail *ListNode // 表尾节点
len int // 链表的长度
}


// 创建一个空链表
func NewList() (list *List) {
list = &List{
}
return
}

// 返回链表头节点
func (l *List) Head() (head *ListNode) {
head = l.head

return
}

// 返回链表尾节点
func (l *List) Tail() (tail *ListNode) {
tail = l.tail

return
}

// 返回链表长度
func (l *List) Len() (len int) {
len = l.len

return
}
  • 在链表的右边插入一个元素
// 在链表的右边插入一个元素
func (l *List) RPush(value string) {

node := NewListNode(value)

// 链表未空的时候
if l.Len() == 0 {
l.head = node
l.tail = node
} else {
tail := l.tail
tail.next = node
node.prev = tail

l.tail = node
}

l.len = l.len + 1

return
}
  • 从链表左边取出一个节点
// 从链表左边取出一个节点
func (l *List) LPop() (node *ListNode) {

// 数据为空
if l.len == 0 {

return
}

node = l.head

if node.next == nil {
// 链表未空
l.head = nil
l.tail = nil
} else {
l.head = node.next
}
l.len = l.len - 1

return
}
  • 通过索引查找节点

通过索引查找节点,如果索引是负数则从表尾开始查找。

自然数和负数索引分别通过两种方式查找节点,找到指定索引或者是链表全部查找完则查找完成。

// 通过索引查找节点
// 查不到节点则返回空
func (l *List) Index(index int) (node *ListNode) {

// 索引为负数则表尾开始查找
if index < 0 {
index = (-index) - 1
node = l.tail
for true {
// 未找到
if node == nil {

return
}

// 查到数据
if index == 0 {

return
}

node = node.prev
index--
}
} else {
node = l.head
for ; index > 0 && node != nil; index-- {
node = node.next
}
}

return
}
  • 返回指定区间的元素
// 返回指定区间的元素
func (l *List) Range(start, stop int) (nodes []*ListNode) {
nodes = make([]*ListNode, 0)

// 转为自然数
if start < 0 {
start = l.len + start
if start < 0 {
start = 0
}
}

if stop < 0 {
stop = l.len + stop
if stop < 0 {
stop = 0
}
}

// 区间个数
rangeLen := stop - start + 1
if rangeLen < 0 {

return
}

startNode := l.Index(start)
for i := 0; i < rangeLen; i++ {
if startNode == nil {
break
}

nodes = append(nodes, startNode)
startNode = startNode.next
}

return
}

4、总结

  • 到这里关于链表的使用已经结束,介绍链表是有哪些(单向链表,双向链表以及循环链表),也介绍了链表的应用场景(Redis 列表使用的是链表作为底层实现),最后用 Go 实现了双向链表,演示了链表在 Go 语言中是怎么使用的,大家可以在项目中更具实际的情况去使用。

5、参考文献

维基百科 链表

github redis

项目地址:go 实现队列

https://github.com/link1st/link1st/tree/master/linked

gnet: 一个轻量级且高性能的 Go 网络库

开源程序panjf2000 发表了文章 • 1 个评论 • 517 次浏览 • 2019-09-18 16:08 • 来自相关话题


gnet












# 博客原文
https://taohuawu.club/go-event-loop-networking-library-gnet

# Github 主页
https://github.com/panjf2000/gnet

欢迎大家围观~~,目前还在持续更新,感兴趣的话可以 star 一下暗中观察哦。

# 简介

`gnet` 是一个基于 Event-Loop 事件驱动的高性能和轻量级网络库。这个库直接使用 [epoll](https://en.wikipedia.org/wiki/Epoll) 和 [kqueue](https://en.wikipedia.org/wiki/Kqueue) 系统调用而非标准 Golang 网络包:[net](https://golang.org/pkg/net/) 来构建网络应用,它的工作原理类似两个开源的网络库:[libuv](https://github.com/libuv/libuv) 和 [libevent](https://github.com/libevent/libevent)。

这个项目存在的价值是提供一个在网络包处理方面能和 [Redis](http://redis.io)、[Haproxy](http://www.haproxy.org) 这两个项目具有相近性能的Go 语言网络服务器框架。

`gnet` 的亮点在于它是一个高性能、轻量级、非阻塞的纯 Go 实现的传输层(TCP/UDP/Unix-Socket)网络库,开发者可以使用 `gnet` 来实现自己的应用层网络协议,从而构建出自己的应用层网络应用:比如在 `gnet` 上实现 HTTP 协议就可以创建出一个 HTTP 服务器 或者 Web 开发框架,实现 Redis 协议就可以创建出自己的 Redis 服务器等等。

**`gnet` 衍生自另一个项目:`evio`,但是性能更好。**

# 功能

- [高性能](#性能测试) 的基于多线程模型的 Event-Loop 事件驱动
- 内置 Round-Robin 轮询负载均衡算法
- 简洁的 APIs
- 基于 Ring-Buffer 的高效内存利用
- 支持多种网络协议:TCP、UDP、Unix Sockets
- 支持两种事件驱动机制:Linux 里的 epoll 以及 FreeBSD 里的 kqueue
- 支持异步写操作
- 允许多个网络监听地址绑定在一个 Event-Loop 上
- 灵活的事件定时器
- SO_REUSEPORT 端口重用

# 核心设计

## 多线程模型

`gnet` 重新设计开发了一个新内置的多线程模型:『主从 Reactor 多线程』,这也是 `netty` 默认的线程模型,下面是这个模型的原理图:


multi_reactor



它的运行流程如下面的时序图:


reactor



现在我正在 `gnet` 里开发一个新的多线程模型:『带线程/go程池的主从 Reactors 多线程』,并且很快就能完成,这个模型的架构图如下所示:


multi_reactor_thread_pool



它的运行流程如下面的时序图:


multi-reactors



## 通信机制

`gnet` 的『主从 Reactors 多线程』模型是基于 Golang 里的 Goroutines的,一个 Reactor 挂载在一个 Goroutine 上,所以在 `gnet` 的这个网络模型里主 Reactor/Goroutine 与从 Reactors/Goroutines 有海量通信的需求,因此 `gnet` 里必须要有一个能在 Goroutines 之间进行高效率的通信的机制,我没有选择 Golang 里的主流方案:基于 Channel 的 CSP 模型,而是选择了性能更好、基于 Ring-Buffer 的 Disruptor 方案。

所以我最终选择了 [go-disruptor](https://github.com/smartystreets-prototypes/go-disruptor):高性能消息分发队列 LMAX Disruptor 的 Golang 实现。

## 自动扩容的 Ring-Buffer

`gnet` 利用 Ring-Buffer 来缓存 TCP 流数据以及管理内存使用。







# 开始使用

## 安装

```sh
$ go get -u github.com/panjf2000/gnet
```

## 使用示例

```go
// ======================== Echo Server implemented with gnet ===========================

package main

import (
"flag"
"fmt"
"log"
"strings"

"github.com/panjf2000/gnet"
"github.com/panjf2000/gnet/ringbuffer"
)

func main() {
var port int
var loops int
var udp bool
var trace bool
var reuseport bool

flag.IntVar(&port, "port", 5000, "server port")
flag.BoolVar(&udp, "udp", false, "listen on udp")
flag.BoolVar(&reuseport, "reuseport", false, "reuseport (SO_REUSEPORT)")
flag.BoolVar(&trace, "trace", false, "print packets to console")
flag.IntVar(&loops, "loops", 0, "num loops")
flag.Parse()

var events gnet.Events
events.NumLoops = loops
events.OnInitComplete = func(srv gnet.Server) (action gnet.Action) {
log.Printf("echo server started on port %d (loops: %d)", port, srv.NumLoops)
if reuseport {
log.Printf("reuseport")
}
return
}
events.React = func(c gnet.Conn, inBuf *ringbuffer.RingBuffer) (out []byte, action gnet.Action) {
top, tail := inBuf.PreReadAll()
out = append(top, tail...)
inBuf.Reset()

if trace {
log.Printf("%s", strings.TrimSpace(string(top)+string(tail)))
}
return
}
scheme := "tcp"
if udp {
scheme = "udp"
}
log.Fatal(gnet.Serve(events, fmt.Sprintf("%s://:%d", scheme, port)))
}

```

## I/O 事件

`gnet` 目前支持的 I/O 事件如下:

- `OnInitComplete` 当 server 初始化完成之后调用。
- `OnOpened` 当连接被打开的时候调用。
- `OnClosed` 当连接被关闭的时候调用。
- `OnDetached` 当主动摘除连接的时候的调用。
- `React` 当 server 端接收到从 client 端发送来的数据的时候调用。(你的核心业务代码一般是写在这个方法里)
- `Tick` 服务器启动的时候会调用一次,之后就以给定的时间间隔定时调用一次,是一个定时器方法。
- `PreWrite` 预先写数据方法,在 server 端写数据回 client 端之前调用。

# 性能测试

## Linux (epoll)

### 系统参数

```powershell
Go Version: go1.12.9 linux/amd64
OS: Ubuntu 18.04
CPU: 8 Virtual CPUs
Memory: 16.0 GiB
```

### Echo Server

![echolinux.png](https://img.hacpai.com/file/2019/09/echolinux-fca6e6e5.png)


### HTTP Server

![httplinux.png](https://img.hacpai.com/file/2019/09/httplinux-663a0318.png)


## FreeBSD (kqueue)

### 系统参数

```powershell
Go Version: go version go1.12.9 darwin/amd64
OS: macOS Mojave 10.14.6
CPU: 4 CPUs
Memory: 8.0 GiB
```

### Echo Server

![echomac.png](https://img.hacpai.com/file/2019/09/echomac-7a29e0d1.png)


### HTTP Server

![httpmac.png](https://img.hacpai.com/file/2019/09/httpmac-cb6d26ea.png)


# 证书

`gnet` 的源码允许用户在遵循 MIT [开源证书](https://github.com/panjf2000/gnet/blob/master/LICENSE) 规则的前提下使用。

# 待做事项

> gnet 还在持续开发的过程中,所以这个仓库的代码和文档会一直持续更新,如果你对 gnet 感兴趣的话,欢迎给这个开源库贡献你的代码~~

花了半天用go写一个带ui的redis客户端

Go开源项目ownGolang 回复了问题 • 6 人关注 • 5 个回复 • 1311 次浏览 • 2019-09-18 10:17 • 来自相关话题

beego 开发的博客 go-blog

开源程序猴子 发表了文章 • 3 个评论 • 283 次浏览 • 2019-09-16 12:00 • 来自相关话题

个人博客 项目地址: https://github.com/1920853199/go-blog demo 地址: http://leechan.online ...查看全部
个人博客 
项目地址: https://github.com/1920853199/go-blog
demo 
地址: http://leechan.online






GoCN每日新闻(2019-09-10)

回复

每日新闻smallfish1 发起了问题 • 1 人关注 • 0 个回复 • 13767 次浏览 • 2019-09-10 11:30 • 来自相关话题

推荐一下自己的Go分布式缓存项目

开源程序seaguest 发表了文章 • 0 个评论 • 354 次浏览 • 2019-09-04 11:02 • 来自相关话题

最近整理了一下自己的分布式缓存方案,是cache-aside模式的二级缓存(memory+redis)实现,目前已经在线上投入使用,经过百万日活的验证。 内存部分是采用的是 sync.Map, 读取缓存的时候先从内存读,如果未读到则去 ...查看全部
最近整理了一下自己的分布式缓存方案,是cache-aside模式的二级缓存(memory+redis)实现,目前已经在线上投入使用,经过百万日活的验证。

内存部分是采用的是 sync.Map, 读取缓存的时候先从内存读,如果未读到则去读 redis,如果 redis 未读到,则根据定义的加载函数加载到 redis 和内存。

缓存有 lazy 模式,为了避免缓存被击穿,可以设置 lazy 模式,缓存数据存活的时间更久,但是每次读取的时候依然会判断数据是否是最新的,不是最新的话会异步加载更新。

通过 redis 的 Publish/Subscribe 功能,实现缓存的分布式更新,目前仅实现删除同步。

因为是很轻量级的,所以性能理论上会很不错,测试1000个并发毫无压力,有兴趣的同学可以验证一下。理论上性能的瓶颈在于加载函数里数据库的查询,如果数据库配置够好,而redis单机10万QPS应该没什么问题,那么应该可以达到比较高的并发。

项目地址 https://github.com/seaguest/cache

欢迎大家批评指正!

yiigo 3.x 版本发布

文章分享IIInsomnia 发表了文章 • 0 个评论 • 771 次浏览 • 2019-09-02 17:25 • 来自相关话题

# [yiigo](https://github.com/iiinsomnia/yiigo) A simple and light library which makes Golang development easier ! ...查看全部
# [yiigo](https://github.com/iiinsomnia/yiigo)

A simple and light library which makes Golang development easier !

## Features

- Support [MySQL](https://github.com/go-sql-driver/mysql)
- Support [PostgreSQL](https://github.com/lib/pq)
- Support [MongoDB](https://github.com/mongodb/mongo-go-driver)
- Support [Redis](https://github.com/gomodule/redigo)
- Support [Zipkin](https://github.com/openzipkin/zipkin-go)
- Use [gomail](https://github.com/go-gomail/gomail) for email sending
- Use [toml](https://github.com/pelletier/go-toml) for configuration
- Use [sqlx](https://github.com/jmoiron/sqlx) for SQL executing
- Use [zap](https://github.com/uber-go/zap) for logging

## Requirements

`Go1.11+`

## Installation

```sh
go get github.com/iiinsomnia/yiigo/v3
```

## Usage

#### MySQL

```go
// default db
yiigo.RegisterDB(yiigo.AsDefault, yiigo.MySQL, "root:root@tcp(localhost:3306)/test")

yiigo.DB.Get(&User{}, "SELECT * FROM `user` WHERE `id` = ?", 1)

// other db
yiigo.RegisterDB("foo", yiigo.MySQL, "root:root@tcp(localhost:3306)/foo")

yiigo.UseDB("foo").Get(&User{}, "SELECT * FROM `user` WHERE `id` = ?", 1)
```

#### MongoDB

```go
// default mongodb
yiigo.RegisterMongoDB(yiigo.AsDefault, "mongodb://localhost:27017")

ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
yiigo.Mongo.Database("test").Collection("numbers").InsertOne(ctx, bson.M{"name": "pi", "value": 3.14159})

// other mongodb
yiigo.RegisterMongoDB("foo", "mongodb://localhost:27017")

ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
yiigo.UseMongo("foo").Database("test").Collection("numbers").InsertOne(ctx, bson.M{"name": "pi", "value": 3.14159})
```

#### Redis

```go
// default redis
yiigo.RegisterRedis(yiigo.AsDefault, "localhost:6379")

conn, err := yiigo.Redis.Get()

if err != nil {
log.Fatal(err)
}

defer yiigo.Redis.Put(conn)

conn.Do("SET", "test_key", "hello world")

// other redis
yiigo.RegisterRedis("foo", "localhost:6379")

foo := yiigo.UseRedis("foo")
conn, err := foo.Get()

if err != nil {
log.Fatal(err)
}

defer foo.Put(conn)

conn.Do("SET", "test_key", "hello world")
```

#### Config

```go
// env.toml
//
// [app]
// env = "dev"
// debug = true
// port = 50001

yiigo.UseEnv("env.toml")

yiigo.Env.Bool("app.debug", true)
yiigo.Env.Int("app.port", 12345)
yiigo.Env.String("app.env", "dev")
```

#### Zipkin

```go
tracer, err := yiigo.NewZipkinTracer("http://localhost:9411/api/v2/spans",
yiigo.WithZipkinTracerEndpoint("zipkin-test", "localhost"),
yiigo.WithZipkinTracerSharedSpans(false),
yiigo.WithZipkinTracerSamplerMod(1),
)

if err != nil {
log.Fatal(err)
}

client, err := yiigo.NewZipkinClient(tracer)

if err != nil {
log.Fatal(err)
}

b, err := client.Get(context.Background(), "url...",
yiigo.WithRequestHeader("Content-Type", "application/json; charset=utf-8"),
yiigo.WithRequestTimeout(5*time.Second),
)

if err != nil {
log.Fatal(err)
}

fmt.Println(string(b))
```

#### Logger

```go
// default logger
yiigo.RegisterLogger(yiigo.AsDefault, "app.log")
yiigo.Logger.Info("hello world")

// other logger
yiigo.RegisterLogger("foo", "foo.log")
yiigo.UseLogger("foo").Info("hello world")
```

## Documentation

- [API Reference](https://godoc.org/github.com/iiinsomnia/yiigo)
- [TOML](https://github.com/toml-lang/toml)
- [Example](https://github.com/iiinsomnia/yiigo-example)

**Enjoy ^_^**

[北京]美餐网招聘Golang/Web 前端/大数据开发/Android/iOS开发,老铁们冲鸭~

招聘应聘recruiter 发表了文章 • 0 个评论 • 394 次浏览 • 2019-08-29 16:36 • 来自相关话题

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190829160718237.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,te ...查看全部
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190829160718237.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjYxMzQ5Mg==,size_16,color_FFFFFF,t_70)

工作日上午 10 点,美餐( meican.com ) 11 层产品技术部的办公室里,几个习惯早到的工程师已经打开 MacBook 敲下了几行代码,他们在持续优化企业订餐的预订、交互功能。

活跃于微博的工程师飞树先生刚刚经过中关村地区的海淀黄庄地铁站,候车间隙对面的广告牌上刚好是他的个人特 写和招聘词:「我是美餐 BUG 开发工程师,我们正在招聘技术大牛」。

习惯了夜间创作的 LETO,已经被 Slack 里关于企业 wiki 的问题叫醒,随即就在 Asana 里增加了一个 Task。

美餐每年投入数万美元购买的 Confluence,经过他的二次开发,已经成为了公司内部不可缺少的知识库和交流中心。

在美餐,每个人都是多面手,从参与解决普通的技术问题,到带领团队独当一面兼顾前端后端和设计。在这个扁平化的团队中,工程师需要担任 leader 的角色,发挥创新思维,创造极致产品和体验。

除了写代码改 bug,工程师们还会拉琴、摄影、骑车,会在团队欢聚的时候带上专业单反拍摄精美大片。从美餐还没上线就在参与创业的元老级工程师马老师,甚至连盛大的婚礼都在办公室完成了。

你看,工程师们是一群有着不同爱好的有趣的人,除了精通 Lua / Go / Erlang / Scala / Ruby / R 语言,他们还是音乐达人、绘画才子和马拉松健将,会将冰冷的代码组合成有趣的产品,也会将平凡的生活过出自己的乐趣。

在美餐,没有日复一日乏味的工作,更多的是有意思的创作,每一次敲键盘、做优化、改设计,会让美餐用户每一天的浏览、订餐、下单、欢聚更加快捷顺畅,帮助更多的企业客户提升员工幸福感。

美餐是中国领先的企业订餐和综合消费平台,目前覆盖北京、上海、广州、深圳、成都等城市,为数千家企业客户员工提供企业用餐、企业活动以及精选欢聚活动推荐等综合消费服务。


--------------------------------------------------------------------------------

#### 关于美餐:

2011 年,获得来自真格基金和九合创投的天使投资。

2012 年,获得来自 KPCB 的 A 轮投资。

2013 年,获得来自 NGP 的 B 轮投资。

2014 年,获得来自挚信资本的 B+ 轮投资。

2015 年,获得来自美团点评的 C 轮战略投资。

2016 年,美餐荣获中国最具潜力创业公司,员工福利管理服务机构十强,年度最佳企业服务商等荣誉。

2017 年,美餐荣获中国团餐高成长性品牌企业,中国团餐十强企业,中国生活服务产业十大创新力企业等荣誉。

2017 年,获得来自高盛( Goldman Sachs )的 D 轮投资。

2018 年,美餐荣获中国餐饮百强企业。美餐智能餐柜 SMARTWAITER W1 荣获德国 iF 设计奖和红点奖。

2018 年,获得来自阳光保险的 D+ 轮投资。


--------------------------------------------------------------------------------

#### 工作环境:

Herman Miller Embody 人体工学座椅

B&W; Zeppelin Air 无线音响

De'Lo- nghi 全自动咖啡机

PlayStation 4/Xbox One 游戏机

3D 打印机

Blueair 空气净化器

大提琴 & 小提琴

各种 Apple 产品

懒人沙发

#### 员工福利:

每 2 年报销一台 Mac (归个人所有)

每月团建吃喝腐败

无限量零食、饮料

免费午餐、晚餐( top 连锁餐饮品牌)

分配期权

弹性工作,不打卡

重视技术,无官僚,Geek 团队

介绍对象


--------------------------------------------------------------------------------

#### 招聘职位:

资深 Web 开发工程师( Golang/Java )

Golang 开发工程师

Web 前端工程师

Android 开发工程师

iOS 开发工程师( swift )

数据仓库工程师

UI/UE 设计师

品牌设计师

--------------------------------------------------------------------------------

团队:大牛+极客+文艺男;我们团队的成员来自谷歌,阿里巴巴,腾讯,头条、滴滴以及行业内知名公司;

技术:从 UI 到代码的简洁,却凝聚最挑战的设计、线上满是科技,线下满是黑科技。

如果你热爱技术,如果你具有产品思维(开发负责产品规划,我们木有产品经理),如果你喜欢自由,创新...来

美餐网哦,这里的平台绝对适合你!欢迎加入美餐,和我们一起快速成长!更多信息请登录 www.meican.com


--------------------------------------------------------------------------------

工作地点:北京市海淀区中国外文大厦

薪酬待遇:具有行业竞争力的整体薪酬


--------------------------------------------------------------------------------

#### 简历投递:

[cuilixia@meican.com]

标题注明「来自 gopherChina 」

请附上 GitHub / Blog 链接

加分项请你自己发挥

我们喜欢认真有趣的人 :-)
***
#### 职位描述:

#### 资深 Web 开发工程师( Golang/Java )

工作职责:
1. 负责公司项目后台的的重构工作,编写核心代码,推动工作的开展;
2. 持续改进系统的架构和核心技术,保证系统的稳定性、高性能、高可用性和可扩展性。

岗位要求:
1. 全日制本科及以上学历,计算机相关专业;
2. 熟悉主流开源框架,至少熟悉 Go / Java 中的一种或两种及语言对应的 Restful 框架,并具有优秀的开发能力,能快速完成原型的开发;
3. 负责平台服务技术框架的规划与设计,编写核心代码,实现核心技术组件,为业务调用提供基础,推动自动化测试和部署;
4. 熟悉底层中间件、分布式技术(包括缓存、消息系统、热部署等)并能熟练使用各种工具
5. 具备高可用、高性能、高并发、高扩展系统设计经验,熟练操作主流关系数据库;
6. 热爱技术,工作严谨,对系统运行质量有苛刻的要求者最佳;

优先条件:
1. 过往有架构设计或重构经验的优先考虑;
2. 开源贡献者优先,github 源码者优先,技术博客者优先。

工作中将涉及的技术:
- AWS
- Kubernetes
- Ansible
- Gin-Gonic
- Play Framework
- Webpack
- React
- Angular
- ES6
- RESTful API
- OAuth 2.0 协议
- LDAP
- 支付宝、微信支付相关开发
***
#### Web 前端开发工程师

工作职责:
1. 在具体使用场景中理解用户使用方式和遇到的问题;
2. 通过 Web 前端开发,解决上述问题,带给用户更好的使用体验;
3. 创造自动化工具,帮助工作伙伴提升运营效率。

任职要求:
1. 前端基础知识扎实,能独立完成工作,熟练掌握原生 JavaScript、CSS、HTML ;
2. 可以精准还原设计稿,愿意为了提升用户体验作出最大努力;
3. 了解前端测试,懂得如何写出可测试的代码;
4. 了解 GitFlow 流程;
5. 了解 HTTP、TCP、UDP 等常见协议;
6. 良好的学习能力,有责任心,愿意自我驱动,爱折腾愿意尝试新鲜事物,能够快速学习新技术并实践;
7. 具有英文文档阅读能力。

#### 加分项:
* 了解一门后端语言,如 Node.js 、Java、Go 等;
* 了解或使用过 AWS、腾讯云、阿里云等云服务;
* 了解 MQTT 协议;
* 有 Native 端开发经验;
* 对 Flutter 开发感兴趣;
* 使用过 Sketch 或 Adobe XD 等原型工具;
* 组件库开发经验;
* WebGL 开发经验。

#### 工作中将涉及的技术:
* React、Vue
* Redux、MobX、Vuex
* Immutable.js
* Webpack
* Next.js 、Nuxt.js
* WebSocket
* Cypress
* Storybook
* TypeScript
* PWA
* Serverless
* Chrome Packaged App、Electron
* AWS
* Taro (微信小程序)
***
#### Golang 开发工程师

工作职责:
1. 参与需求分析,产品设计,配合团队分工完成设计和开发任务;
2. 实时定位和处理各系统问题,快速解决线上问题;
3. 参与设计产品架构与编码;
4. 改进现有的工具,帮助工作伙伴提升开发与运营效率;
5. 能够与各个 Team 高效沟通,解决实际问题。

任职要求:
1. 大学本科及以上学历;
2. 有完整的项目开发经验,追求代码质量,乐于分享;
3. 熟练使用至少一种语言,包括但不限于 Go/Java/Ruby/Python ;
4. 熟练使用 MySQL/Postgres 关系型数据库;
5. 掌握一种或多种非关系型数据库,包括但不限于 DynamoDB/MongoDB/Cassandra/Redis ;
6. 对分布式处理有一定经验;
7. 熟悉常见的消息队列, 各自的使用场景.;
8. 有过 RPC 开发经验,熟悉 RPC 通信的各个环节。

#### 优先条件:
1. 有过完整的 Go 项目经验, 同时熟悉 Go 与 Java 更佳;
2. 有过流量接入层开发经验;
3. 参与过开源项目, 有开放的技术源码提供(我们会去看您的项目);
4. 对前端技术有了解, 有开发经验更佳。

#### 工作中将涉及的技术:
- AWS
- Kubernetes
- Ansible
- Gin-Gonic
- Play Framework
- MySQL/Postgres
- Redis
- Nginx/Openresty
- RESTful API
- Grpc
- OAuth 2.0 协议
- 支付宝、微信支付相关开发
***
#### Android 开发工程师

工作职责:
1. 负责美餐 Android 客户端以及后端接口的开发;
2. 根据工作需要参与技术团队其他的工作。

任职要求:
1. 大学本科及以上学历;
2. 扎实的 Java 基础,了解常用的软件编程思想,了解常用算法及数据结构等知识;
3. 从事 Android 开发 3 年及以上,有上线的 Android 应用,熟练使用数据库,多线程 /多进程通讯,网络编程,进程配置,权限管理等;
4. 熟悉 Android 常用组件、熟悉 Android 框架、熟悉 Android 开发文档,了解 JNI/NDK 开发,有阅读 Android 源码经验;
5. 逻辑清晰,好奇心强,善于自我激励;
6. 能够创造前所未有的方案解决新问题,代码整洁;
7. 对良好代码风格具有强迫症;
8. 如有 GitHub 帐号或技术博客,请在简历中写明。

#### 工作内容将涉及的技术(优先考虑):
- RESTful API
- OAuth 2.0 协议
- 熟悉 Android Test Framework
- 熟悉 MVC, MVP, MVVM 架构
- 熟悉 Material Design 设计规范
- 熟悉性能优化方法
- 支付宝、微信支付相关开发
- 地图与 LBS
- socket 通信
- Push Notification
- Bluetooth LE
***
#### iOS 开发工程师

工作职责:
1. 负责美餐旗下 iOS 客户端的开发与维护,设备包括 iPhone / iPad / Apple Watch ;
2. 根据工作需要参与技术团队其他的工作。

任职要求:
1. 计算机,软件工程,信息技术相关本科及以上学历;
2. 熟悉 iOS Human Interface Guidelines ;
3. 熟悉 iOS 开发相关技术,有 3 年以上的开发经验,1 年以上的 Swift 开发经验,有上线的 app 尤佳;
5. 熟悉 HTTP / HTTPS / Socket 等常用协议,有后端 API 设计经验尤佳;
6. 有撰写文档和测试的能力;
7. 良好的团队协作能力,乐于分享;
8. 逻辑清晰,好奇心强并善于寻求答案;
9. 具备技术热情,崇尚极客精神;
10. 良好代码风格和编程习惯,对工程质量和产品品质有较高要求;
11. 如有 GitHub 帐号或技术博客,请在简历中写明。

#### 工作中涉及的技术:
Languages:
- Swift
- Swift / Objective-C 混合编程 (aka. Mix and Match)

Tools:
- Xcode
- Instruments
- XCTest

App Frameworks:
- UIKit (使用 Auto Layout 和 Storyboard )
- Foundation
- WatchKit (加分项)

Graphics:
- Core Animation
- Core Graphics
- ARKit / Vision (加分项)

App Services and System:
- UserNotifications / UserNotificationsUI
- NotificationCenter
- MapKit
- Core Location
- CFNetwork
- GCD/NSOperation
- Core Bluetooth (加分项)
- PassKit (加分项)
- WatchConnectivity (加分项)
- HealthKit / HealthKitUI (加分项)
- Core ML (加分项)
- 快速上手 iOS framework
- 封装 Cocoa Touch framework

Others:
- RESTful API
- OAuth 2.0
- Apple Pay / 支付宝 / 微信支付
***

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190829160140111.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjYxMzQ5Mg==,size_16,color_FFFFFF,t_70)

go实现的压测工具【单台机器100w连接压测实战】

文章分享link1st 发表了文章 • 2 个评论 • 1019 次浏览 • 2019-08-29 09:43 • 来自相关话题

本文介绍压测是什么,解释压测的专属名词,教大家如何压测。介绍市面上的常见压测工具(ab、locust、Jmeter、go实现的压测工具、云压测),对比这些压测工具,教大家如何选择一款适合自己的压测工具,本文还有两个压测实战项目: - ...查看全部
本文介绍压测是什么,解释压测的专属名词,教大家如何压测。介绍市面上的常见压测工具(ab、locust、Jmeter、go实现的压测工具、云压测),对比这些压测工具,教大家如何选择一款适合自己的压测工具,本文还有两个压测实战项目:

- 单台机器对HTTP短连接 QPS 1W+ 的压测实战
- 单台机器100W长连接的压测实战


## 目录
- 1、项目说明
- 1.1 go-stress-testing
- 1.2 项目体验本文介绍压测是什么,解释压测的专属名词,教大家如何压测。介绍市面上的常见压测工具(ab、locust、Jmeter、go实现的压测工具、云压测),对比这些压测工具,教大家如何选择一款适合自己的压测工具,本文还有两个压测实战项目:
  • 单台机器对HTTP短连接 QPS 1W+ 的压测实战
  • 单台机器100W长连接的压测实战

    原文地址 (更好的阅读格式)

目录

  • 1、项目说明
    • 1.1 go-stress-testing
    • 1.2 项目体验
  • 2、压测
    • 2.1 压测是什么
    • 2.2 为什么要压测
    • 2.3 压测名词解释
      • 2.3.1 压测类型解释
      • 2.3.2 压测名词解释
      • 2.3.3 机器性能指标解释
      • 2.3.4 访问指标解释
    • 3.4 如何计算压测指标
  • 3、常见的压测工具
    • 3.1 ab
    • 3.2 locust
    • 3.3 Jmete
    • 3.4 云压测
      • 3.4.1 云压测介绍
      • 3.4.2 阿里云 性能测试 PTS
      • 3.4.3 腾讯云 压测大师 LM
  • 4、go-stress-testing go语言实现的压测工具
    • 4.1 介绍
    • 4.2 用法
    • 4.3 实现
    • 4.4 go-stress-testing 对 Golang web 压测
  • 5、压测工具的比较
    • 5.1 比较
    • 5.2 如何选择压测工具
  • 6、单台机器100w连接压测实战
    • 6.1 说明
    • 6.2 内核优化
    • 6.3 客户端配置
    • 6.4 准备
    • 6.5 压测数据
  • 7、总结
  • 8、参考文献

1、项目说明

1.1 go-stress-testing

go 实现的压测工具,每个用户用一个协程的方式模拟,最大限度的利用CPU资源

1.2 项目体验

  • 可以在 mac/linux/windows 不同平台下执行的命令
  • go-stress-testing 压测工具下载地址

参数说明:

-c 表示并发数

-n 每个并发执行请求的次数,总请求的次数 = 并发数 * 每个并发执行请求的次数

-u 需要压测的地址

# 运行 以mac为示例
./go-stress-testing-mac -c 1 -n 100 -u https://www.baidu.com/
  • 压测结果展示

执行以后,终端每秒钟都会输出一次结果,压测完成以后输出执行的压测结果

压测结果展示:

─────┬───────┬───────┬───────┬────────┬────────┬────────┬────────┬────────
耗时│ 并发数 │ 成功数│ 失败数 │ qps │最长耗时 │最短耗时│平均耗时 │ 错误码
─────┼───────┼───────┼───────┼────────┼────────┼────────┼────────┼────────
1s│ 1│ 8│ 0│ 8.09│ 133.16│ 110.98│ 123.56│200:8
2s│ 1│ 15│ 0│ 8.02│ 138.74│ 110.98│ 124.61│200:15
3s│ 1│ 23│ 0│ 7.80│ 220.43│ 110.98│ 128.18│200:23
4s│ 1│ 31│ 0│ 7.83│ 220.43│ 110.23│ 127.67│200:31
5s│ 1│ 39│ 0│ 7.81│ 220.43│ 110.23│ 128.03│200:39
6s│ 1│ 46│ 0│ 7.72│ 220.43│ 110.23│ 129.59│200:46
7s│ 1│ 54│ 0│ 7.79│ 220.43│ 110.23│ 128.42│200:54
8s│ 1│ 62│ 0│ 7.81│ 220.43│ 110.23│ 128.09│200:62
9s│ 1│ 70│ 0│ 7.79│ 220.43│ 110.23│ 128.33│200:70
10s│ 1│ 78│ 0│ 7.82│ 220.43│ 106.47│ 127.85│200:78
11s│ 1│ 84│ 0│ 7.64│ 371.02│ 106.47│ 130.96│200:84
12s│ 1│ 91│ 0│ 7.63│ 371.02│ 106.47│ 131.02│200:91
13s│ 1│ 99│ 0│ 7.66│ 371.02│ 106.47│ 130.54│200:99
13s│ 1│ 100│ 0│ 7.66│ 371.02│ 106.47│ 130.52│200:100


************************* 结果 stat ****************************
处理协程数量: 1
请求总数: 100 总请求时间: 13.055 秒 successNum: 100 failureNum: 0
************************* 结果 end ****************************

参数解释:

耗时: 程序运行耗时。程序每秒钟输出一次压测结果

并发数: 并发数,启动的协程数

成功数: 压测中,请求成功的数量

失败数: 压测中,请求失败的数量

qps: 当前压测的QPS(每秒钟处理请求数量)

最长耗时: 压测中,单个请求最长的响应时长

最短耗时: 压测中,单个请求最短的响应时长

平均耗时: 压测中,单个请求平均的响应时长

错误码: 压测中,接口返回的 code码:返回次数的集合

2、压测

2.1 压测是什么

压测,即压力测试,是确立系统稳定性的一种测试方法,通常在系统正常运作范围之外进行,以考察其功能极限和隐患。

主要检测服务器的承受能力,包括用户承受能力(多少用户同时玩基本不影响质量)、流量承受等。

2.2 为什么要压测

  • 压测的目的就是通过压测(模拟真实用户的行为),测算出机器的性能(单台机器的QPS),从而推算出系统在承受指定用户数(100W)时,需要多少机器能支撑得住
  • 压测是在上线前为了应对未来可能达到的用户数量的一次预估(提前演练),压测以后通过优化程序的性能或准备充足的机器,来保证用户的体验。

2.3 压测名词解释

2.3.1 压测类型解释

压测类型

解释

压力测试(Stress Testing)

也称之为强度测试,测试一个系统的最大抗压能力,在强负载(大数据、高并发)的情况下,测试系统所能承受的最大压力,预估系统的瓶颈

并发测试(Concurrency Testing)

通过模拟很多用户同一时刻访问系统或对系统某一个功能进行操作,来测试系统的性能,从中发现问题(并发读写、线程控制、资源争抢)

耐久性测试(Configuration Testing)

通过对系统在大负荷的条件下长时间运行,测试系统、机器的长时间运行下的状况,从中发现问题(内存泄漏、数据库连接池不释放、资源不回收)

2.3.2 压测名词解释

压测名词

解释

并发(Concurrency)

指一个处理器同时处理多个任务的能力(逻辑上处理的能力)

并行(Parallel)

多个处理器或者是多核的处理器同时处理多个不同的任务(物理上同时执行)

QPS(每秒钟查询数量 Query Per Second)

服务器每秒钟处理请求数量 (req/sec 请求数/秒 一段时间内总请求数/请求时间)

事务(Transactions)

是用户一次或者是几次请求的集合

TPS(每秒钟处理事务数量 Transaction Per Second)

服务器每秒钟处理事务数量(一个事务可能包括多个请求)

请求成功数(Request Success Number)

在一次压测中,请求成功的数量

请求失败数(Request Failures Number)

在一次压测中,请求失败的数量

错误率(Error Rate)

在压测中,请求成功的数量与请求失败数量的比率

最大响应时间(Max Response Time)

在一次事务中,从发出请求或指令系统做出的反映(响应)的最大时间

最少响应时间(Mininum Response Time)

在一次事务中,从发出请求或指令系统做出的反映(响应)的最少时间

平均响应时间(Average Response Time)

在一次事务中,从发出请求或指令系统做出的反映(响应)的平均时间

2.3.3 机器性能指标解释

机器性能

解释

CUP利用率(CPU Usage)

CUP 利用率分用户态、系统态和空闲态,CPU利用率是指:CPU执行非系统空闲进程的时间与CPU总执行时间的比率

内存使用率(Memory usage)

内存使用率指的是此进程所开销的内存。

IO(Disk input/ output)

磁盘的读写包速率

网卡负载(Network Load)

网卡的进出带宽,包量

2.3.4 访问指标解释

访问

解释

PV(页面浏览量 Page View)

用户每打开1个网站页面,记录1个PV。用户多次打开同一页面,PV值累计多次

UV(网站独立访客 Unique Visitor)

通过互联网访问、流量网站的自然人。1天内相同访客多次访问网站,只计算为1个独立访客

2.4 如何计算压测指标

  • 压测我们需要有目的性的压测,这次压测我们需要达到什么目标(如:单台机器的性能为100QPS?网站能同时满足100W人同时在线)
  • 可以通过以下计算方法来进行计算:
  • 压测原则:每天80%的访问量集中在20%的时间里,这20%的时间就叫做峰值
  • 公式: ( 总PV数80% ) / ( 每天的秒数20% ) = 峰值时间每秒钟请求数(QPS)
  • 机器: 峰值时间每秒钟请求数(QPS) / 单台机器的QPS = 需要的机器的数量
  • 假设:网站每天的用户数(100W),每天的用户的访问量约为3000W PV,这台机器的需要多少QPS?( 30000000*0.8 ) / (86400 * 0.2) ≈ 1389 (QPS)
  • 假设:单台机器的的QPS是69,需要需要多少台机器来支撑?1389 / 69 ≈ 20

3、常见的压测工具

3.1 ab

  • 简介

ApacheBench 是 Apache服务器自带的一个web压力测试工具,简称ab。ab又是一个命令行工具,对发起负载的本机要求很低,根据ab命令可以创建很多的并发访问线程,模拟多个访问者同时对某一URL地址进行访问,因此可以用来测试目标服务器的负载压力。总的来说ab工具小巧简单,上手学习较快,可以提供需要的基本性能指标,但是没有图形化结果,不能监控。

ab属于一个轻量级的压测工具,结果不会特别准确,可以用作参考。

  • 安装
# 在linux环境安装
sudo yum -y install httpd
  • 用法
Usage: ab [options] [http[s]://]hostname[:port]/path
用法:ab [选项] 地址

选项:
Options are:
-n requests #执行的请求数,即一共发起多少请求。
-c concurrency #请求并发数。
-s timeout #指定每个请求的超时时间,默认是30秒。
-k #启用HTTP KeepAlive功能,即在一个HTTP会话中执行多个请求。默认时,不启用KeepAlive功能。
  • 压测命令
# 使用ab压测工具,对百度的链接 请求100次,并发数1
ab -n 100 -c 1 https://www.baidu.com/

压测结果

~ >ab -n 100 -c 1 https://www.baidu.com/
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking www.baidu.com (be patient).....done


Server Software: BWS/1.1
Server Hostname: www.baidu.com
Server Port: 443
SSL/TLS Protocol: TLSv1.2,ECDHE-RSA-AES128-GCM-SHA256,2048,128

Document Path: /
Document Length: 227 bytes

Concurrency Level: 1
Time taken for tests: 9.430 seconds
Complete requests: 100
Failed requests: 0
Write errors: 0
Total transferred: 89300 bytes
HTML transferred: 22700 bytes
Requests per second: 10.60 [#/sec] (mean)
Time per request: 94.301 [ms] (mean)
Time per request: 94.301 [ms] (mean, across all concurrent requests)
Transfer rate: 9.25 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 54 70 16.5 69 180
Processing: 18 24 12.0 23 140
Waiting: 18 24 12.0 23 139
Total: 72 94 20.5 93 203

Percentage of the requests served within a certain time (ms)
50% 93
66% 99
75% 101
80% 102
90% 108
95% 122
98% 196
99% 203
100% 203 (longest request)
  • 主要关注的测试指标
  • Concurrency Level 并发请求数
  • Time taken for tests 整个测试时间
  • Complete requests 完成请求个数
  • Failed requests 失败个数
  • Requests per second 吞吐量,指的是某个并发用户下单位时间内处理的请求数。等效于QPS,其实可以看作同一个统计方式,只是叫法不同而已。
  • Time per request 用户平均请求等待时间
  • Time per request 服务器处理时间

3.2 Locust

  • 简介

是非常简单易用、分布式、python开发的压力测试工具。有图形化界面,支持将压测数据导出。

  • 安装
# pip3 安装locust
pip3 install locust
# 查看是否安装成功
locust -h
# 运行 Locust 分布在多个进程/机器库
pip3 install pyzmq
# webSocket 压测库
pip3 install websocket-client
  • 用法

编写压测脚本 test.py

from locust import HttpLocust, TaskSet, task

# 定义用户行为
class UserBehavior(TaskSet):

@task
def baidu_index(self):
self.client.get("/")


class WebsiteUser(HttpLocust):
task_set = UserBehavior # 指向一个定义的用户行为类
min_wait = 3000 # 执行事务之间用户等待时间的下界(单位:毫秒)
max_wait = 6000 # 执行事务之间用户等待时间的上界(单位:毫秒)
  • 启动压测
locust -f  test.py --host=https://www.baidu.com

访问 http://localhost:8089 进入压测首页

Number of users to simulate 模拟用户数

Hatch rate (users spawned/second) 每秒钟增加用户数

点击 "Start swarming" 进入压测页面

<figure></figure>locust 首页

压测界面右上角有:被压测的地址、当前状态、RPS、失败率、开始或重启按钮

性能测试参数

  • Type 请求的类型,例如GET/POST
  • Name 请求的路径
  • Request 当前请求的数量
  • Fails 当前请求失败的数量
  • Median 中间值,单位毫秒,请求响应时间的中间值
  • Average 平均值,单位毫秒,请求的平均响应时间
  • Min 请求的最小服务器响应时间,单位毫秒
  • Max 请求的最大服务器响应时间,单位毫秒
  • Average size 单个请求的大小,单位字节
  • Current RPS 代表吞吐量(Requests Per Second的缩写),指的是某个并发用户数下单位时间内处理的请求数。等效于QPS,其实可以看作同一个统计方式,只是叫法不同而已。
<figure>
<figcaption>locust 压测页面</figcaption>
</figure>

3.3 JMete

  • 简介

Apache JMeter是Apache组织开发的基于Java的压力测试工具。用于对软件做压力测试,它最初被设计用于Web应用测试,但后来扩展到其他测试领域。

JMeter能够对应用程序做功能/回归测试,通过创建带有断言的脚本来验证你的程序返回了你期望的结果。

  • 安装

访问 https://jmeter-plugins.org/install/Install/ 下载解压以后即可使用

  • 用法

JMeter的功能过于强大,这里暂时不介绍用法,可以查询相关文档使用(参考文献中有推荐的教程文档)

3.4 云压测

3.4.1 云压测介绍

顾名思义就是将压测脚本部署在云端,通过云端对对我们的应用进行全方位压测,只需要配置压测的参数,无需准备实体机,云端自动给我们分配需要压测的云主机,对被压测目标进行压测。

云压测的优势:

  1. 轻易的实现分布式部署
  2. 能够模拟海量用户的访问
  3. 流量可以从全国各地发起,更加真实的反映用户的体验
  4. 全方位的监控压测指标
  5. 文档比较完善

当然了云压测是一款商业产品,在使用的时候自然还是需要收费的,而且价格还是比较昂贵的~

3.4.2 阿里云 性能测试 PTS

PTS(Performance Testing Service)是面向所有技术背景人员的云化测试工具。有别于传统工具的繁复,PTS以互联网化的交互,提供性能测试、API调试和监测等多种能力。自研和适配开源的功能都可以轻松模拟任意体量的用户访问业务的场景,任务随时发起,免去繁琐的搭建和维护成本。更是紧密结合监控、流控等兄弟产品提供一站式高可用能力,高效检验和管理业务性能。

阿里云同样还是支持渗透测试,通过模拟黑客对业务系统进行全面深入的安全测试。

3.4.3 腾讯云 压测大师 LM

通过创建虚拟机器人模拟多用户的并发场景,提供一整套完整的服务器压测解决方案

4、go-stress-testing go语言实现的压测工具

4.1 介绍

  • go-stress-testing 是go语言实现的简单压测工具,源码开源、支持二次开发,可以压测http、webSocket请求,使用协程模拟单个用户,可以更高效的利用CPU资源。
  • 项目地址 https://github.com/link1st/go-stress-testing

4.2 用法

Usage of ./go-stress-testing-mac:
-c uint
并发数 (default 1)
-d string
调试模式 (default "false")
-n uint
请求总数 (default 1)
-p string
curl文件路径
-u string
请求地址
-v string
验证方法 http 支持:statusCode、json webSocket支持:json (default "statusCode")
  • -n 是单个用户请求的次数,请求总次数 = -c* -n, 这里考虑的是模拟用户行为,所以这个是每个用户请求的次数
  • 下载以后执行下面命令即可压测
  • 使用示例:
# 查看用法
./go-stress-testing-mac

# 使用请求百度页面
./go-stress-testing-mac -c 1 -n 100 -u https://www.baidu.com/

# 使用debug模式请求百度页面
./go-stress-testing-mac -c 1 -n 1 -d true -u https://www.baidu.com/

# 使用 curl文件(文件在curl目录下) 的方式请求
./go-stress-testing-mac -c 1 -n 1 -p curl/baidu.curl.txt

# 压测webSocket连接
./go-stress-testing-mac -c 10 -n 10 -u ws://127.0.0.1:8089/acc
  • 使用 curl文件进行压测

curl是Linux在命令行下的工作的文件传输工具,是一款很强大的http命令行工具。

使用curl文件可以压测使用非GET的请求,支持设置http请求的 method、cookies、header、body等参数

chrome 浏览器生成 curl文件,打开开发者模式(快捷键F12),如图所示,生成 curl 在终端执行命令

<figure>
<figcaption>copy cURL</figcaption>
</figure>

生成内容粘贴到项目目录下的curl/baidu.curl.txt文件中,执行下面命令就可以从curl.txt文件中读取需要压测的内容进行压测了

# 使用 curl文件(文件在curl目录下) 的方式请求
go run main.go -c 1 -n 1 -p curl/baidu.curl.txt

4.3 实现

  • 具体需求可以查看项目源码
  • 项目目录结构
|____main.go                      // main函数,获取命令行参数
|____server // 处理程序目录
| |____dispose.go // 压测启动,注册验证器、启动统计函数、启动协程进行压测
| |____statistics // 统计目录
| | |____statistics.go // 接收压测统计结果并处理
| |____golink // 建立连接目录
| | |____http_link.go // http建立连接
| | |____websocket_link.go // webSocket建立连接
| |____client // 请求数据客户端目录
| | |____http_client.go // http客户端
| | |____websocket_client.go // webSocket客户端
| |____verify // 对返回数据校验目录
| | |____http_verify.go // http返回数据校验
| | |____websokcet_verify.go // webSocket返回数据校验
|____heper // 通用函数目录
| |____heper.go // 通用函数
|____model // 模型目录
| |____request_model.go // 请求数据模型
| |____curl_model.go // curl文件解析
|____vendor // 项目依赖目录

4.4 go-stress-testing 对 Golang web 压测

这里使用go-stress-testing对go server进行压测(部署在同一台机器上),并统计压测结果

  • 申请的服务器配置

CPU: 4核 (Intel Xeon(Cascade Lake) Platinum 8269 2.5 GHz/3.2 GHz)

内存: 16G

硬盘: 20G SSD

系统: CentOS 7.6

go version: go1.12.9 linux/amd64

<figure>
<figcaption>go-stress-testing01</figcaption>
</figure>
  • go serve
package main

import (
"log"
"net/http"
)

const (
httpPort = "8088"
)

func main() {

runtime.GOMAXPROCS(runtime.NumCPU() - 1)

hello := func(w http.ResponseWriter, req *http.Request) {
data := "Hello, World!"

w.Header().Add("Server", "golang")
w.Write([]byte(data))

return
}

http.HandleFunc("/", hello)
err := http.ListenAndServe(":"+httpPort, nil)

if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
  • go_stress_testing 压测命令
./go_stress_testing_linux -c 100 -n 10000 -u http://127.0.0.1:8088/
  • 压测结果

并发数

go_stress_testing QPS

1

6394.86

4

16909.36

10

18456.81

20

19490.50

30

19947.47

50

19922.56

80

19155.33

100

18336.46

200

16813.86

从压测的结果上看:效果还不错,压测QPS有接近2W

5、压测工具的比较

5.1 比较

ab

locust

Jmeter

go-stress-testing

云压测

实现语言

C

Python

Java

Golang

UI界面

优势

使用简单,上手简单

支持分布式、压测数据支持导出

插件丰富,支持生成HTML报告

项目开源,使用简单,没有依赖,支持webSocket压测

更加真实的模拟用户,支持更高的压测力度

5.2 如何选择压测工具

这个世界上没有最好的,只有最适合的,工具千千万,选择一款适合你的才是最重要的

在实际使用中有各种场景,选择工具的时候就需要考虑这些:

  • 明确你的目的,需要做什么压测、压测的目标是什么?
  • 使用的工具你是否熟悉,你愿意花多大的成本了解它?
  • 你是为了测试还是想了解其中的原理?
  • 工具是否能支持你需要压测的场景

6、单台机器100w连接压测实战

6.1 说明

之前写了一篇文章,基于websocket单台机器支持百万连接分布式聊天(IM)系统(不了解这个项目可以查看上一篇或搜索一下文章),这里我们要实现单台机器支持100W连接的压测

目标:

  • 单台机器能保持100W个长连接
  • 机器的CPU、内存、网络、I/O 状态都正常

说明:

gowebsocket 分布式聊天(IM)系统:

  • 之前用户连接以后有个全员广播,这里需要将用户连接、退出等事件关闭
  • 服务器准备:由于自己手上没有自己的服务器,所以需要临时购买的云服务器

压测服务器:

16台(稍后解释为什么需要16台机器)

CPU: 2核

内存: 8G

硬盘: 20G

系统: CentOS 7.6

<figure>
<figcaption>webSocket压测服务器</figcaption>
</figure>

被压测服务:

1台

CPU: 4核

内存: 32G

硬盘: 20G SSD

系统: CentOS 7.6

<figure>
<figcaption>webSocket被压测服务器</figcaption>
</figure>

6.2 内核优化

  • 修改程序最大打开文件数

被压测服务器需要保持100W长连接,客户和服务器端是通过socket通讯的,每个连接需要建立一个socket,程序需要保持100W长连接就需要单个程序能打开100W个文件句柄

# 查看系统默认的值
ulimit -n
# 设置最大打开文件数
ulimit -n 1040000

这里设置的要超过100W,程序除了有100W连接还有其它资源连接(数据库、资源等连接),这里设置为 104W

centOS 7.6 上述设置不生效,需要手动修改配置文件

vim /etc/security/limits.conf

这里需要把硬限制和软限制、root用户和所有用户都设置为 1040000

core 是限制内核文件的大小,这里设置为 unlimited

# 添加以下参数
root soft nofile 1040000
root hard nofile 1040000

root soft nofile 1040000
root hard nproc 1040000

root soft core unlimited
root hard core unlimited

* soft nofile 1040000
* hard nofile 1040000

* soft nofile 1040000
* hard nproc 1040000

* soft core unlimited
* hard core unlimited

注意:

/proc/sys/fs/file-max 表示系统级别的能够打开的文件句柄的数量,不能小于limits中设置的值

如果file-max的值小于limits设置的值会导致系统重启以后无法登录

# file-max 设置的值参考
cat /proc/sys/fs/file-max
12553500

修改以后重启服务器,ulimit -n 查看配置是否生效

6.3 客户端配置

由于linux端口的范围是 0~65535(2^16-1)这个和操作系统无关,不管linux是32位的还是64位的

这个数字是由于tcp协议决定的,tcp协议头部表示端口只有16位,所以最大值只有65535(如果每台机器多几个虚拟ip就能突破这个限制)

1024以下是系统保留端口,所以能使用的1024到65535

如果需要100W长连接,每台机器有 65535-1024 个端口, 100W / (65535-1024) ≈ 15.5,所以这里需要16台服务器

  • vim /etc/sysctl.conf 在文件末尾添加
net.ipv4.ip_local_port_range = 1024 65000
net.ipv4.tcp_mem = 786432 2097152 3145728
net.ipv4.tcp_rmem = 4096 4096 16777216
net.ipv4.tcp_wmem = 4096 4096 16777216

sysctl -p 修改配置以后使得配置生效命令

配置解释:

  • ip_local_port_range 表示TCP/UDP协议允许使用的本地端口号 范围:1024~65000
  • tcp_mem 确定TCP栈应该如何反映内存使用,每个值的单位都是内存页(通常是4KB)。第一个值是内存使用的下限;第二个值是内存压力模式开始对缓冲区使用应用压力的上限;第三个值是内存使用的上限。在这个层次上可以将报文丢弃,从而减少对内存的使用。对于较大的BDP可以增大这些值(注意,其单位是内存页而不是字节)
  • tcp_rmem 为自动调优定义socket使用的内存。第一个值是为socket接收缓冲区分配的最少字节数;第二个值是默认值(该值会被rmem_default覆盖),缓冲区在系统负载不重的情况下可以增长到这个值;第三个值是接收缓冲区空间的最大字节数(该值会被rmem_max覆盖)。
  • tcp_wmem 为自动调优定义socket使用的内存。第一个值是为socket发送缓冲区分配的最少字节数;第二个值是默认值(该值会被wmem_default覆盖),缓冲区在系统负载不重的情况下可以增长到这个值;第三个值是发送缓冲区空间的最大字节数(该值会被wmem_max覆盖)。

6.4 准备

  1. 在被压测服务器上启动Server服务(gowebsocket)
  2. 查看被压测服务器的内网端口
  3. 登录上16台压测服务器,这里我提前把需要优化的系统做成了镜像,申请机器的时候就可以直接使用这个镜像(参数已经调好)
<figure>
<figcaption>压测服务器16台准备</figcaption>
</figure>
  1. 启动压测
 ./go_stress_testing_linux -c 62500 -n 1  -u ws://192.168.0.74:443/acc

62500*16 = 100W正好可以达到我们的要求

建立连接以后,-n 1发送一个ping的消息给服务器,收到响应以后保持连接不中断

  1. 通过 gowebsocket服务器的http接口,实时查询连接数和项目启动的协程数
  2. 压测过程中查看系统状态
# linux 命令
ps # 查看进程内存、cup使用情况
iostat # 查看系统IO情况
nload # 查看网络流量情况
/proc/pid/status # 查看进程状态

6.5 压测数据

  • 压测以后,查看连接数到100W,然后保持10分钟观察系统是否正常
  • 观察以后,系统运行正常、CPU、内存、I/O 都正常,打开页面都正常
  • 压测完成以后的数据

查看goWebSocket连接数统计,可以看到 clientsLen连接数为100W,goroutine数量2000008个,每个连接两个goroutine加上项目启动默认的8个。这里可以看到连接数满足了100W

<figure>
<figcaption>查看goWebSocket连接数统计</figcaption>
</figure>

从压测服务上查看连接数是否达到了要求,压测完成的统计数据并发数为62500,是每个客户端连接的数量,总连接数: 62500*16=100W

<figure>
<figcaption>压测服务16台 压测完成</figcaption>
</figure>
  • 记录内存使用情况,分别记录了1W到100W连接数内存使用情况

连接数

内存

10000

281M

100000

2.7g

200000

5.4g

500000

13.1g

1000000

25.8g

100W连接时的查看内存详细数据:

cat /proc/pid/status
VmSize: 27133804 kB

27133804/1000000≈27.1 100W连接,占用了25.8g的内存,粗略计算了一下,一个连接占用了27.1Kb的内存,由于goWebSocket项目每个用户连接起了两个协程处理用户的读写事件,所以内存占用稍微多一点

如果需要如何减少内存使用可以参考 @Roy11568780 大佬给的解决方案

传统的golang中是采用的一个goroutine循环read的方法对应每一个socket。实际百万链路场景中这是巨大的资源浪费,优化的原理也不是什么新东西,golang中一样也可以使用epoll的,把fd拿到epoll中,检测到事件然后在协程池里面去读就行了,看情况读写分别10-20的协程goroutine池应该就足够了

至此,压测已经全部完成,单台机器支持100W连接已经满足~

7、总结

到这里压测总算完成,本次压测花费16元巨款。

单台机器支持100W连接是实测是满足的,但是实际业务比较复杂,还是需要持续优化~

本文通过介绍什么是压测,在什么情况下需要压测,通过单台机器100W长连接的压测实战了解Linux内核的参数的调优。如果觉得现有的压测工具不适用,可以自己实现或者是改造成属于自己的自己的工具。

8、参考文献

性能测试工具

性能测试常见名词解释

性能测试名词解释

PV、TPS、QPS是怎么计算出来的?

超实用压力测试工具-ab工具

Locust 介绍

Jmeter性能测试 入门

基于websocket单台机器支持百万连接分布式聊天(IM)系统

https://github.com/link1st/go-stress-testing

github 搜:link1st 查看项目 go-stress-testing


条新动态, 点击查看
astaxie

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

大家推荐哪种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 22:12 • 4 个回复 不感兴趣

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

赞同来自:

len出来的值是检查用于计算的,如果是uint的话,那么加减一下负数就不行了 详见:https://groups.google.com/forum/#!topic/golang-nuts/jJWAAMdquwQ
len出来的值是检查用于计算的,如果是uint的话,那么加减一下负数就不行了 详见:https://groups.google.com/forum/#!topic/golang-nuts/jJWAAMdquwQ
name5566

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

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

赞同来自:

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

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

求一些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的话相对重了一点。
sheepbao

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

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

赞同来自:

```go package main import ( "bytes" "fmt" "strings" "time" ) var way map[int]string func benchmarkStringFunction(n int, ind... 显示全部 »
```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倍
ecofast

ecofast 回答了问题 • 2018-02-25 14:00 • 8 个回复 不感兴趣

发现一个非常不错的性能优化的视频

赞同来自:

要点简单小结: 1) Don't use Go, use Assembly --> NO. 2) Don't use Go, use C/C++ --> NO. Go 1.4 的编译速度非常快,后因编译器开始自举,编译速度下降不少,但一直在改进 3) C... 显示全部 »
要点简单小结: 1) Don't use Go, use Assembly --> NO. 2) Don't use Go, use C/C++ --> NO. Go 1.4 的编译速度非常快,后因编译器开始自举,编译速度下降不少,但一直在改进 3) CGO --> 应审慎地通过 CGO 来使用经由 C/C++ 高度优化过的代码库(如加解密算法),且每次 CGO 调用约有 150ns 的额外开销 4) Go 的编译器优化水准一直在提升,通常来说,新版本的 Go 能生成更优化的机器码 5) 在 Go 中作 Benchmark 非常容易,能用它来很方便地分析 CPU 及内存分配等性能热点 6) pprof 是很实用的性能剖析工具 7) Go 的 GC 实现一直在改进,而且改进明显 8) 不应在循环里作频繁的内存分配等工作 --> 耗时且加重 GC 负担,可善用 bytes.Buffer 和 sync.Pool 等设施 9) 避免内存碎片 --> 逃逸分析对性能的影响 10) 锁本身并不慢,慢的是竞争 --> sync/atomic 在某些场合是好东西 11) hashmap 查找虽快(O(1) 的时间复杂度),但元素数量对查找速度有明显影响 --> map 的初始容量、粒度问题
九命猫

九命猫 回答了问题 • 2016-10-31 10:52 • 5 个回复 不感兴趣

go中如何连接两个slice

赞同来自:

```go append([]int{1, 2}, []int{3, 4}...) ```
```go append([]int{1, 2}, []int{3, 4}...) ```
astaxie

astaxie 回答了问题 • 2016-11-13 13:29 • 4 个回复 不感兴趣

golang vendor路径问题

赞同来自:

刚和 @viktor1992 看了一下源码,看到这个vendor的函数 ``` // vendoredImportPath returns the expansion of path when it appears in parent. // If pare... 显示全部 »
刚和 @viktor1992 看了一下源码,看到这个vendor的函数 ``` // vendoredImportPath returns the expansion of path when it appears in parent. // If parent is x/y/z, then path might expand to x/y/z/vendor/path, x/y/vendor/path, // x/vendor/path, vendor/path, or else stay path if none of those exist. // vendoredImportPath returns the expanded path or, if no expansion is found, the original. func vendoredImportPath(parent *Package, path string) (found string) { if parent == nil || parent.Root == "" { return path } ``` 这个里面有一个概念parent,我们可以看到Go官方关于vendor的文档设计的时候都有这个,也就是说你的源码要有一个包存起来,我们看一下这个实验里面的代码。 首先`a.go`放在了gopath低目录,也就是他的parent是nil,所以这个返回了`wer`,而这个包又不在gopath下面,所以第一个报错了。 第二个你创建了test目录,那么他的parent就是test,那么就会去找test/vendor/path和vendor/path下面找,所以vendor不管你放在test下面还是gopath下面都是可以找到的。
傅小黑

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

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, &r) } ``` ... 显示全部 »
```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`才是自己的项目,这样会更清晰。
EasyHacking

EasyHacking 回答了问题 • 2018-02-26 21:54 • 45 个回复 不感兴趣

Go 零基础编程入门教程

赞同来自:

课程持续更新中,欢迎大家持续关注我们课程。 欢迎对课程提建议,也欢迎加Q群一起学习交流:694650181
课程持续更新中,欢迎大家持续关注我们课程。 欢迎对课程提建议,也欢迎加Q群一起学习交流:694650181
chenqinghe

chenqinghe 回答了问题 • 2018-07-31 08:42 • 6 个回复 不感兴趣

忽略 Close() 的 error 是不是安全的呢?

赞同来自:

如果不想忽略可以这样: ```go func hi()(err error){ f,err:= os.Create("a.txt") if err!=nil { return err } defer func(){ if e:= f.Clos... 显示全部 »
如果不想忽略可以这样: ```go func hi()(err error){ f,err:= os.Create("a.txt") if err!=nil { return err } defer func(){ if e:= f.Close();e!=nil { err = e } }() return nil } ```

关于规范招聘信息发帖说明

招聘应聘 回复了问题 • 9 人关注 • 4 个回复 • 5235 次浏览 • 2019-10-04 10:59 • 来自相关话题

Go 零基础编程入门教程

文章分享ducklife 回复了问题 • 104 人关注 • 45 个回复 • 30145 次浏览 • 2019-08-19 12:59 • 来自相关话题

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

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

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

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

Authors: startover * [startover's Github](https://github.com/startover) * [Cloudinsight](http://cloudinsight.oneapm.c ...查看全部
Authors: startover
* [startover's Github](https://github.com/startover)
* [Cloudinsight](http://cloudinsight.oneapm.com/?utm_source=CiTechBlog&utm_medium=gocn&utm_term=pythongolang2&utm_campaign=CiTech&from=jscwgyegna) 工程师

------------


[上一篇文章](https://gocn.io/article/36)我们已经对 Golang 有了初步的了解,这篇主要介绍如何在 Ubuntu 14.04 上搭建 Golang 开发环境。

## 安装 Golang

这里就按照[官方文档](https://golang.org/doc/install#install)进行安装即可,如下:

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

```
$ wget https://storage.googleapis.com/golang/go1.6.3.linux-amd64.tar.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 的开发目录,详细可参考[官方文档](https://golang.org/cmd/go/#hdr-GOPATH_environment_variable)。

## 开发工具

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

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

在 [k-vim](https://github.com/wklken/k-vim) 中开启 Golang 语言的支持,非常简单,如下:

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

```bash
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 s (go-implements)
au FileType go nmap i (go-info)
au FileType go nmap gd (go-doc)
au FileType go nmap gv (go-doc-vertical)
au FileType go nmap r (go-run)
au FileType go nmap b (go-build)
au FileType go nmap t (go-test)
au FileType go nmap c (go-coverage)
au FileType go nmap ds (go-def-split)
au FileType go nmap dv (go-def-vertical)
au FileType go nmap dt (go-def-tab)
au FileType go nmap e (go-rename)
au FileType go nnoremap gr :GoRun %
" }}}
```

* 在 Vim 内执行 `:PlugInstall`,安装 [vim-go](https://github.com/fatih/vim-go)。

* 在 Vim 内执行 `:GoInstallBinaries`,下载并安装 [vim-go](https://github.com/fatih/vim-go) 依赖的二进制工具,`goimports`,`golint` 等。

* 安装 [gotags](https://github.com/jstemmer/gotags),使 `tagbar` 配置生效。

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

我们来看一下最终效果:

![Image of Golang Environment in Vim](http://startover.github.io/images/golang-for-pythonistas-environment.png)


## 编写第一个程序

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

```
$ cd $GOPATH
$ vim hello.go
package main

import "fmt"

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

运行程序:

```
$ go run hello.go
Hello, World!
```


------------
本文章为 [Cloudinsight](http://cloudinsight.oneapm.com/?utm_source=CiTechBlog&utm_medium=gocn&utm_term=pythongolang1&utm_campaign=CiTech&from=jscwgyegna) 技术团队工程师原创,更多技术文章可访问 [Cloudinsight 技术博客](http://cloudinsight.oneapm.com/blog/?utm_source=CiTechBlog&utm_medium=gocn&utm_term=pythongolang1&utm_campaign=CiTech&from=jscwgyegna)。[Cloudinsight](http://cloudinsight.oneapm.com/?utm_source=CiTechBlog&utm_medium=gocn&utm_term=pythongolang1&utm_campaign=CiTech&from=jscwgyegna) 为可视化系统监控工具,涵盖 Windows、Linux 操作系统,用 Golang 开发的 Cloudinsight Agent 正式开源了,欢迎 fork,Github:https://github.com/cloudinsight/cloudinsight-agent


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

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

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

Authors: startover * [startover's Github](https://github.com/startover) * [Cloudinsight](http://cloudinsight.oneapm.c ...查看全部
Authors: startover
* [startover's Github](https://github.com/startover)
* [Cloudinsight](http://cloudinsight.oneapm.com/?utm_source=CiTechBlog&utm_medium=gocn&utm_term=pythongolang1&utm_campaign=CiTech&from=jscwgyegna) 工程师

------------



## Go 语言简介

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

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

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

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

总结为以下几点:

* 清晰的依赖关系
* 清晰的语法
* 清晰的语义
* 偏向组合而不是继承
* 提供简单的编程模型(垃圾回收、并发)
* 强大的内置工具(gofmt、godoc、gofix等)

建议有兴趣的同学看看 [Go在谷歌:以软件工程为目的的语言设计](http://www.oschina.net/translate/go-at-google-language-design-in-the-service-of-software-engineering)。

## Go 语言相对 Python 有哪些优势

这里引用一段[知乎](https://www.zhihu.com/question/21409296)上某大牛的回答,如下:

* **部署简单**。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](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 新手开发者要注意的陷阱和常见错误](http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/)。

## 学习资料推荐

建议先把 Go 的[官方文档](https://golang.org/doc/)过一遍,主要有以下几项:

* [A Tour of Go](https://tour.golang.org/welcome/1)
* [How to write Go code](https://golang.org/doc/code.html)
* [Effective Go](https://golang.org/doc/effective_go.html)
* [Language Specification](https://golang.org/ref/spec)

官方文档看完后,基本也算入门了,这时候可以看看 [Go 的示例代码](https://gobyexample.com/),或者去 [Project Euler](https://projecteuler.net/) 刷刷题。

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

## 总结

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



相关链接:
[https://golang.org/doc/](https://golang.org/doc/)
[https://talks.golang.org/2012/splash.article](https://talks.golang.org/2012/splash.article)
[https://www.zhihu.com/question/21409296](https://www.zhihu.com/question/21409296)
[https://www.zhihu.com/question/23486344](https://www.zhihu.com/question/23486344)
[http://stackoverflow.com/questions/7933460/how-do-you-write-multiline-strings-in-go](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://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](http://www.oschina.net/translate/go-at-google-language-design-in-the-service-of-software-engineering)


------------



本文章为 [Cloudinsight](http://cloudinsight.oneapm.com/?utm_source=CiTechBlog&utm_medium=gocn&utm_term=pythongolang1&utm_campaign=CiTech&from=jscwgyegna) 技术团队工程师原创,更多技术文章可访问 [Cloudinsight 技术博客](http://cloudinsight.oneapm.com/blog/?utm_source=CiTechBlog&utm_medium=gocn&utm_term=pythongolang1&utm_campaign=CiTech&from=jscwgyegna)。[Cloudinsight](http://cloudinsight.oneapm.com/?utm_source=CiTechBlog&utm_medium=gocn&utm_term=pythongolang1&utm_campaign=CiTech&from=jscwgyegna) 为可视化系统监控工具,涵盖 Windows、Linux 操作系统,用 Golang 开发的 Cloudinsight Agent 正式开源了,欢迎 fork,Github:https://github.com/cloudinsight/cloudinsight-agent


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

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

有问必答lesliehuang 回复了问题 • 73 人关注 • 40 个回复 • 14710 次浏览 • 2019-08-13 19:06 • 来自相关话题

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

技术讨论ducklife 回复了问题 • 33 人关注 • 14 个回复 • 29435 次浏览 • 2019-08-19 12:59 • 来自相关话题

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

有问必答ducklife 回复了问题 • 5 人关注 • 4 个回复 • 5491 次浏览 • 2019-08-19 13:00 • 来自相关话题

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

有问必答asdfsx 回复了问题 • 38 人关注 • 28 个回复 • 37781 次浏览 • 2018-03-05 18:50 • 来自相关话题

关于规范招聘信息发帖说明

回复

招聘应聘 回复了问题 • 9 人关注 • 4 个回复 • 5235 次浏览 • 2019-10-04 10:59 • 来自相关话题

GoCN每日新闻(2019-10-07)

回复

每日新闻smallfish1 发起了问题 • 1 人关注 • 0 个回复 • 6688 次浏览 • 2019-10-07 15:42 • 来自相关话题

为什么gRPC客户端不提供连接池?

回复

技术讨论tsingson 回复了问题 • 9 人关注 • 6 个回复 • 15787 次浏览 • 2019-10-03 02:58 • 来自相关话题

花了半天用go写一个带ui的redis客户端

回复

Go开源项目ownGolang 回复了问题 • 6 人关注 • 5 个回复 • 1311 次浏览 • 2019-09-18 10:17 • 来自相关话题

GoCN每日新闻(2019-09-10)

回复

每日新闻smallfish1 发起了问题 • 1 人关注 • 0 个回复 • 13767 次浏览 • 2019-09-10 11:30 • 来自相关话题

http接口调试

回复

有问必答tsingson 回复了问题 • 7 人关注 • 8 个回复 • 814 次浏览 • 2019-08-19 13:50 • 来自相关话题

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

回复

有问必答ducklife 回复了问题 • 5 人关注 • 4 个回复 • 5491 次浏览 • 2019-08-19 13:00 • 来自相关话题

Go 零基础编程入门教程

回复

文章分享ducklife 回复了问题 • 104 人关注 • 45 个回复 • 30145 次浏览 • 2019-08-19 12:59 • 来自相关话题

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

回复

技术讨论ducklife 回复了问题 • 33 人关注 • 14 个回复 • 29435 次浏览 • 2019-08-19 12:59 • 来自相关话题

在群里看到一段代码,是内存模型的问题还是协程调度的问题呢?

回复

有问必答h12 回复了问题 • 5 人关注 • 4 个回复 • 559 次浏览 • 2019-08-13 20:01 • 来自相关话题

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

回复

有问必答lesliehuang 回复了问题 • 73 人关注 • 40 个回复 • 14710 次浏览 • 2019-08-13 19:06 • 来自相关话题

golang开发

回复

招聘应聘君莫笑 发起了问题 • 1 人关注 • 0 个回复 • 428 次浏览 • 2019-08-01 11:20 • 来自相关话题

golang的错误处理机制是什么???

回复

有问必答dncmn 发起了问题 • 1 人关注 • 0 个回复 • 312 次浏览 • 2019-07-18 17:18 • 来自相关话题

GoCN每日新闻(2019-07-15)

回复

每日新闻smallfish1 发起了问题 • 1 人关注 • 0 个回复 • 40075 次浏览 • 2019-07-15 11:23 • 来自相关话题

对channel的理解

回复

有问必答lrita 回复了问题 • 4 人关注 • 1 个回复 • 291 次浏览 • 2019-07-13 20:40 • 来自相关话题

Excelize 发布 2.0.2 版本, Go 语言 Excel 文档基础库

Go开源项目xuri 发表了文章 • 0 个评论 • 234 次浏览 • 4 天前 • 来自相关话题


Excelize 是 Go 语言编写的用于操作 Office Excel 文档类库,基于 ECMA-376 Office Open XML 标准。可以使用它来读取、写入由 Microsoft Excel™ 2007 及以上版本创建的 XLSX 文档。相比较其他的开源类库,Excelize 支持写入原本带有图片(表)、透视表和切片器等复杂样式的文档,还支持向 Excel 文档中插入图片与图表,并且在保存后不会丢失文档原有样式,可以应用于各类报表系统中。入选 2018 开源中国码云 Gitee 最有价值开源项目 GVP,目前已成为 Go 语言最受欢迎的 Excel 文档基础库。

开源代码

GitHub: github.com/xuri/excelize
Gitee: gitee.com/xurime/excelize
中文文档: xuri.me/excelize/zh-hans

Excelize 知名用户


2019年10月9日,社区正式发布了 2.0.2 版本,该版本包含了多项新增功能、错误修复和兼容性提升优化。下面是有关该版本更新内容的摘要,完整的更改列表可查看 change log

有关更改的摘要,请参阅 Release Notes。完整的更改列表可查看 change log

Release Notes

此版本中最显著的变化包括:

兼容性提示

升级至该版本需要您的 Go 语言版本高于 1.10。

新增功能

问题修复

  • 修复部分情况下读取批注内容文本不完整的问题,解决 issue #434
  • 修复由于内部合并单元格偏移量计算错误导致的部分情况下使用 RemoveRow() 删除行出现下标越界问题,解决 issue #437
  • 修复部分情况下数据验证下拉菜单中的公式失效问题
  • 修复在循环迭代中调用 Save() 方法保存导致的文档损坏问题,解决 issue #443
  • 提升文档内部 workbook.xml.rels 中相对路径格式解析的兼容性,解决 issue #442
  • 修复部分情况下,删除带有合并单元格的文档所导致的文件损坏问题
  • 修复部分情况下设置保护工作表属性失效的情况,解决 issue #454
  • 修复部分情况下 GetSheetName 获取工作表名称为空的问题, 解决 issue #457
  • 增加单元格内多行文本解析的支持, 相关 issue #464
  • 修复 32 位操作系统环境下数字溢出问题,相关 issue #386
  • 修复 go module 依赖版本不匹配问题, 相关 issue #466 和 issue #480
  • 修复部分情况下调用 SetSheetPrOptions() 所致的文档损坏问题,解决 issue #483

性能表现

  • 性能优化,减少读取文档时的内存开销和耗时,相关 issue #439

其他

  • 完善 SetSheetRow() 函数中的异常处理
  • 代码精简优化, 合并了下列内部函数:

将函数 workBookRelsWriterdrawingRelsWriter 合并为 relsWriter;
将函数 drawingRelsReaderworkbookRelsReaderworkSheetRelsReader 合并为 relsReader;
将函数 addDrawingRelationshipsaddSheetRelationships 合并为 addRels

【杭州】河象网络科技 招聘 Golang 工程师 5 名

招聘应聘airylinus 发表了文章 • 0 个评论 • 374 次浏览 • 2019-09-29 21:02 • 来自相关话题

我们是谁?我们做什么?       杭州河象网络科技有限公司,成立于 2017 年 5 月,以“为孩 ...查看全部

我们是谁?我们做什么?


       杭州河象网络科技有限公司,成立于 2017 年 5 月,以“为孩子提供有效、有趣的普惠教育”为使命,是一家专注3-12岁少儿素质教育的在线教育公司。“河小象” 是公司旗下品牌,采用“AI+教育”模式,融合图像识别、语音测评、智能匹配、人机交互及大数据分析等创新科技,自主研发课程。以“大语文综合素养”为核心,同时推出了写字、书法、美术等明星产品,致力于帮助孩子提高综合素养。

       公司拥有浙江大学、北京师范大学等知名院校教授、博士组成的教研团队,并由原阿里巴巴顶级技术团队负责研发,回归教育本质,持续不断地升级学习体验。服务超过两百万学员,覆盖北京、上海、广州、杭州等270余个城市。同时,河小象已完成 2 亿元人民币 B 轮融资,由创新工场、贝塔斯曼、好未来及上一轮投资方元璟、亦联、金沙江、志拙资本等投资。

我们提供的什么待遇和福利


- 公司提供五险一金,签署劳动合同,办理社保。
- 办公地点在 A 级写字楼:余杭区未来科技城创鑫时代广场。
- 配备办公桌、饮水间,技术开发配备 Macbook 作为办公设备。
- 享受国家法定节假日,中秋、端午会有礼品福利。
- 在招的 Golang 开发工程师岗位的薪资在 10000 - 25000,若技术水平超过岗位标准可另议。

我们对候选人的要求


- **1** 年以上 Golang 开发经验,熟悉语言特性,基础扎实。
- 有良好的编码风格,能进行测试驱动开发、集成测试,能主动改进开发过程和提高交付质量。
- 熟悉常用关系数据库( MySQL,PostgreSQL )的使用经验,能有意识的改进查询性能。
- 熟悉 Linux 操作系统,熟练使用 Docker 相关技术,了解常用运维管理操作。
- 有良好的自学能力,对新技术能持续学习、保持好奇心,不断挖掘自身潜力。

以下是加分项目:

- 能使用 Linux、MacOS 作为日常工作系统。
- 乐于分享,热衷于参与开源社区,曾参与过开源项目的开发。
- 良好的英文阅读能力,善于使用 Google、stackoverflow 等来解决技术问题

我们的联系方式


简历发送到电邮 : `[]rune{120, 122, 109}@hexiaoxiang.com`

我在这家公司的身份和角色


我是 Golang 团队的负责人,希望大家能成为战友。






[gev] 一个轻量、快速的基于 Reactor 模式的非阻塞 TCP 网络库

Go开源项目惜朝 发表了文章 • 0 个评论 • 313 次浏览 • 2019-09-25 16:45 • 来自相关话题

`gev` 是一个 ...查看全部

`gev` 是一个轻量、快速的基于 Reactor 模式的非阻塞 TCP 网络库。

➡️➡️ https://github.com/Allenxuxu/gev

特点

- 基于 epoll 和 kqueue 实现的高性能事件循环
- 支持多核多线程
- 动态扩容 Ring Buffer 实现的读写缓冲区
- 异步读写
- SO_REUSEPORT 端口重用支持

网络模型

`gev` 只使用极少的 goroutine, 一个 goroutine 负责监听客户端连接,其他 goroutine (work 协程)负责处理已连接客户端的读写事件,work 协程数量可以配置,默认与运行主机 CPU 数量相同。

性能测试

> 测试环境 Ubuntu18.04 | 4 Virtual CPUs | 4.0 GiB

吞吐量测试

限制 GOMAXPROCS=1(单线程),1 个 work 协程


限制 GOMAXPROCS=4,4 个 work 协程


其他测试

速度测试


和同类库的简单性能比较, 压测方式与 evio 项目相同。
- gnet
- eviop
- evio
- net (标准库)

限制 GOMAXPROCS=1,1 个 work 协程


限制 GOMAXPROCS=1,4 个 work 协程


限制 GOMAXPROCS=4,4 个 work 协程


安装 gev


```bash
go get -u github.com/Allenxuxu/gev
```

快速入门


```go
package main

import (
"log"

"github.com/Allenxuxu/gev"
"github.com/Allenxuxu/gev/connection"
"github.com/Allenxuxu/ringbuffer"
)

type example struct{}

func (s *example) OnConnect(c *connection.Connection) {
log.Println(" OnConnect : ", c.PeerAddr())
}

func (s *example) OnMessage(c *connection.Connection, buffer *ringbuffer.RingBuffer) (out []byte) {
log.Println("OnMessage")
first, end := buffer.PeekAll()
out = first
if len(end) > 0 {
out = append(out, end...)
}
buffer.RetrieveAll()
return
}

func (s *example) OnClose(c *connection.Connection) {
log.Println("OnClose")
}

func main() {
handler := new(example)

s, err := gev.NewServer(handler,
gev.Address(":1833"),
gev.NumLoops(2),
gev.ReusePort(true))
if err != nil {
panic(err)
}

s.Start()
}
```

Handler 是一个接口,我们的程序必须实现它。

```go
type Handler interface {
OnConnect(c *connection.Connection)
OnMessage(c *connection.Connection, buffer *ringbuffer.RingBuffer) []byte
OnClose(c *connection.Connection)
}

func NewServer(handler Handler, opts ...Option) (server *Server, err error) {
```

在消息到来时,gev 会回调 OnMessage ,在这个函数中可以通过返回一个切片来发送数据给客户端。

```go
func (s *example) OnMessage(c *connection.Connection, buffer *ringbuffer.RingBuffer) (out []byte)
```

Connection 还提供 Send 方法来发送数据。Send 并不会立刻发送数据,而是先添加到 event loop 的任务队列中,然后唤醒 event loop 去发送。

更详细的使用方式可以参考示例:[服务端定时推送]

```go
func (c *Connection) Send(buffer []byte) error
```

Connection ShutdownWrite 会关闭写端,从而断开连接。

更详细的使用方式可以参考示例:[限制最大连接数]

```go
func (c *Connection) ShutdownWrite() error
```


➡️➡️ https://github.com/Allenxuxu/gev



Go实现双向链表

文章分享link1st 发表了文章 • 1 个评论 • 704 次浏览 • 2019-09-20 09:30 • 来自相关话题

本文介绍什么是链表,常见的链表有哪些,然后介绍链表这种数据结构会在哪些地方可以用到,以及 Redis 队列是底层的实现,通过一个小实例来演示 Redis 队列有哪些功能,最后通过 Go 实现一个双向链表。 ...查看全部

本文介绍什么是链表,常见的链表有哪些,然后介绍链表这种数据结构会在哪些地方可以用到,以及 Redis 队列是底层的实现,通过一个小实例来演示 Redis 队列有哪些功能,最后通过 Go 实现一个双向链表。

链表

目录

1、链表

1.1 说明

链表

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

链表有很多种不同的类型:单向链表,双向链表以及循环链表。

  • 优势:

可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。链表允许插入和移除表上任意位置上的节点。

  • 劣势:

由于链表增加了节点指针,空间开销比较大。链表一般查找数据的时候需要从第一个节点开始每次访问下一个节点,直到访问到需要的位置,查找数据比较慢。

  • 用途:

常用于组织检索较少,而删除、添加、遍历较多的数据。

如:文件系统、LRU cache、Redis 列表、内存管理等。

1.2 单向链表

链表中最简单的一种是单向链表,

一个单向链表的节点被分成两个部分。它包含两个域,一个信息域和一个指针域。第一个部分保存或者显示关于节点的信息,第二个部分存储下一个节点的地址,而最后一个节点则指向一个空值。单向链表只可向一个方向遍历。

单链表有一个头节点head,指向链表在内存的首地址。链表中的每一个节点的数据类型为结构体类型,节点有两个成员:整型成员(实际需要保存的数据)和指向下一个结构体类型节点的指针即下一个节点的地址(事实上,此单链表是用于存放整型数据的动态数组)。链表按此结构对各节点的访问需从链表的头找起,后续节点的地址由当前节点给出。无论在表中访问哪个节点,都需要从链表的头开始,顺序向后查找。链表的尾节点由于无后续节点,其指针域为空,写作为NULL。

1.3 循环链表

循环链表是与单向链表一样,是一种链式的存储结构,所不同的是,循环链表的最后一个结点的指针是指向该循环链表的第一个结点或者表头结点,从而构成一个环形的链。

循环链表的运算与单链表的运算基本一致。所不同的有以下几点:

1、在建立一个循环链表时,必须使其最后一个结点的指针指向表头结点,而不是像单链表那样置为NULL。

2、在判断是否到表尾时,是判断该结点链域的值是否是表头结点,当链域的值等于表头指针时,说明已到表尾。而非象单链表那样判断链域的值是否为NULL。

1.4 双向链表

双向链表

双向链表其实是单链表的改进,当我们对单链表进行操作时,有时你要对某个结点的直接前驱进行操作时,又必须从表头开始查找。这是由单链表结点的结构所限制的。因为单链表每个结点只有一个存储直接后继结点地址的链域,那么能不能定义一个既有存储直接后继结点地址的链域,又有存储直接前驱结点地址的链域的这样一个双链域结点结构呢?这就是双向链表。

在双向链表中,结点除含有数据域外,还有两个链域,一个存储直接后继结点地址,一般称之为右链域(当此“连接”为最后一个“连接”时,指向空值或者空列表);一个存储直接前驱结点地址,一般称之为左链域(当此“连接”为第一个“连接”时,指向空值或者空列表)。

2、redis队列

2.1 说明

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)

Redis 列表使用两种数据结构作为底层实现:双端列表(linkedlist)、压缩列表(ziplist)

通过配置文件中(list-max-ziplist-entries、list-max-ziplist-value)来选择是哪种实现方式

在数据量比较少的时候,使用双端链表和压缩列表性能差异不大,但是使用压缩列表更能节约内存空间

redis 链表的实现源码 redis src/adlist.h

2.2 应用场景

消息队列,秒杀项目

秒杀项目:

提前将需要的商品码信息存入 Redis 队列,在抢购的时候每个用户都从 Redis 队列中取商品码,由于 Redis 是单线程的,同时只能有一个商品码被取出,取到商品码的用户为购买成功,而且 Redis 性能比较高,能抗住较大的用户压力。

2.3 演示

如何通过 Redis 队列中防止并发情况下商品超卖的情况。

假设:

网站有三件商品需要卖,我们将数据存入 Redis 队列中

1、 将三个商品码(10001、10002、10003)存入 Redis 队列中

# 存入商品
RPUSH commodity:queue 10001 10002 10003

2、 存入以后,查询数据是否符合预期

# 查看全部元素
LRANGE commodity:queue 0 -1

# 查看队列的长度
LLEN commodity:queue

3、 抢购开始,获取商品码,抢到商品码的用户则可以购买(由于 Redis 是单线程的,同一个商品码只能被取一次 )

# 出队
LPOP commodity:queue

这里了解到 Redis 列表是怎么使用的,下面就用 Go 语言实现一个双向链表来实现这些功能。

3、Go双向链表

3.1 说明

这里只是用 Go 语言实现一个双向链表,实现:查询链表的长度、链表右端插入数据、左端取数据、取指定区间的节点等功能( 类似于 Redis 列表的中的 RPUSH、LRANGE、LPOP、LLEN功能 )。

3.2 实现

golang 双向链表

  • 节点定义

双向链表有两个指针,分别指向前一个节点和后一个节点

链表表头 prev 的指针为空,链表表尾 next 的指针为空

// 链表的一个节点
type ListNode struct {
prev *ListNode // 前一个节点
next *ListNode // 后一个节点
value string // 数据
}

// 创建一个节点
func NewListNode(value string) (listNode *ListNode) {
listNode = &ListNode{
value: value,
}

return
}

// 当前节点的前一个节点
func (n *ListNode) Prev() (prev *ListNode) {
prev = n.prev

return
}

// 当前节点的前一个节点
func (n *ListNode) Next() (next *ListNode) {
next = n.next

return
}

// 获取节点的值
func (n *ListNode) GetValue() (value string) {
if n == nil {

return
}
value = n.value

return
}
  • 定义一个链表

链表为了方便操作,定义一个结构体,可以直接从表头、表尾进行访问,定义了一个属性 len ,直接可以返回链表的长度,直接查询链表的长度就不用遍历时间复杂度从 O(n) 到 O(1)。

// 链表
type List struct {
head *ListNode // 表头节点
tail *ListNode // 表尾节点
len int // 链表的长度
}


// 创建一个空链表
func NewList() (list *List) {
list = &List{
}
return
}

// 返回链表头节点
func (l *List) Head() (head *ListNode) {
head = l.head

return
}

// 返回链表尾节点
func (l *List) Tail() (tail *ListNode) {
tail = l.tail

return
}

// 返回链表长度
func (l *List) Len() (len int) {
len = l.len

return
}
  • 在链表的右边插入一个元素
// 在链表的右边插入一个元素
func (l *List) RPush(value string) {

node := NewListNode(value)

// 链表未空的时候
if l.Len() == 0 {
l.head = node
l.tail = node
} else {
tail := l.tail
tail.next = node
node.prev = tail

l.tail = node
}

l.len = l.len + 1

return
}
  • 从链表左边取出一个节点
// 从链表左边取出一个节点
func (l *List) LPop() (node *ListNode) {

// 数据为空
if l.len == 0 {

return
}

node = l.head

if node.next == nil {
// 链表未空
l.head = nil
l.tail = nil
} else {
l.head = node.next
}
l.len = l.len - 1

return
}
  • 通过索引查找节点

通过索引查找节点,如果索引是负数则从表尾开始查找。

自然数和负数索引分别通过两种方式查找节点,找到指定索引或者是链表全部查找完则查找完成。

// 通过索引查找节点
// 查不到节点则返回空
func (l *List) Index(index int) (node *ListNode) {

// 索引为负数则表尾开始查找
if index < 0 {
index = (-index) - 1
node = l.tail
for true {
// 未找到
if node == nil {

return
}

// 查到数据
if index == 0 {

return
}

node = node.prev
index--
}
} else {
node = l.head
for ; index > 0 && node != nil; index-- {
node = node.next
}
}

return
}
  • 返回指定区间的元素
// 返回指定区间的元素
func (l *List) Range(start, stop int) (nodes []*ListNode) {
nodes = make([]*ListNode, 0)

// 转为自然数
if start < 0 {
start = l.len + start
if start < 0 {
start = 0
}
}

if stop < 0 {
stop = l.len + stop
if stop < 0 {
stop = 0
}
}

// 区间个数
rangeLen := stop - start + 1
if rangeLen < 0 {

return
}

startNode := l.Index(start)
for i := 0; i < rangeLen; i++ {
if startNode == nil {
break
}

nodes = append(nodes, startNode)
startNode = startNode.next
}

return
}

4、总结

  • 到这里关于链表的使用已经结束,介绍链表是有哪些(单向链表,双向链表以及循环链表),也介绍了链表的应用场景(Redis 列表使用的是链表作为底层实现),最后用 Go 实现了双向链表,演示了链表在 Go 语言中是怎么使用的,大家可以在项目中更具实际的情况去使用。

5、参考文献

维基百科 链表

github redis

项目地址:go 实现队列

https://github.com/link1st/link1st/tree/master/linked

gnet: 一个轻量级且高性能的 Go 网络库

开源程序panjf2000 发表了文章 • 1 个评论 • 517 次浏览 • 2019-09-18 16:08 • 来自相关话题


gnet












# 博客原文
https://taohuawu.club/go-event-loop-networking-library-gnet

# Github 主页
https://github.com/panjf2000/gnet

欢迎大家围观~~,目前还在持续更新,感兴趣的话可以 star 一下暗中观察哦。

# 简介

`gnet` 是一个基于 Event-Loop 事件驱动的高性能和轻量级网络库。这个库直接使用 [epoll](https://en.wikipedia.org/wiki/Epoll) 和 [kqueue](https://en.wikipedia.org/wiki/Kqueue) 系统调用而非标准 Golang 网络包:[net](https://golang.org/pkg/net/) 来构建网络应用,它的工作原理类似两个开源的网络库:[libuv](https://github.com/libuv/libuv) 和 [libevent](https://github.com/libevent/libevent)。

这个项目存在的价值是提供一个在网络包处理方面能和 [Redis](http://redis.io)、[Haproxy](http://www.haproxy.org) 这两个项目具有相近性能的Go 语言网络服务器框架。

`gnet` 的亮点在于它是一个高性能、轻量级、非阻塞的纯 Go 实现的传输层(TCP/UDP/Unix-Socket)网络库,开发者可以使用 `gnet` 来实现自己的应用层网络协议,从而构建出自己的应用层网络应用:比如在 `gnet` 上实现 HTTP 协议就可以创建出一个 HTTP 服务器 或者 Web 开发框架,实现 Redis 协议就可以创建出自己的 Redis 服务器等等。

**`gnet` 衍生自另一个项目:`evio`,但是性能更好。**

# 功能

- [高性能](#性能测试) 的基于多线程模型的 Event-Loop 事件驱动
- 内置 Round-Robin 轮询负载均衡算法
- 简洁的 APIs
- 基于 Ring-Buffer 的高效内存利用
- 支持多种网络协议:TCP、UDP、Unix Sockets
- 支持两种事件驱动机制:Linux 里的 epoll 以及 FreeBSD 里的 kqueue
- 支持异步写操作
- 允许多个网络监听地址绑定在一个 Event-Loop 上
- 灵活的事件定时器
- SO_REUSEPORT 端口重用

# 核心设计

## 多线程模型

`gnet` 重新设计开发了一个新内置的多线程模型:『主从 Reactor 多线程』,这也是 `netty` 默认的线程模型,下面是这个模型的原理图:


multi_reactor



它的运行流程如下面的时序图:


reactor



现在我正在 `gnet` 里开发一个新的多线程模型:『带线程/go程池的主从 Reactors 多线程』,并且很快就能完成,这个模型的架构图如下所示:


multi_reactor_thread_pool



它的运行流程如下面的时序图:


multi-reactors



## 通信机制

`gnet` 的『主从 Reactors 多线程』模型是基于 Golang 里的 Goroutines的,一个 Reactor 挂载在一个 Goroutine 上,所以在 `gnet` 的这个网络模型里主 Reactor/Goroutine 与从 Reactors/Goroutines 有海量通信的需求,因此 `gnet` 里必须要有一个能在 Goroutines 之间进行高效率的通信的机制,我没有选择 Golang 里的主流方案:基于 Channel 的 CSP 模型,而是选择了性能更好、基于 Ring-Buffer 的 Disruptor 方案。

所以我最终选择了 [go-disruptor](https://github.com/smartystreets-prototypes/go-disruptor):高性能消息分发队列 LMAX Disruptor 的 Golang 实现。

## 自动扩容的 Ring-Buffer

`gnet` 利用 Ring-Buffer 来缓存 TCP 流数据以及管理内存使用。







# 开始使用

## 安装

```sh
$ go get -u github.com/panjf2000/gnet
```

## 使用示例

```go
// ======================== Echo Server implemented with gnet ===========================

package main

import (
"flag"
"fmt"
"log"
"strings"

"github.com/panjf2000/gnet"
"github.com/panjf2000/gnet/ringbuffer"
)

func main() {
var port int
var loops int
var udp bool
var trace bool
var reuseport bool

flag.IntVar(&port, "port", 5000, "server port")
flag.BoolVar(&udp, "udp", false, "listen on udp")
flag.BoolVar(&reuseport, "reuseport", false, "reuseport (SO_REUSEPORT)")
flag.BoolVar(&trace, "trace", false, "print packets to console")
flag.IntVar(&loops, "loops", 0, "num loops")
flag.Parse()

var events gnet.Events
events.NumLoops = loops
events.OnInitComplete = func(srv gnet.Server) (action gnet.Action) {
log.Printf("echo server started on port %d (loops: %d)", port, srv.NumLoops)
if reuseport {
log.Printf("reuseport")
}
return
}
events.React = func(c gnet.Conn, inBuf *ringbuffer.RingBuffer) (out []byte, action gnet.Action) {
top, tail := inBuf.PreReadAll()
out = append(top, tail...)
inBuf.Reset()

if trace {
log.Printf("%s", strings.TrimSpace(string(top)+string(tail)))
}
return
}
scheme := "tcp"
if udp {
scheme = "udp"
}
log.Fatal(gnet.Serve(events, fmt.Sprintf("%s://:%d", scheme, port)))
}

```

## I/O 事件

`gnet` 目前支持的 I/O 事件如下:

- `OnInitComplete` 当 server 初始化完成之后调用。
- `OnOpened` 当连接被打开的时候调用。
- `OnClosed` 当连接被关闭的时候调用。
- `OnDetached` 当主动摘除连接的时候的调用。
- `React` 当 server 端接收到从 client 端发送来的数据的时候调用。(你的核心业务代码一般是写在这个方法里)
- `Tick` 服务器启动的时候会调用一次,之后就以给定的时间间隔定时调用一次,是一个定时器方法。
- `PreWrite` 预先写数据方法,在 server 端写数据回 client 端之前调用。

# 性能测试

## Linux (epoll)

### 系统参数

```powershell
Go Version: go1.12.9 linux/amd64
OS: Ubuntu 18.04
CPU: 8 Virtual CPUs
Memory: 16.0 GiB
```

### Echo Server

![echolinux.png](https://img.hacpai.com/file/2019/09/echolinux-fca6e6e5.png)


### HTTP Server

![httplinux.png](https://img.hacpai.com/file/2019/09/httplinux-663a0318.png)


## FreeBSD (kqueue)

### 系统参数

```powershell
Go Version: go version go1.12.9 darwin/amd64
OS: macOS Mojave 10.14.6
CPU: 4 CPUs
Memory: 8.0 GiB
```

### Echo Server

![echomac.png](https://img.hacpai.com/file/2019/09/echomac-7a29e0d1.png)


### HTTP Server

![httpmac.png](https://img.hacpai.com/file/2019/09/httpmac-cb6d26ea.png)


# 证书

`gnet` 的源码允许用户在遵循 MIT [开源证书](https://github.com/panjf2000/gnet/blob/master/LICENSE) 规则的前提下使用。

# 待做事项

> gnet 还在持续开发的过程中,所以这个仓库的代码和文档会一直持续更新,如果你对 gnet 感兴趣的话,欢迎给这个开源库贡献你的代码~~

beego 开发的博客 go-blog

开源程序猴子 发表了文章 • 3 个评论 • 283 次浏览 • 2019-09-16 12:00 • 来自相关话题

个人博客 项目地址: https://github.com/1920853199/go-blog demo 地址: http://leechan.online ...查看全部
个人博客 
项目地址: https://github.com/1920853199/go-blog
demo 
地址: http://leechan.online






推荐一下自己的Go分布式缓存项目

开源程序seaguest 发表了文章 • 0 个评论 • 354 次浏览 • 2019-09-04 11:02 • 来自相关话题

最近整理了一下自己的分布式缓存方案,是cache-aside模式的二级缓存(memory+redis)实现,目前已经在线上投入使用,经过百万日活的验证。 内存部分是采用的是 sync.Map, 读取缓存的时候先从内存读,如果未读到则去 ...查看全部
最近整理了一下自己的分布式缓存方案,是cache-aside模式的二级缓存(memory+redis)实现,目前已经在线上投入使用,经过百万日活的验证。

内存部分是采用的是 sync.Map, 读取缓存的时候先从内存读,如果未读到则去读 redis,如果 redis 未读到,则根据定义的加载函数加载到 redis 和内存。

缓存有 lazy 模式,为了避免缓存被击穿,可以设置 lazy 模式,缓存数据存活的时间更久,但是每次读取的时候依然会判断数据是否是最新的,不是最新的话会异步加载更新。

通过 redis 的 Publish/Subscribe 功能,实现缓存的分布式更新,目前仅实现删除同步。

因为是很轻量级的,所以性能理论上会很不错,测试1000个并发毫无压力,有兴趣的同学可以验证一下。理论上性能的瓶颈在于加载函数里数据库的查询,如果数据库配置够好,而redis单机10万QPS应该没什么问题,那么应该可以达到比较高的并发。

项目地址 https://github.com/seaguest/cache

欢迎大家批评指正!

yiigo 3.x 版本发布

文章分享IIInsomnia 发表了文章 • 0 个评论 • 771 次浏览 • 2019-09-02 17:25 • 来自相关话题

# [yiigo](https://github.com/iiinsomnia/yiigo) A simple and light library which makes Golang development easier ! ...查看全部
# [yiigo](https://github.com/iiinsomnia/yiigo)

A simple and light library which makes Golang development easier !

## Features

- Support [MySQL](https://github.com/go-sql-driver/mysql)
- Support [PostgreSQL](https://github.com/lib/pq)
- Support [MongoDB](https://github.com/mongodb/mongo-go-driver)
- Support [Redis](https://github.com/gomodule/redigo)
- Support [Zipkin](https://github.com/openzipkin/zipkin-go)
- Use [gomail](https://github.com/go-gomail/gomail) for email sending
- Use [toml](https://github.com/pelletier/go-toml) for configuration
- Use [sqlx](https://github.com/jmoiron/sqlx) for SQL executing
- Use [zap](https://github.com/uber-go/zap) for logging

## Requirements

`Go1.11+`

## Installation

```sh
go get github.com/iiinsomnia/yiigo/v3
```

## Usage

#### MySQL

```go
// default db
yiigo.RegisterDB(yiigo.AsDefault, yiigo.MySQL, "root:root@tcp(localhost:3306)/test")

yiigo.DB.Get(&User{}, "SELECT * FROM `user` WHERE `id` = ?", 1)

// other db
yiigo.RegisterDB("foo", yiigo.MySQL, "root:root@tcp(localhost:3306)/foo")

yiigo.UseDB("foo").Get(&User{}, "SELECT * FROM `user` WHERE `id` = ?", 1)
```

#### MongoDB

```go
// default mongodb
yiigo.RegisterMongoDB(yiigo.AsDefault, "mongodb://localhost:27017")

ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
yiigo.Mongo.Database("test").Collection("numbers").InsertOne(ctx, bson.M{"name": "pi", "value": 3.14159})

// other mongodb
yiigo.RegisterMongoDB("foo", "mongodb://localhost:27017")

ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
yiigo.UseMongo("foo").Database("test").Collection("numbers").InsertOne(ctx, bson.M{"name": "pi", "value": 3.14159})
```

#### Redis

```go
// default redis
yiigo.RegisterRedis(yiigo.AsDefault, "localhost:6379")

conn, err := yiigo.Redis.Get()

if err != nil {
log.Fatal(err)
}

defer yiigo.Redis.Put(conn)

conn.Do("SET", "test_key", "hello world")

// other redis
yiigo.RegisterRedis("foo", "localhost:6379")

foo := yiigo.UseRedis("foo")
conn, err := foo.Get()

if err != nil {
log.Fatal(err)
}

defer foo.Put(conn)

conn.Do("SET", "test_key", "hello world")
```

#### Config

```go
// env.toml
//
// [app]
// env = "dev"
// debug = true
// port = 50001

yiigo.UseEnv("env.toml")

yiigo.Env.Bool("app.debug", true)
yiigo.Env.Int("app.port", 12345)
yiigo.Env.String("app.env", "dev")
```

#### Zipkin

```go
tracer, err := yiigo.NewZipkinTracer("http://localhost:9411/api/v2/spans",
yiigo.WithZipkinTracerEndpoint("zipkin-test", "localhost"),
yiigo.WithZipkinTracerSharedSpans(false),
yiigo.WithZipkinTracerSamplerMod(1),
)

if err != nil {
log.Fatal(err)
}

client, err := yiigo.NewZipkinClient(tracer)

if err != nil {
log.Fatal(err)
}

b, err := client.Get(context.Background(), "url...",
yiigo.WithRequestHeader("Content-Type", "application/json; charset=utf-8"),
yiigo.WithRequestTimeout(5*time.Second),
)

if err != nil {
log.Fatal(err)
}

fmt.Println(string(b))
```

#### Logger

```go
// default logger
yiigo.RegisterLogger(yiigo.AsDefault, "app.log")
yiigo.Logger.Info("hello world")

// other logger
yiigo.RegisterLogger("foo", "foo.log")
yiigo.UseLogger("foo").Info("hello world")
```

## Documentation

- [API Reference](https://godoc.org/github.com/iiinsomnia/yiigo)
- [TOML](https://github.com/toml-lang/toml)
- [Example](https://github.com/iiinsomnia/yiigo-example)

**Enjoy ^_^**

[北京]美餐网招聘Golang/Web 前端/大数据开发/Android/iOS开发,老铁们冲鸭~

招聘应聘recruiter 发表了文章 • 0 个评论 • 394 次浏览 • 2019-08-29 16:36 • 来自相关话题

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190829160718237.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,te ...查看全部
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190829160718237.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjYxMzQ5Mg==,size_16,color_FFFFFF,t_70)

工作日上午 10 点,美餐( meican.com ) 11 层产品技术部的办公室里,几个习惯早到的工程师已经打开 MacBook 敲下了几行代码,他们在持续优化企业订餐的预订、交互功能。

活跃于微博的工程师飞树先生刚刚经过中关村地区的海淀黄庄地铁站,候车间隙对面的广告牌上刚好是他的个人特 写和招聘词:「我是美餐 BUG 开发工程师,我们正在招聘技术大牛」。

习惯了夜间创作的 LETO,已经被 Slack 里关于企业 wiki 的问题叫醒,随即就在 Asana 里增加了一个 Task。

美餐每年投入数万美元购买的 Confluence,经过他的二次开发,已经成为了公司内部不可缺少的知识库和交流中心。

在美餐,每个人都是多面手,从参与解决普通的技术问题,到带领团队独当一面兼顾前端后端和设计。在这个扁平化的团队中,工程师需要担任 leader 的角色,发挥创新思维,创造极致产品和体验。

除了写代码改 bug,工程师们还会拉琴、摄影、骑车,会在团队欢聚的时候带上专业单反拍摄精美大片。从美餐还没上线就在参与创业的元老级工程师马老师,甚至连盛大的婚礼都在办公室完成了。

你看,工程师们是一群有着不同爱好的有趣的人,除了精通 Lua / Go / Erlang / Scala / Ruby / R 语言,他们还是音乐达人、绘画才子和马拉松健将,会将冰冷的代码组合成有趣的产品,也会将平凡的生活过出自己的乐趣。

在美餐,没有日复一日乏味的工作,更多的是有意思的创作,每一次敲键盘、做优化、改设计,会让美餐用户每一天的浏览、订餐、下单、欢聚更加快捷顺畅,帮助更多的企业客户提升员工幸福感。

美餐是中国领先的企业订餐和综合消费平台,目前覆盖北京、上海、广州、深圳、成都等城市,为数千家企业客户员工提供企业用餐、企业活动以及精选欢聚活动推荐等综合消费服务。


--------------------------------------------------------------------------------

#### 关于美餐:

2011 年,获得来自真格基金和九合创投的天使投资。

2012 年,获得来自 KPCB 的 A 轮投资。

2013 年,获得来自 NGP 的 B 轮投资。

2014 年,获得来自挚信资本的 B+ 轮投资。

2015 年,获得来自美团点评的 C 轮战略投资。

2016 年,美餐荣获中国最具潜力创业公司,员工福利管理服务机构十强,年度最佳企业服务商等荣誉。

2017 年,美餐荣获中国团餐高成长性品牌企业,中国团餐十强企业,中国生活服务产业十大创新力企业等荣誉。

2017 年,获得来自高盛( Goldman Sachs )的 D 轮投资。

2018 年,美餐荣获中国餐饮百强企业。美餐智能餐柜 SMARTWAITER W1 荣获德国 iF 设计奖和红点奖。

2018 年,获得来自阳光保险的 D+ 轮投资。


--------------------------------------------------------------------------------

#### 工作环境:

Herman Miller Embody 人体工学座椅

B&W; Zeppelin Air 无线音响

De'Lo- nghi 全自动咖啡机

PlayStation 4/Xbox One 游戏机

3D 打印机

Blueair 空气净化器

大提琴 & 小提琴

各种 Apple 产品

懒人沙发

#### 员工福利:

每 2 年报销一台 Mac (归个人所有)

每月团建吃喝腐败

无限量零食、饮料

免费午餐、晚餐( top 连锁餐饮品牌)

分配期权

弹性工作,不打卡

重视技术,无官僚,Geek 团队

介绍对象


--------------------------------------------------------------------------------

#### 招聘职位:

资深 Web 开发工程师( Golang/Java )

Golang 开发工程师

Web 前端工程师

Android 开发工程师

iOS 开发工程师( swift )

数据仓库工程师

UI/UE 设计师

品牌设计师

--------------------------------------------------------------------------------

团队:大牛+极客+文艺男;我们团队的成员来自谷歌,阿里巴巴,腾讯,头条、滴滴以及行业内知名公司;

技术:从 UI 到代码的简洁,却凝聚最挑战的设计、线上满是科技,线下满是黑科技。

如果你热爱技术,如果你具有产品思维(开发负责产品规划,我们木有产品经理),如果你喜欢自由,创新...来

美餐网哦,这里的平台绝对适合你!欢迎加入美餐,和我们一起快速成长!更多信息请登录 www.meican.com


--------------------------------------------------------------------------------

工作地点:北京市海淀区中国外文大厦

薪酬待遇:具有行业竞争力的整体薪酬


--------------------------------------------------------------------------------

#### 简历投递:

[cuilixia@meican.com]

标题注明「来自 gopherChina 」

请附上 GitHub / Blog 链接

加分项请你自己发挥

我们喜欢认真有趣的人 :-)
***
#### 职位描述:

#### 资深 Web 开发工程师( Golang/Java )

工作职责:
1. 负责公司项目后台的的重构工作,编写核心代码,推动工作的开展;
2. 持续改进系统的架构和核心技术,保证系统的稳定性、高性能、高可用性和可扩展性。

岗位要求:
1. 全日制本科及以上学历,计算机相关专业;
2. 熟悉主流开源框架,至少熟悉 Go / Java 中的一种或两种及语言对应的 Restful 框架,并具有优秀的开发能力,能快速完成原型的开发;
3. 负责平台服务技术框架的规划与设计,编写核心代码,实现核心技术组件,为业务调用提供基础,推动自动化测试和部署;
4. 熟悉底层中间件、分布式技术(包括缓存、消息系统、热部署等)并能熟练使用各种工具
5. 具备高可用、高性能、高并发、高扩展系统设计经验,熟练操作主流关系数据库;
6. 热爱技术,工作严谨,对系统运行质量有苛刻的要求者最佳;

优先条件:
1. 过往有架构设计或重构经验的优先考虑;
2. 开源贡献者优先,github 源码者优先,技术博客者优先。

工作中将涉及的技术:
- AWS
- Kubernetes
- Ansible
- Gin-Gonic
- Play Framework
- Webpack
- React
- Angular
- ES6
- RESTful API
- OAuth 2.0 协议
- LDAP
- 支付宝、微信支付相关开发
***
#### Web 前端开发工程师

工作职责:
1. 在具体使用场景中理解用户使用方式和遇到的问题;
2. 通过 Web 前端开发,解决上述问题,带给用户更好的使用体验;
3. 创造自动化工具,帮助工作伙伴提升运营效率。

任职要求:
1. 前端基础知识扎实,能独立完成工作,熟练掌握原生 JavaScript、CSS、HTML ;
2. 可以精准还原设计稿,愿意为了提升用户体验作出最大努力;
3. 了解前端测试,懂得如何写出可测试的代码;
4. 了解 GitFlow 流程;
5. 了解 HTTP、TCP、UDP 等常见协议;
6. 良好的学习能力,有责任心,愿意自我驱动,爱折腾愿意尝试新鲜事物,能够快速学习新技术并实践;
7. 具有英文文档阅读能力。

#### 加分项:
* 了解一门后端语言,如 Node.js 、Java、Go 等;
* 了解或使用过 AWS、腾讯云、阿里云等云服务;
* 了解 MQTT 协议;
* 有 Native 端开发经验;
* 对 Flutter 开发感兴趣;
* 使用过 Sketch 或 Adobe XD 等原型工具;
* 组件库开发经验;
* WebGL 开发经验。

#### 工作中将涉及的技术:
* React、Vue
* Redux、MobX、Vuex
* Immutable.js
* Webpack
* Next.js 、Nuxt.js
* WebSocket
* Cypress
* Storybook
* TypeScript
* PWA
* Serverless
* Chrome Packaged App、Electron
* AWS
* Taro (微信小程序)
***
#### Golang 开发工程师

工作职责:
1. 参与需求分析,产品设计,配合团队分工完成设计和开发任务;
2. 实时定位和处理各系统问题,快速解决线上问题;
3. 参与设计产品架构与编码;
4. 改进现有的工具,帮助工作伙伴提升开发与运营效率;
5. 能够与各个 Team 高效沟通,解决实际问题。

任职要求:
1. 大学本科及以上学历;
2. 有完整的项目开发经验,追求代码质量,乐于分享;
3. 熟练使用至少一种语言,包括但不限于 Go/Java/Ruby/Python ;
4. 熟练使用 MySQL/Postgres 关系型数据库;
5. 掌握一种或多种非关系型数据库,包括但不限于 DynamoDB/MongoDB/Cassandra/Redis ;
6. 对分布式处理有一定经验;
7. 熟悉常见的消息队列, 各自的使用场景.;
8. 有过 RPC 开发经验,熟悉 RPC 通信的各个环节。

#### 优先条件:
1. 有过完整的 Go 项目经验, 同时熟悉 Go 与 Java 更佳;
2. 有过流量接入层开发经验;
3. 参与过开源项目, 有开放的技术源码提供(我们会去看您的项目);
4. 对前端技术有了解, 有开发经验更佳。

#### 工作中将涉及的技术:
- AWS
- Kubernetes
- Ansible
- Gin-Gonic
- Play Framework
- MySQL/Postgres
- Redis
- Nginx/Openresty
- RESTful API
- Grpc
- OAuth 2.0 协议
- 支付宝、微信支付相关开发
***
#### Android 开发工程师

工作职责:
1. 负责美餐 Android 客户端以及后端接口的开发;
2. 根据工作需要参与技术团队其他的工作。

任职要求:
1. 大学本科及以上学历;
2. 扎实的 Java 基础,了解常用的软件编程思想,了解常用算法及数据结构等知识;
3. 从事 Android 开发 3 年及以上,有上线的 Android 应用,熟练使用数据库,多线程 /多进程通讯,网络编程,进程配置,权限管理等;
4. 熟悉 Android 常用组件、熟悉 Android 框架、熟悉 Android 开发文档,了解 JNI/NDK 开发,有阅读 Android 源码经验;
5. 逻辑清晰,好奇心强,善于自我激励;
6. 能够创造前所未有的方案解决新问题,代码整洁;
7. 对良好代码风格具有强迫症;
8. 如有 GitHub 帐号或技术博客,请在简历中写明。

#### 工作内容将涉及的技术(优先考虑):
- RESTful API
- OAuth 2.0 协议
- 熟悉 Android Test Framework
- 熟悉 MVC, MVP, MVVM 架构
- 熟悉 Material Design 设计规范
- 熟悉性能优化方法
- 支付宝、微信支付相关开发
- 地图与 LBS
- socket 通信
- Push Notification
- Bluetooth LE
***
#### iOS 开发工程师

工作职责:
1. 负责美餐旗下 iOS 客户端的开发与维护,设备包括 iPhone / iPad / Apple Watch ;
2. 根据工作需要参与技术团队其他的工作。

任职要求:
1. 计算机,软件工程,信息技术相关本科及以上学历;
2. 熟悉 iOS Human Interface Guidelines ;
3. 熟悉 iOS 开发相关技术,有 3 年以上的开发经验,1 年以上的 Swift 开发经验,有上线的 app 尤佳;
5. 熟悉 HTTP / HTTPS / Socket 等常用协议,有后端 API 设计经验尤佳;
6. 有撰写文档和测试的能力;
7. 良好的团队协作能力,乐于分享;
8. 逻辑清晰,好奇心强并善于寻求答案;
9. 具备技术热情,崇尚极客精神;
10. 良好代码风格和编程习惯,对工程质量和产品品质有较高要求;
11. 如有 GitHub 帐号或技术博客,请在简历中写明。

#### 工作中涉及的技术:
Languages:
- Swift
- Swift / Objective-C 混合编程 (aka. Mix and Match)

Tools:
- Xcode
- Instruments
- XCTest

App Frameworks:
- UIKit (使用 Auto Layout 和 Storyboard )
- Foundation
- WatchKit (加分项)

Graphics:
- Core Animation
- Core Graphics
- ARKit / Vision (加分项)

App Services and System:
- UserNotifications / UserNotificationsUI
- NotificationCenter
- MapKit
- Core Location
- CFNetwork
- GCD/NSOperation
- Core Bluetooth (加分项)
- PassKit (加分项)
- WatchConnectivity (加分项)
- HealthKit / HealthKitUI (加分项)
- Core ML (加分项)
- 快速上手 iOS framework
- 封装 Cocoa Touch framework

Others:
- RESTful API
- OAuth 2.0
- Apple Pay / 支付宝 / 微信支付
***

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190829160140111.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjYxMzQ5Mg==,size_16,color_FFFFFF,t_70)

go实现的压测工具【单台机器100w连接压测实战】

文章分享link1st 发表了文章 • 2 个评论 • 1019 次浏览 • 2019-08-29 09:43 • 来自相关话题

本文介绍压测是什么,解释压测的专属名词,教大家如何压测。介绍市面上的常见压测工具(ab、locust、Jmeter、go实现的压测工具、云压测),对比这些压测工具,教大家如何选择一款适合自己的压测工具,本文还有两个压测实战项目: - ...查看全部
本文介绍压测是什么,解释压测的专属名词,教大家如何压测。介绍市面上的常见压测工具(ab、locust、Jmeter、go实现的压测工具、云压测),对比这些压测工具,教大家如何选择一款适合自己的压测工具,本文还有两个压测实战项目:

- 单台机器对HTTP短连接 QPS 1W+ 的压测实战
- 单台机器100W长连接的压测实战


## 目录
- 1、项目说明
- 1.1 go-stress-testing
- 1.2 项目体验本文介绍压测是什么,解释压测的专属名词,教大家如何压测。介绍市面上的常见压测工具(ab、locust、Jmeter、go实现的压测工具、云压测),对比这些压测工具,教大家如何选择一款适合自己的压测工具,本文还有两个压测实战项目:
  • 单台机器对HTTP短连接 QPS 1W+ 的压测实战
  • 单台机器100W长连接的压测实战

    原文地址 (更好的阅读格式)

目录

  • 1、项目说明
    • 1.1 go-stress-testing
    • 1.2 项目体验
  • 2、压测
    • 2.1 压测是什么
    • 2.2 为什么要压测
    • 2.3 压测名词解释
      • 2.3.1 压测类型解释
      • 2.3.2 压测名词解释
      • 2.3.3 机器性能指标解释
      • 2.3.4 访问指标解释
    • 3.4 如何计算压测指标
  • 3、常见的压测工具
    • 3.1 ab
    • 3.2 locust
    • 3.3 Jmete
    • 3.4 云压测
      • 3.4.1 云压测介绍
      • 3.4.2 阿里云 性能测试 PTS
      • 3.4.3 腾讯云 压测大师 LM
  • 4、go-stress-testing go语言实现的压测工具
    • 4.1 介绍
    • 4.2 用法
    • 4.3 实现
    • 4.4 go-stress-testing 对 Golang web 压测
  • 5、压测工具的比较
    • 5.1 比较
    • 5.2 如何选择压测工具
  • 6、单台机器100w连接压测实战
    • 6.1 说明
    • 6.2 内核优化
    • 6.3 客户端配置
    • 6.4 准备
    • 6.5 压测数据
  • 7、总结
  • 8、参考文献

1、项目说明

1.1 go-stress-testing

go 实现的压测工具,每个用户用一个协程的方式模拟,最大限度的利用CPU资源

1.2 项目体验

  • 可以在 mac/linux/windows 不同平台下执行的命令
  • go-stress-testing 压测工具下载地址

参数说明:

-c 表示并发数

-n 每个并发执行请求的次数,总请求的次数 = 并发数 * 每个并发执行请求的次数

-u 需要压测的地址

# 运行 以mac为示例
./go-stress-testing-mac -c 1 -n 100 -u https://www.baidu.com/
  • 压测结果展示

执行以后,终端每秒钟都会输出一次结果,压测完成以后输出执行的压测结果

压测结果展示:

─────┬───────┬───────┬───────┬────────┬────────┬────────┬────────┬────────
耗时│ 并发数 │ 成功数│ 失败数 │ qps │最长耗时 │最短耗时│平均耗时 │ 错误码
─────┼───────┼───────┼───────┼────────┼────────┼────────┼────────┼────────
1s│ 1│ 8│ 0│ 8.09│ 133.16│ 110.98│ 123.56│200:8
2s│ 1│ 15│ 0│ 8.02│ 138.74│ 110.98│ 124.61│200:15
3s│ 1│ 23│ 0│ 7.80│ 220.43│ 110.98│ 128.18│200:23
4s│ 1│ 31│ 0│ 7.83│ 220.43│ 110.23│ 127.67│200:31
5s│ 1│ 39│ 0│ 7.81│ 220.43│ 110.23│ 128.03│200:39
6s│ 1│ 46│ 0│ 7.72│ 220.43│ 110.23│ 129.59│200:46
7s│ 1│ 54│ 0│ 7.79│ 220.43│ 110.23│ 128.42│200:54
8s│ 1│ 62│ 0│ 7.81│ 220.43│ 110.23│ 128.09│200:62
9s│ 1│ 70│ 0│ 7.79│ 220.43│ 110.23│ 128.33│200:70
10s│ 1│ 78│ 0│ 7.82│ 220.43│ 106.47│ 127.85│200:78
11s│ 1│ 84│ 0│ 7.64│ 371.02│ 106.47│ 130.96│200:84
12s│ 1│ 91│ 0│ 7.63│ 371.02│ 106.47│ 131.02│200:91
13s│ 1│ 99│ 0│ 7.66│ 371.02│ 106.47│ 130.54│200:99
13s│ 1│ 100│ 0│ 7.66│ 371.02│ 106.47│ 130.52│200:100


************************* 结果 stat ****************************
处理协程数量: 1
请求总数: 100 总请求时间: 13.055 秒 successNum: 100 failureNum: 0
************************* 结果 end ****************************

参数解释:

耗时: 程序运行耗时。程序每秒钟输出一次压测结果

并发数: 并发数,启动的协程数

成功数: 压测中,请求成功的数量

失败数: 压测中,请求失败的数量

qps: 当前压测的QPS(每秒钟处理请求数量)

最长耗时: 压测中,单个请求最长的响应时长

最短耗时: 压测中,单个请求最短的响应时长

平均耗时: 压测中,单个请求平均的响应时长

错误码: 压测中,接口返回的 code码:返回次数的集合

2、压测

2.1 压测是什么

压测,即压力测试,是确立系统稳定性的一种测试方法,通常在系统正常运作范围之外进行,以考察其功能极限和隐患。

主要检测服务器的承受能力,包括用户承受能力(多少用户同时玩基本不影响质量)、流量承受等。

2.2 为什么要压测

  • 压测的目的就是通过压测(模拟真实用户的行为),测算出机器的性能(单台机器的QPS),从而推算出系统在承受指定用户数(100W)时,需要多少机器能支撑得住
  • 压测是在上线前为了应对未来可能达到的用户数量的一次预估(提前演练),压测以后通过优化程序的性能或准备充足的机器,来保证用户的体验。

2.3 压测名词解释

2.3.1 压测类型解释

压测类型

解释

压力测试(Stress Testing)

也称之为强度测试,测试一个系统的最大抗压能力,在强负载(大数据、高并发)的情况下,测试系统所能承受的最大压力,预估系统的瓶颈

并发测试(Concurrency Testing)

通过模拟很多用户同一时刻访问系统或对系统某一个功能进行操作,来测试系统的性能,从中发现问题(并发读写、线程控制、资源争抢)

耐久性测试(Configuration Testing)

通过对系统在大负荷的条件下长时间运行,测试系统、机器的长时间运行下的状况,从中发现问题(内存泄漏、数据库连接池不释放、资源不回收)

2.3.2 压测名词解释

压测名词

解释

并发(Concurrency)

指一个处理器同时处理多个任务的能力(逻辑上处理的能力)

并行(Parallel)

多个处理器或者是多核的处理器同时处理多个不同的任务(物理上同时执行)

QPS(每秒钟查询数量 Query Per Second)

服务器每秒钟处理请求数量 (req/sec 请求数/秒 一段时间内总请求数/请求时间)

事务(Transactions)

是用户一次或者是几次请求的集合

TPS(每秒钟处理事务数量 Transaction Per Second)

服务器每秒钟处理事务数量(一个事务可能包括多个请求)

请求成功数(Request Success Number)

在一次压测中,请求成功的数量

请求失败数(Request Failures Number)

在一次压测中,请求失败的数量

错误率(Error Rate)

在压测中,请求成功的数量与请求失败数量的比率

最大响应时间(Max Response Time)

在一次事务中,从发出请求或指令系统做出的反映(响应)的最大时间

最少响应时间(Mininum Response Time)

在一次事务中,从发出请求或指令系统做出的反映(响应)的最少时间

平均响应时间(Average Response Time)

在一次事务中,从发出请求或指令系统做出的反映(响应)的平均时间

2.3.3 机器性能指标解释

机器性能

解释

CUP利用率(CPU Usage)

CUP 利用率分用户态、系统态和空闲态,CPU利用率是指:CPU执行非系统空闲进程的时间与CPU总执行时间的比率

内存使用率(Memory usage)

内存使用率指的是此进程所开销的内存。

IO(Disk input/ output)

磁盘的读写包速率

网卡负载(Network Load)

网卡的进出带宽,包量

2.3.4 访问指标解释

访问

解释

PV(页面浏览量 Page View)

用户每打开1个网站页面,记录1个PV。用户多次打开同一页面,PV值累计多次

UV(网站独立访客 Unique Visitor)

通过互联网访问、流量网站的自然人。1天内相同访客多次访问网站,只计算为1个独立访客

2.4 如何计算压测指标

  • 压测我们需要有目的性的压测,这次压测我们需要达到什么目标(如:单台机器的性能为100QPS?网站能同时满足100W人同时在线)
  • 可以通过以下计算方法来进行计算:
  • 压测原则:每天80%的访问量集中在20%的时间里,这20%的时间就叫做峰值
  • 公式: ( 总PV数80% ) / ( 每天的秒数20% ) = 峰值时间每秒钟请求数(QPS)
  • 机器: 峰值时间每秒钟请求数(QPS) / 单台机器的QPS = 需要的机器的数量
  • 假设:网站每天的用户数(100W),每天的用户的访问量约为3000W PV,这台机器的需要多少QPS?( 30000000*0.8 ) / (86400 * 0.2) ≈ 1389 (QPS)
  • 假设:单台机器的的QPS是69,需要需要多少台机器来支撑?1389 / 69 ≈ 20

3、常见的压测工具

3.1 ab

  • 简介

ApacheBench 是 Apache服务器自带的一个web压力测试工具,简称ab。ab又是一个命令行工具,对发起负载的本机要求很低,根据ab命令可以创建很多的并发访问线程,模拟多个访问者同时对某一URL地址进行访问,因此可以用来测试目标服务器的负载压力。总的来说ab工具小巧简单,上手学习较快,可以提供需要的基本性能指标,但是没有图形化结果,不能监控。

ab属于一个轻量级的压测工具,结果不会特别准确,可以用作参考。

  • 安装
# 在linux环境安装
sudo yum -y install httpd
  • 用法
Usage: ab [options] [http[s]://]hostname[:port]/path
用法:ab [选项] 地址

选项:
Options are:
-n requests #执行的请求数,即一共发起多少请求。
-c concurrency #请求并发数。
-s timeout #指定每个请求的超时时间,默认是30秒。
-k #启用HTTP KeepAlive功能,即在一个HTTP会话中执行多个请求。默认时,不启用KeepAlive功能。
  • 压测命令
# 使用ab压测工具,对百度的链接 请求100次,并发数1
ab -n 100 -c 1 https://www.baidu.com/

压测结果

~ >ab -n 100 -c 1 https://www.baidu.com/
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking www.baidu.com (be patient).....done


Server Software: BWS/1.1
Server Hostname: www.baidu.com
Server Port: 443
SSL/TLS Protocol: TLSv1.2,ECDHE-RSA-AES128-GCM-SHA256,2048,128

Document Path: /
Document Length: 227 bytes

Concurrency Level: 1
Time taken for tests: 9.430 seconds
Complete requests: 100
Failed requests: 0
Write errors: 0
Total transferred: 89300 bytes
HTML transferred: 22700 bytes
Requests per second: 10.60 [#/sec] (mean)
Time per request: 94.301 [ms] (mean)
Time per request: 94.301 [ms] (mean, across all concurrent requests)
Transfer rate: 9.25 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 54 70 16.5 69 180
Processing: 18 24 12.0 23 140
Waiting: 18 24 12.0 23 139
Total: 72 94 20.5 93 203

Percentage of the requests served within a certain time (ms)
50% 93
66% 99
75% 101
80% 102
90% 108
95% 122
98% 196
99% 203
100% 203 (longest request)
  • 主要关注的测试指标
  • Concurrency Level 并发请求数
  • Time taken for tests 整个测试时间
  • Complete requests 完成请求个数
  • Failed requests 失败个数
  • Requests per second 吞吐量,指的是某个并发用户下单位时间内处理的请求数。等效于QPS,其实可以看作同一个统计方式,只是叫法不同而已。
  • Time per request 用户平均请求等待时间
  • Time per request 服务器处理时间

3.2 Locust

  • 简介

是非常简单易用、分布式、python开发的压力测试工具。有图形化界面,支持将压测数据导出。

  • 安装
# pip3 安装locust
pip3 install locust
# 查看是否安装成功
locust -h
# 运行 Locust 分布在多个进程/机器库
pip3 install pyzmq
# webSocket 压测库
pip3 install websocket-client
  • 用法

编写压测脚本 test.py

from locust import HttpLocust, TaskSet, task

# 定义用户行为
class UserBehavior(TaskSet):

@task
def baidu_index(self):
self.client.get("/")


class WebsiteUser(HttpLocust):
task_set = UserBehavior # 指向一个定义的用户行为类
min_wait = 3000 # 执行事务之间用户等待时间的下界(单位:毫秒)
max_wait = 6000 # 执行事务之间用户等待时间的上界(单位:毫秒)
  • 启动压测
locust -f  test.py --host=https://www.baidu.com

访问 http://localhost:8089 进入压测首页

Number of users to simulate 模拟用户数

Hatch rate (users spawned/second) 每秒钟增加用户数

点击 "Start swarming" 进入压测页面

<figure></figure>locust 首页

压测界面右上角有:被压测的地址、当前状态、RPS、失败率、开始或重启按钮

性能测试参数

  • Type 请求的类型,例如GET/POST
  • Name 请求的路径
  • Request 当前请求的数量
  • Fails 当前请求失败的数量
  • Median 中间值,单位毫秒,请求响应时间的中间值
  • Average 平均值,单位毫秒,请求的平均响应时间
  • Min 请求的最小服务器响应时间,单位毫秒
  • Max 请求的最大服务器响应时间,单位毫秒
  • Average size 单个请求的大小,单位字节
  • Current RPS 代表吞吐量(Requests Per Second的缩写),指的是某个并发用户数下单位时间内处理的请求数。等效于QPS,其实可以看作同一个统计方式,只是叫法不同而已。
<figure>
<figcaption>locust 压测页面</figcaption>
</figure>

3.3 JMete

  • 简介

Apache JMeter是Apache组织开发的基于Java的压力测试工具。用于对软件做压力测试,它最初被设计用于Web应用测试,但后来扩展到其他测试领域。

JMeter能够对应用程序做功能/回归测试,通过创建带有断言的脚本来验证你的程序返回了你期望的结果。

  • 安装

访问 https://jmeter-plugins.org/install/Install/ 下载解压以后即可使用

  • 用法

JMeter的功能过于强大,这里暂时不介绍用法,可以查询相关文档使用(参考文献中有推荐的教程文档)

3.4 云压测

3.4.1 云压测介绍

顾名思义就是将压测脚本部署在云端,通过云端对对我们的应用进行全方位压测,只需要配置压测的参数,无需准备实体机,云端自动给我们分配需要压测的云主机,对被压测目标进行压测。

云压测的优势:

  1. 轻易的实现分布式部署
  2. 能够模拟海量用户的访问
  3. 流量可以从全国各地发起,更加真实的反映用户的体验
  4. 全方位的监控压测指标
  5. 文档比较完善

当然了云压测是一款商业产品,在使用的时候自然还是需要收费的,而且价格还是比较昂贵的~

3.4.2 阿里云 性能测试 PTS

PTS(Performance Testing Service)是面向所有技术背景人员的云化测试工具。有别于传统工具的繁复,PTS以互联网化的交互,提供性能测试、API调试和监测等多种能力。自研和适配开源的功能都可以轻松模拟任意体量的用户访问业务的场景,任务随时发起,免去繁琐的搭建和维护成本。更是紧密结合监控、流控等兄弟产品提供一站式高可用能力,高效检验和管理业务性能。

阿里云同样还是支持渗透测试,通过模拟黑客对业务系统进行全面深入的安全测试。

3.4.3 腾讯云 压测大师 LM

通过创建虚拟机器人模拟多用户的并发场景,提供一整套完整的服务器压测解决方案

4、go-stress-testing go语言实现的压测工具

4.1 介绍

  • go-stress-testing 是go语言实现的简单压测工具,源码开源、支持二次开发,可以压测http、webSocket请求,使用协程模拟单个用户,可以更高效的利用CPU资源。
  • 项目地址 https://github.com/link1st/go-stress-testing

4.2 用法

Usage of ./go-stress-testing-mac:
-c uint
并发数 (default 1)
-d string
调试模式 (default "false")
-n uint
请求总数 (default 1)
-p string
curl文件路径
-u string
请求地址
-v string
验证方法 http 支持:statusCode、json webSocket支持:json (default "statusCode")
  • -n 是单个用户请求的次数,请求总次数 = -c* -n, 这里考虑的是模拟用户行为,所以这个是每个用户请求的次数
  • 下载以后执行下面命令即可压测
  • 使用示例:
# 查看用法
./go-stress-testing-mac

# 使用请求百度页面
./go-stress-testing-mac -c 1 -n 100 -u https://www.baidu.com/

# 使用debug模式请求百度页面
./go-stress-testing-mac -c 1 -n 1 -d true -u https://www.baidu.com/

# 使用 curl文件(文件在curl目录下) 的方式请求
./go-stress-testing-mac -c 1 -n 1 -p curl/baidu.curl.txt

# 压测webSocket连接
./go-stress-testing-mac -c 10 -n 10 -u ws://127.0.0.1:8089/acc
  • 使用 curl文件进行压测

curl是Linux在命令行下的工作的文件传输工具,是一款很强大的http命令行工具。

使用curl文件可以压测使用非GET的请求,支持设置http请求的 method、cookies、header、body等参数

chrome 浏览器生成 curl文件,打开开发者模式(快捷键F12),如图所示,生成 curl 在终端执行命令

<figure>
<figcaption>copy cURL</figcaption>
</figure>

生成内容粘贴到项目目录下的curl/baidu.curl.txt文件中,执行下面命令就可以从curl.txt文件中读取需要压测的内容进行压测了

# 使用 curl文件(文件在curl目录下) 的方式请求
go run main.go -c 1 -n 1 -p curl/baidu.curl.txt

4.3 实现

  • 具体需求可以查看项目源码
  • 项目目录结构
|____main.go                      // main函数,获取命令行参数
|____server // 处理程序目录
| |____dispose.go // 压测启动,注册验证器、启动统计函数、启动协程进行压测
| |____statistics // 统计目录
| | |____statistics.go // 接收压测统计结果并处理
| |____golink // 建立连接目录
| | |____http_link.go // http建立连接
| | |____websocket_link.go // webSocket建立连接
| |____client // 请求数据客户端目录
| | |____http_client.go // http客户端
| | |____websocket_client.go // webSocket客户端
| |____verify // 对返回数据校验目录
| | |____http_verify.go // http返回数据校验
| | |____websokcet_verify.go // webSocket返回数据校验
|____heper // 通用函数目录
| |____heper.go // 通用函数
|____model // 模型目录
| |____request_model.go // 请求数据模型
| |____curl_model.go // curl文件解析
|____vendor // 项目依赖目录

4.4 go-stress-testing 对 Golang web 压测

这里使用go-stress-testing对go server进行压测(部署在同一台机器上),并统计压测结果

  • 申请的服务器配置

CPU: 4核 (Intel Xeon(Cascade Lake) Platinum 8269 2.5 GHz/3.2 GHz)

内存: 16G

硬盘: 20G SSD

系统: CentOS 7.6

go version: go1.12.9 linux/amd64

<figure>
<figcaption>go-stress-testing01</figcaption>
</figure>
  • go serve
package main

import (
"log"
"net/http"
)

const (
httpPort = "8088"
)

func main() {

runtime.GOMAXPROCS(runtime.NumCPU() - 1)

hello := func(w http.ResponseWriter, req *http.Request) {
data := "Hello, World!"

w.Header().Add("Server", "golang")
w.Write([]byte(data))

return
}

http.HandleFunc("/", hello)
err := http.ListenAndServe(":"+httpPort, nil)

if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
  • go_stress_testing 压测命令
./go_stress_testing_linux -c 100 -n 10000 -u http://127.0.0.1:8088/
  • 压测结果

并发数

go_stress_testing QPS

1

6394.86

4

16909.36

10

18456.81

20

19490.50

30

19947.47

50

19922.56

80

19155.33

100

18336.46

200

16813.86

从压测的结果上看:效果还不错,压测QPS有接近2W

5、压测工具的比较

5.1 比较

ab

locust

Jmeter

go-stress-testing

云压测

实现语言

C

Python

Java

Golang

UI界面

优势

使用简单,上手简单

支持分布式、压测数据支持导出

插件丰富,支持生成HTML报告

项目开源,使用简单,没有依赖,支持webSocket压测

更加真实的模拟用户,支持更高的压测力度

5.2 如何选择压测工具

这个世界上没有最好的,只有最适合的,工具千千万,选择一款适合你的才是最重要的

在实际使用中有各种场景,选择工具的时候就需要考虑这些:

  • 明确你的目的,需要做什么压测、压测的目标是什么?
  • 使用的工具你是否熟悉,你愿意花多大的成本了解它?
  • 你是为了测试还是想了解其中的原理?
  • 工具是否能支持你需要压测的场景

6、单台机器100w连接压测实战

6.1 说明

之前写了一篇文章,基于websocket单台机器支持百万连接分布式聊天(IM)系统(不了解这个项目可以查看上一篇或搜索一下文章),这里我们要实现单台机器支持100W连接的压测

目标:

  • 单台机器能保持100W个长连接
  • 机器的CPU、内存、网络、I/O 状态都正常

说明:

gowebsocket 分布式聊天(IM)系统:

  • 之前用户连接以后有个全员广播,这里需要将用户连接、退出等事件关闭
  • 服务器准备:由于自己手上没有自己的服务器,所以需要临时购买的云服务器

压测服务器:

16台(稍后解释为什么需要16台机器)

CPU: 2核

内存: 8G

硬盘: 20G

系统: CentOS 7.6

<figure>
<figcaption>webSocket压测服务器</figcaption>
</figure>

被压测服务:

1台

CPU: 4核

内存: 32G

硬盘: 20G SSD

系统: CentOS 7.6

<figure>
<figcaption>webSocket被压测服务器</figcaption>
</figure>

6.2 内核优化

  • 修改程序最大打开文件数

被压测服务器需要保持100W长连接,客户和服务器端是通过socket通讯的,每个连接需要建立一个socket,程序需要保持100W长连接就需要单个程序能打开100W个文件句柄

# 查看系统默认的值
ulimit -n
# 设置最大打开文件数
ulimit -n 1040000

这里设置的要超过100W,程序除了有100W连接还有其它资源连接(数据库、资源等连接),这里设置为 104W

centOS 7.6 上述设置不生效,需要手动修改配置文件

vim /etc/security/limits.conf

这里需要把硬限制和软限制、root用户和所有用户都设置为 1040000

core 是限制内核文件的大小,这里设置为 unlimited

# 添加以下参数
root soft nofile 1040000
root hard nofile 1040000

root soft nofile 1040000
root hard nproc 1040000

root soft core unlimited
root hard core unlimited

* soft nofile 1040000
* hard nofile 1040000

* soft nofile 1040000
* hard nproc 1040000

* soft core unlimited
* hard core unlimited

注意:

/proc/sys/fs/file-max 表示系统级别的能够打开的文件句柄的数量,不能小于limits中设置的值

如果file-max的值小于limits设置的值会导致系统重启以后无法登录

# file-max 设置的值参考
cat /proc/sys/fs/file-max
12553500

修改以后重启服务器,ulimit -n 查看配置是否生效

6.3 客户端配置

由于linux端口的范围是 0~65535(2^16-1)这个和操作系统无关,不管linux是32位的还是64位的

这个数字是由于tcp协议决定的,tcp协议头部表示端口只有16位,所以最大值只有65535(如果每台机器多几个虚拟ip就能突破这个限制)

1024以下是系统保留端口,所以能使用的1024到65535

如果需要100W长连接,每台机器有 65535-1024 个端口, 100W / (65535-1024) ≈ 15.5,所以这里需要16台服务器

  • vim /etc/sysctl.conf 在文件末尾添加
net.ipv4.ip_local_port_range = 1024 65000
net.ipv4.tcp_mem = 786432 2097152 3145728
net.ipv4.tcp_rmem = 4096 4096 16777216
net.ipv4.tcp_wmem = 4096 4096 16777216

sysctl -p 修改配置以后使得配置生效命令

配置解释:

  • ip_local_port_range 表示TCP/UDP协议允许使用的本地端口号 范围:1024~65000
  • tcp_mem 确定TCP栈应该如何反映内存使用,每个值的单位都是内存页(通常是4KB)。第一个值是内存使用的下限;第二个值是内存压力模式开始对缓冲区使用应用压力的上限;第三个值是内存使用的上限。在这个层次上可以将报文丢弃,从而减少对内存的使用。对于较大的BDP可以增大这些值(注意,其单位是内存页而不是字节)
  • tcp_rmem 为自动调优定义socket使用的内存。第一个值是为socket接收缓冲区分配的最少字节数;第二个值是默认值(该值会被rmem_default覆盖),缓冲区在系统负载不重的情况下可以增长到这个值;第三个值是接收缓冲区空间的最大字节数(该值会被rmem_max覆盖)。
  • tcp_wmem 为自动调优定义socket使用的内存。第一个值是为socket发送缓冲区分配的最少字节数;第二个值是默认值(该值会被wmem_default覆盖),缓冲区在系统负载不重的情况下可以增长到这个值;第三个值是发送缓冲区空间的最大字节数(该值会被wmem_max覆盖)。

6.4 准备

  1. 在被压测服务器上启动Server服务(gowebsocket)
  2. 查看被压测服务器的内网端口
  3. 登录上16台压测服务器,这里我提前把需要优化的系统做成了镜像,申请机器的时候就可以直接使用这个镜像(参数已经调好)
<figure>
<figcaption>压测服务器16台准备</figcaption>
</figure>
  1. 启动压测
 ./go_stress_testing_linux -c 62500 -n 1  -u ws://192.168.0.74:443/acc

62500*16 = 100W正好可以达到我们的要求

建立连接以后,-n 1发送一个ping的消息给服务器,收到响应以后保持连接不中断

  1. 通过 gowebsocket服务器的http接口,实时查询连接数和项目启动的协程数
  2. 压测过程中查看系统状态
# linux 命令
ps # 查看进程内存、cup使用情况
iostat # 查看系统IO情况
nload # 查看网络流量情况
/proc/pid/status # 查看进程状态

6.5 压测数据

  • 压测以后,查看连接数到100W,然后保持10分钟观察系统是否正常
  • 观察以后,系统运行正常、CPU、内存、I/O 都正常,打开页面都正常
  • 压测完成以后的数据

查看goWebSocket连接数统计,可以看到 clientsLen连接数为100W,goroutine数量2000008个,每个连接两个goroutine加上项目启动默认的8个。这里可以看到连接数满足了100W

<figure>
<figcaption>查看goWebSocket连接数统计</figcaption>
</figure>

从压测服务上查看连接数是否达到了要求,压测完成的统计数据并发数为62500,是每个客户端连接的数量,总连接数: 62500*16=100W

<figure>
<figcaption>压测服务16台 压测完成</figcaption>
</figure>
  • 记录内存使用情况,分别记录了1W到100W连接数内存使用情况

连接数

内存

10000

281M

100000

2.7g

200000

5.4g

500000

13.1g

1000000

25.8g

100W连接时的查看内存详细数据:

cat /proc/pid/status
VmSize: 27133804 kB

27133804/1000000≈27.1 100W连接,占用了25.8g的内存,粗略计算了一下,一个连接占用了27.1Kb的内存,由于goWebSocket项目每个用户连接起了两个协程处理用户的读写事件,所以内存占用稍微多一点

如果需要如何减少内存使用可以参考 @Roy11568780 大佬给的解决方案

传统的golang中是采用的一个goroutine循环read的方法对应每一个socket。实际百万链路场景中这是巨大的资源浪费,优化的原理也不是什么新东西,golang中一样也可以使用epoll的,把fd拿到epoll中,检测到事件然后在协程池里面去读就行了,看情况读写分别10-20的协程goroutine池应该就足够了

至此,压测已经全部完成,单台机器支持100W连接已经满足~

7、总结

到这里压测总算完成,本次压测花费16元巨款。

单台机器支持100W连接是实测是满足的,但是实际业务比较复杂,还是需要持续优化~

本文通过介绍什么是压测,在什么情况下需要压测,通过单台机器100W长连接的压测实战了解Linux内核的参数的调优。如果觉得现有的压测工具不适用,可以自己实现或者是改造成属于自己的自己的工具。

8、参考文献

性能测试工具

性能测试常见名词解释

性能测试名词解释

PV、TPS、QPS是怎么计算出来的?

超实用压力测试工具-ab工具

Locust 介绍

Jmeter性能测试 入门

基于websocket单台机器支持百万连接分布式聊天(IM)系统

https://github.com/link1st/go-stress-testing

github 搜:link1st 查看项目 go-stress-testing


招聘golang开发&架构师

招聘应聘君莫笑 发表了文章 • 0 个评论 • 537 次浏览 • 2019-08-27 12:46 • 来自相关话题

企业:一二线大厂,大厂,创业公司都有。 地点:北京上海杭州。 待遇:薪资open,具体可谈。 微信941605108
企业:一二线大厂,大厂,创业公司都有。
地点:北京上海杭州。
待遇:薪资open,具体可谈。
微信941605108

拒绝 996,弹性时间搭配各种福利,来美餐做我的同事吗?

招聘应聘recruiter 发表了文章 • 0 个评论 • 463 次浏览 • 2019-08-13 10:34 • 来自相关话题

工作日上午 10 点,美餐( meican.com ) 11 层产品技术部的办公室里,几个习惯早到的工程师已经打开 MacBook 敲下了几行代码,他们在持续优化企业订餐的预订、交互功能。 活跃于微博的工程师飞树先生刚刚经过中关村地区 ...查看全部
工作日上午 10 点,美餐( meican.com ) 11 层产品技术部的办公室里,几个习惯早到的工程师已经打开 MacBook 敲下了几行代码,他们在持续优化企业订餐的预订、交互功能。

活跃于微博的工程师飞树先生刚刚经过中关村地区的海淀黄庄地铁站,候车间隙对面的广告牌上刚好是他的个人特 写和招聘词:「我是美餐 BUG 开发工程师,我们正在招聘技术大牛」。

习惯了夜间创作的 LETO,已经被 Slack 里关于企业 wiki 的问题叫醒,随即就在 Asana 里增加了一个 Task。

美餐每年投入数万美元购买的 Confluence,经过他的二次开发,已经成为了公司内部不可缺少的知识库和交流中心。

在美餐,每个人都是多面手,从参与解决普通的技术问题,到带领团队独当一面兼顾前端后端和设计。在这个扁平化的团队中,工程师需要担任 leader 的角色,发挥创新思维,创造极致产品和体验。

除了写代码改 bug,工程师们还会拉琴、摄影、骑车,会在团队欢聚的时候带上专业单反拍摄精美大片。从美餐还没上线就在参与创业的元老级工程师马老师,甚至连盛大的婚礼都在办公室完成了。

你看,工程师们是一群有着不同爱好的有趣的人,除了精通 Lua / Go / Erlang / Scala / Ruby / R 语言,他们还是音乐达人、绘画才子和马拉松健将,会将冰冷的代码组合成有趣的产品,也会将平凡的生活过出自己的乐趣。

在美餐,没有日复一日乏味的工作,更多的是有意思的创作,每一次敲键盘、做优化、改设计,会让美餐用户每一天的浏览、订餐、下单、欢聚更加快捷顺畅,帮助更多的企业客户提升员工幸福感。

美餐是中国领先的企业订餐和综合消费平台,目前覆盖北京、上海、广州、深圳、成都等城市,为数千家企业客户员工提供企业用餐、企业活动以及精选欢聚活动推荐等综合消费服务。
***
**关于美餐:**

2011 年,获得来自真格基金和九合创投的天使投资。

2012 年,获得来自 KPCB 的 A 轮投资。

2013 年,获得来自 NGP 的 B 轮投资。

2014 年,获得来自挚信资本的 B+ 轮投资。

2015 年,获得来自美团点评的 C 轮战略投资。

2016 年,美餐荣获中国最具潜力创业公司,员工福利管理服务机构十强,年度最佳企业服务商等荣誉。

2017 年,美餐荣获中国团餐高成长性品牌企业,中国团餐十强企业,中国生活服务产业十大创新力企业等荣誉。

2017 年,获得来自高盛( Goldman Sachs )的 D 轮投资。

2018 年,美餐荣获中国餐饮百强企业。美餐智能餐柜 SMARTWAITER W1 荣获德国 iF 设计奖和红点奖。

2018 年,获得来自阳光保险的 D+ 轮投资。
***
**工作环境:**

Herman Miller Embody 人体工学座椅

B&W; Zeppelin Air 无线音响

De'Lo- nghi 全自动咖啡机

PlayStation 4/Xbox One 游戏机

3D 打印机

Blueair 空气净化器

大提琴 & 小提琴

各种 Apple 产品

懒人沙发

**员工福利:**

每 2 年报销一台 Mac (归个人所有)

每月团建吃喝腐败

无限量零食、饮料

免费午餐、晚餐

分配期权

弹性工作,不打卡

重视技术,无官僚,Geek 团队

介绍对象
***
**招聘职位:**

Web 全栈开发工程师

Golang 开发工程师

Web 前端工程师

BI 开发工程师 / 数仓开发工程师

嵌入式软件开发

数据分析师
***
团队:大牛+极客+文艺男;我们团队的成员来自谷歌,阿里巴巴,腾讯,头条、滴滴以及行业内知名公司;

技术:从 UI 到代码的简洁,却凝聚最挑战的设计、线上满是科技,线下满是黑科技。

如果你热爱技术,如果你具有产品思维(开发负责产品规划,我们木有产品经理),如果你喜欢自由,创新...来美餐网哦,这里的平台绝对适合你!欢迎加入美餐,和我们一起快速成长!更多信息请登录 www.meican.com
***
工作地点:北京市海淀区中国外文大厦

薪酬待遇:具有行业竞争力的整体薪酬
***
**简历投递:**

cuilixia@meican.com

标题注明「来自 gopher 」

请附上 GitHub / Blog 链接

加分项请你自己发挥

我们喜欢认真有趣的人 :-)

基于websocket单台机器支持百万连接分布式聊天(IM)系统

文章分享link1st 发表了文章 • 1 个评论 • 1167 次浏览 • 2019-08-12 15:11 • 来自相关话题

本文将介绍如何实现一个基于websocket分布式聊天(IM)系统。使用golang实现websocket通讯,单机可以支持百万连接,使用gin框架、nginx负载、可以水平部署、程序内部相互通讯、使用grpc通讯 ...查看全部


本文将介绍如何实现一个基于websocket分布式聊天(IM)系统。

使用golang实现websocket通讯,单机可以支持百万连接,使用gin框架、nginx负载、可以水平部署、程序内部相互通讯、使用grpc通讯协议。

本文内容比较长,如果直接想clone项目体验直接进入项目体验 goWebSocket项目下载 ,文本从介绍webSocket是什么开始,然后开始介绍这个项目,以及在Nginx中配置域名做webSocket的转发,然后介绍如何搭建一个分布式系统。

目录

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>1、项目说明

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>1.1 goWebSocket

本文将介绍如何实现一个基于websocket聊天(IM)分布式系统。

使用golang实现websocket通讯,单机支持百万连接,使用gin框架、nginx负载、可以水平部署、程序内部相互通讯、使用grpc通讯协议。

  • 一般项目中webSocket使用的架构图 网站架构图

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>1.2 项目体验

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>2、介绍webSocket

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>2.1 webSocket 是什么

WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。

它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

  • HTTP和WebSocket在通讯过程的比较 HTTP协议和WebSocket比较

  • HTTP和webSocket都支持配置证书,ws:// 无证书 wss:// 配置证书的协议标识 HTTP协议和WebSocket比较

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>2.2 webSocket的兼容性

  • 浏览器的兼容性,开始支持webSocket的版本

浏览器开始支持webSocket的版本

  • 服务端的支持

golang、java、php、node.js、python、nginx 都有不错的支持

  • Android和IOS的支持

Android可以使用java-webSocket对webSocket支持

iOS 4.2及更高版本具有WebSockets支持

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>2.3 为什么要用webSocket

    1. 从业务上出发,需要一个主动通达客户端的能力

目前大多数的请求都是使用HTTP,都是由客户端发起一个请求,有服务端处理,然后返回结果,不可以服务端主动向某一个客户端主动发送数据

服务端处理一个请求

    1. 大多数场景我们需要主动通知用户,如:聊天系统、用户完成任务主动告诉用户、一些运营活动需要通知到在线的用户
    1. 可以获取用户在线状态
    1. 在没有长连接的时候通过客户端主动轮询获取数据
    1. 可以通过一种方式实现,多种不同平台(H5/Android/IOS)去使用

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>2.4 webSocket建立过程

    1. 客户端先发起升级协议的请求

客户端发起升级协议的请求,采用标准的HTTP报文格式,在报文中添加头部信息

Connection: Upgrade表明连接需要升级

Upgrade: websocket需要升级到 websocket协议

Sec-WebSocket-Version: 13 协议的版本为13

Sec-WebSocket-Key: I6qjdEaqYljv3+9x+GrhqA== 这个是base64 encode 的值,是浏览器随机生成的,与服务器响应的 Sec-WebSocket-Accept对应

# Request Headers
Connection: Upgrade
Host: im.91vh.com
Origin: http://im.91vh.com
Pragma: no-cache
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: I6qjdEaqYljv3+9x+GrhqA==
Sec-WebSocket-Version: 13
Upgrade: websocket

浏览器 Network

    1. 服务器响应升级协议

服务端接收到升级协议的请求,如果服务端支持升级协议会做如下响应

返回:

Status Code: 101 Switching Protocols 表示支持切换协议

# Response Headers
Connection: upgrade
Date: Fri, 09 Aug 2019 07:36:59 GMT
Sec-WebSocket-Accept: mB5emvxi2jwTUhDdlRtADuBax9E=
Server: nginx/1.12.1
Upgrade: websocket
    1. 升级协议完成以后,客户端和服务器就可以相互发送数据

websocket接收和发送数据

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>3、如何实现基于webSocket的长连接系统

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>3.1 使用go实现webSocket服务端

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>3.1.1 启动端口监听

  • websocket需要监听端口,所以需要在golang 成功的 main 函数中用协程的方式去启动程序
  • main.go 实现启动
go websocket.StartWebSocket()
  • init_acc.go 启动程序
// 启动程序
func StartWebSocket() {
http.HandleFunc("/acc", wsPage)
http.ListenAndServe(":8089", nil)
}

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>3.1.2 升级协议

  • 客户端是通过http请求发送到服务端,我们需要对http协议进行升级为websocket协议
  • 对http请求协议进行升级 golang 库gorilla/websocket 已经做得很好了,我们直接使用就可以了
  • 在实际使用的时候,建议每个连接使用两个协程处理客户端请求数据和向客户端发送数据,虽然开启协程会占用一些内存,但是读取分离,减少收发数据堵塞的可能
  • init_acc.go
func wsPage(w http.ResponseWriter, req *http.Request) {

// 升级协议
conn, err := (&websocket.Upgrader{CheckOrigin: func(r *http.Request) bool {
fmt.Println("升级协议", "ua:", r.Header["User-Agent"], "referer:", r.Header["Referer"])

return true
}}).Upgrade(w, req, nil)
if err != nil {
http.NotFound(w, req)

return
}

fmt.Println("webSocket 建立连接:", conn.RemoteAddr().String())

currentTime := uint64(time.Now().Unix())
client := NewClient(conn.RemoteAddr().String(), conn, currentTime)

go client.read()
go client.write()

// 用户连接事件
clientManager.Register <- client
}

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>3.1.3 客户端连接的管理

  • 当前程序有多少用户连接,还需要对用户广播的需要,这里我们就需要一个管理者(clientManager),处理这些事件:
  • 记录全部的连接、登录用户的可以通过 appId+uuid 查到用户连接
  • 使用map存储,就涉及到多协程并发读写的问题,所以需要加读写锁
  • 定义四个channel ,分别处理客户端建立连接、用户登录、断开连接、全员广播事件
// 连接管理
type ClientManager struct {
Clients map[*Client]bool // 全部的连接
ClientsLock sync.RWMutex // 读写锁
Users map[string]*Client // 登录的用户 // appId+uuid
UserLock sync.RWMutex // 读写锁
Register chan *Client // 连接连接处理
Login chan *login // 用户登录处理
Unregister chan *Client // 断开连接处理程序
Broadcast chan []byte // 广播 向全部成员发送数据
}

// 初始化
func NewClientManager() (clientManager *ClientManager) {
clientManager = &ClientManager{
Clients: make(map[*Client]bool),
Users: make(map[string]*Client),
Register: make(chan *Client, 1000),
Login: make(chan *login, 1000),
Unregister: make(chan *Client, 1000),
Broadcast: make(chan []byte, 1000),
}

return
}

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>3.1.4 注册客户端的socket的写的异步处理程序

  • 防止发生程序崩溃,所以需要捕获异常
  • 为了显示异常崩溃位置这里使用string(debug.Stack())打印调用堆栈信息
  • 如果写入数据失败了,可能连接有问题,就关闭连接
  • client.go
// 向客户端写数据
func (c *Client) write() {
defer func() {
if r := recover(); r != nil {
fmt.Println("write stop", string(debug.Stack()), r)

}
}()

defer func() {
clientManager.Unregister <- c
c.Socket.Close()
fmt.Println("Client发送数据 defer", c)
}()

for {
select {
case message, ok := <-c.Send:
if !ok {
// 发送数据错误 关闭连接
fmt.Println("Client发送数据 关闭连接", c.Addr, "ok", ok)

return
}

c.Socket.WriteMessage(websocket.TextMessage, message)
}
}
}

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>3.1.5 注册客户端的socket的读的异步处理程序

  • 循环读取客户端发送的数据并处理
  • 如果读取数据失败了,关闭channel
  • client.go
// 读取客户端数据
func (c *Client) read() {
defer func() {
if r := recover(); r != nil {
fmt.Println("write stop", string(debug.Stack()), r)
}
}()

defer func() {
fmt.Println("读取客户端数据 关闭send", c)
close(c.Send)
}()

for {
_, message, err := c.Socket.ReadMessage()
if err != nil {
fmt.Println("读取客户端数据 错误", c.Addr, err)

return
}

// 处理程序
fmt.Println("读取客户端数据 处理:", string(message))
ProcessData(c, message)
}
}

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>3.1.6 接收客户端数据并处理

  • 约定发送和接收请求数据格式,为了js处理方便,采用了json的数据格式发送和接收数据(人类可以阅读的格式在工作开发中使用是比较方便的)

  • 登录发送数据示例:

{"seq":"1565336219141-266129","cmd":"login","data":{"userId":"马远","appId":101}}
  • 登录响应数据示例:
{"seq":"1565336219141-266129","cmd":"login","response":{"code":200,"codeMsg":"Success","data":null}}
  • websocket是双向的数据通讯,可以连续发送,如果发送的数据需要服务端回复,就需要一个seq来确定服务端的响应是回复哪一次的请求数据

  • cmd 是用来确定动作,websocket没有类似于http的url,所以规定 cmd 是什么动作

  • 目前的动作有:login/heartbeat 用来发送登录请求和连接保活(长时间没有数据发送的长连接容易被浏览器、移动中间商、nginx、服务端程序断开)

  • 为什么需要AppId,UserId是表示用户的唯一字段,设计的时候为了做成通用性,设计AppId用来表示用户在哪个平台登录的(web、app、ios等),方便后续扩展

  • request_model.go 约定的请求数据格式

/************************  请求数据  **************************/
// 通用请求数据格式
type Request struct {
Seq string `json:"seq"` // 消息的唯一Id
Cmd string `json:"cmd"` // 请求命令字
Data interface{} `json:"data,omitempty"` // 数据 json
}

// 登录请求数据
type Login struct {
ServiceToken string `json:"serviceToken"` // 验证用户是否登录
AppId uint32 `json:"appId,omitempty"`
UserId string `json:"userId,omitempty"`
}

// 心跳请求数据
type HeartBeat struct {
UserId string `json:"userId,omitempty"`
}
  • response_model.go
/************************  响应数据  **************************/
type Head struct {
Seq string `json:"seq"` // 消息的Id
Cmd string `json:"cmd"` // 消息的cmd 动作
Response *Response `json:"response"` // 消息体
}

type Response struct {
Code uint32 `json:"code"`
CodeMsg string `json:"codeMsg"`
Data interface{} `json:"data"` // 数据 json
}

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>3.1.7 使用路由的方式处理客户端的请求数据

  • 使用路由的方式处理由客户端发送过来的请求数据
  • 以后添加请求类型以后就可以用类是用http相类似的方式(router-controller)去处理
  • acc_routers.go
// Websocket 路由
func WebsocketInit() {
websocket.Register("login", websocket.LoginController)
websocket.Register("heartbeat", websocket.HeartbeatController)
}

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>3.1.8 防止内存溢出和Goroutine不回收

    1. 定时任务清除超时连接 没有登录的连接和登录的连接6分钟没有心跳则断开连接

client_manager.go

// 定时清理超时连接
func ClearTimeoutConnections() {
currentTime := uint64(time.Now().Unix())

for client := range clientManager.Clients {
if client.IsHeartbeatTimeout(currentTime) {
fmt.Println("心跳时间超时 关闭连接", client.Addr, client.UserId, client.LoginTime, client.HeartbeatTime)

client.Socket.Close()
}
}
}
    1. 读写的Goroutine有一个失败,则相互关闭 write()Goroutine写入数据失败,关闭c.Socket.Close()连接,会关闭read()Goroutine read()Goroutine读取数据失败,关闭close(c.Send)连接,会关闭write()Goroutine
    1. 客户端主动关闭 关闭读写的Goroutine 从ClientManager删除连接
    1. 监控用户连接、Goroutine数 十个内存溢出有九个和Goroutine有关 添加一个http的接口,可以查看系统的状态,防止Goroutine不回收 查看系统状态
    1. Nginx 配置不活跃的连接释放时间,防止忘记关闭的连接
    1. 使用 pprof 分析性能、耗时

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>3.2 使用javaScript实现webSocket客户端

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>3.2.1 启动并注册监听程序

  • js 建立连接,并处理连接成功、收到数据、断开连接的事件处理
ws = new WebSocket("ws://127.0.0.1:8089/acc");


ws.onopen = function(evt) {
console.log("Connection open ...");
};

ws.onmessage = function(evt) {
console.log( "Received Message: " + evt.data);
data_array = JSON.parse(evt.data);
console.log( data_array);
};

ws.onclose = function(evt) {
console.log("Connection closed.");
};

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>3.2.2 发送数据

  • 需要注意:连接建立成功以后才可以发送数据
  • 建立连接以后由客户端向服务器发送数据示例
登录:
ws.send('{"seq":"2323","cmd":"login","data":{"userId":"11","appId":101}}');

心跳:
ws.send('{"seq":"2324","cmd":"heartbeat","data":{}}');

ping 查看服务是否正常:
ws.send('{"seq":"2325","cmd":"ping","data":{}}');

关闭连接:
ws.close();

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>3.3 发送消息

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>3.3.1 文本消息

客户端只要只到发送用户是谁,还有内容就可以显示文本消息,这里我们重点关注一下数据部分

target:定义接收的目标,目前未设置

type:消息的类型,text 文本消息 img 图片消息

msg:文本消息内容

from:消息的发送者

文本消息的结构:

{
"seq": "1569080188418-747717",
"cmd": "msg",
"response": {
"code": 200,
"codeMsg": "Ok",
"data": {
"target": "",
"type": "text",
"msg": "hello",
"from": "马超"
}
}
}

这样一个文本消息的结构就设计完成了,客户端在接收到消息内容就可以展现到 IM 界面上

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>3.3.2 图片和语言消息

发送图片消息,发送消息者的客户端需要先把图片上传到文件服务器,上传成功以后获得图片访问的 URL,然后由发送消息者的客户端需要将图片 URL 发送到 gowebsocket,gowebsocket 图片的消息格式发送给目标客户端,消息接收者客户端接收到图片的 URL 就可以显示图片消息。

图片消息的结构:

{
"type": "img",
"from": "马超",
"url": "http://91vh.com/images/home_logo.png",
"secret": "消息鉴权 secret",
"size": {
"width": 480,
"height": 720
}
}

语言消息、和视频消息和图片消息类似,都是先把文件上传服务器,然后通过 gowebsocket 传递文件的 URL,需要注意的是部分消息涉及到隐私的文件,文件访问的时候需要做好鉴权信息,不能让非接收用户也能查看到别人的消息内容。

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>4、goWebSocket 项目

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>4.1 项目说明

  • 本项目是基于webSocket实现的分布式IM系统

  • 客户端随机分配用户名,所有人进入一个聊天室,实现群聊的功能

  • 单台机器(24核128G内存)支持百万客户端连接

  • 支持水平部署,部署的机器之间可以相互通讯

  • 项目架构图 网站架构图

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>4.2 项目依赖

  • 本项目只需要使用 redis 和 golang
  • 本项目使用govendor管理依赖,克隆本项目就可以直接使用
# 主要使用到的包
github.com/gin-gonic/gin@v1.4.0
github.com/go-redis/redis
github.com/gorilla/websocket
github.com/spf13/viper
google.golang.org/grpc
github.com/golang/protobuf

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>4.3 项目启动

  • 克隆项目
git clone git@github.com:link1st/gowebsocket.git
# 或
git clone https://github.com/link1st/gowebsocket.git
  • 修改项目配置
cd gowebsocket
cd config
mv app.yaml.example app.yaml
# 修改项目监听端口,redis连接等(默认127.0.0.1:3306)
vim app.yaml
# 返回项目目录,为以后启动做准备
cd ..
  • 配置文件说明
app:
logFile: log/gin.log # 日志文件位置
httpPort: 8080 # http端口
webSocketPort: 8089 # webSocket端口
rpcPort: 9001 # 分布式部署程序内部通讯端口
httpUrl: 127.0.0.1:8080
webSocketUrl: 127.0.0.1:8089


redis:
addr: "localhost:6379"
password: ""
DB: 0
poolSize: 30
minIdleConns: 30
  • 启动项目
go run main.go

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>5、webSocket项目Nginx配置

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>5.1 为什么要配置Nginx

  • 使用nginx实现内外网分离,对外只暴露Nginx的Ip(一般的互联网企业会在nginx之前加一层LVS做负载均衡),减少入侵的可能
  • 使用Nginx可以利用Nginx的负载功能,前端再使用的时候只需要连接固定的域名,通过Nginx将流量分发了到不同的机器
  • 同时我们也可以使用Nginx的不同的负载策略(轮询、weight、ip_hash)

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>5.2 nginx配置

  • 使用域名 im.91vh.com 为示例,参考配置
  • 一级目录im.91vh.com/acc 是给webSocket使用,是用nginx stream转发功能(nginx 1.3.31 开始支持,使用Tengine配置也是相同的),转发到golang 8089 端口处理
  • 其它目录是给HTTP使用,转发到golang 8080 端口处理
upstream  go-im
{
server 127.0.0.1:8080 weight=1 max_fails=2 fail_timeout=10s;
keepalive 16;
}

upstream go-acc
{
server 127.0.0.1:8089 weight=1 max_fails=2 fail_timeout=10s;
keepalive 16;
}


server {
listen 80 ;
server_name im.91vh.com;
index index.html index.htm ;


location /acc {
proxy_set_header Host $host;
proxy_pass http://go-acc;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Connection "";
proxy_redirect off;
proxy_intercept_errors on;
client_max_body_size 10m;
}

location /
{
proxy_set_header Host $host;
proxy_pass http://go-im;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_redirect off;
proxy_intercept_errors on;
client_max_body_size 30m;
}

access_log /link/log/nginx/access/im.log;
error_log /link/log/nginx/access/im.error.log;
}

<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>5.3 问题处理

  • 运行nginx测试命令,查看配置文件是否正确
/link/server/tengine/sbin/nginx -t

  • 如果出现错误
nginx: [emerg] unknown "connection_upgrade" variable
configuration file /link/server/tengine/conf/nginx.conf test failed
  • 处理方法
  • nginx.com添加
http{
fastcgi_temp_file_write_size 128k;
..... # 需要添加的内容

#support websocket
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

.....
gzip on;

}

  • 原因:Nginx代理webSocket的时候就会遇到Nginx的设计问题 End-to-end and Hop-by-hop Headers


更多内容查看 
gowebsocket

go + koa = ? 一个新的web框架 goa 诞生

Go开源项目nicholascao 发表了文章 • 1 个评论 • 465 次浏览 • 2019-08-08 15:52 • 来自相关话题

## koajs 相信绝大部分使用nodejs的开发者都知道[koa](https://koa.bootcss.com/),甚至每天都在跟koa打交道。 ## goa 最近因工作需要 ...查看全部
## koajs

相信绝大部分使用nodejs的开发者都知道[koa](https://koa.bootcss.com/),甚至每天都在跟koa打交道。

## goa

最近因工作需要从nodejs转到go,因此开发了一个koa for golang的web框架--goa。
几乎一样的语法,一样基于中间件。

github地址:[goa](https://github.com/goa-go/goa)

demo:

``` golang
package main

import (
"fmt"
"time"

"github.com/goa-go/goa"
"github.com/goa-go/goa/router"
)

func logger(c *goa.Context, next func()) {
start := time.Now()

fmt.Printf("[%s] <-- %s %s\n", start.Format("2006-6-2 15:04:05"), c.Method, c.URL)
next()
fmt.Printf("[%s] --> %s %s %d%s\n", time.Now().Format("2006-6-2 15:04:05"), c.Method, c.URL, time.Since(start).Nanoseconds()/1e6, "ms")
}

func json(c *goa.Context) {
c.JSON(goa.M{
"string": "string",
"int": 1,
"json": goa.M{
"key": "value",
},
})
}

func main() {
app := goa.New()
router := router.New()

router.GET("/", func(c *goa.Context) {
c.String("hello world")
})
router.GET("/json", json)

app.Use(logger)
app.Use(router.Routes())
app.Listen(":3000")
}
```
如果觉得这个项目不错的话,请给个star给予作者鼓励,
另外欢迎fork和加入开发团队共建。

再次贴上地址https://github.com/goa-go/goa

招初/中/高级golang开发,base广州天河(15-40K)

招聘应聘bmkcrypto 发表了文章 • 4 个评论 • 487 次浏览 • 2019-07-31 11:36 • 来自相关话题

PS:公司已有较成熟的 GO 语言研发团队,目前团队用到的都是最新技术:K8s,Istio,微服务等,期望有相关经验或者是有志于往 GO 语言发展的小伙伴加入!!! 职位职责: 1.负责系统服务端技术选型和架 ...查看全部
PS:公司已有较成熟的 GO 语言研发团队,目前团队用到的都是最新技术:K8s,Istio,微服务等,期望有相关经验或者是有志于往 GO 语言发展的小伙伴加入!!!

职位职责:

1.负责系统服务端技术选型和架构;

2.负责系统核心模块的设计,开发和维护。

任职要求:

1.1 年以上 Golang 开发经验;

2.熟悉服务端接口开发,有一定的技术架构经验;

3.熟悉 http,protobuf,grpc ;

4.熟悉 goroutine,channel,io,http 等模块;

5.熟悉 gin,xorm,sqlboiler,go-micro 等优先;

6.掌握 mysql,pgsql,redis 基本使用;

7.有分布式,高并发系统开发经验优先;

8.掌握 linux,shell,js,docker 优先;

9.有个人技术博客优先。

联系方式:

[ QQ ] 1426589457 (微信同号)

[邮箱] hr@bmkcrypto.com