技术讨论

技术讨论

bench fasthttp 和 库自带的net.http性能,fasthttp完败?

技术讨论tsingson 回复了问题 • 3 人关注 • 4 个回复 • 57 次浏览 • 2 小时前 • 来自相关话题

写了一个100%Go语言的Web-Term-SSH 堡垒机项目欢迎大家拍砖

回复

文章分享jjjjerk 发起了问题 • 1 人关注 • 0 个回复 • 288 次浏览 • 2019-10-22 15:53 • 来自相关话题

使用 Docker 构建 Nebula Graph 源码

文章分享NebulaGraph 发表了文章 • 0 个评论 • 633 次浏览 • 2019-09-06 10:16 • 来自相关话题

![](https://pic4.zhimg.com/v2-8c5114adb5b955b5a52df78ac2ede317_1200x500.jpg) ### Nebula Graph 介绍 [Nebula ...查看全部
![](https://pic4.zhimg.com/v2-8c5114adb5b955b5a52df78ac2ede317_1200x500.jpg)

### Nebula Graph 介绍

[Nebula Graph](https://0x7.me/go2github) 是开源的高性能分布式图数据库。项目使用 C++ 语言开发,`cmake` 工具构建。其中两个重要的依赖是 Facebook 的 Thrift RPC 框架和 [folly 库](https://github.com/facebook/folly).

由于项目采用了 C++ 14 标准开发,需要使用较新版本的编译器和一些三方库。虽然 Nebula Graph 官方给出了一份[开发者构建指南](https://github.com/vesoft-inc/nebula/blob/master/docs/manual-CN/how-to-build.md),但是在本地构建完整的编译环境依然不是一件轻松的事。

### 开发环境构建

Nebula Graph 依赖较多,且一些第三方库需本地编译安装,为了方便开发者本地编译项目源码, Nebula Graph 官方为大家提供了一个预安装所有依赖的 [docker 镜像]([docker hub](https://hub.docker.com/r/vesoft/nebula-dev))。开发者只需如下的三步即可快速的编译 Nebula Graph 工程,参与 Nebula Graph 的开源贡献:

- 本地安装好 Docker

- 将 [`vesoft/nebula-dev`](https://hub.docker.com/r/vesoft/nebula-dev) 镜像 `pull` 到本地

```shell
$ docker pull vesoft/nebula-dev
```

- 运行 `Docker` 并挂载 Nebula 源码目录到容器的 `/home/nebula` 目录

```shell
$ docker run --rm -ti -v {nebula-root-path}:/home/nebula vesoft/nebula-dev bash
```

> 社区小伙伴@阿东 友情建议:记得把上面的 {nebula-root-path}
替换成你 Nebula Graph 实际 clone 的目录

为了避免每次退出 docker 容器之后,重新键入上述的命令,我们在 [vesoft-inc/nebula-dev-docker](https://github.com/vesoft-inc/nebula-dev-docker.git) 中提供了一个简单的 `build.sh` 脚本,可通过 `./build.sh /path/to/nebula/root/` 进入容器。

- 使用 `cmake` 构建 Nebula 工程

```shell
docker> mkdir _build && cd _build
docker> cmake .. && make -j2
docker> ctest # 执行单元测试
```

### 提醒

Nebula 项目目前主要采用静态依赖的方式编译,加上附加的一些调试信息,所以生产的一些可执行文件会比较占用磁盘空间,建议小伙伴预留 20G 以上的空闲空间给 Nebula 目录 :)

### Docker 加速小 Tips

由于 Docker 镜像文件存储在国外,在 pull 过程中会遇到速度过慢的问题,这里 Nebula Graph 提供一种加速 pull 的方法:通过配置国内地址解决,例如:
- Azure 中国镜像 https://dockerhub.azk8s.cn
- 七牛云 https://reg-mirror.qiniu.com

Linux 小伙伴可在 `/etc/docker/daemon.json` 中加入如下内容(若文件不存在,请新建该文件)

```
{
"registry-mirrors": [
"https://dockerhub.azk8s.cn",
"https://reg-mirror.qiniu.com"
]
}
```
macOS 小伙伴请点击 `Docker Desktop 图标 -> Preferences -> Daemon -> Registry mirrors`。 在列表中添加 `https://dockerhub.azk8s.cn` 和 `https://reg-mirror.qiniu.com` 。修改后,点击 Apply & Restart 按钮, 重启 Docker。

![](https://pic3.zhimg.com/80/v2-6d2dd1b7e5999207ace1b590d31a15ea_hd.jpg)

### Nebula Graph 社区

Nebula Graph 社区是由一群爱好图数据库,共同推进图数据库发展的开发者构成的社区。

本文由 Nebula Graph 社区 Committer 伊兴路贡献,也欢迎阅读本文的你参与到 Nebula Graph 的开发,或向 Nebula Graph 投稿。


### 附录

> Nebula Graph:一个开源的分布式图数据库。

> GitHub:[https://github.com/vesoft-inc/nebula](https://0x7.me/go2github)

> 知乎:https://www.zhihu.com/org/nebulagraph/posts

> 微博:https://weibo.com/nebulagraph

程序运行总是卡机,击回车键后恢复运行

有问必答h12 回复了问题 • 2 人关注 • 1 个回复 • 245 次浏览 • 2019-08-23 21:09 • 来自相关话题

制作了两个关于docker的视频

回复

技术讨论jicg 发起了问题 • 2 人关注 • 0 个回复 • 462 次浏览 • 2019-01-25 10:04 • 来自相关话题

Cloud Native Days中国巡回Meetup——8.11成都站

线下活动华为云 发表了文章 • 0 个评论 • 412 次浏览 • 2018-07-30 20:50 • 来自相关话题

![](https://forum-img.huaweicloud.com/data/attachment/forum/201807/30/204736n7lfcvjmsu5j9epu.png)
![](https://forum-img.huaweicloud.com/data/attachment/forum/201807/30/204736n7lfcvjmsu5j9epu.png)

《和我一步步部署 kubernetes 集群》- 更新到 kubernetes v1.10.4 版本了!

文章分享opsnull 发表了文章 • 1 个评论 • 698 次浏览 • 2018-06-19 13:29 • 来自相关话题

![k8s-install](https://raw.githubusercontent.com/opsnull/follow-me-install-kubernetes-cluster/master/images/dashboard-home.png) ...查看全部
![k8s-install](https://raw.githubusercontent.com/opsnull/follow-me-install-kubernetes-cluster/master/images/dashboard-home.png)

《和我一步步部署 kubernetes 集群》自发布以来,受到 kubernetes 社区做多爱好者的关注,提了不少问题和建议(见 Github Issues),很开心能给大家带来帮助!

上一版是基于 kubernetes 1.6.2 版本,这一年来,kubernetes 经历了 4 次大版本升级,很多配置参数都发生了变化,组件功能也有所变化。为保持文档的指导性,紧跟社区变化,特将文档更新到最新稳定版本 v1.10.4。

相对上一版本,这一版本文档充分吸收了大家的反馈,在可读性、可操作性上做了改进:
1. 所有操作均在一台机器上执行,然后远程复制文件或执行命令。
2. 所有配置文件均用 bash here document 进行创建,方便 Copy & Paste;
3. master 节点和 worker 节点拆分为多个子章节;

紧跟社区趋势,面向下一版本的参数配置策略:
1. 关闭非安全端口、全部通过安全端口通信;
2. 动态创建 Bootstrpping auth token、Bootstrpping TLS Cert、自动 approve 证书、证书轮转等;
3. 在配置文件中(而不是命令行)配置 kublet、kube-proxy 参数,为后续动态修改组件配置做好准备;

enjoying,happy hacking!

github 地址:[follow-me-install-kubernetes-cluster](https://github.com/opsnull/follow-me-install-kubernetes-cluster)

电子书:
[pdf 格式](https://www.gitbook.com/download/pdf/book/opsnull/follow-me-install-kubernetes-cluster)
[epub 格式](https://www.gitbook.com/download/epub/book/opsnull/follow-me-install-kubernetes-cluster)


docker 容器的 ip 问题?

有问必答hej8875 回复了问题 • 4 人关注 • 4 个回复 • 1684 次浏览 • 2018-05-01 12:31 • 来自相关话题

整理了一些区块链、以太坊技术开发相关的文件,觉得有用的拿去

文章分享dilisk 回复了问题 • 16 人关注 • 6 个回复 • 9404 次浏览 • 2018-04-24 12:02 • 来自相关话题

Golang 在十二赞的深度应用

文章分享santower 发表了文章 • 2 个评论 • 2510 次浏览 • 2018-04-23 20:27 • 来自相关话题

我们是“十二赞”,一个致力于帮助电商卖家进入小程序的小团队,我们的主页是[http://www.12zan.cn/](http://www.12zan.cn/)。 在实际运行中,我们使用了大量由golang写就的小工具,几乎每一个工具代码量都超短,一般在200 ...查看全部


我们是“十二赞”,一个致力于帮助电商卖家进入小程序的小团队,我们的主页是[http://www.12zan.cn/](http://www.12zan.cn/)。 在实际运行中,我们使用了大量由golang写就的小工具,几乎每一个工具代码量都超短,一般在200行左右就完成了一个独立的功能,同时担当了相当重要的角色;像代理服务器,代码量一共500行多一点点,却是我们的核心支柱,压测时QPS也直追nginx,表现优异。

## 基于Docker的基础结构

做为基础架构,我介绍一下我们的机器架构。
我们的整个业务构建于阿里云之上,有5台server,每一对都有独立的外网IP,同时也在同一个内网之中。在每一台机器上都跑了一个我们自己用golang写的守护进程,这个进程负责监听一些业务重启、新增域名等类似的指令并执行(这些指令最后都传递给了docker)。同时,每台机器上都有一个consul进程,这些consul都join到了一起。另外,我们每一台机器上,都用docker跑了一个nginx来做80端口的服务,同时跑了一个用golang自己写的HTTP代理。nginx做做日志啊基础的功能之后就把请求丢给这个http代理 ,HTTP代理会到consul里去查应该将请求转发到哪个IP的哪个端口上。

## 400行Golang代码写的HTTP Proxy

在架构选型的第一天,我们就决定,我们会服务化,会大量使用http 接口来提供服务,并使用自己的http proxy来分发请求、添加自有的一些业务逻辑比如API的权限验证等逻辑。我们限定,所有业务,域名都是\*.app.12zan.net,比如我们要上一个聊天服务,请求的接口就会是chat.app.12zan.net,哪天再上个评论服务,请求的接口就是comment.app.12zan.net。
确定域名后我第一件事情,就是拿golang自己写了一个非常简单的基于consul的http proxy server;感谢Golang这完善的HTTP库,我们只用了几百行代码就完成了所有功能。每当有http请求过来时,这个proxy server就会根据HTTP请求中HTTP_HOST 字段去consul去查,有哪些后端是用这个域名名称来注册服务的,并根据指定的算法,取出一台后端来,把这个HTTP请求Proxy过去。每个具体的业务,可能运行在我们5台机器中的任何一台之中的docker上,也可能是多个docker实例上。所以这里有一个机制,选择哪个实际的docker实例来服务这个请求的问题。我们现在支持随机选取、按客户端IP地址做hash之后选取、按URL做Hash选取、按负载选取几种方式。

## WEB服务的服务注册

基于php+laralel和nodejs+koajs两种场景,我们制作了自己的docker镜像。这个镜像除开可以将php+nginx和nodejs构建的web服务运行起来之外,还包含一个golang写的consul客户端。在docker容器里,这个客户端随着php+nginx或是nodejs的web服务一起启动,启动之后会向宿主机的consul 进程注册自己这个服务,注册的时候会通知说,某某应用,在某某IP某某端口提供服务啦,如果前面有到**.app.12zan.net的请求你可以转发给我;同时会每隔一秒上报自己的进程数、当前机器CPU占用、内存占用情况。也是一样的简单,几百行golang代码,就鼓捣出了这个consul客户端。为什么使用golang呢?第一个原因当然是因为consul天生是golang阵营,第二个,是因为我们的docker容器种类较多,所以这个客户端直接就是在Mac上跨平台编译出来的在linux64平台上运行的,不管docker容器是python为基准的还是ruby为基准的,还是nodejs的,只要把这个二进制文件拷贝进去就能正确运行,不像别的语言需要解决依赖问题。

我们还开发了一个web console界面,在这里,我们可以注册app,也可以为app新增实例。注册app时,我们要指定代码仓库的地址(对了,我们的代码管理是用的golang写的gogs),指定对外服务的域名,指定是nodejs应用还是php+laravel应用。添加应用之后,可以在这个应用下新建实例,让系统在指定的IP上去跑这个实例。实例运行的过程实际就是下发一个通知到某个机器上,去执行一个docker实例启动的过程。docker启动的时候带了一些环境变量,比如当前内网IP、docker监听的端口、对外提供服务时是用何域名提供服务。

## 日志和存储

前面这种架构有一个问题,就是后端可能是在任何一台机器上运行的,今天可能是A,明天可能是B,那我要是把文件存在A上了是不是让B来提供服务的时候就挂掉了?所以我们想了这么一个办法(也是因为穷。。。。),我们把所有的文件都挪到阿里云的OSS服务上。同时为了不管是Nodejs应用还是php应用 还是python写的应用都能做到把用户上传的文件或是系统生成的文件存到oss上面,我们很省事地写了一个ossUploader,编译好的可执行文件发布,只需要执行它,传进来本地路径和oss上的目标路径,就保证给你上传到oss上去就完整,不需要再在nodejs、php、python、ruby、java各种平台下都琢磨一遍oss的SDK。

对日志的处理是一样的, 所有的日志文件的内容,都会被一个golang写的工具gtail监听着(就像linux 的tail -f命令一样),所有新产生的内容都会被gtail挪到oss上去存储。当然,也是可执行文件发布的。

这个实现之后, 我们的实际业务就真正可以在5台机器上之间任意腾挪了。

## 消息广播

得益于golang的一些开源仓库,我们还做了一些好玩的东西。
比如,看到[https://github.com/gorilla/websocket](https://github.com/gorilla/websocket)这个东东,我们忍不住撸了一个websocket server,或是说叫群聊服务器更好一点。
接下来,我们看到有一个golang的库叫go-mysql-elasticsearch,伪装了一个mysql的slave,去MySQL的master机器上去读binlog,读到binlog以后就将MySQL里的数据发送给ElasticSearch去索引数据。
我们就结合了一个,把这两个结合起来,修改了一下go-mysql-elastichsearch,让它监听到MySQL的数据变更之后,在WebSocket server的某个群聊里推送出来,形成一个数据变更的广播。
再接下来我们就可以用nodejs写一个应用,连上这个websocket server,加入特定的某个群聊,就源源不断地收听到数据变更的消息。这个nodejs端的代码就非常简洁了,只需要不到100行代码可以做各种好玩的事情,比如监听到用户留言表有新增,可以发邮件让运营马上去审核。还有比如说,每当订单表有成交的时候,我们某个小小的nodejs应用因为监听了数据库消息,第一时间就知道了,马上就去追溯用户来源,来计算返利;同时这个nodejs的代码更新是和订单主逻辑完全不相关的,写这个业务的开发人员只需要知道订单表的结构,不需要了解订单应用后台代码。




[杭州] imToken (区块链钱包) 招聘 Golang 及 Devops 工程师

招聘应聘kaichen 发表了文章 • 0 个评论 • 1892 次浏览 • 2018-04-14 07:55 • 来自相关话题

## 公司介绍 我们的产品 imToken 是一款区块链钱包,东南亚用户量最大的区块链钱包。超过百万用户信赖,长期保持高速增长。 ## Golang 后端工程师 工作地点,杭州 ...查看全部
## 公司介绍

我们的产品 imToken 是一款区块链钱包,东南亚用户量最大的区块链钱包。超过百万用户信赖,长期保持高速增长。

## Golang 后端工程师

工作地点,杭州

岗位职责

- 负责业务系统开发
- 使用常见区块链协议开发业务支持系统

岗位要求

- 五年以上后端开发经验
- 熟悉 Golang/Ruby 编程语言,使用超过三年以上
- 有一定系统架构能力
- 为人正直,思路清晰,自我驱动,自我管理,良好协作习惯,持续学习
- 全栈开发经验者加分
- 有高并发系统开发经验加分
- 有线上系统运维经验加分
- 熟悉区块链技术,有相关开发经验加分
- 参与大型开源项目经验加分

## DevOps 工程师

岗位职责

- 维护和搭建 Docker & Kubernetes 运维平台
- 运维产品系统,支撑业务系统
- 运维区块链服务节点池
- 开发集成完整的监控日志服务
- 推进团队基于容器的开发及集成测试环境优化

岗位要求

- 五年以上工作经验,三年以上线上 Docker 运维经验
- 熟悉 Docker 及相关技术,掌握 Docker 源码加分
- 掌握 Ansible/Chef/Puppet/Terraform 等其中一种编排工具
- 熟悉 Kubernetes/Mesos/Swarm 等其中一种编排系统
- 熟悉 Golang/Bash/Ruby/Python 等一门开发语言
- 熟悉 Linux 系统原理,了解虚拟化,快速定位线上系统瓶颈
- 有 Aliyun/AWS 等云平台服务运维经验
- 为人正直,思路清晰,自我驱动,自我管理,良好协作习惯,持续学习
- 有多年互联网项目开发经验优先
- 有大规模分布式线上系统运维经验加分
- 在 CI/CD 有一定实践经验加分
- 对区块链有一定认识加分
- 参与大型开源项目经验加分

## 关于 imToken

2017 对于 imToken 是激动人心的一年,

我们从 4 人创始,到现在 31 人团队,
我们从区块链探索,到做出行业的品牌,
我们从 0 到 1 打造了首个以太坊区块链的移动端数字钱包,
我们从 1 月到 12 月为全球 150 万用户提供了数字资产服务。
我们见证了以太币从 1 美金到 1000 美金,让我们懂得敬畏市场,拥抱变化, 也经历了 5, 6 月份 ICO 狂潮到 9 月嘎然退潮,让我们学会波澜不惊,泰然处之。 我们始终追逐初心,Make Blockchain Happen!

互联网不分国界,区块链更无偏见, 作为中国团队,我们很希望创造打破偏见的一流产品,树立一个国际影响力的品牌。 如果你对 imToken 有兴趣,对区块链行情有热情,欢迎加入我们,一起开启 2018 新篇章。

imToken,是新一代区块链数字钱包的代表,即将推出的 v2.0 将支持区块链身份、多链资产管理、去中心化交易和 DApp 浏览器。我们的愿景是:让财富不可侵犯,让价值自由流动,让数据主权回归。

公司成立于 2016 年 5 月,目前处于 A 轮阶段

联系方式: hr@consenlabs.com

Swirl:Docker Swarm 集群管理的新选择

开源程序noname 发表了文章 • 0 个评论 • 1172 次浏览 • 2018-03-14 12:08 • 来自相关话题

# SWIRL GitHub 地址:[https://github.com/cuigh/swirl](https://github.com/cuigh/swirl) [**Swirl**](https://g ...查看全部
# SWIRL

GitHub 地址:[https://github.com/cuigh/swirl](https://github.com/cuigh/swirl)

[**Swirl**](https://github.com/cuigh/swirl) 是一个 Docker 管理工具,专注于 Swarm 集群。

## 主要功能

* Swarm 各组件管理,包括服务、网络、任务等
* 镜像与容器管理
* Compose 管理与部署
* 服务状态监控(基于 [Prometheus](https://hub.docker.com/r/cuigh/prometheus/))
* 服务自动伸缩
* 支持 LDAP 认证
* 基于 RBAC 完整的权限控制模型
* 支持横向扩展部署
* 多语言支持
* 更多功能...

## Snapshots

### 首页

![Dashboard](https://github.com/cuigh/swirl/raw/master/docs/images/home.png)

### 服务列表

![Service list](https://github.com/cuigh/swirl/raw/master/docs/images/service-list.png)

### 服务监控

![Service stats](https://github.com/cuigh/swirl/raw/master/docs/images/service-stats.png)

### 服务编排管理

![Stack list](https://github.com/cuigh/swirl/raw/master/docs/images/stack-list.png)

### 系统设置

![Setting](https://github.com/cuigh/swirl/raw/master/docs/images/setting.png)

## 配置

### 使用配置文件

所有选项都可以通过 `config/app.yml` 配置文件来设置。

```yaml
name: swirl
banner: false

web:
address: ':8001'
authorize: '?'

swirl:
db_type: mongo
db_address: localhost:27017/swirl
# docker_endpoint: tcp://docker-proxy:2375

log:
loggers:
- level: info
writers: console
writers:
- name: console
type: console
layout: '[{L}]{T}: {M}{N}'
```

### 使用环境变量

3 个主要的设置支持通过环境变量来设置,便于以 Docker 方式部署。

| Name | Value |
| --------------- | ------------------------------------------------|
| DB_TYPE | mongo |
| DB_ADDRESS | localhost:27017/swirl |
| DOCKER_ENDPOINT | tcp://docker-proxy:2375 |
| AUTH_TIMEOUT | 4h |

### 使用 Swarm 的 Config 功能

Docker 从 v17.06 版本起,内置了配置管理模块,服务可以直接挂载存储在集群中的配置文件,因此你也可以通过这种方式来挂载 Swirl 的配置文件。

## 部署

### 独立部署

编译后把 swirl 执行文件和 config/assets/views 这 3 个目录复制到服务器任意目录中,直接运行即可。

```bash
./swirl
```

### Docker 方式

```bash
docker run -d -p 8001:8001 \
--mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock \
-e DB_TYPE=mongo \
-e DB_ADDRESS=localhost:27017/swirl \
--name=swirl \
cuigh/swirl
```

### Docker Swarm 方式

```bash
docker service create \
--name=swirl \
--publish=8001:8001/tcp \
--env DB_ADDRESS=localhost:27017/swirl \
--constraint=node.role==manager \
--mount=type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock \
cuigh/swirl
```

### Docker Compose 方式

Swirl 项目中已经包含了一个简单的 Compose 部署文件,你可以使用 Docker 的 stack 命令直接部署

```bash
docker stack deploy -c compose.yml swirl
```

## 高级特性

**Swirl** 使用服务标签来实现一些高级的管理功能,下表中的标签是目前已经支持的。

名称 | 描述 | 示例
--- | --- | ---
swirl.scale | 用于控制服务自动伸缩 | `swirl.scale=min=1,max=5,cpu=30:50`

## 从源码构建

**Swirl** 使用 `dep` 作为依赖管理工具,下载源代码后,你可以使用如下两行简单的命令来构建。

```sh
$ dep ensure
$ go build
```

## 许可证

This product is licensed to you under the MIT License. You may not use this product except in compliance with the License. See LICENSE and NOTICE for more information.

软件产品经理需要技术吗?

文章分享huixinyunit 发表了文章 • 0 个评论 • 1103 次浏览 • 2018-02-27 15:31 • 来自相关话题

让我们直接跳到标题,如果你是产品经理,或者希望开始从事产品经理工作,特别是在像软件这样的技术行业,具有技术背景或至少熟悉工程方面的东西,可能会在你的职业生涯中获得重大的好处。但是,作为技术领域的产品经理,你需要成为技术人才吗?答案是否定的。我将讨论作为产品经理 ...查看全部
让我们直接跳到标题,如果你是产品经理,或者希望开始从事产品经理工作,特别是在像软件这样的技术行业,具有技术背景或至少熟悉工程方面的东西,可能会在你的职业生涯中获得重大的好处。但是,作为技术领域的产品经理,你需要成为技术人才吗?答案是否定的。我将讨论作为产品经理的技术人员所带来的诸多好处,但是我首先要指出的是,如果你不小心的话,要面对任何一个技术产品经理的巨大的红旗。


技术[产品经理](http://www.huixinyun.com/UserRegister/Register?r=2017r2ltbjb1214 "产品经理")的一个巨大可能的缺陷


技术背景是产品经理职业的一条非常普遍的途径。许多新的PM直接来自技术学位 - 计算机科学,电子工程,机械科学与系统,软件应用等等。其他已经花时间在人才市场上的人员在进入产品经理岗位之后担任技术职位 - 例如设计工程师或软件编码人员。但是,尽管这种技术熟练程度对一个产品经理来说是可行的,但它也同样容易对你产生不利影响。技术熟练可以成为产品经理的一项资产,但如果你和工程团队陷入困境,它会很容易和你作对。


一个成功的产品经理最重要的特征是平衡往往相互冲突和竞争方面她的角色的能力。我们甚至写了一篇关于这个主题的文章,被称为是一个优秀的产品经理,在这里我们指出了保持平衡的必要性,例如,专注于产品开发的战术细节,并密切关注你的产品更大的战略目标。


所以,如果你认为自己是一个技术人员,你有兴趣在软件等技术领域成为产品经理,你需要问问自己,你是否想承担全面、复杂的产品管理角色,或者你是否真的被你的工程团队“深入”杂草的想法所吸引。


这种情况比你想象的要频繁。一个技术产品经理,甚至可能是因为他印象深刻的技术背景而被公司雇佣,或者是因为他在面试中给工程团队留下了深刻的印象,所以过于专注于产品的技术面,因为那是他感觉最舒服的地方。结果,他失去了关注他的许多其他的责任,这是至少提供一个成功的产品责任,如了解他的用户角色重要,沟通他的产品战略计划的一个团队执行的利益相关者和保持这些利益相关者知情和热情在整个过程中,收集和分析用户数据和行业研究,等等。


作为产品经理,您需要随时保持这种复杂的平衡。你需要花时间和精力集中于战术问题,战略问题,人才和资源的问题,研究和数据分析,与不同的团队和利益相关者,是的,钻研有时到你的产品的技术细节。


但你需要保持技术含量,不要让你对有趣的开发或工程问题感兴趣,以免分散你作为产品经理的其他关键任务的责任。


成为技术产品经理的5个好处


好吧,刚刚只是简单的一个警示,就是不让你的技术部门排挤你PM角色,下面简单说明一下作为一个技术产品经理能让你和你的公司受益匪浅的一些原因。


1。它使你通常更好地了解你的产品和如何管理他们。


有一家网络开发公司,招聘新员工有一个有趣的政策。不管公司雇佣你的角色是什么,你必须在你的头三个星期里全职工作在客户支持上。


他们为什么这么做?该公司指出,这是让新员工熟悉他们产品的最佳方式,也是最快的方式。这是有道理的:回答许多现实世界中的问题或抱怨似乎比阅读市场营销文学更能加深对公司产品的了解。


但更重要的一点是,这家公司正在帮助他们的新员工更加全面,更加熟悉公司及其对市场的影响,而不是让他们的新员工立即跳入部门的孤岛。 (这就提出了这个政策的一个相关部分:每年你将不得不花费一周的时间在客户支持上)。这个经验无疑会帮助这些员工在公司工作,不管他们是什么 - 会计,人力资源,软件设计,销售。


了解贵公司产品的底线也是如此。它给了你一个更全面的观点,可以帮助你更好地识别潜在的问题或见解,否则如果你只是对你的产品肤浅的认知。


2.有助于您更好地了解开发时间,资源需求,如何确定路线图的优先顺序,以及如何设定利益相关方的期望。


作为产品经理拥有一些技术知识的另一个好处是能够根据您在产品路线图中制定的战略计划,更准确地了解构建产品所需的内容。


如果您只是从市场营销或用户的角度理解您的产品的工作原理,那么很多产品的开发对于您来说始终是一个谜 - 一个黑匣子 - 您将不得不依靠您的工程师或编码人员来为您每个组件将需要建立的时间,需要多少人,您需要确保哪些预算等等。


这意味着,在实际制定产品路线图之前,您经常需要咨询贵公司的技术团队,或者找出在给定的时间范围内,您可以期望交付多少,哪些史诗或功能。这是一个巨大的缺点。但作为一名技术产品经理,实际上可以深入了解并且知道创建后端细节真正涉及到什么的PM,您可以更接近现实的路线图和计划,而不需要推迟到技术部门。


3.它可以帮助您更好地识别您的工程师或开发人员在您提供的计划或资源估计中是错误的(甚至夸大了一点点)。


比方说,你是一个非技术性的产品经理,负责像软件这样的技术产品,而你的开发团队告诉你,你所要求的主题或新功能需要三个月的时间进行编码,或者需要四个专门的开发资源(比现在可以访问的要多),或者因为他们正在做的其他事情,他们甚至不能开始数周。让我们进一步说,你不满意他们给你的这些答案。


你能做什么?


如果你不是技术性的,你不知道他们的评估是否正确。你也不会知道,如果他们告诉你一个有点高的故事,因为他们想给自己更多的项目之间的缓冲,或者他们只是与你谈判更多的时间。


但是,如果你是技术性的,如果你甚至对编码所需的功能有什么一般的认识 - 需要多长时间,需要多少人,需要什么技能水平等等,你将能够嗅探到解决这些问题,并向开发人员展示可能有办法完成。如果他们知道你是技术人员,他们会更有可能听到你的声音。


4。这会使你获得技术团队中最为重要的尊重。


作为一个产品经理,你的许多职责将包括与各个团队谈判为你的产品做事情。当涉及到开发时,您必须提倡您的技术团队尽可能快地为您的产品做更多的工作。


如果你没有技术如果你不知道的细节背后的产品是如何实际建造,升级,如果任何依赖是什么,等等,那么你的开发团队,更不可能尊重你、重视你的人。


他们可能会认为他们比你更了解如何建立你的产品。也许他们是对的。


5.它可以帮助您更好地了解您的市场,您的行业以及始终即将到来的新技术和新威胁。


如果您是一位软件产品经理,并且您一再听到您市场上有关NoSQL数据库的谣言,那么您需要知道这项新技术是否会影响您的产品。也许它与你的产品组合没有任何关系;也许它代表了一个强大的新的数据库方法,你和你的团队现在应该研究,因为它可能成为一个潜在的竞争优势。


在这里有一些技术知识将是有价值的。您将能够更好地了解您的行业技术发展机会以及威胁,这可能会影响您的产品和公司的底线。


正如我们在本网站上的许多博客文章(包括关于产品经理的主要职责的这篇文章)中指出的那样,作为PM,您将成为整个组织中人员的中心产品信息中心。这意味着您需要随时了解贵公司的任何人的竞争情况,用户反馈,市场趋势以及可能影响您产品的其他因素。其中一个持续的因素是您的行业如何应对和采用新技术 - 移动平台,加密协议,数据分析工具等。


技术上越精明,阅读行业科技杂志和博客越舒服,对这些新的技术机会和陷阱做出适当的反应就越有准备。


假如你不是技术人员的产品经理呢?


作为一个产品经理来说,我有一些技术真诚的案例,我可以想象你可能会想:这听起来不错。但是我没有在大学获得技术学位,我也没有任何技术领域的工作经验。作为软件产品经理,我不会处于一个巨大的劣势吗?


不一定。


那是因为今天比以前更容易学习技术技能,而不是通过正式的教育计划或在工作中获得技术经验。
今天,您可以在业余时间在线完成全部课程,学习机械工程,软件开发,甚至是产品创建。这意味着如果你发现你自己是一个负责技术产品的产品经理,而且在技术会议上感觉不到你的深度,那么你可以向自己学习大量的信息,有更多的知识和信心。


此外,在最近的一篇文章中,产品经理的职业发展路径:3个神话被揭穿,我们介绍了我们与高技术公司产品经理的几次采访,他们来自完全不同的教育和专业背景,其中一些不是技术。


底线:没有必要有技术背景成为技术领域的成功产品经理。但是,如果你能够在你的PM角色中展现出一些技术实力,那么这通常会有帮助。而好消息是,即使你没有先前的技术经验或培训,你也可以比以往更容易地自己挑选。


如果你是一位处理技术产品的PM,你认为有必要成为技术人员吗?在评论部分分享您的想法。

本文转载至汇新云平台——全球专业的IT协同产业链平台:[阅读原文](http://www.huixinyun.com/article/9a420b65ba8042a296ba746d6083abd6.html "阅读原文")

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

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

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

[![humpback架构](https://humpback.github.io/humpback/_media/humpback-arch.png)](https://humpback.github.io/humpback/_media/humpback-arch.png)

###Humpback 功能特点

-Web操作,简单易用
-权限分组隔离
-容器升级与克隆
-容器监控
-容器日志
-集群容器调度
-集群弹性伸缩
-私有仓库

###Humpback 模式介绍
-**Single Mode** 单一模式,对单组主机实现容器管理,提供容器创建,容器操作,容器重命名,容器升级与克隆,容器监控,容器日志输出等功能。
-**Cluster Mode** 容器集群模式,实现按实例数批量创建容器,容器调度,批量操作容器,升级和迁移等功能。

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

[![系统登录](https://humpback.github.io/humpback/_media/humpback-web.png "系统登录")](https://humpback.github.io/humpback/_media/humpback-web.png "系统登录")

**Single Mode**
[![SingleMode](https://humpback.github.io/humpback/_media/single-mode-ui.png "SingleMode")](https://humpback.github.io/humpback/_media/single-mode-ui.png "SingleMode")

**Cluster Mode**
[![ClusterMode](https://humpback.github.io/humpback/_media/cluster-mode-ui.png "ClusterMode")](https://humpback.github.io/humpback/_media/cluster-mode-ui.png "ClusterMode")

**Container Monitor**
[![ContainerMonitor](https://humpback.github.io/humpback/_media/container-monitor.png "ContainerMonitor")](https://humpback.github.io/humpback/_media/container-monitor.png "ContainerMonitor")

**Container Logs**
[![Container Logs](https://humpback.github.io/humpback/_media/container-logs.png "Container Logs")](https://humpback.github.io/humpback/_media/container-logs.png "Container Logs")

**Container Detail**
[![ContainerDetail](https://humpback.github.io/humpback/_media/container-single-info.png "ContainerDetail")](https://humpback.github.io/humpback/_media/container-single-info.png "ContainerDetail")

[![ContainerDetail](https://humpback.github.io/humpback/_media/container-cluster-info.png "ContainerDetail")](https://humpback.github.io/humpback/_media/container-cluster-info.png "ContainerDetail")

项目地址:https://humpback.github.io/humpback
授权协议:Apache
开发语言:TypeScript、Golang
操作系统:垮平台

GOLANG探测HTTP连接断开

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

考虑基于HTTP的RPC,或者HTTP服务器主动通知客户端的机制,就是[HTTP Long-Polling](https://www.pubnub.com/blog/2014-12-01-http-long-polling/),意思就是客户端发起一个长连接,服 ...查看全部
考虑基于HTTP的RPC,或者HTTP服务器主动通知客户端的机制,就是[HTTP Long-Polling](https://www.pubnub.com/blog/2014-12-01-http-long-polling/),意思就是客户端发起一个长连接,服务器阻塞忍住不响应直到:

1. 超时,比如5秒后,我们给客户端响应一个keepalive,意思是现在还没有啥事,请继续polling。
1. 拿到结果,这个可能是任何时候,比如300毫秒、1100毫秒、2300毫秒拿到一个事件,响应给客户端,实现了有事件异步通知。

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

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

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

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

可能有以下方式:

1. 读取`r.Body`,如果发现断开应该会有错误。
1. 有朋友用reflect或hijack取到底层的TCPConn,然后Peek。
1. 将`w`转换成`http.CloseNotifier`,在TCP连接关闭时拿到事件。

## r.Body Read

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

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

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

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

## Peek TcpConn

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

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

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

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

Reflect方式始终不是最好的。

另外,还有一种方式,就是用http hijack方式,这种方式虽然是http库提供的接口,但是很多地方注释都说hijack需要特殊处理,因此也不是最好的方式。参考[When to use hijack](https://stackoverflow.com/questions/27075478/when-to-use-hijack-in-golang)。

## Close Notifier

在GO1.1提供了`http.CloseNotifier`接口,参考[Close Notifier](https://golang.org/pkg/net/http/#CloseNotifier),但是也注意会有一些问题,参考[net/http: CloseNotifier fails to fire when underlying connection is gone](https://github.com/golang/go/issues/13165)。用法如下:

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

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

```
func polling(ctx context.Context, incoming chan []byte) {
http.HandleFunc("/polling", func(w http.ResponseWriter, r *http.Request) {
select {
case <- ctx.Done():
fmt.Println("system quit")
case b := <- incoming:
w.Write(b)
case <-time.After(5 * time.Second):
w.Write("keepalive")
case <- w.(http.CloseNotifier).CloseNotify():
fmt.Println("connection closed")
}
})
}
```
条新动态, 点击查看
再补充几个: 9. Node.js区块链开发 :https://pan.baidu.com/s/1Ldpn0DvJ5LgLqwix6eWgyg 10. geth使用指南文档中文版 :https://pan.baidu.com/s/1M0W... 显示全部 »
再补充几个: 9. Node.js区块链开发 :https://pan.baidu.com/s/1Ldpn0DvJ5LgLqwix6eWgyg 10. geth使用指南文档中文版 :https://pan.baidu.com/s/1M0WxhmumF_fRqzt_cegnag 11. 以太坊DApp开发环境搭建-Ubuntu : https://pan.baidu.com/s/10qL4q-uKooMehv9X2R1qSA 12. 以太坊DApp开发环境搭建-windows :https://pan.baidu.com/s/1cyYkhIJIFuI2oyxM9Ut0eA

bench fasthttp 和 库自带的net.http性能,fasthttp完败?

回复

技术讨论tsingson 回复了问题 • 3 人关注 • 4 个回复 • 57 次浏览 • 2 小时前 • 来自相关话题

写了一个100%Go语言的Web-Term-SSH 堡垒机项目欢迎大家拍砖

回复

文章分享jjjjerk 发起了问题 • 1 人关注 • 0 个回复 • 288 次浏览 • 2019-10-22 15:53 • 来自相关话题

程序运行总是卡机,击回车键后恢复运行

回复

有问必答h12 回复了问题 • 2 人关注 • 1 个回复 • 245 次浏览 • 2019-08-23 21:09 • 来自相关话题

整理了一些区块链、以太坊技术开发相关的文件,觉得有用的拿去

回复

文章分享dilisk 回复了问题 • 16 人关注 • 6 个回复 • 9404 次浏览 • 2018-04-24 12:02 • 来自相关话题

软件产品经理需要技术吗?

文章分享huixinyunit 发表了文章 • 0 个评论 • 1103 次浏览 • 2018-02-27 15:31 • 来自相关话题

让我们直接跳到标题,如果你是产品经理,或者希望开始从事产品经理工作,特别是在像软件这样的技术行业,具有技术背景或至少熟悉工程方面的东西,可能会在你的职业生涯中获得重大的好处。但是,作为技术领域的产品经理,你需要成为技术人才吗?答案是否定的。我将讨论作为产品经理 ...查看全部
让我们直接跳到标题,如果你是产品经理,或者希望开始从事产品经理工作,特别是在像软件这样的技术行业,具有技术背景或至少熟悉工程方面的东西,可能会在你的职业生涯中获得重大的好处。但是,作为技术领域的产品经理,你需要成为技术人才吗?答案是否定的。我将讨论作为产品经理的技术人员所带来的诸多好处,但是我首先要指出的是,如果你不小心的话,要面对任何一个技术产品经理的巨大的红旗。


技术[产品经理](http://www.huixinyun.com/UserRegister/Register?r=2017r2ltbjb1214 "产品经理")的一个巨大可能的缺陷


技术背景是产品经理职业的一条非常普遍的途径。许多新的PM直接来自技术学位 - 计算机科学,电子工程,机械科学与系统,软件应用等等。其他已经花时间在人才市场上的人员在进入产品经理岗位之后担任技术职位 - 例如设计工程师或软件编码人员。但是,尽管这种技术熟练程度对一个产品经理来说是可行的,但它也同样容易对你产生不利影响。技术熟练可以成为产品经理的一项资产,但如果你和工程团队陷入困境,它会很容易和你作对。


一个成功的产品经理最重要的特征是平衡往往相互冲突和竞争方面她的角色的能力。我们甚至写了一篇关于这个主题的文章,被称为是一个优秀的产品经理,在这里我们指出了保持平衡的必要性,例如,专注于产品开发的战术细节,并密切关注你的产品更大的战略目标。


所以,如果你认为自己是一个技术人员,你有兴趣在软件等技术领域成为产品经理,你需要问问自己,你是否想承担全面、复杂的产品管理角色,或者你是否真的被你的工程团队“深入”杂草的想法所吸引。


这种情况比你想象的要频繁。一个技术产品经理,甚至可能是因为他印象深刻的技术背景而被公司雇佣,或者是因为他在面试中给工程团队留下了深刻的印象,所以过于专注于产品的技术面,因为那是他感觉最舒服的地方。结果,他失去了关注他的许多其他的责任,这是至少提供一个成功的产品责任,如了解他的用户角色重要,沟通他的产品战略计划的一个团队执行的利益相关者和保持这些利益相关者知情和热情在整个过程中,收集和分析用户数据和行业研究,等等。


作为产品经理,您需要随时保持这种复杂的平衡。你需要花时间和精力集中于战术问题,战略问题,人才和资源的问题,研究和数据分析,与不同的团队和利益相关者,是的,钻研有时到你的产品的技术细节。


但你需要保持技术含量,不要让你对有趣的开发或工程问题感兴趣,以免分散你作为产品经理的其他关键任务的责任。


成为技术产品经理的5个好处


好吧,刚刚只是简单的一个警示,就是不让你的技术部门排挤你PM角色,下面简单说明一下作为一个技术产品经理能让你和你的公司受益匪浅的一些原因。


1。它使你通常更好地了解你的产品和如何管理他们。


有一家网络开发公司,招聘新员工有一个有趣的政策。不管公司雇佣你的角色是什么,你必须在你的头三个星期里全职工作在客户支持上。


他们为什么这么做?该公司指出,这是让新员工熟悉他们产品的最佳方式,也是最快的方式。这是有道理的:回答许多现实世界中的问题或抱怨似乎比阅读市场营销文学更能加深对公司产品的了解。


但更重要的一点是,这家公司正在帮助他们的新员工更加全面,更加熟悉公司及其对市场的影响,而不是让他们的新员工立即跳入部门的孤岛。 (这就提出了这个政策的一个相关部分:每年你将不得不花费一周的时间在客户支持上)。这个经验无疑会帮助这些员工在公司工作,不管他们是什么 - 会计,人力资源,软件设计,销售。


了解贵公司产品的底线也是如此。它给了你一个更全面的观点,可以帮助你更好地识别潜在的问题或见解,否则如果你只是对你的产品肤浅的认知。


2.有助于您更好地了解开发时间,资源需求,如何确定路线图的优先顺序,以及如何设定利益相关方的期望。


作为产品经理拥有一些技术知识的另一个好处是能够根据您在产品路线图中制定的战略计划,更准确地了解构建产品所需的内容。


如果您只是从市场营销或用户的角度理解您的产品的工作原理,那么很多产品的开发对于您来说始终是一个谜 - 一个黑匣子 - 您将不得不依靠您的工程师或编码人员来为您每个组件将需要建立的时间,需要多少人,您需要确保哪些预算等等。


这意味着,在实际制定产品路线图之前,您经常需要咨询贵公司的技术团队,或者找出在给定的时间范围内,您可以期望交付多少,哪些史诗或功能。这是一个巨大的缺点。但作为一名技术产品经理,实际上可以深入了解并且知道创建后端细节真正涉及到什么的PM,您可以更接近现实的路线图和计划,而不需要推迟到技术部门。


3.它可以帮助您更好地识别您的工程师或开发人员在您提供的计划或资源估计中是错误的(甚至夸大了一点点)。


比方说,你是一个非技术性的产品经理,负责像软件这样的技术产品,而你的开发团队告诉你,你所要求的主题或新功能需要三个月的时间进行编码,或者需要四个专门的开发资源(比现在可以访问的要多),或者因为他们正在做的其他事情,他们甚至不能开始数周。让我们进一步说,你不满意他们给你的这些答案。


你能做什么?


如果你不是技术性的,你不知道他们的评估是否正确。你也不会知道,如果他们告诉你一个有点高的故事,因为他们想给自己更多的项目之间的缓冲,或者他们只是与你谈判更多的时间。


但是,如果你是技术性的,如果你甚至对编码所需的功能有什么一般的认识 - 需要多长时间,需要多少人,需要什么技能水平等等,你将能够嗅探到解决这些问题,并向开发人员展示可能有办法完成。如果他们知道你是技术人员,他们会更有可能听到你的声音。


4。这会使你获得技术团队中最为重要的尊重。


作为一个产品经理,你的许多职责将包括与各个团队谈判为你的产品做事情。当涉及到开发时,您必须提倡您的技术团队尽可能快地为您的产品做更多的工作。


如果你没有技术如果你不知道的细节背后的产品是如何实际建造,升级,如果任何依赖是什么,等等,那么你的开发团队,更不可能尊重你、重视你的人。


他们可能会认为他们比你更了解如何建立你的产品。也许他们是对的。


5.它可以帮助您更好地了解您的市场,您的行业以及始终即将到来的新技术和新威胁。


如果您是一位软件产品经理,并且您一再听到您市场上有关NoSQL数据库的谣言,那么您需要知道这项新技术是否会影响您的产品。也许它与你的产品组合没有任何关系;也许它代表了一个强大的新的数据库方法,你和你的团队现在应该研究,因为它可能成为一个潜在的竞争优势。


在这里有一些技术知识将是有价值的。您将能够更好地了解您的行业技术发展机会以及威胁,这可能会影响您的产品和公司的底线。


正如我们在本网站上的许多博客文章(包括关于产品经理的主要职责的这篇文章)中指出的那样,作为PM,您将成为整个组织中人员的中心产品信息中心。这意味着您需要随时了解贵公司的任何人的竞争情况,用户反馈,市场趋势以及可能影响您产品的其他因素。其中一个持续的因素是您的行业如何应对和采用新技术 - 移动平台,加密协议,数据分析工具等。


技术上越精明,阅读行业科技杂志和博客越舒服,对这些新的技术机会和陷阱做出适当的反应就越有准备。


假如你不是技术人员的产品经理呢?


作为一个产品经理来说,我有一些技术真诚的案例,我可以想象你可能会想:这听起来不错。但是我没有在大学获得技术学位,我也没有任何技术领域的工作经验。作为软件产品经理,我不会处于一个巨大的劣势吗?


不一定。


那是因为今天比以前更容易学习技术技能,而不是通过正式的教育计划或在工作中获得技术经验。
今天,您可以在业余时间在线完成全部课程,学习机械工程,软件开发,甚至是产品创建。这意味着如果你发现你自己是一个负责技术产品的产品经理,而且在技术会议上感觉不到你的深度,那么你可以向自己学习大量的信息,有更多的知识和信心。


此外,在最近的一篇文章中,产品经理的职业发展路径:3个神话被揭穿,我们介绍了我们与高技术公司产品经理的几次采访,他们来自完全不同的教育和专业背景,其中一些不是技术。


底线:没有必要有技术背景成为技术领域的成功产品经理。但是,如果你能够在你的PM角色中展现出一些技术实力,那么这通常会有帮助。而好消息是,即使你没有先前的技术经验或培训,你也可以比以往更容易地自己挑选。


如果你是一位处理技术产品的PM,你认为有必要成为技术人员吗?在评论部分分享您的想法。

本文转载至汇新云平台——全球专业的IT协同产业链平台:[阅读原文](http://www.huixinyun.com/article/9a420b65ba8042a296ba746d6083abd6.html "阅读原文")

GOLANG探测HTTP连接断开

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

考虑基于HTTP的RPC,或者HTTP服务器主动通知客户端的机制,就是[HTTP Long-Polling](https://www.pubnub.com/blog/2014-12-01-http-long-polling/),意思就是客户端发起一个长连接,服 ...查看全部
考虑基于HTTP的RPC,或者HTTP服务器主动通知客户端的机制,就是[HTTP Long-Polling](https://www.pubnub.com/blog/2014-12-01-http-long-polling/),意思就是客户端发起一个长连接,服务器阻塞忍住不响应直到:

1. 超时,比如5秒后,我们给客户端响应一个keepalive,意思是现在还没有啥事,请继续polling。
1. 拿到结果,这个可能是任何时候,比如300毫秒、1100毫秒、2300毫秒拿到一个事件,响应给客户端,实现了有事件异步通知。

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

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

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

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

可能有以下方式:

1. 读取`r.Body`,如果发现断开应该会有错误。
1. 有朋友用reflect或hijack取到底层的TCPConn,然后Peek。
1. 将`w`转换成`http.CloseNotifier`,在TCP连接关闭时拿到事件。

## r.Body Read

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

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

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

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

## Peek TcpConn

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

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

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

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

Reflect方式始终不是最好的。

另外,还有一种方式,就是用http hijack方式,这种方式虽然是http库提供的接口,但是很多地方注释都说hijack需要特殊处理,因此也不是最好的方式。参考[When to use hijack](https://stackoverflow.com/questions/27075478/when-to-use-hijack-in-golang)。

## Close Notifier

在GO1.1提供了`http.CloseNotifier`接口,参考[Close Notifier](https://golang.org/pkg/net/http/#CloseNotifier),但是也注意会有一些问题,参考[net/http: CloseNotifier fails to fire when underlying connection is gone](https://github.com/golang/go/issues/13165)。用法如下:

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

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

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

GOLANG实现的HTTP转HTTPS的代理

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

有时候需要将后端的HTTP服务,转成HTTPS,可以用一个代理。 > Reamark: 如果是GOLANG的后端服务,可以直接用库`go-oryx-lib/https`。 这个代理支持自签名的证书,也支持[l ...查看全部
有时候需要将后端的HTTP服务,转成HTTPS,可以用一个代理。

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

这个代理支持自签名的证书,也支持[letsencrypt](https://letsencrypt.org)的证书。

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

我们有个HTTP API, [SRS Version](http://ossrs.net:1985/api/v1/versions):

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

下面演示实现HTTPS的代理。

## Self-sign Certificate

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

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

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

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

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

> Remark: GOLANG的设置请参考[GO环境配置](http://blog.csdn.net/win_lin/article/details/48265493)。

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

启动服务,代理到[SRS Version](http://ossrs.net:1985/api/v1/versions):

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

访问本机HTTP和HTTPS就可以:

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

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

## LetsEncrypt Certificate

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

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

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

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

## Advance Proxy

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

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

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

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

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

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

GOLANG中time.After释放的问题

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

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

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

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

```
package main

import "time"

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

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

```
package main

import "time"

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

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

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

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

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

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

```
package main

import "time"

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

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

```
package main

import "time"

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

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

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

1. GC肯定会回收`time.After`的,就在d之后就回收。一般情况下让系统自己回收就好了。
1. 如果有效率问题,应该使用`Timer`在不需要时主动Stop。大部分时候都不用考虑这个问题的。

交作业。

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

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

GO1.7之后,新增了`context.Context`这个package,实现goroutine的管理。 Context基本的用法参考[GOLANG使用Context管理关联goroutine](https://gocn.io/ar ...查看全部
GO1.7之后,新增了`context.Context`这个package,实现goroutine的管理。

Context基本的用法参考[GOLANG使用Context管理关联goroutine](https://gocn.io/article/333)。

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

```
type Packet interface {
encoding.BinaryMarshaler
encoding.BinaryUnmarshaler
}

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

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

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

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

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

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

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

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

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

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

另外,Context还可以传递上下文的Key-Value对象,比如我们希望日志中,相关的goroutine都打印一个简化的CID,那么就可以用`context.WithValue`,参考[go-oryx-lib/logger](https://github.com/ossrs/go-oryx-lib/blob/6c9beeb9c5d1c453769c069c3f744f014191e066/logger/example_go17_test.go#L38)。

GOLANG如何避免字符串转义

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

避免转义字符,例如造个json: ``` json.Unmarshal(`{"code":0, "data":{"server":"127.0.0.1:8080"}}`) ``` 是不 ...查看全部
避免转义字符,例如造个json:

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

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

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

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

考虑测试一个函数: ``` func request(ctx context.Context, hc *http.Client, api string) (err error) { var hreq *htt ...查看全部
考虑测试一个函数:

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

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

// ......
return nil
}
```

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

```
type mockHttpClient struct {
http.Client
}
```

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

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

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

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

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

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

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

GOLANG测试必须用带堆栈的errors

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

GOLANG测试时,可以用匿名对象填充测试序列,但是如果没有带堆栈的errors,那么会造成出现错误时无法排查。 GOLANG初始化匿名结构的方式,可以很方便的建立测试集合。 先看测试序列的填充,参考[tri ...查看全部
GOLANG测试时,可以用匿名对象填充测试序列,但是如果没有带堆栈的errors,那么会造成出现错误时无法排查。

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

先看测试序列的填充,参考[tricks-2015](https://talks.golang.org/2015/tricks.slide#12)这个文章,怕你翻不了墙,我把内容粘贴过来就是:

Anonymous structs: test cases (1/2)

These properties enable a nice way to express test cases:

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

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

```
type MyHeader struct {
Version uint8
Size uint16
}

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

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

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

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

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

怎么办呢?把堆栈信息带上,参考[错误最佳实践](https://gocn.io/article/348),改成这样:

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

创建error时用这个package:
```
if p[1] != 0x01 {
return oe.Errorf("p[1] is %v", p[1]) // line 194
}
if p[1] != 0x02 {
return oe.Errorf("p[1] is %v", p[1]) // line 200
}
```

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

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

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

GOLANG最容易做测试MOCK

技术讨论winlin 发表了文章 • 0 个评论 • 2963 次浏览 • 2017-06-09 18:26 • 来自相关话题

测试时,一些底层的库非常难以MOCK,比如HASH摘要算法,怎么MOCK?假设有个函数,是用MD5做摘要: ``` func digest(data []byte, h hash.Hash) ([]byte, error) ...查看全部
测试时,一些底层的库非常难以MOCK,比如HASH摘要算法,怎么MOCK?假设有个函数,是用MD5做摘要:

```
func digest(data []byte, h hash.Hash) ([]byte, error) {
if _, err = h.Write(data); err != nil {
return nil, errors.Wrap(err, "hash write")
}

d := h.Sum(nil)
if len(d) != 16 {
return nil, errors.Errorf("digest's %v bytes", len(d))
}
return d,nil
}
```

难以覆盖的因素有几个:

1. 私有函数,一般其他语言在utest中只能访问public函数,而golang的utest是和目标在同一个package,所有函数和数据都可以访问。
1. 有些函数非常难以出错,但是不代表不出错,比如这里的`Write`方法,一般都是不会有问题的,但是测试如果覆盖不到,保不齐哪天跑到这一行就挂掉了。
1. MOCK桩对象或者函数,如果总是要把目标全部实现一遍,比如hash这个接口有5个方法,对`Write`打桩时只需要覆盖这个函数,其他的可以不动。是的,聪明的你可能会想到继承,但是如果这个类是隐藏的呢?比如一个md5的实现是隐藏不能访问的,暴露的只有hash的接口,怎么从md5这个类继承呢?GOLANG提供了类似从实现了接口对象的接口继承的方式,实际上是组合,具体看下面的实现。
1. 有些古怪的逻辑,比如这里判断摘要是16字节,一般情况下也不会出现错误,当然utest也必须得覆盖到,万一哪天用了一个hash算法跑到这个地方,不能出现问题。

> Remark: 注意到这个地方用了一个`errors`的package,它可以打印出问题出现的堆栈,参考[Error最佳实践](https://gocn.io/article/348).

用GOLANG就可以完美解决上面所有的覆盖问题,先上代码:

```
type mockMD5Write struct {
hash.Hash
}
func (v *mockMD5Write) Write(p []byte) (n int, err error) {
return 0,fmt.Errorf("mock md5")
}
```

就这么简单?对的,但是不要小看这几行代码,深藏功与名~

## 组合接口

结构体`mockMD5Write`里面嵌套的不是实现md5哈希的类,而是直接嵌套的`hash.Hash`接口。这个有什么厉害的呢?假设用C++,看应该怎么搞:

```
class Hash {
public: virtual int Write(const char* data, int size) = 0;
public: virtual int Sum(const char* data, int size, char digest[16]) = 0;
public: virtual int Size() = 0;
};

class MD5 : public Hash {
// 省略了实现的代码
}

class mockMD5Write : public Hash {
private: Hash* imp;
public: mockMD5Write(Hash* v) {
imp = v;
}
public: int Write(const char* data, int size) {
return 100; // 总是返回个错误。
}
};
```

是么?错了,`mockMD5Write`编译时会报错,会提示没有实现其他的接口。应该这么写:

```
class mockMD5Write : public Hash {
private: Hash* imp;
public: mockMD5Write(Hash* v) {
imp = v;
}
public: int Write(const char* data, int size) {
return 100; // 总是返回个错误。
}
public: int Sum(const char* data, int size, char digest[16]) {
return imp->Sum(data, size, digest);
}
public: int Size() {
return imp->Size();
}
};
```

对比下够浪的接口组合,因为组合了一个`hash.Hash`的接口,所以它也就默认实现了,不用再把函数代理一遍了:

```
type mockMD5Write struct {
hash.Hash
}
func (v *mockMD5Write) Write(p []byte) (n int, err error) {
return 0,fmt.Errorf("mock md5")
}
```

这个可不是少写了几行代码的区别,这是本质的区别,我鸡冻的辩解道~如果这个接口有十个函数,我们要测试100个接口呢?这个MOCK该怎么写?另外,这个实际上是OO和GOLANG的细微差异,GOLANG的接口是契约,只要满足就可以,面向的全是动作,GOLANG像很多函数组合,它没有类体系的概念,也就是它的结构体不用明显符合哪个接口和哪个接口它才是合法的,实际上它可以符合任何适配的接口,也就是`Die()`这个动作,是自动被所有会`Die`的对象适配了的,不用显式声明自己会`Die`,关注的不是声明和实现了接口的关系,而是关注动作或者说接口本身,`!@#$%^&*()$%^&*(#$%^&*#$^&`不能说了,说多了都懂了我还怎么装逼去~

## 复杂错误

我们用了errors这个包,用来返回复杂错误,可以看到堆栈信息,对于utest也是一样,能看到堆栈对于解决问题也很重要。可以参考[Error最佳实践](https://gocn.io/article/348)。比如打印信息:

```
--- FAIL: TestDigest (0.00s)
digest_test.go:45: digest, mock md5
hash write data
_/Users/winlin/git/test/utility.digest
/Users/winlin/git/test/utility.go:46
_/Users/winlin/git/test/TestDigest
/Users/winlin/git/test/digest_test.go:42
testing.tRunner
/usr/local/Cellar/go/1.8.1/libexec/src/testing/testing.go:657
runtime.goexit
/usr/local/Cellar/go/1.8.1/libexec/src/runtime/asm_amd64.s:2197
```

测试代码:

```
func TestDigest(t *testing.T) {
if _, err := digest(nil, &mockMD5Write{md5.New()}); err == nil {
t.Error("should failed")
} else {
t.Errorf("digest, %+v", err)
}
}
```

当然这个地方是主动把error打印出来,因为用例就是应该要返回错误的,一般情况是:

```
func TestXXX(t *testing.T) {
if err := pfn(); err != nil {
t.Errorf("failed, %+v", err)
}
}
```

这样就可以知道堆栈了。

GOLANG空指针崩溃时堆栈消失和解决方案

技术讨论winlin 发表了文章 • 6 个评论 • 3064 次浏览 • 2017-06-07 17:04 • 来自相关话题

在[错误处理](https://gocn.io/article/348)这个文章中,[tkk](https://gocn.io/people/tkk)提出了空指针时堆栈消失的问题,看下面的[例子](https://play.golang.org/p/MHKPG ...查看全部
在[错误处理](https://gocn.io/article/348)这个文章中,[tkk](https://gocn.io/people/tkk)提出了空指针时堆栈消失的问题,看下面的[例子](https://play.golang.org/p/MHKPG5uFFn):

```
package main

func main() {
run() // line 4
}
func run() {
causedPanic()
}
func causedPanic() {
//defer func() {}() // line 10
//panic("Panic from user") // line 11
var p *byte
*p = 0 // line 13
}
```

这个程序崩溃时,打印的竟然是:

```
panic: runtime error: invalid memory address or nil pointer dereference

goroutine 1 [running]:
main.main()
/tmp/sandbox277759147/main.go:4 +0x4
```

神奇的是,把第10行的defer打开,变成[这样](https://play.golang.org/p/V7qDdyt_4Z):
```
func causedPanic() {
defer func() {}() // line 10
//panic("Panic from user") // line 11
var p *byte
*p = 0 // line 13
}
```

堆栈神奇的回来了:
```
panic: runtime error: invalid memory address or nil pointer dereference

goroutine 1 [running]:
main.causedPanic()
/tmp/sandbox416089181/main.go:13 +0x48
main.run()
/tmp/sandbox416089181/main.go:7 +0x20
main.main()
/tmp/sandbox416089181/main.go:4 +0x20
```

而主动调用panic堆栈也是没有问题的,可以把第10行注释掉,同时打开第11行。这个问题确实很诡异,在go-nuts中发了一篇文章问,[strange stack trace when panic](https://groups.google.com/d/msg/golang-nuts/clvdsdousDw/sHLSG7SnAAAJ),马上就有神回复了:

```
On Wednesday, June 7, 2017 at 4:25:35 PM UTC+8, Dave Cheney wrote:

Try building your program with -gcflags="-l" to disable inlining.
If that restores the stacktrace, then it's inlining.
The good news is this should be fixed with Go 1.9
```

果然,运行时加上这个参数(编译时加上也是可以的),禁用内联编译后,堆栈就回来了:

```
go run -gcflags="-l" t.go
```

难怪了,主动调用panic时,内联编译不会把函数怼一坨去,如果没有defer和panic这种函数,就可能把函数怼一坨,看起来像是一个函数,堆栈消失了,这样在空指针时就找不到堆栈信息。

解决方案:

1. 编译时加参数`-gcflags="-l"`
1. 可能在GO1.9会解决这个问题。

结贴。

GOLANG错误处理最佳方案

技术讨论winlin 发表了文章 • 23 个评论 • 8891 次浏览 • 2017-06-05 10:05 • 来自相关话题

GOLANG的错误很简单的,用error接口,参考[golang error handling](https://blog.golang.org/error-handling-and-go): ``` if f,err : ...查看全部
GOLANG的错误很简单的,用error接口,参考[golang error handling](https://blog.golang.org/error-handling-and-go):

```
if f,err := os.Open("test.txt"); err != nil {
return err
}
```

实际上如果习惯于C返回错误码,也是可以的,定义一个整形的error:

```
type errorCode int
func (v errorCode) Error() string {
return fmt.Sprintf("error code is %v", v)
}

const loadFailed errorCode = 100

func load(filename string) error {
if f,err := os.Open(filename); err != nil {
return loadFailed
}
defer f.Close()

content : = readFromFile(f);
if len(content) == 0 {
return loadFailed
}

return nil
}
```

这貌似没有什么难的啊?实际上,这只是error的基本单元,在实际的产品中,比如有个播放器会打印一个这个信息:

```
Player: Decode failed.
```

对的,就只有这一条信息,然后呢?就没有然后了,只知道是解码失败了,没有任何的线索,必须得调试播放器才能知道发生了什么。看我们的例子,如果`load`失败,也是一样的,只会打印一条信息:

```
error code is 100
```

这些信息是不够的,这是一个错误库很流行的原因,这个库是[errors](https://github.com/pkg/errors),它提供了一个Wrap方法:

```
_, err := ioutil.ReadAll(r)
if err != nil {
return errors.Wrap(err, "read failed")
}
```

也就是加入了多个error,如果用这个库,那么上面的例子该这么写:

```
func load(filename string) error {
if f,err := os.Open(filename); err != nil {
return errors.Wrap(err, "open failed")
}
defer f.Close()

content : = readFromFile(f);
if len(content) == 0 {
return errors.New("content empty")
}

return nil
}
```

这个库给每个error可以加上额外的消息`errors.WithMessage(err,msg)`,或者加上堆栈信息`errors.WithStack(err)`,或者两个都加上`erros.Wrap`, 或者创建带堆栈信息的错误`errors.New`和`errors.Errorf`。这样在多层函数调用时,就有足够的信息可以展现当时的情况了。

在多层函数调用中,甚至可以每层都加上自己的信息,例如:

```
func initialize() error {
if err := load("sys.db"); err != nil {
return errors.WithMessage(err, "init failed")
}

if f,err := os.Open("sys.log"); err != nil {
return errors.Wrap(err, "open log failed")
}
return nil
}
```

在`init`函数中,调用`load`时因为这个err已经被`Wrap`过了,所以就只是加上自己的信息(如果用`Wrap`会导致重复的堆栈,不过也没有啥问题的了)。第二个错误用Wrap加上信息。打印日志如下:

```
empty content
main.load
/Users/winlin/git/test/src/demo/test/main.go:160
main.initialize
/Users/winlin/git/test/src/demo/test/main.go:167
main.main
/Users/winlin/git/test/src/demo/test/main.go:179
runtime.main
/usr/local/Cellar/go/1.8.1/libexec/src/runtime/proc.go:185
runtime.goexit
/usr/local/Cellar/go/1.8.1/libexec/src/runtime/asm_amd64.s:2197
load sys.db failed
```

这样就可以知道是加载`sys.db`时候出错,错误内容是`empty content`,堆栈也有了。遇到错误时,会非常容易解决问题。

例如,AAC的一个库,用到了ASC对象,在解析时需要判断是否数据合法,实现如下(参考[code](https://github.com/ossrs/go-oryx-lib/blob/e482302c1c163934488a195765b5a239ea7eaa88/aac/aac.go#L348)):
```
func (v *adts) Decode(data []byte) (raw, left []byte, err error) {
p := data
if len(p) <= 7 {
return nil, nil, errors.Errorf("requires 7+ but only %v bytes", len(p))
}

// Decode the ADTS.

if err = v.asc.validate(); err != nil {
return nil, nil, errors.WithMessage(err, "adts decode")
}
return
}

func (v *AudioSpecificConfig) validate() (err error) {
if v.Channels < ChannelMono || v.Channels > Channel7_1 {
return errors.Errorf("invalid channels %#x", uint8(v.Channels))
}
return
}
```

在错误发生的最原始处,加上堆栈,在外层加上额外的必要信息,这样在使用时发生错误后,可以知道问题在哪里,写一个实例程序:
```
func run() {
adts,_ := aac.NewADTS()
if _,_,err := adts.Decode(nil); err != nil {
fmt.Println(fmt.Sprintf("Decode failed, err is %+v", err))
}
}

func main() {
run()
}
```

打印详细的堆栈:
```
Decode failed, err is invalid object 0x0
github.com/ossrs/go-oryx-lib/aac.(*AudioSpecificConfig).validate
/Users/winlin/go/src/github.com/ossrs/go-oryx-lib/aac/aac.go:462
github.com/ossrs/go-oryx-lib/aac.(*adts).Decode
/Users/winlin/go/src/github.com/ossrs/go-oryx-lib/aac/aac.go:439
main.run
/Users/winlin/git/test/src/test/main.go:13
main.main
/Users/winlin/git/test/src/test/main.go:19
runtime.main
/usr/local/Cellar/go/1.8.1/libexec/src/runtime/proc.go:185
runtime.goexit
/usr/local/Cellar/go/1.8.1/libexec/src/runtime/asm_amd64.s:2197
adts decode
```

错误信息包含:

1. `adts decode`,由ADTS打印出。
1. `invalid object 0x00`,由ASC打印出。
1. 完整的堆栈,包含`main/run/aac.Decode/asc.Decode`。

如果这个信息是客户端的,发送到后台后,非常容易找到问题所在,比一个简单的`Decode failed`有用太多了,有本质的区别。如果是服务器端,那还需要加上上下文关于连接的信息,区分出这个错误是哪个连接造成的,也非常容易找到问题。

加上堆栈会不会性能低?错误出现的概率还是比较小的,几乎不会对性能有损失。使用复杂的error对象,就可以在库中避免用logger,在应用层使用logger打印到文件或者网络中。

对于其他的语言,比如多线程程序,也可以用类似方法,返回int错误码,但是把上下文信息保存到线程的信息中,清理线程时也清理这个信息。对于协程也是一样的,例如[ST](https://github.com/ossrs/state-threads)的thread也可以拿到当前的ID,利用全局变量保存信息。对于goroutine这种拿不到协程ID,可以用`context.Context`,实际上最简单的就是在error中加入上下文,因为`Context`要在1.7之后才纳入标准库。

一个C++的例子,得借助于宏定义:
```
struct ComplexError {
int code;
ComplexError* wrapped;
string msg;

string func;
string file;
int line;
};

#define errors_new(code, fmt, ...) \
_errors_new(__FUNCTION__, __FILE__, __LINE__, code, fmt, ##__VA_ARGS__)
extern ComplexError* _errors_new(const char* func, const char* file, int line, int code, const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
char buffer[1024];
size_t size = vsnprintf(buffer, sizeof(buffer), fmt, ap);
va_end(ap);

ComplexError* err = new ComplexError();
err->code = code;
err->func = func;
err->file = file;
err->line = line;
err->msg.assign(buffer, size);
return err;
}

#define errors_wrap(err, fmt, ...) \
_errors_wrap(__FUNCTION__, __FILE__, __LINE__, err, fmt, ##__VA_ARGS__)
extern ComplexError* _errors_wrap(const char* func, const char* file, int line, ComplexError* v, const char* fmt, ...) {
ComplexError* wrapped = (ComplexError*)v;

va_list ap;
va_start(ap, fmt);
char buffer[1024];
size_t size = vsnprintf(buffer, sizeof(buffer), fmt, ap);
va_end(ap);

ComplexError* err = new ComplexError();
err->wrapped = wrapped;
err->code = wrapped->code;
err->func = func;
err->file = file;
err->line = line;
err->msg.assign(buffer, size);
return err;
}
```

使用时,和GOLANG有点类似:

```
ComplexError* loads(string filename) {
if (filename.empty()) {
return errors_new(100, "invalid file");
}
return NULL;
}
ComplexError* initialize() {
string filename = "sys.db";
ComplexError* err = loads(filename);
if (err) {
return errors_wrap("load system from %s failed", filename.c_str());
}
return NULL;
}
int main(int argc, char** argv) {
ComplexError* err = initialize();
// Print err stack.
return err;
}
```
比单纯一个code要好很多,错误发生的概率也不高,获取详细的信息比较好。

另外,logger和error是两个不同的概念,比如对于library,错误时用errors返回复杂的错误,包含丰富的信息,但是logger一样非常重要,比如对于某些特定的信息,access log能看到客户端的访问信息,还有协议一般会在关键的流程点加日志,说明目前的运行状况,此外,还可以有json格式的日志或者叫做消息,可以把这些日志发送到数据系统处理。

对于logger,支持`context.Context`就尤其重要了,实际上`context`就是一次会话比如一个http request的请求的处理过程,或者一个RTMP的连接的处理。一个典型的logger的定义应该是:

```
// C++ style
logger(int level, void* ctx, const char* fmt, ...)
// GOLANG style
logger(level:int, ctx:context.Context, format string, args ...interface{})
```

这样在文本日志,或者在消息系统中,就可以区分出哪个会话。当然在error中也可以包含context的信息,这样不仅仅可以看到出错的错误和堆栈,还可以看到之前的重要的日志。还可以记录线程信息,对于多线程和回调函数,可以记录堆栈:

```
[2017-06-08 09:44:10.815][Error][54417][100][60] Main: Run, code=1015 : run : callback : cycle : api=http://127.0.0.1:8080, url=rtmp://localhost/live/livestream, token=16357216378262183 : parse json={"code":0,"data":{"servers":["127.0.0.1:1935"]}} : no data.key
thread #122848: run() [src/test/main.cpp:303][errno=60]
thread #987592: do_callback() [src/test/main.cpp:346][errno=36]
thread #987592: cycle() [src/sdk/test.cpp:3332][errno=36]
thread #987592: do_cycle() [src/sdk/test.cpp:3355][errno=36]
thread #987592: gslb() [src/sdk/test.cpp:2255][errno=36]
thread #987592: gslb_parse() [src/sdk/test.cpp:2284][errno=36]
```

当然,在ComplexError中得加入`uint64_t trd`和`int rerrno`,然后new和wrap时赋值就好了。

GOLANG环境设置

技术讨论winlin 发表了文章 • 0 个评论 • 1947 次浏览 • 2017-05-25 09:41 • 来自相关话题

原文:http://blog.csdn.net/win_lin/article/details/48265493 # GO环境 ## 安装GO 官网下载GOLANG的安装:[gola ...查看全部
原文:http://blog.csdn.net/win_lin/article/details/48265493

# GO环境

## 安装GO

官网下载GOLANG的安装:[golang.org](https://golang.org/dl)

如果不能翻墙,可以从[golangtc](http://www.golangtc.com/download)或者[gocn.io](https://dl.gocn.io/)下载。

下载时,按照OS选择安装包:

1. MAC,用brew安装(`brew install go`)(**推荐**),不需要设置PATH。或者下载包含`darwin`的pkg或tar,pkg可以直接安装但是卸载比较麻烦,tar需要自己设置PATH安装麻烦卸载比较方便,例如[go1.8.1.darwin-amd64.pkg](https://storage.googleapis.com/golang/go1.8.1.darwin-amd64.pkg)或[go1.8.1.darwin-amd64.tar.gz](https://storage.googleapis.com/golang/go1.8.1.darwin-amd64.tar.gz)。
1. Windows,一般就是安装包了,根据自己的系统是32位还是64位下载,譬如[go1.8.1.windows-amd64.msi](https://storage.googleapis.com/golang/go1.8.1.windows-amd64.msi)或者[go1.8.1.windows-386.msi](https://storage.googleapis.com/golang/go1.8.1.windows-386.msi)
1. Linux,一般都是64位的,而且需要手动解压和设置PATH,当然还有ARM的,可以选择自己需要的下载,譬如[go1.8.1.linux-amd64.tar.gz](https://storage.googleapis.com/golang/go1.8.1.linux-amd64.tar.gz)

如果是需要设置PATH的,步骤如下:

```
# 先解压和移动目录,以1.8.1为例
tar xf go1.8.1.linux-amd64.tar.gz &&
sudo mkdir -p /usr/local/ &&
sudo mv go /usr/local/go

# 设置PATH,打开文件
sudo vi /etc/profile

# 在文件末尾输入内容,然后保存
export PATH=$PATH:/usr/local/go/bin

# 应用修改的配置
source /etc/profile
```

## GOPATH

UNIX(linux/darwin)设置GOPATH,存放项目的路径,GOLANG的项目都是绝对路径,从这个目录开始搜索:
```
# 创建目录,一般都是$HOME下面的go目录,譬如:/home/winlin/go
mkdir -p $HOME/go

# 设置GOPATH,打开文件
sudo vi /etc/profile

# 在文件末尾输入内容,然后保存
export GOPATH=$HOME/go

# 应用修改的配置
source /etc/profile
```

这样就可以用go build等编译了。譬如执行命令`go version`:
```
Mac winlin$ go version
go version go1.8.1 darwin/amd64
```

用`go get`从GITHUB下载一个项目,可以用到自己的项目中,可以看到下载到了GOPATH中:
```
Mac winlin$ go get github.com/ossrs/go-oryx-lib

Mac winlin$ ls -lh $GOPATH/src/github.com/ossrs
drwxr-xr-x 22 winlin 748B May 25 09:43 go-oryx-lib
```

GO的环境就配置成功了。

GOLANG使用Context管理关联goroutine

技术讨论winlin 发表了文章 • 6 个评论 • 5157 次浏览 • 2017-05-19 15:08 • 来自相关话题

一般一个业务很少不用到goroutine的,因为很多方法是需要等待的,例如`http.Server.ListenAndServe`这个就是等待的,除非关闭了Server或Listener,否则是不会返回的。除非是一个API服务器,否则肯定需要另外起gorout ...查看全部
一般一个业务很少不用到goroutine的,因为很多方法是需要等待的,例如`http.Server.ListenAndServe`这个就是等待的,除非关闭了Server或Listener,否则是不会返回的。除非是一个API服务器,否则肯定需要另外起goroutine发起其他的服务,而且对于API服务器来说,在`http.Handler`的处理函数中一般也需要起goroutine,如何管理这些goroutine,在GOLANG1.7提供`context.Context`。

先看一个简单的,如果启动两个goroutine,一个是HTTP,还有个信号处理的收到退出信号做清理:

```
wg := sync.WaitGroup{}
defer wg.Wait()

wg.Add(1)
go func() {
defer wg.Done()

ss := make(os.Signal, 0)
signal.Notify(ss, syscall.SIGINT, syscall.SIGTERM)
for s := ss {
fmt.Println("Got signal", s)
break
}
}()

wg.Add(1)
go func() {
defer wg.Done()

svr := &http.Server{ Addr:":8080", Handler:nil, }
fmt.Println(svr.ListenAndServe())
}
```

很清楚,起了两个goroutine,然后用WaitGroup等待它们退出。如果它们之间没有交互,不互相影响,那真的是蛮简单的,可惜这样是不行的,因为信号的goroutine收到退出信号后,应该通知server退出。暴力一点的是直接调用`svr.Close()`,但是如果有些请求还需要取消怎么办呢?最好用Context了:

```
wg := sync.WaitGroup{}
defer wg.Wait()

ctx,cancel := context.WithCancel(context.Background())

wg.Add(1)
go func() {
defer wg.Done()

ss := make(chan os.Signal, 0)
signal.Notify(ss, syscall.SIGINT, syscall.SIGTERM)
select {
case <- ctx.Done():
return
case s := <- ss:
fmt.Println("Got signal", s)
cancel() // 取消请求,通知用到ctx的所有goroutine
return
}
}()

wg.Add(1)
go func() {
defer wg.Done()
defer cancel()

svr := &http.Server{ Addr:":8080", Handler:nil, }

go func(){
select {
case <- ctx.Done():
svr.Close()
}
}

fmt.Println(svr.ListenAndServe())
}
```

这个方式可以在新开goroutine时继续使用,譬如新加一个goroutine,里面读写了UDPConn:

```
wg.Add(1)
go func() {
defer wg.Done()
defer cancel()

var conn *net.UDPConn
if conn,err = net.Dial("udp", "127.0.0.1:1935"); err != nil {
fmt.Println("Dial UDP server failed, err is", err)
return
}

fmt.Println(UDPRead(ctx, conn))
}()

UDPRead = func(ctx context.Context, conn *net.UDPConn) (err error) {
wg := sync.WaitGroup{}
defer wg.Wait()

ctx, cancel := context.WithCancel(ctx)

wg.Add(1)
go func() {
defer wg.Done()
defer cancel()

for {
b := make([]byte, core.MTUSize)
size, _, err := conn.ReadFromUDP(b)
// 处理UDP包 b[:size]
}
}()

select {
case <-ctx.Done():
conn.Close()
}
return
}
```

如果只是用到HTTP Server,可以这么写:

```
func run(ctx contex.Context) {
server := &http.Server{Addr: addr, Handler: nil}
go func() {
select {
case <-ctx.Done():
server.Close()
}
}()

http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
})

fmt.Println(server.ListenAndServe())
}
```

如果需要提供一个API来让服务器退出,可以这么写:

```
func run(ctx contex.Context) {
server := &http.Server{Addr: addr, Handler: nil}

ctx, cancel := context.WithCancel(ctx)
http.HandleFunc("/quit", func(w http.ResponseWriter, r *http.Request) {
cancel() // 使用局部的ctx和cancel
})

go func() {
select {
case <-ctx.Done():
server.Close()
}
}()

fmt.Println(server.ListenAndServe())
}
```

使用局部的ctx和cancel,可以避免cancel传入的ctx,只是影响当前的ctx。

GOLANG使用嵌入结构实现接口

技术讨论winlin 发表了文章 • 0 个评论 • 1856 次浏览 • 2017-05-19 13:23 • 来自相关话题

考虑一个Packet接口,一般会返回一个Header,例如: ``` type PacketHeader struct { ID uint32 Timestamp uint64 } ...查看全部
考虑一个Packet接口,一般会返回一个Header,例如:

```
type PacketHeader struct {
ID uint32
Timestamp uint64
}

type Packet interface {
encoding.BinaryMarshaler
encoding.BinaryUnmarshaler
Header() *PacketHeader
}
```

如果是OO的语言,一般会有一个基类,里面包含了Header和实现这个Header:

```
class BasePacket : public Packet {
protected:
PacketHeader h;
public:
virtual Header() *PacketHeader;
};

class HandshakePacket : public BasePacket {
};
```

在子类中就都实现了这个`Header()`方法了,在GOLANG同样可以做到,通过在Header中定义方法,在Packet中包含Header就可以。

```
func (v *PacketHeader) Header() *PakcetHeader {
return v
}

type HandshakePacket struct {
PacketHeader
}
```

看起来还差不多的,都可以实现,golang只是代码少一点,清晰一点点而已。考虑要添加一些辅助函数,譬如给Packet添加是否是紧急类型的包,那OO语言得做一次代理:

```
type Packet interface {
IsErgency() bool
}

class BasePacketHeader {
public:
bool IsErgency() {
return realtime < 3;
}
}

class BasePacket {
public:
bool IsErgency() {
return h.IsErgency();
}
}
```

而在GOLANG中,只需要在Header实现就好了:

```
func (v *PacketHeader) IsErgency() bool {
return v.realtime < 3
}
```

更高级的可以直接嵌入接口。譬如`context.Context`的实现,`cancelCtx`直接嵌入了一个接口:

```
type cancelCtx struct {
Context
```

通过指定类型,或者初始化的顺序初始化`struct`:
```
func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{
Context: parent,
done: make(chan struct{}),
}
}
```

结构嵌套的方式,让组合实现起来非常便捷,避免频繁的代理。

使用两个context实现CLOSE包的超时等待

技术讨论winlin 发表了文章 • 0 个评论 • 1673 次浏览 • 2017-05-18 20:39 • 来自相关话题

在UDP中,一般发送者发送包后,如果一定的时间对方没有收到,就需要重传。例如UDP实现握手的过程,如果握手的包,比如RTMFP协议的IHELLO,发送给对方后,如果一定1秒没有收到,就应该重发一次,然后等3秒、6秒、9秒,如果最后没有收到就是超时了。 ...查看全部
在UDP中,一般发送者发送包后,如果一定的时间对方没有收到,就需要重传。例如UDP实现握手的过程,如果握手的包,比如RTMFP协议的IHELLO,发送给对方后,如果一定1秒没有收到,就应该重发一次,然后等3秒、6秒、9秒,如果最后没有收到就是超时了。

最后一个Close包,发送者不能等待这么长的时间,所以需要设置一个较短的时间做超时退出。一般收发都是一个context,在最后这个Close包时,收到ctx.Done也不能立刻退出,因为还需要稍微等待,譬如600毫秒如果没有收到响应才能退出。

一个可能的实现是这样:

```
in := make(chan []byte)

func Close(ctx context.Context) (err error) {
timeous := ... // 1s,3s,6s,9s...
for _, to := range timeouts {
// 发送给对方WriteToUDP("CLOSE", peer)
// 另外一个goroutine读取UDP包到in

select {
case <- time.After(to):
case <- in:
fmt.Println("Close ok")
return
case <- ctx.Done():
fmt.Println("Program quit")
return
}
}
return
}
```

但是这个问题在于,在程序退出时,一般都会cancel ctx然后调用Close方法,这个地方就不会等待任何的超时,就打印"Program quit"然后返回了。解决方案是用另外一个context。但是如何处理之前的ctx的done呢?可以再起一个goroutine做同步:

```
in := make(chan []byte)

func Close(ctx context.Context) (err error) {
ctxRead,cancelRead := context.WithCancel(context.Background())
go func(){ // sync ctx with ctxRead
select {
case <-ctxRead.Done():
case <-ctx.Done():
select {
case <-ctxRead.Done():
case <-time.After(600*time.Milliseconds):
cancelRead()
}
}
}()

ctx = ctxRead // 下面直接用ctxRead。
timeous := ... // 1s,3s,6s,9s...
for _, to := range timeouts {
// 发送给对方WriteToUDP("CLOSE", peer)
// 另外一个goroutine读取UDP包到in

select {
case <- time.After(to):
case <- in:
fmt.Println("Close ok")
return
case <- ctx.Done():
fmt.Println("Program quit")
return
}
}
return
}
```

这样在主要的逻辑中,还是只需要处理ctx,但是这个ctx已经是新的context了。不过在实际的过程中,这个sync的goroutine需要确定起来后,才能继续,否则会造成执行顺序不确定:

```
sc := make(chan bool, 1)
go func(){ // sync ctx with ctxRead
sc <- true
select {
......
}
<- sc
```

使用context,来控制多个goroutine的执行和取消,是非常好用的,关键可以完全关注业务的逻辑,而不会引入因为ctx取消或者超时机制而造成的特殊逻辑。
讨论关于Go和相关生态的空间