如何实现一个Java Class字节解析器

tinylcy 发表了文章 • 4 个评论 • 71 次浏览 • 1 天前 • 来自相关话题

最近在写一个私人项目,名字叫做SmallVMSmallVM的目的在于通过实现一个轻量级的Java虚拟机... 查看全部

最近在写一个私人项目,名字叫做SmallVMSmallVM的目的在于通过实现一个轻量级的Java虚拟机,加深对Java虚拟机的认知和理解。在Java虚拟机加载类的过程中,需要对Class文件进行解析,我曾经单独实现过一个Java版的Class字节解析器ClassAnalyzer,相比于Java版,新版(Golang版)更加健壮,思路也更加清晰。本文即阐述我实现Class字节解析器的思路。


Class文件


作为类或者接口信息的载体,每个Class文件都完整的定义了一个类。为了使Java程序可以“编写一次,处处运行”,Java虚拟机规范Class文件进行了严格的规定。构成Class文件的基本数据单位是字节,这些字节之间不存在任何分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据,单个字节无法表示的数据由多个连续的字节来表示。


根据Java虚拟机规范,Class文件采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:无符号数和表。Java虚拟机规范定义了u1u2u4u8来分别表示1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者是字符串。表是由多个无符号数或者其它表作为数据项构成的复合数据类型,表用于描述有层次关系的复合结构的数据,因此整个Class文件本质上就是一张表。在SmallVMu1u2u4u8分别对应于uint8uint16uint32uint64Class文件被描述为如下结构体。


type ClassFile struct {
magic uint32
minorVersion uint16
majorVersion uint16
constantPoolCount uint16
constantPool []constantpool.ConstantInfo
accessFlags uint16
thisClass uint16
superClass uint16
interfacesCount uint16
interfaces []uint16
fieldsCount uint16
fields []FieldInfo
methodsCount uint16
methods []MethodInfo
attributesCount uint16
attributes []attribute.AttributeInfo
}

type FieldInfo struct {
accessFlags uint16
nameIndex uint16
descriptorIndex uint16
attributesCount uint16
attributes []attribute.AttributeInfo
}

type MethodInfo struct {
accessFlags uint16
nameIndex uint16
descriptorIndex uint16
attributesCount uint16
attributes []attribute.AttributeInfo
}

如何解析


组成Class文件的各个数据项中,例如魔数、Class文件的版本、访问标志、类索引和父类索引等数据项,它们在每个Class文件中都占用固定数量的字节,在解析时只需要读取相应数量的字节。除此之外,需要灵活处理的主要包括4部分:常量池、字段表集合、方法表集合和属性表集合。字段和方法都可以具备自己的属性,Class本身也有相应的属性,因此,在解析字段表集合和方法表集合的同时也包含了属性表的解析。


常量池占据了Class文件很大一部分的数据,用于存储所有的常量信息,包括数字和字符串常量、类名、接口名、字段名和方法名等。Java虚拟机规范定义了多种常量类型,每一种常量类型都有自己的结构。常量池本身是一个表,在解析时有几点需要注意。



  • 每个常量类型都通过一个u1类型的tag来标识。

  • 表头给出的常量池大小(constantPoolCount)比实际大1,例如,如果constantPoolCount等于47,那么常量池中有46项常量。

  • 常量池的索引范围从1开始,例如,如果constantPoolCount等于47,那么常量池的索引范围为1~46。设计者将第0项空出来的目的是用于表达“不引用任何一个常量池项目”。

  • 如果一个CONSTANT_Long_infoCONSTANT_Double_info结构的项在常量池中的索引为n,则常量池中下一个有效的项的索引为n+2,此时常量池中索引为n+1的项有效但必须被认为不可用。

  • CONSTANT_Utf8_info型常量的结构中包含u1类型的tagu2类型的length和由lengthu1类型组成的bytes,这length字节的连续数据是一个使用MUTF-8Modified UTF-8)编码的字符串。MUTF-8UTF-8并不兼容,主要区别有两点:一是null字符会被编码成2字节(0xC00x80);二是补充字符是按照UTF-16拆分为代理对分别编码的,相关细节可以看这里(变种UTF-8)


属性表用于描述某些场景专有的信息,Class文件、字段表和方法表都有相应的属性表集合。Java虚拟机规范定义了多种属性,SmallVM目前实现了对常用属性的解析。和常量类型的数据项不同,属性并没有一个tag来标识属性的类型,但是每个属性都包含有一个u2类型的attribute_name_indexattribute_name_index指向常量池中的一个CONSTANT_Utf8_info类型的常量,该常量包含着属性的名称。在解析属性时,SmallVM正是通过attribute_name_index指向的常量对应的属性名称来得知属性的类型。


字段表用于描述类或者接口中声明的变量,字段包括类级变量以及实例级变量。字段表的结构包含一个u2类型的access_flags、一个u2类型的name_index、一个u2类型的descriptor_index、一个u2类型的attributes_countattributes_countattribute_info类型的attributes。我们已经介绍了属性表的解析,attributes的解析方式与属性表的解析方式一致。


