笔记本

FPM 应用打包神器

pkg

背景

应用程序写好了以后,需要分发给其他人,这时就需要控制启动脚本啊、配置管理啊,这些事情其实还是需要操作系统提供的rpm、deb、pkg来完成,但是要写rpm-build, dpkg-config。之前写过rpm打包过程,要有自动构建等操作,还需要对文件attr进行控制,各种细节都要写。 累都累死了。 包对安装者很方便,对写包的人太蛋疼,而这就是我们今天的主角要解决的问题。

fpm @Github

真不是php-fpm模块

fpm官方号称支持以下类型打包

太赞了!我的主机是Ubuntu(deb),那这次我们的目标就是Redhat的rpm好了!

安装fpm

首先安装fpm,这货依赖ruby的gem,所以

sudo apt-get install ruby ruby-dev rubygems gcc make
sudo gem install --no-ri --no-rdoc fpm

这些都安装好了以后,我们就可以开始打包了。

打包方式

推荐直接构建文件目录的方式,因为这样很简单。简单到你怎么安排文件目录结构,fpm就忠实地从根目录开始还原。例如下面这个结构

 $ mkdir test && cd test && mkdir -p usr/bin && mkdir -p var/log/dummy
 $ cd .. && tree test
.
├── usr
│   └── bin
└── var
    └── log
        └── dummy

执行

fpm -s dir  -C test

就是把test当成根目录/ , test/var 就会变成/var

简单吧!

目标包确定

我们这次选择的是rpm,所以-t (type)就指定rpm了。

fpm -s dir -C test -t rpm

接下来要确定包的名字,例如我们这次叫hi,所以-n (name) 就是 hi

$ fpm -s dir -C test -t rpm -n hi
Created package {:path=>"hi-1.0-1.x86_64.rpm"}

执行完了以后,就自动出了一个rpm包。 看看里面有什么

rpm -qlp hi-1.0-1.x86_64.rpm 
/usr/bin
/var/log/dummy

Bingo!这样就做好rpm包了。

高级用法

脚本

某些包需要执行一些脚本, 可以通过--after-install --before-install等参数指定.需要注意的是, fpm会自动读取并放在包中.

配置文件

rpm和deb都有自动配置文件管理(自动diff等功能)所以最好一开始就加上所有的配置文件. --config--files 就可以指定了. 不过,deb会默认/etc下的所有文件都是配置.

一些小tips

2016年终总结

工作

😂这个表情真的代表了我今年的心情,本来以为可以在Glu再多干一年的,结果4月份公司就解散了。加上之前答应老婆去深圳,于是就搬来深圳了。 刚到的那几天就开始面试,结果各个公司都问,为啥不去鹅厂试试,没想到,一面试,就真的进了鹅厂清水衙门TEG😂

6月份入的职,当时就震惊啦,朗科这边不比腾大差啊,超级海景办公室,配合着六月的阳光,那是相当的漂亮。

鹅厂大牛是多,问题也多。不过得到了离职那天再来评论评论。

博客

这里也开始长草了,不过新版的bla已经日趋完善了,希望明年能和川普一样能猛刷博客。

读书

自己在AI领域没有优势,今年只是积累了些基础,明年一定要好好在实战中啃下来。

给程序添加Prometheus监控

Demo

最近在编写Bla的时候,发现需要统计平均响应时间,CPU和内存之类的数据,在公司已经用过了metricbeat,感觉还好,但技术栈是Java的,而且内存要求太高了,所以只好换成Golang编写Prometheus做分析数据库。

Prometheus跟Golang官方标准库expvar是属于Pull的监控类型。需要服务器去拉取采集器上面的数据,因此,daemon类的程序(比如说server)最方便的地方就是直接bind一个接口暴露数据就可以了。

代码时间

go get github.com/prometheus/client_golang/prometheus

然后在自己的启动main.go里添加

