Michael_Ding

Michael_Ding 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

开源爱好者,技术爱好者,爱动手,爱实践
擅长C++(Qt), Python, Ruby

个人动态

Michael_Ding 赞了回答 · 2016-02-20

解决一直搞不懂a++和++a的区别 怎么办?

a++是先执行表达式后再自增,执行表达式时使用的是a的值。
++a是先自增再执行表达示,执行表达式时使用的是a自增后的值。
所以$b得值为5,$a的值为6.
第一个问题理解了,第二个也就顺理成章了。

关注 7 回答 7

Michael_Ding 发布了文章 · 2015-11-10

etcd 集群的管理 - 暴走漫画容器实践系列 Part4

搭建完 etcd 集群后,接下来就是集群的管理了。集群的管理主要包括节点的重启,监控以及集群的运行时更改(Runtime Reconfiguration)。

关于节点的重启、监控相对来说比较简单,这里主要介绍下集群的运行时更改。

1. 在什么情况下需要集群的运行时更改?

让我们来看看需要集群的运行时更改的几个场景。他们中的绝大多数只需要运用到重配置中的 “添加/删除” 节点操作的组合。

1.1. 维护和升级多个机器

  • 如果你因为进行诸如硬件升级或者断网等计划维护,而需要移动多个节点到新机器上,最好是逐个节点移动,一次操作一个。

  • 移动 leader 节点是安全的,只不过 leader 节点下线后,需要耗费更多的时间选举新节点,所以建议最后移动。

  • 如果你的集群有超过 50M 的数据,最好进行节点的迁移(见3.1节 迁移节点),而不要通过删除旧节点,增加新节点来实现节点的移动。

1.2. 更改集群的大小

如上一篇博客所说的,增加集群节点的个数,容错能力越强,读性能也越好。不过相应的,写性能也会下降。减少集群节点的个数,容错能力下降,不过写性能也会提高。

更改集群大小也需要集群运行时更改。

1.3. 替换一个坏掉的节点

如果一个节点的机器因为硬件出错而宕机了,那需要尽快用新机器替换。替换的操作就是简单地分为两步:(通过集群运行时更改)先删除坏掉的节点,再添加新的节点(见2节 集群节点的操作)。不过,如果你的集群有超过 50M 的数据,最好进行节点迁移(见3.1节 迁移节点)

1.4. 集群多数宕机(Majority Failure)后的重启

如果你的集群出现了多数宕机(例如超过(N-1)/2的节点当机),或者所有的节点都更改了 IP,你就需要手动操作,重启(恢复)集群了。基本步骤包括:1.基于原先的数据创建新集群;2.强制让一个节点成为 leader 节点,并最终通过运行时更改添加新节点的方式将其他节点添加到这个新的集群中。

2. 集群运行时更改的操作

知道了什么样的情况下需要运行时更改,下面让我们来了解下具体的运行时更改的操作。

一般来说,这些操作需要确保集群的多数节点是正常服务的,并且一次只操作一个节点。

  • 升级单个节点的 peerURLs,需要执行一个更新节点操作

  • 替换一个节点,需要先执行一个添加节点操作,再执行一个删除节点操作

  • 将集群大小从 3 更改到 5,需要执行两个添加节点操作

  • 将集群大小从 5 降低到 3,需要执行两个删除节点操作

下面的所有例子都是利用 etcdctl 命令实现操作,其本质是调用 etcd 的 REST API。你也可以使用其他你习惯的客户端。

2.1 更新一个节点

如果你想更新一个节点的 IP(peerURLS),首先你需要知道那个节点的 ID。你可以列出所有节点,找出对应节点的 ID。

$ etcdctl member list
6e3bd23ae5f1eae0: name=node2 peerURLs=http://localhost:23802 clientURLs=http://127.0.0.1:23792
924e2e83e93f2560: name=node3 peerURLs=http://localhost:23803 clientURLs=http://127.0.0.1:23793
a8266ecf031671f3: name=node1 peerURLs=http://localhost:23801 clientURLs=http://127.0.0.1:23791

在本例中,我们假设要更新 ID 为 a8266ecf031671f3 的节点的 peerURLs 为:http://10.0.1.10:2380

$ etcdctl member update a8266ecf031671f3 http://10.0.1.10:2380
Updated member with ID a8266ecf031671f3 in cluster

2.2 删除一个节点

假设我们要删除 ID 为 a8266ecf031671f3 的节点

$ etcdctl member remove a8266ecf031671f3
Removed member a8266ecf031671f3 from cluster

执行完后,目标节点会自动停止服务,并且打印一行日志:

etcd: this member has been permanently removed from the cluster. Exiting.

如果删除的是 leader 节点,则需要耗费额外的时间重新选举 leader

2.3 增加一个新的节点

增加一个新的节点分为两步:

  • 通过 etcdctl 或对应的 API 注册新节点

  • 使用恰当的参数启动新节点

先看第一步,假设我们要新加的节点取名为 infra3, peerURLshttp://10.0.1.13:2380

$ etcdctl member add infra3 http://10.0.1.13:2380
added member 9bf1b35fc7761a23 to cluster

ETCD_NAME="infra3"
ETCD_INITIAL_CLUSTER="infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380,infra3=http://10.0.1.13:2380"
ETCD_INITIAL_CLUSTER_STATE=existing

etcdctl 在注册完新节点后,会返回一段提示,包含3个环境变量。然后在第二部启动新节点的时候,带上这3个环境变量即可。

$ export ETCD_NAME="infra3"
$ export ETCD_INITIAL_CLUSTER="infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380,infra3=http://10.0.1.13:2380"
$ export ETCD_INITIAL_CLUSTER_STATE=existing
$ etcd -listen-client-urls http://10.0.1.13:2379 -advertise-client-urls http://10.0.1.13:2379  -listen-peer-urls http://10.0.1.13:2380 -initial-advertise-peer-urls http://10.0.1.13:2380 -data-dir %data_dir%

这样,新节点就会运行起来并且加入到已有的集群中了。

值得注意的是,如果原先的集群只有1个节点,在新节点成功启动之前,新集群并不能正确的形成。因为原先的单节点集群无法完成leader的选举。
直到新节点启动完,和原先的节点建立连接以后,新集群才能正确形成。

3. 迁移节点和灾难恢复

3.1 迁移节点

移动节点有两种方式:1. 删除旧的节点,增加新的节点; 2. 迁移节点。当集群的数据超过 50M 的时候,建议通过迁移节点的方式来移动节点。

