最佳实践【二】从 0 开始,用 flask+mongodb 打造分布式服务器监控平台

2018-12-24 16:00:31 浏览数 (1)

今天我们将编写功能模块,并在代码开始之前对功能模块进行分析,并通过流程图和 UML 类图的绘制将模块功能细化,接着从搭建骨架开始,逐步完成一个模块的编写。

阅读本文大概需要 9.9 分钟。

经过之前的学习 《 Python 系统资源信息获取工具,你用过没?》、《【一】从0开始,用flask mongo打造分布式服务器监控平台》, 召唤师峡谷萌新 已经可以启动一个 Web 页面了,并且已经通过 MongoEngine 定义了一个 ORM。接下来我们应该对每个模块功能进行编写,并且为每个编写好的模块编写视图。

模块编写顺序

思考:在多个模块当中,萌新 应该先完成哪一个模块呢?

我们给需求进行排序,看看他们各自的权重和依赖关系,回顾一下我们需要编写的模块:

  • 数据处理与可视化
  • 信息监控
  • 警报模块
  • 数据存储
  • API 视图

奎因 画了一张图,从这张图上我们就可以看出每个模块的依赖和作用:

首先应该先读取每台服务器的资源,在读取资源的时候检查是否超过阈值,然后主机一次性获取所有服务器资源信息并存入数据库,接着出库计算并将数据处理成可视化图表。所以可以将上面的模块编写顺序做一下调整:

  • 信息监控
  • 数据存储
  • 数据处理与可视化

至于剩下的警报模块和 API 视图,在前三个模块开发过程中用于调试,可以只保留逻辑占位(也就是 python 中常用的 Pass)待前三个模块开发完成后再回头补充警报模块和 API 视图的逻辑即可。

信息监控模块的实现

在 《Python 系统资源信息获取工具,你用过没?》 中,我们已经学会了(非 windows)服务器资源信息的获取,现在我们需要将之前所学的知识转化成为功能模块,也就是在代码中通过类来实现资源信息获取,这样我们就可以通过类的实例化和方法调用的方式获取的所需的数据。

思考:在弄清楚需求、作用之后,我们就可以开始编写代码了吗?

如果你是一名经验丰富的开发者,想必你心中已经有了代码的一个大致结构。但是作为萌新,我们还没能够那么快在脑海中生成结构,所以我们还是需要画图。在代码开始前进行构思、画图可以让萌新们减少错误、同时也减少代码的改动次数,有一定的几率提升,既然现在是编写类,那么我们就来绘制一个 UML 类图吧!

首先,我们应该给类想一个名字,就叫 PresentMomentSystemResource 吧,所以一个空的 UML 图就可以画出来了:

至于里面的方法,我们想一下:

  • 应该有一个 init 方法,这样就可以在类实例化的时候指定一些类变量;
  • 对于 cpu、内存硬盘以及进程的数据,应该有不同的方法进行获取;
  • 考虑到这个类可能会被多次实例化,在多个地方被调用,那么就有可能需要使用单例模式;

所以 UML 图改动一下:

奎因 给大家解释一下这个 UML 类图的含义:

  • 魔术方法 new 来完成类单例模式
  • 魔术方法 init 设定实例化时使用的一些类变量
  • 接下来给每个硬件信息数据定义一个方法,并且的到的结果都以 dict 的数据类型返回

然后我们就可以开始代码的编写了。

对于这一次的项目,我们新建一个文件夹 Monitors,然后再在里面新建一个 python package 名字叫 monitors,接着在项目内新建一个名为 core.py 的文件,结构如下图所示:

并且根据 UML 类图编写类的基础结构:

代码语言:javascript复制
# 崔庆才丨静觅、韦世东丨奎因 邀请你关注微信公众号【进击的Coder】和大佬一起coding 共同进步
class PresentMomentSystemResource:

    def __new__(cls, *args, **kwargs):
        # singleton
        pass

    def __init__(self):
        pass

    def memory_usage(self):
        """当前时刻内存用量信息
        """
       pass

    def cpu_usage(self):
        """ 当前时刻cpu用量信息 """
        pass

    def disks_usage(self):
        """ 当前时刻根目录磁盘用量信息 """
        pass

    def processes_id(self):
        """ 筛选当前时刻关键字相关的pid列表及数量 """
        pass

搭好类的骨架之后,我们就来为每个方法编写实际的代码。

首先,我们用魔术方法 new 将类变成单例模式,所以 new 方法部分的代码改为:

