2017年3月1日

     摘要: from:https://zhangge.net/4703.html昨天,同事告诉我发现一个诡异的问题,grep无法搜索shell中的变量,着实很惊讶。到他所说的服务器上试了下,还真是不行!大概就是这样一个要求:①、有个文本为userid.txt,里面每一行一个用户id,类似如下:Shell1234500010003000500070009②、另外还有一个文本为record...  阅读全文
posted @ 2017-03-01 16:59 小马歌 阅读(38) | 评论 (0)编辑 收藏

2016年10月25日

来自: http://tonybai.com/2016/02/26/deploy-a-private-docker-registry/

安装部署一个私有的Docker Registry是引入、学习和使用 Docker 这门技术的必经之路之一。尤其是当Docker被所在组织接受,更多人、项目和产品开始接触和使用Docker时,存储和分发自制的Docker image便成了刚需。Docker Registry一如既往的继承了“Docker坑多”的特点,为此这里将自己搭建”各类”Registry过程中执行的步骤、遇到的问题记录下来,为己备忘,为他参考。

Docker在2015年推出了 distribution 项目,即Docker Registry 2。相比于 old registry ,Registry 2使用Go实现,在安全性、性能方面均有大幅改进。Registry设计了全新的Rest API,并且在image存储格式等方面不再兼容于old Registry。去年8月份,docker官方hub使用Registriy 2.1替代了原先的old Registry。如果你要与Registry2交互,你的Docker版本至少要是Docker 1.6。

Docker的开发者也一直在致力于改善Registry安装和使用的体验,通过提供 官方Registry Image以及 Docker Compose工具 等来简化Registry的配置。不过在本文中,我们只是利用Docker以及Registry的官方Image来部署Registry,这样更便于全面了解Registry的部署配置细节。

Registry2在镜像存储方面不仅支持本地盘,还支持诸多主流第三方存储方案。通过分布式存储系统你还可以实现一个分布式Docker Registry服务。这里仅以本地盘以及single node registry2为例。

一、环境

这里还是复用以往文章中的Docker环境:

Docker Registry Server: 10.10.105.71 Ubuntu 14.04 3.16.0-57-generic;docker 1.9.1

其他两个工作Server:
10.10.105.72 Ubuntu 14.04 3.19.0-25-generic; docker 1.9.1
10.10.126.101 Ubuntu 12.04 3.16.7-013607-generic; docker 1.9.1

本次Registry使用当前最新stable版本:Registry 2.3.0。由于镜像采用本地磁盘存储,root分区较小,需要映射使用其他volume。

二、初次搭建

本以为Docker Registry的搭建是何其简单的,甚至简单到通过一行命令就可以完成的。比如我们在Registry Server上执行:

在~/dockerregistry下,执行:

$sudo docker run -d -p 5000:5000 -v `pwd`/data:/var/lib/registry --restart=always --name registry registry:2
Unable to find image 'registry:2' locally
2: Pulling from library/registry
f32095d4ba8a: Pull complete
9b607719a62a: Pull complete
973de4038269: Pull complete
2867140211c1: Pull complete
8da16446f5ca: Pull complete
fd8c38b8b68d: Pull complete
136640b01f02: Pull complete
e039ba1c0008: Pull complete
c457c689c328: Pull complete
Digest: sha256:339d702cf9a4b0aa665269cc36255ee7ce424412d56bee9ad8a247afe8c49ef1
Status: Downloaded newer image for registry:2
e9088ef901cb00546c59f89defa4625230f4b36b0a44b3713f38ab3d2a5a2b44

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
registry            2                   c457c689c328        9 days ago          165.7 MB

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                    NAMES
e9088ef901cb        registry:2          "/bin/registry /etc/d"   About a minute ago   Up About a minute   0.0.0.0:5000->5000/tcp   registry

Registry container已经跑起来了,其启动日志可以通过:docker logs registry查看。

我们在71本地给busybox:latest打一个tag,并尝试将新tag下的image push到Registry中去:

$ docker tag busybox:latest 10.10.105.71:5000/tonybai/busybox:latest
$ docker images
REPOSITORY                          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
registry                            2                   c457c689c328        9 days ago          165.7 MB
busybox                             latest              65e4158d9625        9 days ago          1.114 MB
10.10.105.71:5000/tonybai/busybox   latest              65e4158d9625        9 days ago          1.114 MB
... ...

push到Registry中:

$ docker push 10.10.105.71:5000/tonybai/busybox
The push refers to a repository [10.10.105.71:5000/tonybai/busybox] (len: 1)
unable to ping registry endpoint https://10.10.105.71:5000/v0/
v2 ping attempt failed with error: Get https://10.10.105.71:5000/v2/: Tunnel or SSL Forbidden
 v1 ping attempt failed with error: Get https://10.10.105.71:5000/v1/_ping: Tunnel or SSL Forbidden

出错了!简单分析了一下,可能是71上docker daemon配置中加了http代理的缘故,导致无法ping通registry endpoint。于是在/etc/default/docker中注释掉export http_proxy=”xxx”的设置,并重启docker daemon。

再次尝试push:

$ docker push 10.10.105.71:5000/tonybai/busybox
The push refers to a repository [10.10.105.71:5000/tonybai/busybox] (len: 1)
unable to ping registry endpoint https://10.10.105.71:5000/v0/
v2 ping attempt failed with error: Get https://10.10.105.71:5000/v2/: tls: oversized record received with length 20527
 v1 ping attempt failed with error: Get https://10.10.105.71:5000/v1/_ping: tls: oversized record received with length 20527

虽然还是失败,但错误信息已有所不同了。这次看来连接是可以建立的,但client端通过https访问server端,似乎想tls通信,但这一过程并未完成。

在其他机器上尝试push image到registry也遇到了同样的错误输出,如下:

10.10.105.72:

$ docker push 10.10.105.71:5000/tonybai/ubuntu
The push refers to a repository [10.10.105.71:5000/tonybai/ubuntu] (len: 1)
unable to ping registry endpoint https://10.10.105.71:5000/v0/
v2 ping attempt failed with error: Get https://10.10.105.71:5000/v2/: tls: oversized record received with length 20527
 v1 ping attempt failed with error: Get https://10.10.105.71:5000/v1/_ping: tls: oversized record received with length 20527

从错误信息来看,client与Registry交互,默认将采用https访问,但我们在install Registry时并未配置指定任何tls相关的key和crt文件,https访问定然失败。要想弄清这个问题,只能查看 Registry Manual 。

三、Insecure Registry

Registry的文档还是相对详尽的。在文档中,我们找到了 Insecure Registry ,即接收plain http访问的Registry的配置和使用方法,虽然这不是官方推荐的。

实际上对于我们内部网络而言,Insecure Registry基本能满足需求,部署过程也避免了secure registry的那些繁琐步骤,比如制作和部署证书等。

为了搭建一个Insecure Registry,我们需要先清理一下上面已经启动的Registry容器。

$ docker stop registry
registry
$ docker rm registry
registry

修改Registry server上的Docker daemon的配置,为DOCKER_OPTS增加–insecure-registry:

DOCKER_OPTS="--insecure-registry 10.10.105.71:5000 ....

重启Docker Daemon,启动Registry容器:

$ sudo service docker restart
docker stop/waiting
docker start/running, process 6712
$ sudo docker run -d -p 5000:5000 -v `pwd`/data:/var/lib/registry --restart=always --name registry registry:2
5966e92fce9c34705050e19368d19574e021a272ede1575385ef35ecf5cea019

尝试再次Push image:

$ docker push 10.10.105.71:5000/tonybai/busybox
The push refers to a repository [10.10.105.71:5000/tonybai/busybox] (len: 1)
65e4158d9625: Pushed
5506dda26018: Pushed
latest: digest: sha256:800f2d4558acd67f52262fbe170c9fc2e67efaa6f230a74b41b555e6fcca2892 size: 2739

这回push ok!

我们将本地的tag做untag处理,再从Registry pull相关image:

$ docker images
REPOSITORY                          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
registry                            2                   c457c689c328        9 days ago          165.7 MB
10.10.105.71:5000/tonybai/busybox   latest              65e4158d9625        9 days ago          1.114 MB
busybox                             latest              65e4158d9625        9 days ago          1.114 MB
ubuntu                              14.04               6cc0fc2a5ee3        5 weeks ago         187.9 MB

$ docker rmi 10.10.105.71:5000/tonybai/busybox
Untagged: 10.10.105.71:5000/tonybai/busybox:latest

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
registry            2                   c457c689c328        9 days ago          165.7 MB
busybox             latest              65e4158d9625        9 days ago          1.114 MB
ubuntu              14.04               6cc0fc2a5ee3        5 weeks ago         187.9 MB

$ docker pull 10.10.105.71:5000/tonybai/busybox
Using default tag: latest
latest: Pulling from tonybai/busybox
Digest: sha256:800f2d4558acd67f52262fbe170c9fc2e67efaa6f230a74b41b555e6fcca2892
Status: Downloaded newer image for 10.10.105.71:5000/tonybai/busybox:latest

$ docker images
REPOSITORY                          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
registry                            2                   c457c689c328        9 days ago          165.7 MB
10.10.105.71:5000/tonybai/busybox   latest              65e4158d9625        9 days ago          1.114 MB
busybox                             latest              65e4158d9625        9 days ago          1.114 MB
ubuntu                              14.04               6cc0fc2a5ee3        5 weeks ago         187.9 MB

可以看到:Pull过程也很顺利。

在Private Registry2中查看或检索Repository或images, 将不能用docker search :

$ docker search 10.10.105.71:5000/tonybai/busybox/
Error response from daemon: Unexpected status code 404

但通过v2版本的API,我们可以实现相同目的:

$curl  http://10.10.105.71:5000/v2/_catalog
{"repositories":["tonybai/busybox"]}

$ curl  http://10.10.105.71:5000/v2/tonybai/busybox/tags/list
{"name":"tonybai/busybox","tags":["latest"]}

在其他主机上,我们尝试pull busybox:

10.10.105.72:

$docker pull 10.10.105.71:5000/tonybai/busybox
Using default tag: latest
Error response from daemon: unable to ping registry endpoint https://10.10.105.71:5000/v0/
v2 ping attempt failed with error: Get https://10.10.105.71:5000/v2/: tls: oversized record received with length 20527
 v1 ping attempt failed with error: Get https://10.10.105.71:5000/v1/_ping: tls: oversized record received with length 20527

我们发现依旧不能pull和push!在Registry手册中讲到,如果采用insecure registry的模式,那么所有与Registry交互的主机上的Docker Daemon都要配置:–insecure-registry选项。

我们按照上面的配置方法,修改105.72上的/etc/default/docker,重启Docker daemon,再执行pull/push就会得到正确的结果:

$ sudo vi /etc/default/docker
$ sudo service docker restart
docker stop/waiting
docker start/running, process 10614
$ docker pull 10.10.105.71:5000/tonybai/busybox
Using default tag: latest
latest: Pulling from tonybai/busybox
5506dda26018: Pull complete
65e4158d9625: Pull complete
Digest: sha256:800f2d4558acd67f52262fbe170c9fc2e67efaa6f230a74b41b555e6fcca2892
Status: Downloaded newer image for 10.10.105.71:5000/tonybai/busybox:latest

$ docker images
REPOSITORY                          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu                              14.04               36248ae4a9ac        8 days ago          187.9 MB
10.10.105.71:5000/tonybai/ubuntu    14.04               36248ae4a9ac        8 days ago          187.9 MB
10.10.105.71:5000/tonybai/busybox   latest              65e4158d9625        9 days ago          1.114 MB

$ docker push 10.10.105.71:5000/tonybai/ubuntu
The push refers to a repository [10.10.105.71:5000/tonybai/ubuntu] (len: 1)
36248ae4a9ac: Pushed
8ea5373bf5a6: Pushed
2e0188208e83: Pushed
e3c70beaa378: Pushed
14.04: digest: sha256:72e56686cb9fb38438f0fd68fecf02ef592ce2ef7069bbf97802d959d568c5cc size: 6781

