线上运维过程中切日志用了logrotate,但无奈日志打的太多,一天的日志几十G难以分析,遂决定改成每小时切分一次日志。从logrotate的说明看有hourly的支持,但是直接把daily改成hourly之后启动报错:

unknown option 'hourly'

感觉是logrotate 3.8.7的版本不支持hourly语法

rpm安装logrotate高版本的包提示缺少fillup和其他的依赖,同时glibc的版本也要求高版本。于是下载了logrotate的源码安装,以最新版本3.9.2为例

./autogen.sh
./configure

提示缺少libpopt头文件,下载了libpopt 1.5的源码安装,提示libtool版本不对。我擦嘞。

后来突然想到libpopt是debian下的命名,试一下centos下

yum install popt-devel -y

搞定。于是继续源码安装,直接用logrotate的官网版本好了。

yum install popt-devel -y
wget https://fedorahosted.org/releases/l/o/logrotate/logrotate-3.8.6.tar.gz
tar zxvf  logrotate-3.8.6.tar.gz
cd logrotate-3.8.6 && make && make install

这个版本是可以稳定使用的。中间试了几个别的版本,3.8.3还是不支持hourly语法,3.8.5支持了语法,但是测试的时候

logrotate -d /etc/logrotate.conf

有core,跟进源码去感觉是依赖bug,换到3.8.6终于ok了…回去看作者更新日志:

3.8.4 -> 3.8.5
     - Improved rotation during daylight saving time and between timezone
       changes.
     - Fixed ACL setting problem caused by ext3 erroneously reporting ENOSYS
       instead of ENOSUP.
     - Do not continue with rotation if state file is corrupted.
     - Make logrotate.status creation atomic.
     - Allow "hourly" rotation. See manpage for more information.
     - Use "/bin/echo" in tests. Fixes tests execution in Dash.
     - Do no try to parse config files bigger than 16MB.
     - Improved manpage consistency and formatting.
     - Fix race condition between acl_set_fd() and fchmod().

好久没写写自己心里的感受了。大概自从毕业工作之后,每天忙很多的事情,就不再有心情去体会一些事情。习惯了忙碌于工作,在这个城市的街头、地铁和公交站穿行,每天想的是五环的房价、今天有没有雾霾和路上堵不堵,只有夜深人静的时候才会想起以前的日子。

想起北航的参天大树,想起以前的同学,想起清华的荷塘,想起校园里美好的日子和可爱的女生,想起晚上安静的校园和校园外的车水马龙。大学的生活确实是人生最美好的几年,让你无论什么时候回想起来都觉得心底柔软、岁月静好。

有时候觉得自己迷失在时间里了。每天早晨醒来,想的都是匆匆逝去的时间和忙不完的事情。停下脚步,听见风穿过的声音,才觉得时间的洪流里面,也有这些许的温存,有难得的避风港,让灵魂停下来歇息一下。哎,我曾经是多么感性的一个人啊…

物是人非。尽量不去想这些事情。每每回忆以前都觉得会触动心里的痛点,看时光匆匆流去,那些逝去的画面,变得越发模糊,而又无可奈何。于是习惯了让工作占据思想,理性太多而湮没感性,这如何又不是一种躯壳。生活节奏已经进入快车道,每每停下来的时候都会觉得不安,这偶然的感性就变得异常奢侈。

入夜。这座熟悉而陌生的城市,又一次慢慢进入梦乡。同一面蓝天下的人啊,有人睡去,有人醒着,有人睡不着。窗外灯火阑珊,寂寞的人,愿你在时光中找到温暖。

作为获取文件名和文件路径的函数,dirname和basename的签名是:

#include <libgen.h>

char *dirname(char *path);

char *basename(char *path);

之前没注意的地方是这个函数的输入输出都不是const的,也就意味着这个函数调用过程可能会修改char*指向的string内容。所以直接输出一个不可变字符串是不行的,同理,也要考虑这个非const函数会破坏入参。也就是:

char *str = "/abc/def";
printf("%s\n", dirname(str));

会core掉。而

#include <stdio.h>
#include <libgen.h>
int main(int argc, const char *argv[]) {
  char str[] = "/abc/def";
  printf("%s\n", dirname(str));
  printf("%s\n", basename(str));
  printf("%s\n", str);
  return 0;
}

输出结果是:

/abc
abc
/abc

还有一个更有意思的问题….如果对同一个path先后调用dirname和basename,那么返回的只有一个是对的….因为源已经被修改了。反过来可以。

path在执行过程中被修改了。C系的函数很多面临这个问题,如果不这么做的话要申请一块额外的内存,然后返回,而释放这块内存的工作得调用方完成,这种情况下,内存泄露的可能性很大,所以很多c库函数的选择是用全局变量(getopt等)或者修改入参(basename等)。

