GOLANG如何避免字符串转义

winlin 发表了文章 • 0 个评论 • 74 次浏览 • 1 天前 • 来自相关话题

避免转义字符,例如造个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 个评论 • 70 次浏览 • 1 天前 • 来自相关话题

考虑测试一个函数:

func request(ctx context.Context, hc *http.Client, api string) (err error... 			查看全部
					

考虑测试一个函数:


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 个评论 • 143 次浏览 • 2 天前 • 来自相关话题

GOLANG测试时,可以用匿名对象填充测试序列,但是如果没有带堆栈的errors,那么会造成出现错误时无法排查。

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

先看测试序列的填充,参考查看全部

GOLANG测试时,可以用匿名对象填充测试序列,但是如果没有带堆栈的errors,那么会造成出现错误时无法排查。


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


先看测试序列的填充,参考tricks-2015这个文章,怕你翻不了墙,我把内容粘贴过来就是:


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

怎么办呢?把堆栈信息带上,参考错误最佳实践,改成这样:


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

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

beego访问redis

yuyifeichina 回复了问题 • 2 人关注 • 3 个回复 • 270 次浏览 • 2017-06-12 16:10 • 来自相关话题

GOLANG最容易做测试MOCK

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

测试时,一些底层的库非常难以MOCK,比如HASH摘要算法,怎么MOCK?假设有个函数,是用MD5做摘要:

func digest(data []byte, h ha... 			查看全部
					

测试时,一些底层的库非常难以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,所有函数和数据都可以访问。

  2. 有些函数非常难以出错,但是不代表不出错,比如这里的Write方法,一般都是不会有问题的,但是测试如果覆盖不到,保不齐哪天跑到这一行就挂掉了。

  3. MOCK桩对象或者函数,如果总是要把目标全部实现一遍,比如hash这个接口有5个方法,对Write打桩时只需要覆盖这个函数,其他的可以不动。是的,聪明的你可能会想到继承,但是如果这个类是隐藏的呢?比如一个md5的实现是隐藏不能访问的,暴露的只有hash的接口,怎么从md5这个类继承呢?GOLANG提供了类似从实现了接口对象的接口继承的方式,实际上是组合,具体看下面的实现。

  4. 有些古怪的逻辑,比如这里判断摘要是16字节,一般情况下也不会出现错误,当然utest也必须得覆盖到,万一哪天用了一个hash算法跑到这个地方,不能出现问题。



Remark: 注意到这个地方用了一个errors的package,它可以打印出问题出现的堆栈,参考Error最佳实践.



用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最佳实践。比如打印信息:


--- 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 个评论 • 479 次浏览 • 2017-06-07 17:04 • 来自相关话题

错误处理这个文章中,tkk提出了空指针时堆栈消失的问题,看下面的查看全部

错误处理这个文章中,tkk提出了空指针时堆栈消失的问题,看下面的例子


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打开,变成这样


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,马上就有神回复了:


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"

  2. 可能在GO1.9会解决这个问题。


结贴。

很喜欢这个论坛,清爽简洁

tpkeeper 发表了文章 • 0 个评论 • 163 次浏览 • 2017-06-07 10:08 • 来自相关话题

以后有啥文章,就在这里发了

以后有啥文章,就在这里发了

实际项目中大家都用哪个rpc的框架,说说有没有遇到哪些个坑

tupunco 回复了问题 • 5 人关注 • 3 个回复 • 328 次浏览 • 2017-06-07 09:29 • 来自相关话题

golang 操作hbase集群

mintzhao 回复了问题 • 2 人关注 • 1 个回复 • 156 次浏览 • 2017-06-06 15:03 • 来自相关话题

GOLANG错误处理最佳方案

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

GOLANG的错误很简单的,用error接口,参考golang error handling:

查看全部
					

GOLANG的错误很简单的,用error接口,参考golang error handling:


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,它提供了一个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.Newerrors.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):


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打印出。

  2. invalid object 0x00,由ASC打印出。

  3. 完整的堆栈,包含main/run/aac.Decode/asc.Decode


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


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


对于其他的语言,比如多线程程序,也可以用类似方法,返回int错误码,但是把上下文信息保存到线程的信息中,清理线程时也清理这个信息。对于协程也是一样的,例如ST的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 trdint rerrno,然后new和wrap时赋值就好了。

大家写go的时候用什么样的思想去写呢

sheepbao 回复了问题 • 7 人关注 • 4 个回复 • 385 次浏览 • 2017-05-28 11:37 • 来自相关话题

GOLANG环境设置

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

原文:http://blog.csdn.net/win_lin/article/details/48265493


GO环境


官网下载GOLANG的安装:golang.org


如果不能翻墙,可以从golangtc或者gocn.io下载。


下载时,如何选择安装包:



  1. MAC,下载包含darwin的pkg或tar(推荐PKG),pkg可以直接安装但是卸载比较麻烦,tar需要自己设置PATH安装麻烦卸载比较方便,例如go1.8.1.darwin-amd64.pkggo1.8.1.darwin-amd64.tar.gz

  2. Windows,一般就是安装包了,设置GOPATH比较麻烦,根据自己的系统是32位还是64位下载,譬如go1.8.1.windows-amd64.msi或者go1.8.1.windows-386.msi

  3. Linux,一般都是64位的,而且需要手动解压和设置GOPATH,当然还有ARM的,可以选择自己需要的下载,譬如go1.8.1.linux-amd64.tar.gz


如果是安装包,双击就可以安装,可以跳过下面这步。如果是linux这种需要自己解压和设置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

UNIX设置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的环境就配置成功了。

有人在用go-micro吗

chrislee 回复了问题 • 6 人关注 • 2 个回复 • 441 次浏览 • 2017-05-24 21:45 • 来自相关话题

RPC框架大比拼

chrislee 发表了文章 • 1 个评论 • 283 次浏览 • 2017-05-24 10:52 • 来自相关话题

对thrift-go和grpc-go在长链接、短链接下各方面性能做了对比,涉及QPS、CPU、Latency,详细内容参见rpc_benchmar... 查看全部

对thrift-go和grpc-go在长链接、短链接下各方面性能做了对比,涉及QPS、CPU、Latency,详细内容参见rpc_benchmark. 希望对大家的技术选型有些帮助。

redis 使用GETRANGE 来获取一组bitmap状态

luw2007 发表了文章 • 2 个评论 • 161 次浏览 • 2017-05-22 16:42 • 来自相关话题

在使用redis 的 bitmap 来存储业务状态。经常需要顺序获取一个范围内bitmap。 业务代码里,一般会使用pipeline来优化查询逻辑。伪代码如下

查看全部
					

在使用redis 的 bitmap 来存储业务状态。经常需要顺序获取一个范围内bitmap。
业务代码里,一般会使用pipeline来优化查询逻辑。伪代码如下


def get_bits_pipe(pipe, key, cur, num=10):
""" 使用pipeline gitbit"""
for i in xrange(num + 1):
pipe.getbit(key, cur + i)
return [cur + i for i, v in enumerate(pipe.execute()) if v]

这里减少了和redis的数据交换。提高了查询性能,但是随着查询数量的增加,性能急剧下降。


其实 redis 有更高效的方式来获取顺序的bitmap。就是通过 getrange来获取bitmap所在的字符串,然后计算出每位的值。需要注意:由于redis 的 bit 并非按照自然二进制位增加,
比如:'\x01' 对应的ascii 为1。其二进制表示'1', 在redis中表示offset为 7。感兴趣可以看看redis的实现逻辑。


以下提供golang 和python版本的样例。
实现代码: https://gist.github.com/luw2007/692d4a615dd71aa2bfa42190ad6a12e3


var nums = [8]uint8{1, 2, 4, 8, 16, 32, 64, 128}

// BitRange 计算下标表
// str: 计算的字符串
// start: 开始的座标
// offset: 偏移值
// size: 查询个数
func BitRange(str []byte, start int, offset int, size int) []int {
bits := []int{}
k := 0
for i, b := range str {
for j, num := range nums {
if b&num != num {
continue
}
k = int(i*8 + 7 - j)
if offset <= k && k < offset+size {
bits = append(bits, start*8+k)
}

}
}
return bits
}

// GetBitRange 按位查询 返回bit位为1的下标
// client: redis的client
// key: redis 存储的key
// cur: 开始位置
// size: 查询个数
func (p *Pool) BitRange(key string, cur int, size int) ([]int, error) {
start := cur / 8
// end必须按8取整
end := (cur+size+7)/8
str, err := r.Bytes(p.ExecuteCMD("GETRANGE", key, start, end))
if err != nil {
return nil, err
}
bits := BitRange(str, start, cur%8, size)
return bits, nil
}