web
  stats

Golang Error: bad file descriptor

Intro

好久没写 Blog 了,正好今天写了一个爬取 Bing 首页高清图片的服务 Application => bingImgCrawer 碰到了之前遇到的一个写文件的问题,现在就将其纪录下来吧。

Problem

我的需求是将匹配出来的 图片URL 按照日期纪录到 img_url.txt 文件中。

下面是我的问题代码:

const (
    LogFile = "./img_url.txt"
)
func logFile(url string) {
    ver file *os.File 
    var err error
    if isExist(LogFile) {
        file, err = os.OpenFile(LogFile, os.O_APPEND, 0666)
    } else {
        log.Println("log file not exist, creating...")
		file, err = os.Create(LogFile)
    }
    defer file.Close()
    ...
}

当文件已经存在的时候,调用 file, err = os.OpenFile(LogFile, O_APPEND, 0666) 语句打开的 file 变量在使用 file.WriteString(...) 方法的时候,会出现错误信息:

write ./img_url.txt: bad file descriptor

Solve

当第一次遇到该错误的时候,我还以为是上一次的线程使用完 img_url.txt 之后没有释放掉文件句柄,而导致第二次获取该文件句柄的时候出现的 bad file descriptor 错误。

于是我将打开该文件的变量作为 全局变量 进行写入操作,问题依然没有得到解决(因为改动太多,代码就不放出来了)

于是只能借助万能的 stack overflow 了。。。

解决办法 => Golang bad file descriptor

原来是我没有将写文件的 flag 也写进去…原来 O_APPEND 只是负责当用户有写入文件这个动作的时候,将文件指针指向文件结尾。

正确写法:

    if isExist(LogFile) {
        file, err = os.OpenFile(LogFile, os.O_WRONLY | os.O_APPEND, 0666)
    } else {
        log.Println("log file not exist, creating...")
		file, err = os.Create(LogFile)
    }

最后再好奇一件事情, os.O_WRONLY | os.O_APPEND 的结果是什么呢?

上代码:

func main() {
    fmt.Println(os.O_APPEND)  // 8
	fmt.Println(os.O_WRONLY)  // 1
	fmt.Println(os.O_RDWR)    // 2
	fmt.Println(os.O_APPEND | os.O_WRONLY) // 9
	fmt.Println(os.O_APPEND | os.O_RDWR)   // 10
}

最后翻定义,见 /usr/local/go/src/syscall/zerrors_darwin_amd64.go :

    ...
    O_APPEND = 0x8
    ...
    O_RDWR = 0x2
    ...
    O_WRONLY = 0x1
    ...

OVER

References

Update Mac OS Problem

Intro

最近将 Mac OS 升级到 Sierra(10.12) ,升级完成后,部分的 Go 应用无法正常启动了,下面说说我是怎么解决的。

Problem

直接给出当我运行 go-sqlite3 包时候的报错信息吧:

../github.com/mattn/go-sqlite3/sqlite3-binding.c:20515:17: warning: 'OSAtomicCompareAndSwapPtrBarrier' is deprecated: first deprecated in macOS 10.12 - Use atomic_compare_exchange_strong() from <stdatomic.h> instead [-Wdeprecated-declarations]
/usr/include/libkern/OSAtomicDeprecated.h:547:6: note: 'OSAtomicCompareAndSwapPtrBarrier' has been explicitly marked deprecated here

从报错的信息应该能猜想到是 go-sqlite3 包下的 sqlite3-binding.c 文件的依赖头文件不被支持了。

但是经过查看,该头文件的确存在而且应该是没有错误的,那么问题出现在哪呢?

Slove

后来我想是不是因为这个包我没有更新而出现的不兼容呢?于是我尝试了:

  • git pull
  • go install github.com/mattn/go-sqlite3
  • brew update # 因为我之前已经安装了,未安装的同学可以试试 $ brew install sqlite3

当我运行到 brew 命令时候发现, /usr/local/ 目录的权限在当前账户下无法进行写入?

猫腻出来了,接下来我输入 $ sudo chown -R $(whoami) /usr/local 更改权限,再次运行 go-sqlite3 ,应用正常了!

Last

因为更新系统之后,账户的很多权限都会被覆盖掉,所以不要怕,多多尝试给权限,说不定就好了呢!

P.S. 更新完系统之后 Git 不能正常使用的,报错信息:

xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), 
missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun

可以通过命令 xcode-select --install 下载 XCODE 的插件解决。

CircuitBreaker 设计模式

Intro

今天发现了 Sony 竟然在 Github 上开源了他们的一些项目!而他们也是在用 Golang 在开发后台!

Amazing

于是不亦乐乎地看起了其中的 Golang 开源项目,而其中一个名为 sony/gobreaker 的项目引起了我的注意。

项目简述中描述了这是一个 Golang 版本的 CircuitBreaker 实现!

那么什么是 CircuitBreaker(断路器) 呢?下面就来一起看看。

What is CircuitBreaker

根据传统的解释,断路器是广泛用于 电子工程产业 的一个重要安全保障!

当你家里的洗衣机漏电了,电流就会瞬间增大,那么连接家里总线的 断路器 就会剩下,及时切断总电源,防止意外的发生!

那么在最近的 微服务 越来越流行的时代,软件架构开始将 断路器 这一概念添加进来了。

我们知道,当你一旦开始将系统中的一部分拆解为一个独立服务,那么你就已经走进了 微服务 的时代了。 而在微服务中最重要的是要保证服务运行的稳定性,如果独立服务无法提供高质量或者是不能提供服务,那么这将会导致整个系统的崩溃!

微服务 会遇到的故障有可能是: - 瞬时故障:如慢的网络连接、超时,资源过度使用而暂时不可用; - 不容易预见的突发故障:需要更长时间来纠正的故障;

而解决这些故障常常有两种方法: - 重试机制:对于预期的短暂故障问题,通过重试模式是可以解决的; - 断路器(CircuitBreaker)模式:将受保护的服务封装在一个可以监控故障的断路器对象中,当故障达到一定门限,断路器将跳闸(trip),所有后继调用将不会发往受保护的服务而由断路器对象之间返回错误。对于需要更长时间解决的故障问题,不断重试就没有太大意义了,可以使用断路器模式。

CircuitBreaker - Sketch

CircuitBreaker - State

Action in gobreaker

简单介绍完 CircuitBreaker 的概念,那么接下来就结合 gobreaker 的源码实际看看如何设计一个 断触器。

首先一个值得我们关注的点是 CircuitBreaker State, 它被设计为 3 种状态:

type State int
const (
    StateClosed State = iota
    StateHalfOpen
    StateOpen
)

CircuitBreaker 会根据当前处于不同的 State ,而判断最多可以通过多少个 Request

接下来是 Setting ,通过 Setting 对象的值,可以新建出一个 CircuitBreaker :

type Settings struct {
	Name          string  // CircuitBreaker 的名字
	MaxRequests   uint32  // 最大连接数,根据 State 会自动调节允许通过的 Request 值
	Interval      time.Duration // 当 CircuitBreaker 处于 Close 状态的时候,循环该时间段,清空连接数
	Timeout       time.Duration // 当 CircuitBreaker 处于 Open 状态的时候,如果触发了该超时时间,将它置为 Half-Open
	ReadyToTrip   func(counts Counts) bool // 判断当前失败数,是否应该进入 Close 状态
	OnStateChange func(name string, from State, to State) // 当状态发生变化时候,触发该函数
}

func NewCircuitBreaker(st Settings) *CircuitBreaker {
    ...
}

因为这个源码实现其实非常简单,我也就不一一讲诉了,就再将一个比较重要的函数 Execute 吧:

func (cb *CircuitBreaker) Execute(req func() (interface{}, error)) (interface{}, error) {
	generation, err := cb.beforeRequest()
	if err != nil {
		return nil, err
	}

	defer func() {
		e := recover()
		if e != nil {
			cb.afterRequest(generation, fmt.Errorf("panic in request"))
			panic(e)
		}
	}()

	result, err := req()
	cb.afterRequest(generation, err)
	return result, err
}

该函数用于执行需要 CircuitBreaker 触发的函数,详情可以参考这里 => example

首先执行 CircuitBreaker 的 beforeRequest,然后执行传进来的 req 函数,最后执行 afterRequest ,并捕获异常,如果有异常, recover 它,不停止程序,返回错误信息。

参考网站

mryqu - blog

English

tcpdump in Action

Intro

昨天上线了新完成的网站 Personal-Dictionary 源码在此

但是上线期间却发生网站从 http://127.0.0.1:8563 可以访问,但是外网访问却显示 “无法访问此网络” 的情况,最后通过分析抓包工具 tcpdump 的结果解决了该问题。

Problem

这是一个 Beego 应用,按理来说只要编译出二进制文件,然后将静态文件和二进制文件发送到服务器端,然后运行该二进制文件即可。

编译 Linux OS 下可执行文件的命令为

$ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o PD main.go

编译完成之后, 通过 $ scp PD root@123.207.0.81:/home/... 将文件上传至服务器。