最近在琢磨MVCC和悲观乐观锁的问题,感觉有些以前学习的点没有串联起来,主要是隔离级别和锁相关的,这里总结思考一下:

  1. 两阶段锁解决了什么问题

    两阶段锁主要解决事务调度的可串行化,保证了调度是正确的。一个简单的例子参考度娘这里。

  2. 实现两阶段锁对应了什么隔离级别。

    两阶段锁保证了基本的隔离级别正确性,RC之上的隔离级别(包含RC)都需要至少保证两阶段锁。一个例外是如果where条件不走索引的话,是可能全表加锁的,这种情况下mysql为了性能提前解锁了不满足条件的行,参见这里

  3. 各个隔离级别对应的加锁策略。

    这个比较简单了:

    • RU:读加S锁,写加X锁,完成即可释放。
    • RC:读加S锁,写加X锁,读锁完成可释放,写锁一直到事务完成再释放。
    • RR:读加S锁,写加X锁,读写锁都一直到事务完成再释放。
    • SE: RR基础上再加范围锁。
  4. select如何防止丢失更新。

    按照何登成博客里面的定义,可以区分MVCC下两种读。

    快照读

    • select * from table where ?;

    当前读。

    • select * from table where ? lock in share mode;
    • select * from table where ? for update;
    • insert into table values (…);
    • update table set ? where ?;
    • delete from table where ?;

    快照读级别下,写事务可能丢失更新,因为select并不阻塞写,两个读写事务可能基于同一个快照点。当前读级别下,写阻塞读,所以涉及同一行的读写事务一定是串行的。不会丢失更新。

    基于乐观锁的方式下,也不会丢失更新,因为检查到更新可能被覆盖的操作都会回滚(打回重试)了。

  5. MVCC和锁(悲观乐观)的实现方式下,隔离级别是怎么实现的?

    MVCC主要针对冲突数据的处理,乐观锁、悲观锁决定了最终原子的更新一行的方式。

    MVCC加乐观锁的方式基本思路如下:

    定义一个keyValueSet,Conditional Update在此基础上加上了一组更新条件conditionSet { … data[keyx]=valuex, … },即只有在D满足更新条件的情况下才将数据更新为keyValueSet’;否则,返回错误信息。引用

    MVCC加悲观锁主要是提供了不加锁的读。按何登成的文章里看,就是快照读+当前读。快照读级别下,直接按照版本读就行,当前读级别下,如果有锁冲突还是要加锁。

    在ob里的实现上看比较明显,行的修改增量组织为一棵B树,历史版本表现为B树叶子节点上挂的链表。链表的按照版本号串接起所有历史版本,全局Publish version决定了当前可见的最新版本。

    在快照读级别下,select不需要加锁,只需要每次按照publish version去链表遍历,找到可见的结果并返回。如果不修改transaction consistency set的情况下,这种读取可能导致两次读结果不一致,不满足RR或者SI(Snapshot Isolation)。OB0.5增加了一个readonly snapshot的级别,可以提供对一个快照的只读操作,保证了多次读取的一致,但是没有snapshot级别不加锁的读写事务(快照写),毕竟基于一个旧的快照做写操作可能使新的提交丢失。这里要么类似乐观锁验证一下版本,要么加锁来延迟读写。

    当前读级别下,select也要加锁直到事务结束释放,跟mysql的实现一致。

    总结来讲,如果把当前读看成写事务的话,那么ob事实上是读操作只看版本号,写操作只看锁。如果只考虑当前读和写操作的话,那么相当于有冲突的时候读锁延迟了写操作,写锁延迟了读操作,保证了调度的串行。这种情况下,多次读取的结果是一致的。

  6. select for update在mvcc下如何实现。

    道理是一样的,跟mvcc没什么关系,select for update实际上相当于写事务(select的时候加写锁,直到事务结束再释放,update操作本身也是一样的过程,先检索符合条件的记录加锁,再修改并提交,这样才能保证是原子的)

  7. 分布式环境下的快照读

    单点的快照相对来讲比较容易,因为每次可以取一下publish version,来决定什么是可以读取的,但是分布式环境下,每个点读取的时候不能总去看全局publish version(似乎也可以), 这样本地读取的时候可能由于时间偏移出现全局本地开启事务的时间戳大于全局publish version的问题,

工作里遇到一个问题,想把mysql的crc直接封装一下让go来调用,因为查表的crc计算性能实在是不快,对我们这种文件系统的大报文计算来看,crc容易变成瓶颈。大概性能对比如下:

两线程crc32查表 O2优化
time_elapsed:471.474976s
total_size_m : 200000.000000M
crc rate : 424.200684m/s


两线程crc64指令 O2优化
time_elapsed:24.877853s
total_size_m : 200000.000000M
crc rate : 8039.278809m/s

c这一端的计算比较容易,go的crc默认是查表, 所以存在不兼容的问题。出于兼容和性能考虑,用cgo封装一下。

首先在c语言下把crc打包成lib库,考虑go的移植,直接用静态库比较好。go这边调用如下:

package s3crc

import "unsafe"

// #include <stdlib.h>
// #cgo CFLAGS: -I../../../../src/lib
// #cgo LDFLAGS: -L../../../../src/lib -lcrc
// #include "s3_crc64.h"
import "C"

func s3_crc64(buf string, len int64) uint64 {
    cbuf := C.CString(buf)
    defer C.free(unsafe.Pointer(cbuf))
    return uint64(C.s3_crc64(unsafe.Pointer(cbuf), C.int64_t(len)))
}

