ellios's blog

ellios's trivial story.

2015个人总结

| Comments

刚把部门总结写完,顺带着想起2015这一整年的经历,对个人的影响还是挺大的,于是决定把我的2015给记个流水出来。

年初,力扑默而不宣地散了,到最后李有兵也没站出来给大家个说法,灰溜溜的跑了,好像世上再也不存在这个人。拼命加班一个多月的工资没了,之前拼命干了大半年的产品还没正式出世就死了,内心难过和自责了好久。和大家一起去仲裁,但是最终发现也只是走个形式而已,面对一个存心耍无赖的人,法律也只会呵呵。结局虽然不是很好,但是对于自己确实是一段宝贵的经历,经历了一个公司从出生到死亡的过程,经历了带领40人团队的巨大跳跃,经历了一个团队打了鸡血拼命工作到最后不欢而散的过程,经历了一个号称身家10多亿的老板从极度虚幻到跌落尘埃的过程,经历了半夜12点警察叔叔的狠狠呵斥,经历了。。。,真正参与过了这么一段创业,才明白创业的各种辛酸和困难。

年初除了力扑的事情收尾,然后就是面试,找工作。面了3,4家公司,最终到了乐视体育。3月底正式入职,然后就开始各种忙,持续了半年多的996的状态,不过忙下来,收获还可以。

  • 从0开始组建了服务端团队。(招人特别辛酸,这边招人挑学校和公司,工资给的在业内也不算高,流程还长正常要1个月才能下offer,好多不错的人到人力环节就被卡了,没办法,只能去前东家挖人了,挖了3个人过来,前领导知道了,现在还不和说话)。
  • 团队在高强度的工作状态中坚持了下来,提高很快,目前逐步步入了正规,我也能从大量的coding中解放出来了。
  • 团队参与的多个重要项目基本都完成了,而且公司这些产品的发展趋势和关注度都还不错,作为核心的服务端团队负责人,内心成就感也还不错。
  • 入职前说的股权协议终于签了,一块石头落了地,杂七杂八的念头也少了,希望最终能有所收获吧。
  • 技术管理的路走得越来越稳了,个人觉得现在算是一个合适的技术管理者了。

这一年工作主要的收获还是偏管理方向,技术方向的收获不大,专注研究一些技术的时间越来越少,一些新的技术基本都只能大概了解,处于吃老本的阶段,不过吃老本的过程中,对技术的理解想相对深刻了些。新的一年计划带领团队做些更技术含量的事情,实时推送,搜索,推荐,大数据,知识图谱大概这些,希望各个方向都能有初步的进展。

今年大部头的技术书籍基本没看过,目前在看《R语言实战》,其他粗略地看了《创业维艰》,《从0到1》,《企业估值就是讲故事》,《经济学原理》,《聪明的投资者》,《怎样选择成长股》新一年计划还是这些方向吧,创业和投资,为若干年后的个人规划做些准备。

今年终于在北京把房买了,了却了一桩大心愿。买房的过程还是挺戏剧的,十一放假爸妈突然说可以提供一部分资金让我们买房,本来没有买房计划的我和老婆一合计,决定今年就把房买了。于是第二天就杀北京看房,第三天就交了定金,一个月后办完了各种手续。事后想想,我和老婆在人生几件大事上基本都是跟着感觉走,不过最终结果都还可以。

家庭生活越来越和谐了,和老婆基本没有7年之痒的问题。晶晶4岁了,喜欢臭美,喜欢画画,喜欢哭,性格善良(除了爱欺负我),长大的梦想是当画家。

创业记二

| Comments

一眨眼,已经快两个月了,距离网站上线刚刚过去了一个星期,自己终于能得闲整理下这段时间在创业团队工作的过程了。回想从刚进入团队到网站上线,虽然只有一个半月的时间,不过那段时间每天都觉得像是已经过了好几年似的,心里承受的压力还是挺大的,每天如果不工作到很晚,心里都会很不安,怕是有什么事情被自己漏过了。尤其是最初的两周,自己需要定下短期的技术方案;把一些重要的事情分解到个人;鼓舞整个团队的士气;四处招人,但是最终只有一个人顺利入职;降低各种不确定因素造成的影响。每一天都在挑战自己的神经,70%的事情自己都是从新开始。管理一个技术团队,之前一直觉得是挺简单地一件事,现在一个半月走下来,其中各种不容易,还是挺有感触的。