使用 $ nohup ./PD & 即可将该应用以后台服务的形式运行在服务器中。

但是问题出现了,通过外网的 IP 地址访问该 应用的端口地址却提示 “无法访问此网络” 的情况, 然后我使用 $ curl http://127.0.0.1:8563 却能直接返回网页内容!

Slove

为了解决这个问题,我最先想到的就是该端口是否被外网的中间件屏蔽了呢。有一个最直观的方法可以观察检查该情况,就是使用 tcpdump 抓包工具!

在服务器端启动应用之后,使用 $ tcpdump 网卡[eth0, lo] tcp port 8563 host 123.207.0.81

去截获访问该应用的端口 TCP 包,然后通过浏览器访问 http://123.207.0.81:8563 测试 TCP 包是否能正确到达 服务器的该端口。

结果 tcpdump 截获到的结果可以简化为

.... [S]
.... [R.]
.... [S]
.... [R.]
.... [S]
.... [R.]

很明显了: - S 代表着 SEND - R 代表着 RST

浏览器试图访问该端口,然后服务器直接就 RST 掉该请求,这样的情况下很可能就是该端口没有被打开!

关于更多的 RST 情况我推荐一篇 Blog —— costaxu

根据上面的情况,我很快就能定位到问题所在:

// main.go
func main() {
    beego.Run("127.0.0.1"+beego.AppConfig.String("httpport")) // 这里只指定了内网的服务地址...
}

最后,只需要将 beego.Run() 替换掉上面的即可解决。

Action in Protocol Buffers

Intro

如果要介绍 Protocol Buffers 那么首先要从 RPC 说起,而熟悉分布式的同学应该都知道 RPC 吧,我简单介绍一下:

PRC 也就是 Remote Procedure Call Protocol (远程调用协议),也就是说当有 A, B 两台服务器,A 服务器 中可以通过 RPC 调用 B服务器 中的函数。

在微服务和分布式变得越来越流行的今天,了解和学会 RPC 这个协议无疑是首要任务。

Protocol Buffers

那么 RPC 只是一个应用层协议,可以由不同的框架实现,而不同框架所支持的传输数据格式可以有很多:

  • JSON
  • Gob
  • MessagePack
  • Protocol Buffers …

那么可以说 Protocol Buffers 是这些所被支持的传输数据中比较高效而且通用的一种,其 官网 就列举出如:

  • C++
  • C#
  • Go
  • Java
  • Python

的使用指南,而更多的如 Ruby JavaNano 等也可以被支持。

Install in Go

在 Go 中,我们可以直接使用 protoc-gen-go 这个工具由 .proto 模版文件直接生成可用的 .go 文件。

安装方法:

  1. 首先需要下载 Protobuf 的编译器 protoc ,你可以下载一个对应系统版本的二进制版本,然后解压缩之后会在 bin 文件夹下找到编译好的可执行文件。

  2. 然后需要安装 protoc-gen-go 插件和编解码支持库。

# 插件安装
$ go get github.com/golang/protobuf/protoc-gen-go
$ cd $\
GOPATH/src/github.com/golang/protobuf/protoc-gen-go
$ go build && go install

# 编解码支持库
$ go get github.com/golang/protobuf/proto
$ cd $\
GOPATH/src/github.com/golang/golang/protobuf/proto
$ go build && go install

最后还需要将 $GOPAHTH/bin 加入环境变量

  1. 安装完成之后,可以在测试文件夹里先新建一个 .proto 文件,往里面写入:
syntax="proto2";
package xx // 该文件所属包名

enum FOO { X = 17; };

message Test {
    required string label = 1;
    optional int32 type = 2 [default=77];
    repeated int64 reps = 3;
    optional group OptionalGroup = 4 {
    required string RequiredField = 5;
    }
}

然后运行 protoc -I='*.proto文件所处的文件夹' --go_out=. *.proto 即可生成以下内容:

// Code generated by protoc-gen-go.
// source: example/human.proto
// DO NOT EDIT!

/*
Package example is a generated protocol buffer package.

It is generated from these files:
       	example/human.proto

It has these top-level messages:
       	Test
*/
package example

import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"

// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf

// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package

type FOO int32

const (
       	FOO_X FOO = 17
)

var FOO_name = map[int32]string{
       	17: "X",
}
var FOO_value = map[string]int32{
       	"X": 17,
}

func (x FOO) Enum() *FOO {
       	p := new(FOO)
       	*p = x
       	return p
}
func (x FOO) String() string {
       	return proto.EnumName(FOO_name, int32(x))
}
func (x *FOO) UnmarshalJSON(data []byte) error {
       	value, err := proto.UnmarshalJSONEnum(FOO_value, data, "FOO")
       	if err != nil {
       		return err
       	}
       	*x = FOO(value)
       	return nil
}
func (FOO) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }

type Test struct {
       	Label            *string             `protobuf:"bytes,1,req,name=label" json:"label,omitempty"`
       	Type             *int32              `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"`
       	Reps             []int64             `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"`
       	Optionalgroup    *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup,json=optionalgroup" json:"optionalgroup,omitempty"`
       	XXX_unrecognized []byte              `json:"-"`
}

func (m *Test) Reset()                    { *m = Test{} }
func (m *Test) String() string            { return proto.CompactTextString(m) }
func (*Test) ProtoMessage()               {}
func (*Test) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }

const Default_Test_Type int32 = 77

func (m *Test) GetLabel() string {
       	if m != nil && m.Label != nil {
       		return *m.Label
       	}
       	return ""
}

func (m *Test) GetType() int32 {
       	if m != nil && m.Type != nil {
       		return *m.Type
       	}
       	return Default_Test_Type
}

func (m *Test) GetReps() []int64 {
       	if m != nil {
       		return m.Reps
       	}
       	return nil
}

func (m *Test) GetOptionalgroup() *Test_OptionalGroup {
       	if m != nil {
       		return m.Optionalgroup
       	}
       	return nil
}

type Test_OptionalGroup struct {
       	RequiredField    *string `protobuf:"bytes,5,req,name=RequiredField,json=requiredField" json:"RequiredField,omitempty"`
       	XXX_unrecognized []byte  `json:"-"`
}

func (m *Test_OptionalGroup) Reset()                    { *m = Test_OptionalGroup{} }
func (m *Test_OptionalGroup) String() string            { return proto.CompactTextString(m) }
func (*Test_OptionalGroup) ProtoMessage()               {}
func (*Test_OptionalGroup) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 0} }

func (m *Test_OptionalGroup) GetRequiredField() string {
       	if m != nil && m.RequiredField != nil {
       		return *m.RequiredField
       	}
       	return ""
}

func init() {
       	proto.RegisterType((*Test)(nil), "example.Test")
       	proto.RegisterType((*Test_OptionalGroup)(nil), "example.Test.OptionalGroup")
       	proto.RegisterEnum("example.FOO", FOO_name, FOO_value)
}

func init() { proto.RegisterFile("example/human.proto", fileDescriptor0) }

var fileDescriptor0 = []byte{
       	// 200 bytes of a gzipped FileDescriptorProto
       	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0x4e, 0xad, 0x48, 0xcc,
       	0x2d, 0xc8, 0x49, 0xd5, 0xcf, 0x28, 0xcd, 0x4d, 0xcc, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17,
       	0x62, 0x87, 0x0a, 0x2a, 0x1d, 0x62, 0xe4, 0x62, 0x09, 0x49, 0x2d, 0x2e, 0x11, 0x12, 0xe1, 0x62,
       	0xcd, 0x49, 0x4c, 0x4a, 0xcd, 0x91, 0x60, 0x54, 0x60, 0xd2, 0xe0, 0x0c, 0x82, 0x70, 0x84, 0xc4,
       	0xb8, 0x58, 0x4a, 0x2a, 0x0b, 0x52, 0x25, 0x98, 0x14, 0x18, 0x35, 0x58, 0xad, 0x98, 0xcc, 0xcd,
       	0x83, 0xc0, 0x7c, 0x21, 0x21, 0x2e, 0x96, 0xa2, 0xd4, 0x82, 0x62, 0x09, 0x66, 0x05, 0x66, 0x0d,
       	0xe6, 0x20, 0x30, 0x5b, 0xc8, 0x91, 0x8b, 0x37, 0xbf, 0xa0, 0x24, 0x33, 0x3f, 0x2f, 0x31, 0x27,
       	0xbd, 0x28, 0xbf, 0xb4, 0x40, 0x82, 0x45, 0x81, 0x51, 0x83, 0xcb, 0x48, 0x5a, 0x0f, 0x6a, 0x97,
       	0x1e, 0xc8, 0x1e, 0x3d, 0x7f, 0xa8, 0x12, 0x77, 0x90, 0x92, 0x20, 0x54, 0x1d, 0x52, 0xa6, 0x5c,
       	0xbc, 0x28, 0xf2, 0x42, 0x2a, 0x5c, 0xbc, 0x41, 0xa9, 0x85, 0xa5, 0x99, 0x45, 0xa9, 0x29, 0x6e,
       	0x99, 0xa9, 0x39, 0x29, 0x12, 0xac, 0x60, 0xd7, 0xf1, 0x16, 0x21, 0x0b, 0x6a, 0xf1, 0x70, 0x31,
       	0xbb, 0xf9, 0xfb, 0x0b, 0xb1, 0x72, 0x31, 0x46, 0x08, 0x08, 0x02, 0x02, 0x00, 0x00, 0xff, 0xff,
       	0x89, 0x47, 0xa8, 0x4e, 0xf1, 0x00, 0x00, 0x00,
}