代码语言:javascript复制
    def __new__(cls, *args, **kwargs):
        # singleton
        if not hasattr(cls, '_instance'):
            cls._instance = super(PresentMomentSystemResource, cls).__new__(cls, *args, **kwargs)
        return cls._instance

作为老司机,应该是可以在编写完代码开始下一个方法的编写。但是现在我们是 召唤师峡谷的萌新 ,对不对?那 萌新 肯定是不知道自己写的代码是否正确,所以我们需要编写测试代码,只要确定类实例化后确实只有一个实例对象,那么就可以进行下一步了。

所以我们还需要新建一个测试文件 testing.py,并在里面编写:

代码语言:javascript复制
from monitors.core import PresentMomentSystemResource


if __name__ == "__main__":
    p1 = PresentMomentSystemResource()
    p2 = PresentMomentSystemResource()
    print(p1, p2)

运行 testing.py 文件,我们看一看控制台的输出内容:

代码语言:javascript复制
<monitors.core.PresentMomentSystemResource object at 0x7fb0862a7128>
<monitors.core.PresentMomentSystemResource object at 0x7fb0862a7128>

Process finished with exit code 0

由输出结果得知,p1 和 p2 是同一个实例对象,说明 new 方法实现单例模式奏效了。

接着我们来编写下一个方法。在之前的文章 《Python 系统资源信息获取工具,你用过没?》 中提到过可以获取系统资源信息的 psutil ,并且知道获取 cpu 信息、内存信息以及磁盘信息所用的方法,所以我们可以在 init 方法中将这几个方法初始化:

代码语言:javascript复制
import psutil

    def __init__(self):
        self.memory = psutil.virtual_memory()
        self.cpu = psutil.cpu_times()
        self.disk = psutil.disk_usage("/")

其中关于磁盘部分的信息,我们制定获取 "/" 盘符信息即可,其他挂载磁盘信息并没有那么重要。

获取到的数值单位为k,但是通常情况下我们使用的单位是 M 或者 G ,这里还需要设定两个单位:

代码语言:javascript复制
from math import pow

    def __init__(self):
        self.memory = psutil.virtual_memory()
        self.cpu = psutil.cpu_times()
        self.disk = psutil.disk_usage("/")
        self.mb = pow(1024, 2)
        self.gb = pow(1024, 3)

pow 方法是 python 内置库中用于计算幂的方法,可以计算某个数值 m 的 n 次幂,也就是可以将它理解为:

pow(m, n) = m 的n 次方。

到了真正编写每个硬件资源信息代码的时候了,我们首先来看看内存。内存需要的信息为内存总量、已使用量、剩余量及剩余百分比。我们从之前的文章可以知道,通过上面定义的 self.memory 就可以直接取到部分内存的用量信息:

代码语言:javascript复制
    def memory_usage(self):
        """当前时刻内存用量信息
        """
        total = self.memory.tota
        used = self.memory.used

psutil 并没有给我们提供直接获取余量和余量百分比,所以我们在将数值单位计算完毕后,可以用数学运算计算出余量和余量百分比,此处 memory_usage 代码改为:

代码语言:javascript复制
    def memory_usage(self):
        """当前时刻内存用量信息
        """
        total = self.memory.total/self.gb
        used = self.memory.used/self.gb
        free = total - used
        percent = round(free/total, 2)
        return total, used, free, percent

然后到 testing.py 中测试一下:

代码语言:javascript复制
from monitors.core import PresentMomentSystemResource


if __name__ == "__main__":
    p1 = PresentMomentSystemResource()
    res = p1.memory_usage()
    print(res)

运行后得到的输出结果为:

代码语言:javascript复制
(7.676643371582031, 1.7717132568359375, 5.904930114746094, 0.77)

跟系统自带的系统资源监控做个比对:

总体上是吻合的,说明这种方法取值和计算是没有问题的。

然后按照之前的文章和这样的方法,编写其他几个硬件的代码,最后整个 core.py 文件的代码为:

代码语言:javascript复制
import psutil
from math import pow
from functools import reduce


