Browsed by
分类:golang

My Yearly Goals

My Yearly Goals

 

Effective Go & GoFAQ 阅读笔记

Effective Go & GoFAQ 阅读笔记

Effective go: https://golang.org/doc/effective_go.html

Go FAQ: https://golang.org/doc/faq

下面是对以上两篇文章内相关内容的记录与解释/总结

Interface

某一个 type “implement” 这个 interface 的话,只要实现这个interface的全部方法即可 不过这里有一些容易产生误区的地方,看下面这个例子

 

Equaler是一个interface, 下面看这两个例子

type T并没有实现Equaler interface 因为参数类型不是 Equaler 而是一个具体的类型 T

这个问题很容易理解, 试想一下其他某一个type implement了Equaler类型(假设为Q) 而 t.Equal(Q) 是不合法的 因为Q不是T类型, 所以 T 并没有实现Equaler这个interface 因为Equaler接口要求只要满足type为Equaler就都可以作为参数传递给Equal

下面这个才是T implement Equaler的例子

 

interface 的 value = nil 不代表interface为nil, interface的结构为type, value两部分 下面这个代码永远会返回错误

上述代码中 Process成功将返回一个p(type=*DemoError, value=nil)这个与nil进行对比的时候,永远会返回假 因而不要使用这种方式返回error相对的,直接return Error即可(待斟酌)

 

map slice channel存的是引用, array 存的是value

在go中函数传递的都为按值传递, 如果传递一个array并且对array进行修改,修改是无法保留下来的,这个和C语言中传递array不一样,C语言传递数组传递的是数组的地址,如果想要修改数组中的元素, 可以传递slice,并不是说slice的传递是按照地址传递,slice传递依旧是按值传递,不过slice包含三部分 len, cap, reference to array  而对slice操作的时候,操作的就是它下面指向的array,因而可以修改元素,不过没有办法append元素或者remove元素,这些操作会修改len 和 cap,在函数返回的时候len和cap的修改会丢失

Idiom

Almost Never, Do not use a pointer to an interface

Do not communicating by sharing memory, Share memory by communicating

Concurrency is not Parallelism

 

Naming

对于Getter的名字,不要使用GetXXX

interface type name end with -er

 

Control Flow

if often exit current function

e.g:

if 经常直接用于退出这个函数,执行块等,这样就可以省去多余的else

 

switch可以用来判断value的类型

用法为

 

break 可以跳出多层循环

用法为 break Label

注意Label一定要打在某一个循环的for statement之前,位置错误的话会报编译错误

 

Function

 

使用named return value

使用这种形式

而不是这种

 

使用 defer 做cleanup

当打开文件, 数据库链接,或者TCP链接的时候,我们需要在使用完毕之后关闭链接, 在传统的C语言中, 在函数退出的地方编写cleanup相关的代码 而在go里, 有一个书写,阅读都更友好的方式,即使用defer

 

这种写法的好处有两点(至少)

  1. 在打开的代码之后马上就书写关闭,便于排查代码中的问题,提高了代码的可读性
  2. 如果函数内有多个返回点,不需要多次书写cleanup代码,只要一次即可

不过在使用的时候要注意一点: 一定要在确保成功操作之后(如上述代码,确保文件成功打开之后)再执行defer cleanup,不然程序在打开f失败的时候,调用Close就会产生panic

 

Data

make and new

make创建的是对应type的一个实例 new创建的是一个type实例的指针

new和make都会将对象初始化为0

 

println , printf %s/%v recursion hell

当使用println/printf (%v/%s)打印自己定义的结构的信息时 要注意避免无线递归

看下面的代码

 

这段代码会导致无限递归死循环

原理就是 printf %s, %v会调用相应type的String()函数, 而在String函数内我们又使用了%v,又会去调用String()函数,因而就无限递归了

这里只要做一个修改就能避免这个递归问题

 

将 f 类型转换为string 内置的类型会自动进行处理,不会再去调用Forever类型的String()函数

 

Append a slice to a slice

将一个slice append到另一个slice的语法是这样的

一定要有那三个”.”

 

Concurrency Tips

  • Only one goroutine can access the value at any time
  • Do not spawn unlimited go routines
  • using goroutines and channel can build non-blocking RPC system
  • [OFFTOPIC] Channel can use as queue, only in single goroutine(e.g leaky buffer)
[Docker] 新建一个Docker Image并push到dockerhub

[Docker] 新建一个Docker Image并push到dockerhub

Preface