How to Use Captcha in Beego Correct

Intro

最近在做 Beego 的 Web 网站开发,主题是 个人词典 ,项目地址 点我 。 在关于 Beego 的验证码使用方法上出现了问题,后面通过阅读 Beego 的源码解决了该问题,下面来详细讲诉一下。

Problem

先来看看错误的代码吧

// ...
var cpt *captcha.Captcha

func (c *MainController) Login () {
    // ...
    // Get Verification Code.
    store = cache.NewMemoryCache()
    cpt = captcha.NewWithFilter("/captcha/", store)
	cpt.ChallengeNums, _ = beego.AppConfig.Int("captcha_length")
	cpt.StdWidth = 100
	cpt.StdHeight = 42

    c.TplName = "login.tpl"
}

这样写出现的问题是,开启服务器之后,只有第一次输入的验证码是正确的,一旦刷新页面或者已经登录之后,无论再次怎么进行登录都是 验证码不正确 的错误。 甚至在很多情况下都会出现验证码图片显示不出来。

Solve

其实很容易就可以想到是 Cache 模块而导致的问题,因为 Beego 的 Memory Cache 从源代码中看其实就是一个存放在内存的 Map ,而且该 Map 是带生存周期的。所以应该将 Cache 设置为一个全局变量,那么才能可以让 Captcha 每次都能到正确的内存地址中的 Map 存取数据。而不是每次访问都新建一次 Cache

正确的用法如下:

// ...
var cpt *captcha.Captcha
var store cache.Cache

func (c *MainController) Login () {
    // ...
    // Get Verification Code.
    cpt = captcha.NewWithFilter("/captcha/", store)
	cpt.ChallengeNums, _ = beego.AppConfig.Int("captcha_length")
	cpt.StdWidth = 100
	cpt.StdHeight = 42

    c.TplName = "login.tpl"
}

func init() {
    store = cache.NewMemoryCache()
}

这样,无论是怎么折腾该验证码, Captcha 都能到正确的 Map 中进行操作,一切的问题都解决了。

Fun in Docker Day-2

Intro

今天来详细讲一下之前没有讲清楚的 Docker 镜像打包方法。

Page Command

我们都知道,打包 Docker 应用有两种方式:

  1. 在已经存在的 images 中 commit 修改。
  2. 创建一个全新的 images 。

这两种方法各有优缺点。下面都来说一下怎么进行操作。

Way One

在已有的 images 中修改并 commit

  • 优点:
    • 这是最方便快捷的方法
    • 可以避免自己打包而导致出现的一些问题,如静态文件引用错误等
  • 缺点:
    • 可定制程度低
    • 打包出来的镜像文件可能会很大,不利于存储

在 Docker 官网的文档中已经很详细操作过了,下面的是我翻译的版本。具体的官方英文点我

首先你需要一个 images 才能进行更新操作呀,所以首先:

#   获取 images
$ docker pull training/sinatra
#   运行 images 并进入到命令行中
$ docker run -t -i training/sinatra /bin/bash
root@0b2616b0e5a8:/#

记住这个被创建容器的 ID, 0b2616b0e5a8 ,一会你会用得上的。

接上面的操作…

#   首先更新一下 Ruby
root@0b2616b0e5a8:/# apt-get install -y ruby2.0-dev
#   然后安装  gem  json
root@0b2616b0e5a8:/# gem2.0 install json

完成了这些更改之后,你可以运行 exit 命令退出。

现在你可以像使用 git 一样更新这个镜像了:

#   '-m' '-a' 这些看起来很熟悉啦,和 git 中是一样的,就不再说了...
$ docker commit -m "Added json gem" -a "Kate Smith" \
0b2616b0e5a8 ouruser/sinatra:v2

4f177bd27a9ff0f6dc2a830403925b5360bfe0b93d476f7fc3231110e7f71b1c

然后运行一下 docker images 来看看新创建的容器吧

$ docker images

REPOSITORY          TAG     IMAGE ID       CREATED       SIZE
training/sinatra    latest  5bc342fa0b91   10 hours ago  446.7 MB
ouruser/sinatra     v2      3c59e02ddd1a   10 hours ago  446.7 MB
ouruser/sinatra     latest  5db5f8471261   10 hours ago  446.7 MB

Way Two

完全创建一个新的 images 是很多人第一时间就想做的,但是官网简介中并没有太多详细标注的细节,那么我就以我的第一视角讲诉一下我是怎么创建一个全新的 images 的。

依然是 Go ,首先创建一个简单的读取文件内容的项目:

$ cd $GOPATH/src
$ mkdir File_Reader
$ vim File_Reader/main.go

源代码如下:

package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"os"
)

func main() {
	file, err := os.OpenFile("./file.txt", os.O_RDONLY, os.ModePerm)
	defer file.Close()

	if err != nil {
		log.Println("file.txt open false!")
	}

	fileReader := bufio.NewReader(file)

	for {
		line, err := fileReader.ReadString('\n')
		if err != nil {
			if err == io.EOF {
				break
			}
			log.Println("Read File Content Failed!", err.Error())
			return
		}
		fmt.Println(line)
	}

	fmt.Println("File Read Done!")
}

接下来就是在当前目录下创建一个 file.txt ,然后输入你想要的内容进去。

$ "xxxxxx" >> file.txt

接下来就需要先将源代码文件编译成二进制文件,因为如果我们希望这个 images 越小,越少的编译环境可以达到更好的效果。 命令如之前一样:

$ CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

然后你可以看到当前目录下出现了一个 main 的可执行文件。

重要的步骤来了,当前目录下创建一个 Dockerfile 文件,输入如下内容:

FROM scratch
MAINTAINER HackerZ
ADD main /
ADD file.txt /
CMD ["/main"]

我来解释一下这些都是什么意思:

  • FROM : Docker 用来指定该镜像是基于哪个基础镜像构建的
  • MAINTAINER : 镜像创建人的名字
  • ADD : 从 Dockerfile 所在目录拷贝文件到指定路径下
  • CMD : 用来指示当运行 docker run 命令运行该镜像时要执行的命令

其余的还有:

  • EXPOSE : 开放的网络端口号
  • ENV : 设置环境变量
  • VOLUME : 可以将本地文件夹或者其他容器的文件夹挂载到该容器中。
  • WORKDIR: 切换目录用,可以多次切换(相当于cd命令),对RUN,CMD,ENTRYPOINT生效
  • ONBUILD: ONBUILD 指定的命令在构建镜像时并不执行,而是在它的子镜像中执行

好了,运行 docker build -t fileReader . 创建全新的 images 吧。

Fun in Docker Day-1

Intro

今天心血来潮,想在 OSX 中重新体验一下 Docker,结果因为 Docker 是基于 Linux,在 OSX 中实在是 Fun 不起来,于是便纪录下来这天的过程。

Install

这部分没什么好说的, Docker 官方已经出了 OSX 的安装包,直接下载拖进 Application/ 即可完成安装。

安装完成之后,可以直接在命令行中运行:

$ docker --version  # Docker 主体
$ docker-compose --version  # 定义和管理复杂 Docker 应用的工具
$ docker-machine --version  # 简化 Docker 安装的工具

查看所有的安装工具是否能够正确启动。

之后,便可以尝试运行 Hello Worldnginx 玩一下了。

# Hello World
$ docker run hello-world

# nginx
$ docker run -d -p 80:80 --name webserver nginx

Package

以我的开源项目 getMeizi 为例子尝试打包 Golang应用

First Try

首先我在项目的根目录下编写了一个 Dockerfile 文件,其内容为:

FROM golang:onbuild

然后通过 $ docker build -t getmeizi . 来构建一个镜像。

但是这样构建的镜像会将 Golang 的整个环境都打包进去,生成的镜像大小为 832.5 MB

很显然我们会更加愿意得到一个更灵活小巧的镜像,于是,我的目光转向了 scratch

Second Try

修改 Dockerfile 文件内容为:

FROM scratch
ADD main /
CMD ["/main"]

然后先将 getMeizi 应用编译完:

$ CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

最后使用 $ docker build -t getmeizi .

构建即可生成一个仅有 5.83 MB 大小的镜像。

Push

最后将打包好的镜像发布到 Docker.io 中:

$ docker images
# REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
# getmeizi            latest              7ddcbed63a17        2 minutes ago         5.839 MB

$ docker tag 7ddcbed63a17 hackerz/getmeizi

