• CDH集群监控方案架构与实现(一)基础监控

    背景

    大数据集群的维护本身是充满挑战的,庞大的数据量、复杂的运算逻辑、关联的大数据组件、一个企业级的集群往往有PB 级的数据、成百上千和各式运算任务 在一套集群上运行。  所以运维如果想不是很被动的话,就必须做好各式监控,预防风险、发现风险、分析问题、进而针对性的处理问题。

    因为大数据集群一旦出问题,往往就已经很难处理了 ,所以监控要前置,有趋势预测、有风险评估、才行。

    针对这些,我们要从多个维度入手对大数据集群进行基础监控:

    如下,我们对整个集群的基础监控主要分为三大维度:

    1. 底层服务监控:服务器底层的CPU、内存、IO、磁盘 等;
    2. Hadoop性能监控:各式组件各维度性能监控
    3. Runtime 监控:自定义脚本轮训服务;

    11

    底层服务监控借助 Zabbix,这个很简单,不再详赘了。

    这里主要说下第二个维度: 组件性能监控。

    CDH 组件性能监控

    对 CDH 组件的性能监控,如上图,我们借助 Prometheus + AlertManager + Grafana 的架构方式。

    • 写一个 exporter ,从 Cloudera Manager 的 API 中取出各式组件的 Metrics 。
    • 借助 Alertmanager 对需要的 metrics 进行告警。
    • 写一个 钉钉告警API 供 Alertmanager 调用。
    • 将 Prometheus data source 接入 Grafana 做报表展示。

    这里具体的代码与实现细节,不展开了,否则太多,主要是将大概的思路和实现方式以及官网地址交待清楚:

    一、明确要获取的 Metrics

    首先我们要明确有哪些Metrics 可以供我们获取,或者说我们从哪里挑到 可以获取的 Metrics 列表。 这一步我们从 Cloudera Manager 官网可以看到:

    https://docs.cloudera.com/documentation/enterprise/5-15-x/topics/cm_metrics.html

    b

    这里有所有的 CM 组件相关 Metrics,我们随便点一个进去,可以看到 组件的 Metrics 详细信息,有描述,也有单位等:

    1

    如果我们对这些 Metrics 还不是很懂的话,其实可以借助 Cloudera Manager 的图表生成器:

    1

    如上,其实这些 Metric 从 CM 的图标生成器也可以查到,并且有自动匹配功能和中文释义,非常方便。

    二、Cloudera Manager 的 API 接口交互

    与 Cloudera Manager 的接口交互当然也是看官方文档:http://cloudera.github.io/cm_api/apidocs/v19/

    1

    API 对应的官方文档里面有大量的接口调用方式,包括一些动作、状态、Metrics 获取等。这里我们暂时只关注 Metrics 获取,我们会发现,官方文档中所有的 Metrics 获取都建议我们走时序型接口:

    2

    如上,所有关于角色的 Metrics 建议我们走 时序型 API 去获取:

    3

    最简单的一个 Python 代码调用示例如下:

    得到的结果如下:

    如上,就可以获取到对应 Metrics 相关值 。

    三、自己写 Exporter 从 Cloudera Manager 获取 Metrics 数据

    这里用 Python 写一个 Exporter ,获取数据,因为涉及到的 Metrics 很多,所以采取多线程并发的方式去获取。

    这里主要借助 Python的这个库:https://github.com/prometheus/client_python#instrumenting

    1

    这个 Python 包里有大量 prometheus exporter client 方式,可以参考使用。

    这块的代码就不再这里详细赘述了,总体来说,开 30个线程并发,取 几百个 Item,最终入Prometheus 的时候大概 几万个 Metric ,总共需要约 8 s 多。

    四、搭建配置 AlertManager ,并实现自定义钉钉告警接口

    数据入到 Prometheus 之后,我们当然要去设置针对一些Metrics 的告警阈值,这里借助和Prometheus 的告警插件 AlertManager,AlertManager 的安装很简单,这里不再详赘,之前的 Prometheus初探 一文中已经说过,这里主要说下配置相关:

    关于相关配置可以参考官方文档:https://prometheus.io/docs/alerting/configuration/

    如上,我们在 AlertManager 中加了这些配置,它的配置总体来说主要是包括 routes 告警转发路由,receiver 告警接收器,inhibit_rule 告警收敛规则 等。

    如上,我们默认将报警都发给了我们自己实现的一个  http://10.25.x.x:5000/ding_alert  webhook,其实是用 Python 写的一个钉钉告警接收器。

    五、Flask 钉钉告警网关

    正如上面所说,我们自己实现一个简单的钉钉告警接收器,用 Flask 实现,可以让 AlertManager 通过 5000 端口调用发送钉钉告警,这里主要是要注意处理发送与接收格式:

    相关代码简要如下:

    六、Prometheus 中配置 AlertManager ,并添加配置告警项

    前面的准备好之后,我们还需要配置 Prometheus 告诉它哪些需要告警,并且把AlertManager 加进去:

    先是 Prometheus 的主配置文件:

    监控项我们放在单独的 rules 目录的文件下:

    七、Grafana 接入 Prometheus

    其实前六步昨晚之后,数据就已经入到我们的 Prometheus 了,可以在Prometheus 上查到:

    1

    如上图,我们点击 Graph ,然后输入 Metrics 名称或正则匹配就可以查到,点击 图片中间的 Graph还可以看到图形。

    当然,我们配置的 Alert 也是可以在这里看到的:

    2

    甚至我们可以像 Zabbix一样看到告警相关的 Reports:

    1

    当然最重要的是也能收到相关的钉钉告警:

    4

    但是单纯的收到告警信息不行,我们需要一个炫酷的,有维度的监控展示。  Grafana 是很好的选择,我们只需要将 Prometheus 作为 Datasource 纳入 Grafana,即可构建丰富的报表展示:

    4

     

    总结

    至此,我们就完成了我们的基础监控部分。 可以随着各式各样的需求逐步完善收集 Metrics 即可。 既有告警信息、又有丰富的图形展示,而且很灵活,方便调配。

    但这只是基础监控,距离我们掌控运维好集群还有很大的距离,后续还要继续补充几个维度:

    • 元数据监控。
    • 业务运行日志与任务状态监控。
    • 定时统计报表发送等。

    其实每个维度想要做好都很难,会在后续的时间过程中持续更新此系列博客。

     

     

  • Kudu 升级引发的磁盘扩容将MBR分区修改为GPT

    背景

    一夜之间,Kudu 集群的存储达到了瓶颈,6台 Kudu 服务器的数据存储分区基本都快满了:

    10

    所以需要扩容Kudu存储的这个磁盘,但没想到是个苦逼的过程,计划中的步骤是这样的:

    1. 停止 Kudu 服务,关掉 ECS 服务器。
    2. 阿里云高效云盘做快照,以防万一。
    3. 阿里云高效云盘扩容至 6T 。
    4. 重新启动 ECS 服务器,fdisk 系列命令重新生成分区,并使用 xfs_growfs 命令提升磁盘分区空间。

    但是在真正做的过程中,发现一个问题:

    目前这个分区是 2T 的,并且用的是 MBR 分区方式,MBR 分区最大就支持2T,GPT分区方式才支持更大的分区。

    经过思考得出如下两种解决方案,过程记录如下:

    方案一(已放弃)
    1. 新挂载一个GPT格式的磁盘分区
    2. 然后将数据目录拷贝到新的分区
    3. 修改kudu的目录参数,在新的分区启动存储

    此方式最大的问题就是在实际操作的时候,发现拷贝慢到超乎我的想象,试了好多办法:cp、mv、dd 都不行,2T 的数据拷完大概需要十几个小时,我分析应该是小文件太多的缘故,导致不管用哪种方式,都需要很长时间,测试了半天以后,最终放弃了。

    方案二: 修改已有的 MBR 分区格式为 GPT

    首先不得不说这种操作方式,真的很危险,不到万不得已不要使用,虽然我这次成功了。但是在阿里云的官方文途径是获取不到操作步骤的,它不建议这么做,自然不会给你提供文档说明,它只是建议我们用方案一的:

    11

    但是方案一对我们来说成本太高了,我们继续探索修改分区方式。

    首先,在阿里云扩容此分区到 6T,过程可以参考:阿里云磁盘扩容概述

    然后,按照网上这篇文档进行磁盘分区方式修改和扩容:Linux下数据无损动态修改MBR分区表格式为GPT

     

    扩容完毕。

     

     

     

  • Flask explorer FAQ

    背景

    此博文意在记录Flask探索过程中遇到的一些问题,及解决。

    一、Flask_sqlalchemy 报错ModuleNotFoundError: No module named ‘MySQLdb’

    原因:

    在 python3  中取消了MySQLdb的数据库连接驱动方式,转而替换成了   pymysql 。而 python2 连接数据驱动可以用  pymysql、MySQLdb 两种。sqlalchemy 默认使用 MySQLdb 连接数据库,所以如果你用的是Python3 作为Flask 项目的代码版本,则会遇到这个问题。

    解决:

    二、abort 函数 触发 error_handle 时报参数错误

    问题现象:

    我在API接口的 errors.py 模块中定义了蓝图对应的错误handle,如下:

    所以只要在 view.py 接口函数中,用abort函数则可以抛出对应的错误信息:

    但是运行时却报如下错误:

    TypeError: Object of type BadRequest is not JSON serializable

    问题的原因:

    经过排查,发现是 message 参数类型的缘故,无法被 jsonify 转换为 json:

    所以 message 是flask 异常类的对象,不能被直接转换为 json 格式中的一环。

    问题解决:

    将此对象进行类型强转,转换为 str 类型,如下:

    然后再次请求,结果如下:

     

     

     

  • SQL优化案例(二)高并发下的简单SQL占尽CPU资源

    背景

    线上数据库突然从某一刻开始CPU资源告警,如下:

    a

    和业务核对上线发布情况和系统调整后,发现可能不是由上线代码改动引起的。

    登录线上服务器后,执行  show processlist 发现比平时的连接数要高很多,而且很多都在执行这一条SQL:

    a

    开始其实我并没有怀疑这条SQL,因为它确实很简单,没有 order by ,也没有 group by。 所以第一时间是怀疑有大事务或者死锁发生,但是经过查看发现并不是:

    紧接着我怀疑是不是有其他的大SQL 与 这个高并发 SQL 有冲突,于是翻找了一下,发现是有两个SQL比较慢,并且耗费资源,但是量不大,应该难以这么快降数据库拖垮。

    于是还是将目标瞄准了这个简单SQL,下面是它的一些基本信息:

    表结构与索引如下:

    表行数如下:

    SQL 的执行计划如下:

    在主库上执行这条SQL只需要0.22s(我们的主库是读写分离,所以当时没有问题,可以用来测试):

    profile 分析结果如下:

    解决

    其实也是因为运气好,出问题时RDS实例没有各种业务模块的SQL在并发进行,所以我在管理控制台,将所有的读请求量转到一台空闲的备份RDS上。 发现这台新的RDS又立马被这条SQL夯满了,并且因为没有CPU资源所以很多查询都被阻塞的很慢 。

    值得怀疑的有两点:

    1、就是profile 中 Sending data 环节的CPU资源占用还是有点的多的,虽然是0.17 左右,但是成百上千个同时过来的话也不算小。

    2、就是Explain结果中的 rows 有18w行,而 filtered 字段值只有1.11,也就意味着这条SQL每执行一次要扫描 18w行,但是还要进行过滤,过滤的比率高达 99% 左右,才返回。

    从上面的分析结果来看,所以做出如下修改:

    然后再看执行计划和profile:

    如上,加的索引立马被用到了,扫描行数变成了 2406 ,filtered 变成了 5%,再看下Profile:

    如上,变化果然很明显,Sending Data 环节的CPU消耗立马减少了好多。

    然后加了此索引后,线上也立马恢复正常了。

    总结

    有时候一个看似简单的SQL,在一定的量级下也会对数据库造成毁灭性的打击。 这件事又一次侧面强调了索引的重要性。

    另外再出现类似的情况时,可以结合 Explain 和 Profile 来综合分析。

     

     

     

  • CDH 添加Spark2服务

    背景

    我们用的 Cloudera Manager 5.15.1 默认自带的Spark版本是 1.6.0 ,这个版本的 Spark 略有点老,用起来不是很方便。所以其实想在集群中并行Spark1 和 Spark2 来跑。

    在CDH中,Spark2 和之前我们之前讲的Kafka一样是单独作为parcel分发安装包来支持的。

    打开官网的Document文档首页,不用选择版本,在下面就可以看到Spark2的Quick Links:

    a

    1、CDS Powered by Apache Spark

    CDH 里的Spark2 是托管于 CDS中的一个附加服务,此组件 通常支持在 CDH 5.9 或更高版本中。

    Spark2 在 CDS 2.0   release1 发行版中存在一些Hive兼容性问题,主要影响 CDH 5.10.1及以上,CDH 5.9.2及以上,CDH 5.8.5及以上,CDH 5.7.6及以上。 如果正在使用这些CDH版本之一,则必须升级到CDS 2.0  release2 或更高的包,以避免在使用Hive功能时发生 Spark2 作业失败。

    在CDH上使用Spark2 时,并不是所有的特性、组件、建议等都与官方原生的Spark完全切合。

    Spark 2 由这几个相关项目组成:

    • Spark SQL: Module for working with structured data. Allows you to seamlessly mix SQL queries with Spark programs.
    • Spark Streaming: API that allows you to build scalable fault-tolerant streaming applications.
    • MLlib: API that implements common machine learning algorithms.

    Cloudera 提供的版本主要有 Spark: 1.6, 2.0, 2.1, 2.2, 2.3, 2.4。

    在 CDH5 上,Spark 1.6 服务可以与Spark 2服务共存。这两个服务的配置并不冲突,并且两个服务使都可以基于 Yarn 来运行。Spark History 服务器的端口是18088(用于Spark 1.6)和 18089 (用于Spark 2)。

    2. Install Requirement (安装需要)
    • Spark 2 无法在 Scala 2.10.上运行,需要 安装配置 Scala 2.11
    • Python 版本:
      • Python 2.7 or higher, when using Python 2.
      • Python 3.4 or higher, when using Python 3. (CDS 2.0 only supports Python 3.4 and 3.5; CDS 2.1 and higher include support for Python 3.6 and higher.)
    • Java JDK8,如果你使用了CSD2.2 或更高版本,必须从集群所有节点移除JDK7 。
    • 虽然可以使 Spark1 和 Spark2 并行,但是不能同时安装使用配置多个 Spark2 版本。

    二、安装

    2.1 CSD包下载配置

    首先在如下地址中,下载CSD 对应的Spark2 包: http://archive.cloudera.com/spark2/csd/

    a

    将下载的 CSD包放到到机器的/opt/cloudera/csd目录下,并修改权限:

     

    2.2 parcel包下载配置

    首先在如下地址中,下载parcel包:http://archive.cloudera.com/spark2/parcels/

    a

    将下载的包放到 parcel-repo 目录下,并修改权限:

    注1:下载parcel包时要下载对应的版本,这里系统使centOS 7,所以下载el7

    注2:CSD和 parcel 包都有cloudera1 和 cloudera2之分,因此在下载CSD和parcel包时两者版本必须一致。

    注3:如果 /opt/cloudera/parcel-repo目录下有其他的安装包,不用删除 ,但是如果本目录下有其他的重名文件比如manifest.json文件,把它重命名备份掉。然后把新的文件放在这里。

    2.3 CM 中配置 Parcel 生效

    首先,重启 CM :

    然后在CM的控制界面 hosts–>parcels 就可以看到对应的Parcel中多了 Spark2 ,并且是已下载待分配状态:

    a

    然后我们点击分配,这个分配的过程其实是把这些包从 CM 拷贝到所有的其他节点(包括Gateway),我们来对比一下:

    分配完成后,会发现它处于待激活状态,我们来看一下:

    a

    我们点击激活,我们发现激活完毕后,在 parcels 目录下多了一个软链:

    2.4 服务添加与部署

    这时候就可以在CM管理界面选择Spark2 服务去添加了:

    a

    分配角色:

    a

    遇到的问题1:

    安装好之后,测试使用时发现 Spark2-shell ,Pyspark2 的使用都没有问题,但是Spark2-submit 在跑任务时会出现问题:

    当我安装的Spark2.4版本时,发现运行Spark2-submit 测试任务报如下错误:

    如上,它一直报这个错误,然后任务无法正常运行下去。

    经过大量测试,无法定位解决,最后把 Spark2.4 换成 Spark2.3,发现上面那个报错没了,但是有了新的问题。

    在Spark2.3 下,当我指定 deploy-mode 方式为 Yarn-cluster 模式时,任务可以顺利正常运行,当我指定 deploy-mode 为 Client 模式时,任务会出现如下现象:

    如上,从日志看感觉像是无法连接 在 Client 上运行的 Driver 端一样,然后他重新在datanode上起了Driver端,接着运行成功了,这个问题也始终没有定位到。

    我将 CM 中的默认 deploy-mode 方式改为 yarn-cluster 了,后续有机会再继续查吧。

    遇到的问题2:

    Pyspark 运行时会报如下错误:

    而 Pyspark 没有这个问题,经定位发现pyspark必须使用yarn-client模式,如果指定yarn-cluster,则会报错:

    因为spark-shell作为一个与用户交互的命令行,必须将Driver运行在本地,而不是yarn上。

    所以运行时,如下指定既可:

     

     

     

     

  • ELK 业务监控部署

    一、背景

    监控是运维很重要的一项KPI、是能够直接面向业务线、可量化的工作内容。 并且做好了也很方便自己定位问题,对维护的业务系统有比较清晰的认知和了解。

    这里将一套将业务 Nginx 日志接入ELK做展示的数据流前后配置与搭建流程。

    a

    二、搭建与配置

    2.1 部署Filebeat 收集 Nginx web日志

    这里我们使用Filebeat轻量级日志收集机制来收集Nginx web并且吐向 Logstash,Filebeat 的安装这里不再详细赘述了,可以参考官网,很简单的:

    Filebeat 官网安装地址:https://www.elastic.co/guide/en/beats/filebeat/7.0/filebeat-installation.html

    安装好之后我们添加一个Nginx web日志的收集 Filebeat 配置:

    然后启动Filebeat:

    2.2 配置Logstash 做日志过滤和转发

    我们启动三台Logstash做日志转发,并且做前端Filebeat日志output的负载均衡,Logstash 的安装这里也不再详细赘述,可以参考官网的安装。

    Logstash 官网安装地址:https://www.elastic.co/guide/en/logstash/current/getting-started-with-logstash.html

    安装好之后,我们配置Logstash:

    2.2.1 先配置公共部分

    我们先在logstash配置目录下,创建一个公共配置文件:

    上面的 template 配置如下:

    2.2.2 以网站项目为单位配置 Logstash 转发

    在配置文件下,创建以网站名称为名的配置文件项:

    配置完了以后,重新启动 Logstash:

    这里要注意的一点是,重启Logstash要逐台重启,从Nginx日志观察到重启的Logstash重新又提供服务了,再重启,因为Logstash重启很慢,全部重启的话,过程中没有Alive的logstash提供服务了。

    2.3 配置 Nginx 转发

    这里之所以配置一层Nginx转发是因为我们的 Logstash  和 ELK 不在同一个网段,我们在ELK的网段搭建一个Nginx转发,然后再内网转给ELK:

    Nginx 的安装这里不再详赘,这里附上Nginx转发的配置:

    然后可以从Nginx的log中看到有三个logstash传输日志过来。

    2.4 配置 ELK

    2.4.1 在ELK上创建对应的索引

    首先,ES 中我们是面向索引的,所以每个业务网站我们建一条索引,如下:

    a

    点击Index Patterns 进入如下创建索引页面:

    a

    点击创建索引:

    a

    如上,这里填写index的时候以 logstash-www.xxx.com-* 然后Time Filter选择时间戳。因为这里的索引必须与 Logstash 的 common.conf 公共配置部分的 output 对齐。

    创建完成后,会发现这个索引下多了很多属性:

    a

    2.4.2 创建视图

    我们要想把各个 Item 起来在各个图里,并且在 Dashboard 中放几张图展示,要先生成这些图:

    a

    2.4.2.1 生成 HTTP 请求量图例

    我们首先来生成一个HTTP请求图例,选择 Line 样式:

    a

    然后做出如下配置,竖轴选择Count(即HTTP请求量),横轴选择时间:

    a

    2.4.2.2 配置HTTP响应时间:

    竖轴选择Request_time 表示响应时间,横轴还是时间戳:

    a

    2.4.2.3 配置HTTP Status 

    竖轴选择Count,横轴选择时间戳。然后展示按照response 选项分别做展示:

    a

    2.4.2.4 HTTP 请求URI分布

    竖轴选择Count,横轴按照时间戳。展示时以请求URL为展示粒度:

    a

    三、关于ELK中的Grok模块

    其实在上述配置中有一个很关键的配置操作,就是在Logstash 的配置中添加 Grok 模块来匹配我们的Nginx web日志。

    官网地址:https://www.elastic.co/guide/en/logstash/7.x/plugins-filters-grok.html

    Logstash 支持各种数据源,而 Nginx 的 web 日志是其中之一。 当Nginx的web日志被Filebeat吐过来到Logstash时,我们不仅仅把它单纯的存储转发到ELK,而是要进行各日志项的匹配,比如Request_time、Response_status 等等。如果我们要向取的有效的Web日志的各种 Item 字段,就需要我们的匹配规则和日志格式完全统一。

    最后,如果能成功匹配后,会在ES中创建索引后,这些Item属性都会一一独立体现出来。

    Grok 模块是Logstash 的核心模块,用来做正则过滤规则匹配,它有非常多的正则变量,代表一系列正则表达式,我们可以在如下github地址中找到对应的变量含义:

    Grok正则变量释义: https://github.com/elastic/logstash/blob/v1.4.2/patterns/grok-patterns

    在我们的Nginx中,日志配置格式如下:

    对应的实际日志格式如下:

    而我们的Logstash的Grok模块配置过滤规则如下:

    我们可以借助ELK中自带的 Grok Dubugger,它可以帮助我们检测 “正则变量” 和具体日志是否匹配。

    a

    如上,点击 Simulate 后,如果匹配,它会在下面给出匹配正确后的 Item 字段。 这样就很方便我们做调试了。

    这里给出一些常用的:

     

     

    Nginx日志配置
    访问实例
    正则变量
    $time_local
    24/Apr/2019:16:48:06 +0800
    %{HTTPDATE:timestamp}
    $upstream_addr
    39.106.237.240
    %{HOSTPORT:upstream}
    $request_method
    GET
    %{WORD:request_method}
    $uri
    /v1/list
    %{URIPATH:uri}
    $args
    a=1&b=2
    %{URIPARAM1:args}
    $remote_addr
    x.x.x.x
    %{IP:clientip}
    $server_protocol
    HTTP/1.1
    HTTP/%{NUMBER:httpversion}
    $status
    200
    %{NUMBER:status:int}
    $request_time
    1.24
    %{NUMBER:request_time:float}
    $upstream_response_time
    1.89
    %{NUMBER:response_time:float}