func init() {

	prometheus.MustRegister(httpRequestCount)
	http.Handle("/metrics", promhttp.Handler())
	http.ListenAndServe(":8080", nil)

}

var (
	httpRequestCount = prometheus.NewCounter(prometheus.CounterOpts{
		Namespace: "http",
		Subsystem: "request",
		Name:      "requests_count",
		Help:      "The total number of http request",
	})
)

代码部分就完成了。 当你需要变更数值时,直接Inc()那个httpRequestCount就好了。

测试一下

curl http://localhost:8080/metrics

# HELP http_request_requests_count The total number of http request
# TYPE http_request_requests_count counter
http_request_requests_count 73

官方对于这些Namespace、Subsystem如何命名有详细的规定,具体的大家自己看就好了。

配置Prometheus

下载安装好Prometheus之后,将刚才我们暴露的metric接口配置进去

  - job_name: "bla"
    scrape_interval: "15s"
    static_configs:
    - targets: ['localhost:9200']

大功告成啦~~

3种优雅的Go channel用法

写Go的人应该都听过Rob Pike的这句话

Do not communicate by sharing memory; instead, share memory by communicating.

相信很多朋友和我一样,在实际应用中总感觉不到好处,为了用channel而用。但以我的切身体会来说,这是写代码时碰到的场景不复杂、对channel不熟悉导致的,所以希望这篇文章能给大家带来点新思路,对Golang优雅的channel有更深的认识 :)

Fan In/Out

数据的输出有时候需要做扇出/入(Fan In/Out),但是在函数中调用常常得修改接口,而且上下游对于数据的依赖程度非常高,所以一般使用通过channel进行Fan In/Out,这样就可以轻易实现类似于shell里的管道。

func fanIn(input1, input2 <-chan string) <-chan string {
   c := make(chan string)
   go func() {
       for {
           select {
           case s := <-input1:  c <- s
           case s := <-input2:  c <- s
           }
       }
   }()
   return c
}

同步Goroutine

两个goroutine之间同步状态,例如A goroutine需要让B goroutine退出,一般做法如下:

func main() {
   g = make(chan int)
   quit = make(chan bool)
   go B()
   for i := 0; i < 3; i++ {
       g <- i
   }
   quit <- true // 没办法等待B的退出只能Sleep
   fmt.Println("Main quit")
}

func B() {
   for {
       select {
       case i := <-g:
           fmt.Println(i + 1)
       case <-quit:
           fmt.Println("B quit")
           return
       }
   }
}

/*
Output:
1
2
3
Main quit
*/

可是了main函数没办法等待B合适地退出,所以B quit 没办法打印,程序直接退出了。然而,chan是Go里的第一对象,所以可以把chan传入chan中,所以上面的代码可以把quit 定义为chan chan bool,以此控制两个goroutine的同步

func main() {
   g = make(chan int)
   quit = make(chan chan bool)
   go B()
   for i := 0; i < 5; i++ {
       g <- i
   }
   wait := make(chan bool)
   quit <- wait
   <-wait //这样就可以等待B的退出了
   fmt.Println("Main Quit")
}

func B() {
   for {
       select {
       case i := <-g:
           fmt.Println(i + 1)
       case c := <-quit:
           c <- true
           fmt.Println("B Quit")
           return
       }
   }
}

/* Output
1
2
3
B Quit
Main Quit
*/

分布式递归调用

在现实生活中,如果你要找美国总统聊天,你会怎么做?第一步打电话给在美国的朋友,然后他们也会发动自己的关系网,再找可能认识美国总统的人,以此类推,直到找到为止。这在Kadmelia分布式系统中也是一样的,如果需要获取目标ID信息,那么就不停地查询,被查询节点就算没有相关信息,也会返回它觉得最近节点,直到找到ID或者等待超时。 好了,这个要用Go来实现怎么做呢?