四、Secure Registry

Docker官方是推荐你采用Secure Registry的工作模式的,即transport采用tls。这样我们就需要为Registry配置tls所需的key和crt文件了。

我们首先清理一下环境,将上面的Insecure Registry停掉并rm掉;将各台主机上Docker Daemon的DOCKER_OPTS配置中的–insecure-registry去掉,并重启Docker Daemon。

如果你拥有一个域名,域名下主机提供Registry服务,并且你拥有某知名CA签署的证书文件,那么你可以建立起一个Secure Registry。不过我这里没有现成的证书,只能使用自签署的证书。严格来讲,使用自签署的证书在Docker官方眼中依旧属于Insecure,不过这里只是借助自签署的证书来说明一下Secure Registry的部署步骤罢了。

1、制作自签署证书

如果你有知名CA签署的证书,那么这步可直接忽略。

$ openssl req -newkey rsa:2048 -nodes -sha256 -keyout certs/domain.key -x509 -days 365 -out certs/domain.crt
Generating a 2048 bit RSA private key
..............+++
............................................+++
writing new private key to 'certs/domain.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:Liaoning
Locality Name (eg, city) []:shenyang
Organization Name (eg, company) [Internet Widgits Pty Ltd]:foo
Organizational Unit Name (eg, section) []:bar
Common Name (e.g. server FQDN or YOUR name) []:mydockerhub.com
Email Address []:bigwhite.cn@gmail.com

2、启动Secure Registry

启动带证书的Registry:

$ docker run -d -p 5000:5000 --restart=always --name registry \
  -v `pwd`/data:/var/lib/registry \
  -v `pwd`/certs:/certs \
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
  -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
  registry:2
35e8ce77dd455f2bd50854e4581cd52be8a137f4aaea717239b6d676c5ea5777

由于证书的CN是mydockerhub.com,我们需要修改一下/etc/hosts文件:

10.10.105.71 mydockerhub.com

重新为busybox制作一个tag:

$docker tag busybox:latest mydockerhub.com:5000/tonybai/busybox:latest

Push到Registry:

$ docker push mydockerhub.com:5000/tonybai/busybox
The push refers to a repository [mydockerhub.com:5000/tonybai/busybox] (len: 1)
unable to ping registry endpoint https://mydockerhub.com:5000/v0/
v2 ping attempt failed with error: Get https://mydockerhub.com:5000/v2/: x509: certificate signed by unknown authority
 v1 ping attempt failed with error: Get https://mydockerhub.com:5000/v1/_ping: x509: certificate signed by unknown authority

push失败了!从错误日志来看,docker client认为server传输过来的证书的签署方是一个unknown authority(未知的CA),因此验证失败。我们需要让docker client安装我们的CA证书:

$ sudo mkdir -p /etc/docker/certs.d/mydockerhub.com:5000
$ sudo cp certs/domain.crt /etc/docker/certs.d/mydockerhub.com:5000/ca.crt
$ sudo service docker restart //安装证书后,重启Docker Daemon

再执行Push,我们看到了成功的输出日志。由于data目录下之前已经被push了tonybai/busybox repository,因此提示“已存在”:

$docker push mydockerhub.com:5000/tonybai/busybox
The push refers to a repository [mydockerhub.com:5000/tonybai/busybox] (len: 1)
65e4158d9625: Image already exists
5506dda26018: Image already exists
latest: digest: sha256:800f2d4558acd67f52262fbe170c9fc2e67efaa6f230a74b41b555e6fcca2892 size: 2739

3、外部访问Registry

我们换其他机器试试访问这个secure registry。根据之前的要求,我们照猫画虎的修改一下hosts文件,安装ca.cert,去除–insecure-registry选项,并重启Docker daemon。之后尝试从registry pull image:

$ docker pull mydockerhub.com:5000/tonybai/busybox
Using default tag: latest
latest: Pulling from tonybai/busybox

Digest: sha256:800f2d4558acd67f52262fbe170c9fc2e67efaa6f230a74b41b555e6fcca2892
Status: Downloaded newer image for mydockerhub.com:5000/tonybai/busybox:latest

$ docker images
REPOSITORY                             TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
10.10.105.71:5000/tonybai/ubuntu       14.04               36248ae4a9ac        9 days ago          187.9 MB
ubuntu                                 14.04               36248ae4a9ac        9 days ago          187.9 MB
10.10.105.71:5000/tonybai/busybox      latest              65e4158d9625        9 days ago          1.114 MB
mydockerhub.com:5000/tonybai/busybox   latest              65e4158d9625        9 days ago          1.114 MB

这样来看,如果使用自签署的证书,那么所有要与Registry交互的Docker主机都需要安装mydockerhub.com的ca.crt(domain.crt)。但如果你使用知名CA,这一步也就可以忽略。

五、Registry的鉴权管理

Registry提供了一种基础的鉴权方式。我们通过下面步骤即可为Registry加上基础鉴权:

在Register server上,为Registry增加foo用户,密码foo123:(之前需要停掉已有的Registry,并删除之)

//生成鉴权密码文件
$ mkdir auth
$ docker run --entrypoint htpasswd registry:2 -Bbn foo foo123  > auth/htpasswd
$ ls auth
htpasswd

//启动带鉴权功能的Registry:
$ docker run -d -p 5000:5000 --restart=always --name registry \
   -v `pwd`/auth:/auth \
   -e "REGISTRY_AUTH=htpasswd" \
   -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
   -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
   -v `pwd`/data:/var/lib/registry \
   -v `pwd`/certs:/certs \
   -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
   -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
   registry:2
199ad0b3591fb9613b21b1c96f017267f3c39661a7025d30df636c6805e7ab50

在105.72上,我们尝试push image到Registry:

$ docker push mydockerhub.com:5000/tonybai/busybox
The push refers to a repository [mydockerhub.com:5000/tonybai/busybox] (len: 1)
65e4158d9625: Image push failed
Head https://mydockerhub.com:5000/v2/tonybai/busybox/blobs/sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4: no basic auth credentials

错误信息提示:鉴权失败。

在72上执行docker login:

$docker login mydockerhub.com:5000
Username: foo
Password:
Email: bigwhite.cn@gmail.com
WARNING: login credentials saved in /home/baiming/.docker/config.json
Login Succeeded

login成功后,再行Push:

$ docker push mydockerhub.com:5000/tonybai/busybox
The push refers to a repository [mydockerhub.com:5000/tonybai/busybox] (len: 1)
65e4158d9625: Image already exists
5506dda26018: Image already exists
latest: digest: sha256:800f2d4558acd67f52262fbe170c9fc2e67efaa6f230a74b41b555e6fcca2892 size: 2739

Push ok!

六、Registry中images的管理

前面提到过,通过V2版Rest API可以查询Repository和images:

$ curl --cacert domain.crt  --basic --user foo:foo123 https://mydockerhub.com:5000/v2/_catalog
{"repositories":["tonybai/busybox","tonybai/ubuntu"]}

但如果要删除Registry中的Repository或某个tag的Image,目前v2还不支持,原因见 Registry的roadmap中的说明 。

不过如果你的Registry的存储引擎使用的是本地盘,倒是有一些第三方脚本可供使用,比如:delete-docker-registry-image 。

七、小结

Registry2发布不到1年,目前还有许多问题待解决,就比如delete image的问题,相信在2.4以及后续版本这些问题会被逐个解决掉或能找到一个相对理想的方案。

posted @ 2016-10-25 14:24 小马歌 阅读(56) | 评论 (0)编辑 收藏
 
from:http://www.infoq.com/cn/articles/centos7-practical-kubernetes-deployment

. 前言

上一节我们阐述了Kubernetes的系统架构,让大家对Kubernetes有一定的初步了解,但是就如何使用Kubernetes, 也许大家还不知如何下手。本文作者将带领大家如何在本地部署、配置Kubernetes集群网络环境以及通过实例演示跨机器服务间的通信,主要包括如下内容:

  • 部署环境介绍
  • Kubernetes集群逻辑架构
  • 部署Open vSwitch、Kubernetes、Etcd组件
  • 演示Kubernetes管理容器

2. 部署环境

  • VMware Workstation:10.0.3
  • VMware Workstation网络模式:NAT
  • 操作系统信息:CentOS 7 64位
  • Open vSwitch版本信息:2.3.0
  • Kubernetes版本信息:0.5.2
  • Etcd版本信息:0.4.6
  • Docker版本信息:1.3.1
  • 服务器信息:

            | Role      | Hostname   | IP Address  | 	|:---------:|:----------:|:----------: | 	|APIServer  |kubernetes  |192.168.230.3| 	|Minion     | minion1    |192.168.230.4| 	|Minion     | minion2    |192.168.230.5|

3. Kubernetes集群逻辑架构

在详细介绍部署Kubernetes集群前,先给大家展示下集群的逻辑架构。从下图可知,整个系统分为两部分,第一部分是Kubernetes APIServer,是整个系统的核心,承担集群中所有容器的管理工作;第二部分是minion,运行Container Daemon,是所有容器栖息之地,同时在minion上运行Open vSwitch程序,通过GRE Tunnel负责minion之间Pod的网络通信工作。

4. 部署Open vSwitch、Kubernetes、Etcd组件

4.1 安装Open vSwitch及配置GRE

为了解决跨minion之间Pod的通信问题,我们在每个minion上安装Open vSwtich,并使用GRE或者VxLAN使得跨机器之间Pod能相互通信,本文使用GRE,而VxLAN通常用在需要隔离的大规模网络中。对于Open vSwitch的具体安装步骤,可参考这篇博客,我们在这里就不再详细介绍安装步骤了。安装完Open vSwitch后,接下来便建立minion1和minion2之间的隧道。首先在minion1和minion2上建立OVS Bridge,

[root@minion1 ~]# ovs-vsctl add-br obr0 

接下来建立gre,并将新建的gre0添加到obr0,在minion1上执行如下命令,

[root@minion1 ~]# ovs-vsctl add-port obr0 gre0 -- set Interface gre0 type=gre options:remote_ip=192.168.230.5 

在minion2上执行,

[root@minion2 ~]# ovs-vsctl add-port obr0 gre0 -- set Interface gre0 type=gre options:remote_ip=192.168.230.4 

至此,minion1和minion2之间的隧道已经建立。然后我们在minion1和minion2上创建Linux网桥kbr0替代Docker默认的docker0(我们假设minion1和minion2都已安装Docker),设置minion1的kbr0的地址为172.17.1.1/24, minion2的kbr0的地址为172.17.2.1/24,并添加obr0为kbr0的接口,以下命令在minion1和minion2上执行。

[root@minion1 ~]# brctl addbr kbr0               //创建linux bridge [root@minion1 ~]# brctl addif kbr0 obr0          //添加obr0为kbr0的接口 [root@minion1 ~]# ip link set dev docker0 down   //设置docker0为down状态 [root@minion1 ~]# ip link del dev docker0        //删除docker0 

为了使新建的kbr0在每次系统重启后任然有效,我们在/etc/sysconfig/network-scripts/目录下新建minion1的ifcfg-kbr0如下:

DEVICE=kbr0 ONBOOT=yes BOOTPROTO=static IPADDR=172.17.1.1 NETMASK=255.255.255.0 GATEWAY=172.17.1.0 USERCTL=no TYPE=Bridge IPV6INIT=no 

同样在minion2上新建ifcfg-kbr0,只需修改ipaddr为172.17.2.1和gateway为172.17.2.0即可,然后执行systemctl restart network重启系统网络服务,你能在minion1和minion2上发现kbr0都设置了相应的IP地址。为了验证我们创建的隧道是否能通信,我们在minion1和minion2上相互ping对方kbr0的IP地址,从下面的结果发现是不通的,经查找这是因为在minion1和minion2上缺少访问172.17.1.1和172.17.2.1的路由,因此我们需要添加路由保证彼此之间能通信。