由于产品迟迟不能上线,整个公司包括运营和市场团队很长一段时间是在空转,巧妇难为无米之炊。这个时候,技术和产品团队自然要承受最大的压力,但是由于缺乏具体的执行方案,技术和产品这个时候也是处于无所事事的状态。这就是我决定加入时,整个公司的状态。看到这种情况,自己心里开始发急了,如果这种状态一直持续下去,之前定好的上线日期肯定又要delay了,而如果再次delay,投资人很可能对整个公司的执行力失去信心,当时真的希望技术和产品团队的每个人第二天能跟打了兴奋剂一样,每天都能开始通宵。但是,面对现实,团队不可能一下子飞起来,而我也必须加快行动的步伐了。提前一周加入了团队,和大家简单聊了下,团队成员的水平基本处于初中级的水平,缺乏对大型系统整体把控的能力,自己短期内需要充当架构师的角色,把整个系统分割成效的任务点让大家开发,另外慢慢的去培养几个人。第二天,召集技术和产品团队的所有人开了次动员会,告诉大家请看很严重,要强制大家加班了,按时上线奖金大大的,胡萝卜加大棒那套。讲话的时候自带洗脑光环,跟之前公司的CTO学的,能影响到几个算几个。谈话加大会的效果还不错,开始有一大半的人在加班了。初步解决了大家工作积极性的问题,开始解决技术性的问题了。试着和大家沟通自己的技术想法,但是发现大家完全不能理解,而且对你报以怀疑的目光,唉,不在一个频道的感觉。只能自己亲自动手了,趁着周末,搭建gitlab,把项目从gradle迁移回maven(实在没精力再去研究gradle了),搭建测试环境,还是做技术的活顺手点。有点孤独感,安慰自己,在把整个团队在往自己设想的方向上给掰了。

又是一周,整个团队在我指引的方向上终于有了点感觉,这周计划的任务,大家基本都完成了。刚有点感觉的时候,不确定因素开始出现了,之前本来说好要加入团队的几个核心开发人员,突然各种原因都来不了了,当时顿时有点手足无措的感觉。一心想走捷径,指望这些人之前的经验短时间复制一套他们之前做过的系统,现在都只能靠自己了,那个时候深深的感觉到靠什么都不如靠自己来得踏实,所谓的捷径都是自己一点一滴干出来的。于是重新梳理产品需求,重新设计,给大家做了简单地培训,发现团队里几个成员稍微培养下还挺不错,两周,自己和这个团队都找到了感觉,心情开始不那么焦虑了。

大方向定了,剩下的就是执行了,加班成了家常便饭,每天都觉得过得很漫长,一个月下来,感觉跟过了一年似得。期间还经历了公司搬家,还有各种其他事件的干扰,不过最后大家都撑过来了。当系统完成上线的那一刻,大大了送了一口气,不过更大的挑战紧接着就又来临了。

创业记一

| Comments

收到了一家创业公司的橄榄枝,内心稍微挣扎了一下(主要是说服老婆),很快就同意加入了。公司是做在线教育,目前大热的方向,和创始人聊得不错,自我感觉将来会很美好。

作为典型的技术工种,我担任了公司的技术总监,title挺唬人的,现实其实也是很唬人的,我要带着现有的14人的产品+技术团队,在一个半月之内把我们的一期产品上线。而当了解了整个产品需求和开发进度的时候,我立即认识到,除非找到合适的人,否则现有的团队在规定的时间点内把产品上线,几乎是不可能的。而我也终于明确了自己作为一个技术管理者的使命感,放下代码,拼命找人,开始不停的给之前的同事打电话,折腾了一个星期,招到了4个人,准确的说是给4个人发了offer,而他们真正入职,可能得1个月之后了(就连我也要到两周之后才能正式入职)那个时候,我们的产品只有半个月时间了,在这么短时间内做一个新产品出来,根本是不可能的。于是只能用创业公司的杀手锏了,加班和兼职。加班不用说了,上过班的都知道。兼职,对于那些还不能入职的同事,周末过来和我们联调,平时兼职开发,效率不好保证,不过也是现有情况下,唯一的办法了。

人员基本确认了,但是怎么带着将近20人(之前有两人发了offer,在我确认入伙后两天,他两也入职了)的团队把产品做上线,我还是一点底都没有。之前在奇艺我最多只带过6个人的团队,那个时候是纯粹的java团队,我可以把每个人的代码都review一遍,自己还有时间写些比较核心的模块。我不用太多去想产品,设计,运维这些东西,因为有专门的团队去做了,而现在,这些东西都要去考虑,买了些书去恶补这方面的知识,但是短时间内也很难入门。之前习惯了事必躬亲,每一件事情自己都要想清楚才会去做,而现在需要放手让别人去做了,自己很多时候只能旁观了。除了我招过来的几个人,之前的十多个个同事,我基本都不熟悉,找每个人都谈了一次,了解了下大家的情况,团队整体的情况不错,这给了我小小的信心。

周末把几个现在比较核心的和即将入职的同事召集到一起,重新过了一遍产品ui和现有的系统的数据结构,把整体的排期和工作安排定了下来。下周我决定提前过去了(本来还有一周才正式入职呢),不过考虑到自己在奇艺的年假可以充分利用下,另外项目的压力还是蛮大的,能早点过去就早点过去吧。

Devops

| Comments

