Ender's Blog

分布式计算相关理论总结

| Comments

CAP理论

C:一致性 分布式系统中所有的节点在某一时刻数据必须是一致的

A:可用性 指的是分布式系统能够一直对外响应服务。

P:分区容忍性 指在分布式系统各个节点之间网络消息丢失或者延迟依然能够对外服务。

CAP理论断言一个分布式系统只能满足以上三个特性中的两个,由于分布式系统中不能够保证所有节点之间的通信是绝对稳定的,所以P几乎是必选,接下来就C还是A的问题了, 要保持强一致性的话,在所有节点数据没有同步之前系统将会失去可用性,如果要保持高可用性,必然要丢失一些一致性。所以许多分布式系统为了保证可用性,牺牲了 强一致性的特性,只保证系统的最终一致性,例如dns,cassandra等。

Google最近推出的cloud spanner宣称同时提供了强一致性和高可用性,有兴趣的话可以看看这篇文章-谷歌新发布的分布式数据库服务,是要打破CAP定理了吗?

Raft一致性算法

Raft源于Paxos算法过于复杂, 作为替代品而出现,它比Paxos算法更易于理解。

Raft一致性算法通过选举leader、只通过leader写数据然后同步到follower节点来保证强一致性。 这其中leader选举的过程、leader如何同步数据到follower节点以及脑裂的情况处理,这篇文章Raft 为什么是更易理解的分布式一致性算法讲得非常通俗易懂.

使用link连接docker容器

| Comments

首先我们启动一个elasticsearch container命名为elas。

1
docker run -d --name elas -v "$PWD/data":/usr/share/elasticsearch/data localhost:5000/docker.io/elasticsearch

接下来我们需要启动一个kibana container自动连接到elas, 使用link参数可以很容易将elas的网络参数传递到kibana container

1
docker run --name kibana --link elas:elasticsearch -p 5601:5601 -d localhost:5000/docker.io/kibana

link参数在启动kibana container时做了两件事:

  • 在kibana的/etc/hosts加入一条dns记录,将container name、另外一个别名和elas的container id指向elas的ip。
1
172.17.0.3   elasticsearch 6604804b8e3d elas
  • 在kibana里设置一系列elas相关的环境变量
1
2
3
# 进入kibana shell
docker exec -i -t kibana /bin/bash
/# set
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ELASTICSEARCH_ENV_CA_CERTIFICATES_JAVA_VERSION=20170531+nmu1
ELASTICSEARCH_ENV_ELASTICSEARCH_DEB_VERSION=5.5.1
ELASTICSEARCH_ENV_ELASTICSEARCH_VERSION=5.5.1
ELASTICSEARCH_ENV_GOSU_VERSION=1.10
ELASTICSEARCH_ENV_JAVA_DEBIAN_VERSION=8u141-b15-1~deb9u1
ELASTICSEARCH_ENV_JAVA_HOME=/docker-java-home/jre
ELASTICSEARCH_ENV_JAVA_VERSION=8u141
ELASTICSEARCH_ENV_LANG=C.UTF-8
ELASTICSEARCH_NAME=/kibana/elasticsearch
ELASTICSEARCH_PORT=tcp://172.17.0.3:9200
ELASTICSEARCH_PORT_9200_TCP=tcp://172.17.0.3:9200
ELASTICSEARCH_PORT_9200_TCP_ADDR=172.17.0.3
ELASTICSEARCH_PORT_9200_TCP_PORT=9200
ELASTICSEARCH_PORT_9200_TCP_PROTO=tcp
ELASTICSEARCH_PORT_9300_TCP=tcp://172.17.0.3:9300
ELASTICSEARCH_PORT_9300_TCP_ADDR=172.17.0.3
ELASTICSEARCH_PORT_9300_TCP_PORT=9300
ELASTICSEARCH_PORT_9300_TCP_PROTO=tcp

在kibana container里面用到如上dns记录或者环境变量,就可以在容器启动时自动连接elasticsearch.

将这个用法扩展开来可以很方便连接不同容器内的服务。