class PresentMomentSystemResource:

    def __new__(cls, *args, **kwargs):
        # singleton
        if not hasattr(cls, '_instance'):
            cls._instance = super(PresentMomentSystemResource, cls).__new__(cls, *args, **kwargs)
        return cls._instance

    def __init__(self):
        self.memory = psutil.virtual_memory()
        self.cpu = psutil.cpu_times()
        self.disk = psutil.disk_usage("/")
        self.mb = pow(1024, 2)
        self.gb = pow(1024, 3)

    @property
    def memory_usage(self):
        """当前时刻内存用量信息
        """
        total = self.memory.total/self.gb
        used = self.memory.used/self.gb
        free = total - used
        percent = round(free/total, 2)
        buffers = self.memory.buffers/self.gb
        cached = self.memory.cached/self.gb
        total, used, free, buffers, cached = map(lambda x: round(x, 2), [total, used, free, buffers, cached])
        return {"total": total, "used": used, "free": free, "free_percent": percent, "buffers": buffers, "cached": cached}

    @property
    def cpu_usage(self):
        """ 当前时刻cpu用量信息 """
        count = psutil.cpu_count()
        logical_count = psutil.cpu_count(logical=True)
        percent = psutil.cpu_percent(interval=1)
        return {"count": count, "logical_count": logical_count, "percent": percent}

    @property
    def disks_usage(self):
        """ 当前时刻根目录磁盘用量信息 """
        total, used, free = map(lambda x: round(x/self.gb), self.disk[:3])
        percent = self.disk.percent
        return {"total": total, "used": used, "free": free, "free_percent": percent}

    def processes_id(self, keywords=['python']):
        """ 筛选当前时刻关键字相关的pid列表及数量 """
        attrs = psutil.process_iter(attrs=['pid', 'name'])
        pid = [[p.info for p in attrs if keyword in p.info['name']] for keyword in keywords]
        pid_number = reduce(lambda x, y: x y, [len(p) for p in pid])
        return {"pid": pid, "type_number": len(pid), "pid_number": pid_number}

这里着重说明一下:由于我们的监控是针对爬虫与服务器资源关系的监控,所以 processes_id 方法限定进程 id 的获取仅获取 Python 相关的进程。

flask 视图编写

有了信息获取,那么我们来试试,如何在 flask 中使用这个类。

首先,我们在 monitors 的 init.py 文件中设置好 flask

代码语言:javascript复制
from flask import Flask
from flask.ext.restful import Resource, Api

app = Flask(__name__)
api = Api(app)
resource = Resource

然后新建一个 start.py 文件,并像之前的文章一样将 flask 的骨架搭好

(在此之前请在电脑的 python 环境中安装 flask、flask-restful):

代码语言:javascript复制
# start.py
from monitors import app, api, resource

class PresentMomentSystemResourceView(resource):
    """ 当前时刻系统资源占用信息视图 """
    def __init__(self):
        pass

    def get(self):

        return {"status": "success", "message": "this is flask view"}

api.add_resource(PresentMomentSystemResourceView, '/')

if __name__ == '__main__':
    app.run(debug=False)

接着运行 start.py 文件,得到输出:

代码语言:javascript复制
  from flask.ext.restful import Resource, Api
 * Running on http://127.0.0.1:5000/ (Press CTRL C to quit)

说明我们可以通过浏览器访问本机的 5000 端口:

这里返回的内容就是刚才编写的试图时 return 的内容,说明 flask 的视图骨架搭好了。下一步则是将系统资源信息获取类与视图类相关联,将 start .py 的代码改为:

代码语言:javascript复制
# start.py
from monitors import app, api, resource
from monitors.core import PresentMomentSystemResource


class PresentMomentSystemResourceView(resource):
    """ 当前时刻系统资源占用信息视图 """
    def __init__(self):
        self.sr = PresentMomentSystemResource()

    def get(self):
        memory = self.sr.memory_usage
        disks = self.sr.disks_usage
        cpu = self.sr.cpu_usage
        pid = self.sr.processes_id()
        return {"cpu": cpu, "memory": memory, "disk": disks, "pid": pid}

在运行 start.py 文件后,我们刷新刚才浏览器的页面,得到一串数据(火狐浏览器自动格式化,其他浏览器的数据显示可能没有那么整齐):

这些数据就是我们在视图类中 return 的 cpu、内存、磁盘以及进程信息数据。

至此,我们 德玛西亚阵营 的服务器信息获取模块就编写完成,下一次我们将会编写数据存储以及其他的模块。


小结

今天你在 召唤师峡谷 学习到的并不止是模块的编写和 flask 的基本代码编写, 更重要的是学会了如何分析模块的构成、通过绘制流程图和 UML 类图对自己所编写的模块进行细化,最终实现了了一个独立的模块。作为 召唤师峡谷的萌新 ,你体会到 奎因 今天的用意了吗?

0 人点赞