迁移节点的核心就是数据目录的迁移。因为 etcd 的各个节点会将自己的 ID 存放在自己的数据目录下面,所以迁移节点不会改变节点的 ID。

迁移节点的步骤简单来说,包括以下几步:

  • 停止需要迁移的节点的服务

  • 从老机器上拷贝数据目录到新机器上

  • 通过集群运行时更改的更新操作,改变节点的 peerURLs 值为新机器的 IP:port

  • 在新机器上指定拷贝过来的数据目录,启动 etcd 的节点服务

下面通过一个例子具体说明。

假设我们已有的集群是这样的:

namepeerURLs
infra010.0.1.10:2380
infra110.0.1.11:2380
infra210.0.1.12:2380
$ etcdctl member list
84194f7c5edd8b37: name=infra0 peerURLs=http://10.0.1.10:2380 clientURLs=http://127.0.0.1:2379,http://10.0.1.10:2379
b4db3bf5e495e255: name=infra1 peerURLs=http://10.0.1.11:2380 clientURLs=http://127.0.0.1:2379,http://10.0.1.11:2379
bc1083c870280d44: name=infra2 peerURLs=http://10.0.1.12:2380 clientURLs=http://127.0.0.1:2379,http://10.0.1.12:2379

我们要移动 infra1 从 10.0.1.11 到 10.0.1.13

1. 停止 infra1 的 etcd 进程
$ssh 10.0.1.11
$ kill `pgrep etcd`
2. 从 10.0.1.11 拷贝etcd的数据目录到 10.0.1.13 的机器上
$ tar -cvzf infra1.etcd.tar.gz %data_dir%
$ scp infra1.etcd.tar.gz 10.0.1.13:~/
3. 变更 infra1 的 peerURLs
$ curl http://10.0.1.10:2379/v2/members/b4db3bf5e495e255 -XPUT \
-H "Content-Type: application/json" -d '{"peerURLs":["http://10.0.1.13:2380"]}'

或者利用 etcdctl

etcdctl member update b4db3bf5e495e255 http://10.0.1.13:2380
4. 在新机器上使用 infra1 的数据和配置启动 etcd
$ ssh 10.0.1.13
$ tar -xzvf infra1.etcd.tar.gz -C %data_dir%
$ etcd -name infra1 \
> -listen-peer-urls http://10.0.1.13:2380 \
> -listen-client-urls http://10.0.1.13:2379,http://127.0.0.1:2379 \
> -advertise-client-urls http://10.0.1.13:2379,http://127.0.0.1:2379

3.2 灾难恢复

总的来说,etcd 的集群还是相当可靠的,但是也不能排除极端情况的出现。当出现灾难性的多数节点宕机,就不得不进行灾难恢复了。

灾难恢复需要以下几个步骤:

1 备份数据

备份操作需要在一台还'活着'的节点上进行

$ etcdctl backup \
      --data-dir %data_dir% \
      --backup-dir %backup_data_dir%

这个命令会备份原数据到 %backup_data_dir% 目录下,并重新相关的元数据(例如 节点的 id 和 集群的 id)。
这意味着在 %backup_data_dir% 中只包含原先的数据,而不包含原先的身份信息。

接下来我们就可以基于备份的数据创建一个单节点的集群。

2 从备份数据中重建单节点集群
$ etcd \
      -data-dir=%backup_data_dir% \
      -force-new-cluster \
      ...

...部分省略了其他相关的参数,例如-peer-urls-client-urls 等等

这时候,应该就成功创建了一个新的只包含一个节点的集群,并包含之前的所有数据。
当你确认新集群正常后,就可以删除原来集群的数据,暂停新集群,将新集群的数据目录拷贝回原先数据的位置,并重新启动。

$ pkill etcd
$ rm -rf %data_dir%
$ mv %backup_data_dir% %data_dir%
$ etcd \
    -data-dir=%data_dir% \
    ...
3 恢复集群

现在已经有了一个拥有之前数据的单节点的集群了。接下来,你可以通过添加节点的操作,重建出一个同样大小的集群。
值得注意的是,如果你还是使用之前的其他机器来重建这个新的集群,一定杀掉之前的etcd 进程,并且清除掉之前的数据。

查看原文

赞 7 收藏 20 评论 0

Michael_Ding 发布了文章 · 2015-10-21

Spark 集群概述

本篇博客简述 Spark 集群相关的概念。

概述

Spark 的"集群"不是提供运算服务的,而是一种资源分配的调度器。
执行任务的 Spark 进程作为客户端向"集群"申请资源(运算节点), "集群"分配资源以后,
这个 Spark 进程会分解一些计算工作,并把他们放到这些申请来的资源中运行。

提交给 Spark 执行的工作称做 application(应用),对应的主程序称作:driver program
driver program 通过一个叫做 SparkContext 的对象来协调 Spark 集群中不同进程的任务。

具体来说:

  1. driver program 向"集群"申请到得运算节点称作 worker node;

  2. 一旦申请到 worker node,driver program 会连接这些 worker node, 并在 worker node 上创建(acquire)执行计算的进程(executor);

  3. 接下来 driver program 将计算需要的代码和数据发给 executor

  4. 最后 SparkContext 将分解出来的 task(任务) 发送给各个 executor 去执行。

过程如下图所示:

spark cluster overview

这里有一些注意点:

  1. 每个 application 都获得自己独立的 executor 进程,这个executor进程利用多个线程运行多个 task。这样可以保证不同application的隔离性,无论是调度端(driver program 分解各自的 task),还是执行端(每个executor只跑来自同一个 applicationtask)。不过这也意味着,不同的 application 之间除非借助外部存储系统(例如数据库),否则是不可以共享数据的。

  2. Spark 是不需要知道运行在什么样的 "集群" 上的。Spark 只需要可以创建进程,并且和这些进程通信,无论是运行在什么样的集群上(eg. Mesos/YARN)都可以。

  3. driver program 必须在整个生命周期中可以从不同的 executor 接受连接。因此,driver program对于 executor 来说,
    必须是网路可及的。

  4. 因为由driver program分解 task,它必须和 worker 节点很接近,最好在同一个局域网。
    如果你不能做到这一点(例如从远程提交 application),最好开一个 RPC,利用靠近 Spark 集群的机器来运行 driver program

Spark 集群的类型