$ docker images
# REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
# getmeizi            latest              7ddcbed63a17        3 minutes ago         5.839 MB
# hackerz/getmeizi    latest              7ddcbed63a17        3 minutes ago         5.839 MB

$ docker login
# ***
# Login Succeeded

$ docker push hackerz/getmeizi

打开 hackerz/getmeizi 查看并编辑镜像描述。

Global Ignore File in Git

Intro

自从换了 OSX 进行开发,就发现每当修改了项目文件, OSX 在项目目录都会生成一个 .DS_Store 的隐藏文件,该文件用于记录当前目录下文件的 Meta 信息。

对于这样的情况,我不可能在每个项目的根目录都配置一个 .gitignore 文件,这样可复用性太地了,于是我便想能不能配置一个 Git 的 .gitignore_global 文件,统一忽略掉所有我不需要上传的文件呢。

Solve

Git 还真有这样的方法,它提供了一个 忽略规则 ,我们可以通过编写一个忽略规则文件,然后通过如下的命令配置进 Git :

$ git config --global core.excludesfile '忽略文件完整路径'

即可。

Global GitIgnore

新建一个 .gitignore_global 文件,并往里面编写忽略文件语法,该语法符合正则表达式, # 号为注释,每一行为一个忽略规则:

# OSX
.DS_Store
.DS_Store*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# Python
*.pyc

# C
*.[ao]

# Package
*.7z
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip

How To Install Scikit-learn Under OSX 10.11

如何在osx下安装正确安装 scikit-learn

使用 Anaconda

Anaconda 是一个 Python 的集成科学计算环境,一键安装,方便好用。但是当我安装之后发现其只有自带了:

  • numpy
  • scipy

而并没有 scikit-learn,然后当我使用

conda install scikit-learn

进行安装之后发现还是无法使用这个缺省的库,经我多方查找资料,原来是 Mac 10.11 版本中自带的 Python 2.7 环境与 Anaconda 的环境是分离的,所以只能 Anaconda 中使用这些库。

然而这是我不太喜欢的一种方式,所以我放弃了,转而研究在 Mac 自带的 Python 环境中安装。

解除 OSX 10.11 的 `SIP` 限制

当我开始用 pip 安装第三方包的时候出现了如下的错误:

Collecting numpy

Using cached numpy-1.10.2-cp27-none-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whlInstalling
 collected packages: numpy
 Found existing installation: numpy 1.8.0rc1
 DEPRECATION: Uninstalling a distutils installed project (numpy) has been deprecated and will be removed in a future version. This is due to the fact that uninstalling a distutils project will only partially uninstall the project.
 Uninstalling numpy-1.8.0rc1:Exception:Traceback
 (most recent call last):
 File "/Library/Python/2.7/site-packages/pip-7.1.2-py2.7.egg/pip/basecommand.py", line 211, in main
 status = self.run(options, args)
 File "/Library/Python/2.7/site-packages/pip-7.1.2-py2.7.egg/pip/commands/install.py", line 311, in run
 root=options.root_path,
 File "/Library/Python/2.7/site-packages/pip-7.1.2-py2.7.egg/pip/req/req_set.py", line 640, in install
 requirement.uninstall(auto_confirm=True)
 File "/Library/Python/2.7/site-packages/pip-7.1.2-py2.7.egg/pip/req/req_install.py", line 716, in uninstall
 paths_to_remove.remove(auto_confirm)
 File "/Library/Python/2.7/site-packages/pip-7.1.2-py2.7.egg/pip/req/req_uninstall.py", line 125, in remove
 renames(path, new_path)
 File "/Library/Python/2.7/site-packages/pip-7.1.2-py2.7.egg/pip/utils/__init__.py",
 line 315, in renames
shutil.move(old, new)
 File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/shutil.py", line 302, in move
 copy2(src, real_dst)
 File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/shutil.py", line 131, in copy2
 copystat(src, dst)
 File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/shutil.py", line 103, in copystat
 os.chflags(dst, st.st_flags)OSError:
 [Errno 1] Operation not permitted: '/var/folders/5n/vbm997m56xg3kw67y6bccn2m0000gn/T/pip-4tcBsd-uninstall/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/numpy-1.8.0rc1-py2.7.egg-info'

仔细一看,发现了 Operation not permitted 的错误。

经过 Google ,发现了原来是 Apple 经历了 XCode编译器注入 事件之后,提升了 Mac OS X El Capitan系统 的安全保护机制,加入了:

System Integrity Protection (SIP) —— 系统完整性保护,其作用为强制性地保护系统相关的文件夹,开发者不能直接操作相关的文件内容。

而 Python 库所在的路径为:

/System/Library/Frameworks/Python.framework/Versions/2.7/...

当然是属于其完整性保护的保护伞之下的,所以我需要把 SIP 关掉之后才能对其进行安装。

引用外国大牛的关闭 SIP 的方法如下:

  1. Click the  menu.
  2. Select Restart
  3. Hold down command-R to boot into the Recovery System.
  4. Click the Utilities menu and select Terminal.
  5. Type csrutil disable and press return .
  6. Close the Terminal app.
  7. Click the  menu and select Restart … .

当然在安装完成之后最好还是将 SIP 重新打开:

  1. Click the  menu.
  2. Select Restart
  3. Hold down command-R to boot into the Recovery System.
  4. Click the Utilities menu and select Terminal.
  5. Type csrutil enable and press return .
  6. Close the Terminal app.
  7. Click the  menu and select Restart … .

安装 scikit-learn 的正确姿势

好吧,终于解决了上面这些问题了,安装应该没有问题了吧?很可惜,当我运行完:

$ sudo pip install numpy
$ sudo pip install scipy
$ sudo pip install scikit-learn

然后在 Pythonimport sklearn 时发生了如下错误:

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Library/Python/2.7/site-packages/sklearn/__init__.py", line 57, in <module>
from .base import clone
File "/Library/Python/2.7/site-packages/sklearn/base.py", line 11, in <module>
from .utils.fixes import signature
File "/Library/Python/2.7/site-packages/sklearn/utils/__init__.py", line 10, in <module>
from .murmurhash import murmurhash3_32
File "numpy.pxd", line 155, in init sklearn.utils.murmurhash (sklearn/utils/murmurhash.c:5029)
ValueError: numpy.dtype has the wrong size, try recompiling

好惨,经过多方搜索,绝大部分的解答都是:

$ pip uninstall numpy scipy scikit-learn
$ pip install numpy scipy scikit-learn

然而都并没有什么卵用,最后在这里找到了解决办法 => here

其方法时不要使用 pip install scikit-learn

而是到 Github 中找到 scikit-learn 项目进行安装:

$ git clone https://github.com/scikit-learn/scikit-learn.git
$ sudo python setup.py install

等待几分钟,安装完成,大功告成~

参考网站:

Golang Package Dependency Management Tool

Golang Package Dependency Management Tool

Intro

Golang一直以来被外界诟病的一个问题就是包的依赖管理问题。那么今天就来讲一个:

Golang包依赖管理工具 —— gb

gb 在其官网中定义自己为:

A project based build tool for the Go programming language.

一个Golang的项目工程通常由 binpkgsrc三个子目录构成:

  • bin : 存放编译后生成的可执行文件
  • pkg : 编译后生成的文件(如:.a)
  • src : 存放源代码(如:.go .c .h .s等)

gb 在这个概念的基础上新增了一个 vendor 目录来存放项目依赖的第三方包(如 beegogracehttp 等)

gb action

Install

gb ==> 首页 ==> Github

根据说明,使用

$ go get github.com/constabulary/gb/...

命令即可安装 gb。

当该命令运行完毕,请检查 env 下的第一个 $GOPATHbin 目录下是否生成了 gb 以及 gb-vendor 两个可执行文件。

如安装报错,请检查你是否正确配置了 $GOPATH 等环境变量。

Use

下面试着使用 gb 来构建一个基于第三方包 gracehttp 的简易 Golang Web 项目,来体验一下 gb 的魅力。

首先初始化 hellogb 项目目录结构:

$ cd $GOPATH/src/hellogb
$ mkdir -p src/hellogb
$ mkdir -p vendor/src

编写 Web 程序:

// vim src/hellogb/main.go
package main

import (
    "fmt"
    "net/http"

    "github.com/tabalt/gracehttp"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "hello gb")
    })

    err := gracehttp.ListenAndServe(":8080", nil)
    if err != nil {
        fmt.Println(err)
    }
}

使用 gb 添加依赖的 gracehttp 第三方包:

$ gb vendor fetch github.com/tabalt/gracehttp

最终整个项目目录结构为:

./
|-- src
|   `-- hellogb
|       `-- main.go
`-- vendor
    |-- manifest
    `-- src
        `-- github.com
            `-- tabalt
                `-- gracehttp
                    |-- README.md
                    |-- connection.go
                    |-- gracehttpdemo
                    |   `-- main.go
                    |-- listener.go
                    `-- server.go

编译执行程序:

$ gb build hellogb
$ ./bin/hellogb

最后访问 http://127.0.0.1:8080/ 即可访问 Web 服务。

Command

gb Command
Command 功能
build 编译包
vendor 调用 gb-vendor
doc 显示文档
env 打印项目的环境变量
generate 处理源代码生成Go文件
info 显示项目的信息
list 显示项目下的所有包
test 执行测试

gb vendor Parameter
Parameter 功能
fetch 获取一个远程依赖
update 更新一个本地依赖
list 每行一个列出所有依赖
delete 删除一个本地依赖
purge 清除所有未引用的依赖
restore 从manifest清单文件还原依赖

本文参考 tabalt 的 Golang包依赖管理工具gb 一文。

C Prefess Program Note

《C专家编程》 笔记本

《C专家编程》是每一位程序员应该读的第二本C语言书籍!

讲了各种c的缺陷,推荐go用户好好体验,很多都在go里做了修正 —— [ggarlic]()

书很好,翻译一般,校对不负责任。

  1. 新西兰 是关于时间编程的一个特殊地点;

  2. 编译器设计者的金科玉律 效率(几乎)就是一切

  3. B语言是 解释模式 语言,而C语言是 编译模式并引入了类型系统 ,使效率大大提高;

  4. C语言是为了 编译器设计者 而生的,这就是为什么很多编程语言在初期都是使用C语言编译器的原因;

  5. 为什么数组 下标从0开始

    • 在计算资源缺乏的过去,0标号的写法可以节省编译时间
    • 现代语言中0标号可以更优雅的表示数组字串
    • 在支持指针的语言中,标号被视作是偏移量,因此从0开始更符合逻辑
  6. 常量放在比较表达式的前面 可以大大减少因打字出错将 == 输入为 = 的情况 if(3 == x) {...}

  7. Bourne Shell 的出现促成了 The International Obfuscated C Code Competition(国际C语言混乱代码大赛);

  8. ANSI C 美国国家标准化组织所定下的C语言标准;

  9. 语言律师 —— “可以从200多页的手册中提炼出5句话,并起来放到你面前,你只要一看就能明白自己问题答案的人”;

  10. 关键字 const 并不能把变量变成常量!在一个符号前面加上 const 只是表示这个符号不能被赋值,也就是变为 Read Only 。其最有用之处在于限定函数的形参,这样该函数将不会修改实参指针所指的数据,但其他的(没有用 const 的)函数却有可能修改它。

  11. 尽量不要在你的程序中使用 无符号类型 ,以免增加不必要的复杂性。尤其是,不要仅仅因为其不存在负值(如年龄、 国债)而用它来表示数量。因为在某些情况下,会出现以下 BUG:

    • -1 会被翻译成非常巨大的正整数。
    • -1 会比 1 大。
  12. malloc(strlen(str)) 几乎永远是错误的,因为 不要忘记还有 '\0'

  13. NUL 用于结束有一个 ASCII 码零的正确术语; NULL 用于表示什么都不指向;

  14. 如果需要使用一些临时变量的时候,请把它放在块的开始处!

  15. 缺省采用 “Fall Through”,在 97% 的情况下都是错误的!

    // Fall Through : End without break;
    switch (number) {
    case 1: printf("case 1\n");
    case 2: printf("case 2\n");
    case 3: printf("case 3\n");
    ...
    }
    
  16. 一种简单的方法,使一段代码第一次执行时的行为与以后的执行的行为不同;

    generate_initializer(char * string) 
    {
    static char separator = ' ';
    printf("%c %s\n", separator, string);
    separator = ',';
    }
    
  17. 重载之过: ```c // 这是多少个乘号? p = N * sizeof * q; r = malloc(p); // 答案:1个,sizeof操作符将指针q指向的东西作为操作数,它返回q所指对象的类型的字节数

// 这是int的长度乘以p?还是把未知类型的指针强制转换为int? apple = sizeof(int) * p;


18. 什么是 **结合性** ?

> 在几个操作符具有相同优先级的时候决定先运行哪一个。

19. 为什么要使用 fgets() 而不是 gets() ?