[root@minion1 network-scripts]# ping 172.17.2.1 PING 172.17.2.1 (172.17.2.1) 56(84) bytes of data. ^C --- 172.17.2.1 ping statistics --- 2 packets transmitted, 0 received, 100% packet loss, time 1000ms  [root@minion2 ~]#  ping 172.17.1.1 PING 172.17.1.1 (172.17.1.1) 56(84) bytes of data. ^C --- 172.17.1.1 ping statistics --- 2 packets transmitted, 0 received, 100% packet loss, time 1000ms 

由于通过ip route add添加的路由会在下次系统重启后失效,为此我们在/etc/sysconfig/network-scripts目录下新建一个文件route-eth0存储路由,这里需要注意的是route-eth0和ifcfg-eth0的黑体部分必须保持一致,否则不能工作,这样添加的路由在下次重启后不会失效。为了保证两台minion的kbr0能相互通信,我们在minion1的route-eth0里添加路由172.17.2.0/24 via 192.168.230.5 dev eno16777736,eno16777736是minion1的网卡,同样在minion2的route-eth0里添加路由172.17.1.0/24 via 192.168.230.4 dev eno16777736。重启网络服务后再次验证,彼此kbr0的地址可以ping通,如:

[root@minion2 network-scripts]# ping 172.17.1.1 PING 172.17.1.1 (172.17.1.1) 56(84) bytes of data. 64 bytes from 172.17.1.1: icmp_seq=1 ttl=64 time=2.49 ms 64 bytes from 172.17.1.1: icmp_seq=2 ttl=64 time=0.512 ms ^C --- 172.17.1.1 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1002ms rtt min/avg/max/mdev = 0.512/1.505/2.498/0.993 ms 

到现在我们已经建立了两minion之间的隧道,而且能正确的工作。下面我们将介绍如何安装Kubernetes APIServer及kubelet、proxy等服务。

4.2 安装Kubernetes APIServer

在安装APIServer之前,我们先下载Kubernetes及Etcd,做一些准备工作。在kubernetes上的具体操作如下:

[root@kubernetes ~]# mkdir /tmp/kubernetes [root@kubernetes ~]# cd /tmp/kubernetes/ [root@kubernetes kubernetes]# wget https://github.com/GoogleCloudPlatform/kubernetes/releases/download/v0.5.2/kubernetes.tar.gz [root@kubernetes kubernetes]# wget https://github.com/coreos/etcd/releases/download/v0.4.6/etcd-v0.4.6-linux-amd64.tar.gz 

然后解压下载的kubernetes和etcd包,并在kubernetes、minion1、minion2上创建目录/opt/kubernetes/bin,

[root@kubernetes kubernetes]# mkdir -p /opt/kubernetes/bin [root@kubernetes kubernetes]# tar xf kubernetes.tar.gz [root@kubernetes kubernetes]# tar xf etcd-v0.4.6-linux-amd64.tar.gz [root@kubernetes kubernetes]# cd ~/kubernetes/server [root@kubernetes server]# tar xf kubernetes-server-linux-amd64.tar.gz [root@kubernetes kubernetes]# /tmp/kubernetes/kubernetes/server/kubernetes/server/bin 

复制kube-apiserver,kube-controller-manager,kube-scheduler,kubecfg到kubernetes的/opt/kubernetes/bin目录下,而kubelet,kube-proxy则复制到minion1和minion2的/opt/kubernetes/bin,并确保都是可执行的。

[root@kubernetes amd64]# cp kube-apiserver kube-controller-manager kubecfg kube-scheduler /opt/kubernetes/bin [root@kubernetes amd64]# scp kube-proxy kubelet root@192.168.230.4:/opt/kubernetes/bin [root@kubernetes amd64]# scp kube-proxy kubelet root@192.168.230.5:/opt/kubernetes/bin 

为了简单我们只部署一台etcd服务器,如果需要部署etcd的集群,请参考官方文档,在本文中将其跟Kubernetes APIServer部署同一台机器上,而且将etcd放置在/opt/kubernetes/bin下,etcdctl跟ectd同一目录。

[root@kubernetes kubernetes]# cd /tmp/kubernetes/etcd-v0.4.6-linux-amd64 [root@kubernetes etcd-v0.4.6-linux-amd64]# cp etcd etcdctl /opt/kubernetes/bin 

需注意的是kubernetes和minion上/opt/kubernetes/bin目录下的文件都必须是可执行的。到目前,我们准备工作已经差不多,现在开始给apiserver,controller-manager,scheduler,etcd配置unit文件。首先我们用如下脚本etcd.sh配置etcd的unit文件,

#!/bin/sh  ETCD_PEER_ADDR=192.168.230.3:7001 ETCD_ADDR=192.168.230.3:4001 ETCD_DATA_DIR=/var/lib/etcd ETCD_NAME=kubernetes  ! test -d $ETCD_DATA_DIR && mkdir -p $ETCD_DATA_DIR cat <<EOF >/usr/lib/systemd/system/etcd.service [Unit] Description=Etcd Server  [Service] ExecStart=/opt/kubernetes/bin/etcd \\     -peer-addr=$ETCD_PEER_ADDR \\     -addr=$ETCD_ADDR \\     -data-dir=$ETCD_DATA_DIR \\     -name=$ETCD_NAME \\     -bind-addr=0.0.0.0  [Install] WantedBy=multi-user.target EOF  systemctl daemon-reload systemctl enable etcd systemctl start etcd 

对剩下的apiserver,controller-manager,scheduler的unit文件配置的脚本,可以在github 上GetStartingKubernetes找到,在此就不一一列举。运行相应的脚本后,在APIServer上etcd, apiserver, controller-manager, scheduler服务就能正常运行。

4.3 安装Kubernetes Kubelet及Proxy

根据Kubernetes的设计架构,需要在minion上部署docker, kubelet, kube-proxy,在4.2节部署APIServer时,我们已经将kubelet和kube-proxy已经分发到两minion上,所以只需配置docker,kubelet,proxy的unit文件,然后启动服务就即可,具体配置见GetStartingKubernetes

5. 演示Kubernetes管理容器

为了方便,我们使用Kubernetes提供的例子Guestbook来演示Kubernetes管理跨机器运行的容器,下面我们根据Guestbook的步骤创建容器及服务。在下面的过程中如果是第一次操作,可能会有一定的等待时间,状态处于pending,这是因为第一次下载images需要一段时间。

5.1 创建redis-master Pod和redis-master服务

[root@kubernetes ~]# cd /tmp/kubernetes/kubernetes/examples/guestbook [root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 -c redis-master.json create pods [root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 -c redis-master-service.json create services 

完成上面的操作后,我们可以看到如下redis-master Pod被调度到192.168.230.4。

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list pods Name                                   Image(s)                   Host                Labels                                       Status ----------                             ----------                 ----------          ----------                                   ---------- redis-master                           dockerfile/redis           192.168.230.4/      name=redis-master                            Running 

但除了发现redis-master的服务之外,还有两个Kubernetes系统默认的服务kubernetes-ro和kubernetes。而且我们可以看到每个服务都有一个服务IP及相应的端口,对于服务IP,是一个虚拟地址,根据apiserver的portal_net选项设置的CIDR表示的IP地址段来选取,在我们的集群中设置为10.10.10.0/24。为此每新创建一个服务,apiserver都会在这个地址段中随机选择一个IP作为该服务的IP地址,而端口是事先确定的。对redis-master服务,其服务地址为10.10.10.206,端口为6379。

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list services Name                Labels              Selector                                  IP                  Port ----------          ----------          ----------                                ----------          ---------- kubernetes-ro                           component=apiserver,provider=kubernetes   10.10.10.207        80 redis-master        name=redis-master   name=redis-master                         10.10.10.206        6379 kubernetes                              component=apiserver,provider=kubernetes   10.10.10.161        443 