最近做的监控平台基本work起来了,后期就是完善文档,然后推动大家去用了。自己统计了下,发现来公司一年半,基本上有一半的时间都是在做运维的工作。能得到的运维支持太少了,没办法,很多事情只能赶鸭子上架自己来了。不过做完之后,还能挺有成就感的,自己也收获了不少,处理问题的能力有了很大的提升。

看下自己在公司做过的一些运维相关的工作

  • 基于jenkins搭建持续集成和自动化部署的平台
  • 用ganglia对JVM和业务数据进行监控,ganglia画出来的图太难看,定制化也太麻烦,试用了一段时间没有推广了
  • 试着用了下zabbix做监控,因为公司其他部门用这个比较多,看了下太复杂,放弃
  • 试用了下fabric,自动化安装JDK,自动压缩清理日志,目前还是入门阶段的使用,没有太深入
  • 做了一个zookeeper的监控界面,做的比较糙,能增删改查数据,目前满足我们开发的需求是够了
  • 基于graphite搭建监控平台,graphite就是专业画图的,画的图超赞。而且周边的开源项目很多,目前我们用了jmxtrans做JMX的数据收集,基于logster做了定制开发,可以收集nginx日志数据以及业务产生的日志,用了leonardo做dashboard,用了seyren做报警
  • 用了monit对进程进行监控,目前还是属于初级使用阶段
  • 试用了supervisord,托管进程的运行,不过这个东西要用好的话得做大量的定制开发,暂时放弃吧
  • 使用了saltstack,目前的观感不错,后续会继续深挖它的潜力,

我这算是devops的alone mode吧,现在深刻觉得开发人员掌握一点运维技能还能很有必要的,另外会点python之类的脚本语言在运维的时候会有很大的优势,以后如果team招人的话,这会是一个很大的加分项。

Reviewboard折腾二

| Comments

最近因为兄弟团队的一次比较严重的故障,领导又开始强制要求code reivew了,并且要提交到reviewboard上。之前也有过类似的强制要求,我还写过篇日志《艰难的reviewboard折腾》,但是应付完领导之后,整个团队没一个人主动提交review。对于code review这种事情,很难立刻见到成效,而且还会增加大家的负担,最好是能有一种长效机制,慢慢去培养大家的习惯,而且靠领导命令去推动,也违背了进行code review的初衷–学习和交流。

OK,废话说完,开始正题,目前的团队用eclipse和idea各占一半,用eclipse的可以用tao-reviewboard,但是idea目前发现的各种插件,经测试都不可用。没办法,我们团队的同学自己造了一个,基于一个开源的项目https://code.google.com/p/reviewboard-plugin-for-idea/,我们修复了这个项目的一些bug,代码放在https://github.com/georgecao/reviewboard4idea。下面简单介绍下该插件的安装和使用

  1. 下载编译好的jar包
  2. 打开idea,进入菜单 Settings->Plugins, 点击按钮Install plugin from disk,选择刚刚下载的jar包。下图是安装成功后的截图:
  3. 点击上图的Review Board,简单配置下.其中Server Url是reviewboard的url,如http://reviewboard.xx.com, Username是用户名,Password就是密码了
  4. 配置好后,简单修改一个文件,下方的Changes状态栏就会显示那个文件,如下图
  5. 右键点击该文件,选择PostReview菜单项,弹出Post Review对话框,如图 其中 Summary:简短描述信息,Branch:代码分支一般是trunk吧,Bug:和bug相关的信息,比如Bug id等信息, Group:Review Board中的分组,多个分组用逗号分割,People:reviewers,需要谁来review,多个人用逗号分割,Description:这次提交的详细描述信息。

随想

| Comments

转眼一年又快过去了,今年是目前参加工作以来最忙的一年,尤其是后半年,被各种产品需求和线上报障追着跑,连写个博客的时间都没有了,本来已经上手了的emacs,最近又都忘完了。现在是用sublime text写的,还是这个简单,不需要折腾一堆插件和记一堆的快捷键。

下半年基本上没接触什么新的技术,主要的精力都放在数据服务了,好在经过一年的推动,目前的数据服务已经基本稳定了,整个系统的服务化改造也基本完成了。最近把restful service相关的文章又重新看了下,之前对restful的理解还是太片面了,之后会推动我们的系统朝着真正的restful的方向发展,最近会写一些东西分享下我们做restful服务的心得。上周偷闲,终于把买了好久的树莓派给跑起来,之前一直觉得挺对不起它的,买来放了3,4个月连包装都没拆。之前一直想好好学学的lua, scala, hadoop, hbase更是一眼都没看过,希望过段时间能有时间把这些都好好看一遍。

虽然技术的收获不多,但是对于业务的理解这半年却加深了不少,能够独自和产品PK了。也开始学着做一些管理,手下有了两个小兵,虽然刚开始的时候有点不太习惯,脾气也比较急,基本上什么都是亲力亲为,但是最近也开始慢慢进入角色了,目前主要是通过每天的站例会议和code review的方式,团队的协作自我感觉还算不错。和其他部门同事的交往越来越多了,基本上跨部门的合作都会比较麻烦,这个我觉得需要一种艺术,目前我还不得要领,探索学习中吧。