实现集群的程序称为:集群管理器。目前有三种集群管理器

  • Standalone - 这个集群管理器打包在 spark 的程序里,是最简单的集群管理器。

  • Apache Mesos - 一个非常成熟的分布式操作系统,可以用来运行除 Spark 以外的很多系统。

  • Hadoop YARN - Hadoop 的 资源管理器。

术语表

术语解释
Application在 Spark 上运行的工作, 由 driver programexecutors 组成
Application jar包含 Application 代码的 jar 包。在一些应用场景中,jar 需要包含依赖的库。不过永远不要包含 Hadoop 和 Spark 的库
Driver program运行 Application 的main() 函数的进程,并且 SparkContext 对象在此进程中创建
Cluster manager(集群管理器)实现集群的资源调度分配的外部程序
Deploy mode用于区分 driver program 进程在哪里运行。cluster 模式下,driver 在集群中的节点上运行。 client 模式下,driver 在集群以外的地方运行
Worker node集群中运行程序的节点
Executorworker node 中为 各 Application 创建的进程。它会执行 Application 相关的 task,将它们的数据保存在内存中或磁盘上。
Task执行具体计算的单元,会被发送给特定的 executor 执行
Job一个由多个 task 组成的并行计算集,它们生成 Spark 动作(eg. save, collect) 的结果。这个术语会出现在 driver 的日志中
Stage每个 job 会被分解成更小的 task 的集合,这些集合被称作 stage。它们彼此依赖(就像 MapReduce 中的 map 和 reduce 两个 stage);这个术语会出现在 driver 的日志中
查看原文

赞 2 收藏 19 评论 2

Michael_Ding 发布了文章 · 2015-10-13

在 Dockone 的分享 - 暴走漫画容器实践系列 Part1

大家好,我是 Michael Ding,来自暴走漫画。

暴走漫画是一家文化传媒公司。公司除了有若干视频娱乐节目,还有相应的社区网站及 App。流量 UV 200w/天 左右,PV 千万。
为了更加有效地运营以及推荐用户个性化,2015年成立了数据部,负责暴漫的数据分析和数据挖掘相关服务。

暴漫没有自己的服务器,是使用的国内某云服务。暴漫的后端主要是基于 Ruby 开发。也有基于 go, python 的一些micro service。
Docker 在暴漫中的应用主要包括:

  • 开发环境的 service 搭建

  • 代码托管,持续集成,docker 镜像,等若干 support 服务

  • 部分 micro service 以及整个数据服务系统

所以今天的内容是一些中小规模以及国内云服务下的 docker 实践的相关心得,主要包括在数据服务的架构及 docker 化的部署。

1. 简单介绍下开发环境以及 support 服务的 docker 应用

由于开发环境主要是 Mac,也有少量 Ubuntu 和 Windows,所以主要采用 Vagrant + docker 方式。
将 micro service 做成 image,在 Vagrant 中起相应的container,把端口暴露给 Host(Vagrant)。本地跑 Ruby(on Rails)

support 服务的话,其他都很简单,只有持续集成介绍下。我们用的 gitlab ci。gitlab ci 支持将 task 跑在 docker container 里面
所以我们为不同的项目准备不同的测试环境(image)以及外部依赖(eg. mysql, redis),然后在对应的 container 里面跑测试。
关于部署的话,我们平时的开发在 develop 分支,一旦向 master 分支合并后,会触发部署的 task。
部署的 task 跑在特定的 container 里面,这个 container 共享了 Host 的 docker unix sock 文件,可以执行 docker build, push 等命令

关于开发环境和 support 服务的 docker 应用,因为不是今天的重点,并且前面也有很多朋友做过类似的介绍,所以先简单介绍到这里。

2. micro service 和 数据服务系统的 docker 应用

今年我们做了很多 micro service 的尝试,例如消息推送,推荐系统,反垃圾系统,数据分析系统,视频抓取等等若干子系统的拆分上线。
虽然过程是痛苦的,但是结果却是令人欣慰的。这些 micro service,几乎都是基于 docker 的。

2.1 Rails + docker 化的 micro service

整体来说,我们是个混合的架构,Rails 是正常的跑在云主机中的,micro service 跑在 docker 中。为了协调好各方,我们对基础服务做了一点小小的调整。

这里不得不说说我做架构的一点心得。好的架构除了能满足业务需求,还要是与特定的团队,特定的资源所配套的。
在暴漫,由于技术力量有限,开发排期满,所以我都是尽量采用“非侵入式”的方案,这在后面的数据服务的构建中也有体现。

首先,我们给所有的机器都装上了 docker
其次,我们搭建了一个 etcd 集群,将所有的云主机都纳入了 etcd 集群。而 etcd 也是跑在 docker 里的。
为了方便的跑起来 etcd,我们写了个一套 bash + python 的脚本(Python 的脚本也是跑在 docker 里的)
然后,所有的机器直接访问本机 IP 可以 access etcd。

这里插一句,我们没有去折腾如何让docker跨主机组网,而是直接采用映射到 host的方式。一方面国内云主机只能这么干。另一方面,我们之前使用云主机也是单个主机特定用途的。
另外,在生产环境中,我们大量的使用了 shell + etcd 来启动 docker container 的方式。可以给大家看个 etcd 的启动 script。这个 script 放到最初的机器上就可以方便地启动起来etcd 集群。

#!/bin/bash

check_non_empty() {
  # $1 is the content of the variable in quotes e.g. "$FROM_EMAIL"
  # $2 is the error message
  if [[ $1 == "" ]]; then
  echo "ERROR: specify $2"
  exit -1
  fi
}

check_exec_success() {
  # $1 is the content of the variable in quotes e.g. "$FROM_EMAIL"
  # $2 is the error message
  if [[ $1 != "0" ]]; then
  echo "ERROR: $2 failed"
  echo "$3"
  exit -1
  fi
}

