CZookeeperHelper:
https://github.com/eyjian/libmooon/blob/master/include/mooon/net/zookeeper_helper.h

CMainHelper:
https://github.com/eyjian/libmooon/blob/master/include/mooon/sys/main_template.h

  1. // Zookeeper C++编程实战之配置更新,
  2. // 演示一个多线程程序如何借助zookeeper,实现配置的动态更新
  3. //
  4. // 实现理念(有些场景不适合):
  5. // 1) 让线程不涉及配置的动态更新,这样避免了动态更新配置
  6. // 2) 通过创建新线程的方式达到配置动态更新的目的,老的线程直接退出
  7. // 3) 先创建新线程,再退出老线程,保持服务不中断
  8. //
  9. // 实际上,也可以通过父子进程方式来达到配置动态更新,
  10. // 父进程检测到配置更新后,父进程读取配置,并检查配置的合法性。
  11. // 如果合法则创建新的子进程,完成后再kill原有的子进程,
  12. // 这样子进程就不涉及配置更新逻辑。
  13. //
  14. // 这两种方法,均可比较简单应对复杂的配置动态更新,
  15. // 但如果新旧配置无法同时兼容,则需要先停掉老的线程或进程,
  16. // 然后再启动新的线程或进程,否则做到无缝地动态更新。
  17. //
  18. // https://github.com/eyjian/libmooon/blob/master/include/mooon/net/zookeeper_helper.h
  19. // https://github.com/eyjian/libmooon/blob/master/include/mooon/sys/main_template.h
  20. //
  21. // 编译要求环境:C++11或更高
  22. // 编译语句大致如下:
  23. // g++ -g -o b zk_conf_example.cpp -I/usr/local/mooon/include -I/usr/local/zookeeper/include /usr/local/mooon/lib/libmooon.a /usr/local/zookeeper/lib/libzookeeper_mt.a -pthread -std=c++11 -DMOOON_HAVE_ZOOKEEPER=1 -lz
  24. #include <mooon/net/zookeeper_helper.h>
  25. #include <mooon/sys/datetime_utils.h> // 格式化时间也可以考虑C++标准库提供的std::put_time
  26. #include <mooon/sys/main_template.h>
  27. #include <mooon/utils/args_parser.h>
  28. #include <chrono>
  29. #include <condition_variable>
  30. #include <mutex>
  31. #include <system_error>
  32. #include <thread>
  33.  
  34. // 指定存放配置的zookeeper
  35. STRING_ARG_DEFINE(zookeeper, "", "Comma separated list of servers in the ZooKeeper Quorum, example: --zookeeper=127.0.0.1:2181");
  36.  
  37. class CMyApplication;
  38.  
  39. // 负责具体业务的工作者(线程)
  40. class CWorker
  41. {
  42. public:
  43. CWorker(CMyApplication* app, int index);
  44. void run(); // 线程入口函数
  45. void stop() { _stop = true; }
  46.  
  47. private:
  48. CMyApplication* _app;
  49. int _index;
  50. volatile bool _stop;
  51. };
  52.  
  53. // 应用程序主类(或叫上下文类,也可叫入口类)
  54. // 通过继承CZookeeperHelper,获得zookeeper操作能力,
  55. // 包括读写zookeeper数据能力、发现配置更新能力和主备切换能力。
  56. //
  57. // 可继承mooon::sys::CMainHelper,
  58. // 以获得通过信号SIGTERM的优雅退出能力,
  59. // CMainHelper提供了优雅和安全的信号处理,
  60. // 默认的优雅退出信号为SIGTERM,可自定义为其它信号。
  61. class CMyApplication: public mooon::net::CZookeeperHelper, public mooon::sys::CMainHelper
  62. {
  63. public:
  64. CMyApplication();
  65.  
  66. private:
  67. // num_workers 需要启动的CWorker个数
  68. bool start_workers(
  69. std::vector<std::thread>* work_threads,
  70. std::vector<std::shared_ptr<CWorker>>* workers,
  71. int num_workers);
  72. void stop_workers(
  73. std::vector<std::thread>* work_threads,
  74. std::vector<std::shared_ptr<CWorker>>* workers);
  75. // 当zookeeper的会话过期后,
  76. // 需要调用recreate_zookeeper_session重新建立会话
  77. void recreate_zookeeper_session();
  78.  
  79. // 实现父类CMainHelper定义的虚拟函数(实为回调函数),
  80. // 以下五个“on_”函数,均运行在独立的信号线程中,而不是主线程中。
  81. private:
  82. // 主线程的调用顺序:
  83. // main()
  84. // -> on_check_parameter() -> on_init()
  85. // -> on_run() -> on_fini()
  86. //
  87. // 注意on_terminated()是由信号触发的,
  88. // 由独立的信号线程调用,但位于on_init()之后。
  89. virtual bool on_check_parameter();
  90. virtual bool on_init(int argc, char* argv[]);
  91. virtual bool on_run(); // 这里使得配置动态生效
  92. virtual void on_fini();
  93. virtual void on_terminated();
  94.  
  95. // 实现父类CZookeeperHelper定义的虚拟函数(实为回调函数)
  96. // 以下五个“on_”函数,均运行在独立的zookeeper线程中,而不是主线程中。
  97. private:
  98. virtual void on_zookeeper_session_connected(const char* path);
  99. virtual void on_zookeeper_session_connecting(const char* path);
  100. virtual void on_zookeeper_session_expired(const char *path);
  101. virtual void on_zookeeper_session_event(int state, const char *path);
  102. virtual void on_zookeeper_event(int type, int state, const char *path);
  103.  
  104. private:
  105. volatile bool _stop;
  106. std::mutex _mutex;
  107. std::condition_variable _cond;
  108. std::vector<std::thread> _work_threads;
  109. std::vector<std::shared_ptr<CWorker>> _workers;
  110.  
  111. private:
  112. volatile bool _conf_changed; // 配置发生变化
  113. volatile bool _zookeeper_session_expired; // zookeeper的会话(session)过期
  114. std::string _zk_nodes; // 存放配置的zookeeper节点列表
  115. std::string _conf_zkpath; // 配置的zookeeper节点路径
  116. };
  117.  
  118. int main(int argc, char* argv[])
  119. {
  120. CMyApplication app;
  121. return mooon::sys::main_template(&app, argc, argv);
  122. }
  123.  
  124. static unsigned long long get_current_thread_id()
  125. {
  126. std::stringstream ss;
  127. ss << std::this_thread::get_id();
  128. return std::stoull(ss.str());
  129. }
  130.  
  131. CMyApplication::CMyApplication()
  132. : _stop(false), _conf_changed(false), _zookeeper_session_expired(false)
  133. {
  134. _conf_zkpath = "/tmp/conf";
  135. }
  136.  
  137. bool CMyApplication::on_check_parameter()
  138. {
  139. // 命令行参数“--zookeeper”不能为空
  140. return !mooon::argument::zookeeper->value().empty();
  141. }
  142.  
  143. bool CMyApplication::on_init(int argc, char* argv[])
  144. {
  145. try
  146. {
  147. // 以this方式调用的函数,均为CZookeeperHelper提供
  148. _zk_nodes = mooon::argument::zookeeper->value();
  149. this->create_session(_zk_nodes);
  150.  
  151. // zookeeper的会话(session)是异步创建的,
  152. // 只有连接成功后,方可读取存放在zookeeper上的配置数据。
  153. for (int i=0; i<5&&!_stop; ++i)
  154. {
  155. if (this->is_connected())
  156. break;
  157. else
  158. std::this_thread::sleep_for(std::chrono::milliseconds(1000));
  159. }
  160.  
  161. if (!this->is_connected())
  162. {
  163. fprintf(stderr, "Can not connect zookeeper://%s\n", _zk_nodes.c_str());
  164. return false;
  165. }
  166. else
  167. {
  168. // 取zookeeper节点数据
  169. std::string zkdata;
  170. int n = get_zk_data(_conf_zkpath.c_str(), &zkdata, 4);
  171. if (n > 4 || zkdata.empty())
  172. {
  173. // 配置数据的大小超出预期
  174. fprintf(stderr, "conf size error: %d\n", n);
  175. return false;
  176. }
  177. else
  178. {
  179. // 如果zkdata不是一个有效的数字,
  180. // stoi会抛出异常invalid_argument
  181. const int num_workers = std::stoi(zkdata);
  182.  
  183. if (num_workers < 1 || num_workers > 10)
  184. {
  185. fprintf(stderr, "conf error: %d\n", num_workers);
  186. return false;
  187. }
  188. else
  189. {
  190. return start_workers(&_work_threads, &_workers, num_workers);
  191. }
  192. }
  193. }
  194. }
  195. catch (std::invalid_argument& ex)
  196. {
  197. fprintf(stderr, "%s\n", ex.what());
  198. return false;
  199. }
  200. catch (mooon::sys::CSyscallException& ex)
  201. {
  202. fprintf(stderr, "%s\n", ex.str().c_str());
  203. return false;
  204. }
  205. catch (mooon::utils::CException& ex)
  206. {
  207. fprintf(stderr, "%s\n", ex.str().c_str());
  208. return false;
  209. }
  210. }
  211.  
  212. bool CMyApplication::on_run()
  213. {
  214. while (!_stop)
  215. {
  216. std::unique_lock<std::mutex> lock(_mutex);
  217. _cond.wait(lock); // 等待配置更新或收到退出指令
  218. if (_stop)
  219. {
  220. break;
  221. }
  222.  
  223. // 以下实现省略了函数调用抛异常处理
  224. if (_zookeeper_session_expired)
  225. {
  226. // 如果会话过期,则需要重新建会话
  227. recreate_zookeeper_session();
  228. }
  229. if (_stop)
  230. {
  231. // 在建立会话过程中,可能收到了停止指令
  232. break;
  233. }
  234. if (_conf_changed)
  235. {
  236. _conf_changed = false;
  237.  
  238. // 读取新的配置
  239. std::string zkdata;
  240. int n = get_zk_data(_conf_zkpath.c_str(), &zkdata, 4);
  241. if (n > 4)
  242. {
  243. // 这种情况下应触发告警
  244. // 配置数据的大小超出预期
  245. fprintf(stderr, "conf size error: %d\n", n);
  246. }
  247. else
  248. {
  249. // 这里可考虑加上优化:
  250. // 只有配置确实发生变化时才进行后续操作。
  251. const int num_workers = std::stoi(zkdata);
  252.  
  253. if (num_workers < 1 || num_workers > 10)
  254. {
  255. // 这种情况下应触发告警
  256. fprintf(stderr, "conf error: %d\n", num_workers);
  257. }
  258. else
  259. {
  260. std::vector<std::thread> work_threads;
  261. std::vector<std::shared_ptr<CWorker>> workers;
  262.  
  263. // 新的配置生效,才停掉原来的,
  264. // 防止因为误操破坏配置,导致整个系统崩溃
  265. if (!start_workers(&work_threads, &workers, num_workers))
  266. {
  267. // 这种情况下应触发告警
  268. }
  269. else
  270. {
  271. stop_workers(&_work_threads, &_workers);
  272. _work_threads.swap(work_threads);
  273. _workers.swap(workers);
  274. }
  275. }
  276. }
  277. }
  278. }
  279.  
  280. return true;
  281. }
  282.  
  283. void CMyApplication::on_fini()
  284. {
  285. // 应用退出时被调用
  286. fprintf(stdout, "Application is about to quit\n");
  287. }
  288.  
  289. // 接收到了SIGTERM信号
  290. void CMyApplication::on_terminated()
  291. {
  292. // 一定要最先调用父类CMainHelper的on_terminated
  293. mooon::sys::CMainHelper::on_terminated();
  294.  
  295. _stop = true;
  296. stop_workers(&_work_threads, &_workers);
  297.  
  298. std::unique_lock<std::mutex> lock(_mutex);
  299. _cond.notify_one(); // 唤醒等待状态的CMyApplication::run
  300. }
  301.  
  302. bool CMyApplication::start_workers(
  303. std::vector<std::thread>* work_threads,
  304. std::vector<std::shared_ptr<CWorker>>* workers,
  305. int num_workers)
  306. {
  307. try
  308. {
  309. for (int i=0; i<num_workers; ++i)
  310. {
  311. std::shared_ptr<CWorker> worker(new CWorker(this, i));
  312. workers->push_back(worker);
  313. work_threads->push_back(std::thread(&CWorker::run, worker));
  314. }
  315. return true;
  316. }
  317. catch(const std::system_error& ex)
  318. {
  319. // 如果有部分启动功能应当回退,这里省略了
  320. fprintf(stderr, "(%d)%s\n", ex.code().value(), ex.what());
  321. return false;
  322. }
  323. }
  324.  
  325. void CMyApplication::stop_workers(
  326. std::vector<std::thread>* work_threads,
  327. std::vector<std::shared_ptr<CWorker>>* workers)
  328. {
  329. for (std::vector<std::shared_ptr<CWorker>>::size_type i=0; i<workers->size(); ++i)
  330. {
  331. (*workers)[i]->stop();
  332. if ((*work_threads)[i].joinable())
  333. (*work_threads)[i].join();
  334. }
  335. work_threads->clear();
  336. workers->clear();
  337. }
  338.  
  339. void CMyApplication::recreate_zookeeper_session()
  340. {
  341. unsigned int count = 0;
  342.  
  343. while (!_stop)
  344. {
  345. try
  346. {
  347. recreate_session();
  348. _zookeeper_session_expired = false;
  349. break;
  350. }
  351. catch (mooon::utils::CException& ex)
  352. {
  353. std::this_thread::sleep_for(std::chrono::milliseconds(2000));
  354.  
  355. if (0 == count++%30)
  356. {
  357. fprintf(stderr, "recreate zookeeper session failed: (count:%d)%s\n", count, ex.str().c_str());
  358. }
  359. }
  360. }
  361. }
  362.  
  363. void CMyApplication::on_zookeeper_session_connected(const char* path)
  364. {
  365. fprintf(stdout, "path=%s\n", path);
  366. }
  367.  
  368. void CMyApplication::on_zookeeper_session_connecting(const char* path)
  369. {
  370. fprintf(stdout, "path=%s\n", path);
  371. }
  372.  
  373. void CMyApplication::on_zookeeper_session_expired(const char *path)
  374. {
  375. fprintf(stdout, "path=%s\n", path);
  376.  
  377. std::unique_lock<std::mutex> lock(_mutex);
  378. _zookeeper_session_expired = true;
  379. _cond.notify_one(); // 唤醒等待状态的CMyApplication::run
  380. }
  381.  
  382. void CMyApplication::on_zookeeper_session_event(int state, const char *path)
  383. {
  384. fprintf(stdout, "state=%d, path=%s\n", state, path);
  385. }
  386.  
  387. void CMyApplication::on_zookeeper_event(int type, int state, const char *path)
  388. {
  389. fprintf(stdout, "type=%d, state=%d, path=%s\n", type, state, path);
  390.  
  391. if (ZOO_CONNECTED_STATE == state &&
  392. ZOO_CHANGED_EVENT == type &&
  393. 0 == strcmp(path, _conf_zkpath.c_str()))
  394. {
  395. // 配置发生变化
  396. std::unique_lock<std::mutex> lock(_mutex);
  397. _conf_changed = true;
  398. _cond.notify_one(); // 唤醒等待状态的CMyApplication::run
  399. }
  400. }
  401.  
  402. CWorker::CWorker(CMyApplication* app, int index)
  403. : _app(app), _index(index), _stop(false)
  404. {
  405. }
  406.  
  407. void CWorker::run()
  408. {
  409. fprintf(stdout, "Worker[%d/%llu] \033[1;33mstarted\033[m\n", _index, get_current_thread_id());
  410.  
  411. while (!_stop)
  412. {
  413. // 执行具体的业务逻辑操作,这里仅以sleep替代做示范
  414. std::this_thread::sleep_for(std::chrono::milliseconds(2000));
  415. fprintf(stdout, "[%s] Worker[\033[1;33m%d\033[m/%llu] is working ...\n",
  416. mooon::sys::CDatetimeUtils::get_current_time().c_str(),
  417. _index, get_current_thread_id());
  418. }
  419.  
  420. fprintf(stdout, "Worker[%d/%llu] \033[1;33mstopped\033[m\n", _index, get_current_thread_id());
  421. }

  

