git地址在这里:

https://github.com/Lazyshot/go-hbase

这是一个使用go操作hbase的行为。

分析scan行为

如何使用scan看下面这个例子,伪代码如下:

func scan(phone string, start time.Time, end time.Time) ([]Loc, error) {
... client := hbase.NewClient(zks, "/hbase")
client.SetLogLevel("DEBUG") scan := client.Scan(table) scan.StartRow = []byte(phone + strconv.Itoa(int(end.Unix())))
scan.StopRow = []byte(phone + strconv.Itoa(int(start.Unix()))) var locs []Loc
scan.Map(func(ret *hbase.ResultRow) {
var loc Loc
for _, v := range ret.Columns {
switch v.ColumnName {
case "lbs:phone":
loc.Phone = v.Value.String()
case "lbs:lat":
loc.Lat = v.Value.String()
...
}
}
locs = append(locs, loc)
})
return locs, nil
}

首先是NewClient, 返回的结构是hbase.Client, 这个结构代表的是与hbase服务端交互的客户端实体。

这里没有什么好看的,倒是有一点要注意,在NewClient的时候,里面的zkRootReginPath是写死的,就是说hbase在zk中的地址是固定的。当然这个也是默认的。

func NewClient(zkHosts []string, zkRoot string) *Client {
cl := &Client{
zkHosts: zkHosts,
zkRoot: zkRoot,
zkRootRegionPath: "/meta-region-server", servers: make(map[string]*connection),
cachedRegionLocations: make(map[string]map[string]*regionInfo),
prefetched: make(map[string]bool),
maxRetries: max_action_retries,
} cl.initZk() return cl
}

下面是client.Scan

client.Scan

返回的是

func newScan(table []byte, client *Client) *Scan {
return &Scan{
client: client,
table: table,
nextStartRow: nil, families: make([][]byte, 0),
qualifiers: make([][][]byte, 0), numCached: 100,
closed: false, timeRange: nil,
}
}

scan结构:

type Scan struct {
client *Client id uint64
table []byte StartRow []byte
StopRow []byte families [][]byte
qualifiers [][][]byte nextStartRow []byte numCached int
closed bool //for filters
timeRange *TimeRange location *regionInfo
server *connection
}

设置了开始位置,结束位置,就可以进行Map操作了。

func (s *Scan) Map(f func(*ResultRow)) {
for {
results := s.next() if results == nil {
break
} for _, v := range results {
f(v) if s.closed {
return
}
}
}
}

这个map的参数是一个函数f,没有返回值。框架的行为就是一个大循环,不断调用s.next(),注意,这里s.next返回回来的result可能是由多条,然后把这个多条数据每条进行一次实际的函数调用。结束循环有两个方法,一个是next中再也取不到数据(数据已经取完了)。还有一个是s.closed呗设置为true。

s.next()

func (s *Scan) next() []*ResultRow {
startRow := s.nextStartRow
if startRow == nil {
startRow = s.StartRow
} return s.getData(startRow)
}

这里其实是把startRow不断往前推进,但是每次从startRow获取多少数据呢?需要看getData

getData

最核心的流程如下:

func (s *Scan) getData(nextStart []byte) []*ResultRow {
... server, location := s.getServerAndLocation(s.table, nextStart) req := &proto.ScanRequest{
Region: &proto.RegionSpecifier{
Type: proto.RegionSpecifier_REGION_NAME.Enum(),
Value: []byte(location.name),
},
NumberOfRows: pb.Uint32(uint32(s.numCached)),
Scan: &proto.Scan{},
} ... cl := newCall(req)
server.call(cl) ... select {
case msg := <-cl.responseCh:
return s.processResponse(msg)
}
}

这里看到有一个s.numCached, 我们猜测这个是用来指定一次call请求调用回多少条数据的。

看call函数

func newCall(request pb.Message) *call {
var responseBuffer pb.Message
var methodName string switch request.(type) {
...
case *proto.ScanRequest:
responseBuffer = &proto.ScanResponse{}
methodName = "Scan"
...
} return &call{
methodName: methodName,
request: request,
responseBuffer: responseBuffer,
responseCh: make(chan pb.Message, 1),
}
}
type call struct {
id uint32
methodName string
request pb.Message
responseBuffer pb.Message
responseCh chan pb.Message
}

可以看出,这个call是一个有responseBuffer的实际调用者。

下面看server.Call

至于这里的server, 我们不看代码流程了,只需要知道最后他返回的是connection这么个结构

type connection struct {
connstr string id int
name string socket net.Conn
in *inputStream calls map[int]*call
callId *atomicCounter isMaster bool
}

创建是使用函数newConnection调用