Class的文件方法表采用了和字段表相同的存储格式,只是access_flags对应的含义有所不同。方法表包含着一个重要的属性:Code属性。Code属性存储了Java代码编译成的字节码指令,在SmallVM中,Code对应的结构体如下所示(仅列出了类属性)。


type Code struct {
pool []constantpool.ConstantInfo
attributeNameIndex uint16
attributeLength uint32
maxStack uint16
maxLocals uint16
codeLength uint32
code []byte
exceptionTableLength uint16
exceptionTable []ExceptionInfo
attributesCount uint16
attributes []AttributeInfo
}

type ExceptionInfo struct {
startPc uint16
endPc uint16
handlerPc uint16
catchType uint16
}

Code属性中,codeLengthcode分别用于存储字节码长度和字节码指令,每条指令即一个字节(u1类型)。在虚拟机执行时,通过读取code中的一个个字节码,并将字节码翻译成相应的指令。另外,虽然codeLength是一个u4类型的值,但是实际上一个方法不允许超过65535条字节码指令。


代码实现


整个Class字节解析器的源码已放在了GitHub上,字节解析器仅仅是SmallVM的一个小模块,对应的目录为src/classfile。另外,可以参考ClassAnalyzerREADME,我以一个类的Class文件为例,对该Class文件的每个字节进行了分析,希望对大家的理解有所帮助。

Go 語言的錯誤訊息處理

appleboy 发表了文章 • 1 个评论 • 133 次浏览 • 4 天前 • 来自相关话题

本文轉錄自: Go 語言的錯誤訊息處理

每個語言對於錯誤訊息的處理方式都不同,在學習每個語言時... 查看全部

本文轉錄自: Go 語言的錯誤訊息處理


每個語言對於錯誤訊息的處理方式都不同,在學習每個語言時,都要先學會如何在程式內處理錯誤訊息 (Error Handler),而在 Go 語言的錯誤處理是非常簡單,本篇會用簡單的範例教大家 Go 如何處理錯誤訊息。



Go 輸出錯誤訊息


在 Go 語言內有兩種方式讓函示 (function) 可以回傳錯誤訊息,一種是透過 errors 套件或 fmt 套件,先看看 errors 套件使用方式:


package main

import (
"errors"
"fmt"
)

func isEnable(enable bool) (bool, error) {
if enable {
return false, errors.New("You can't enable this setting")
}

return true, nil
}

func main() {
if _, err := isEnable(true); err != nil {
fmt.Println(err.Error())
}
}

請先引入 errors 套件,接著透過 errors.New("message here"),就可以實現 error 錯誤訊息。接著我們打開 errors package 原始碼來看看


package errors

// New returns an error that formats as the given text.
func New(text string) error {
return &errorString{text}
}

// errorString is a trivial implementation of error.
type errorString struct {
s string
}

func (e *errorString) Error() string {
return e.s
}

可以發現 errors 套件內提供了 New 函示,讓開發者可以直接建立 error 物件,並且實現了 error interface。在 Go 語言有定義 error interface 為:


type error interface {
Error() string
}

只要任何 stuct 有實作 Error() 接口,就可以變成 error 物件。這在下面的自訂錯誤訊息會在提到。除了上面使用 errors 套件外,還可以使用 fmt 套件,將上述程式碼改成:


func isEnable(enable bool) (bool, error) {
if enable {
return false, fmt.Errorf("You can't enable this setting")
}

return true, nil
}

這樣也可以成功輸出錯誤訊息,請深入看 fmt.Errorf


// Errorf formats according to a format specifier and returns the string
// as a value that satisfies error.
func Errorf(format string, a ...interface{}) error {
return errors.New(Sprintf(format, a...))
}

你可以發現在 fmt 套件內,引用了 errors 套件,所以基本上本質是一樣的。


Go 錯誤訊息測試


在 Go 語言如何測試錯誤訊息,直接引用 testing 套件


package error

import "testing"

func TestIsMyError(t *testing.T) {
ok, err := isEnable(true)

if ok {
t.Fatal("should be false")
}

if err.Error() != "You can't enable this setting" {
t.Fatal("message error")
}
}

另外 Go 語言最常用的測試套件 Testify,可以改寫如下:


package error

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestIsEnable(t *testing.T) {
ok, err := isEnable(true)
assert.False(t, ok)
assert.NotNil(t, err)
assert.Equal(t, "You can't enable this setting", err.Error())
}

Go 自訂錯誤訊息


從上面的例子可以看到,錯誤訊息都是固定的,如果我們要動態改動錯誤訊息,就必須帶變數進去。底下我們來看看如何實現自訂錯誤訊息:


package main

import (
"fmt"
)

// MyError is an error implementation that includes a time and message.
type MyError struct {
Title string
Message string
}

func (e MyError) Error() string {
return fmt.Sprintf("%v: %v", e.Title, e.Message)
}