上个月,经过李剑同学的提醒,发现偶用的手机竟然是移动版的3G手机,不支持联通3G,汗,用了半年了才知道。于是紧急花900又买了一个华为的G610U,上网快多了,顺便刷了一个miui系统玩,怪不得小米手机那么多人抢,miui做得确实挺舒服的。个人觉得android的手机现在和apple的用户体验的差距已经很小了,apple还得越狱,费半天劲,一般人真心没必要多花那么多钱买个apple。

很早就想学一些经济和金融的知识,觉得这东西挺重要的,但是一直不知道该怎么入门。前段时间上豆瓣买了本《白话金融投资》的电子书,居然让俺看懂了点,于是觉得看书这方式也挺靠谱,上豆瓣找了一堆经济和金融的书先看着,权当入门吧,计划春节前能把买得7,8本书全部看完,我要好好学习啦。

晶晶已经2岁半多了,也越来越可爱了,小家伙太爱说话,跟她妈一个性格,说起话来不带停的,不过有时间你听半天也听不明白她说啥。每次晚上加完班,走在路上,想想晶晶和晶晶她妈,心里就可满足了。

Ganglia Python

| Comments

老大想把我们放在mongdodb的一些统计数据,在Ganglia展示出来,想到Ganglia支持用python来写扩展模块,于是欣然应下,正好也学习下怎么写Ganglia的python扩展模块。

Ganglia要集成python模块是非常容易的,简单的数据展示,只需要写个pyconf后缀的文件和一个python的脚本就好了。Ganglia自身的源码里面也有很多python的模块的源码,其中有一个是专门用来做示例用的,我们就从他开始吧。

首先要弄清楚你需要收集哪些各数据,在pyconf文件中配置你要收集的数据的名称,像下面这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
modules {
  module {
    name = "example"
    language = "python"
    enabled = "yes"
    param RandomMax {
        value = 600
    }
    param ConstantValue {
        value = 112
    }
  }
}

collection_group {
  collect_every = 10
  time_threshold = 50
  metric {
    name = "PyRandom_Numbers"
    value_threshold = 1.0
  }
}

collection_group {
  collect_once = yes
  time_threshold = 20
  metric {
    name = "PyConstant_Number"
  }
}

很简单,modules里面配置了要加载的模块的名称,具体的配置项这里就不纠结了,collection_group里面可以配置你要收集的数据,metric项就是了。

配置文件写好了,下面看看真真干活的模块吧,ganglia的python模块是有一定的规范的,每个模块必要要有 metric_init, metric_cleanup方法,metric_cleanup做些收尾的工作,当gmond关闭时释放资源。metric_init方法做初始化操作,gmond在加载模块时,会先调用metric_init方法。这里你需要定义要收集的数据的详细参数,以及数据的call_back方法。具体看下面吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import random
descriptors = list()
Random_Max = 50
Constant_Value = 50

def Random_Numbers(name):
    '''Return a random number.'''
    return int(random.uniform(0, Random_Max))

def Constant_Number(name):
    '''Return a constant number.'''
    return int(Constant_Value)

def metric_init(params):
    '''Initialize the random number generator and create the
    metric definition dictionary object for each metric.'''
    global descriptors
    global Random_Max
    global Constant_Value
    random.seed()

    print '[pyexample] Received the following parameters'
    print params

    if 'RandomMax' in params:
        Random_Max = int(params['RandomMax'])
    if 'ConstantValue' in params:
        Constant_Value = int(params['ConstantValue'])

    d1 = {'name': 'PyRandom_Numbers',
        'call_back': Random_Numbers,
        'time_max': 90,
        'value_type': 'uint',
        'units': 'N',
        'slope': 'both',
        'format': '%u',
        'description': 'Example module metric (random numbers)',
        'groups': 'example,random'}

    d2 = {'name': 'PyConstant_Number',
        'call_back': Constant_Number,
        'time_max': 90,
        'value_type': 'uint',
        'units': 'N',
        'slope': 'zero',
        'format': '%hu',
      'description': 'Example module metric (constant number)'}

    descriptors = [d1, d2]
    return descriptors

def metric_cleanup():
    '''Clean up the metric module.'''
    pass

#This code is for debugging and unit testing
if __name__ == '__main__':
    params = {'RandomMax': '500',
        'ConstantValue': '322'}
    metric_init(params)
    for d in descriptors:
        v = d['call_back'](d['name'])
        print 'value for %s is %u' % (d['name'],  v)

其他还需要做些简单的配置,不过看下gmond.conf的配置文件基本上就都搞好了。这里就不啰嗦了,这两天感冒的厉害,需要好好休息。

参考资料

Ganglia整合java

| Comments