Zookeeper C++编程实战之配置更新的更多相关文章

  1. 【Zookeeper】编程实战之Zookeeper分布式锁实现秒杀

    1. Zookeeper简述 我们要了解一样技术,首先应该要到它的官网,因为官网的信息一般都是最准确的,如下图是Zookeeper官网对它的介绍. 从官网的介绍中,可以总结出,Zookeeper是一个 ...

  2. Zookeeper C++编程实战之主备切换

    默认zookeeper日志输出到stderr,可以调用zoo_set_log_stream(FILE*)设置输出到文件中还可以调用zoo_set_debug_level(ZooLogLevel)控制日 ...

  3. Linux下的C编程实战

    Linux下的C编程实战(一) ――开发平台搭建 1.引言 Linux操作系统在服务器领域的应用和普及已经有较长的历史,这源于它的开源特点以及其超越Windows的安全性和稳定性.而近年来, Linu ...

  4. Linux下的编程实战【转】

    一篇比较不错的文章, 降到了 makefile make , gcc编译器,GDB调试器, Linux文件系统,Linux文件API,.C语言库函数(C库函数的文件操作实际上是独立于具体的操作系统平台 ...

  5. zookeeper服务发现实战及原理--spring-cloud-zookeeper源码分析

    1.为什么要服务发现? 服务实例的网络位置都是动态分配的.由于扩展.失败和升级,服务实例会经常动态改变,因此,客户端代码需要使用更加复杂的服务发现机制. 2.常见的服务发现开源组件 etcd—用于共享 ...

  6. 【Spark】编程实战之模拟SparkRPC原理实现自定义RPC

    1. 什么是RPC RPC(Remote Procedure Call)远程过程调用.在Hadoop和Spark中都使用了PRC,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的 ...

  7. webpack4 中的最新 React全家桶实战使用配置指南!

    最新React全家桶实战使用配置指南 这篇文档 是吕小明老师结合以往的项目经验 加上自己本身对react webpack redux理解写下的总结文档,总共耗时一周总结下来的,希望能对读者能够有收获, ...

  8. elastic-job lite 编程实战经验

    (继续贴一篇之前写的经验案例) elastic-job lite 编程实战经验 其实这是一次失败的项目,虽然最后还是做出来了,但是付出了很大代价.并且需要较深入的踩坑改造elastic-job,导致代 ...

  9. Ligg.WinOa-000: Windows运维自动化编程实战--前言

        本开源项目Ligg.WinOa是一个基于Ligg.EasyWinApp的Windows运维自动化应用.通过Ligg.EasyWinForm生成2个功能界面:管理员工具箱和用户工具箱:通过Lig ...

随机推荐

  1. 42-字符串到json 的错误 com.alibaba.fastjson.JSONObject cannot be cast to java.lang.String

    json: {"updated_at":1551780617,"attr":{"uptime_h":3,"uptime_m&quo ...

  2. VML、SVG、Canvas简介

    1.VML: VML的全称是Vector Markup Language(矢量可标记语言),矢量的图形,意味着图形可以任意放大缩小而不损失图形的质量,这在制作地图上有很大用途,VML只是被IE支持. ...

  3. redis 哨兵(sentinel)

    redis哨兵 哨兵自动故障转移 自动通知应用最新master信息 无需担心,master挂了,程序不需要修改IP啥的,由哨兵自动完成 修改sentinel.conf protected-mode n ...

  4. jQuery的节点选择

    jQuery.parent(expr) 找父亲节点,可以传入expr进行过滤,比如$("span").parent()或者$("span").parent(&q ...

  5. iOS 3D Touch功能

    新的触摸体验——iOS9的3D Touch 一.引言 在iphone6s问世之后,很多果粉都争先要体验3D Touch给用户带来的额外维度上的交互,这个设计之所以叫做3D Touch,其原理上是增加了 ...

  6. Java的OOP三大特征之一——多态

    OOP(面对对象)三大特征之一——多态 What:多态性是指允许不同类的对象对同一消息作出响应,多态性包括参数化多态性和包含多态性,多态性语言具有灵活.抽象.行为共享.代码共享的优势,很好的解决了应用 ...

  7. [Spark]What's the difference between spark.sql.shuffle.partitions and spark.default.parallelism?

    From the answer here, spark.sql.shuffle.partitions configures the number of partitions that are used ...

  8. 摹客 iDoc 12月上半月新功能点评

    转眼就到了2018年的最后一个月,小伙伴们是不是都在奋力拼搏做年底的冲刺呢?摹客也没有放慢脚步,不断地优化,给大家带来一个又一个的惊喜.那么,让小摹来带大家看看12月摹客iDoc更新了哪些特色功能: ...

  9. rabbitmq安装.教程

    https://www.cnblogs.com/ericli-ericli/p/5902270.html (rabbitmq安装)https://www.cnblogs.com/iiwen/p/538 ...

  10. servlet 高级知识之Filter

    Filter叫做拦截器, 对目标资源拦截,拦截HTTP请求和HTTP响应,本质是对url进行拦截. 与serlvet不同的是, Filter的初始化是随着服务器启动而启动. 在Filter接口中定义了 ...