软件开发测试过程中的环境配置一直是令人头疼的事情,Docker正是为了解决这种问题而诞生的一套工具,现在已经发展成为了一个生态,使用Docker,可以轻松的部署你的项目,并且保证不同slave机器的环境配置完全一致,当你提交一个patch(commit)之后,想要测试项目的运行的时候, 只需要docker run <your-image>就可以轻松的搭建好项目运行环境,并且访问配置好的服务器端口,就可以访问到刚刚修改后的项目了~ 同时docker结合DroneCI TravisCI等CI平台,使得软件测试也变得十分容易 NEUOJ的健壮性测试,就是在自建的私有DroneCI服务器上运行的

但是因为之前我是直接使用的centos的镜像,没有进行修改,所以之前我们每一次运行测试,都需要下载一大堆软件包,然后才开始运行代码测试, 既费时又费流量(每一次都需要安装200MB的东西)

hhhh

因而自己新建一个Docker镜像包含这些所有的软件包能省去很多时间以及流量

新建一个Docker镜像

使用docker build命令可以轻松的创建Docker Image

创建docker image需要Dockerfile, Dockerfile描述了这个image要用到的base镜像, 你可以从一个已经存在的镜像开始创建, 也可以Build From Scratch

Dockerfile的每一个指令的格式如下

常用的INSTRUCTION有

FROM: 表示镜像的base是什么,scratch的话表示无base

RUN: 执行Shell命令,并将结果打包到images中

CMD: docker run images的时候执行的命令

MAINTAINER: 维护者

以下是一个示例Dockerfile

然后 生成Docker Image的指令为

docker build -t <username>/<imagename> /path/to/your/Dockerfile/directory

e.g docker build -t void001/neuoj-test .

然后 image 就生成好啦~

将Docker镜像push到Docker Registry

镜像生成好了,我们可以用 docker images来从查看 为了使我们的镜像全网络都可以用,我们需要将他push到一个docker registry上,可以理解为和github类似,只不过github托管的是code,docker registry(也叫dockerhub)托管的是镜像 docker官方的registry是 hub.docker.com 我们也可以用docker自己在本地搭建docker registry服务器,这里就不做介绍了

在dockerhub注册一个账号之后,我们在本地使用如下指令就可以登录自己的dockerhub账号

docker login

登录的时候需要提供用户名和密码 然后就会将你的credential保存在本地的一个文件里(~/.docker/config.json) 然后你就可以向docker reg 中你的repo进行推送操作了

这里注意,之前这个镜像的名字一定要为 yourname/your-image 而不能单单是 your-image 不然会导致直接推送到官方的docker lib/image下,而你是没有这个权限的,所以推送失败QAQ确定好镜像名字之后,直接使用

docker push yourname/your-image 就可以将镜像推送到docker registry了~

hhhh

然后就可以在自己的.drone.yml里配置使用我的neuoj-test镜像作为测试用镜像了,已经预装好了所有NEUOJ运行需要的依赖,因而只需要运行测试就可以而不需要每次都安装一大堆软件包了~(镜像下载完之后,会存到本地的docker的lib下,因而只需要pull一次镜像,以后就可以很快的从本地get到这个镜像)

[go]heka插件开发

[go]heka插件开发

官方文档传送门

Heka是一个用于进行DataProcessing的一个系统, 具有高度的扩展性, 官方自称为 Swiss Army Knife 型的工具

heka通过一系列插件进行工作heka支持的插件种类有

  • Input
  • Splitters
  • Decoders
  • Filters
  • Encoders
  • Outputs

六种插件 , 对于不同插件的作用, 可以在官方文档内很容易的找到, 这里就不做说明了

开发一个Input插件

heka语言的插件大部分使用 golang 开发, 下面是一个使用heka实现一个input插件, 从文件读入数据的例子

使插件能够被heka识别(注册插件到heka中)

首先, go的开发要保证GOPATH配置正确, heka安装好之后, 为heka开发插件的默认GOPATH是 /path/to/your/heka-src/build/heka/ 如果不将gopath设置为这个, 就会找不到要导入的heka有关的包

为了保证我们对插件开发的可视化, 每一步开发都能看到结果是最好的, 因而我们先尝试开发一个最最简单的插件, 只让这个插件注册到heka中, 能够确定这个插件被加载即可

参考文档:

  • [1]http://hekad.readthedocs.io/en/v0.10.0/developing/plugin.html#registering-your-plugin
  • [2]http://hekad.readthedocs.io/en/v0.10.0/installing.html#building-hekad-with-external-plugins

首先我们在新的GOPATH下的src/github.com/下建立自己的工作区和包目录 (我这里以./VOID001/inputplug 为例)

然后我们定义一个type 这个type就是我们的Plugin的type, 这里起名为SampleInput, 其他的名字也都可以 , 这个地方对命名没有什么约束, 下面有对名字有约束的地方, 会进行说明.  然后 将这个type实现为Plugin interface(即将 Plugin 这个interface对应的全部方法实现在type上)