up() {

  # create ${EtcdData}
  mkdir -p ${EtcdData}

  # pull pycsa docker image
  docker pull private/pycsa:latest

  check_exec_success "$?" "pulling 'pycsa' image"

  # pull etcd docker image
  docker pull quay.io/coreos/etcd:latest

  check_exec_success "$?" "pulling 'etcd' image"

  # build cluster nodes list for `-initial-cluster`
  cwd=$(pwd)
  ClusterNodes=$(docker run --rm \
    -v ${cwd}:/data \
    private/pycsa:latest \
    python up.py cluster-nodes ${1} ${ETCD_NAME} ${HostIP})

    check_exec_success "$?" ${ClusterNodes}

    case "$1" in
    "-a")
    ${BaseCmd} -initial-cluster ${ClusterNodes} \
    -initial-cluster-state existing
    ;;
    "")
    ${BaseCmd} -initial-cluster ${ClusterNodes} \
    -initial-cluster-token bzetcd-cluster -initial-cluster-state new
    ;;
    *)
    echo "Usage: ./etcd.sh up [-a]"
    exit 1
    ;;
    esac
  }

  start() {
    docker kill etcd 2>/dev/null
    docker rm etcd 2>/dev/null
    ${BaseCmd}
  }

  stop() {
    docker stop etcd
    docker rm etcd
  }


  ##################
  # Start of script
  ##################

  # source env
  . /etc/default/etcd

  check_non_empty "${ETCD_NAME}" "ETCD_NAME"

  # get host ip
  HostIP=$(ifconfig eth0 | awk '/\<inet\>/ { print $2}' | sed 's/addr://g')

  # set data dir
  EtcdData=/data/etcd/data

  # create etcd container base cmd
  BaseCmd="docker run -d \
  -v /usr/share/ca-certificates/:/etc/ssl/certs \
  -v ${EtcdData}:/data \
  -p 4001:4001 -p 2380:2380 -p 2379:2379 \
  --name etcd quay.io/coreos/etcd:latest \
  -name ${ETCD_NAME} \
  -data-dir /data \
  -advertise-client-urls http://${HostIP}:2379,http://${HostIP}:4001 \
  -listen-client-urls http://0.0.0.0:2379,http://0.0.0.0:4001 \
  -initial-advertise-peer-urls http://${HostIP}:2380 \
  -listen-peer-urls http://0.0.0.0:2380"

  case "$1" in
  up) up "$2" ;;
  start) start ;;
  stop) stop ;;
  restart)
  stop
  start
  ;;
  *)
  echo "Usage: ./etcd.sh start|stop|restart or ./etcd.sh up [-a]"
  exit 1
  ;;
  esac

  exit 0

解释下, up.py 是个 python 的脚本,跑在一个 pycsa 的容器里,这个容器有 python 环境以及相关的 package

这样原来的服务几乎不受任何影响,我们可以利用 etcd + docker + shell script 来组建新的服务。

2.2 数据服务

我们的数据服务包括数据分析和数据挖掘两大块。数据分析主要是为了给运营提供量化的效果评估以及指导。数据挖掘则包括推荐,反垃圾等。

数据服务的基础是数据流,即:数据收集->数据分发->数据处理<->数据存储

先给大家看个整体的架构图,由于本人不擅作图,所以直接用手画的,还请见谅。。

fig

首先数据收集部分,就像之前说的,我尽量采用“非侵入式”的方案,所以,我们的整个数据收集都是基于日志的。
我们在每个应用服务器上装了 logstash (跑在 docker 中) 来收集各个应用服务器的日志,然后打到 kafka (跑在 docker 中) 里,给不同的用途使用。

一份COPY 直接由kafka 一端的 logstash 存储到 elasticsearch(跑在 docker 中) 中
一份COPY 经过 spark (跑在 docker 中) stream 做实时处理(包括一些特定日志的提取),然后将处理的结果存储在 elasticsearch 里
还有一份 COPY 直接存储到 HDFS (由云服务商提供)

这里有个小问题,比如有些数据本身日志里并没有,比如用户的点击行为。这个时候,我们专门开发了一些 "ping" 接口,这些接口通过 Nginx 直接返回 200,并记录相关日志

此外还有一部分数据,例如一些比较需要“较严格的完备”的,例如用于推荐系统,反垃圾系统学习的数据,我们存储在 SQL 数据库中

下面我做些稍微详细的介绍

2.2.1 数据分析

数据分析有两种:实时数据分析和离线数据分析

实时数据分析从 kafka 到 spark stream,处理结果进 elasticsearch,离线分析是定时任务,从 HDFS 到 spark,处理结果进 elasticsearch。一般来说,离线的结果会逐步包含实时的结果,
同时实时的结果领先于离线分析的结果。

这里的分析有些抽象,我来举个例子:

Q: 统计某个板块同时在线人数的变化趋势
A: 用户每次访问都有日志,日志里包括访问内容以及用户标识。首先 spark stream 从日志里抽取出特定板块不同用户的访问事件,以秒为单位合并相同用户事件。这就是分析结果:时间戳:人数

然后这个结果怎么用?

elasticsearch 有很强大的 agg 接口。你可以以1秒,10秒,1分等等各种时间间隔单位聚合这段时间内的在线人数,聚合方式用 '平均'或'最大'

2.2.2 数据挖掘

我们主要做了2个具体的数据挖掘系统:推荐+反垃圾

今天主要讲下架构。

这两个系统基本上步骤是一样的,分为2步:训练(train) 和 服务(serve)

在 train 阶段,定时起一个 spark job,从训练数据集中读取数据,学习出 model,然后将 model 存储成文件
在 serve 阶段,起一个带 serve 的 spark job,load 之前学习出来的model 文件进内存,然后接受外部api 调用,返回结果。

关于服务的开发这部分因为涉及到太多额外的知识,我就不多说了。

这里讲个难点:spark 的 docker 化。

2.2.3 Spark 的 docker 化

Spark 的 docker 化分为两个部分:

  • docker 化的 spark 集群

  • docker 化的 spark 调用

Spark 和我们一般用的服务不太一样,它的集群不是提供运算服务的,而是一种资源分配的调度器。
让 Spark 跑 Job,其实是起的一个 Spark 的本地程序,这个本地程序会向 cluster 要资源(其他机器),cluster 分配资源以后,这个 spark 程序就把一些工作放在这些资源当中运行(进程)

所以 Spark 的 docker 化分为两个部分。

对于 spark 调用,也就是启动 spark 的本地程序,我们就是在跑程序的 image 中集成 java 环境,spark 程序

对于 spark 集群,稍微复杂一些。spark 支持三种集群:mesos, yard,还有 spark 自己的一个 standalone
我们搭建的 spark standalone 集群,这还是考虑到我们自身的资源与需求。

由于没找到官方的 spark docker image,我们自己做了一个,就是 java 环境 + spark 程序
然后利用 script + etcd 以不同的姿势(master 或 slave)在不同的云主机上启动 spark container