5.2 创建redis-slave Pod和redis-slave服务

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 -c redis-slave-controller.json create replicationControllers [root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 -c redis-slave-service.json create services 

然后通过list命令可知新建的redis-slave Pod根据调度算法调度到两台minion上,服务IP为10.10.10.92,端口为6379

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list pods Name                                   Image(s)                   Host                Labels                                       Status ----------                             ----------                 ----------          ----------                                   ---------- redis-master                           dockerfile/redis           192.168.230.4/      name=redis-master                            Running 8c0ddbda-728c-11e4-8233-000c297db206   brendanburns/redis-slave   192.168.230.5/      name=redisslave,uses=redis-master            Running 8c0e1430-728c-11e4-8233-000c297db206   brendanburns/redis-slave   192.168.230.4/      name=redisslave,uses=redis-master            Running  [root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list services Name                Labels              Selector                                  IP                  Port ----------          ----------          ----------                                ----------          ---------- redisslave          name=redisslave     name=redisslave                           10.10.10.92         6379 kubernetes                              component=apiserver,provider=kubernetes   10.10.10.161        443 kubernetes-ro                           component=apiserver,provider=kubernetes   10.10.10.207        80 redis-master        name=redis-master   name=redis-master                         10.10.10.206        6379 

5.3 创建Frontend Pod和Frontend服务

在创建之前修改frontend-controller.json的Replicas数量为2,这是因为我们的集群中只有2台minion,如果按照frontend-controller.json的Replicas默认值3,那会导致有2个Pod会调度到同一台minion上,产生端口冲突,有一个Pod会一直处于pending状态,不能被调度。

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 -c frontend-controller.json create replicationControllers [root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 -c frontend-service.json create services 

通过查看可知Frontend Pod也被调度到两台minion,服务IP为10.10.10.220,端口是80。

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list pods Name                                   Image(s)                   Host                Labels                                       Status ----------                             ----------                 ----------          ----------                                   ---------- redis-master                           dockerfile/redis           192.168.230.4/      name=redis-master                            Running 8c0ddbda-728c-11e4-8233-000c297db206   brendanburns/redis-slave   192.168.230.5/      name=redisslave,uses=redis-master            Running 8c0e1430-728c-11e4-8233-000c297db206   brendanburns/redis-slave   192.168.230.4/      name=redisslave,uses=redis-master            Running a880b119-7295-11e4-8233-000c297db206   brendanburns/php-redis     192.168.230.4/      name=frontend,uses=redisslave,redis-master   Running a881674d-7295-11e4-8233-000c297db206   brendanburns/php-redis     192.168.230.5/      name=frontend,uses=redisslave,redis-master   Running  [root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list services Name                Labels              Selector                                  IP                  Port ----------          ----------          ----------                                ----------          ---------- kubernetes-ro                           component=apiserver,provider=kubernetes   10.10.10.207        80 redis-master        name=redis-master   name=redis-master                         10.10.10.206        6379 redisslave          name=redisslave     name=redisslave                           10.10.10.92         6379 frontend            name=frontend       name=frontend                             10.10.10.220        80 kubernetes                              component=apiserver,provider=kubernetes   10.10.10.161        443 

除此之外,你可以删除Pod、Service及更新ReplicationController的Replicas数量等操作,如删除Frontend服务:

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 delete services/frontend Status ---------- Success 

还可以更新ReplicationController的Replicas的数量,下面是更新Replicas之前ReplicationController的信息。

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list replicationControllers Name                   Image(s)                   Selector            Replicas ----------             ----------                 ----------          ---------- redisSlaveController   brendanburns/redis-slave   name=redisslave     2 frontendController     brendanburns/php-redis     name=frontend       2 

现在我们想把frontendController的Replicas更新为1,则这行如下命令,然后再通过上面的命令查看frontendController信息,发现Replicas已变为1。

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 resize frontendController 1  [root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list replicationControllers Name                   Image(s)                   Selector            Replicas ----------             ----------                 ----------          ---------- redisSlaveController   brendanburns/redis-slave   name=redisslave     2 frontendController     brendanburns/php-redis     name=frontend       1 

5.4 演示跨机器服务通信

完成上面的操作后,我们来看当前Kubernetes集群中运行着的Pod信息。

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list pods Name                                   Image(s)                   Host                Labels                                       Status ----------                             ----------                 ----------          ----------                                   ---------- a881674d-7295-11e4-8233-000c297db206   brendanburns/php-redis     192.168.230.5/      name=frontend,uses=redisslave,redis-master   Running redis-master                           dockerfile/redis           192.168.230.4/      name=redis-master                            Running 8c0ddbda-728c-11e4-8233-000c297db206   brendanburns/redis-slave   192.168.230.5/      name=redisslave,uses=redis-master            Running 8c0e1430-728c-11e4-8233-000c297db206   brendanburns/redis-slave   192.168.230.4/      name=redisslave,uses=redis-master            Running 

通过上面的结果可知当前提供前端服务的PHP和提供数据存储的后端服务Redis master的Pod分别运行在192.168.230.5和192.168.230.4上,即容器运行在不同主机上,还有Redis slave也运行在两台不同的主机上,它会从Redis master同步前端写入Redis master的数据。下面我们从两方面验证Kubernetes能提供跨机器间容器的通信:

  • 在浏览器打开http://${IPAddress}:8000,IPAddress为PHP容器运行的minion的IP地址,其暴漏的端口为8000,这里IP_Address为192.168.230.5。打开浏览器会显示如下信息:

    你可以输入信息并提交,如"Hello Kubernetes"、"Container",然后Submit按钮下方会显示你输入的信息。

    由于前端PHP容器和后端Redis master容器分别在两台minion上,因此PHP在访问Redis master服务时一定得跨机器通信,可见Kubernetes的实现方式避免了用link只能在同一主机上实现容器间通信的缺陷,对于Kubernetes跨机器通信的实现方法,以后我会详细介绍。

     

  • 从上面的结果,可得知已经实现了跨机器的通信,现在我们从后端数据层验证不同机器容器间的通信。根据上面的输出结果发现Redis slave和Redis master分别调度到两台不同的minion上,在192.168.230.4主机上执行docker exec -ti c41711cc8971 /bin/sh,c41711cc8971是Redis master的容器ID,进入容器后通过redis-cli命令查看从浏览器输入的信息如下:

    如果我们在192.168.230.5上运行的Redis slave容器里查到跟Redis master容器里相同的信息,那说明Redis master和Redis slave之间的数据同步正常工作,下面是从192.168.230.5上运行的Redis slave容器查询到的信息:

    由此可见Redis master和Redis slave之间数据同步正常,OVS GRE隧道技术使得跨机器间容器正常通信。

6. 结论

本文主要介绍如何在本地环境部署Kubernetes集群和演示如何通过Kubernetes管理集群中运行的容器,并通过OVS管理集群不同minion的Pod之间的网络通信。接下来会对Kubernetes各个组件源码进行详细分析,阐述Kubernetes的工作原理。

7. 个人简介

杨章显,现就职于Cisco,主要从事WebEx SaaS服务运维,系统性能分析等工作。特别关注云计算,自动化运维,部署等技术,尤其是Go、OpenvSwitch、Docker及其生态圈技术,如Kubernetes、Flocker等Docker相关开源项目。Email: yangzhangxian@gmail.com

8. 参考资料

  1. https://n40lab.wordpress.com/2014/09/04/openvswitch-2-3-0-lts-and-centos-7/
  2. https://github.com/GoogleCloudPlatform/kubernetes/tree/master/examples/guestbook

感谢郭蕾对本文的策划和审校。

posted @ 2016-10-25 14:23 小马歌 阅读(65) | 评论 (0)编辑 收藏
 
from:http://blog.csdn.net/linuxgo/article/details/52121125

加快Kubernetes编译速度

除了Linux/amd64,默认还会为其他平台做交叉编译。为了减少编译时间,可以修改hack/lib/golang.sh,把KUBE_SERVER_PLATFORMS, KUBE_CLIENT_PLATFORMS和KUBE_TEST_PLATFORMS中除linux/amd64以外的其他平台注释掉

gcr.io无法访问

Kubernetes在创建Pod的时候,需要从gcr.io下载一个helper镜像(目前是 gcr.io/google_containers/pause-amd64:3.0 )。

但是目前国内无法访问gcr.io,这个问题会导致无法下载该镜像,然后Pod一直处于ContainerCreating状态。

解决办法

1) 在可以访问gcr.io的地方

docker pull gcr.io/google_containers/pause-amd64:3.0

传到私有docker registry

docker tag gcr.io/google_containers/pause-amd64:3.0 k8s-docker.mydomain.com/google_containers/pause-amd64:3.0

docker push k8s-docker.mydomain.com/google_containers/pause-amd64:3.0

2) 在所有的k8s节点

docker pull k8s-docker.mydomain.com/google_containers/pause-amd64:3.0
docker tag k8s-docker.mydomain.com/google_containers/pause-amd64:3.0 gcr.io/google_containers/pause-amd64:3.0

Note

不通过私有registry中转,而是使用Docker save/load应该也可以,只是要把save导出的文件复制到所有节点.

如何从集群外访问Service和Pod

这里说的集群外是指K8s集群以外的主机,比如使用nginx/HAProxy搭建的负载均衡主机。这些主机跟K8s集群部署在一起,到K8s网络可达。

对于不是部署在GCE以及AWS等云平台的K8s,我们一般需要自己搭建负载均衡,然后分发请求到到Service。

使用NodePort方式发布服务,那么负载均衡主机上不需要额外配置;使用ClusterIP方式,为了能够访问Service的ClusterIP, 需要在这些主机上安装Flanneld和kube-proxy

posted @ 2016-10-25 14:23 小马歌 阅读(154) | 评论 (0)编辑 收藏

2016年10月18日

优化CMS(concurrent garbage collection)
   使用CMS,old代的垃圾回收执行线程会和应用程序的线程最大程度的并发执行。这个提供了一个机会来减少最坏延迟的频率和最坏延迟的时间消耗。CMS没有执行压缩,所以可以避免old代空间的stop-the-world压缩(会让整个应用暂停运行)。

   优化CMS的目标就是避开stop-the-world压缩垃圾回收,然而,这个说比做起来容易。在一些的部署情况下,这个是不可避免的,尤其是当内存分配受限的时候。

   在一些特殊的情况下,CMS比其他类型的垃圾回收需要更多优化,更需要优化young代的空间,以及潜在的优化该什么时候初始化old代的垃圾回收循环。

   当从吞吐量垃圾回收器(Throughput)迁移到CMS的时候,有可能会获得更慢的MinorGC,由于对象从young代转移到old会更慢 ,由于CMS在old代里面分配的内存是一个不连续的列表,相反,吞吐量垃圾回收器只是在本地线程的分配缓存里面指定一个指针。另外,由于old代的垃圾回收线程和应用的线程是尽可能的并发运行的,所以吞吐量会更小一些。然而,最坏的延迟的频率会少很多,由于在old代的不可获取的对象能够在应用运行的过程被垃圾回收,这样可以避免old代的空间溢出。

   使用CMS,如果old代能够使用的空间有限,单线程的stop-the-world压缩垃圾回收会执行。这种情况下,FullGC的时间会比吞吐量垃圾回收器的FullGC时间还要长,导致的结果是,CMS的绝对最差延迟会比吞吐量垃圾回收器的最差延迟严重很多。old代的空间溢出以及运行了stop-the-world垃圾回收必须被应用负责人重视,由于在响应上会有更长的中断。因此,不要让old代运行得溢出就非常重要了。对于从吞吐量垃圾回收器迁移到CMS的一个比较重要的建议就是提升old代20%到30%的容量。

   在优化CMS的时候有几个注意点,首先,对象从young代转移到old代的转移率。其次,CMS重新分配内存的概率。再次,CMS回收对象时候产生的old代的分隔,这个会在可获得的对象中间产生一些空隙,从而导致了分隔空间。

   碎片可以被下面的几种方法寻址。第一办法是压缩old代,压缩old代空间是通过stop-the-world垃圾回收压缩完成的,就像前面所说的那样,stop-the-world垃圾回收会执行很长时间,会严重影响应用的响应时间,应该避开。第二种办法是,对碎片编址,提高old代的空间,这个办法不能完全解决碎片的问题的,但是可以延迟old代压缩的时间。通常来讲,old代越多内存,由于碎片导致需要执行的压缩的时间久越长。努力把old的空间增大的目标是在应用的生命周期中,避免堆碎片导致stop-the-world压缩垃圾回收,换句话说,应用GC最大内存原则。另外一种处理碎片的办法是减少对象从young代移动到old的概率,就是减少MinorGC,应用MinorGC回收原则。

   任期阀值(tenuring threshold)控制了对象该什么时候从young代移动到old代。任期阀值会在后面详细的介绍,它是HotSpot VM基于young代的占用空间来计算的,尤其是survivor(幸存者)空间的占用量。下面详细介绍一下survivor空间以及讨论任期阀值。

survivor空间

   survivor空间是young代的一部分,如下图所示。young代被分成了一个eden区域和两个survivor空间。
   
   两个survivor空间的中一个被标记为“from”,另外一个标记为“to”。新的Java对象被分配到Eden空间。比如说,下面的一条语句:
   
[java] view plain copy
  1. <span style="font-size:14px;">   Map<String,String> map = new HashMap<String,String>();</span>  

   一个新的HashMap对象会被放到eden空间,当eden空间满了的时候,MinorGC就会执行,任何存活的对象,都从eden空间复制到“to” survivor空间,任何在“from” survivor空间里面的存活对象也会被复制到“to” survivor。MinorGC结束的时候,eden空间和“from” survivor空间都是空的,“to” survivor空间里面存储存活的对象,然后,在下次MinorGC的时候,两个survivor空间交换他们的标签,现在是空的“from” survivor标记成为“to”,“to” survivor标记为“from”。因此,在MinorGC结束的时候,eden空间是空的,两个survivor空间中的一个是空的。

   在MinorGC过程,如果“to” survivor空间不够大,不能够存储所有的从eden空间和from suvivor空间复制过来活动对象,溢出的对象会被复制到old代。溢出迁移到old代,会导致old代的空间快速增长,会导致stop-the-world压缩垃圾回收,所以,这里要使用MinorGC回收原则。

   避免survivor空间溢出可以通过指定survivor空间的大小来实现,以使得survivor有足够的空间来让对象存活足够的岁数。高效的岁数控制会导致只有长时间存活的对象转移到old代空间。

   岁数控制是指一个对象保持在young代里面直到无法获取,所以让old代只是存储长时间保存的对象。

   survivor的空间可以大小设置可以用HotSpot命令行参数:-XX:SurvivorRatio=<ratio>

   <ratio>必须是以一个大于0的值,-XX:SurvivorRatio=<ratio>表示了每一个survivor的空间和eden空间的比值。下面这个公式可以用来计算survivor空间的大小

   
[html] view plain copy
  1. survivor spave size = -Xmn<value>/(-XX:SurvivorRatio=<ratio>+2)  

   这里有一个+2的理由是有两个survivor空间,是一个调节参数。ratio设置的越大,survivor的空间越小。为了说明这个问题,假设young代的大小是-Xmn512m而且-XX:SurvivorRatio=6.那么,young代有两个survivor空间且空间大小是64M,那么eden空间的大小是384M。

   同样假如young代的大小是512M,但是修改-XX:SurvivorRatio=2,这样的配置会使得每一个survivor空间的大小是128m而eden空间的大小是256M。

   对于一个给定大小young代空间大小,减小ratio参数增加survivor空间的大小而且减少eden空间的大小。反之,增加ratio会导致survivor空间减少而且eden空间增大。减少eden空间会导致MinorGC更加频繁,相反,增加eden空间的大小会导致更小的MinorGC,越多的MinorGC,对象的岁数增长得越快。

   为了更好的优化survivor空间的大小和完善young代空间的大小,需要监控任期阀值,任期阀值决定了对象会再young代保存多久。怎么样来监控和优化任期阀值将在下一节中介绍。
   
任期阀值

   “任期”是转移的代名词,换句话说,任期阀值意味着对象移动到old代空间里面。HotSpot VM每次MinorGC的时候都会计算任期,以决定对象是否需要移动到old代去。任期阀值就是对象的岁数。对象的岁数是指他存活过的MinorGC次数。当一个对象被分配的时候,它的岁数是0。在下次MinorGC的时候之后,如果对象还是存活在young代里面,它的岁数就是1。如果再经历过一次MinorGC,它的岁数变成2,依此类推。在young代里面的岁数超过HotSpot VM指定阀值的对象会被移动到old代里面。换句话说,任期阀值决定对象在young代里面保存多久。

   任期阀值的计算依赖于young代里面能够存放的对象数以及MinorGC之后,“to” servivor的空间占用。HotSpot VM有一个选项-XX:MaxTenuringThreshold=<n>,可以用来指定当时对象的岁数超过<n>的时候,HotSpot VM会把对象移动到old代去。内部计算的任期阀值一定不会超过指定的最大任期阀值。最大任期阀值在可以被设定为0-15,不过在Java 5 update 5之前可以设置为1-31。

   不推荐把最大任期阀值设定成0或者超过15,这样会导致GC的低效率。

   如果HotSpot VM它无法保持目标survivor 空间的占用量,它会使用一个小于最大值的任期阀值来维持目标survivor空间的占用量,任何比这个任期阀值的大的对象都会被移动到old代。话句话说,当存活对象的量大于目标survivor空间能够接受的量的时候,溢出发生了,溢出会导致对象快速的移动到old代,导致不期望的FullGC。甚至会导致更频繁的stop-the-world压缩垃圾回收。哪些对象会被移动到old代是根据评估对象的岁数和任期阀值来确定的。因此,很有必要监控任期阀值以避免survivor空间溢出,接下来详细讨论。

监控任期阀值

   为了不被内部计算的任期阀值迷惑,我们可以使用命令选项-XX:MaxTenuringThreshod=<n>来指定最大的任期阀值。为了决定出最大的任期阀值,需要监控任期阀值的分布和对象岁数的分布,通过使用下面的选项实现

 
[html] view plain copy
  1. -XX:+PrintTenuringDistribution  

   -XX:+PrintTenuringDistribution的输出显示在survivor空间里面有效的对象的岁数情况。阅读-XX:+PrintTenuringDistribution输出的方式是观察在每一个岁数上面,对象的存活的数量,以及其增减情况,以及HotSpot VM计算的任期阀值是不是等于或者近似于设定的最大任期阀值。

   -XX:+PrintTenuringDistribution在MinorGC的时候产生任期分布信息。它可以同其他选项一同使用,比如-XX:+PrintGCDateStamps,-XX:+PrintGCTimeStamps以及-XX:+PringGCDetails。当调整survivor空间大小以获得有效的对象岁数分布,你应该使用-XX:+PrintTenuringDistribution。在生产环境中,它同样非常有用,可以用来判断stop-the-world的垃圾回收是否发生。

   下面是一个输出的例子:

   Desired survivor size 8388608 bytes, new threshold 1 (max 15) 
   - age 1: 16690480 bytes, 16690480 total

   在这里例子中,最大任期阀值被设置为15,(通过max 15表示)。内部计算出来的任期阀值是1,通过threshold 1表示。Desired survivor size 8388608 bytes表示一个survivor的空间大小。目标survivor的占有率是指目标survivor和两个survivor空间总和的比值。怎么样指定期望的survivor空间大小在后面会详细介绍。在第一行下面,会列出一个对象的岁数列表。每行会列出每一个岁数的字节数,在这个例子中,岁数是1的对象有16690480字节,而且每行后面有一个总的字节数,如果有多行输出的话,总字节数是前面的每行的累加数。后面举例说明。

   在前面的例子中,由于期望的survivor大小(8388608)比实际总共survivor字节数(16690480)小,也就是说,survivor空间溢出了,这次MinorGC会有一些对象移动到old代。这个就意味着survivor的空间太小了。另外,设定的最大任期阀值是15,但是实际上JVM使用的是1,也表明了survivor的空间太小了。

   如果发现survivor区域太小,就增大survivor的空间,下面详细介绍如何操作。
   
设定survivor空间

   当修改survivor空间的大小的时候,有一点需要记住。当修改survivor空间大小的时候,如果young代的大小不改变,那么eden空间会减小,进一步会导致更频繁的MinorGC。因此,增加survivor空间的时候,如果young代的空间大小违背了MinorGC频率的需求,eden空间的大小同需要需要增加。换句话说,当survivor空间增加的时候,young代的大小需要增加。

   如果有空间来增加MinorGC的频率,有两种选择,一是拿一些eden空间来增加survivor的空间,二是让young的空间更大一些。常规来讲,更好的选择是如果有可以使用的内存,增加young代的空间会比减少eden的空间更好一些。让eden空间大小保持恒定,MinorGC的频率不会改变,即使调整survivor空间的大小。

   使用-XX:+PrintTenuringDistribution选项,对象的总字节数和目标survivor空间占用可以用来计算survivor空间的大小。重复前面的例子:
   Desired survivor size 8388608 bytes, new threshold 1 (max 15) 
   - age 1: 16690480 bytes, 16690480 total

   存活对象的总字节数是1669048,这个并发垃圾回收器(CMS)的目标survivor默认使用50%的survivor空间。通过这个信息,我们可以知道survivor空间至少应该是33380960字节,大概是32M。这个计算让我们知道对survivor空间的预估值需要计算对象的岁数更高效以及防止溢出。为了更好的预估survivor的可用空间,你应该监控应用稳定运行情况下的任期分布,并且使用所有的额外总存活对象的字节数来作为survivor空间的大小。

   在这个例子,为了让应用计算岁数更加有效,survivor空间需要至少提升32M。前面使用的选项是:

 
[html] view plain copy
  1. -Xmx1536m -Xms1536m -Xmn512m -XX:SurvivorRatio=30  

   那么为了保持MinorGC的频率不发生变化,然后增加survivor空间的大小到32M,那么修改后的选项如下:

 
[html] view plain copy
  1. -Xmx1568m -Xms1568m -Xmn544m -XX:SurvivvorRatio=15  

   当时young代空间增加了,eden空间的大小保持大概相同,且survivor的空间大小增减了。需要注意的时候,-Xmx、-Xms、-Xmn都增加了32m。另外,-XX:SurvivvorRatio=15让每一个survivor空间的大小都是32m (544/(15+2) = 32)。

   如果存在不能增加young代空间大小的限制,那么增加survivor空间大小需要以减少eden空间的大小为代价。下面是一个增加survivor空间大小,每一个survivor空间从16m增减加到32m,那么会见减少eden的空间,从480m减少到448m(512-32-32=448,512-16-16=480)。

   
[html] view plain copy
  1. -Xms1536m -Xms1536m -Xmn1512m -XX:SurvivorRatio=14  

   再次强调,减少eden空间大小会增加MinorGC的频率。但是,对象会在young代里面保持更长的时间,由于提升survivor的空间。

   假如运行同样的应用,我们保持eden的空间不变,增加survivor空间的大小,如下面选项:

 
[html] view plain copy
  1. <span style="font-size:14px;"> -Xmx1568m -Xms1568m -Xmn544m -XX:SurvivorRatio=15</span>  

   可以产生如下的任期分布:
   Desired survivor size 16777216 bytes, new threshold 15 (max 15)
- age 1: 6115072 bytes, 6115072 total
- age 2: 286672 bytes, 6401744 total
- age 3: 115704 bytes, 6517448 total
- age 4: 95932 bytes, 6613380 total
- age 5: 89465 bytes, 6702845 total
- age 6: 88322 bytes, 6791167 total
- age 7: 88201 bytes, 6879368 total
- age 8: 88176 bytes, 6967544 total
- age 9: 88176 bytes, 7055720 total
- age 10: 88176 bytes, 7143896 total
- age 11: 88176 bytes, 7232072 total
- age 12: 88176 bytes, 7320248 total

   从任期分布的情况来看,survivor空间没有溢出,由于存活的总大小是7320248,但是预期的survivor空间大小是16777216以及任期阀值和最大任期阀值是相等的。这个表明,对象的老化速度是高效的,而且survivor空间没有溢出。

   在这个例子中,由于岁数超过3的对象很少,你可能像把最大任期阀值设置为3来测试一下,即设置选项-XX:MaxTenuringThreshhold=3,那么整个选项可以设置为:

 
[html] view plain copy
  1. -Xmx1568m -Xms1658m -Xmn544m -XX:SurvivorRatio=15 -XX:MaxTenuringThreshold=3  

   这个选项设置和之前的选项设置的权衡是,后面这个选择可以避免在MinorGC的时候不必要地把对象从“from” survivor复制到“to” survivor。在应用运行在稳定状态的情况下,观察多次MinorGC任期分布情况,看是否有对象最终移动到old代或者显示的结果还是和前面的结果类似。如果你观察得到和前面的任期分布情况相同,基本没有对象的岁数达到15,也没有survivor的空间溢出,你应该自己设置最大任期阀值以代替JVM默认的15。在这个例子中,没有长时间存活的对象,由于在他们的岁数没有到达15的时候就被垃圾回收了。这些对象在MinorGC中被回收了,而不是移动到old代里面。使用并发垃圾回收(CMS)的时候,对象从young代移动到old代最终会导致old的碎片增加,有可能导致stop-the-world压缩垃圾回收,这些都是不希望出现的。宁可选择让对象在“from” survivor和“to” survivor中复制,也不要太快的移动到old代。

   你可能需要重复数次监控任期分布、修改survivor空间大小或者重新配置young代的空间大小直到你对应用由于MinorGC引起的延迟满意为止。如果你发现MinorGC的时间太长,你可以通过减少young代的大小直到你满意为止。尽管,减少young代的大小,会导致更快地移动对象到old代,可能导致更多的碎片,如果CMS的并发垃圾回收能够跟上对象的转移率,这种情况就比不能满足应用的延迟需求更好。如果这步不能满足应用的MinorGC的延迟和频率需求,这个时候就有必要重新审视需求以及修改应用程序了。

   如果满足对MinorGC延迟的需求,包括延迟时间和延迟频率,你可以进入下一步,优化CMS垃圾回收周期的启动,下节详细介绍。
posted @ 2016-10-18 20:24 小马歌 阅读(104) | 评论 (0)编辑 收藏
 
     摘要: from:http://blog.csdn.net/fenglibing/article/details/6321453这是我公司同事的GC学习笔记,写得蛮详细的,由浅入深,循序渐进,让人一看就懂,特转到这里。一、GC特性以及各种GC的选择1、垃圾回收器的特性2、对垃圾回收器的选择2.1 连续 VS. 并行2.2 并发 VS. stop-...  阅读全文
posted @ 2016-10-18 15:37 小马歌 阅读(101) | 评论 (0)编辑 收藏
 
     摘要: from:http://www.tuicool.com/articles/RNjUfa原文  http://286.iteye.com/blog/1924947主题 JVM        -XX 参数被称为不稳定参数,之所以这么叫是因为此类参数的设置很容易引起JVM 性能上的差异,使JVM 存在极大...  阅读全文
posted @ 2016-10-18 11:18 小马歌 阅读(107) | 评论 (0)编辑 收藏

2016年10月14日

     摘要: from:http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html不管是YGC还是Full GC,GC过程中都会对导致程序运行中中断,正确的选择不同的GC策略,调整JVM、GC的参数,可以极大的减少由于GC工作,而导致的程序运行中断方面的问题,进而适当的提高Java程序的工作效率。但是调整GC是以个极为复杂的过程,由于各个程序具...  阅读全文
posted @ 2016-10-14 15:06 小马歌 阅读(106) | 评论 (0)编辑 收藏

2016年9月8日

from:http://www.oschina.net/news/74775/open-source-database-situation

数据库作为业务的核心,是整个基础软件栈非常重要的一环。近几年的开源社区,新的思想和方案层出不穷,我将总结一下近几年一些主流的开源数据库方案,及其背后的设计思想以及适用场景。本人才疏学浅如有遗漏或者错误请见谅。本次分享聚焦于数据库即结构化数据存储 OLTP 及 NoSQL 领域,不会涉及 OLAP、对象存储以及分布式文件系统。

开源 RDBMS 与互联网的崛起

很长时间以来,关系型数据库一直是大公司的专利,市场被 Oracle / DB2 等企业数据库牢牢把持。但是随着互联网的崛起和开源社区的发展,上世纪九十年代 MySQL 1.0 的发布,标志着在关系型数据库的领域,社区终于有了可选择的方案。

MySQL

第一个介绍的单机 RDBMS 就是 MySQL。相信大多数朋友都已经对 MySQL 非常熟悉,基本上 MySQL 的成长史就是互联网的成长史。我接触的第一个 MySQL 版本是 MySQL 4.0,后来的 MySQL 5.5 更是经典——基本上所有的互联网公司都在使用。MySQL 也普及了「可插拔」引擎这一概念,即针对不同的业务场景选用不同的存储引擎,这也是 MySQL tuning 的一个重要方式。比如对于有事务需求的场景使用 InnoDB;对于并发读取的场景 MyISAM 可能比较合适;但是现在我推荐绝大多数情况还是使用 InnoDB,毕竟 MySQL 5.6 后它已经成为了官方的默认引擎。MySQL适用于几乎所有需要持久化结构化数据的场景, 大多数朋友应该都知道,我就不赘述了。

另外值得一提的是 MySQL 5.6 中引入了多线程复制和 GTID,使得故障恢复和主从的运维变得比较方便。另外,MySQL 5.7(目前处于 GA 版本) 发布了一个重大更新,主要是在读写性能和复制性能上有了长足的进步:在5.6版本中实现了 SCHEMA 级别的并行复制。不过意义不大,倒是 MariaDB 的多线程并行复制大放异彩,有不少人因为这个特性选择 MariaDB。另外,MySQL 5.7 MTS 支持两种模式,一种是和5.6一样,另一种则是基于 binlog group commit 实现的多线程复制,也就是MASTER上同时提交的 binlog 在 SLAVE 端也可以同时被 apply,实现并行复制。如果有单机数据库技术选型的朋友,基本上只需要考虑 MySQL 5.7 或者 MariaDB 就好了,而且 MySQL 5.6和5.7 由 Oracle 接手后,性能和稳定性都有了明显的提升。

PostgreSQL

PostgreSQL 的历史也非常悠久,其前身是 UCB 的 Ingres,主持这个项目的 Michael Stronebraker 于 2015 年获得图灵奖。后来该项目更名为 Post-Ingres,基于 BSD license 下开源。 1995 年几个 UCB 的学生为 Post-Ingres 开发了 SQL 的接口,正式发布了 PostgreSQL95,随后一步步在开源社区中成长起来。和 MySQL 一样,PostgreSQL 也是一个单机的关系型数据库,但是与 MySQL 方便用户过度扩展的 SQL 文法不一样的是,PostgreSQL 的 SQL 支持非常强大,不管是内置类型、JSON 支持、GIS 类型以及对于复杂查询的支持,PL/SQL 等都比 MySQL 强大得多,而且从代码质量上来看,PostgreSQL 的代码质量是优于 MySQL 的。另外,相对于MySQL 5.7以前的版本, PostgreSQL 的 SQL 优化器比 MySQL 强大很多,几乎所有稍微复杂的查询PostgreSQL 的表现都优于 MySQL。

从近几年的趋势上来看,PostgreSQL 的势头也很强劲,我认为 PostgreSQL 的不足之处在于没有 MySQL 那样强大的社区和群众基础。MySQL 经过那么多年的发展,积累了很多的运维工具和最佳实践,但是 PostgreSQL 作为后起之秀,拥有更优秀的设计和更丰富的功能。PostgreSQL 9 以后的版本也足够稳定,在做新项目技术选型的时候,是一个很好的选择。另外也有很多新的数据库项目是基于 PostgreSQL 源码的基础上进行二次开发,比如 Greenplum 等。

我认为,单机数据库的时代很快就会过去。摩尔定律带来的硬件红利总是有上限的,现代业务的数据规模、流量以及现代的数据科学对于数据库的要求,单机已经很难满足。比如,网卡磁盘 IO 和 CPU 总有瓶颈,线上敏感的业务系统可能还得承担 SPOF(单点故障) 的风险,主从复制模型在主挂掉时到底切还是不切?切了以后数据如何恢复?如果只是出现主从机器网络分区问题呢?甚至是监控环境出现网络分区问题呢?这些都是单机数据库面临的巨大挑战。所以我的观点是,无论单机性能多棒(很多令人乍舌的评测数据都是针对特定场景的优化,另外甚至有些都是本机不走网络,而大多数情况数据库出现的第一个瓶颈其实是网卡和并发连接……),随着互联网的蓬勃发展和移动互联网的出现,数据库系统迎来了第一次分布式的洗礼。

分布式时代:NoSQL 的复兴和模型简化的力量

在介绍 NoSQL 之前,我想提两个公司,一个是 Google,另一个是 Amazon。

Google

Google 应该是第一个将分布式存储技术应用到大规模生产环境的公司,同时也是在分布式系统上积累最深的公司,可以说目前工业界的分布式系统的工程实践及思想大都来源于 Google。比如 2003 年的 GFS 开创了分布式文件系统,2006 年的 Bigtable 论文开创了分布式键值系统,直接催生的就是 Hadoop 的生态;至于 2012 年发表论文的 Spanner 和 F1更是一个指明未来关系型数据库发展方向的里程碑式的项目,这个我们后续会说。

Amazon

另一个公司是 Amazon。2007 年发表的 Dynamo的论文 尝试引入了最终一致性的概念, WRN 的模型及向量时钟的应用,同时将一致性 HASH、merkle tree 等当时一些很新潮的技术整合起来,正式标志着 NoSQL 的诞生。NoSQL——对后来业界的影响非常也是很大,包括后来的 Cassandra、RiakDB、Voldemort 等数据库都是基于 Dynamo 的设计发展起来的。

新思潮

另外这个时期(2006 年前后持续至今)一个比较重要的思潮就是数据库(持久化)和缓存开始有明确的分离——我觉得这个趋势是从 memcached 开始的。随着业务的并发越来越高,对于低延迟的要求也越来越高;另外一个原因是随着内存越来越便宜,基于内存的存储方案渐渐开始普及。当然内存缓存方案也经历了一个从单机到分布式的过程,但是这个过程相比关系型数据库的进化要快得多。这是因为 NoSQL 的另外一个重要的标志——数据模型的变化——大多 NoSQL 都抛弃了关系模型,选择更简单的键值或者文档类型进行存储。数据结构和查询接口都相对简单,没有了 SQL 的包袱,实现的难度会降低很多。另外 NoSQL 的设计几乎都选择牺牲掉复杂 SQL 的支持及 ACID 事务换取弹性扩展能力,也是从当时互联网的实际情况出发:业务模型简单、爆发性增长带来的海量并发及数据总量爆炸、历史包袱小、工程师强悍,等等。其中最重要的还是业务模型相对简单。

嵌入式存储引擎

在开始介绍具体的开源的完整方案前,我想介绍一下嵌入式存储引擎们。

随着 NoSQL 的发展,不仅仅缓存和持久化存储开始细分,再往后的存储引擎也开始分化并走上前台。之前很难想象一个存储引擎独立于数据库直接对外提供服务,就像你不会直接拿着 InnoDB 或者 MyISAM甚至一个 B-tree 出来用一样(当然,bdb 这样鼎鼎大名的除外)。人们基于这些开源的存储引擎进行进一步的封装,比如加上网络协议层、加上复制机制等等,一步步构建出完整的风格各异的 NoSQL 产品。

这里我挑选几个比较著名的存储引擎介绍一下。

TC

我最早接触的是 Tokyo Cabinet(TC)。TC 相信很多人也都听说过,TC 是由日本最大的社交网站 Mixi 开发并开源的一个混合 Key-Value 存储引擎,其中包括 HASH Table 和 B+ Tree 的实现。但是这个引擎的一个缺陷是随着数据量的膨胀,性能的下降会非常明显,而且现在也基本不怎么维护了,所以入坑请慎重。与于 TC 配合使用的 Tokyo Tyrant(TT) 是一个网络库,为 TC 提供网络的接口使其变成一个数据库服务,TT + TC 应该是比较早的 NoSQL 的一个尝试。

LevelDB

在 2011 年,Google 开源了 Bigtable 的底层存储引擎: LevelDB。LevelDB 是一个使用 C++ 开发的嵌入式的 Key-Value 存储引擎,数据结构采用了 LSM-Tree,具体 LSM-Tree 的算法分析可以很容易在网上搜索到,我就不赘述了。其特点是,对于写入极其友好,LSM 的设计避免了大量的随机写入;对于特定的读也能达到不错的性能(热数据在内存中);另外 LSM-Tree 和 B-tree 一样是支持有序 Scan 的;而且 LevelDB 是出自 Jeff Dean 之手,他的事迹做分布式系统的朋友一定都知道,不知道的可以去 Google 搜一下。

LevelDB 拥有极好的写性能,线程安全,BatcTCh Write 和 Snapshot 等特性,使其很容易的在上层构建 MVCC 系统或者事务模型,这对于数据库来说非常重要。另外值得一说的是,Facebook 维护了一个活跃的 LevelDB 的分支,名为 RocksDB。RocksDB 在 LevelDB 上做了很多的改进,比如多线程 Compactor、分层自定义压缩、多 MemTable 等。另外 RocksDB 对外暴露了很多 ConfigurationConfigration ,可以根据不同业务的形态进行调优;同时 Facebook 在内部正在用 RocksDB 来实现一个全新的 MySQL 存储引擎:MyRocks,值得关注。RocksDB 的社区响应速度很快也很友好,实际上 PingCAP 也是 RocksDB 的社区贡献者。我建议新的项目如果在 LevelDB 和 RocksDB 之间纠结的话,请果断选择 RocksDB。

B-tree 家族

当然,除了 LSM-Tree 外,B-tree 的家族也还是有很多不错的引擎。首先大多数传统的单机数据库的存储引擎都选择了B+Tree,B+Tree 对磁盘的读比较友好,第三方存储引擎比较著名的纯 B+Tree 实现是 LMDB。首先 LMDB 选择在内存映像文件 (mmap) 实现 B+Tree,而且同时使用了 Copy-On-Write 实现了 MVCC 实现并发事务无锁读的能力,对于高并发读的场景比较友好;同时因为使用的是 mmap 所以拥有跨进程读取的能力。不过因为我并没有在生产环境中使用过 LMDB ,所以并不能给出 LMDB 的一些缺陷,见谅。

混合引擎

还有一部分的存储引擎选择了多种引擎混合,比如最著名的应该是 WiredTiger,大概是2014年去年被 MongoDB 收购,现在成为了 MongoDB 的默认存储引擎。WiredTiger 内部有 LSM-Tree 和 B-tree 两种实现,对外提供相同的一套接口,根据业务的情况可自由选择。另外一些特殊数据结构的存储引擎在某些特殊场合下非常抢眼,比如极高压缩比 TokuDB,采用了名为分形树的数据结构,在维持一个可接受的读写压力的情况下,能拥有 10 倍以上的压缩率。

NoSQL

说完了几个比较著名的存储引擎,我们来讲讲比较著名的 NoSQL。在我的定义中,NoSQL 是 Not Only SQL 的缩写,所以可能包含的范围有内存数据库,持久化数据库等。总之就是和单机的关系型数据库不一样的结构化数据存储系统。

我们先从缓存开始。

memcached

前面提到了 memcached 应该是第一个大规模在业界使用的缓存数据库,memcached 的实现极其简单,相当于将内存用作大的 HASH Table,只能在上面进行 get/set/ 计数器等操作,在此之上用 libevent 封装了一层网络层和文本协议(也有简单的二进制协议),虽然支持一些 CAS 的操作,但是总体上来看,还是非常简单的。但是 memcached 的内存利用率并不太高,这是这个因为 memcached 为了避免频繁申请内存导致的内存碎片的问题,采用了自己实现的 slab allocator 的方式。即内存的分配都是一块一块的,最终存储在固定长度的 chunk 上,内存最小的分配单元是 chunk,另外 libevent 的性能也并没有优化到极致。但是这些缺点并不妨碍 memcached 成为当时的开源缓存事实标准。(另外,八卦一下,memcached 的作者 Brad Fitzpatrick 现在在 Google,大家如果用 Golang 的话,Go 的官方 HTTP 包就是这哥们写的,是个很高产的工程师)。

Redis

如果我没记错的话,在 2009 年前后,一位意大利的工程师 Antirez ,开源了 Redis。从此彻底颠覆了缓存的市场,到现在大多数缓存的业务都已用上 Redis,memcached 基本退出了历史舞台。Redis 最大的特点是拥有丰富的数据结构支持,不仅仅是简单的 Key-Value,还包括队列、集合、Sorted Set 等等,提供了非常丰富的表达力,而且 Redis 还提供 sub/pub 等超出数据库范畴的便捷功能,使得几乎一夜之间大家纷纷投入 Redis 的怀抱。

Twemproxy

但是随着 Redis 渐渐的普及,而且越用越狠,另外内存也越来越便宜,人们开始寻求扩展单机 Redis 的方案,最早的尝试是 twitter 开源的 twemproxy。twemproxy 是一个 Redis 中间件,基本只有最简单的数据路由功能,并没有动态的伸缩能力,但是还是受到了很多公司的追捧,因为确实没其他替代方案。随后的 Redis Cluster 也是难产了好久,时隔好几年,中间出了 7 个RC 版本,最后才发布;2014 年底,我们开源了 Codis,解决了 Redis 中间件的数据弹性伸缩问题,目前广泛应用于国内各大互联网公司中,这个在网上也有很多文章介绍,我也就不展开了。所以在缓存上面,开源社区现在倒是非常统一,就是 Redis 及其极其周边的扩展方案。

MongoDB

在 NoSQL 的大家庭中,MongoDB 其实是一个异类,大多 NoSQL 舍弃掉 SQL 是为了追求更极致的性能和可扩展能力,而 MongoDB 主动选择了文档作为对外的接口,非常像 JSON 的格式。Schema-less 的特性对于很多轻量级业务和快速变更的了互联网业务意义很大,而且 MongoDB 的易用性很好,基本做到了开箱即用,开发者不需要费心研究数据的表结构,只需要往里存就好了,这确实笼络了一大批开发者。

尽管 MongoDB 早期的版本各种不稳定,性能也不太好(早期的 Mongo 并没有存储引擎,直接使用了 mmap 文件),集群模式还全是问题(比如至今还未解决的 Cluster 同步带宽占用过多的问题),但是因为确实太方便了,在早期的项目快速迭代中,Mongo 是一个不错的选择。但是这也正是它的问题,我不止一次听到当项目变得庞大或者「严肃」的时候,团队最后还是回归了关系型数据库。Anyway,在 2014 年底 MongoDB 收购了 WiredTiger 后,在 2.8 版本中正式亮相,同时 3.0 版本后更是作为默认存储引擎提供,性能和稳定性有了非常大的提升。

但是,从另一方面讲,Schema-less 到底对软件工程是好事还是坏事这个问题还是有待商榷。我个人是站在 Schema 这边的,不过在一些小项目或者需要快速开发的项目中使用 Mongo 确实能提升很多的开发效率,这是毋庸置疑的。

HBase

说到 NoSQL 不得不提的是 HBase,HBase 作为 Hadoop 旗下的重要产品,Google Bigtable 的正统开源实现,是不是有一种钦定的感觉 :)。提到 HBase 就不得不提一下 Bigtable, Bigtable 是 Google 内部广泛使用的分布式数据库,接口也不是简单的Key-Value,按照论文的说法叫:multi-dimensional sorted map,也就是 Value 是按照列划分的。Bigtable 构建在 GFS 之上,弥补了分布式文件系统对于海量、小的、结构化数据的插入、更新以及、随机读请求的缺陷。

HBase 就是这么一个系统的实现,底层依赖 HDFS。HBase 本身并不实际存储数据,持久化的日志和 SST file (HBase 也是 LSM-Tree 的结构) 直接存储在 HDFS 上,Region Server (RS) 维护了 MemTable 以提供快速的查询,写入都是写日志,后台进行 Compact,避免了直接随机读写 HDFS。数据通过 Region 在逻辑上进行分割,负载均衡通过调节各个 Region Server 负责的 Region 区间实现。当某 Region 太大时,这个 Region 会分裂,后续可能由不同的 RS 负责,但是前面提到了,HBase 本身并不存储数据,这里的 Region 仅是逻辑上的,数据还是以文件的形式存储在 HDFS 上,所以 HBase 并不关心 Replication 、水平扩展和数据的分布,统统交给 HDFS 解决。

和 Bigtable 一样,HBase 提供行级的一致性,严格来说在 CAP 理论中它是一个 CP 的系统,但遗憾的是并没有更进一步提供 ACID 的跨行事务。HBase 的好处就不用说了,显而易见,通过扩展 RS 可以几乎线性提升系统的吞吐,及 HDFS 本身就具有的水平扩展能力。

但是缺点仍然是有的。首先,Hadoop 的软件栈是 Java,JVM 的 GC Tuning 是一个非常烦人的事情,即使已经调得很好了,平均延迟也得几十毫秒;另外在架构设计上,HBase 本身并不存储数据,所以可能造成客户端请求的 RS 并不知道数据到底存在哪台 HDFS DataNode 上,凭空多了一次 RPC;第三,HBase 和 Bigtable 一样,并不支持跨行事务,在 Google 内部不停的有团队基于 Bigtable 来做分布式事务的支持,比如 MegaStore、Percolator。后来 Jeff Dean 有次接受采访也提到非常后悔没有在 Bigtable 中加入跨行事务,不过还好这个遗憾在 Spanner 中得到了弥补,这个一会儿说。总体来说,HBase 还是一个非常健壮且久经考验的系统,但是需要你有对于 Java 和 Hadoop 比较深入的了解后,才能玩转,这也是 Hadoop 生态的一个问题,易用性真是不是太好,而且社区演进速度相对缓慢,也是因为历史包袱过重的缘故吧。

Cassandra

提到 Cassandra (C*),虽然也是 Dynamo 的开源实现,但就没有这种钦定的感觉了。C* 确实命途多舛,最早 2008 由 Facebook 开发并开源,早期的 C* 几乎全是 bug,Facebook 后来索性也不再维护转过头搞 HBase 去了,一个烂摊子直接丢给社区。还好 DataStax 把这个项目捡起来商业化,搞了两年,终于渐渐开始流行起来。

C* 不能简单的归纳为读快写慢,或者读慢写快,因为采用了 qourm 的模型,调整复制的副本数以及读的数量,可以达到不同的效果,对于一致性不是特别高的场景,可以选择只从一个节点读取数据,达到最高的读性能。另外 C* 并不依赖分布式文件系统,数据直接存储在磁盘上,各个存储节点之间自己维护复制关系,减少了一层 RPC 调用,延迟上对比 HBase 还是有一定优势的。

不过即使使用 qourm 的模型也并不代表 C* 是一个强一致的系统。C* 并不帮你解决冲突,即使你 W(写的副本数) + R(读请求的副本数) > N(节点总数),C* 也没办法帮你决定哪些副本拥有更新的版本,因为每个数据的版本是一个 NTP 的时间戳或者客户端自行提供,每台机器可能都有误差,所以有可能并不准确,这也就是为什么 C* 是一个 AP 的系统。不过 C* 一个比较友好的地方是提供了 CQL,一个简单的 SQL 方言,比起 HBase 在易用性上有明显优势。

即使作为一个 AP 系统,C* 已经挺快了,但是人们追求更高性能的脚步还是不会停止。应该是今年年初,ScyllaDB 的发布就是典型的证明,ScyllaDB 是一个兼容 C* 的 NoSQL 数据库,不一样的是,ScyllaDB 完全用 C++ 开发,同时使用了类似 DPDK 这样的黑科技,具体我就不展开了,有兴趣可以到 Scylla 的官网去看看。BTW, 国内的蘑菇街第一时间使用了 ScyllaDB,同时在 Scylla 的官网上 share 了他们的方案,性能还是很不错的。

中间件与分库分表

NoSQL 就先介绍到这里,接下来我想说的是一些在基于单机关系型数据库之上的中间件和分库分表方案。

这些技术确实历史悠久,而且也是没有办法的选择。关系型数据库不比 Redis,并不是简单的写一个类似 Twemproxy 的中间件就搞定了。数据库的中间件需要考虑很多,比如解析 SQL,解析出 sharding key,然后根据 sharding key 分发请求,再合并;另外数据库有事务,在中间件这层还需要维护 Session 及事务状态,而且大多数方案并没有办法支持跨 shard 的事务。这就不可避免的导致了业务使用起来会比较麻烦,需要重写代码,而且会增加逻辑的复杂度,更别提动态的扩容缩容和自动的故障恢复了。在集群规模越来越大的情况下,运维和 DDL 的复杂度是指数级上升的。

中间件项目盘点

数据库中间件最早的项目大概是 MySQL Proxy,用于实现读写分离。后来国人在这个领域有过很多著名项目,比如阿里的 Cobar 和 TDDL(并未完全开源);后来社区基于 Cobar 改进的 MyCAT、360 开源的 Atlas 等,都属于这一类中间件产品;在中间件这个方案上基本走到头的开源项目应该是 Youtube 的 Vitess。Vitess 基本上是一个集大成的中间件产品,内置了热数据缓存、水平动态分片、读写分离等等,但是代价也是整个项目非常复杂,另外文档也不太好。大概1年多以前,我们尝试搭建起完整的 Vitess 集群,但是并未成功,可见其复杂度。

另外一个值得一提的是 Postgres-XC 这个项目,Postgres-XC 的野心还是很大的,整体的架构有点像早期版本的 OceanBase,由一个中央节点来处理协调分布式事务 / 解决冲突,数据分散在各个存储节点上,应该是目前 PostgreSQL 社区最好的分布式扩展方案。其他的就不提了。

未来在哪里?NewSQL!

一句话,NewSQL 就是未来。

2012 年 Google 在 OSDI 上发表了 Spanner 的论文,2013 年在 SIGMOD 发表了 F1 的论文。这两篇论文让业界第一次看到了关系模型和 NoSQL 的扩展性在超庞大集群规模上融合的可能性。在此之前,大家普遍认为这个是不可能的,即使是 Google 也经历了 Megastore 这样的失败。

Spanner综述

但是 Spanner 的创新之处在于通过硬件(GPS时钟+原子钟)来解决时钟同步的问题。在分布式系统里,时钟是最让人头痛的问题,刚才提到了 C* 为什么不是一个强 C 的系统,正是因为时钟的问题。而 Spanner 的厉害之处在于即使两个数据中心隔得非常远,不需要有通信(因为通信的代价太大,最快也就是光速)就能保证 TrueTime API的时钟误差在一个很小的范围内(10ms)。另外 Spanner 沿用了很多 Bigtable 的设计,比如 Tablet / Directory 等,同时在 Replica 这层使用 Paxos 复制,并未完全依赖底层的分布式文件系统。但是 Spanner 的设计底层仍然沿用了 Colossus,不过论文里也说是可以未来改进的点。

Google 的内部的数据库存储业务,大多是 3~5 副本,重要一点的 7 副本,遍布全球各大洲的数据中心,由于普遍使用了 Paxos,延迟是可以缩短到一个可以接受的范围(Google 的风格一向是追求吞吐的水平扩展而不是低延迟,从悲观锁的选择也能看得出来,因为跨数据中心复制是必选的,延迟不可能低,对于低延迟的场景,业务层自己解决或者依赖缓存)。另外由 Paxos 带来的 Auto-Failover 能力,更是能让整个集群即使数据中心瘫痪,业务层都是透明无感知的。另外 F1 构建在 Spanner 之上,对外提供了更丰富的 SQL 语法支持,F1 更像一个分布式 MPP SQL——F1 本身并不存储数据,而是将客户端的 SQL 翻译成类似 MapReduce 的任务,调用 Spanner 来完成请求。

其实 Spanner 和 F1 除了 TrueTime 整个系统并没有用什么全新的算法,其意义在于这是近些年来第一个 NewSQL 在生产环境中提供服务的分布式系统技术。

Spanner 和 F1 有以下几个重点:

1. 完整的 SQL 支持,ACID 事务;2. 弹性伸缩能力;

3. 自动的故障转移和故障恢复,多机房异地灾备。

NewSQL 特性确实非常诱人,在 Google 内部,大量的业务已经从原来的 Bigtable 切换到 Spanner 之上。我相信未来几年,整个业界的趋势也是如此,就像当年的 Hadoop 一样,Google 的基础软件的技术趋势是走在社区前面的。

社区反应

Spanner 的论文发表之后,当然也有社区的追随者开始实现(比如我们 :D ),第一个团队是在纽约的 CockroachDB。CockroachDB 的团队的组成还是非常豪华的,早期团队由是 Google 的分布式文件系统 Colossus 团队的成员组成;技术上来说,Cockroach 的设计和 Spanner 很像,不一样的地方是没有选择 TrueTime而是 HLC (Hybrid logical clock),也就是 NTP +逻辑时钟来代替 TrueTime 时间戳;另外 Cockroach 选用了 Raft 代替 Paxos 实现复制和自动容灾,底层存储依赖 RocksDB 实现,整个项目使用 Go 语言开发,对外接口选用 PostgreSQL 的 SQL 子集。

TiDB

目前从全球范围来看,另一个朝着 Spanner / F1 的开源实现这个目标上走的产品是 TiDB(终于谈到我们的产品了)。TiDB 本质上是一个更加正统的 Spanner 和 F1 实现,并不像 CockroachDB 那样选择将 SQL 和 Key-Value 融合,而是像 Spanner 和 F1 一样选择分离,这样分层的思想也是贯穿整个 TiDB 项目始终的。对于测试、滚动升级以及各层的复杂度控制会比较有优势;另外 TiDB 选择了 MySQL 协议和语法的兼容,MySQL 社区的 ORM 框架和运维工具,直接可以应用在 TiDB 上。

和 F1 一样,TiDB 是一个无状态的 MPP SQL Layer,整个系统的底层是依赖 TiKV 来提供分布式存储和分布式事务的支持。TiKV 的分布式事务模型采用的是 Google Percolator 的模型,但是在此之上做了很多优化。Percolator 的优点是去中心化程度非常高,整个集群不需要一个独立的事务管理模块,事务提交状态这些信息其实是均匀分散在系统的各个 Key 的 meta 中,整个模型唯一依赖的是一个授时服务器。在我们的系统上,极限情况这个授时服务器每秒能分配 400w 以上个单调递增的时间戳,大多数情况基本够用了(毕竟有 Google 量级的场景并不多见);同时在 TiKV中,这个授时服务本身是高可用的,也不存在单点故障的问题。

TiKV 和 CockroachDB 一样也是选择了 Raft 作为整个数据库的基础;不一样的是,TiKV 整体采用 Rust 语言开发,作为一个没有 GC 和 Runtime 的语言,在性能上可以挖掘的潜力会更大。

关于未来

我觉得未来的数据库会有几个趋势,也是 TiDB 项目追求的目标:

●数据库会随着业务云化,未来一切的业务都会跑在云端,不管是私有云、公有云还是混合云,运维团队接触的可能再也不是真实的物理机,而是一个个隔离的容器或者「计算资源」。这对数据库也是一个挑战,因为数据库天生就是有状态的,数据总是要存储在物理的磁盘上,而移动数据的代价比移动容器的代价可能大很多。

●多租户技术会成为标配,一个大数据库承载一切的业务,数据在底层打通,上层通过权限,容器等技术进行隔离;但是数据的打通和扩展会变得异常简单,结合第一点提到的云化,业务层可以再也不用关心物理机的容量和拓扑,只需要认为底层是一个无穷大的数据库平台即可,不用再担心单机容量和负载均衡等问题。

●OLAP 和 OLTP 会进一步细分,底层存储也许会共享一套,但是SQL优化器这层的实现一定是千差万别的。对于用户而言,如果能使用同一套标准的语法和规则来进行数据的读写和分析,会有更好的体验。

●在未来分布式数据库系统上,主从日志同步这样落后的备份方式会被 Multi-Paxos / Raft 这样更强的分布式一致性算法替代,人工的数据库运维在管理大规模数据库集群时是不可能的,所有的故障恢复和高可用都会是高度自动化的。


Q&A

问:HANA等内存数据库怎么保证系统掉电而处理结果不丢?传统数据库也用缓存,可是HANA用的内存太大。

黄东旭:没用过 HANA,但是直观感觉这类内存数据库的可用性可能通过集中方式保证:●写入会先写 WAL;

> - 写入可能会通过主从或者paxos 之类的算法做同步和冗余复制还有 HANA 本身就是内存数据库,会尽可能把数据放到内存里,这样查询才能快呀。 

问:对于传统创业公司如何弥补NoSQL的技术短板?快速的引入NoSQL提高效率?

黄东旭:选用 NoSQL 主要注意两点:1.做好业务的调研,估计并发量,数据量,数据的结构看看适不适合;2.对各种 NoSQL 擅长和不擅长的地方都尽可能了解。

不要盲目相信关系型数据库,也不要盲目相信 NoSQL,没有银弹的。

问:有多个条件  比如年龄20到30或年龄35到40 并且加入购物车或下单  这种数据怎么存储?

黄东旭:购物车这种场景是典型的 OLTP 的场景,可以选用关系型数据库 MySQL PostgreSQL 什么的,如果对于扩展性的数据跨机房有要求的话,可以调研一下 NewSQL,比如我们的 TiDB。

问:多纬度查询应该选择哪种数据库?

黄东旭:多纬度查询可以说是一个 OLAP 的场景,可以选用 Greenplum 或者 Vertica 之类的分析性数据库。

问:想知道为什么需要这些开源的数据库,既然已经有了MySQL、DB2、Oracle这些成熟的数据库,成本考虑,还是传统数据库满足不了需求?

黄东旭:对,传统数据库的扩展性是有问题的,在海量并发和数据量的场景下很难支持业务。所以可以看到比较大的互联网公司基本都有自己的分布式数据库方案。

问:未来可能不再需要数据仓库吗?

黄东旭:大家可以想想数据仓库的定义,如果是还需要离线的从线上库倒腾数据到数据仓库上,这样很难做到实时查询,而且空间的利用率也低,我认为是目前并没有太好的方案的情况下的折衷……如果有一个更好的数据库能解决数据仓库的场景,为什么还需要一个独立的数据仓库?


关于作者

黄东旭,PingCAP 联合创始人兼 CTO。PingCAP 是一家专注于研发下一代的开源的分布式数据库的公司,主要作品是 TiDB / TiKV,是 Google Spanner 及 F1 的开源实现。

相关链接

    想通过手机客户端(支持 Android、iPhone 和 Windows Phone)访问开源中国:请点这里

    posted @ 2016-09-08 15:05 小马歌 阅读(88) | 评论 (0)编辑 收藏
     
    from:https://linux.cn/article-2871-1.html

    如果你想在命令行界面监控网络吞吐量,nload 应用程序是个不错的选择。它是一个实时监控网络流量和带宽使用的控制台应用程序,使用两个图表可视化地展示接收和发送的流量,并提供诸如数据交换总量、最小/最大网络带宽使用量等附加信息。

    安装

    在 CentOS/RHEL/Red Hat/Fedora Linux 上安装 nload

    首先在 CentOS 或者基于 RHEL 的操作系统上启用 EPEL 仓库,然后键入 yum 命令安装 nload:

    1. # yum install nload

    在 Debian 或者 Ubuntu Linux 上安装 nload

    键入 apt-get 命令

    1. $ sudo apt-get install nload

    在 FreeBSD 操作系统上安装 nload

    通过 port 安装 nload,键入:

    1. # cd /usr/ports/net/nload/ &amp;&amp; make install clean

    或者添加包

    1. # pkg install net/nload

    在 OpenBSD 操作系统上安装 nload

    键入下列命令:

    1. $ sudo pkg_add -i nload

    在类 Unix 操作系统上从源代码安装 nload

    首先,使用 wget 或者 curl 命令获取源代码:

    1. $ cd /tmp
    2. $ wget http://www.roland-riegel.de/nload/nload-0.7.4.tar.gz

    使用 tar 命令解压缩名为 nload-0.7.4.tar.gz 的 tar 包,键入:

    1. $ tar xvf nload-0.7.4.tar.gz

    使用 cd 命令进入 nload 源代码所在目录:

    1. $ cd nload*

    然后键入 ./configure 为你的操作系统配置安装包:

    1. $ sh ./configure

    或者

    1. $ ./configure

    运行 configure 命令需要一点时间。完成后,使用 make 命令编译 nload:

    1. $ make

    最后,键入 make install 命令以 root 用户身份安装 nload 应用程序和相关文件:

    1. $ sudo make install

    或者

    1. # make install

    使用

    如何使用 nload 显示当前网络使用量呢?

    基本语法是:

    1. nload
    2. nload device
    3. nload [options] device1 device2

    键入下列命令:

    1. $ nload
    2. $ nload eth0
    3. $ nload em0 em2

    会得到输出:

    图01: 使用 nload 命令

    图01: 使用 nload 命令

    操控 nload 应用程序

    nload 命令一旦执行就会开始监控网络设备,你可以使用下列快捷键操控 nload 应用程序。

    1. 你可以按键盘上的 ← → 或者 Enter/Tab 键在设备间切换。
    2. 按 F2 显示选项窗口。
    3. 按 F5 将当前设置保存到用户配置文件。
    4. 按 F6 从配置文件重新加载设置。
    5. 按 q 或者 Ctrl+C 退出 nload。

    设置显示刷新间隔

    默认每 100 毫秒刷新一次显示数值,下面的例子将时间间隔设置成 500 毫秒:

    1. $ nload -t {interval_number_in_millisec}
    2. $ nload -t 500

    输出:

    Animated gif 01 - nload command in action

    Animated gif 01 - nload command in action

    GIF 动画 01 - 使用 nload 命令

    设置流量数值显示的单位

    语法如下:

    1. $ nload -u h|H|b|B|k|K|m|M|g|G
    2. $ nload -U h|H|b|B|k|K|m|M|g|G
    3. $ nload -u h
    4. $ nload -u G
    5. $ nload -U G

    释义:

    • 小写选项 -u: h 意为自动格式化为人类易读的单位,b 意为 Bit/s,k 意为 kBit/s,m 意为 MBit/s,g 意为 GBit/s。大写字母意为使用 Byte 替代 Bit。默认为 k。
    • 大写选项 -U 与小写选项 -u 非常相似,不同之处在于它展示的是数据量,比如 Bit, kByte, GBit 等等。(没有 "/s")。默认值是 M。

    结论

    我觉得 nload 是一个稳定可靠的应用程序,如果你喜欢 nload,你可能也想试试 Linux 和其他类 Unix 操作系统环境下的 vnstat 与 iftop 工具。


    译自: http://www.cyberciti.biz/networking/nload-linux-command-to-monitor-network-traffic-bandwidth-usage/

    posted @ 2016-09-08 15:04 小马歌 阅读(112) | 评论 (0)编辑 收藏
    仅列出标题  下一页