Python 基于Python及zookeeper实现简单分布式任务调度系统设计思路及核心代码实现
基于Python及zookeeper实现简单分布式任务调度系统设计思路及核心代码实现
by:授客 QQ:1033553122
测试环境
功能需求
实现思路
代码实践(关键技术点实现)
代码模块组织结构
配置文件解析
MyTCPServer.py
MyTCPClient.py
appClient.py
loadAgent.py
运行效果 13
测试环境
Win7 64位
Linux 64位
Python 3.3.4
kazoo-2.6.1-py2.py3-none-any.whl(windows)
kazoo-2.6.1.tar.gz (linux)
https://pypi.org/project/kazoo/#files
zookeeper-3.4.13.tar.gz
下载地址1:
http://zookeeper.apache.org/releases.html#download
https://www.apache.org/dyn/closer.cgi/zookeeper/
https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/
功能需求
把不同的负载主机,注册为zookeeper的节点,其它应用模块请求zookeeper获取相关节点信息(服务器ip,端口号,服务器任务执行状态),通过服务器任务状态选择没有运行指定任务的服务器执行相关任务。
针对以上需求,做一个技术预研,核心代码实现
实现思路
负载服务器启动时,初始化zookeeper客户端,创建tcp服务器,注册节点信息到zookeeper服务器(信息包含tcp服务器ip,端口,负载服务器任务执行状态),然后定时检测负载服务器任务执行状态(通过检测某个进程的名称是否存在进行判断),其它应用模块通过zookeeper获取节点信息后,通过tcp socket通信,向负载服务器发送执行命令,然后负载服务器根据这些命令进行不同的处理。
代码实践(关键技术点实现)
代码模块组织结构
配置文件解析
conf/agent.conf
[AGENT]
interval = 5
proc = sftp-server
[README]
interval = 更新服务器节点信息频率(单位 秒
proc = 需要检测的进程名称(程序通过查找对应进程名称来判断负载程序是否还在运行,从而判断服务器状态
conf/tcpserver.conf
[TCPSERVER]
host=10.202.7.165
port = 8000
[README]
host = tcp服务器主机地址
port = tcp服务器监听端口
conf/zookeeper.conf
[ZOOKEEPER]
hosts = 10.118.52.26:2181
nodeParentPath=/rootNode
[README]
hosts = zookeeper地址,如果是集群地址,即有多个,用英文逗号分隔
nodeParentPath=负载机节点所在父级路径
MyTCPServer.py
#!/usr/bin/env python 3.4.0
#-*- encoding:utf-8 -*-
__author__ = 'shouke'
import socketserver
from log import logger
class MyTCPHandler(socketserver.BaseRequestHandler):
"""
The RequestHandler class for our server.
It is instantiated once per connection to the server, and must
override the handle() method to implement communication to the
client.
"""
def handle(self):
while True:
# self.request is the TCP socket connected to the client
self.data = self.request.recv(1024).decode('utf-8').strip()
logger.info('receive data from client[host:%s port:%s]:%s' % (self.client_address[0], self.client_address[1], self.data))
if self.data == 'bye':
self.request.sendall(bytes('bye', encoding='utf-8'))
self.request.close()
break
else:
self.request.sendall(self.data.upper().encode('utf-8'))
class MyTCPServer:
def __init__(self, host, port):
try:
self.host = host
self.port = port
# Create the server, binding to self.host on port 'self.port'
self.server = socketserver.TCPServer((self.host, self.port), MyTCPHandler)
except Exception as e:
logger.error('初始化TCPServer失败:%s' % e)
exit(1)
def start(self):
# Activate the server; this will keep running until you interrupt the program with Ctrl-C
self.server.serve_forever()
MyTCPClient.py
#!/usr/bin/env python 3.4.0
#-*- encoding:utf-8 -*-
__author__ = 'shouke'
import socket
import configparser
import time
from log import logger
if __name__ == '__main__':
if_sock_connected = False
try:
config_parser = configparser.ConfigParser()
config_parser.read('./conf/tcpserver.conf', encoding='utf-8-sig')
host = config_parser.get('TCPSERVER', 'host')
port = int(config_parser.get('TCPSERVER', 'port'))
# Create a socket (SOCK_STREAM means a TCP socket)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Connect to server and send data
sock.connect((host, port))
if_sock_connected = True # 标记socket是否已连接
i = 0
while i < 10000:
if i == 1000:
sock.sendall(bytes('bye\n', "utf-8"))
else:
sock.sendall(bytes('hello world with tcp\n', "utf-8"))
# Receive data from the server
received = str(sock.recv(1024), "utf-8")
logger.info('receive data from server:%s' % received)
if received == 'bye':
break
time.sleep(5)
i += 1
except Exception as e:
logger.error('程序运行出错:%s' % e)
finally:
if if_sock_connected:
sock.close()
appClient.py
#!/usr/bin/env python
#-*- encoding:utf-8 -*-
__author__ = 'shouke'
import time
from log import logger
from kazoo.client import KazooClient
from kazoo.client import KazooState
def my_listener(state):
if state == KazooState.LOST:
logger.info('LOST')
# Register somewhere that the session was lost
elif state == KazooState.SUSPENDED:
logger.info('SUSPENDED')
# Handle being disconnected from Zookeeper
else:
logger.info('CONNECTED')
# Handle being connected/reconnected to Zookeeper
def my_event_listener(event):
logger.info(event)
zk_client = KazooClient(hosts='10.118.52.26:2181')
zk_client.add_listener(my_listener)
zk_client.start()
node_path = '/rootNode'
sub_node = 'loaderAgent102027165'
children = zk_client.get_children(node_path, watch=my_event_listener)
logger.info('there are %s children with names %s' % (len(children), children))
@zk_client.ChildrenWatch(node_path)
def watch_children(children):
logger.info("Children are now: %s" % children)
@zk_client.DataWatch("%s/%s" % (node_path, sub_node))
def watch_node(data, state):
"""监视节点数据是否变化"""
if state:
logger.info('Version:%s, data:%s' % (state.version, data))
i = 0
while i < 1000:
time.sleep(5)
children = zk_client.get_children(node_path, watch=my_event_listener)
logger.info('there are %s children with names %s' % (len(children), children))
i += 1
zk_client.stop()
zk_client.close()
loadAgent.py
#!/usr/bin/env python 3.4.0
#-*- encoding:utf-8 -*-
__author__ = 'shouke'
import time
import threading
import configparser
import json
import subprocess
from kazoo.client import KazooClient
from kazoo.client import KazooState
from log import logger
from myTCPServer import MyTCPServer
# 全局变量
zk_conn_stat = 0 # zookeeper连接状态 1-LOST 2-SUSPENDED 3-CONNECTED/RECONNECTED
registry_status = 0 # 服务器节点在zookeeper的注册状态 0-未注册、正在注册, 1-已注册
def restart_zk_client():
'''重启zookeeper会话'''
global zk_client
global zk_conn_stat
try:
zk_client.restart()
registry_zookeeper()
except Exception as e:
logger.error('重启zookeeper客户端异常:%s' % e)
def zk_conn_listener(state):
'''zookeeper连接状态监听器'''
global zk_conn_stat
global registry_status
if state == KazooState.LOST:
logger.warn('zookeeper connection lost')
zk_conn_stat = 1
registry_status = 0 # 重置是否完成注册
# Register somewhere that the session was lost
thread = threading.Thread(target=restart_zk_client)
thread.start()
elif state == KazooState.SUSPENDED:
logger.warn('zookeeper connection dicconnected')
zk_conn_stat = 2
# Handle being disconnected from Zookeeper
else:
zk_conn_stat = 3
logger.info('zookeeper connection cconnected/reconnected')
# Handle being connected/reconnected to Zookeeper
def registry_zookeeper():
'''注册节点信息到zookeeper'''
global node_parent_path
global host
global port
global zk_client
global zk_conn_stat
global registry_status
try:
while zk_conn_stat != 3: # 如果zookeeper客户端没连上zookeeper,则先不让注册
continue
logger.info('正在注册负载机到zookeeper...')
zk_client.ensure_path(node_parent_path)
loader_agent_info = '{"host":"%s", "port":%s, "status":"idle"}' % (host, port)
if not zk_client.exists('%s/loaderAgent%s' % (node_parent_path, host.replace('.', ''))):
zk_client.create('%s/loaderAgent%s' % (node_parent_path, host.replace('.', '')), loader_agent_info.encode('utf-8'), ephemeral=True, sequence=False)
# children = zk_client.get_children(node_parent_path)
# logger.info('there are %s children with names: %s' % (len(children), children))
# for child in children:
# logger.info(child)
# data, stat = zk_client.get('%s/%s' % (node_parent_path, child))
# logger.info(data)
registry_status = 1 # 完成注册
logger.info('注册负载机到zookeeper成功')
return True
except Exception as e:
logger.error('注册负载机到zookeeper失败:%s' % e)
return False
def start_tcpserver(tcpserver):
'''启动tcp服务器'''
tcpserver.start()
def get_server_status(proc_name):
'''通过给定进程名称获取服务器状态'''
with subprocess.Popen('ps -e | grep "%s"' % proc_name, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, universal_newlines=True) as proc:
try:
outs, errs = proc.communicate(timeout=30)
outs = outs.strip()
if outs.find(proc_name) != -1:
# logger.info('获取负载机状态成功 %s' % outs)
server_status = 'busy'
elif outs == '':
# logger.info('获取负载机状态成功')
server_status = 'idle'
else:
logger.error('获取负载机状态失败:%s' % errs)
server_status = 'unknow'
except Exception as e:
proc.kill()
logger.error('获取负载机状态失败:%s' % e)
server_status = 'unknow'
return server_status
def update_server_status(interval, proc_name):
'''定时检测并更新服务器状态:根据进程名称是否存在来判断服务器状态,如果存在则表示服务器被占用,标记服务器状态为busy,否则标记服务器状态为 idle
如果根据进程名,检查进程失败,则标记服务器状态为unknow'''
global node_parent_path
global host
global port
while True:
second_for_localtime1 = time.mktime(time.localtime()) # UTC时间(秒)
if zk_conn_stat != 3: # 如果zookeeper客户端还没连上zookeeper,则不让进行后续操作
continue
if registry_status != 1: # 如果zookeeper客户端已连上zookeeper,但是还没注册节点到zookeeper,则不让进行后续操作
continue
server_status = get_server_status(proc_name)
loader_agent_info = '{"host":"%s", "port":%s, "status":"%s"}' % (host, port, server_status)
'''
这里为啥要加这个判断:zookeeper删除临时节点存在延迟,如果zookeeper客户端主动关闭后快速重启并注册节点信息 这个过程耗时比较短,可能注册完节点信息时,zookeeper
还没来得及删除重启之前创建的临时节点,而本次创建的临时节点路径和重启前的一模一样,这样导致的结果是,zookeeper接下来的删除操作,会把重启后注册的节点也删除
'''
if zk_client.exists('%s/loaderAgent%s' % (node_parent_path, host.replace('.', ''))):
zk_client.set('%s/loaderAgent%s' % (node_parent_path, host.replace('.', '')), loader_agent_info.encode('utf-8'))
else:
registry_zookeeper()
second_for_localtime2 = time.mktime(time.localtime()) # UTC时间(秒)
time_difference = second_for_localtime2 - second_for_localtime1
if time_difference < interval:
time.sleep(interval - time_difference)
if __name__ == '__main__':
logger.info('正在启动代理...')
try:
logger.info('正在读取zookeeper配置...')
config_parser = configparser.ConfigParser()
config_parser.read('./conf/zookeeper.conf', encoding='utf-8-sig')
zk_hosts = config_parser.get('ZOOKEEPER', 'hosts').replace(',', ',').strip()
node_parent_path = config_parser.get('ZOOKEEPER', 'nodeParentPath').replace(',', ',').strip()
logger.info('正在构建并启动zookeeper客户端...')
zk_client = KazooClient(hosts=zk_hosts)
zk_client.add_listener(zk_conn_listener)
zk_client.start()
except Exception as e:
logger.error('初始化zookeeper客户端失败: %s' % e)
exit(1)
try:
config_parser.clear()
config_parser.read('./conf/tcpserver.conf', encoding='utf-8-sig')
host = config_parser.get('TCPSERVER', 'host')
port = int(config_parser.get('TCPSERVER', 'port'))
tcp_server = MyTCPServer(host, port)
thread = threading.Thread(target=start_tcpserver, args=(tcp_server,))
thread.start()
except Exception as e:
logger.error('TCPServer启动失败:%s,请检查配置/conf/tcpserver.conf是否正确' % e)
exit(1)
try:
# 注册到zookeeper
registry_zookeeper()
config_parser.clear()
config_parser.read('./conf/agent.conf', encoding='utf-8-sig')
interval = int(config_parser.get('AGENT', 'interval'))
proc = config_parser.get('AGENT', 'proc').strip()
# 定时更新服务器节点繁忙状态
update_server_status(interval, proc)
except Exception as e:
logger.error('zk_client运行失败:%s,请检查配置/conf/agent.conf是否正确' % e)
exit(1)
运行效果
Python 基于Python及zookeeper实现简单分布式任务调度系统设计思路及核心代码实现的更多相关文章
- Python 基于Python实现的ssh兼sftp客户端(上)
基于Python实现的ssh兼sftp客户端 by:授客 QQ:1033553122 实现功能 实现ssh客户端兼ftp客户端:实现远程连接,执行linux命令,上传下载文件 测试环境 Win7 ...
- SpringBoot + Dubbo + zookeeper 搭建简单分布式服务
SpringBoot + Dubbo + zookeeper 搭建简单分布式服务 详细操作及源码见: https://github.com/BillyYangOne/dubbo-springboot
- 通过游戏学python 3.6 第一季 第九章 实例项目 猜数字游戏--核心代码--猜测次数--随机函数和屏蔽错误代码--优化代码及注释--简单账号密码登陆--账号的注册查询和密码的找回修改--锁定账号--锁定次数--菜单功能'menufile
通过游戏学python 3.6 第一季 第九章 实例项目 猜数字游戏--核心代码--猜测次数--随机函数和屏蔽错误代码--优化代码及注释--简单账号密码登陆--账号的注册查询和密码的找回修改--锁 ...
- 通过游戏学python 3.6 第一季 第八章 实例项目 猜数字游戏--核心代码--猜测次数--随机函数和屏蔽错误代码--优化代码及注释--简单账号密码登陆--账号的注册查询和密码的找回修改--锁定账号--锁定次数
通过游戏学python 3.6 第一季 第八章 实例项目 猜数字游戏--核心代码--猜测次数--随机函数和屏蔽错误代码--优化代码及注释--简单账号密码登陆--账号的注册查询和密码的找回修改--锁定账 ...
- 通过游戏学python 3.6 第一季 第二章 实例项目 猜数字游戏--核心代码--猜测次数 可复制直接使用 娱乐 可封装 函数
猜数字游戏--核心代码--猜测次数 #猜数字--核心代码--猜测次数 number=33 amount=3 count=0 while count<=amount: conversion ...
- 基于nginx+xxl-job+springboot高可用分布式任务调度系统
技术.原理讲解: <分布式任务调度平台XXL-JOB--源码解析一:项目介绍> <分布式任务调度平台XXL-JOB--源码解析二:基于docker搭建admin调度中心和execut ...
- Python 基于python操纵zookeeper介绍
基于python操纵zookeeper介绍 by:授客 QQ:1033553122 测试环境 Win7 64位 Python 3.3.4 kazoo-2.6.1-py2.py3-none-any.w ...
- Python 基于Python结合pykafka实现kafka生产及消费速率&主题分区偏移实时监控
基于Python结合pykafka实现kafka生产及消费速率&主题分区偏移实时监控 By: 授客 QQ:1033553122 1.测试环境 python 3.4 zookeeper- ...
- Python 基于python实现单例模式
基于python实现单例模式 by:授客 QQ:1033553122 概念 简单说,单例模式(也叫单件模式)的作用就是保证在整个应用程序的生命周期中,任何一个时刻,单例类的实例都只存在一个(当然也 ...
随机推荐
- Ubuntu命令用法详解——curl命令
简介: cURL(CommandLine Uniform Resource Locator)是一个利用URL语法在命令行下工作的文件传输工具,1997年首次发行.它支持文件上传和下载,所以是综合传输工 ...
- laravel中如何利用反射实现依赖注入
依赖注入 在一个类中经常会依赖于其他的对象,先看一下经典的写法 class Foo { public $bar; public function __construct() { $this->b ...
- cesium 之三维场景展示篇(附源码下载)
前言 cesium 官网的api文档介绍地址cesium官网api,里面详细的介绍 cesium 各个类的介绍,还有就是在线例子:cesium 官网在线例子,这个也是学习 cesium 的好素材. 内 ...
- How to build ffmpeg with hardware accelerated codecs for Android x86
In order to build a complete ffmpeg with hardware acceleration for Intel platform (XXX lake + Atom), ...
- Django【部署】uwsgi+nginx
uwsgi 遵循wsgi协议的web服务器 uwsgi的安装 pip install uwsgi uwsgi的配置 项目部署时,需要把settings.py文件夹下的: DEBUG = FALSE A ...
- MySQL性能优化总结___本文乃《MySQL性能调优与架构设计》读书笔记!
一.MySQL的主要适用场景 1.Web网站系统 2.日志记录系统 3.数据仓库系统 4.嵌入式系统 二.MySQL架构图: 三.MySQL存储引擎概述 1)MyISAM存储引擎 MyISAM存储引擎 ...
- 模式识别笔记4-集成学习之AdaBoost
目前集成学习(Ensemble Learning) 分为两类: 个体学习器间存在强依赖关系.必须串行化生成的序列化方法:Boosting 个体学习器间不存在强依赖关系,可同时生成的并行化方法:Bagg ...
- Mysql查询的一些操作(查表名,查字段名,查当月,查一周,查当天)
查询数据库中所有表名 select table_name from information_schema.tables where table_schema='tools' and table_typ ...
- Java数据结构和算法 - 递归
三角数字 Q: 什么是三角数字? A: 据说一群在毕达哥拉斯领导下工作的古希腊的数学家,发现了在数学序列1,3,6,10,15,21,……中有一种奇特的联系.这个数列中的第N项是由第N-1项加N得到的 ...
- 警惕挂着开源的招牌到处坑蒙拐骗的垃圾项目,比如iBase4J
开源界,本是技术爱好者百花齐放.各显其能的地方.但是,不管什么好东西,到了这块奇葩的土地都能变了味.现在的开源界,真的是鱼龙混杂,有些开源软件,不知道是噱头喊得高,还是star刷得好,竟能凭借一身垃圾 ...