遇到的坑主要有:

  1. cgo不支持ccache,所以这个比较扯。习惯使用ccache的同学(好样的)建议export CC=gcc
  2. 所有类型都需要做转换。go的类型对应到c的类型之后,都在C命名空间下。
  3. 静态库路径需要指明,go的buffer映射到c下面之后,需要考虑释放的问题。参见文献

最近在总结一些协议这么设计的原因,比较杂,纯做记录:

  1. 选主过程相当于Paxos的prepare过程:

    选出的Leader得到了多数派的同意,相当于Paxos一阶段完成,由于当前Term下投过票的Server保证不再接受小于等于当前Term的,所以保证了选主最终只能选出一个。

    不同于Paxos的地方在于这一轮Term下,follower除非Lease过期,否则不会发起新的选主,也就是发起Epoc更大的提案。Raft可以认为MultiPaxos的一种应用,MultiPaxos可以认为是合并了Prepare阶段的SingleDegreePaxos.

  2. raft读一致性和选主timeout

    raft协议本身由于主只是续约了follower的lease,所以正常情况下follower不会在lease过期前发起新的选主,但是master网络分区情况下,master本身不维护自己的lease,follower lease过期会发起新的选主从而产生新的主。这时候旧主的写一定不能成功,但是旧主的读不受影响。

    以前在ob的时候阳老师设计的选主协议里面通过主lease来解决了这个问题,只是当时没体会到原因在这里。协议要求补充限制:主在自己的lease过期后放弃主的身份,主维持心跳的过程中续约follower的lease,根据rpc的返回,只有收到多数派的回应并且term没有更大的才续约自己的lease。新启动的server在follower状态至少等一个lease周期才发起选主,来让可能的旧主过期。

  3. paxos是两阶段提交的一种场景。

    两阶段提交的一阶段也是获得所有参与者的同意。只不过是所有而不是多数派,一阶段通过之后,相当于决议已经形成,所以后一阶段不会产生新的决议。2阶段可以完全异步来做,如果2阶段失败,不影响形成的决议。这意味着两阶段提交是非抢占式的。

上次见刘鹏老师可能还是搜狐实习的时候,后来刘鹏走了,峰扬走了,整个团队改做推荐系统,最后又回到效果广告,算是绕了一个圈吧。买了本计算广告,更多的是给自己一个念想,念念不忘必有回响么。

主要记录几个知识点或者观点:

  1. 竞价的历史性作用、gd广告和竞价广告的诞生原因

    保量(guarantee delivery)广告相对于品牌广告(CPT),主要解决了流量浪费的问题,CPM的售卖方式相对增加了广告流量的利用率(比如按照性别的售卖,大致可以增加20-25%的收益)。然而保量广告相对来讲对流量的利用并不极致,大部分gd系统都面临超卖或者保守售卖的问题,可能会导致补量或者浪费,在线流量分配和优化也是个比较复杂的问题。所以才有了竞价广告,根据Nash均衡,广告价格是出价方博弈的结果。这样就可以让广告效益最大化,区分优质流量,同时长尾的流量也有变现的机会。

    当然也就引入了程序化交易。程序化交易是工业化的结果,但是对于广告来讲,良莠不齐的广告对于场景融入是非常不利的。程序化交易在移动时代带来了更不好的用户体验。

  2. 搜索广告的场景和精妙之处

    搜索广告实现了两点最精巧的地方:精确判断用户意图,跟媒体内容完美融入,所以历来是兵家必争之地。

  3. 移动广告、gps定位、地理位置

    移动时代,广告不能照搬PC时代的经验。移动设备屏幕上广告的体验是很差的。移动时代对于用户意图的判断实际上是更精准了,比如可以通过GPS等判断用户所处的位置,推送更符合场景的广告。但是广告体验就更加困难了,这是有利有弊的地方。

  4. O2O是潜在的广告市场

    O2O商家是之前几乎不可能成为广告主的一个群体。O2O广告有相当的地域要求,所以对Targeting的要求更高,相对PC时代,移动时代才是O2O广告能发力的地方。智能手机的普及提供了更精准的Targeting,使这部分人群有可能成为潜在广告主。

Bibliography:

[1] 怎样向非专业人士专业地解释「纳什均衡」?, https://www.zhihu.com/question/19804990

今天遇到一个设计上没考虑好的问题,记录一下。

之前Libeasy的逻辑如果一个连接上有超时的报文的话,整个连接会destroy掉。考虑网络拥塞的情况,如果AB两个报文同时在等待发送,A报文先进入发送队列(链表,非TCP发送buffer),B后进入,而A超时时间长,B立即超时,那么清理掉B的待发送报文的时候,如果destroy掉连接,那么本来可以发送出去的A报文就被强制失败了。

考虑这种情况做了一点修改,让超时的报文被清理掉的时候不会destroy连接。这样编码的时候需要指定一个报文被编码出来的buffer是属于哪个会话(session)的,同时记录一下每个session对应的最后一个buffer位置。清理的时候可以从上述位置回溯到不属于当前session的buffer或者到头部为止。看似没啥问题。今天发现了如下的bug:

考虑如果一个会话编码的多个buffer(或者一个buffer)被部分发送完毕(write返回大小为准),这时候会话超时,这个会话所属的所有编码过的buffer都被干掉,但是连接并没有被destroy,之后的报文继续发送的话,客户端实际上收到了不完整的报文,相当于之后的TCP流都错位了。这种情况下,destroy连接是明显更安全的做法。

所以还是要考虑周全啊。

最近其实是乏善可陈的,项目差不多写完,修修补补,提升下性能。其他的事情诸多不顺,看房价蹭蹭的涨,看工资慢慢的爬,感情纠结一团,其他的么,牙疼,估计牙周炎犯了。

然后今天炎泼说要走了,陈闯二话没说也跟着走了,先知先觉的后知后觉的都有各自的想法,只是我比较懵逼,可能最近事情多了,精力都不在这上面。每个人有自己的选择,目前看互联网差不多进入稳定期了,风口还有,但是能飞起来的猪不多了,剩下的人,要么在大公司朝九晚五,要么在创业公司等期权等上市,少数人还追求着理想,不过最后如果没有回到前面两个状态的话,一般是创业去了。

工作几年觉得介于目的性明确和不明确之间吧。做技术的明显分了两派,以KPI为核心的和以兴趣为核心的,很多时候这两个不是不兼容的。目的明确的,一切跟个人发展不相关的都推掉,目的不明确的,还是选择follow heart多一点。没有褒贬,个人选择而已。最好是大方向下,找点自己喜欢做的,否则工作容易无趣啊。不过这种坑也似乎不好找,当然不排除有人兴趣特别,比如我就是向往资产阶级腐朽堕落的生活方式…

博客好久不写了。感觉技术文章吧,一旦开头容易虎头蛇尾,扯扯淡吧,微博微信朋友圈足够了。无聊的时候,也就是刷刷知乎找朋友扯扯淡,看看有没有新梗,翻墙看看。最近整个社会都压抑吧,连门缝里塞小纸片的都少了,可见人民群众都进入到了一种就这样吧还能咋滴的状态里。

所以我还是一直觉得要找到一点精神的寄托,哪怕是打游戏呢(没鄙视的意思打游戏挺好)。最近听许巍的新歌《生活不止眼前的苟且》,觉得似乎又回到校园了,回到北航的主楼,回到绿园的树荫和荷塘,想起陈旧的宿舍楼里阳光洒在斑驳的墙面,想起清华园的午后,想起月下的荷塘,想起东门外五道口的火车道…

    生活不止眼前的苟且
    还有诗和远方的田野
    你赤手空拳来到人世间
    为找到那片海不顾一切

所以我这么感性的人是不是应该去搞音乐…毕竟钢琴比键盘也就是多了几十个键而已,用惯了Emacs的话,和弦弹起来也没什么按不过来的。

都是幻觉,都是幻觉…

最近在做http返回的时候发现go的xml生成(marshal)会把引号转义,如下:

    <?xml version="1.0" encoding="UTF-8"?>

    <Part>
      <PartNumber>1</PartNumber>
      <LastModified>2016-03-18T08:24:25.000Z</LastModified>
      <ETag>&#34;0c78aef83f66abc1fa1e8477f296d394&#34;</ETag>
      <Size>12121</Size>
    </Part>     

看了下源码,marshal函数的实现就会默认转义。这样就只能加一个Type,不直接用string,然后定义这个Type的marshl函数。上网搜了一下发现可以找个方法绕过去:struct的修饰可以指明当前的struct field不做转义,直接输出。

    type Part struct {
         XMLName      xml.Name `xml:"Part"`
         PartNumber   int
         LastModified string
         ETag         string `xml:",innerxml"`
         Size         int64
    }

这样可以直接在序列化的时候传自己拼成的ETag值。比如:

     Part{PartNumber: 1,
         LastModified: S3TimeFormat(GetCurrentTime()),
         ETag:         `<ETag>"acbd18db4cc2f85cedef654fccc4a4d8"</ETag>`,
         Size:         12121}

输出结果满足要求:

    <?xml version="1.0" encoding="UTF-8"?>

    <Part>
      <PartNumber>1</PartNumber>
      <LastModified>2016-03-18T08:32:39.000Z</LastModified><ETag>"acbd18db4cc2f85cedef654fccc4a4d8"</ETag>
      <Size>12121</Size>
    </Part>

换了个ubuntu的环境想编译一下vim,为了防止不兼容手动编译了python2.7,之后把改过的vim源码放上去编译发现有问题:

undefined symbol: PyUnicodeUCS4_AsEncodedString

于是换回标准的vim源码还是一样的问题,我擦嘞。上网搜一下说Python模式是UnicodeUCS2的支持,从源码里面直接grep一下这个函数发现是有的:

Include/unicodeobject.h:# define PyUnicode_AsEncodedString PyUnicodeUCS4_AsEncodedString

那只可能是没有开启编译选项了,./configure —help发现有如下一项:

  --enable-unicode[=ucs[24]]
                      Enable Unicode strings (default is ucs2)

configure到ucs4重新编译python2.7,完美。没毛病。