利用endless实现http Server自更新

| Comments

Endless可以为go http server提供不停机重启的能力,很容易集成到自己的web service里面,只需要用它提供的ListenAndServe就好了。

1
err := endless.ListenAndServe("localhost:4242", handler)

不停机重启的原理大概是通过接收SIGHUP信号,用exec再启动一个自己并将fd传过去,继续监听然后accept连接。父进程不再接收新的连接,等到处理完所有旧的连接或者设置一段时间超时之后自己终止。

利用endless的特性我们就可以设计出一个自更新的解决方案,无论是通过定时还是提供一个api去触发更新操作。一旦触发更新,就去配置服务器上查询是否有比自身更新的版本,有的话就先将自身备份,然后下载最新版本替换掉自身,最后自己给自己发送SIGHUP信号实现自更新。

当然这其中还会涉及一些其他细节需要处理,比如数据库结构、配置有变化等。

Golang实时接收通过exec启动程序的标准输出

| Comments

最近在go里面用exec启动另外一个程序并获取它的stdout时出现了一个比较奇怪的问题。

stdpipe里的数据不是按行传过来,而是过了很久才会刷一大批数据出来,应该是其中存在某种缓冲机制。

跟踪了go标准库的代码没有发现会导致这种现象,查看了管道的缓冲机制好像也没问题,最后查到应该是跟该程序的缓冲io相关.

附上代码, 例子里运行的ping不会出现上面提到的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cmd := exec.Command("ping")
out, err := cmd.StdoutPipe()
if err != nil {
    ewlog.Error(err)
    return err
}

err = cmd.Start()
if err != nil {
    ewlog.Error(err)
    return err
}

scanner := bufio.NewScanner(out)

for scanner.Scan() {
    line := scanner.Text()
    ewlog.Debug(line)
}

Kafka Configuration Memo

| Comments

有时候还真是不能过度透支自己,最近重新找回了编程的乐趣, 想把丢了好久的blog捡起来, 不知道写些什么, 就从一些简单的memo开始写起吧。

最近配置kafka遇到最困惑的问题就是关于两个listener, 不知道为什么导致客户端连接不上, 记录下.

1
2
3
4
5
6
7
8
9
# kafka真正监听的地址, 只这样配置的话本机连接是不会有问题的
listeners=PLAINTEXT://:9092

# 这个是设置在zookeeper里面让客户端真正连接的地址,如果像以下一样留空,或者沿用listeners的配置
# 将会导致客户端访问hostname:9092,所以连接kafka出错
advertised.listeners=PLAINTEXT://:9092

# 将ip段配置成如下kafka机器的域名或者外网ip就可以正常访问了
advertised.listeners=PLAINTEXT://endwan.com:9092

Work at Home This Week

| Comments

这个星期由于脚被烫伤不能走路,于是享受了一下向往已久的"work at home"待遇。我总结了一些优缺点如下:

优点

  • 由于不用去公司,每天上下班路程上耗费的一个半小时省出来可以做点其他事情。
  • 可以选择性的去干干家务什么的,工作生活两不误,还可以放松放松大脑,一天下来不会觉得那么累。:)
  • 可以大声放音乐不用戴耳机,不过这个还真不适合我,有音乐在我永远进入不了那种Zen的状态。

缺点

  • 咖啡!最先怀念的就是那台咖啡机啦,没有咖啡总觉得少了点什么,咖啡才是第一生产力啊!
  • 最大的问题是沟通,稍微复杂点的问题用文字沟通效率非常低,所以有时候还得靠电话。
  • 分心的事情比较多,整个人比较放松,不适合做目的性不太明确的工作。但是适应一两天后这个情况有所改善。
  • 由于工作基本上还是得用公司电脑,远程连接毕竟没有直接操作来得顺畅,所以效率上会低一些。

总的来说我还是比较喜欢公司的气氛,一个人在家真是闷得慌,偶尔这样一下调剂生活倒是不错。当然这个只是针对我而言,也有人更喜欢在家的那种感觉。我觉得在家工作比较适合那种一个人项目,比如我自己随便玩玩的那些东西,真正的团队合作还是得在一起,要不沟通的成本会大大超出在家工作的那些好处。