前段时间简单介绍了Ganglia的安装和配置,现在我们的Ganglia已经跑了起来,并且已经可以展示机器的负载, 网络,io等各种数据,但是光有机器的情况还是远远不够,我们还需要了解每个应用的运行情况。针对一些常见的应用如:Mysql,Memcached,Redis,Nginx等,Ganglia的github上有很多开源的模块,大家可以自己折腾,要是有时间,后面也会简单简介绍下。这里主要介绍下怎么用Ganglia来收集和展示Java的数据,包括JVM的运行数据和业务数据。

获取JMX数据

JVM的运行数据我们是从JMX获得的,其实业务数据也可以写进JMX里,所以如果你的Java应用开启了JMX的话,基本上各种数据都可以从JMX中获得了。获取JMX数据的包Ganglia的网站已经有了,叫jmxetric。要使用它,需要再jvm参数里加入如下的内容

1
-javaagent:jmxetric-1.0.3.jar=host=xxx,port=xxx,mode=unicast,wireformat31x=true,config=jmxetric.xml

host是接收数据gmond的host,port是gmond的端口,mode有multicast和unicast两种,由于网络原因,我们使用的是unicast(单播),wireformat31x这个不清楚,一直都是true来着。前面几项配置都可以写到jmxetric.xml里面,不过为了方便控制这些参数,放到了外面。jmxetric.xml里面写需要获取的JMX的数据项,jmxetric的站点上有各详细的示例,这个不不再占用空间了。

一些注意的地方: 1. jmxetric.jar还依赖与gmetric4j.jar和oncrpc.jar,所以如果是直接下载jar包的话,要把依赖的jar包也都下到,而且要和jmxetric.jar在一个目录下。我们的项目是用maven管理的,直接加如下的依赖

1
2
3
4
5
<dependency>
    <groupId>info.ganglia.jmxetric</groupId>
    <artifactId>jmxetric</artifactId>
    <version>1.0.3</version>
</dependency>
  1. 记得开启JMX哦

直接发送数据

如果觉得从JMX获取数据麻烦的话,或者你不想开启JMX,你也可以直接发送数据到gmond。这个也有了现成的包gmetric4j,其实jmxetric也是利用他来发送数据的。这个没啥好说的,就是利用包提供的api发送数据就好了。 写了一个工具类,对原生的api做了简单的封装。最近看了下yammer开源的metrics,很强大,有兴趣的可以看下它的源码。

Ganglia Monitor Util MonitorUtil.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import info.ganglia.gmetric4j.gmetric.GMetric;
import info.ganglia.gmetric4j.gmetric.GMetricSlope;
import info.ganglia.gmetric4j.gmetric.GMetricType;
import info.ganglia.gmetric4j.gmetric.GangliaException;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MonitorUtil {

    private static final Logger logger = LoggerFactory.getLogger(MonitorUtil.class);
    private static final HedwigConfig HC = HedwigConfig.getInstance();

    private static class GMetricHOlder {
        static String GANGLIA_HOST = HC.getString("ganglia.host", "");
        final static int GANGLIA_PORT = HC.getInt("ganglia.port", 7649);
        final static int GANGLIA_TTL = HC.getInt("ganglia.ttl", 1);
        static {
            if(StringUtils.isEmpty(GANGLIA_HOST)){
                GANGLIA_HOST = NetworkUtils.getEth0Address();
            }
            System.out.println("[GANGLIA MONITOR] begin to init gmetric");
            System.out.println("[GANGLIA MONITOR] GANGLIA_HOST : " + GANGLIA_HOST);
            System.out.println("[GANGLIA MONITOR] GANGLIA_PORT : " + GANGLIA_PORT);
            System.out.println("[GANGLIA MONITOR] GANGLIA_TTL : " + GANGLIA_TTL);
        }
        final static GMetric G_METRIC = new GMetric(GANGLIA_HOST, GANGLIA_PORT,
                GMetric.UDPAddressingMode.UNICAST, GANGLIA_TTL, true);
    }

    private static GMetric getGMetric() {
        return GMetricHOlder.G_METRIC;
    }

    /**
     * monitor a metric
     * @param name Name of the metric
     * @param value Value of the metric
     * @param type Type of the metric.  
     *        Either string|int8|uint8|int16|uint16|int32|uint32|float|double
     * @param units Unit of measure for the value
     * @param slope Either zero|positive|negative|both
     * @param group Group Name of the metric
     */
    public static void monitor(String name, String value, GMetricType type,
                               String units,  GMetricSlope slope, String group) {
        GMetric gm = getGMetric();
        try {
            gm.announce(name, value, type, units, slope, 60, 0,  group);
        } catch (GangliaException e) {
            logger.error("monitor fail. name : {}, value:{}, error:{}",
            name, value, e.getMessage(), e);
        }

    }

    /**
     * monitor a metric
     * @param name Name of the metric
     * @param value Value of the metric
     * @param group Group Name of the metric
     */
    public static void monitor(String name,  String value, String group){
        monitor(name, value, GMetricType.STRING, " ", GMetricSlope.BOTH, group);
    }

    /**
     * monitor a positive metric
     * @param name Name of the metric
     * @param value Value of the metric
     * @param group Group Name of the metric
     */
    public static void counter(String name,  String value, String group){
        monitor(name, value, GMetricType.STRING, " ", GMetricSlope.POSITIVE, group);
    }

    /**
     * monitor a metric
     * @param name Name of the metric
     * @param value Value of the metric
     * @param group Group Name of the metric
     */
    public static void monitor(String name,  float value, String group){
        monitor(name, Float.toString(value),GMetricType.DOUBLE, " ", GMetricSlope.BOTH, group);
    }

    /**
     * monitor a positive metric
     * @param name Name of the metric
     * @param value Value of the metric
     * @param group Group Name of the metric
     */
    public static void counter(String name, float value, String group){
        monitor(name, Float.toString(value),GMetricType.FLOAT, " ", GMetricSlope.POSITIVE, group);
    }

}