官方推荐要起3个 master, 用 zookeeper 做 quorum,这个我们最近正在搞,还没上线,就不分享。我们现在线上跑的是 1 master + 7 slave

谢谢

--

更多文章欢迎关注自由风暴博客

查看原文

赞 8 收藏 25 评论 0

Michael_Ding 发布了文章 · 2015-10-13

搭建 etcd 集群 - 暴走漫画容器实践系列 Part3

etcd 是一个高可用的分布式 key-value(键值) 存储系统。在暴漫我们用他用来做配置管理和服务发现。

这一次我们主要介绍关于 etcd 集群的搭建与管理。

1. etcd 集群概述

首先我们需要理解,etcd 是一个分布式的 key-value 存储系统,所以其基本原理和前面我们介绍过的
分布式数据库相关理论 是一致的。

两种不同的 node(节点)

值得注意的是,为了方便使用,etcd 引入了 proxy 的概念,所以 etcd 的节点分为两种:集群节点代理节点

集群节点代理节点 在使用上几乎没有任何区别。这使得我们可以在每台机器上都安装 etcd,进而把 etcd 当作本地服务来使用(通过 0.0.0.0)。
他们的区别在于:内部原理不同。
集群节点是真正的 etcd 集群的构成者,这些节点负责数据存取,集群管理等等。
代理节点可以理解为一个反向代理,它只是简单的接受请求,转发请求给 etcd 集群。

集群大小与容错

集群的大小指集群节点的个数。根据 etcd 的分布式数据冗余策略,集群节点越多,容错能力(Failure Tolerance)越强,同时写性能也会越差。
所以关于集群大小的优化,其实就是容错和写性能的一个平衡。

另外, etcd 推荐使用 奇数 作为集群节点个数。因为奇数个节点与和其配对的偶数个节点相比(比如 3节点和4节点对比),
容错能力相同,却可以少一个节点。

所以综合考虑性能和容错能力,etcd 官方文档推荐的 etcd 集群大小是 3, 5, 7。至于到底选择 3,5 还是 7,根据需要的容错能力而定。

关于节点数和容错能力对应关系,如下表所示:

集群大小最大容错
10
31
41
52
62
73
83
94

2. etcd 集群的搭建(初始化一个 etcd 集群)

这里说的搭建指“从无到有”搭建。关于在已有集群中添加减少集群节点,属于下面"第3节:etcd 集群的管理"的内容。

etcd 集群的搭建有三种方式,包括:static 方式,etcd discovery 方式 和 DNS discovery。

这里,我们以一个例子来讲解 etcd 集群各种方式的搭建。假设我们需要搭建一个3节点的 etcd 集群。这三个节点的 name(我们需要给每个节点取个名字)和 ip 分别是:

nameip
etcd010.0.0.10
etcd110.0.0.11
etcd210.0.0.12

2.1 static 方式

static 方式是最简单的一种搭建 etcd 的方式。
不像其他两种方式, static 方式不需要任何额外的服务,只需要你知道你准备用来运行 etcd 的所有节点(的name和ip)。

本例中,我们来看看如何在3个节点上构建 etcd 集群。

首先我们需要构造一个描述集群所有节点的参数,这个参数可以以命令行参数的方式传给 etcd 程序,也可以以环境变量的方式

如果用命令行参数,应该将下列参数附在 etcd 的启动命令后面:

-initial-cluster etcd0=http://10.0.1.10:2380,etcd1=http://10.0.1.11:2380,etcd2=http://10.0.1.12:2380 \
  -initial-cluster-state new

其中 -initial-cluster-state new 表示这是在从无到有搭建 etcd 集群。
-initial-cluster 参数描述了这个新集群中总共有哪些节点,其中每个节点用 name=ip的形式描述,节点之间用,分隔。

如果用环境变量,应该在启动 etcd 时,加入如下环境变量:

ETCD_INITIAL_CLUSTER="etcd0=http://10.0.1.10:2380,etcd1=http://10.0.1.11:2380,etcd2=http://10.0.1.12:2380"
ETCD_INITIAL_CLUSTER_STATE=new

ETCD_INITIAL_CLUSTER 变量和 -initial-cluster 作用相同,
ETCD_INITIAL_CLUSTER_STATE 变量和 -initial-cluster-state 作用相同。

接着,分别在3个节点上启动 etcd,以命令行参数方式启动为例:

$ etcd -name etcd0 -initial-advertise-peer-urls http://10.0.1.10:2380 \
  -listen-peer-urls http://10.0.1.10:2380 \
  -listen-client-urls http://10.0.1.10:2379,http://127.0.0.1:2379 \
  -advertise-client-urls http://10.0.1.10:2379 \
  -initial-cluster-token my-etcd-cluster \
  -initial-cluster etcd0=http://10.0.1.10:2380,etcd1=http://10.0.1.11:2380,etcd2=http://10.0.1.12:2380 \
  -initial-cluster-state new
$ etcd -name etcd1 -initial-advertise-peer-urls http://10.0.1.11:2380 \
  -listen-peer-urls http://10.0.1.11:2380 \
  -listen-client-urls http://10.0.1.11:2379,http://127.0.0.1:2379 \
  -advertise-client-urls http://10.0.1.11:2379 \
  -initial-cluster-token my-etcd-cluster \
  -initial-cluster etcd0=http://10.0.1.10:2380,etcd1=http://10.0.1.11:2380,etcd2=http://10.0.1.12:2380 \
  -initial-cluster-state new
$ etcd -name etcd2 -initial-advertise-peer-urls http://10.0.1.12:2380 \
  -listen-peer-urls http://10.0.1.12:2380 \
  -listen-client-urls http://10.0.1.12:2379,http://127.0.0.1:2379 \
  -advertise-client-urls http://10.0.1.12:2379 \
  -initial-cluster-token my-etcd-cluster \
  -initial-cluster etcd0=http://10.0.1.10:2380,etcd1=http://10.0.1.11:2380,etcd2=http://10.0.1.12:2380 \
  -initial-cluster-state new

注意

值得注意的是,无论是 -initial-cluster参数,还是对应的环境变量,只有在第一次启动 etcd 的时候才起作用。
之后如果重启 etcd,这个参数或环境变量会被自动忽略。所以当成功初始化了一个 etcd 集群以后,你就不在需要这个参数或环境变量了。

2.2 etcd discovery 方式

