如何向部署好的kubernetes 1.4 部署上添加node

高质量Node.js微服务的编写和部署
(window.slotbydup=window.slotbydup || []).push({
id: '2611110',
container: s,
size: '240,200',
display: 'inlay-fix'
您当前位置: &
[ 所属分类
| 时间 2016 |
作者 红领巾 ]
为促进Docker、Kubernetes等技术的交流传播,同时帮助用户更全面地了解时速云产品及其应用,时速云每两周会进行一次技术分享,分享时间为 周四晚8:30-9:30 在用户微信群、时速云技术分享群等进行产品、容器技术相关的技术直播分享和现场答疑。以下整理自 7月28日第十三期技术分享内容 ,由 时速云工程师 张鹏程 分享。
微服务架构是一种构造应用程序的替代性方法。应用程序被分解为更小、完全独立的组件,这使得它们拥有更高的敏捷性、可伸缩性和可用性。
一个复杂的应用被拆分为若干微服务,微服务更需要一种成熟的交付能力。持续集成、部署和全自动测试都必不可少。编写代码的开发人员必须负责代码的生产部署。构建和部署链需要重大更改,以便为微服务环境提供正确的关注点分离。
后续我们会聊一下如何在时速云平台上集成 DevOps。
Node.js 是构建微服务的利器,为什么这么说呢,我们先看下 Node.js 有哪些优势:
Node.js 采用事件驱动、异步编程,为网络服务而设计
Node.js 非阻塞模式的IO处理给 Node.js 带来在相对低系统资源耗用下的高性能与出众的负载能力,非常适合用作依赖其它IO资源的中间层服务
Node.js轻量高效,可以认为是数据密集型分布式部署环境下的实时应用系统的完美解决方案。
这些优势正好与微服务的优势:敏捷性、可伸缩性和可用性相契合(捂脸笑),再看下Node.js 的缺点:
单进程,单线程,只支持单核CPU,不能充分的利用多核CPU服务器。一旦这个进程 down 了,那么整个 web 服务就 down 了
异步编程,callback 回调地狱
第一个缺点可以通过启动多个实例来实现CPU充分利用以及负载均衡,话说这不是 K8s 的原生功能吗。
第二个缺点更不是事儿,现在可以通过 generator 、 promise 等来写同步代码,爽的不要不要的。
下面我们主要从 Docker 和 Node.js 出发聊一下高质量Node.js微服务的编写和部署:
Node.js 异步流程控制:generator 与 promise
Express、Koa 的异常处理
如何编写 Dockerfile
微服务部署及 DevOps 集成
1. Node.js 异步流程控制:Generator 与 Promise
Node.js 的设计初衷为了性能而异步,现在已经可以写同步的代码了,你造吗?
目前 Node.js 的 LTS 版本早就支持了 Generator , Promise 这两个特性,也有许多优秀的第三方库bluebird、q 这样的模块支持的也非常好,性能甚至比原生的还好,可以用 bluebird 替换Node.js 原生的 Promise:
global.Promise = require('bluebird')
blurbird 的性能是 V8 里内置的 Promise 3 倍左右(bluebird 的优化方式见 /petkaantonov/bluebird/wiki/Optimization-killers )。
1.1 ES2015 Generator
generator 就像一个取号机,你可以通过取一张票来向机器请求一个号码。你接收了你的号码,但是机器不会自动为你提供下一个。
换句话说,取票机“暂停”直到有人请求另一个号码( next() ),此时它才会向后运行。下面我们看一个简单的示例:
从上面的代码的输出可以看出:
generator 函数的定义,是通过 function *(){} 实现的
对 generator 函数的调用返回的实际是一个遍历器,随后代码通过使用遍历器的 next() 方法来获得函数的输出
通过使用 yield 语句来中断 generator 函数的运行,并且可以返回一个中间结果
每次调用 next() 方法,generator 函数将执行到下一个 yield 语句或者是 return 语句。
下面我们就对上面代码的每次next调用进行一个详细的解释:
第1次调用 next() 方法的时候,函数执行到第一次循环的 yield index++ 语句停了下来,并且返回了 0 这个 value ,随同 value 返回的 done 属性表明 generator 函数的运行还没有结束
第2次调用 next() 方法的时候,函数执行到第二循环的 yield index++ 语句停了下来,并且返回了 1 这个 value ,随同 value 返回的 done 属性表明 generator 函数的运行还没有结束
第4次调用 next() 方法的时候,由于循环已经结束了,所以函数调用立即返回, done 属性表明 generator 函数已经结束运行, value 是 undefined 的,因为这次调用并没有执行任何语句
PS: 如果在 generator 函数内部需要调用另外一个 generator 函数,那么对目标函数的调用就需要使用 yield* 。
1.2 ES2015 Promise
所谓 Promise,就是一个对象,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的 API,可供进一步处理。
一个 Promise 一般有3种状态:
1.pending : 初始状态, 不是 fulfilled ,也不是 rejected .
2.fulfilled : 操作成功完成.
3.rejected : 操作失败.
一个 Promise 的生命周期如下图:
下面我们看一段具体代码:
asyncFunction 这个函数会返回 Promise 对象, 对于这个 Promise 对象,我们调用它的 then 方法来设置 resolve 后的回调函数, catch 方法来设置发生错误时的回调函数。
该 Promise 对象会在 setTimeout 之后的 16ms 时被 resolve , 这时 then 的回调函数会被调用,并输出’Async Hello world’ 。
在这种情况下 catch 的回调函数并不会被执行(因为 Promise 返回了 resolve ), 不过如果运行环境没有提供 setTimeout 函数的话,那么上面代码在执行中就会产生异常,在 catch 中设置的回调函数就会被执行。
如果是编写一个 SDK 或 API,推荐使用传统的 callback 或者 Promise,不使用 generator 的原因是:
generator 的出现不是为了解决异步问题
使用 generator 是会传染的,当你尝试 yield 一下的时候,它要求你也必须在一个 generator function 内
看来学习 Promise 是水到渠成的事情。
2. Express、Koa 的异常处理
一个友好的错误处理机制应该满足三个条件:
对于引发异常的用户,返回 500 页面
其他用户不受影响,可以正常访问
不影响整个进程的正常运行
下面我们就以这三个条件为原则,具体介绍下 Express、Koa 中的异常处理。
2.1 Express 异常处理
在 Express 中有一个内置的错误处理中间件,这个中间件会处理任何遇到的错误。
如果你在 Express 中传递了一个错误给 next() ,而没有自己定义的错误处理函数处理这个错误,这个错误就会被 Express 默认的错误处理函数捕获并处理,而且会把错误的堆栈信息返回到客户端,这样的错误处理是非常不友好的。
还好我没可以通过设置 NODE_ENV 环境变量为 production ,这样 Express 就会在生产环境模式下运行应用,生产环境模式下 Express 不会把错误的堆栈信息返回到客户端。
在 Express 项目中可以定义一个错误处理的中间件用来替换 Express 默认的错误处理函数:
在所有其他 app.use() 以及路由之后引入以上代码,可以满足以上三个友好错误处理条件,是一种非常友好的错误处理机制。
2.2 Koa 异常处理
我们以 Koa 1.x 为例,看代码:
把上面的代码放在所有 app.use() 函数前面,这样基本上所有的同步错误均会被 try{} catch(err){} 捕获到了,具体原理大家可以了解下 Koa 中间件的机制。
2.3 未捕获的异常uncaughtException
上面的两种异常处理方法,只能捕获同步错误,而异步代码产生的错误才是致命的, uncaughtException 错误会导致当前的所有用户连接都被中断,甚至不能返回一个正常的 HTTP 错误码,用户只能等到浏览器超时才能看到一个 no data received 错误。
这是一种非常野蛮粗暴的异常处理机制,任何线上服务都不应该因为 uncaughtException 导致服务器崩溃。
在Node.js 我们可以通过以下代码捕获 uncaughtException 错误:
捕获 uncaughtException 后,Node.js 的进程就不会退出,但是当 Node.js 抛出uncaughtException 异常时就会丢失当前环境的堆栈,导致 Node.js 不能正常进行内存回收。
也就是说,每一次、 uncaughtException 都有可能导致内存泄露。既然如此,退而求其次,我们可以在满足前两个条件的情况下退出进程以便重启服务。
当然还可以利用 domain 模块做更细致的异常处理,这里就不做介绍了。
3. 如何编写 Dockerfile
3.1 基础镜像选择
我们先选用 Node.js 官方推荐的 node:argon 官方 LTS 版本最新镜像,镜像大小为 656.9 MB (解压后大小,下文提到的镜像大小没有特殊说明的均指解压后的大小)
我们事先写好了两个文件 package.json , app.js :
下面开始编写 Dockerfile,由于直接从 Dockerhub 拉取镜像速度较慢,我们选用时速云的docker官方镜像docker_library/node(/repos/docker_library/node),这些官方镜像都是与 Dockerhub 实时同步的:
执行以下命令进行构建:
docker build -t zhangpc/docker_web_app:argon .
最终得到的镜像大小是 660.3 MB ,体积略大,Docker 容器的优势是轻量和可移植,所以承载它的操作系统即基础镜像也应该迎合这个特性,于是我想到了 Alpine
,一个面向安全的,轻量的 Linux 发行版,基于 musllibc 和 busybox 。
下面我们使用 alpine:edge 作为基础镜像,镜像大小为 4.799 MB :
本文前端(javascript)相关术语:javascript是什么意思 javascript下载 javascript权威指南 javascript基础教程 javascript 正则表达式 javascript设计模式 javascript高级程序设计 精通javascript javascript教程
转载请注明本文标题:本站链接:
分享请点击:
1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
这个世界是不公平,但还没有不公平到让有能力的人无法出头的地步。
手机客户端
,专注代码审计及安全周边编程,转载请注明出处:http://www.codesec.net
转载文章如有侵权,请邮件 admin[at]codesec.net下面我们将要在我们的CentOS6.x服务器上配置Nodejs,启动一个简单的nodejs实例,这个服务器有很简单的架构。
开始了,首先创建一个playbook文件,我们尽量让它保持简单。
- hosts: all
定义一些运行这个playbook的主机,然后下面列出一系列的tasks。
一、 添加额外的源
在准备应用一个服务器的时候,为了确保指定些软件包可以用或者在最新的版本,管理员经常首先添加额外的源。
下面的脚本,我们想要添加EPEL和Remi源,以便于我们可以得到类似node.js的软件包。如果使用shell脚本处理的话,如下所示。
# 导入 Remi GPG 密钥 – 请参阅: /RPM-GPG-KEY-remi
wget /RPM-GPG-KEY-remi \
-O /etc/pki/rpm-gpg/RPM-GPG-KEY-remi
rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-remi
# 安装 Remi repo, Remi repo里面包含了很多的PHP扩展
rpm -Uvh --quiet \
/enterprise/remi-release-6.rpm
# 安装EPEL源
yum install epel-release
# 安装 Node.js (npm + 和它的依赖关系).
yum --enablerepo=epel install npm
这个shell脚本用于导入EPEL和Remi的GPG keys,然后添加这源,最后安装Nodejs。这对于简单的部署是没有问题的,但是运行这么多命令是比较笨的方法,如果你的连接不小心断开了,那么你的脚本也会停止的。而如果这个时候,你的脚本刚准备完成呢?
提示:如果你想跳过指定的步骤,你可以跳过添加GPG keys的步骤,只需要在运行命令的时候加上—nogpgcheck.或者在Ansible中,yum模块中设置disable_gpg_check参数为yes,但是最好还是添加GPG keys。使用GPG,你可以知道包的作者是谁,包有没有修改稿,除非你知道你正在做什么,否则最好不要禁止GPG检查。
Ansible让事情变的更有健壮性,下面使用Ansible的案例显得更加详细,它和上面的shell脚本有同样的功能,但是更容易理解,更加结构化。 下面使用了Ansible的变量和其它的一些有用的特性。接着上面的playbook,我们继续往下写。
- name: Import Remi GPG key
rpm_key: "key={{ item }} state=present"
with_items:
- "/RPM-GPG-KEY-remi"
- name: Install Remi repo.
command: "rpm -Uvh --force {{ item.href }} creates={{ item.creates }}"
with_items:
- href: "/enterprise/remi-release-6.rpm"
creates: "/etc/yum.repos.d/remi.repo"
- name: Install epel repo
yum: name=epel-release state=present
- name: Stop the firewall
service: name=iptables state=stopped
- name: Install NodeJS and npm
yum: name=npm state=present enablerepo=epel
我们看一下具体步骤
1、rpm_key 是一个的Ansible模块用于从你的RPM数据库中添加或移除GPG key。我们正在从Remi的源中导入一个key。
2、因为Ansible没有rpm命令,因此我们使用command模块来使用rpm命令,这样我们可以做其它的两件事情。
使用creatse参数告诉Ansible什么时候不运行这个命令,这个例子里,我们告诉Ansible,这个命令成功执行后,将会创建那些文件。当这个文件存在的时候,这个命令将不会运行。
使用with_items定义一个URL和用于creates检查的文件。
yum负责安装EPEL源。
4、因为这个服务器我们将用来做测试用,所以我们使用service模块禁止系统防火墙,防止它干涉我们测试。
5、yum安装Node.js(同时安装npm,Node’s package manager),我们使用enablerepo来指定在EPEL源中搜索它,当然也可以使用disablerepo来指定不使用那个源(repository)。
6、因为NPM现在被安装了,我们使用Ansible 的npm模块安装Node.JS工具forever,forever来运行我们的app,设置global为yes,告诉NPM安装模块在/usr/lib/node_modules位置,然后所有的用户都可以使用它了。
二、 部署一个Node.js app
这一步是在我们的服务器上部署简单的Node.js app。首先,通过创建一个新的文件夹,我们创建一个简单的Node.js app,这个文件夹和你上面的ymal文件处于相同的路径下面。然后创建新的文件,app.js,在这个文件夹里面,编辑下面的文件
// 加载express 模块.
var express = require('express'),
app = express.createServer();
// 响应”/”请求为 'Hello World'.
app.get('/', function(req, res){
res.send('Hello World! Yunzhonge');
// 像一个真实服务器那样监听在80端口
app.listen(80);
console.log('Express server started successfully.')
不要担心node.js的语法的和我们的案例。我们需要一个快速的部署案例,这个案例可以用Python,Perl,Java,PHP或者其他编程语言来写,但是因为Node是非常简单的语言,运行一个简单的轻量级的环境,它是一个非常不错的语言来测试你的服务器。
因为这个小app依赖于Express(一个简单的Node的HTTP框架),我们同样需要通过一个package.json文件告诉NPM关于它的依赖关系,这个文件与app.js处于相同的路径下面。
"name": "examplenodeapp",
"description": "Example Express Node.js app.",
"author": "yunzhonghe",
"dependencies": {
"express": "3.x.x"
"engine": "node &= 0.10.6"
然后添加下面内容到你的playbook里面,拷贝整个app到这个服务器,然后让npm下载依赖的东西,(这里为express.)
- name: Ensure Node.js app folder exists.
file: "path={{ node_apps_location }} state=directory"
- name: Copy example Node.js app to server.
copy: "src=app dest={{ node_apps_location }}"
- name: Install app dependencies defined in package.json.
npm: "path={{ node_apps_location }}/app"
首先我们使用file模块确保我们安装的app目录存在。{{ node_apps_location }}变量可以在vars部分定义,vars部分位于playbook的顶部。当然也可以在inevntory中定义,也可以在运行ansible-playbook的时候定义。
我们使用Ansible的copy模块拷贝这整个app文件夹到测试服务器,copy模块可以聪明的区分单一的文件和包含文件的目录,然后在目录中递归,就像rsync或scp。Ansible的copy模块在单个文件或少量文件时候非常好用,但是如果你拷贝大量的文件,嵌套几层的目录,copy模块就不能胜任。这种情形下,如果你想拷贝整个目录,你最好考虑使用synchronize模块,如果你想拷贝一个归档,然后展开它,最好使用unarchive模块。
第三步,我们使用npm模块,这次除了app的路径之外没有额外的参数。这告诉NPM来解析package.json文件,然后确保所有的依赖关系都存在。
已经都完成了,最后一步是启动这个app
三、运行一个Node.js app
我们现在使用forever来启动这个app。
- name: Check list of running Node.js apps.
command: forever list
register: forever_list
changed_when: false
- name: Start example Node.js app.
command: "forever start {{ node_apps_location }}/app/app.js"
when: "forever_list.stdout.find('{{ node_apps_location }}/app/app.js') == -1"
在这个play中,我们做了两件新的事情。
1、register 创建了一个新的变量,forever_list,以便于下次task的时候使用用于判断是否允运行下一个命令。register用于保存命令的输出包括标准输出和错误输出,然后赋给变量名。
2、changed_when告诉Ansible什么时候这个play会导致改变。在这里,forever list命令永远都不会导致服务器的改变,因为我们指定了false。
第二个play实际上使用forever启动了这个app。我们可以启动这个app通过调用node {{ node_apps_location }}/app/app.js,不过这种方式更难控制。
Forever跟踪它管理的Node app,然后我们使用Forever的list选项,打印一系列的运行app。我们第一次运行这个playbook时候,这list明显是空的,但是判断为空之后就会运行,如果app正在运行,我们不会启动另外一个实例了,为了避免这种情形,我们使用when语句,指定,当app的路径不在forever list的输出信息的时候,我们启动这个app。
四、 Node.js app服务器总结。
在这时候上,你已经完成了playbook,然后安装一个简单的Node.js app,在80端口响应HTTP请求。
为了运行这个playbook在一个服务器上,使用下面的命令,传递node_apps_location变量通过命令
ansible-playbook --extra-vars="node_apps_location=/usr/local/opt/node"
当服务器完成配置和部署服务器的时候,在浏览器中指定测试服务器的主机名查看效果
简单,但是有效,我们已经在少于50行的YMAL文件中配置了一个Nodejs应用服务器
到此结束,非常感谢朋友们的关注。
五、问题:
当我在给100台服务器进行nodejs app部署的时候,到20台中断了,我再重新执行,他是一个怎么个过程,前面已经安装的软件包,进行的配置的会重新的再进行执行一遍还是跳过呢?麻烦大牛解答
Ansible自身有幂等特性使其能有效保证所有操作的安全可靠性,针对执行失败的情况会自动在家目录下生成 对应的错误服务器列表 通过 —limit 再次有针对性的完成剩余工作
请问大侠的nodejs 是通过工具部署的?
npm 好复杂啊哈,npm的配置是一次性的,初始配置确认比较耗时且麻烦,yum 或 npm 各有优劣,视业务而定吧
—extra-vars= 请问企业中应用的多吗?
多,最少在我们的工作中一直有应用,前几期的分享大家应该也有看到,这个参数应用的很多。但官网介绍的却一笔代过。还是建议大家多用
当我通过ansible执行任务的过程中,会出现任务被长时间卡主的原因,这一般需要重哪些地方排查呢
很多朋友遇到这个问题,一直有问,根据个人的经验建议如下几个方面排查:
确实当前命令执行时间不长,
pong 检测服务器存活
有些命令需连接外网下载更新耗时较长的可检查网络宽带情况
-vvv 是非常好的排查方式
如若却无解,/etc/ansible/ansible.cfg 中缩短操作执行时长,等待最终结束尝试
原文链接:
/p/1db87e8a4bd1,著作权归作者所有
号外号外:
现在我们公众号推出参与奖和互动奖,凡是通过微信与我们参与讨论互动最多的朋友,将获得我们送出的神秘礼物,还有机会获得Ansible中文官网马上将出版的新书哦! 我们将不定期的推出奖励计划 ! 奖励多多,小伙伴们,一起来吧!
以上是今天为大家带来的内容,如果有任何问题,大家也可以添加以下QQ群参与问题的讨论。
Ansible中文权威群:(已满)
AWK&SED企业实战:
docker企业架构实践:
Jumpserver交流群 :
Ansible中文权威-2号群:
关于我们:
本文来自微信公众账号提交,由微讯啦收录,转载请注明出处。
微信扫码 分享文章利用ZooKeeper服务实现分布式系统的配置数据同步 | Tony Bai
利用ZooKeeper服务实现分布式系统的配置数据同步
八月 28, 2013
很多时候,一旦习惯了某些事情,也就习惯了它们的恶劣,习惯了它们的丑陋,习惯了它们&赋予&你的各种痛苦。
&&& &&& &&& &&& &&& &&& &&& &&& &&& &&& &&& &&& &&& &&& &&& &&& &&& &&& &&& &&& &&& &&& &&& &&& &&& &&& &&& & & & & & & & & & & & & & & & & & & & & & – Tony Bai
一、痼疾难解
曾几何时,在那个还没有集群化,没有分布式的时代,它还是一个不错的方案,至少在线上没有暴露出太多问题,它也不在我们关注的重点范围之内。但随 着集群化、分布式的新版本的到来,那一大坨遗留的代码就变得格外让人不顺眼,同时问题也随之在线上暴露开来了。
这里的&它&指的就是我们目前的业务配置数据同步方案。简单描述这个方案如下:
* 方案涉及两个角色 – 数据库(DB)与应用节点(app_node);
* 所有的业务配置数据均统一存储在DB中;
* 应用节点在启动后从DB中读取最新业务配置数据;
* 应用节点运行过程中,如果DB中的业务配置数据发生变更(增/删/改),DB中的触发器(trigger)将会执行。在触发器的脚本中,触发器将会【串 行】地与每个应用节点建立TCP链接,并将业务配置表的变更信息发给各个应用节点。 应用节点会接收并【解析】触发器发过来变更数据包,并同步到自己的本地内存中。这样就达到了运行时更新配置的目的。
上面我用【】标记了两个关键词:&串行&和&解析&。这两个词隐含有这个方案的两个主要问题。
&串行& – 意味着每一次DB的业务配置数据变更,trigger脚本都要逐个与应用节点建立链接并收发数据。当应用节点逐渐增多时,每一次业务数据同步都会相当地耗 时。尤其是当某个应用节点所在主机出现问题时,到该节点链接建立的过程会阻塞,导致整个业务配置数据同步的时间达到无法忍受的地步。
&解析& – 我们自定义了trigger与应用节点之间的协议包。协议包中包含了每次变更的详细信息,比如在某个表添加一条记录,trigger会将这个记录的每个字 段信息排成一行打包发给应用节点。应用节点收到这个包后,会根据已有的表字段信息对该包进行解析。看得出这是一个很强的耦合:表字段一旦修 改,trigger脚本要修改,应用节点的解析函数要修改,还要考虑协议包中表字段的排序。如果应用节点解析时与trigger脚本打包时的字段 顺序不同的话,那就可能出现严重错误,而且这种错误有时难于校验并难于发现。
二、曾经的努力
针对这个方案的不足,我们曾经也做过改进,但主要针对的是解决&串行&这个问题上。
第一次改进:同步的发起能否并行做?trigger脚本能否并行发起对各个应用节点的链接建立请求?
Java组同事对trigger脚本做了改进。让trigger脚本调用function,而function中又调用了写好的Java方 法,Java代码由DB加载到环境中。在Java方法中创建多个同步线程,并发与各应用节点建立链接并发送数据。这个方法的确可以变&串行&为 &并行&,但不知为何生产环境中实际运行时偶尔会出现异常,该异常发生在DB中,影响很大。有时还会导致DB的一些异常现象。至今原因尚未明确, 我们无奈退回到以前的方案。
第二次改进:从Push模式到Pull模式
在之前部门新规划的一个产品中,开发人员对数据同步的机制做了重新的设计,将原来的Push模式改为了Pull模式。大致方案是:
&&& * 业务数据变更时,trigger直接将变更内容(以老方案中那个协议包的打包格式)写到一个&变更日志表&中,每条记录有一个唯一的序号,序号递增。
&&& * 应用节点启动后,从DB加载最新配置信息,查询&变更日志表&,得到该表内最新的一条记录的序号n。
&&& * 应用节点以&轮询&的方式定期查询&变更日志表&,并读取和解析那些序号比序号n更新的记录;更新完后,将继续保存最新的一条记录序号。
&&& * 数据库中有job定期对&变更日志表&中的记录进行过期删除处理。
个人感觉第二个方案应该是理想方案的一个雏形,虽然目前它的同步更新可能不是那么及时,与DB交互过多(方案细节中每个应用节点在处理完一条记录 后还要更新记录的状态)。该方案设计者也完全也可以放弃那个导致耦合的协议包设计,但他最终还是选择保留了原有协议包解析函数。目前该方案在产品 环境下运行还算良好,并未暴露出什么问题。这算是一次有效的改进,也为本文中要提到的方案提供了一些思路启示。
三、与时俱进
生来就具备解决分布式系统的配置分发和同步的能力。利用ZooKeeper服务实现分布式系统的统一配置中心已经不是那么新鲜 的话题了。最简单的模型莫过于将配置数据存储在ZooKeeper上的路径节点上,然后应用节点在这些配置节点上添加watch。当配置数据变更 时,每个应用节点都可以及时得到通知,同步到最新数据。这种模型对于一些量少简单的系统配置来说较为合适。对于我们每个表动辄上万条配置的情形似 乎不那么适合,想象一下每个应用节点要添加上万个watch,这对ZooKeeper而言也是压力山大啊。因此用ZooKeeper提供的诸多服 务如何来优化我们上面提到的两个主要问题呢?这里提出一种方案仅供参考。
方案示意图:
DB& —-& Config Center Services(css_agent + ZooKeeper)& —& App Node
在新方案中,我们要:
&&& 保留 – 保留trigger脚本,作为业务数据变更的唯一的触发起点;
&&& 摒弃 – 摒弃那个复杂的带来耦合的协议格式;
&&& 借鉴 – 借鉴&Push -& Pull&的数据获取方式。
新方案中除了DB、应用节点(app_node)外,新增加了一个角色Config Center Services(缩写为ccs),ccs由ZooKeeper + ccs_agent的集群组成。简单起见,每个ZooKeeper节点上部署一个ccs_agent。这些角色之间的数据流和指令流关系,即该方案的原理 如下:
&&& * 初始化
&&& &&& – ZooKeeper集群启动;
&&& &&& – ccs_agent启动,利用ZooKeeper提供的,选出ccs_agent leader。ccs_agent leader启动后负责在ZooKeeper中建立业务配置表node,比如:表employee_info_tab对应的node路径为&/ccs /foo_app/employee_info_tab&;
&&& &&& – ccs_agent启动后会监听一个端口,用来接受DB trigger向其发起的数据链接;
&& && & – 应用节点启动,监听ZooKeeper上所有(数量有限的)业务配置表node的child event;
&&& * 数据变更
&&& &&& – DB中某业务表比如employee_info_tab增加了一条id为&1234567&的记录;
&&& &&& – 触发器启动,向ccs_agent cluster中任意一个可用的节点建立链接,并将数据包&^employee_info_tab|ADD|1234567$&发送给 ccs_agent;
&&& &&& – ccs_agent收取并解析trigger发来的数据包,在对应的/ccs/foo_app/employee_info_tab下建立ZOO_SEQUENCE类 型节点&item-&,该节点的值为&ADD 1234567&;
&&& &&& – ZooKeeper将/ccs/foo_app/employee_info_tab节点的child事件发给所有watch该节点事件的应用节点;
&&& &&& – 应用节点&取出&/ccs/foo_app/employee_info_tab节点下的children节点&item-&,并读取 其值,后续到DB的employee_info_tab中将id = 1234567的这条记录select出来,将该条记录更新到本地内存中。应用节点记录下处理过的当下节点id为&item-&;
&&&&&&& – DB业务表employee_info_tab又增加了两条记录,id分别为&7777777&和&8888888&,经过上面描述的流程,/ccs /foo_app/employee_info_tab节点下会增加&item-&和&item-&两项; 应用节点最终会收到child事件通知。应用节点&取出&/ccs/foo_app/employee_info_tab节点下的所有 children节点并排序。之后,处理那些id号大于&item-&的节点,并将当前节点id记录为&item- &。依次类推。
&&& * 过期处理
&&& &&& – ccs_agent leader负责定期扫描ZooKeeper中/ccs下各个表节点下的子项,对于超出过期时间的item进行删除处理。
&&& * 应用节点重启
&&& &&& -& 应用节点重启后,会首先从db读取最新信息,并记录启动时间戳;
& & & & -& 应用节点重启后,在收到zookeeper的数据变更事件后,会根据当前时间戳与变更表节点下的item创建时间进行比较,并仅处理比启动时间戳新的 item的数据。
这个方案主要利用了ZooKeeper提供的leader election服务以及sequence节点的特性,几点好处在于:
&&& – 串行通知变为并行通知,且通知到达及时;
&&& – 变更数据的Push模式为Pull模式,降低了或去除了诸多耦合,包括:
&&& &&& &&& 1) 去除trigger脚本与表字段及字段顺序的耦合;
&&& &&& &&& 2) 去除应用节点与表字段顺序的耦合;
&&&&&&&&&&& 3) 降低应用节点与表字段构成的耦合。
&&& – 应用节点无需复杂的包解析,简化后期维护。
当然为了该方案新增若干网元会给产品部署和维护带来一些复杂性,这算是不足之处吧。
有一个600多行代码的Demo,模拟新方案中几个角色:
&&& DB – trigger_sim.py
&&& 应用节点 – app.c
&&& ccs_agent – ccs_agent.c
模拟的步骤大致如下(单机版):
a) 启动ZooKeeper
&&& $& zkServer.sh start
&&& JMX enabled by default
&&& Using config: /home1/tonybai/.bin/zookeeper-3.4.5/bin/../conf/zoo.cfg
&&& Starting zookeeper … STARTED
b) 启动ccs_agent
&&& $& ccs_agent
&&& This is [ccs-member], i am a leader
&&& /ccs node exists
&&& /ccs/employee_info_tab node exists
&&& /ccs/boss_info_tab node exists
&&& trigger listen thread start up!
&&& item expire thread start up!
c) 启动app
d) 使用trigger_sim.py模拟DB触发trigger
&&&&&&& $& trigger_sim.py employee_info_tab ADD 1234567
可以看到ccs_agent输出结果如下:
&&& table[employee_info_tab], oper_type[ADD], id[1234567]
app的输出如下:
&&& child event happened: type[4]
&&& employee_info_tab: execute [ADD 1234567]
大约30s后,ccs_agent会输出如下:
&&& [expire]: employee_info_tab: expire [item-]
模拟步骤在README里有写。这里仅是Demo代码,存在硬编码以及异常处理考虑不全面的情况,不要拍砖哦。
& 2013, . 版权所有.
Related posts:
这里是的个人Blog,欢迎访问、订阅和留言!订阅Feed请点击上面图片。
如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!
如果您喜欢通过微信App浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
著名主机提供商Linode 10$优惠码:linode10,在即可免费获得。
阿里云推荐码:1WFZ0V,立享9折!

我要回帖

更多关于 kubernetes集群部署 的文章

 

随机推荐