func newConnection(connstr string, isMaster bool) (*connection, error) {
id := connectionIds.IncrAndGet() log.Debug("Connecting to server[id=%d] [%s]", id, connstr) socket, err := net.Dial("tcp", connstr) if err != nil {
return nil, err
} c := &connection{
connstr: connstr, id: id,
name: fmt.Sprintf("connection(%s) id: %d", connstr, id), socket: socket,
in: newInputStream(socket), calls: make(map[int]*call),
callId: newAtomicCounter(), isMaster: isMaster,
} err = c.init()
if err != nil {
return nil, err
} log.Debug("Initiated connection [id=%d] [%s]", id, connstr) return c, nil
}

好,那么实际上就是调用connection.call(request *call)

func (c *connection) call(request *call) error {
id := c.callId.IncrAndGet()
rh := &proto.RequestHeader{
CallId: pb.Uint32(uint32(id)),
MethodName: pb.String(request.methodName),
RequestParam: pb.Bool(true),
} request.setid(uint32(id)) bfrh := newOutputBuffer()
err := bfrh.WritePBMessage(rh)
... bfr := newOutputBuffer()
err = bfr.WritePBMessage(request.request)
... buf := newOutputBuffer()
buf.writeDelimitedBuffers(bfrh, bfr) c.calls[id] = request
n, err := c.socket.Write(buf.Bytes()) ...
}

逻辑就是先把requestHeader压入,再压入request.request

call只是完成了请求转换成byte传输到hbase服务端,在什么地方进行消息回收呢?

回到NewConnection的方法,里面有个connection.init()

func (c *connection) init() error {

     err := c.writeHead()
if err != nil {
return err
} err = c.writeConnectionHeader()
if err != nil {
return err
} go c.processMessages() return nil
}

这里go c.processMessage()

func (c *connection) processMessages() {
for {
msgs := c.in.processData()
if msgs == nil || len(msgs) == 0 || len(msgs[0]) == 0 {
continue
} var rh proto.ResponseHeader
err := pb.Unmarshal(msgs[0], &rh)
if err != nil {
panic(err)
} callId := rh.GetCallId()
call, ok := c.calls[int(callId)] delete(c.calls, int(callId)) exception := rh.GetException()
if exception != nil {
call.complete(fmt.Errorf("Exception returned: %s\n%s", exception.GetExceptionClassName(), exception.GetStackTrace()), nil)
} else if len(msgs) == 2 {
call.complete(nil, msgs[1])
}
}
}

这里将它简化下:

func (c *connection) processMessages() {
for {
msgs := c.in.processData() call.complete(nil, msgs[1])
}
}

c.in.processData

是在input_stream.go中

func (in *inputStream) processData() [][]byte {

    nBytesExpecting, err := in.readInt32()

    ...

     if nBytesExpecting > 0 {
buf, err := in.readN(nBytesExpecting) if err != nil && err == io.EOF {
panic("Unexpected closed socket")
} payloads := in.processMessage(buf) if len(payloads) > 0 {
return payloads
}
} return nil
}

先读取出一个int值,这个int值判断后面还有多少个bytes,再将后面的bytes读取进入到buf中,进行input_stream的processMessage处理。

我们这里还看到并没有执行我们map中定义的匿名方法。只是把消息解析出来了而已。

call.complete

func (c *call) complete(err error, response []byte) {
... err2 := pb.Unmarshal(response, c.responseBuffer)
... c.responseCh <- c.responseBuffer
}

这个函数有用的也就这两句话把responseBuffer里面的内容通过管道传递给responseCh

这里就看到getData的时候,被堵塞的地方

     select {
case msg := <-cl.responseCh:
return s.processResponse(msg)
}

那么这里就有把获取到的responseCh的消息进行processResponse处理。

func (s *Scan) processResponse(response pb.Message) []*ResultRow {
...
results := res.GetResults()
n := len(results) ...
s.closeScan(s.server, s.location, s.id) ... tbr := make([]*ResultRow, n)
for i, v := range results {
tbr[i] = newResultRow(v)
} return tbr
}

这个函数并没有什么特别的行为,只是进行ResultRow的组装。

好吧,这个包有个地方可以优化,这个go-hbase的scan的时候,numCached默认是100,这个对于hbase来说太小了,完全可以调整大点,到2000~10000之间,你会发现scan的性能提升杠杠的。