很多时候,你只知道你要搭建一个多大(包含多少节点)的集群,但是并不能事先知道这几个节点的 ip,从而无法使用 -initial-cluster 参数。
这个时候,你就需要使用 discovery 的方式来搭建 etcd 集群。discovery 方式有两种:etcd discoveryDNS discovery

这里我们先介绍下 etcd discovery 方式,etcd discovery 有两种:自定义的 etcd discovery公共 etcd discovery

2.2.1 自定义的 etcd discovery 服务

这种方式就是利用一个已有的 etcd 集群来提供 discovery 服务,从而搭建一个新的 etcd 集群。

假设已有的 etcd 集群的一个访问地址是:myetcd.local,那么我们首先需要在已有 etcd 中创建一个特殊的 key,方法如下:

$ curl -X PUT https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83/_config/size -d value=3

其中 value=3 表示本集群的大小,即: 有多少集群节点。而 6c007a14875d53d9bf0ef5a6fc0257c817f0fb83 就是用来做 discovery 的 token。

接下来你在 3 个节点上分别启动 etcd 程序,并加上刚刚的 token。
加 token 的方式同样也有 命令行参数环境变量 两种。

命令行参数:

-discovery https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83

环境变量

ETCD_DISCOVERY=https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83

命令行参数启动方式为例:

$ etcd -name etcd0 -initial-advertise-peer-urls http://10.0.1.10:2380 \
  -listen-peer-urls http://10.0.1.10:2380 \
  -listen-client-urls http://10.0.1.10:2379,http://127.0.0.1:2379 \
  -advertise-client-urls http://10.0.1.10:2379 \
  -discovery https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83
$ etcd -name etcd1 -initial-advertise-peer-urls http://10.0.1.11:2380 \
  -listen-peer-urls http://10.0.1.11:2380 \
  -listen-client-urls http://10.0.1.11:2379,http://127.0.0.1:2379 \
  -advertise-client-urls http://10.0.1.11:2379 \
  -discovery https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83
$ etcd -name etcd2 -initial-advertise-peer-urls http://10.0.1.12:2380 \
  -listen-peer-urls http://10.0.1.12:2380 \
  -listen-client-urls http://10.0.1.12:2379,http://127.0.0.1:2379 \
  -advertise-client-urls http://10.0.1.12:2379 \
  -discovery https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83

2.2.2 公共 etcd discovery 服务

如果没有已有的 etcd 集群,也可以用 etcd 提供的公共服务: discovery.etcd.io
步骤和 2.2.1 节基本一致。

你得先创建一个用于 discovery 的 token,创建方式如下:

$ curl https://discovery.etcd.io/new?size=3

返回:

https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de

返回值作为启动节点时的 -discovery 参数或者 ETCD_DISCOVERY环境变量的值。

环境变量启动方式为例:

$ ETCD_DISCOVERY=https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de \
etcd -name etcd0 -initial-advertise-peer-urls http://10.0.1.10:2380 \
  -listen-peer-urls http://10.0.1.10:2380 \
  -listen-client-urls http://10.0.1.10:2379,http://127.0.0.1:2379 \
  -advertise-client-urls http://10.0.1.10:2379 \
  -discovery https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de
$ ETCD_DISCOVERY=https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de \
etcd -name etcd1 -initial-advertise-peer-urls http://10.0.1.11:2380 \
  -listen-peer-urls http://10.0.1.11:2380 \
  -listen-client-urls http://10.0.1.11:2379,http://127.0.0.1:2379 \
  -advertise-client-urls http://10.0.1.11:2379 \
  -discovery https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de
$ ETCD_DISCOVERY=https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de \
etcd -name etcd2 -initial-advertise-peer-urls http://10.0.1.12:2380 \
  -listen-peer-urls http://10.0.1.12:2380 \
  -listen-client-urls http://10.0.1.12:2379,http://127.0.0.1:2379 \
  -advertise-client-urls http://10.0.1.12:2379 \
  -discovery https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de

2.2.3 注意点

值得注意的是:如果实际启动的 etcd 节点个数大于 discovery token创建时指定的size
多余的节点会自动变为 proxy 节点。

2.3 DNS discovery 方式

这个方式没有实践,而且对于一般团队实用性也不高,所以就不做分享了。

2.4 后续

到这里为止,我们已经有一个3节点的 etcd 集群了,下一篇博客我会介绍如何进行 etcd 集群的管理

--

更多文章欢迎关注自由风暴博客

查看原文

赞 14 收藏 34 评论 0

Michael_Ding 发布了文章 · 2015-05-28

冲突的处理 - 分布式数据库相关理论 Part5

冲突的处理,也是分布式系统中一个重要的议题。今天我们继续以 Riak 为案例,看看 Riak 是怎么做冲突处理的。

Vector Clock(向量钟)

Riak 通过一种叫做 Vector Clock 的机制来处理冲突问题。简单来说,Vector Clock 是一段 token
Riak 这样的分布式系统通过这样的 token 来追踪数据更新操作的先后顺序。

在冲突处理中,能够知道冲突操作(eg. 创建操作,更改操作)的顺序,是非常重要的。
因为对于分布式系统来说,不同的客户端连接到的是不同的服务器节点,
当一个客户端更新了一个服务器节点上的数据,也许另一个客户端也同时更新了另一个服务器节点上的数据。

这时候,也许你会想到:记录每个操作的时间戳,然后依照时间戳靠后的操作来。然而要这么做的话,这里有个隐含的前提:
在这个分布式系统中的每个服务器节点,时钟都必须是完全同步的。
然而事实上,一方面这是非常困难的:需要非常大的财力物力的投入;另一方面,整个系统又是单点故障的。

所以,Riak 使用 Vector Clocks 来处理冲突。Vector Clocks 给每个写操作(创建,更改,删除) 打上一个标签,标签代表了是哪个客户端以什么样的顺序执行的操作。
这样一来,客户端或者开发者就能决定面对冲突,该怎么决定。
如果你熟悉像 Git, Subversion这样的版本控制系统,
这就和两个人同时修改了同一个文件产生的冲突解决思路是相似的。

Vector Clock 小故事 —— Vector Clock 相关理论

暴走大事件的编辑部每周都要整理下一期里要播报的新闻段子。

假设负责整理新闻段子有3个人:王尼玛(A), 张全蛋(B), 纸巾(C)。他们需要确定最终的新闻段子的列表。新闻段子的列表存储在分布式的服务器中。

每个人用自己的终端连接数据库。这些终端都有着唯一的标识,用来构建 vector clock。下面就让我们模拟一下,vector clock 是如何工作的。