本文源码以libev4.20为准,其他版本大同小异。

libev是广泛使用的事件库,是一个功能强大的reactor,可以把Timer、IO、进程线程事件放在一个统一的框架下进行管理。如果有其他的事件触发需求也可以改libev源码把该事件加入libev的框架中(当前前提是得理解libev的设计)。有文章说libev性能比libevent好,没实验过,但是从源码角度看,libev要更简洁,当然更费解一点。作者为了追求代码的整洁和统一使用了大量的宏,造成了阅读的不便。这里我们从宏观分析一下libev的设计实现,然后穿插分析一些小的trick。旨在学习总结libev设计中优雅的地方。

基本概念

首先是一些主要的概念和数据结构。

libev通过定义watcher来关注一个事件,并且把事件类型和对应的毁回调函数关联起来。libev定义了多种事件类型,同时可以在框架中自己添加感兴趣的事件,libev保证了事件触发的顺序性,并在多线程环境下保证事件的串行触发。

每一种类型的watcher都包含几个基本的成员,通过EV_WATCHER和EV_WATCHER_LIST宏实现。EV_WATCHER_LIST比EV_WATCHER多了一个纸箱下一个watcher的指针。EV_WATCHER_TIMER是定时器的基类,多一个timestamp。这几个宏这里留一个小的trick分析,在后面阐述。

首先修改/ssh/sshd_config,把Port 22解注释,然后加一行Port xx。之后修改 /etc/sysconfig/iptables,加入该端口的Rules:

-A INPUT -m state --state NEW -m tcp -p tcp --dport xx -j ACCEPT  

重启即可。

/etc/init.d/sshd restart

这么做的目的是如果出现问题,还有一个端口可以上去修改。登录的时候需要对应的指定一下端口:

ssh -p xx user@host
scp -P xx ... user@host

补充两个SSH技巧:

  1. 客户端配置中转,主要是通过跳板机登录:

    Host xx
    HostName 192.168.1.1
    User xx
    ProxyCommand ssh -q xxx@login2.xxx.xx nc %h %p
    
  2. 保持会话。 ssh会在.ssh目录下生成一个会话选项,下次登录同一个server公用会话,不需要验证。

    Host *
    ControlMaster auto
    ControlPath ~/.ssh/master-%r@%h:%p
    

    上面会话共享,所以不能关闭会话。可以通过 ssh -fN xxx 把第一个会话放到后台不退出

某天ReviewBoard客户端突然用不了,使用 rbt post的时候报错:

from six.moves.urllib.parse import quote
ImportError: No module named urllib.parse

乍一看以为什么包被卸载了。于是pip install six —upgrade,无果。pip uninstall RBTools再重新安装RBTools,也无效。

查了一下six是python2、python3的兼容包,直接修改源码,不要兼容了,发现用到的地方好多,改不过来(ps,兼容python2、python3真不容啊)。

查看six的版本,发现跟本地一样的,本地没什么问题。说明不是six的问题。直接在命令行from six.moves.urllib.parse import quote,发现本地ok,服务器上不行。

这就比较扯了,同样的版本,本地可以服务器不行。直接卸了重装:

pip uninstall six #注,这里卸载了1.9的six
pip uninstall six #日,还有一个1.2的six,不知道pip list为啥显示不出来。
pip install six
easy_install RBTools

然后就ok了。所以还是要习惯在virtualenv下搞啊…

acceptor: a([epoc:4, value:n]) b[epoc:3,value:y] c[epoc:6,:value:y]

proposor: x5, x7, (x4, x3, x6)

prepare:x5[a, b], x7[b, c]

commit:x[4,n] y[6, y]

一共有5个proposer,

1)初始时 x4 (prepare)–> a, x3 (prepare)–> b,x6 (prepare)–> c, 2) x4, x3, x6 挂掉 3)x5(prepare)–> a, b 4)x5(commit[epoc:4, value:n])–>a, b 5) x7(prepare) –>b, c 6) x7(commit[epoc:6, value:y])–>b, c

gitbook是nodejs实现的用来帮助书写电子书的,很多开源的书籍项目是基于gitbook的。gitbook的编写语言是markdown,书籍遵循一定的格式完成之后可以通过gitbook发布为各种版本,比如pdf,mobi等电子书格式,或者发布为静态的website,挂到github pages上,对于一些开源项目来说,这是很方便的书写帮助文档的方式。gitbook同样有一个paas的平台,可以允许多人协作在线完成一部电子书

首先安装gitbook

npm -g install gitbook
npm -g install gitbook-cli
npm -g install ebook-convert

第三个是安装生成电子书的插件,但是这里并不会安装bin文件,需要手动安装Calibre。Mac下可以安装Calibre的app然后链接一下bin:

ln -s /Applications/calibre.app/Contents/MacOS/ebook-convert /usr/local/bin

这样就可以生成pdf/mobi等格式的电子书了。

使用方式可以通过gitbook help来查看。主要是如下几个:

gitbook build [book] [output]   生成电子书,通过--format指定输出格式,默认输出为website
gitbook pdf [book] [output]     生成pdf电子书
gitbook epub [book] [output]    生成epub电子书
gitbook mobi [book] [output]    生成mobi电子书
serve [book]                    生成并开启http server预览
init [directory]                根据summary建立基本目录结构
install [book]                  安装依赖和插件

Summary的基本样子见这里:

# Summary
* [Introduction](README.md)
* [入门](getting_started/README.md)
   * [初识](getting_started/what_is_it.md)
   * [安装](getting_started/installing_es.md)
   * [API](getting_started/api.md)
   * [文档](getting_started/document.md)
   * [索引](getting_started/tutorial_indexing.md)
   * [搜索](getting_started/tutorial_search.md)
   * [汇总](getting_started/tutorial_aggregations.md)
   * [小结](getting_started/tutorial_conclusion.md)
   * [分布式](getting_started/distributed.md)
   * [本章总结](getting_started/conclusion.md)
* [分布式集群](distributed_cluster/README.md)

工作两周年了,感觉这两年走了不少弯路,也学到不少东西,最近看到日照的周年总结,觉得也应该写写。而且看看日照当时的情况,觉得跟我现在也差不多,同样处在职业的选择期,一个明确的规划还是很重要的。

总结

2013年进入支付宝Oceanbase组,待了一年半的时间吧,第一年主要在0.5上修修改改,第二年换到了UPS组,参与1.0的设计。从第一年的情况看,基本是完成了一个不错的职业素养培训,有了分布式的基本概念,处于了解熟悉的阶段。第二年开始对分布式协议有了一些认识,算是入门了吧。总结两年还是学到了一些东西,但是也算是付出了一些代价。出来之后看之前的组,觉得对应届生还是个不错的选择,ob有点像一个学校,闲的时候有不错的学习机会,就是加班太多了。

后来身体原因离职,来搜狐大数据中心,开始做推荐系统相关的东西。从那个时候想做算法,搞了半年多吧,有些了解,不算入门,大概处于这样一个状态。对我来说,兴趣总归是第一位的,然而在一个方向上有所积累更重要。这样容易过了几年发现什么都会点什么都不精通。互联网发展这么快,

log4cxx是apache基金会的log开源项目,log4j的c++实现,安装的时候遇到一个小问题:

首先正常安装:

wget http://mirrors.cnnic.cn/apache/logging/log4cxx/0.10.0/apache-log4cxx-0.10.0.tar.gz
tar zxvf apache-log4cxx-0.10.0.tar.gz
cd apache-log4cxx-0.10.0
./configure
make

报错libdb-4.3.so 格式错误。开始以为是文件损坏了,查了一下这个文件

yum provides */libdb-4.3.so

提示在db4里面,重新安装db4

yum reinstall db4

回去还是报错。后来看了一下格式,貌似链接到了32位版本。修改软连接:

cd /usr/lib/
rm libdb-4.3.so
ln -s ../../lib64/libdb-4.3.so .

同样处理另外一个库

rm libexpat.so
ln -s ../../lib64/libexpat.so.0.5.0 libexpat.so

应该就OK了。

最近突然想到这个东西,以前Oceanbase的UpdateServer提交的时候是做了group commit的。基本思路如下:

  1. 并发小的时候,超过一个时间窗口就直接提交
  2. 并发大的时候,等请求填满一个buffer再一起提交。

实现上可以考虑如下伪代码:

while true:
    start timer
    if buffer full or timer reach limit:
        commit
        reset timer
    else
        wait and receive

广泛运用在一些需要组合请求的地方,比如一些rpc因为历史原因(嗯)一直单条请求,不能改协议的情况下,可以考虑转发一下,做个缓冲。当然这种情况下一般是通过队列来缓冲请求压力。

事实上我也只是带实习生而已。但是感觉上之前更多的是把自己的任务分出去,而现在是开始跟一些人一起完成一个任务。主要的不同是之前做的事情是在个人工作量里面,自己做也只是慢一点,有时候甚至自己做反倒比分出去更快。现在做的事情已经超过一个人工作量了,必须发挥团队的能动性了,而且之前也只是对自己的工作负责,现在你要负责的是一个团队的KPI了。

主要有几点感觉:

  1. 分配好工作。需要能力的,需要经验的,需要稳定细心的,每个人的风格不一样,应该做的工作也不一样。
  2. 规范流程。大部分加班或者事故都是能从流程上避免的,在设计开发测试上省下来的时间最终都会在上线修bug处理问题的过程中补回来。
  3. 做好计划和项目管理。做一个包含不确定性和风险的事情最重要的是路线图。实现的过程就是把抽象的东西一点点具象化的过程。所以计划上要能够自顶向下,逐步求精。把写程序的思路用进来。
  4. 最后就是能力的问题了。统筹考虑是种需要一点点培养的能力。这个也就只能慢慢来了。何况只要不是纯做管理,你就一定有自己的工作,自己的KPI,协调好自己工作和他人工作安排的关系,还是非常重要的。

最后补一个我理解的开发流程图:

一道题目,本来觉得挺简单的,后来卡在一个小问题上。mark一下:

给定一个数据库表,存了所有话题的关系,形式是:parent->child,表示前面是后面话题的父话题。根据这个关系构建出话题树并打印。