20. 注释符缺陷:
```c
a //*
//*/ b

means a/b in C but a in C++

  1. 早用line,勤用lint。当你做错事的时候,他会告诉你哪里不对,应该始终使用lint程序,按照它的道德标准办事。像使用 go-lint 一样写出优秀的代码。

  2. 将结构的声明与变量的定义分开可以使代码更加容易阅读:

    struct veg { int weight, price_per_lb; };
    struct veg onion, radish, turnip;
    
  3. unionstruct 不同的是:

在内存布局中,struct 是将每个成员依次存储,而在 union 中,所有的成员都从偏移地址零开始存储。这样,每个成员的位置都重叠在一起:在某个时刻,只有一个成员真正存储于该地址。

所以 union 一般用于节省空间,因为 有些数据是不可能同时出现的,如果同时存储他们,显然颇为浪費。可以将互斥的两个字段存储于一个 union 中来节省空间:

union secondary_characteristics {
    char has_fur;
    short num_of_leg_in_excess_of_4;
};
struct creature {
    char has_backbone;
    union secondary_characteristics form;
};

这种方法在存储 2*10^7 只动物的时候可以节省 20MB 磁盘空间。

union 也可以将同一个数据解释成两个不同的东西:

union bits32_tag {
    int whole;  /* 一个32位的值 */
    struct {char c0, c1, c2, c3; } byte; /* 4个8位的字节 */
} value;
  1. enum 也就是 Golang 中的 toao

    enum sizes { small = 7, medium, large = 10, humungous };
    // medium = 8 ; humungous = 11;
    
  2. 理解C语言声明的优先级规则

    • A 声明从它的名字开始读取,然后按照优先级顺序依次读取。
    • B 优先级从高到低依次是:
      • 1 声明中被括号括起来的那部分
      • 2 后缀操作符:
        • 括号 () 表示这是一个函数;
        • 方括号 [] 表示这是一个数组;
      • 3 前缀操作符:星号 * 表示 “指向…的指针”
    • C 如果 const 和(或)volatie 关键字的后面紧跟类型说明符(如int,long等),那么它作用于类型说明符。在其他情况下,const 和(或)volatie关键字作用于它左边紧邻的指针星号。
char * const *(*next) ();

next 是一个指向函数的指针,该函数返回另一个指针,该指针指向一个只读的指向char的指针。

  1. 使字符串的比较看上去更自然:

strcmp() 函数用于比较两个字符串,当他们相等返回 0

// 这看起来有点不符合语法
if(!strcmp(s, "volatile")) return QUALIFIER;

// 也许我们可以这样做
#define STRCMP(a, R, b) (strcmp(a, b) R 0)
if(STRCMP(s, == , "volatile"))
  1. 数组与指针的区别: P102

redigo : open too many files

Redigo - panic error : open too many files.

Abstract

今天对 getAcFunPage 项目做 Benchmark 的时候发现了 Redis 会频繁报一个 socket: too many open files 的错误,后来发现并不是代码的问题,而是 Linux 的设置问题。 下面就来说说我是这么解决这个问题的。

Problem

Benchmark 时报错内容截取如下:

http: panic serving 127.0.0.1:53512: dial tcp :6379: socket: too many open files
goroutine 5322 [running]:
net/http.(*conn).serve.func1(0xc820f87f80)
    /usr/local/go/src/net/http/server.go:1389 +0xc1
panic(0x797240, 0xc820b12050)
    /usr/local/go/src/runtime/panic.go:426 +0x4e9
main.GetPageAndJSON(0x0, 0x0)
    /home/hackerzgz/workspace/golang/src/getAcFunPage/main.go:130 +0x20a
main.HandleGetResp(0x7f2103407500, 0xc8212fb450, 0xc8210a68c0)
    /home/hackerzgz/workspace/golang/src/getAcFunPage/main.go:82 +0x18
net/http.HandlerFunc.ServeHTTP(0x8902f0, 0x7f2103407500, 0xc8212fb450, 0xc8210a68c0)
    /usr/local/go/src/net/http/server.go:1618 +0x3a
net/http.(*ServeMux).ServeHTTP(0xc820015740, 0x7f2103407500, 0xc8212fb450, 0xc8210a68c0)
    /usr/local/go/src/net/http/server.go:1910 +0x17d
net/http.serverHandler.ServeHTTP(0xc82008a680, 0x7f2103407500, 0xc8212fb450, 0xc8210a68c0)
    /usr/local/go/src/net/http/server.go:2081 +0x19e
net/http.(*conn).serve(0xc820f87f80)
    /usr/local/go/src/net/http/server.go:1472 +0xf2e
created by net/http.(*Server).Serve
    /usr/local/go/src/net/http/server.go:2137 +0x44e

出现这个错误的时候, webbench 的参数为 -c 300 -t 60 ,也就是并发300个客户端访问并持续60s。

由报错信息第一行中的 dial tcp :6379 很容易看出,这是由 Redis 所引起的错误。

Why

根据 Stack Overflow 上的一个回答,这是由于 Linux 下设置的 文件描述符上限(file descriptors limit) 所引起的错误,在Ubuntu系统中,该值上限为 1024 ,于是当 Redis 需要接收来自高并发所带来的连接请求时,连接数很有可能 超出文件描述符的上限值 ,于是 Redis 就会报错了。

文件描述符:
内核(kernel)利用文件描述符(file descriptor)来访问文件。文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。

Solve

要解决这个问题也很简单,只需要将服务器系统的文件描述符上限修改成一个更大的值即可:

$ ulimit -n 99999

然后还需要对 Redigo 的连接池设置做出修改:

return &redis.Pool{
		MaxIdle:     64,
		IdleTimeout: 3 * time.Second,
		MaxActive:   99999, // max number of connections
		...
}

编译,测试。终于不再报错了。

Why HandleFunc Called Twice

Why HandleFunc() called twice?

Abstract

今天遇到之前碰见过的一个问题,但是之前忘记研究了,正好今天终于把这个问题弄清楚了,于是记录下来。

想必很多做后台的小伙伴都写过服务器了,但是有没有遇到服务器在 通过不同的(Brower、API)方式访问 的时候,服务器响应方法的 次数 是不一样的情况呢?

Problem

先来看看Golang中的简易服务器搭建代码:

func SayHello(rw http.ResponseWriter, req *http.Request) {
    io.WriteString(rw, "hello~ You are in!")
    log.Println("Oh, Here is a Guy coming in!")
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", SayHello)
    http.ListenAndServe(":8080", mux)
}

这是一个最简单的Golang服务器搭建,当http访问 http://localhost:8080 的时候,该服务器会对客户端返回 hello~ You are in! ,同时在服务器控制台中打印 Oh, Here is a Guy coming in!

有意思的部分来了:

通过 Brower 访问的时候,服务器控制台会打印出 两行 Oh, Here is a Guy coming in!

通过 curl http://localhost:8080 命令进行访问的时候,服务器控制台只会打印 一行 Oh, Here is a Guy coming in!

Why

为什么会出现那么有趣的问题呢?StackOver上也有人问了这个问题,原因出现在 Brower 上,通过打印 requsets,你会发现 Brower 还会发起二次请求去请求 /favicon.ico,也就是页面的小图标。

所以这就是用 CURL 发起请求的时候,并不会出现二次请求的原因!

Solve

既然知道了这个问题出现在哪,剩下就好办了,既然浏览器要请求图标,那么我们就在写一个路由专门处理这个请求即可:

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", SayHello)
    // Handle /favicon.ico
    mux.HandleFunc("/favicon.ico", func(rw http.ResponseWriter, req *http.Request) {})
    http.ListenAndServe(":9000", mux)
}

Golang Dev Log

Golang Dev Log


今天快要把 getAcFunPage 这个项目完结了,结果在重构项目的时候出现了两个哭笑不得的 BUG 。总结下来,都是因为自身对 Golang 认识不够深入而出现的问题,所以现在这篇 Blog 是专门记录我在Golang开发中遇到的需要注意的点,以此警醒自己!

  1. 一个通用的结构体应该以一个包的方式存在并进行引用,否则会出现同一个结构体在不同的包中声明之后,在调用的时候,编译器会报 cannot use xxx (type user) as type School.user 的错误。
// Example 1

// file main.go
type user struct {
    name     string
    age      int64
}

// file school.go
type user struct {
    name     string
    age      int64
}
  1. 如果一个函数需要使用一个 相对路径 调用一个 静态文件 ,那么需要将这个 静态文件 的路径作为参数进行传入。
    因为 Golang 中对于 静态文件 的调用不是根据函数所在位置的 相对路径 ,而是取决于调用这个函数的文件的位置所对应的相对路径。
// Example 2

// ./markdown/markdown-style.go (Wrong)
func GetStyle() {
    f, err := os.OpenFile("./markdown-style.css",...)
}

// ./markdown/markdown-style.go (Corrent)
func GetStyle(filepath string) {
    f, err := os.OpenFile(filepath,...)
}

// ./main.go
func main() {
    // Wrong:  The system cannot find the file specified.
    md.GetStyle()

    // Corrent
    md.Corrent("./markdown/markdown-style.css")
}

Redigo Action - 1

Redigo Action

Redis 作为一个内存型的高性能数据库,如今是越来越火了,为了得到更高的 QPS 以及 TPS ,我们无法忽视掉这个如此强大的数据库。

在 Redis 官网中,Golang语言的框架有两个是被官方所推荐的,分别为:

1. Redigo 2. Radix

本着源码易读优先,我选择了 Redigo 进行开发尝试,项目地址点我

Action

熟悉Redis的同学都知道,Redis是 单进程,单线程,IO多路复用 的,这一点不同于 MySQL 的多线程。 这就意味这Redis可以使用长连接来进行通信,那么,我们就需要一个连接池去管理这些长连接,当一个长连接使用完毕之后就可以交给下一个长连接继续进行使用。

连接池 基本思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。 而连接的建立、断开都由连接池自身来管理。同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等。 也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。

而 Redigo 就是支持连接池的,看看 Redigo - Pool. 其 L43 ~ L92 就给出了一个完整的 连接池 的正确打开方式。

func newPool(server, password string) *redis.Pool {
      return &redis.Pool{
          MaxIdle: 3,
          IdleTimeout: 240 * time.Second,
          Dial: func () (redis.Conn, error) {
            c, err := redis.Dial("tcp", server)
            if err != nil {
                return nil, err
            }
            if _, err := c.Do("AUTH", password); err != nil {
                c.Close()
                return nil, err
              }
               return c, err
          },
          TestOnBorrow: func(c redis.Conn, t time.Time) error {
              _, err := c.Do("PING")
              return err
          },
      }
  }

这段简单易懂的代码返回了一个可用的 Redis 连接池,为了能够进行长连接处理,我们还需要定义一个全局的 redis.Pool 变量进行使用。

  var (
      pool *redis.Pool
      redisServer = flag.String("redisServer", ":6379", "")
      redisPassword = flag.String("redisPassword", "", "")
  )

  func main() {
      flag.Parse()
      pool = newPool(*redisServer, *redisPassword)
      ...
  }

request请求 来到,我们就可以这样进行获取连接,并且一定记得在使用完毕之后将连接放回连接池。

   func serveHome(w http.ResponseWriter, r *http.Request) {
       conn := pool.Get()
       defer conn.Close()
       ....
   }
到了这里,一个可用并且高性能的 Redis 数据库的连接已经基本构建完毕了!
接下来就可以愉快地进行使用了~

Cold Start

About Two Different Cold Start

APP Cold Start


What’s App Cold Start

对于最近安装的App a,我们不可能得到该对于该App用户的特殊使用信息。尤其是打开该App的概率 P(a)。另一方面,对于给定的 feature(特征)的先验概率(指根据以往经验和分析得到的概率),可以从其他用户的信息中获取。

因此,对于最近安装的App,怎么估计其打开概率P(a)是至关重要的。

Yahoo’s Experimental Result

通过记录最近安装的App打开记录(活跃度),图5(Daily)以及图6(Hourly):

Days After Installation

图 5 日常App安装后的活跃度

Hours After Installation

图 6 每小时App安装后的活跃度

从图 5 和 6 中可以看到,最近安装的App一个显着的特点是在安装后的数小时内非常活跃。但是经过这段时间之后,最近安装的App 的活跃度显着减少。与此相反,一些一开始活跃度并不高的 App 经常在它们安装后的很长一段时间依然在使用。

To Solve App Cold Start

因此,为了更好地获取最近安装App打开频率,我们根据它们的活跃度持续时间长短,定义两种App类型,分别为:

  1. Short-term (活跃度持续时间短,在刚开始的一段时间活跃度很高)
  2. Long-term(活跃度持续时间长,在刚开始的一段时间活跃度不够高)

为捕抓每个App在时间上的突出显著性,我们将App使用数据转化为 Beta(α,β) 值,为了区分时间显著性,我们使用尖峰(excess kurtosis)δ来评价每个App的时间使用峰度: Excess Kurtosis Expression

通过尖峰值可以判断最近安装App的类型:

  • 一个高尖峰值的App意味着它越有可能是Short-term类App(Game)
  • 一个低尖峰值的App意味着它越有可能是Long-term类App(Communication)

Short-term类的App可以通过获取特定的特点的用户来获取平均打开频率。
Long-term类的App则可以通过获取所有用户的平均值。

随着用户打开App的事件增加,我们可以计算最近安装App的打开概率,因此,我们使用贝叶斯平均其他用户的历史信息来计算接下来的使用信息。计算最近安装App的打开概率公式如下:

App Cold Start Expression

通过计算公式,仅仅通过少量的用户打开App事件得到同一App的其他用户近似的非加权值的启动概率。用户打开App事件越多,该公式的准确率越高。


User Cold Start

What’s User Cold Start

在这一小节,我们提出两种方法解决User冷启动问题,什么是User冷启动呢? 当一个用户安装了一个Launcher软件(Aviate,Buzz,Go 等桌面软件),我们在不知道这个用户的任何信息下如何向该用户推荐App清单呢?这个就是User冷启动问题。

Two Ways to Solve

  1. 最相似用户策略: 当这个new User在安装了Launcher之后,我们可以在已知的用户集中找到跟他最相似的用户,并将这个用户的使用指标赋值给他。
    那么怎么去计算跟他最相似的用户呢,我们可以使用 Jaccard系数 进行计算,这个系数主要用来比较样本集中的相似性和分散性的一个概率。
    计算出用户之间的相似度,就可以将最相似用户的App清单进行推送。
    事实上,最相似的用户的App清单与新用户的清单还是有很大的不一样的,在极端条件下,也就是不涉及到敏感的用户信息条件下,他们之间的App清单相似度甚至不会超过一个 纯粹随机策略 (相当于“猴子排序”)。
    虽然这个策略提高了User冷启动的平均准确度,但是也限制了可生成用户建议数的范围。

  2. 伪用户策略: 通过生成“伪历史”(假的用户使用记录)可以解决用户冷启动问题,而且该策略可以作为新用户训练PTAN模型的一种方法。
    这个想法在于找到少量的相似用户,其App清单能够覆盖新用户的App清单。这是一个简单的 NP-Hard证明问题

P.S 1: NP-Hard问题也就是不能在限定的时间内计算出结果的问题,只能通过候选答案来验证这个答案是不是我们已知问题的一个答案

P.S 2关于这个问题为什么是一个NP-Hard问题,论文中没有给出解释,如果想了解怎么判断一个问题是不是NP-Hard问题,可以到 这里 查看解释,因为解释非常复杂,请允许我不复制粘贴上来

通过算法5,我们生成了伪用户数据: Build Pseudo User

Learning in Hadoop - Day 1

第一次玩Hadoop

最近在折腾机器学习,因为查阅到Yahoo的

《Predicting The Next App That You Are Going To Use》

这一篇Paper的时候,它提到了Google的 MapReduce以及 Word2Vec 。相信折腾过机器学习的小伙伴都会比较熟悉这两个东西了。于是,为了更深入地进行学习,我便无情地掉进了这个 里。

首先介绍一下背景,Yahoo的这篇Paper主要就是根据用户日常APP的使用习惯,然后对用户下一启动的APP进行预测。因为Yahoo认为日常手机的使用场景会对哪个APP的开启与否有着很强的关联性,于是他们便使用了 Word2Vec 对用户手机中记录的6个手机事件:

  1. Last Location Update
  2. Last Charge Cavle
  3. Last Audio Cable
  4. Last Context Trigger
  5. Last Context Pulled
  6. Last App Open

进行计算词向量,用于文本预测。而Word2Vec有着3个广为流传的版本:

  1. C
  2. Python
  3. Java

但是这3个版本对于Yahoo来说性能都是不足的,经我测试,一个800M的文本在C语言版本中计算时间需要20Min!

而在预测下一个APP这个场景里,这种计算速度是完全不可以接受的,于是Yahoo他们利用MapReduce重写了一个Word2Vec,将这个版本放在云端进行计算。这就是我进行MapReduce学习的原因。

Named Question in Golang

Golang中遇到的命名问题

昨天在写随机生成字符串代码时候遇到了一个Golang的命名问题,代码如下:

func GetRandomString(len string) string {
    str := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    bytes := []byte(str)
    result := []byte{}
    r := rand.New(rand.NewSource(time.Now().UnixNano()))

    for i := 0; i < len; i++ {
        result = append(result, bytes[r.Intn(len(str))]) // <- Here is the Problem: `cannot call non-function len (type int)`
    }
    return string(result)
}

这个问题太蛋疼了,之前好像从来没遇到过这个问题,而且Google好像也没有找到相关的问题原因。

后来一步步排查代码,才发现问题原来是出现在:

for i := 0; i < len; i++ {

中的 len 变量与函数 len() 重复而出现的命名错误,所以只需要将 len 变量重新命名即可解决该问题。

总结:在Golang中使用的变量一定不要和某个函数名字相同,否则不会通过,我现在暂时不清楚是Golang编译器出现的问题,还是Golang本来就不允许这样写,我会继续查阅相关文档查清楚!

The Channel in Golang

Golang中的Channel分析

作为Golang语言的核心,并发编程是学习Golang的必经之路。对于不同进程之间的通信手段总会涉及到跨进程通信,那么这个通信手段必须是一个可共享内存的方法,而Golang提倡的理念为:

“应该以通信作为手段来共享内存”

而这一句话的直接体现在于Golang所提供的一个预定义数据类型 —— Channel

Channel提供了一种机制。它既可以 同步 两个被并发执行的函数,又可以让这两个函数通过传递特定类型的值来进行 通信。使用Channel可以让我们编写更清晰且正确的代码。

关于使用Channel需要记住的知识点:

  • 在同一时刻,仅有一个Goroutine能向同一个Channel发送元素值,同时也只有一个Goroutine能从它哪里接收元素值。
  • Channel是一个FIFO的消息队列。
  • Channel中的元素值已经确保具有原子性。
  • Channel可以分为缓冲与非缓冲,它们之间的差别非常大。
  • Channel可分为双向与单向,一般通道都会声明为双向,只有在限制函数体中使用通道的方式(只允许发送或接收)才会使用单向Channel。

Talk is Cheap,Show me the Code!

  • 初始化通道

因为Channel属于引用类型之一,所以必须使用make关键字初始化它。

// 缓冲通道(容纳int类型元素,有长度,可暂存元素)
intChan := make(chan int, 5)

// 非缓冲通道(容纳byte类型元素,无长度,不可暂存元素)
byteChan := make(chan byte)
  • 发送元素值
// 向intChan通道发送一个元素值为5的元素
intChan <- 5

注意:向一个值为nil的Channel进行发送操作会造成当前Goroutine 永久阻塞!
而向一个已经塞满元素的Channel进行发送操作则会将当前的Goroutine 阻塞,直至Channel中的元素被接收,所以一般会在 select 代码块中进行发送操作。

  • 接收元素值
// 在intChan通道中接收一个元素值
elem := <-intChan

// 接收元素值,并判断该通道是否已经关闭
elem, ok := <-intChan
if !ok {
    fmt.Println("Channel is Closed!")
}

同样需要注意的是,如果向一个值为nil的Channel进行接收操作,同样会造成 永久阻塞!
而向一个没有元素值的Channel进行接收操作,也会将当前的Goroutine 阻塞,直至Channel中有了新的元素。

  • 关闭Channel

关闭Channel并不是如其字面意思,完全将Channel关闭。而其正确的作用是告诉系统,不应该再允许任何针对被关闭的通道的发送操作,该通道已经被关闭,但是已经缓存在Channel中的元素不会受到影响,这也是Channel非常优秀的特性之一。

// 调用内建函数close()关闭Channel
close(intChan)

注意,无论任何时候,我们都 不应该 在接收端关闭Channel,因为我们永远都不知道发送端是否已经将元素发送完毕。


最后,可以到这里学习更多的Channel相关代码!

socket

Socket Study


What is Socket

Socket(插座) 作为 UNIX的进程通信机制,一般被通称为

套接字

用于描述IP地址和端口,是一个通信链的句柄。

在Internet上的主机一般会运行多个服务软件,同时提供多个服务,每个服务都打开一个Socket,并绑定在一个端口上,不同端口对应不同的服务。


正如其英文意思 —— 插座,Socket在不同端口提供着不同的服务,互相之间互不干扰,你只需要将插头插入正确的插座,就可以得到你想要的服务了。

Socket通讯过程

Socket类似于电话插座,电话的通话双方相当于互相通信的2个进程,区号是它的网络地址;区内一个单位的交换机相当于一台主机,主机分配给每个用户的局内号码相当于socket号。

  • 任何用户在通话之前,首先要占有一部电话机,相当于申请一个socket;
  • 同时还要知道对方的号码,相当于对方有一个固定的的socket地址;
  • 然后向对方拨打电话,相当于发出连接请求(假如对方不在同一个区内,还要添加区号,相当于给出网络地址)
  • 这时候,如果对方在场并且空闲,相当于通信的另一个主机开机且可以接受连接请求;
  • 对方拿起电话,双方就可以正式通话,相当于连接成功
  • 双方通话的过程,是一方向电话机发出信号和对方电话机接收信号的过程,相当于向socket发送数据以及接收数据;
  • 通话结束后,一方挂起电话相当于关闭socket,撤销连接;

Socket 协议

Socket协议:(协议,本地地址,本地端口)
一个完整的socket有一个本地唯一的socket号,由操作系统进行分配 最重要的是,socket是面向客户/服务器模型而设计的,针对客户和服务器程序提供不同的socket系统调用:

  • 客户可以随机申请一个socket,相当于一个想打电话的人可以在任何一台入网电话上拨号呼叫,系统会为之分配一个socket号;
  • 但是服务器拥有的是全局公认的socket,任何客户都可以向它发出连接请求和信息请求,相当于一个被呼叫的电话拥有一个呼叫方知道的电话号码。

Socket利用客户/服务器模式巧妙地解决了通信之间建立通信连接的问题,服务器socket半相关被全局公认这一概念非常重要。


我们不妨考虑一下,两个完全随机的用户进程之间如何建立通信?假如通信双方没有任何一方的固定socket,就好比打电话的双方不知道彼此的电话号码,要通信是不可能的

Socket API

在Java中,Socket API: > java.net.Socket 继承于 java.lang.Object

它有八个构造器,方法并不多,下面介绍频繁使用的三个方法:

  • Accept

该方法用于产生“阻塞”,直到接收到一个连接,并且返回一个客户端的socket对象实例。

阻塞 —— 它使程序运行暂时“停留”在这个地方,直到会话产生,然后程序继续。通常的“阻塞”都是由循环产生的。

  • getInputStream

该方法获得网络连接输入,同时返回一个InputStream实例,通过该实例可以传输byte字节流。

  • getOutStream

该方法连接的是另一端得到输入,同时返回一个OnputSteam,该实例可以得到输入传输的byte字节流。

注意:其中 getInputStream 以及 getOutStream 方法均可能产生一个 IOException,它必须被捕获,通常都会被另一个流对象使用。

Socket连接过程

根据连接启动方式以及本地socket连接的目标,socket之间的连接过程可以分成三个步骤:

  • 服务器监听
  • 客户端请求
  • 连接确认

服务器监听:是服务器端的socket并不定位具体的客户端socket,而是处于等待连接的状态,实时监控网络状态。

客户端请求:是指客户端的socket提出连接请求,要连接的目标是服务器端的socket,为此,客户端socket必须要描述清楚它要连接的服务器socket,支出该服务器端socket的地址以及端口,然后就想服务器socket提出连接请求。

连接确认:是指当服务器端socket监听到或者说是接收到客户端socekt的连接请求,它就响应客户端socket的请求,建立一个新的进程,把服务器端socket的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端socket继续处于监听状态,继续等待其他客户端socket的连接请求。

Socket UDP && TCP

About Me

About Me

我是朱冠州,以HackerZ的名字混迹于网络中。于1994年8月出生于广东肇庆,迷迷茫茫直至高中开始喜欢上计算机,于是便选择了计算机科学与技术这门学科考上了北京理工大学珠海学院。平常我是一个喜欢拍照/看推理小说/撸代码的一个小小码农。

我的大学生活是从C语言开始的,一路上在 PHP/JAVA/Android/Node/Web 中迷失了自我,直至我遇上了Golang,我喜欢Go的那种优雅/简练的语法,更喜欢在Golang世界中的那种自由,所以我心甘情愿当一个Gopher。

我希望能成为一个能够改善我们身边生活/见证未来科技行业革新的一个创新者以及见证者

Contact Me

Technology stack

前端

  • HTML/CSS/JavaScript
  • JQuery
  • ReactJS

后端

  • PHP (ThinkPHP)
  • NodeJS (Express)
  • Golang (Beego/Hugo)

数据库

  • MySQL
  • MongoDB
  • Redis

移动端

  • Andorid

工具

  • Linux
  • Git

Future

对于未来,我希望能够在广东找到一份我喜爱的工作,能保持我对新技术和事物的爱好,并投身于Coding的最前线。

Qiniu Go SDK v7 Problem

下载七牛Go SDK v7遇到的问题

今天想要使用七牛的Go SDK时候遇到了肯定会出现的一个情况,那就是

$ go get -u qiniupkg.com/api.v7

命令出现了

golang.org/x/net/context

不能下载的问题,首先说一下为什么肯定会报错的问题,那就是Go官方将这个包的下载地址更改了(后来翻墙到Go官网发现的),但是不知道为什么go get命令还是将这个包的下载地址设置为原来的那个。 好吧,于是我试着使用七牛提供的方法下载了那个压缩包,并将其解压在

$GOPATH/src

目录下,再次运行go get 命令,这时候出现了

golang.org/x/net/content is not using a known version

的错误。 无奈之下,只有翻墙出去Go官方网站查看文档了,然后发现Go官方将这个net包放在了Github中,于是我在Github中下载下来这个包,然后在

$GOPATH/src

下,也就是Github.com文件夹的平级目录下手动创建了该路径

…/golang.org/x

,然后将net包放进去,再次运行

$ go get -u qiniupkg.com/api.v7

OK,成功了,接下来就可以开始愉快地玩耍了:)

本篇将教会你如何使用Hugo快捷地在Github中创建自己的Blog

安装Hugo


Hugo是一个使用Golang语言编写的静态Web站点生成框架,其是由Docker前员工Steve Francia进行编写的,因为其开源在Github里,所以安装非常方便,我们可以选择二进制安装包进行安装。安装完成之后可以运行以下命令查看是否正确安装:

$ hugo version

正确安装应该会出现如下信息:

Hugo Static Site Generator v0.14 BuildDate: 2015-05-26T09:29:16+08:00

接下来就可以愉快地开始Hugo之旅了。

创建Hugo项目


创建Hugo项目可以使用如下命令

$ hugo new site <site-name>

这样在该目录下就会出现这个项目文件夹了。 我们 cd 进入该目录,可以看到该目录下有一个名为:

config.toml

的文件,根据其名字很容易就知道这个就是Hugo的站点的配置文件了。 该文件中仅仅只有3行代码:

baseurl = “http://replace-this-with-your-hugo-site.com/" languageCode = “en-us”
title = “My New Hugo Site”

同样也是根据其单词我们也可以知道他们代表的是什么,我们可以对title进行一下修改,改为我们Blog的名字。

然后我们在该目录下运行命令:

$ hugo server

这个命令会将repo转换成静态html文件放入项目的public文件夹下,然后通过访问浏览器的

http://localhost:1313

地址,即可看到Hugo启动起来了。虽然现在站点是空白一片,但是通过添加Hugo主题,我们可以瞬间建立一个完整Blog站点。

选取Hugo主题


我们可以到Hugo官网选取自己喜欢的主题 下面我以Hyde主题为例,将该主题应用到自己的Blog中。

首先在站点的根目录下创建一个 themes 文件夹。

$ mkdir themes
$ cd themes
$ git clone https://github.com/spf13/hyde.git #下载对应主题

然后,我们需要对根目录下的

config.toml

进行配置,以应用下载下来的主题。

配置完成的文件如下所示:

baseurl = "http://replace-this-with-your-hugo-site.com/"
languageCode = "en-us"    
title = "HackerZ - Blog"    
theme = "hyde"   # 指定themes

[params]    
	description = "Welcome to my personal Blog"   # hyde主题的首页描述 
	themeColor = "theme-base-08"  # 指定hyde的主题颜色

这样,主题就算是配置好了,让我们再次运行

$ hugo server

看看效果吧!

新建文章


首页以及样式都已经有了,那么下面就来看看怎么新建一篇文章吧。

在站点项目下运行

$ hugo new welcome.md

即可看到在项目的content目录下被创建了一个 welcome.md 文件,该文件就是刚才新建出来的文章了。 我们可以往里面写点东西,注意,这是 markdown 格式的,hugo会将其编译成 html 格式放置在 public 目录下。

welcome.md

+++    
Categories = ["Development", "GoLang"]    
Description = ""    
Tags = ["Development", "golang"]    
date = "2016-03-29T14:38:19+08:00"    
menu = "main"    
title = "Welcome"    

+++

### 
这是使用Hugo创建的站点中的第一篇文章。

然后启动Hugo服务查看效果吧。

$ hugo server

到这里,基本的Hugo使用已经讲解完毕了,接下来就要将该静态站点迁移到我们自己的 github.io 中了。

使用Github Pages

要使用 github-pages 首先需要注册属于自己的 github 账号,注册完成之后,创建一个 repository,名为

<USERNAME>.github.io

这个是使用 Github Pages 的命名规定,如我自己就是

HackeZ.github.io

创建完成之后,在我们已经写好的Hugo站点下修改配置文件中的 baseurl属性:

config.toml

baseurl = "http://&lt;USERNAME>.github.io//"
# baseurl = "http://replace-this-with-your-hugo-site.com/"
# 原baseurl可以将其注释掉,之后本地可以进行调试

保存后,运行如下命令:

$ hugo -v

该命令会将配置文件中的参数进行静态文件编译,运行完之后,可以到public下的 index.html 中看看静态文件的地址是否是有误,如果有误,将不能正确地显示出主题样式。

一切正确之后,我们以public目录为 <USERNAME>.github.io 项目的主分支,将其 push 到 github仓库中,等待10分钟左右,访问属于你自己的Blog网站吧!

http://<USERNAME>.github.io

配置属于自己的Hugo主题

你们可以看到,我已经对原来的Hyde主题进行了修改了,那么是怎么做到的呢,我们可以直接对 themes/hyde 下的配置文件进行修改,增加自己想要的样式,相信聪明的你肯定可以很快熟悉 Hugo 的语法,创建属于自己的主题。

Welcome

这是使用Hugo创建的站点中的第一篇文章,我是HackerZ。

如果你想知道如何使用Hugo搭建这样一个Blog的话,可以看看这里