func recursiveCall(ctx context.Context, id []byte, initialNodes []*node){
    seen := map[string]*node{} //已见过的节点记录
    request := make(chan *node, 3) //设置请求节点channel

        // 输入初始节点
    go func() {
        for _, n := range initialNodes {
            request <- n
        }
    }()

OUT:
    for {
               //循环直到找到数据
        if data != nil {
            return
        }
                // 在新的请求,超时和上层取消请求中select
        select {
        case n := <-request:
            go func() {
                                // 发送新的请求
                response := s.sendQuery(ctx, n, MethodFindValue, id)
                select {
                case <-ctx.Done():
                case msg :=<-response:
                                    seen[responseToNode(response)] = n //更新已见过的节点信息
                                                // 加载新的节点
                        for _, rn := range LoadNodeInfoFromByte(msg[PayLoadStart:]) {
                            mu.Lock()
                            _, ok := seen[rn.HexID()]
                            mu.Unlock()
                                                        // 见过了,跳过这个节点
                            if ok { 
                                continue
                            }
                            AddNode(rn)
                                                        // 将新的节点送入channel
                            request <- rn
                        }
                    }
                }
            }()
        case <-time.After(500 * time.Millisecond):
            break OUT // break至外层,否则仅仅是跳至loop外
            case <-ctx.Done():
            break OUT
        }
    }
    return
}

这时的buffered channel类似于一个局部queue,对需要的节点进行处理,但这段代码的精妙之处在于,这里的block操作是select的,随时可以取消,而不是要等待或者对queue的长度有认识。

你对这三种channel的用法有什么疑问,欢迎讨论╮(╯▽╰)╭

Golang与树莓派

最近买了个树莓派3b,本来是做下载机用的,但是发现在上面写Go代码,编译,其实和在一般机器上的体验是一样的。

不过树莓派本身有其他电脑没有的玩法,那就是GPIO的支持,配合Go-gpio库,就可以控制这些接口

下面是一个简单的跑马灯+CPU温度探测程序

因为没加散热片……所以温度有点高┐( ̄ヮ ̄)┌

代码如下,根据/sys下的温度文件读数值,另一个是根据负载改变闪烁的频率。

很简单,所以我就不加注释了:)至于为啥叫jurassic,因为侏罗纪公园的电网就是蓝橙指示灯,然后写代码的胖子就被吃掉了

package main

import (
        "fmt"
    "io/ioutil"
    "strconv"
    "time"
    "runtime"

        "github.com/stianeikeland/go-rpio"
    "github.com/shirou/gopsutil/load"
)

const (
    BLUE = 20
    ORANGE = 21
    CORE_TEMP_PATH = "/sys/class/thermal/thermal_zone0/temp"
)

func init(){
    runtime.GOMAXPROCS(1)
}

func main() {

    fmt.Printf("System initial...")
        if rpio.Open() == nil{
        fmt.Println("[OK]")
    } else {
        fmt.Println("[ERROR]")
    }
        defer rpio.Close()
        orange := rpio.Pin(ORANGE)
    blue    := rpio.Pin(BLUE) 
    orange.Output()
    blue.Output()
    orange.Low()
    blue.High()


    for {
        stat, err := load.Avg() 
        if err != nil {
            fmt.Println(err)
            break
        }
        interval := int(stat.Load1)
        if stat.Load1 < 1 {
            interval = 1    
        }
        fmt.Printf("Load1:%.2f Temp:%.2f'C", stat.Load1, loadTemp())
        time.Sleep(time.Millisecond * time.Duration(interval * 900))
        blue.Toggle()
        orange.Toggle()
        fmt.Printf("\r")
    }
}

func loadTemp() float64 {
    b, err := ioutil.ReadFile(CORE_TEMP_PATH)
    if err != nil {
        return -1000
    }
    raw, err := strconv.ParseFloat(string(b[:len(b)-2]), 64)
    if err != nil {
        fmt.Println(err)
        return -1001    
    }
    return raw/100
}