func main() {
err := MyError{"Error Title 1", "Error Message 1"}
fmt.Println(err)

err = MyError{
Title: "Error Title 2",
Message: "Error Message 2",
}
fmt.Println(err)
}

也可以把錯誤訊息包成 Package 方式


package error

import (
"fmt"
)

// MyError is an error implementation that includes a time and message.
type MyError struct {
Title string
Message string
}

func (e MyError) Error() string {
return fmt.Sprintf("%v: %v", e.Title, e.Message)
}

main.go 就可以直接引用 error 套件


package main

import (
"fmt"

my "github.com/go-training/training/example04/error"
)

func main() {
err := my.MyError{"Error Title 1", "Error Message 1"}
fmt.Println(err)

err = my.MyError{
Title: "Error Title 2",
Message: "Error Message 2",
}
fmt.Println(err)
}

如何測試錯誤訊息是我們自己所定義的呢?請在 error 套件內加入底下測試函示


func IsMyError(err error) bool {
_, ok := err.(MyError)
return ok
}

由於我們實作了 error 接口,只要是 Interface 就可以透過 Type assertion 來判斷此錯誤訊息是否為 MyError


package error

import "testing"

func TestIsMyError(t *testing.T) {
err := MyError{"title", "message"}

ok := IsMyError(err)

if !ok {
t.Fatal("error is not MyError")
}

if err.Error() != "title: message" {
t.Fatal("message error")
}
}

這樣在專案裡就可以實現多個錯誤訊息,寫測試時就可以直接判斷錯誤訊息為哪一種自訂格式。


結論


在 Go 裡面寫錯誤訊息真的很方便又很容易,動態訊息請自定,反之,固定訊息請直接宣告 const 就可以了。在寫套件給大家使用時,大部份都是使用固定訊息,如果是大型專案牽扯到資料庫時,通常會用動態自訂錯誤訊息比較多。


上述程式碼請參考這裡

beego1.8版本功能征集

lkhjlbh 回复了问题 • 22 人关注 • 26 个回复 • 2267 次浏览 • 2017-03-22 17:58 • 来自相关话题

orm初探

elvin5 发表了文章 • 0 个评论 • 129 次浏览 • 2017-03-20 11:52 • 来自相关话题

只是研究一下orm是怎么事情而已矣

package main