这样, 就已经实现了一个最基本的Plugin ,SampleInputPlug 指针实现了Plugin这个接口, 然后通过查看 [1] 我们加入这样一句话到Init中, 将我们的Plugin注册到heka中(这里是有两个坑的, 马上就会发现了 = = )

好, 我们go build, 恩 没有什么问题, 下一步 就是把这个插件给编译到heka中(吐槽: go只支持静态编译, 所以每当新开发一个插件都需要对heka进行一次重新编译=A=很不爽)

根据[2]我们修改 /path/to/your/heka/cmake 下面的 plugin_loader.cmake 添加如下这一行(注:我目前还没有找到添加本地的git repo然后直接clone本地的gitrepo进行编译的方法 , 不过我认为肯定是有的, 目前先不管这个) , 在添加的时候要保证你的这个plugin所在的目录(即inputplug)被添加到了一个 git repo并push到了github上

好了,然后在heka根目录执行  source ./build.sh 然后新建一个heka配置文件 sample.toml 里面写上

好了, 然后我们运行  ./hekad -config sample.toml  …. 诶 (#°Д°) 为毛出错了!!! (好了即将进入填坑时间)

这个错误提示我们这个插件没有被注册 = = 仔细阅读文档之后发现 我们的RegisterPlugin的第一个参数字串 是不符合要求的

The name value should be a unique identifier for your plugin, and it should end in one of “Input”, “Splitter”, “Decoder”, “Filter”, “Encoder”, or “Output”, depending on the plugin type.

好, 我们把名字改成 SampleInput 去掉后面的Plug ,然后修改我们的toml配置文件 & 重新编译,运行 还是不行(#°Д°)

这次是一个很无语的错误 = = 我们应该把RegisterPlugin这个写在 init()函数内 而不是 Init 函数内 , init()函数是每一个包在被加载的时候最先调用的函数, 我们应该在这里对Plugin进行注册 , 修改后的代码如下:

再次编译, 运行(WTF)看结果, 恩 终于成功了~

写好Input插件的框架

上面已经将我们的插件注册到 heka中了, 下来就是开始开发一个Input插件 , 我们先实现从文件中读取内容这一个简单的功能

为了实现一个Input插件, 我们必须实现Input接口 , Input接口的定义如下:

我们为*SampleInputPlug 实现这个接口

对于一个Input Plugin 来说, Run这个函数在插件运行的时候就运行了,并且如果不出什么意外的话(比如突然死机, 或者代码写残了导致它崩溃了) 会一直运行到hekad退出  , 当然就算我们的插件跪了, 如果我们让插件具有了无限复活的buff的话~ (参见 Restarting Plugin) 那么还是可以救回来的 ~

Stop函数也就是释放资源, 清理现场, 比如回收打开的文件, 通知goroutine退出等等~

我们先来看Run函数, 这个函数通常要做这样三件事(也就是一个Input Plugin要做的事啦)

  • 获取data
  • 把获取到的data给一个SplitterRunner(这又是什么呢0.0 别急, 一会儿说)
  • (可选的)Provide a “pack decorator” function to the SplitterRunner to populate the message object with any input-specific information(暂时没看懂, 先放着)

那么我们就开始第一步的实现啦

首先获取数据, 我们通过打开一个文件来获取输入

这样 file 就是一个 io.Reader 接口 , 下一步就是将数据传递给Splitter Runner

SplitterRunner 就是一个用于读取接收到的数据并且传递到 heka pipeline的 接口, 通过 InputRunner 自带的函数 NewSplitterRunner 我们可以实现这个功能,  然后我们使用SplitStream这个方法, 读取并切割这个文件流, 下面是具体实现Run的代码

注意:

这一句一定要放在循环外, 放在循环里的话, 会导致内存被分配的sr占满(电脑卡死两次的哭着告诉你)

好,然后我们简单修改一下配置文件 sample.toml 让我们能看到我们的修改的效果

然后我们执行 ./hekad -config /tmp/text (text 已经存在) 然后 , 恩, 我们要的输出看到啦 ~~~

好的现在停止heka(Ctrl + C) 结果发现, 没有办法停止(#°Д°)

一直卡在这里….. = = 好吧看来我们还要继续

为我们的Input插件实现退出功能

在插件退出的时候会call  Stop函数 ,我们Run实现的是一个forever loop , 一直读取数据直到hekad退出,  为了通知Run退出, 我们需要实现一个 channel , 这里在我们的SampleInputPlug内实现了一个 stopMsg 成员, 类型为 chan interface{}  注意: 在使用你的 channel前 一定要make make make!!!!!(又被坑一波的我 = =) 然后我们用 select , 非阻塞的查询信号的到来 , 具体的代码如下~~

好了, 这样我们的插件也能正常退出啦 , 这次就到这里 ~~