首先,王尼玛用自己的终端更新了列表

vclock: A[0]
value: ['news xx']

然后,张全蛋先下载了这个列表,然后更新了这个列表

vclock: A[0], B[0]
value: ['news xx', 'news xyy']

张全蛋更新的同时(王尼玛做更新之后),纸巾同样的下载了已有的列表,做了更新。

vclock: A[0], C[0]
value: ['news xx', 'news yyz']

第二天,张全蛋复查列表,由于纸巾的更新操作并不是在他之后的(而是和他同时的),
这时候就产生了一个冲突,需要处理。

他拿到两个值:

vclock: A[0], B[0]
value: ['news xx', 'news xyy']
--
vclock: A[0], C[0]
value: ['news xx', 'news yyz']

他需要解决这个冲突:于是他选择合并这两个值:

vclock: A[0], C[0], B[1]
value: ['news xx', 'news xyy', 'news yyz']

这样一来,任何人之后获取到的就是这个最新的合并后的值了。

查看原文

赞 1 收藏 2 评论 0

Michael_Ding 发布了文章 · 2015-05-28

Riak 中的 CAP - 分布式数据库相关理论 Part4

上一篇博文一样,这次我们依旧以 Riak 为案例,来分析 CAP 理论在一个实际的分布式数据库中的作用。

如果你还不熟悉 CAP,可以参考我之前的两篇博客 理解 CAP 理论, 最终一致性.html)。

这次我们来看看,在 Riak 这样的分布式key-value数据库中,CAP理论是怎么起作用的。

Nodes/Writes/Reads

首先还是让我们来明确几个概念。

N odes

需要"最终"包含正确的值的服务器节点总数(正确的冗余数据拷贝数)。

W rites

每次写操作,我们需要确保最少有多少节点被更新。也就是说,我们在执行写操作的时候,不需要等待 N 个节点都成功被写入,
而只需要 W 个节点成功写入,这次写操作就返回成功,而其他节点是在后台进行同步。

R eads

每次读操作,我们需要确保最少读到几份冗余数据。也就是说,我们在执行读操作的时候,需要读到 R 个节点的数据才算读成功,否则读取失败。

为什么要这三个变量?其实这三个变量直接关系到了 Riak 的 CAP 特性。下面我们就来一一说明:

Eventual Consistency(W + R <= N)

如下图所示:假设我们的 N=3, 设置 W + R <= N(例如:R=2, W=1)。这样我们的系统可以相对保证读写性能。
因为写操作只需要一个节点写入就返回成功。

clipboard.png

然而这里有机率发生这样的情况:就像图中所示,我写入的是node1(versionB),然后进行了一次读操作。
恰好这时候新数据尚未同步到node2, node3,而读操作又是从node2,node3取的值。由于这两个节点的值都是 version A,
所以得到的值便是 version A。

不过随着时间的推移,node1 中的 versionB 会被同步到 node2 以及 node3 中。
这时候,再有读操作,得到的值便是最新值(versionB)了。

这就是所谓的 Eventual Consistency。整个系统有着较高的读写性能,但一致性有所牺牲。

如果我们需要加强一致性,可以通过调整 W, R, N 来实现。

接下来我们会讨论如何调整 W,R,N 的关系来平衡读写性能和一致性(即 A 和 C 的平衡)。

通过调节 W,R,N 的关系来调节一致性和读写性能的关系

一种极端做法(下图所示),我们可以设 W=N, R=1。其实这就是关系型数据库的做法。
通过确保每次写操作时,所有相关节点都被成功写入,来确保一致性。这样可以保证一致性,但是牺牲了写操作的性能。

clipboard.png

还有一种极端做法,我们可以设W=1, R=N。这样,无论你向哪个node写入了数据,都会被读到。
然后你读到的N个值也可能包含旧的值,只要有办法分辨出哪个是最新的值就可以了
(Riak 是用一直叫向量钟(Vector Clock)的技术来判断的,我们会在后面的博客中做介绍)
这样可以保证一致性,但是牺牲了读操作的性能。

clipboard.png

最后再给出一种被称作 quorum 的做法。如下图所示,可以设置 W + R > N (例如 W=2, R=2)。这样同样可以保证一致性。
然而性能的损失由写操作和读操作共同承担。这种做法叫做 quorum

clipboard.png

查看原文

赞 2 收藏 5 评论 0

Michael_Ding 发布了文章 · 2015-05-28

Riak的分布式数据库模型 - 分布式数据库相关理论 Part3

Riak 是什么

Riak 是一个 erlang 开发的开源的分布式 key-value 数据库,
High Availability, Fault Tolerance, Scalability 方面表现优异。
其实现受 Amazon Dynamodb 启发,是一个很有代表性的分布式数据库。

Riak 集群是一个去中心化的集群。每个服务器节点都是平等的,可以自由地添加和删除。
这使得 Riak 的故障转移(Failure Over)和扩展非常容易。
在 CAP 理论方面,Riak 可以自由地在 CP 和 AP 之间做平衡。

理解 Riak 的分布式数据库模型

Riak 的数据冗余

下面还是让我们从简单的例子开始,来理解下 Riak 的分布式数据库模型,包括数据的存储,节点服务器的,CAP理论的关系等。

首先让我们先定义一个概念:N,表示数据的"份数"。在分布式数据库中,一份数据往往会存储多份拷贝(所谓冗余,或者 replications)

现在,假设我们有一个服务器节点(node1),存有三个数据(key分别是 P0, P1, P2),N = 1。那么可以想象,这三个数据都是存放在 node1 中。如下图所示:

clipboard.png

当 N = 2 时,假设 P0, P1, P2 的冗余数据分别是 R0, R1, R2, 那么可以想象,这6个数据也应该都存储在 node1 中,如 下图所示:

clipboard.png

这时候,让我们把服务器节点增加到2个(node1, node2),那么可以想象,6个数据有很多中组合方式,例如下面这两种:

clipboard.png

clipboard.png

也许你发现了,他们有个共同点:同一个数据的冗余数据放在不同的服务器节点中。这样就算一个节点删除(当机)了,集群的数据仍然能保证完整性。
这为故障转移(Failure over)提供了基础。

那么现在的问题来了,是否有什么科学(公式化)的方式来找到分配这些数据的组合(之一)呢

Riak Ring

Riak 通过被称作 Riak Ring 的东西来解决这个问题。