go-hbase的Scan模型源码分析的更多相关文章

  1. Hbase WAL线程模型源码分析

    版权声明:本文由熊训德原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/257 来源:腾云阁 https://www.qclo ...

  2. Django(60)Django内置User模型源码分析及自定义User

    前言 Django为我们提供了内置的User模型,不需要我们再额外定义用户模型,建立用户体系了.它的完整的路径是在django.contrib.auth.models.User. User模型源码分析 ...

  3. memcached(二)事件模型源码分析

    memcachedd事件模型 在memcachedd中,作者为了专注于缓存的设计,使用了libevent来开发事件模型.memcachedd的时间模型同nginx的类似,拥有一个主进行(master) ...

  4. Django中CBV(Class Base Views)模型源码分析

    在view文件中编写一个类,并配置好路由 class Test(View): def get(self, request, *args, **kwargs): return HttpResponse( ...

  5. 基于Netty的RPC架构学习笔记(五):netty线程模型源码分析(二)

    文章目录 小技巧(如何看开源框架的源码) 源码解析 阅读源码技巧 打印查看 通过打断点调试 查看调用栈 小技巧(如何看开源框架的源码) 一断点 二打印 三看调用栈 四搜索 源码解析 //设置nioso ...

  6. 【图灵学院09】RPC底层通讯原理之Netty线程模型源码分析

    1. dubbo 2.5.3 netty 3.2.5.Final

  7. 基于Netty的RPC架构学习笔记(四):netty线程模型源码分析(一)

    文章目录 如何提高NIO的工作效率 举个

  8. Hbase写入hdfs源码分析

    版权声明:本文由熊训德原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/258 来源:腾云阁 https://www.qclo ...

  9. [源码分析] 并行分布式任务队列 Celery 之 Timer & Heartbeat

    [源码分析] 并行分布式任务队列 Celery 之 Timer & Heartbeat 目录 [源码分析] 并行分布式任务队列 Celery 之 Timer & Heartbeat 0 ...

随机推荐

  1. 关于Vue.js 2.0 的 Vuex 2.0,你需要更新的知识库

    应用结构 实际上,Vuex 在怎么组织你的代码结构上面没有任何限制,相反,它强制规定了一系列高级的原则: 应用级的状态集中放在 store 中. 改变状态的唯一方式是提交mutations,这是个同步 ...

  2. 使用python抓取婚恋网用户数据并用决策树生成自己择偶观

    最近在看<机器学习实战>的时候萌生了一个想法,自己去网上爬一些数据按照书上的方法处理一下,不仅可以加深自己对书本的理解,顺便还可以在github拉拉人气.刚好在看决策树这一章,书里面的理论 ...

  3. ASP.NET MVC5+EF6+EasyUI 后台管理系统(74)-微信公众平台开发-自定义菜单

    系列目录 引言 1.如果不借用Senparc.Weixin SDK自定义菜单,编码起来,工作量是非常之大 2.但是借助SDK似乎一切都是简单得不要不要的 3.自定义菜单无需要建立数据库表 4.自定义菜 ...

  4. 一个IT人的成长路

    毕业四年多了,来深圳三年多了,经历了刚毕业的懵懂少年,成长为现在的成熟稳重青年.职场上,从刚毕业的小白,成长为现在可以成熟应对各种事情的老司机.经历过从初级研发工程师,到中级研发工程师,到高级研发工程 ...

  5. Phantomjs+Nodejs+Mysql数据抓取(2.抓取图片)

    概要 这篇博客是在上一篇博客Phantomjs+Nodejs+Mysql数据抓取(1.抓取数据) http://blog.csdn.net/jokerkon/article/details/50868 ...

  6. 在DevExpress程序中使用GridView直接录入数据的时候,增加列表选择的功能

    在我上篇随笔<在DevExpress程序中使用Winform分页控件直接录入数据并保存>中介绍了在GridView以及在其封装的分页控件上做数据的直接录入的处理,介绍情况下数据的保存和校验 ...

  7. 《动手实现一个网页加载进度loading》

    loading随处可见,比如一个app经常会有下拉刷新,上拉加载的功能,在刷新和加载的过程中为了让用户感知到 load 的过程,我们会使用一些过渡动画来表达.最常见的比如"转圈圈" ...

  8. BPM合同管理解决方案分享

    一.方案概述合同是组织与组织间所订协议的法律 表现形式,体现着双方对于合作在法律和道德上的承诺.然而,大多数企业的合同管理都或多或少存在合同审批过程不规范.签订草率.审批权责不清.合同执行跟踪难.合同 ...

  9. 在 SharePoint Server 2016 本地环境中设置 OneDrive for Business

    建议补丁 建议在sharepoint2016打上KB3127940补丁,补丁下载地址 https://support.microsoft.com/zh-cn/kb/3127940 当然不打,也可以用O ...

  10. Kotlin与Android SDK 集成(KAD 05)

    作者:Antonio Leiva 时间:Dec 19, 2016 原文链接:https://antonioleiva.com/kotlin-integrations-android-sdk/ 使用Ko ...