import (
    "database/sql"
    "fmt"
    "log"
   ... 			查看全部
					

只是研究一下orm是怎么事情而已矣


package main

import (
"database/sql"
"fmt"
"log"
"reflect"
"strconv"

_ "github.com/go-sql-driver/mysql"
)

var db *sql.DB

func init() {
log.SetFlags(log.Lshortfile)
var err error
db, err = sql.Open("mysql", "root:password@tcp(127.0.0.1:3306)/test?charset=utf8mb4")
if nil != err {
log.Fatal(err)
}

db.SetMaxOpenConns(150)
db.SetMaxIdleConns(100)
err = db.Ping()
if nil != err {
log.Fatal(err)
}
}

func main() {
res, err := Query()
if nil != err {
log.Println(err)
return
}
var t T2
err = GetOne(res[0], &t)
if nil != err {
log.Println(err)
return
}
log.Printf("%+v",t)
}

type T2 struct {
Id int `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Ct string `json:"ct" db:"ct"`
UpdatedAt string `json:"updated_at" db:"updated_at"`
Bt byte `json:"bt" db:"bt"`
}

func Query() ([]map[string]string, error) {
rows, err := db.Query("SELECT * FROM t2")
if nil != err {
log.Println(err)
return nil, err
}

columns, _ := rows.Columns()
scanArgs := make([]interface{}, len(columns))
values := make([]interface{}, len(columns))
for j := range values {
scanArgs[j] = &values[j]
}

record := make([]map[string]string, 0)
for rows.Next() {
//将行数据保存到record字典
err = rows.Scan(scanArgs...)
m := map[string]string{}
for i, col := range values {
if col != nil {
m[columns[i]] = string(col.([]byte))
}
}

record = append(record, m)
}

fmt.Printf("%+v\n", record)

return record, nil
}

func GetOne(m map[string]string, v interface{}) error {
tp := reflect.TypeOf(v)
val := reflect.ValueOf(v)

length := tp.Elem().NumField()
for i := 0; i < length; i++ {
dbTag := tp.Elem().Field(i).Tag.Get("db")
if "" == dbTag || "-" == dbTag {
continue
}

val2, ok := m[dbTag]
if !ok {
continue
}

switch tp.Elem().Field(i).Type.Kind() {
case reflect.String:
val.Elem().Field(i).SetString(val2)
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16:
val3, err := strconv.ParseInt(val2, 10, 64)
if nil != err {
log.Println(err)
continue
}
val.Elem().Field(i).SetInt(val3)
case reflect.Float32, reflect.Float64:
case reflect.Bool:
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
default:
}
}

return nil
}

Go之旅-Switch

frankphper 发表了文章 • 0 个评论 • 121 次浏览 • 2017-03-15 22:51 • 来自相关话题

Go之旅-Switch

switch支持初始化语句,注意要用分号结束。后跟条件表达式,如果省略条件表达式,默认为true。不需要显示执行break语句,case执行完毕后自动终端。多个匹配条件,其中一个条件符合即可。case执行中断后,... 查看全部

Go之旅-Switch


switch支持初始化语句,注意要用分号结束。后跟条件表达式,如果省略条件表达式,默认为true。不需要显示执行break语句,case执行完毕后自动终端。多个匹配条件,其中一个条件符合即可。case执行中断后,如果需要继续执行下一个case块的内容,在下一个case块结尾执行fallthrough并且可以在fallthrough前使用break语句阻止。但不继续继续后续case块。


package main

import (
"fmt"
)

func main() {
// 简单声明几个变量
a, b, c, d := 1, 2, 3, 4
switch x := 2; x { // switch支持初始化语句,注意要用分号结束。后跟条件表达式,如果省略条件表达式,默认为true。
case a:
fmt.Println("a")
// break // 不需要显示执行break语句,case执行完毕后自动终端。
case a, b: // 多个匹配条件,其中一个条件符合即可。
fmt.Println("b")
fallthrough // case执行中断后,如果需要继续执行下一个case块的内容,在下一个case块结尾执行fallthrough并且可以在fallthrough前使用break语句阻止。但不继续继续后续case块。
case c:
fmt.Println("c")
case d:
fmt.Println("d")
case 5:
fmt.Println("e")
//case 5, 6: // 支持常量,但不能出现重复常量
// fmt.Println("f")
default:
fmt.Println("x") // 只有全部匹配失败后,才会执行default块。
}
}

第三届GopherChina大会正式启动了

hector 回复了问题 • 15 人关注 • 14 个回复 • 1228 次浏览 • 2017-03-13 15:03 • 来自相关话题

Go之旅-常量

frankphper 发表了文章 • 2 个评论 • 115 次浏览 • 2017-03-12 16:42 • 来自相关话题

Go之旅-常量

常量是指程序运行时不可改变的值,常量必须初始化值,定义常量可以指定类型,编译器也可以通过常量初始化值做类型推断。在函数代码块中定义常量,不被使用也不会出现编译错误。在常量组中如果不指定常量类型和初始化值,那么常量会和上一... 查看全部

Go之旅-常量


常量是指程序运行时不可改变的值,常量必须初始化值,定义常量可以指定类型,编译器也可以通过常量初始化值做类型推断。在函数代码块中定义常量,不被使用也不会出现编译错误。在常量组中如果不指定常量类型和初始化值,那么常量会和上一行的非空常量值相同。


// 声明包main
package main

// 导入包
import (
"fmt"
)

// 定义常量
const a = 10 // 必须赋值,可指定类型,也可以编译器通过初始化值类型推断
const b = "Hello World"
const c = false
const d, e = 1, 10

// 常量组
const (
f = true
g = 100
)

// 定义函数main
func main() {
// 函数块中定义的常量,不适用也不会出现编译错误
const (
h = 1
i // 在常量组中不指定常量类型和初始化值,会和上一行非空的常量值相同。
j
k
)
const g = "Hello World"
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d, e)
fmt.Println(f, g)
fmt.Println(i)
fmt.Println(j)
fmt.Println(k)
}

求推荐或分享go并发编程方面的资源。谢谢

yang11 回复了问题 • 6 人关注 • 2 个回复 • 323 次浏览 • 2017-03-09 21:34 • 来自相关话题

使用 Elastic Stack 来监控和调优 Golang 应用程序

astaxie 发表了文章 • 1 个评论 • 749 次浏览 • 2017-03-05 14:36 • 来自相关话题

Golang 因为其语法简单,上手快且方便部署正被越来越多的开发者所青睐,一个 Golang 程序开发好了之后,势必要关心其运行情况,今天在这里就给大家介绍一下如果使用 Elastic Stack 来分析 Golang 程序的内存使用情况,方便对 Gol... 查看全部

Golang 因为其语法简单,上手快且方便部署正被越来越多的开发者所青睐,一个 Golang 程序开发好了之后,势必要关心其运行情况,今天在这里就给大家介绍一下如果使用 Elastic Stack 来分析 Golang 程序的内存使用情况,方便对 Golang 程序做长期监控进而调优和诊断,甚至发现一些潜在的内存泄露等问题。


Elastic Stack 其实是一个集合,包含 Elasticsearch、Logstash 和 Beats 这几个开源软件,而 Beats 又包含 Filebeat、Packetbeat、Winlogbeat、Metricbeat 和新出的 Heartbeat,呵呵,有点多吧,恩,每个 beat 做的事情不一样,没关系,今天主要用到 Elasticsearch、Metricbeat 和 Kibana 就行了。


Metricbeat 是一个专门用来获取服务器或应用服务内部运行指标数据的收集程序,也是 Golang 写的,部署包比较小才10M 左右,对目标服务器的部署环境也没有依赖,内存资源占用和 CPU 开销也较小,目前除了可以监控服务器本身的资源使用情况外,还支持常见的应用服务器和服务,目前支持列表如下:



  • Apache Module

  • Couchbase Module

  • Docker Module

  • HAProxy Module

  • kafka Module

  • MongoDB Module

  • MySQL Module

  • Nginx Module

  • PostgreSQL Module

  • Prometheus Module

  • Redis Module

  • System Module

  • ZooKeeper Module


当然,也有可能你的应用不在上述列表,没关系,Metricbeat 是可以扩展的,你可以很方便的实现一个模块,而本文接下来所用的 Golang Module 也就是我刚刚为 Metricbeat 添加的扩展模块,目前已经 merge 进入 Metricbeat 的 master 分支,预计会在 6.0 版本发布,想了解是如何扩展这个模块的可以查看 代码路径 和 PR地址。


上面的这些可能还不够吸引人,我们来看一下 Kibana 对 Metricbeat 使用 Golang 模块收集的数据进行的可视化分析吧:



上面的图简单解读一下:
最上面一栏是 Golang Heap 的摘要信息,可以大致了解 Golang 的内存使用和 GC 情况,System 表示 Golang 程序从操作系统申请的内存,可以理解为进程所占的内存(注意不是进程对应的虚拟内存),Bytes allocated 表示 Heap 目前分配的内存,也就是 Golang 里面直接可使用的内存,GC limit 表示当 Golang 的 Heap 内存分配达到这个 limit 值之后就会开始执行 GC,这个值会随着每次 GC 而变化, GC cycles 则代表监控周期内的 GC 次数;


中间的三列分别是堆内存、进程内存和对象的统计情况;Heap Allocated 表示正在用和没有用但还未被回收的对象的大小;Heap Inuse 显然就是活跃的对象大小了;Heap Idle 表示已分配但空闲的内存;


底下两列是 GC 时间和 GC 次数的监控统计,CPUFraction 这个代表该进程 CPU 占用时间花在 GC 上面的百分比,值越大说明 GC 越频繁,浪费更多的时间在 GC 上面,上图虽然趋势陡峭,但是看范围在0.41%~0.52%之间,看起来还算可以,如果GC 比率占到个位数甚至更多比例,那肯定需要进一步优化程序了。


有了这些信息我们就能够知道该 Golang 的内存使用和分配情况和 GC 的执行情况,假如要分析是否有内存泄露,看内存使用和堆内存分配的趋势是否平稳就可以了,另外 GC_Limit 和 Byte Allocation 一直上升,那肯定就是有内存泄露了,结合历史信息还能对不同版本/提交对 Golang 的内存使用和 GC 影响进行分析。


接下来就要给大家介绍如何具体使用了,首先需要启用 Golang 的 expvar 服务,expvar(https://golang.org/pkg/expvar/) 是 Golang 提供的一个暴露内部变量或统计信息的标准包。
使用的方法很简单,只需要在 Golang 的程序引入该包即可,它会自动注册现有的 http 服务上,如下:


import _ "expvar"

如果 Golang 没有启动 http 服务,使用下面的方式启动一个即可,这里端口是 6060,如下:


func metricsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")

first := true
report := func(key string, value interface{}) {
if !first {
fmt.Fprintf(w, ",\n")
}
first = false
if str, ok := value.(string); ok {
fmt.Fprintf(w, "%q: %q", key, str)
} else {
fmt.Fprintf(w, "%q: %v", key, value)
}
}

fmt.Fprintf(w, "{\n")
monitoring.Do(monitoring.Full, report)
expvar.Do(func(kv expvar.KeyValue) {
report(kv.Key, kv.Value)
})
fmt.Fprintf(w, "\n}\n")
}

func main() {
mux := http.NewServeMux()
mux.HandleFunc("/debug/vars", metricsHandler)
endpoint := http.ListenAndServe("localhost:6060", mux)
}

默认注册的访问路径是/debug/vars, 编译启动之后,就可以通过 http://localhost:6060/debug/vars 来访问 expvar 以 JSON 格式暴露出来的这些内部变量,默认提供了 Golang 的 runtime.Memstats 信息,也就是上面分析的数据源,当然你还可以注册自己的变量,这里暂时不提。


OK,现在我们的 Golang 程序已经启动了,并且通过 expvar 暴露出了运行时的内存使用情况,现在我们需要使用 Metricbeat 来获取这些信息并存进 Elasticsearch。


关于 Metricbeat 的安装其实很简单,下载对应平台的包解压(下载地址:https://www.elastic.co/downloads/beats/metricbeat ),启动 Metricbeat 前,修改配置文件:metricbeat.yml


metricbeat.modules:
- module: golang
metricsets: ["heap"]
enabled: true
period: 10s
hosts: ["localhost:6060"]
heap.path: "/debug/vars"

上面的参数启用了 Golang 监控模块,并且会10秒获取一次配置路径的返回内存数据,我们同样配置该配置文件,设置数据输出到本机的 Elasticsearch:


output.elasticsearch:
hosts: ["localhost:9200"]

现在启动 Metricbeat:


./metricbeat -e -v

现在在 Elasticsearch 应该就有数据了,当然记得确保 Elasticsearch 和 Kibana 是可用状态,你可以在 Kibana 根据数据灵活自定义可视化,推荐使用 Timelion 来进行分析,当然为了方便也可以直接导入提供的样例仪表板,就是上面第一个图的效果。
关于如何导入样例仪表板请参照这个文档:https://www.elastic.co/guide/e ... .html


除了监控已经有的内存信息之外,如果你还有一些内部的业务指标想要暴露出来,也是可以的,通过 expvar 来做同样可以。一个简单的例子如下:


var inerInt int64 = 1024
pubInt := expvar.NewInt("your_metric_key")
pubInt.Set(inerInt)
pubInt.Add(2)

在 Metricbeat 内部也同样暴露了很多内部运行的信息,所以 Metricbeat 可以自己监控自己了。。。
首先,启动的时候带上参数设置pprof监控的地址,如下:


./metricbeat -httpprof="127.0.0.1:6060" -e -v

这样我们就能够通过 http://127.0.0.1:6060/debug/vars">http://127.0.0.1:6060/debug/vars]http://127.0.0.1:6060/debug/vars 访问到内部运行情况了,如下:


{
"output.events.acked": 1088,
"output.write.bytes": 1027455,
"output.write.errors": 0,
"output.messages.dropped": 0,
"output.elasticsearch.publishEvents.call.count": 24,
"output.elasticsearch.read.bytes": 12215,
"output.elasticsearch.read.errors": 0,
"output.elasticsearch.write.bytes": 1027455,
"output.elasticsearch.write.errors": 0,
"output.elasticsearch.events.acked": 1088,
"output.elasticsearch.events.not_acked": 0,
"output.kafka.events.acked": 0,
"output.kafka.events.not_acked": 0,
"output.kafka.publishEvents.call.count": 0,
"output.logstash.write.errors": 0,
"output.logstash.write.bytes": 0,
"output.logstash.events.acked": 0,
"output.logstash.events.not_acked": 0,
"output.logstash.publishEvents.call.count": 0,
"output.logstash.read.bytes": 0,
"output.logstash.read.errors": 0,
"output.redis.events.acked": 0,
"output.redis.events.not_acked": 0,
"output.redis.read.bytes": 0,
"output.redis.read.errors": 0,
"output.redis.write.bytes": 0,
"output.redis.write.errors": 0,
"beat.memstats.memory_total": 155721720,
"beat.memstats.memory_alloc": 3632728,
"beat.memstats.gc_next": 6052800,
"cmdline": ["./metricbeat","-httpprof=127.0.0.1:6060","-e","-v"],
"fetches": {"system-cpu": {"events": 4, "failures": 0, "success": 4}, "system-filesystem": {"events": 20, "failures": 0, "success": 4}, "system-fsstat": {"events": 4, "failures": 0, "success": 4}, "system-load": {"events": 4, "failures": 0, "success": 4}, "system-memory": {"events": 4, "failures": 0, "success": 4}, "system-network": {"events": 44, "failures": 0, "success": 4}, "system-process": {"events": 1008, "failures": 0, "success": 4}},
"libbeat.config.module.running": 0,
"libbeat.config.module.starts": 0,
"libbeat.config.module.stops": 0,
"libbeat.config.reloads": 0,
"memstats": {"Alloc":3637704,"TotalAlloc":155
... ...

比如,上面就能看到output模块Elasticsearch的处理情况,如 output.elasticsearch.events.acked 参数表示发送到 Elasticsearch Ack返回之后的消息。


现在我们要修改 Metricbeat 的配置文件,Golang 模块有两个 metricset,可以理解为两个监控的指标类型,我们现在需要加入一个新的 expvar 类型,这个即自定义的其他指标,相应配置文件修改如下:


- module: golang
metricsets: ["heap","expvar"]
enabled: true
period: 1s
hosts: ["localhost:6060"]
heap.path: "/debug/vars"
expvar:
namespace: "metricbeat"
path: "/debug/vars"

上面的一个参数 namespace 表示自定义指标的一个命令空间,主要是为了方便管理,这里是 Metricbeat 自身的信息,所以 namespace 就是 metricbeat。


重启 Metricbeat 应该就能收到新的数据了,我们前往 Kibana。


这里假设关注 output.elasticsearch.events.acked和
output.elasticsearch.events.not_acked这两个指标,我们在Kibana里面简单定义一个曲线图就能看到 Metricbeat 发往 Elasticsearch 消息的成功和失败趋势。
Timelion 表达式:


.es("metricbeat*",metric="max:golang.metricbeat.output.elasticsearch.events.acked").derivative().label("Elasticsearch Success"),.es("metricbeat*",metric="max:golang.metricbeat.output.elasticsearch.events.not_acked").derivative().label("Elasticsearch Failed")

效果如下:



从上图可以看到,发往 Elasticsearch 的消息很稳定,没有出现丢消息的情况,同时关于 Metricbeat 的内存情况,我们打开导入的 Dashboard 查看:



关于如何使用 Metricbeat 来监控 Golang 应用程序的内容基本上就差不多到这里了,上面介绍了如何基于 expvar 来监控 Golang 的内存情况和自定义业务监控指标,在结合 Elastic Stack 可以快速的进行分析,希望对大家有用。


最后,这个 Golang 模块目前还没 release,估计在 beats 6.0 发布,有兴趣尝鲜的可以自己下载源码打包。

五大最前沿的实战主题,与技术大咖面对面,2017 Gopher China 上海站来了!

astaxie 发表了文章 • 1 个评论 • 267 次浏览 • 2017-03-04 22:29 • 来自相关话题

2016年最火的编程语言是什么?那必须是Go 语言。

2016年,Go 语言上升了41位,从第54名晋升到13名,力压Dart和Perl语言,成为2016年Tiobe语言受欢迎指数排行榜的最大赢家!今年2月,Go 团队发布了 Go 1.... 查看全部

2016年最火的编程语言是什么?那必须是Go 语言。


2016年,Go 语言上升了41位,从第54名晋升到13名,力压Dart和Perl语言,成为2016年Tiobe语言受欢迎指数排行榜的最大赢家!今年2月,Go 团队发布了 Go 1.8 ,编译时间比 Go 1.7 提高了约 15%。技术发展进行时,技术大会怎么能落下?


这个4月 ,中国最权威和最实力干货的 Go 大会——Gopher China 大会来啦!!!致力于为中国广大的 Gopher 提供最好的技术大会,Gopher China 每年都会聚集一批大规模应用 Go 的示范企业分享经验。


4 月 15-16 日,2017 Gopher China 大会,诚邀每一个 Go 语言爱好者到魔都上海去!一同分享 Go 的最新内容和实践!


五大最前沿的实战主题,与Gopher大咖面对面


本次分享内容紧跟互联网发展趋势,涉及多个领域 Go 语言的实践经验。这次大会将呈现 Go 在微服务、大数据、金融、Go 内核等多个领域内的技术演讲。本次大会的讲师,来自于非常优秀的技术团队,带来最前沿的干货内容。
例如,来自谷歌云的 Francesc Campoy 讲师将为观众献上最前沿的 Go 技术内容,也有来自哔哩哔哩的技术总监毛剑深入分析 Go 语言在微服务领域的最深度实践。


从gocn官网 报名可以享受 75 折优惠:
优惠码 『gocn.io』


http://www.bagevent.com/event/357764

用docker-machine创建Docker Swarm集群

myml 发表了文章 • 0 个评论 • 120 次浏览 • 2017-03-02 17:57 • 来自相关话题

参考文档 Install and Create a Docker Swarm


安装


要先安装virtualboxDocker Machine,Docker Machine 是一个简化Docker安装的命令行工具,在非linux系统用docker的同学应该用过。


加速


dockerHub访问比较慢,docker-machine执行create时加上--engine-registry-mirror参数来进行加速,例如docker-machine create -d virtualbox --engine-registry-mirror=https://3cd767jz.mirror.aliyuncs.com local


获取Token


已有docker环境
执行docker run swarm create来从dockerHub获取一个全球唯一的token


没有docker环境
执行docker-machine create -d virtualbox local创建一个docker环境


执行eval $(docker-machine env local) 进入刚创建的local,


再执行docker run swarm create获取token,
很简单吧。machine还有其它一些实用功能,可以自行查看文档


创建master


执行docker-machine create -d virtualbox --swarm --swarm-master --swarm-discovery token://$token swarm-master
$token请替换成上一步骤拿到的token


创建节点


创建节点和创建master类似,只是把--swarm-master参数去掉,名字改下。


执行docker-machine create -d virtualbox --swarm --swarm-discovery token://$token swarm-node-0


再创建一个docker-machine create -d virtualbox --swarm --swarm-discovery token://$token swarm-node-1
执行docker-machine ls可以看到


swarm-master   * (swarm)   virtualbox   Running   tcp://192.168.99.100:2376   swarm-master (master)   v17.03.0-ce   
swarm-node-0 - virtualbox Running tcp://192.168.99.101:2376 swarm-master v17.03.0-ce
swarm-node-0 - virtualbox Running tcp://192.168.99.102:2376 swarm-master v17.03.0-ce

执行eval $(docker-machine env --swarm swarm-master) !注意这里加上了--swarm参数,进入master,执行docker info可以看到集群信息

Go1.8将会在net/http内置graceful

FancyGo 回复了问题 • 5 人关注 • 1 个回复 • 2040 次浏览 • 2017-02-23 11:57 • 来自相关话题

Go 1.8新功能声明(英文ppt版)

soul008 发表了文章 • 0 个评论 • 323 次浏览 • 2017-02-17 15:47 • 来自相关话题

Go 1.8新功能

这里写图片描述 ... 			<a class=查看全部

Go 1.8新功能


这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述


这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述


这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述


这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

golang实时消息平台NSQ的使用

changjixiong 发表了文章 • 6 个评论 • 468 次浏览 • 2017-02-13 19:55 • 来自相关话题

NSQ是什么

(本文作者 changjixiong,以下是正文)

NSQ是一个实时消息平台,引用一段InfoQ上的介绍:

“NSQ是一... 			查看全部
					

NSQ是什么


(本文作者 changjixiong,以下是正文)


NSQ是一个实时消息平台,引用一段InfoQ上的介绍:


“NSQ是一个基于Go语言的分布式实时消息平台,它基于MIT开源协议发布,代码托管在GitHub。NSQ可用于大规模系统中的实时消息服务,并且每天能够处理数亿级别的消息,其设计目标是为在分布式环境下运行的去中心化服务提供一个强大的基础架构。NSQ具有分布式、去中心化的拓扑结构,该结构具有无单点故障、故障容错、高可用性以及能够保证消息的可靠传递的特征。NSQ非常容易配置和部署,且具有最大的灵活性,支持众多消息协议。”

如何开始使用


这里有一个例子用来说明如何安装、启动以及发送与接收消息:
An Example of Using NSQ From Go(地址:http://tleyden.github.io/blog/2014/11/12/an-example-of-using-nsq-from-go/)


构建消息的响应函数


如果单是用一个匿名函数来处理收到的消息显然是不够的,下面用代码来演示一下如果根据收到的消息来使用相应的处理函数。


生产者


首先我们来创建生产者


config := nsq.NewConfig()
w, _ := nsq.NewProducer("127.0.0.1:4150", config)

jsonData := []string{}
jsonData = append(jsonData, `
{
"func_name":"BarFuncAdd",
"params":[0.5,0.51]
}`)
jsonData = append(jsonData, `
{
"func_name":"FooFuncSwap",
"params":["a","b"]
}`)

for _, j := range jsonData {
w.Publish("Topic_json", []byte(j))
}

上面的代码向NSQ发送了2个json格式的消息,从字面上不难看出其目的是调用2个函数,分别是BarFuncAdd和FooFuncSwap。


消费者


现在我们来创建消费者


config := nsq.NewConfig()
config.DefaultRequeueDelay = 0
config.MaxBackoffDuration = 20 * time.Millisecond
config.LookupdPollInterval = 1000 * time.Millisecond
config.RDYRedistributeInterval = 1000 * time.Millisecond
config.MaxInFlight = 2500

MakeConsumer("Topic_json", "ch", config, HandleJsonMessage)

MakeConsumer的定义如下:


func MakeConsumer(topic, channel string, config *nsq.Config,
handle func(message *nsq.Message) error) {
consumer, _ := nsq.NewConsumer(topic, channel, config)
consumer.AddHandler(nsq.HandlerFunc(handle))
err := consumer.ConnectToNSQD("127.0.0.1:4150")
if err != nil {
log.Panic("Could not connect")
}
}

处理器函数


NSQ消息的处理器函数定义如下:


func HandleJsonMessage(message *nsq.Message) error {

resultJson := reflectinvoke.InvokeByJson([]byte(message.Body))
result := reflectinvoke.Response{}
err := json.Unmarshal(resultJson, &result)
if err != nil {
return err
}
info := "HandleJsonMessage get a result\n"
info += "raw:\n" + string(resultJson) + "\n"
info += "function: " + result.FuncName + " \n"
info += fmt.Sprintf("result: %v\n", result.Data)
info += fmt.Sprintf("error: %d,%s\n\n", result.ErrorCode,
reflectinvoke.ErrorMsg(result.ErrorCode))

fmt.Println(info)

return nil
}

功能函数


处理器函数根据收到的json数据通过反射最终调用了Foo的FooFuncSwap方法及Bar的BarFuncAdd方法。


type Foo struct {
}

type Bar struct {
}

func (b *Bar) BarFuncAdd(argOne, argTwo float64) float64 {

return argOne + argTwo
}

func (f *Foo) FooFuncSwap(argOne, argTwo string) (string, string) {

return argTwo, argOne
}

怎么调用的


reflectinvoke.InvokeByJson是如何根据形如:


{
"func_name":"BarFuncAdd",
"params":[0.5,0.51]
}

的 json数据调用Bar.BarFuncAdd的?
请参考《golang通过反射使用json字符串调用struct的指定方法及返回json结果》(如果前面这段没有连接地址,那肯定是文章被爬虫干掉了连接,请找本文的原文阅读)


文中代码的完整内容在https://github.com/changjixiong/goNotes/tree/master/nsqNotes以及https://github.com/changjixiong/goNotes/tree/master/reflectinvoke中。


注意事项


同一个消息channel如果有多个消费者则消费者收到的消息是不确定的。例如,如果将文中的生产者运行一个实例,将消费者运行两个实例(命名为A,B),则会出现A收到2个消息或者B收到2个消息或者AB各收到一个消息。