首先,Riak 将所有的 key 通过 hash 函数映射到一个 160 bit 的整数空间中。
即一个 key 对应着一个 0 ~ 2^160 - 1 的整数。

然后,Riak 引入了 vnode(虚拟节点) 的概念,vnode 个数是可以配置的,默认是 64。
160 bit 的整数会均匀的分布到所有的 vnode。

最后,这些 vnode 会"均匀地"分配到 物理节点上。具体的分配的方法很巧妙,通过 Riak Ring 这样的东西。

下面我们用一幅图来具体解释下 Riak Ring。图中,假设 vnode 32 个,服务器节点 4个。

clipboard.png

让我们把 160 bit 想像成一个环,环上的一小段代表一个 vnode。四种颜色分别代表 4 个服务器节点。

2^160 个整数按照从小到大的顺序均匀地分布到 32 个 vnode 中,例如 2^159 是第 17 个 vnode 上的第一个整数。

32 个 vnode 按照从小到大的顺序依次被分配到 4 个服务器节点上。即:

  • 1, 5, 9...29 vnode 分配给第1个服务器节点(node1)
  • 2, 6, 10...30 vnode 分配给第1个服务器节点(node2)
  • 3, 7, 11...31 vnode 分配给第1个服务器节点(node3)
  • 4, 8, 12...32 vnode 分配给第1个服务器节点(node4)

现在还剩下一个问题:

冗余数据的存储

我们先假设 N = 3(即有2份冗余存储)

假设要存储的数据,key 为 test-key ,根据 Riak Ring 算出来,应该存储在 vnode6(即:node2)上。
那么 拷贝1 存储在 vnode7(即:node3)上,拷贝2 存储在 vnode8(即:node4)上。

所以 Riak 对于冗余数据的存储策略是:将冗余数据依次存到下一个vnode中

查看原文

赞 0 收藏 7 评论 0

Michael_Ding 发布了文章 · 2015-05-28

Eventual Consistency(最终一致性) - 分布式数据库相关理论 Part2

1. Eventual Consistency 概述

分布式数据库必须要有 分区容忍性(Partition Tolerant),所以主要是在 一致性(Consistent)可用性(Available) 之间做选择。
虽然在 CAP 理论中,选择了 Availability 就不可能得到真正的 Consistency,但是你可以追求 最终一致性(Evental Consistency)

evental Consistency 背后的思路是:每个系统节点总是 Available 的,同时任何的写(修改数据)操作都会在后台同步给系统的其他节点。
这意味着,在任意时刻,整个系统是Inconsistent(不一致的),然而从概率上讲,大多数的请求得到的值是准确的。

互联网的 DNS(域名服务) 就是最终一致性的一个非常好的例子。你注册了一个域名,
这个新域名需要几天的时间才能通知给所有的 DNS 服务器。但是不管什么时候,你能够连接到的任意 DNS 服务器对你来说都是 'Available' 的。

2. Eventual Consistency 小故事

让我们接着之前的小故事.

假设你不是深山里,是被抓到一个孤岛上造方舟。

2015年7月3日——距离你被抓来造方舟已经将近3个月,你在孤岛的海边捡到了一个漂流瓶,里面写着:

最新一期暴走大事件是第四季第2期

所以你知道:最新一期暴走大事件至少是第四季第2期

假设暴漫的粉丝喜欢玩漂流瓶——只要暴走大事件有更新,就会把最新一期的暴走大事件写在纸上,然后通过漂流瓶扔向大海。
这样,像你这样的被 Partition 的人,总是能时不时地收到记录着最新一期暴走大事件是什么的漂流瓶。
换句话说,虽然每一时刻,关于“最新一期暴走大事件是什么”你并不一定知道的是正确的答案,但你总是会 eventually(最终) 知道正确答案。

查看原文

赞 1 收藏 7 评论 0

Michael_Ding 发布了文章 · 2015-05-28

理解 CAP 理论 - 分布式数据库相关理论 Part1

CAP 是分布式数据库中的重要理论之一。为了更好的理解分布式数据库,我们需要对 CAP 理论有个简单的理解。

1.CAP 概述

CAP 证明了,对于一个分布式数据库系统,存在这样三个指标:

  • C_onsistent_(一致性。写操作是 原子 的,当写操作完成后,所有后续的读取操作获取得到的都必须是新值),
  • A_vailable_(可用性。只要还有一个节点服务器在运行,整个系统对于请求总是要返回结果)
  • P_artition tolerant_(分区容忍性。当节点服务器之间的通信中断后,即:出现网络分区,整个系统还是能提供服务的)。

而你只能在这三个指标中同时照顾好两个。

根据 CAP 理论,当你在设计/使用分布式数据库时,你需要做出选择:在 Consistent, Available, Partition tolerant 中放弃什么。

Partition tolerant 是个架构选择(数据库是否是分布式),所以一般而言,你需要选择是更在意 Consistent 还是 Available。

理解 CAP 理论对于做出正确的选择是至关重要的。

2.CAP 小故事

为了更好地理解 CAP,这里以现实生活中的例子做个类比

假设这个世界是一个巨大的分布式系统,关于暴走漫画的知识是系统中存储的数据,暴漫的粉丝是这个分布式系统中的一个个节点。

假设今天你刚刚看了最新一期暴走大事件(第三季43集),而今天的日期是 2015年4月18日,突然有一伙儿神秘人闯进你家门,把你抓到了深山里,让你参与建造方舟,并且与世隔绝。

时光如梭,一转眼 5 年过去了,到了 2020年1月2日。方舟建成,你被送回了家乡。在回家的路上,你遇到一个路人,问了你一个问题:

暴走大事件最新一期是第几季第几集了?

这时候,你需要做一个选择:

你可以回答你知道的最新一期(第三季43集,5年前的最新一期)。如果你选择回答,那你就是 Available 的。或者你可以选择不回答,因为你已经与世隔绝了 5 年,你知道你的答案很可能和世界上其余暴漫粉丝的答案不一致(Consistent),这样这个路人得不到答案,但是整个世界是 Consistent 的。

即:你可以选择确保路人能得到答案(Available),或者确保世界的一致性(Consistent)。

查看原文

赞 4 收藏 11 评论 3

认证与成就

  • 获得 62 次点赞
  • 获得 14 枚徽章 获得 3 枚金徽章, 获得 4 枚银徽章, 获得 7 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2012-10-28
个人主页被 2.1k 人浏览