参考资料

Ganglia

| Comments

最近忙的一个项目,终于上线了,忙了一个多月,但是心里仍然很没谱。为什么没谱,主要是因为目前的系统缺乏有效的监控,对于机器负载,jvm的运行情况,各接口的调用次数,接口的调用时间。于是痛下决心,利用系统刚上线,空余的一周搭建一套监控系统。

之前做运维的工作很少,对于各种监控系统更是缺乏有效的了解,了解到的一些常用监控系统有catti,zabbix和ganglia,考虑再三选择了ganglia。为什么选择ganglia呢,理由没那么充分,可能之前看过ganglia的一些资料,对他更为熟悉,对于他出身Berkley和支持python,这两点都比较满意,于是就选择它了。

选型之后就开始折腾了,相关的资料不多,很多的配置都是试着来,期间走了很多的弯路,本来预计两天搞定的事情,其实用了一周多。直接导致本周计划的持续集成环境的搭建没时间搞了。这里简单记录下安装过程。

相关简介

Ganglia是UC Berkley发起的一个开源项目,可以用来监控数以千记的服务器集群,具有很好的扩展性。Ganglia主要包括三个部分 1. Gmond,运行在每台目标机器节点上,用于获取节点的监控数据,gmond不仅能够收集本节点的数据,还能接收其他节点发来的数据,对数据做汇集后发送到gmetad,这样就可以保证整个监控系统的可扩展性; 2. Gemtad,将各gmond的数据收集到一起,用rrdtool将数据保存到硬盘上 3. Web Front, 将rrdtool生成的数据输出可视化的图片

简单介绍下rrdtool(Round Robin Database Tool) rrdtool是一种环状结构的数据库,非常适合处理时间序列的数据,能够非常高效的绘制时间序列的图片。ganglia的数据存储和可视化都是通过rrdtool实现的。

安装

Ubuntu安装

Ubuntu下用apt-get会自动把相关的依赖都给安装上,执行下面的命令

```sh
sudo apt-get install ganglia-monitor ganglia-webfrontend
```

rh5编译安装

  1. 安装依赖的库

先用yum把相关的依赖都安装上

1
2
3
yum -y install apr-devel apr-util check-devel cairo-devel pango-devel \
libxml2-devel rpmbuild glib2-devel dbus-devel freetype-devel fontconfig-devel \
gcc-c++ expat-devel python-devel libXrender-devel libconfuse

虽然列出来好多,但是绝大多数都是一些常见的基础包,需要注意的就是expat和libconfuse

  1. 编译安装rrdtool,执行下面的命令
1
2
3
4
5
6
wget http://oss.oetiker.ch/rrdtool/pub/rrdtool.tar.gz
tar zxvf rrdtool*
cd rrdtool-*
./configure --prefix=/data/apps/rrdtool
make
make install
  1. 编译安装ganglia
1
2
3
4
5
6
7
export CPPFLAGS="$CPPFLAGS -I/opt/apps/rrdtool"
export LDFLAGS="$LDFLAGS -L/opt/apps/rrdtool"
tar –zxvf ganglia-3.1.2.tar.gz
cd ganglia-3.1.2.tar.gz
./configure --prefix=/data/apps/ganglia --with-gmetad
make
make install

4.将编译好的ganglia发布到客户端 首先在目标机器上建立ssh信任,然后执行下面的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash

GANGLIA_BIN=/data/apps/ganglia/bin
GANGLIA_SBIN=/data/apps/ganglia/sbin
GANGLIA_LIB=/data/apps/ganglia/lib64
i=$1
ssh $i mkdir -p $GANGLIA_BIN
ssh $i mkdir -p $GANGLIA_SBIN
scp $GANGLIA_BIN/gmetric $i:$GANGLIA_BIN
scp ${GANGLIA_SBIN}/gmond $i:${GANGLIA_SBIN}/gmond
ssh $i ln -fs $GANGLIA_SBIN/gmond /usr/sbin/gmond
scp -r $GANGLIA_LIB $i:$GANGLIA_LIB
ssh $i mkdir -p /etc/ganglia/
scp gmond.conf $i:/etc/ganglia/gmond.conf
scp /etc/init.d/gmond $i:/etc/init.d/
scp /lib64/libexpat.so.0 $i:/lib64/
scp /usr/lib64/libconfuse.so.0 $i:/usr/lib64/
scp /usr/lib64/libapr-1.so.0 $i:/usr/lib64/
ssh $i chkconfig --add gmond
ssh $i chkconfig gmond on
ssh $i service gmond start

5.搭建Http Server ganglia的Web界面是用php写的,所以支持php的http server是必须的。这个没啥好说的,php和各种http Server集成的方法还是 挺多的,我用的最经典的Apache+php_module的方式。

配置

对应于组成ganglia的三种组件,ganglia的配置也需要对这三种组件分别做配置

  1. Web Front的配置 Web Front的配置文件是web项目下的conf.php文件,新版的web项目可能没有这个文件,自己建一个就好了,默认是先从conf.php加载不到,如果 没有的话,就从conf_default.php加载。可以通过这个文件对web项目做些简单的定制。下面是我的配置文件。
1
2
$conf['gmetad_root'] = "/data/appdata/ganglia";
$conf['rrds'] = "${conf['gmetad_root']}/rrds";
  1. Gmetad的配置 Gmetad的配置文件默认是/etc/ganglia/gmetad.conf,编译的时候可以自己指定配置文件的位置在configure加–sysconfdir=xx.这个文件里面 最重要的配置项是data_source,下面是我的一个简要的配置
1
2
3
data_source "monitor" xx.xx.xx.xx:8649
data_source "test" xx.xx.xx.xx:8649
rrd_rootdir "/data/appdata/ganglia/rrds"
  1. Gmond的配置 Gmond是真正干活的,由于没弄明白它的配置,期间犯了好多低级错误。Gmond的默认数据收集的方式有组播和单播两种,默认是组播,组播的好处是配置非常简单, 但是对网络的可适应要差些。我们的机器由于有跨机房的问题,所以用的是单播的方式。

生成默认的gmond配置文件

1
gmond --default_config > /etc/ganglia/gmond.conf

我的配置文件的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
globals {
  daemonize = yes
  setuid = yes
  user = nobody
  debug_level = 0
  max_udp_msg_len = 1472
  mute = no
  deaf = no
  allow_extra_data = yes
  host_dmax = 86400 /*secs. Expires (removes from web interface) hosts in 1 day */
  host_tmax = 20 /*secs */
  cleanup_threshold = 300 /*secs */
  gexec = no
  send_metadata_interval = 120 /*secs */
}

cluster {
  name = "cluster" /*这个字段是集群的名称,对应gmetad的data_source的名称 */
  owner = ""
  latlong = "unspecified"
  url = ""
}

host {
  location = "unspecified"
}


udp_send_channel {
  host = xx.xx.xx.xx
  port = 7649
  ttl = 1
}

udp_recv_channel {
  port = 7649
  bind = yy.yy.yy.yy
}

tcp_accept_channel {
  port = 7649
}

遇到的一些问题

  1. gmond会把一些错误日志打到系统日志里面,如果有问题可以关注下系统日志
  2. gmond -d 10可以打出debug信息

参考资料

Vrs Hedwig入门

| Comments

hedwig(Harry Potter’s owl. A messager.)是一个分布式服务的框架,用户使用它可以很方便的开发分布式服务,并使用这些服务。它主要有以下功能:

  • 统一的服务注册中心
  • 统一的服务管理平台
  • 统一的服务监控平台
  • 使用protocol buffer或者thrift作为消息格式,支持多种语言调用。
  • 服务可动态扩展
  • 负载均衡
  • 服务的容错容灾

下面对hedwig的使用做一个简单的入门介绍.

hedwig支持[protocol buffer]和thrift作为消息格式,两种格式的服务开发和调用略有不同,下面分别讲述。

Protocol bufffe服务

服务Provider

  1. 定义消息格式
example.proto
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.qiyi.vrs.hedwig.examples.pb;

option java_package = "com.qiyi.vrs.hedwig.examples.pb";
option java_outer_classname = "Calculator";

message CalcRequest {
    required int32 num1 = 1;
    required int32 num2 = 2;
    required Operation op = 3;

    enum Operation {
      ADD = 1;
      SUBTRACT = 2;
      MULTIPLY = 3;
      DIVIDE = 4;
    }
}

message CalcResponse {
    required int32 result = 1;
}
  1. 使用protoc生成代码
1
protoc --java_out=../java example.proto
  1. 定义服务接口
CalculatorService.java
1
2
3
4
public interface CalculatorService {