eg:
输入:
    a b
    c a
    d e
    e f
    r c
    r d
输出:
     r
       c
         a
           b
       d
         e
           f
题目隐含:
    1. DAG:有向无环
    2. 节点不重复

直观看类似于Graphviz的算法,只不过保证了是棵树。C++实现上可以直接通过树来做,这里用了Python。

Phibricator是Facebook开源的一体化项目管理、代码review工具,主要特点是集成度高、界面漂亮。根据搭建的经验看,确实做的非常完善,各种细节用起来像是商业产品,不会像其他的开源产品那么难以配置。这里把配置过程根据回忆写一下:

官方给出过一个一键安装的脚本,但是下载的时候感觉有点问题吧,下载完不是sh而是网页,而且我只有一台机器,环境都已经预先有了,也不想再搭一套LAMP。这里我们以centos5为例:

首先安装httpd + mysql + php,由于centos5的版本太低,yum源里的mysql和php均低于phibricator要求的版本,我们需要先升级yum源,参见参考文献, 不过好像安装的时候没有php53u,而是直接php53

之后把对应的扩展也装了:

yum install php53-mbstring
yum install php53-mysql

项目地址: https://github.com/chenxiaohui/resume

说明

通过文本文件生成简历tex的generator。最终生成pdf还是要靠Latex+Moderncv,环境请自行配置。

模板配置

config.tex 定义了公共头文件,包括包含的宏包,版面的布置和字体语言。

xxtemplate.tex定义了模板。模板语法比较简单,不给出严谨的语法定义了,主要如下两点:

  1. %xxx% 表示一个变量,将来会从cv文件中查找对应名字的变量,找到之后替换这个%xxx%
  2. %for=>xx% %endfor%表示一个循环,xx是section的name,将来会从cv文件中找到对应的section,然后parse section下的每个单独的项目,用结果替换for循环中的内容

    1. for循环中{0}{1}..表示一个for循环变量,cv中section下的条目会被分割成多个变量,顺序依次是0,1,2..,渲染的时候会对应的替换上述{0}{1}变量。如果数量不匹配会报错。
  3. 如上语法部分关键字可以配置,在config.py中。可以酌情修改。

xx.cv包含了简历内容,格式上参考了ini文件格式,但是略有不同。

  1. [section name]顶一个了一个section,对应简历中一个部分。section name的显示名称是在template里面写好的。这里的name只是给程序使用的,可以跟显示名称一致也可以不同。
  2. 不包含在某个section下的条目通过 key = value的方式定义。不能跨行,程序parse的时候只找第一个等号,后面有空格等符号都不影响。
  3. section下的条目每条可以包含多个字段,字段之间默认用竖线(|)分割(可修改)。字段的数量需要跟模板中对应section下for循环体中变量的数量一致。

程序中给出了几个实例,分别是英文、中文简历的template和cv文件,供参考。

使用

写好对应的cv和模板之后,make就行。open命令可能在mac之外的系统不能使用,建议注掉。

make distclean清空所有文件包括pdf

make again 是为了生成页码,xelatex跑第一遍的时候页码是乱码。

gen.py 文件格式如下:

./gen.py <template-file> <cvfile> <output-file>

参考文献:

[1] http moderncv 的笔记(支持中文), http://www.xiangsun.org/tex/notes-on-moderncv

名词解释:

SeviceConfig:

一个服务的所有配置存在一个目录下

BucketConfig:

 Bucket的配置存在一个单独的子目录中

配置项类型:

  • Integer
  • Long
  • Short
  • Float
  • Double
  • Byte[]
  • Boolean
  • String
  • 自定义配置项 ConfigObject

使用说明:

POM:

<dependency>
  <groupId>com.sohu.adrd</groupId>
  <artifactId>sohu-zk-client</artifactId>
  <version>1.0.1</version>
</dependency>

初始化

ServiceConfig.Instance().init(“ConnectString”, “serviceName”);//如果做测试可以用10.16.3.61:2181

ServiceConfig.Instance().init(“serviceName”); 默认使用肖永磊的zk地址

配置项使用

先给默认值

private static long exploitDiscardTimespan = 7 * 86400L;

可以保存个Instance()的引用

private ServiceConfig serviceConfig = ServiceConfig.Instance();

所有引用到的地方,通过如下方式取配置项(这是默认config):

serviceConfig.getLong(“exploitDiscardTimespan”, exploitDiscardTimespan)

如果是BucketConfig(bucketConfig会在zookeeper上根目录下再增加一个Node,名字是bucket的名字,在此之下存储config)

serviceConfig.Bucket(“bucketName”).getLong(“exploitDiscardTimespan”, exploitDiscardTimespan)

这样server端更新配置的时候,serviceConfig会根据通知刷新自己的配置缓存,下次使用getLong的时候就得到了新的配置。

上述getLong的过程包含了:

  1. 如果是Bucket配置,从Bucket中获取,如果找不到,从Service的配置获取,如果还是没有,返回默认值。

  2. 对于返回默认值的情况,写回Zookeeper。

  3. 如果Zookeeper端有修改,Zkclient会收到消息,重新update本地配置项的缓存。

  4. 如果定义了handler调用handler,如下所述:

如果需要根据配置修改的通知来做出响应(这是默认事件,node_data_change):

ServiceConfig.Instance().addEventListener("task_period", new IEventHandler() {
  @Override
  public void process(ZkEvent event) {
    logger.info("update task period.");
    setDeclaredField(TimerTask.class, ServerTasks.this, "period", ServiceConfig.Instance().getLong("task_period", period));  
  }
});

如果需要删除的通知:

  ServiceConfig.Instance().addEventListener("task_period", EventType.NodeDeleted, new IEventHandler() {
  @Override
  public void process(ZkEvent event) {
    logger.info("update task period.");
    setDeclaredField(TimerTask.class, ServerTasks.this, "period", ServiceConfig.Instance().getLong("task_period", period));  
  }
});

结合SpringFramework:

基本不变,只不过SpringFramework会先填充InitializingBean的域,这些值只有在出默认值的时候才会被采用。
  1. 生成一个重复列表可以通过如下语句:

     [i] * n #这比[ i for _ in range(0,n) ]简洁太多
     同样可以"i" * n来生成字符串
    
  2. map函数。

    可以用来分类函数和其调用参数,对于线程池比较有用。比如

     import urllib2 
     from multiprocessing.dummy import Pool as ThreadPool 
    
     urls = [
             'http://www.python.org', 
             # etc.. 
             ]
    
     pool = ThreadPool(4) 
     results = pool.map(urllib2.urlopen, urls)
     pool.close() 
     pool.join() 
    

建立了一个远程分支,提交到origin上之后,发现之前有一个同样的远程Repository存在,而且名字一样,这就比较D疼了,git branch -a 显示的分支里面的分支提交到了另一个Repository,但是这个Repository已经改名字了,虽然地址一样。

搞来搞去之后发现git checkout远程分支的时候报不存在的问题,删除这个分支的时候同样有这个问题:

unable to delete 'refactor': remote ref does not exist

导致这个分支就这么存在着删不掉了。查stackoverflow有人给出如下的方案

git fetch -p origin

问题是能解决了,但是不太理解为什么。

mac下使用多屏幕的话,经常需要把一个窗口移动到另一个窗口,windows下有快捷键win+shift+左右,linux下貌似可以通过xdotool实现,绑定一个快捷键,xdotool帮助你移动窗口到一个绝对位置。如下:

xdotool getactivewindow windowmove 0 y windowactivate windowfocus
xdotool getactivewindow windowmove 1280 y windowactivate windowfocus

mac下没有找到对应的系统快捷键,虽然可以设定一个zoom键来完成窗口最适化,但是没有快捷键来完成窗口的移动。这里我们通过Moom实现。

Moom是一个窗口管理工具,安装之后只有一个配置页面,但是你会发现鼠标移动到左上角窗口最适化的按钮上的时候会弹出一个窗口管理的提示框。如下:

然后我们配置窗口移动的功能,首先需要一个全局快捷键:

之后可以设置按下全局快捷键之后上下左右键的功能。有移动窗口,半最大化窗口,缩小放大和移动窗口到一个屏幕。

这样就可以先按下全局快捷键,如下:

再通过你定义的快捷键来移动窗口了。

首先介绍一下Jenkins,如果你熟悉自动化构建的话,那么肯定听说过hudson。Jenkins是hudson的开发者跟Oracle撕bi之后,另立门户的一个hudson分支。当然这么说似乎不太靠谱,目前hudson已经不维护了毕竟,而Jenkins的开发社区还是很活跃的,各种插件层出不穷。总的来说,如果你不是已经习惯了hudson并且有一个能用的副本,都应该迁移到Jenkins。

下面说一下如何配置。

Jenkins的安装非常简单,只需要从Jenkins的主页上下载最新的jenkins.war文件然后运行 java -jar jenkins.war。如果需要配置运行参数可以如下设置一些环境变量。

JENKINS_ROOT=/home/harrychen/share/jenkins
export JENKINS_HOME=$JENKINS_ROOT/jenkins_home
java -jar $JENKINS_ROOT/jenkins.war --httpPort=8080 >>output.log 2>&1 &

打开对应url可以看到如下界面

回想一些事情的时候感觉印象最深刻的往往不是一段时间,而是一些瞬间。比如大学毕业晚会的那个晚上,大家喝的一塌糊涂,早晨醒来,看依然灿烂的阳光,那种孤独感是能镌刻很久的。再比如在北航的时候,那时候习惯晚上看书,安静,一个人在三号楼前的空地走走,听学院路上的川流不息的车声。事情已经远去,感觉依然清晰。每个片段好像人生路上的benchmark,只不过留下最深印象的,往往不是所得,而是所感。

windows下post-review遇到一个问题(不是我,不用windows)。python会报错:

UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position xxx: ordinal not in range(128)     

又是编码的问题啊,我不想去看post-review的源码,所以还是改默认环境的源码好了。找到rbtools\utils\process.py,import sys后面加两行:

reload(sys)
sys.setdefaultencoding("utf-8") 

Ok.