C++任意指针转换为object指针

| Comments

最近碰到一个C++指针转换的问题,发现颇有意思,值得记录一下。 有一个foo class如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class foo
{
public:
    int get_foo(int val);

    int m_foo;
};

int foo::get_foo(int val)
{
    int a = 345;
    int rc = val + a;
    cout << rc << endl;
    return rc;
}

测试如下

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
    int* val = new int[100];

    foo* foo_ptr;
    foo_ptr = (foo*)val;
    foo_ptr->get_foo(100);
    foo_ptr->m_foo = 101;
    cout << foo_ptr->m_foo << endl;

    return 0;
}

以上代码编译后运行竟然完全正常,太奇怪了!强制将任意一个指针转换成对象指针,然后调用它的方法和成员变量竟然可以正常执行。为此我去stackoverflow求助了一下,再经过和同事讨论,得出了如下结果:

  • 尽管这个指针实际不是指向的一个foo对象,但是当编译器把它当作一个foo对象来处理时,调用他的方法其实是调用foo class的方法,属于所有object都公用,如果该方法不是个虚函数也没有用到成员变量,这样做应该是没有问题的。

  • 如果Foo class里有虚函数,那么foo对象里会包含一个指向虚函数表的指针1,调用这个虚函数时会先用这个指针找到vtable,这里foo对象的虚函数表指针应该是val指针指向内存的后面某个位置,而这个位置的数据是不确定的,有很大可能指向的是一个非法访问地址,导致程序崩溃。

  • 访问成员变量在上面行得通是因为我为val分配了一段内存,foo对象访问成员m_foo其实是访问val这段内存的某个位置,修改这段内存不会有什么问题。如果val这样定义:int *val;, 那foo访问成员时行为就很不确定,运气不好,访问到允许的地址,修改了那里的值,问题过很久以后才显现出来。运气好,访问到非法地址,程序立即崩溃,这时出问题的地方离产生问题的地方很近。

最后引用下stackoverflow里很有意思的一个回复 :)

C++ lets you get away with doing things that aren’t allowed, under the name undefined behaviour. Basically, it means “if you do something stupid, we won’t stop you, but absolutely anything could happen”!

Emacs执行shell命令的问题

| Comments

今天在配置emacs的markdown-mode时调用C-c C-c p(转换成html然后在浏览器里打开预览)一直提示/bin/bash: markdown: command not found,它需要一个shell命令能将markdown转换成html,这里我用到了一个python的markdown库。

安装完在我的机器上是这个路径: /Library/Frameworks/Python.framework/Versions/Current/bin/markdown_py

接下来在/usr/bin创建一个markdown的软链接到markdown_py

1
sudo ln -s /Library/Frameworks/Python.framework/Versions/Current/bin/markdown_py /usr/bin/markdown

做完上面这些后在emacs里调用C-c C-c p还是提示同样的错误,但是markdown在我的本地终端可以执行。Google了下之后发现这可能是由于emacs在启动一个shell的时候不会去读取.bash_profile,这个文件只是在登陆的时候被读取一次。而我在.bashrc里没有做任何关于环境变量的设置,所以/usr/bin没有被加到PATH里去,导致bash找不到markdown这条命令。

等我把下面这句加到.bashrc

1
2
3
if [ -f ~/.bash_profile ]; then
. ~/.bash_profile
fi

问题还是没有解决,提示同样的错误。继续Google, 最后终于在这里找到答案。emacs直接执行shell命令默认是非交互式的,所以不会去读取.bashrc文件的。解决的办法就是在.emacs初始化文件里加上这句。

1
(setq shell-command-switch "-ic")

-i指定bash启动是交互式的, 然后才会去读取.bashrc。或者你也可以配置BASH_ENV, 单独为非交互式的shell指定启动脚本。-c表示命令读取来自字符串。

以上统统搞定终于写出了我的第一篇博客!哈哈