    Calculator.CalcResponse calculate(Calculator.CalcRequest request);
}
  1. 实现服务
CalculatorServiceImpl.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class CalculatorServiceImpl implements CalculatorService{

    @Override
    public Calculator.CalcResponse calculate(Calculator.CalcRequest request) {
        int num1 = request.getNum1();
        int num2 = request.getNum2();
        Calculator.CalcRequest.Operation op = request.getOp();
        System.out.println("calculate(, {" + op + "," + num1 + "," + num2 + "})");
        int val = 0;
        switch (op) {
            case ADD:
                val = num1 + num2;
                break;
            case SUBTRACT:
                val = num1 - num2;
                break;
            case MULTIPLY:
                val = num1 * num2;
                break;
            case DIVIDE:
                if (num2 == 0) {
                    throw new IllegalArgumentException("num2 is zero for divide operation");
                }
                val = num1 / num2;
                break;
            default:
                throw new IllegalArgumentException("unknown operation");
        }
        return Calculator.CalcResponse.newBuilder().setResult(val).build();
    }
}
  1. 启动服务,服务启动时会自动在服务中心注册
CalculatorServiceImpl.java
1
2
3
4
5
6
7
8
9
public class PbCalServer {

    public static void main(String... args){
        HedwigServer server = HedwigServer.getServer();
        server.registerService(ServiceConfigFactory.createServiceConfig(CalculatorService.class,
                CalculatorServiceImpl.class));
        server.start();
    }
}

服务调用

调用服务前你需要获取服务的接口类和接口名称,然后就可以调用服务了。

CalculatorServiceImpl.java
1
2
3
4
5
6
7
8
public class PbCalClient {

    public static void main(String... args){
        CalculatorService service = ServiceHelper.getPbService(CalculatorService.class);
        Calculator.CalcRequest request = Calculator.CalcRequest.newBuilder().setNum1(10).setNum2(20).setOp(Calculator.CalcRequest.Operation.ADD).build();
        System.out.println(service.add(request));
    }
}

Thrift服务

Thrift服务Provider

一. 定义服务接口

example.thrift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
* example.thrift
* show how to use thrift rpc
*/
namespace java com.qiyi.vrs.hedwig.examples.thrift
namespace py com.qiyi.vrs.hedwig.examples.thrift

enum Operation {
  ADD = 1,
  SUBTRACT = 2,
  MULTIPLY = 3,
  DIVIDE = 4
}

exception InvalidOperation {
  1: i32 what,
  2: string why
}

service Calculator {

   i32 calculate(1:i32 num1, 2:i32 num2, 3:Operation op) throws (1:InvalidOperation ouch),

}

二. 生成代码,thrift提供了代码生成工具,执行下面的命令生成java代码

example.thrift
1
thrift -gen java -out ../java exmaple.thrift
  1. 实现服务

thrift的代码生成工具会自动生成接口类,直接实现接口就可以了

example.thrift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class CalculatorServiceImpl implements Calculator.Iface{

    @Override
    public int calculate(int num1, int num2, Operation op) throws InvalidOperation{
        System.out.println("calculate({" + op + "," + num1 + "," + num2 + "})");
        int val = 0;
        switch (op) {
            case ADD:
                val = num1 + num2;
                break;
            case SUBTRACT:
                val = num1 - num2;
                break;
            case MULTIPLY:
                val = num1 * num2;
                break;
            case DIVIDE:
                if (num2 == 0) {
                    InvalidOperation io = new InvalidOperation();
                    io.what = op.getValue();
                    io.why = "Cannot divide by 0";
                    throw io;
                }
                val = num1 / num2;
                break;
            default:
                InvalidOperation io = new InvalidOperation();
                io.what = op.getValue();
                io.why = "Unknown operation";
                throw io;
        }
        return val;
    }
}
  1. 启动服务
example.thrift
1
2
3
4
5
6
7
8
9
10
11
public class ThriftCalServer {

    public static void main(String... args){
        HedwigServer server = HedwigServer.getServer();
        server.registerService(ServiceConfigFactory.createServiceConfig("ThriftCalculatorService", 8886,
                ServiceConfig.ServiceSchema.TCP,
                 ServiceConfig.ServiceType.THRIFT, Calculator.Iface.class,
                CalculatorServiceImpl.class));
        server.start();
    }
}

thrift服务调用

example.thrift
1
2
3
4
5
6
7
public class ThriftCalClient {

    public static void main(String... args) throws TException {
        Calculator.Iface service = ServiceHelper.getThriftService("ThriftCalculatorService", Calculator.Iface.class);
        System.out.println(service.calculate(10, 20, Operation.ADD));
    }
}

其他

  1. 服务端支持spring,需要在配置文件hedwig.properties中填加如下内容
1
2
hedwig.spring.enable=true
hedwig.spring.config=spring/vrs_all.xml

hedwig.spring.config可不填,默认会从META-INf/spring/下读取所有的配置文件