版权申明:转载请注明出处。
文章来源:http://bigdataer.net/?p=248

排版乱?请移步原文获得更好的阅读体验

 

1.概述

数据准确性,稳定性,时效性是数据开发中需要重点关注的,一般称之为数据质量。保证数据质量往往会占用数据开发工程师的很多精力,所以一个好的数据监控系统或者一个合理的数据监控方案对于数据质量的保证至关重要。本文将展示一种实际生产中使用过的数据监控方案,并给出相关的代码。
数据计算采用spark,报警形式采用邮件报警。涉及到的内容有使用springMVC构建一个支持发送html和文件的邮件接口;在spark计算任意过程中调用邮件接口;在spark中通过邮件接口发送hdfs上的结果数据。

2.架构图

说明:通常情况下公司内部的hadoop/spark集群和外网隔离,直接在spark作业里发送邮件显然不现实。所以需要构建一个邮件发送服务,暴露内网接口给spark作业调用,同时也能访问外网,把邮件发送到用户邮箱中。

3.基于springMVC构建的邮件服务
3.1 设计目标

(1)支持自定义邮件发件人昵称,主题,内容等
(2)支持发送html以及文件

3.2技术方案

springMVC,JavaMail

3.3核心代码

邮件发送工具类EmailSendUtil

java    98行

import java.io.File;
import java.util.Date;
import java.util.Properties; import javax.activation.CommandMap;
import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.activation.MailcapCommandMap;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart; import org.springframework.stereotype.Service; @Service
public class EmailSendUtil { public void sendEmail(String nick,String subject,String content,String receivers,File file) throws Exception { Properties proper = new Properties();
proper.setProperty("mail.transport.protocol", "smtp");
proper.setProperty("mail.stmp.auth", "true"); //账号密码认证
Session session = Session.getInstance(proper);
MimeMessage msg = new MimeMessage(session); try { MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap();
mc.addMailcap("text/html;; x-Java-content-handler=com.sun.mail.handlers.text_html");
mc.addMailcap("text/xml;; x-java-content-handler=com.sun.mail.handlers.text_xml");
mc.addMailcap("text/plain;; x-java-content-handler=com.sun.mail.handlers.text_plain");
mc.addMailcap("multipart/*;; x-java-content-handler=com.sun.mail.handlers.multipart_mixed");
mc.addMailcap("message/rfc822;; x-java-content-handler=com.sun.mail.handlers.message_rfc822");
CommandMap.setDefaultCommandMap(mc);
//设置发件人
String nickname=javax.mail.internet.MimeUtility.encodeText(nick);
msg.setFrom(new InternetAddress(nickname+"发件人邮箱地址"));
//设置收件人
msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(receivers));
//设置邮件主题
msg.setSubject(subject);
MimeMultipart msgMimePart = new MimeMultipart ("mixed");
//正文内容
MimeBodyPart contents = getBodyPart(content);
msgMimePart.addBodyPart(contents);
//附件
if(file!=null){
MimeBodyPart attachment = getAttachPart(file);
msgMimePart.addBodyPart(attachment);
} //设置邮件消息体
msg.setContent(msgMimePart);
//设置发送时间
msg.setSentDate(new Date());
msg.saveChanges(); Transport trans=session.getTransport();
trans.connect("smtp.exmail.qq.com", "发件人邮箱地址", "密码");
trans.sendMessage(msg, msg.getRecipients(Message.RecipientType.TO));
trans.close();
} catch (Exception e) {
throw new Exception("email send error:"+e.getMessage());
}finally{
if(file!=null&&file.exists()){
file.delete();
}
}
} private static MimeBodyPart getBodyPart(String content) throws MessagingException{
MimeBodyPart body = new MimeBodyPart();
MimeMultipart mmp = new MimeMultipart("related");
MimeBodyPart contents = new MimeBodyPart();
contents.setContent(content, "text/html;charset=utf-8");
mmp.addBodyPart(contents);
body.setContent(mmp);
return body;
} private static MimeBodyPart getAttachPart(File file) throws MessagingException{
MimeBodyPart attach = new MimeBodyPart();
FileDataSource fds = new FileDataSource(file);
attach.setDataHandler(new DataHandler(fds));
attach.setFileName(file.getName());
return attach;
}
}

controller类,写的比较粗糙,提供了两个接口,一个发纯html,一个可以发送混合格式的邮件。

java    47行

import java.io.File;

import net.bigdataer.api.weixin.utils.EmailSendUtil;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile; @Controller
@RequestMapping("/email_api")
public class EmailSendController extends DmBaseController{ @Autowired
private EmailSendUtil est; //只发送html
@RequestMapping("/send")
public @ResponseBody String sendEmail(@RequestParam("nickname") String nickname,@RequestParam("subject") String subject,@RequestParam("content") String content,@RequestParam("receivers") String receivers){
String result = "{\"status\":\"0\",\"msg\":\"success\"}";
try{
est.sendEmail(nickname,subject, content, receivers,null);
}catch(Exception e){
result = "{\"status\":\"-1\",\"msg\":\""+e.getMessage()+"\"}";
}
return result;
} //发送混合格式的邮件
@RequestMapping("/sendattachment")
public @ResponseBody String sendAttachment(@RequestParam("nickname") String nickname,@RequestParam("subject") String subject,@RequestParam("content") String content,@RequestParam("receivers") String receivers,@RequestParam("attachment") MultipartFile attachment){
String result = "{\"status\":\"0\",\"msg\":\"success\"}";
File file = new File("/opt/soft/tomcat/temp/"+attachment.getOriginalFilename());
try {
attachment.transferTo(file);
est.sendEmail(nickname,subject, content, receivers,file);
} catch (Exception e) {
result = "{\"status\":\"-1\",\"msg\":\""+e.getMessage()+"\"}";
} return result;
} }
4.spark作业调用邮件接口
4.1封装一个接口调用工具类

这个类提供了对https及http协议的访问,同时支持get和post请求。在本例中没有使用到get请求。
另外这个类依赖于httpclient的相关jar包。我这里使用的jarmaven依赖如下:

xml    11行

 		<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.2</version>
</dependency>

注意:因为spark源码中也使用了http-core的包,你的引用可能会和spark集群中本身的包冲突导致抛找不到某个类或者没有某个方法的异常,需要根据实际情况调整。不过代码大体上都一样,下面的代码可以参考

java    174行

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils; import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List; /**
* 服务请求类
* 封装了post,get请求
* 同时支持http和https方式
* @author liuxuecheng
*
*/
public class HttpClientUtil { private static final Log logger = LogFactory.getLog(HttpClientUtil.class);
//设置超时(单位ms)
private static int TIME_OUT = 3000;
private static CloseableHttpClient client = null; private static TrustManager trustManager = new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
}; static{ try{
//请求配置
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(TIME_OUT)
.setConnectionRequestTimeout(TIME_OUT)
.setSocketTimeout(TIME_OUT)
.build(); //访问https站点相关
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[]{trustManager}, null);
SSLConnectionSocketFactory scsf = new SSLConnectionSocketFactory(context, NoopHostnameVerifier.INSTANCE);
//注册
Registry<ConnectionSocketFactory> registry = RegistryBuilder
.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.INSTANCE)
.register("https", scsf)
.build(); //连接池
PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager(registry); //构造请求client
client = HttpClients.custom()
.setConnectionManager(manager)
.setDefaultRequestConfig(config)
.build(); }catch(Exception e){
e.printStackTrace();
}
} /**
* post方法
* post请求涉及到不同contentType,对应不同的HttpEntity
* 这里定义HttpEntity接口,所有实现了这个接口的实体均可传入
* @param token
* @param url
* @param entity
* @return
* @throws ClientProtocolException
* @throws IOException
*/
public static JSONObject post(String token,String url,HttpEntity entity) throws ClientProtocolException, IOException{
//UrlEncodedFormEntity stringEntity = new UrlEncodedFormEntity(,"UTF-8");
HttpPost post = new HttpPost(url); if(token !=null){
post.setHeader("Authorization", "Bearer "+token);
}
post.setHeader("Accept-Charset", "UTF-8");
post.setEntity(entity); return client.execute(post, handler);
} /**
* get请求
* @param token
* @param url
* @param content_type
* @param params
* @return
* @throws ClientProtocolException
* @throws IOException
* @throws URISyntaxException
*/
public static JSONObject get(String token,String url,String content_type,List<NameValuePair> params) throws ClientProtocolException, IOException, URISyntaxException{
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params,"UTF-8");
entity.setContentType(content_type); String para = EntityUtils.toString(entity); HttpGet get = new HttpGet(url);
if(token !=null){
get.setHeader("Authorization", "Bearer "+token);
}
get.setHeader("Accept-Charset", "UTF-8"); //get请求将参数拼接在参数中
get.setURI(new URI(get.getURI().toString()+"?"+para));
return client.execute(get, handler);
} //请求返回格式
public static ResponseHandler<JSONObject> handler = new ResponseHandler<JSONObject>(){ @Override
public JSONObject handleResponse(HttpResponse res) throws ClientProtocolException, IOException {
StatusLine status = res.getStatusLine();
HttpEntity entity = res.getEntity(); if(status.getStatusCode()>300){
throw new HttpResponseException(status.getStatusCode(),
status.getReasonPhrase());
} if(entity==null){
throw new ClientProtocolException("respones has no content");
} String res_str = EntityUtils.toString(entity); return JSONObject.parseObject(res_str);
} };
}
4.2进一步对发送邮件的接口封装

以下为scala代码

scala    51行

import java.io.File
import java.util import org.apache.http.NameValuePair
import org.apache.http.client.entity.UrlEncodedFormEntity
import org.apache.http.entity.ContentType
import org.apache.http.entity.mime.MultipartEntityBuilder
import org.apache.http.entity.mime.content.{FileBody, StringBody}
import org.apache.http.message.BasicNameValuePair
import org.apache.http.util.CharsetUtils /**
* Created by liuxuecheng on 2017/1/4.
*/
object EmailSendUtil { def sendEmail(nickname: String,subject:String,content:String,receivers:String):Unit={
val url = "邮件发送接口地址"
val content_type = "application/x-www-form-urlencoded" val params = new util.ArrayList[NameValuePair]()
params.add(new BasicNameValuePair ("nickname",nickname))
params.add(new BasicNameValuePair ("subject",subject))
params.add(new BasicNameValuePair ("content",content))
params.add(new BasicNameValuePair ("receivers",receivers))
try{
val entity = new UrlEncodedFormEntity(params,"UTF-8")
entity.setContentType(content_type)
HttpClientUtil.post(null,url,entity)
}catch{
case e:Throwable=>e.printStackTrace()
}
} def sendAttachment(nickname: String,subject:String,content:String,receivers:String,file:File):Unit={
val url = "邮件发送接口地址"
val body = new FileBody(file)
val entity = MultipartEntityBuilder.create()
.setCharset(CharsetUtils.get("UTF-8"))
.addPart("attachment",body)
.addPart("nickname",new StringBody(nickname,ContentType.create("text/plain",CharsetUtils.get("UTF-8"))))
.addPart("subject",new StringBody(subject,ContentType.create("text/plain",CharsetUtils.get("UTF-8"))))
.addPart("content",new StringBody(content,ContentType.create("text/plain",CharsetUtils.get("UTF-8"))))
.addTextBody("receivers",receivers)
.setContentType(ContentType.MULTIPART_FORM_DATA)
.build() HttpClientUtil.post(null,url,entity)
}
}
4.3spark读取hdfs文件并创建File对象

以下截取代码片段

scala    7行

 def getHdfsFile(sc:SparkContext,path:String):File = {
//需要hdfs的全路径,开头一般为hdfs://或者viewfs:// 并且具体到文件名
val filePath = "viewfs://xx-cluster"+path+"part-00000"
sc.addFile(filePath)
new File(SparkFiles.get(new File(filePath).getName))
}

既然都拿到文件了,发送邮件不就很简单了,调用上面封装好的接口就行。

4.4spark发送html

有时候没必要生成hdfs,计算结果适合报表展示的时候可以直接collect到内存中,然后构建一段html发送,上代码。

scala    17行

	val rdd = group_rdd.groupByKey(200)
.map(x=>{
val raw_uv = x._2.flatMap(e=>e._1).toSeq.distinct.size
val raw_pv = x._2.map(e=>e._2).reduce(_+_)
val cm_uv = x._2.flatMap(e=>e._3).toSeq.distinct.size
val cm_pv = x._2.map(e=>e._4).reduce(_+_)
IndexEntity(x._1._1,x._1._2,x._1._3,raw_uv,raw_pv,cm_uv,cm_pv)
}).collect().sortBy(_.search_flag).sortBy(_.platform).sortBy(_.bd) //模板拼接
val tbody:StringBuffer = new StringBuffer()
rdd.foreach(entity=>{
tbody.append(s"<tr><td>${entity.bd}</td><td>${entity.platform}</td><td>${entity.search_flag}</td>" +
s"<td>${entity.raw_uv}</td><td>${entity.cm_uv}</td><td>${new DecimalFormat(".00").format((entity.cm_uv.toDouble/entity.raw_uv)*100)}%</td>" +
s"<td>${entity.raw_pv}</td><td>${entity.cm_pv}</td><td>${new DecimalFormat(".00").format((entity.cm_pv.toDouble/entity.raw_pv)*100)}%</td></tr>")
})

spark数据监控实战的更多相关文章

  1. Spark大型项目实战:电商用户行为分析大数据平台

    本项目主要讲解了一套应用于互联网电商企业中,使用Java.Spark等技术开发的大数据统计分析平台,对电商网站的各种用户行为(访问行为.页面跳转行为.购物行为.广告点击行为等)进行复杂的分析.用统计分 ...

  2. 大数据开发实战:Spark Streaming流计算开发

    1.背景介绍 Storm以及离线数据平台的MapReduce和Hive构成了Hadoop生态对实时和离线数据处理的一套完整处理解决方案.除了此套解决方案之外,还有一种非常流行的而且完整的离线和 实时数 ...

  3. 大数据开发实战:HDFS和MapReduce优缺点分析

    一. HDFS和MapReduce优缺点 1.HDFS的优势 HDFS的英文全称是 Hadoop Distributed File System,即Hadoop分布式文件系统,它是Hadoop的核心子 ...

  4. Docker 监控实战

    如今,越来越多的公司开始使用 Docker 了,现在来给大家看几组数据: 2 / 3 的公司在尝试了 Docker 后最终使用了它 也就是说 Docker 的转化率达到了 67%,而转化市场也控制在 ...

  5. 项目实战——企业级Zabbix监控实战(一)

    项目实战--企业级Zabbix监控实战 实验一:Zabbix监控的搭建 1.实验准备 centos系统服务器3台. 一台作为监控服务器, 两台台作为被监控节点, 配置好yum源. 防火墙关闭. 各节点 ...

  6. 001_TCP/IP TIME_WAIT状态原理及监控实战

    一.原理 <1>TIME_WAIT状态原理---------------------------- 通信双方建立TCP连接后,主动关闭连接的一方就会进入TIME_WAIT状态. 客户端主动 ...

  7. 大数据开发实战:Stream SQL实时开发一

    1.流计算SQL原理和架构 流计算SQL通常是一个类SQL的声明式语言,主要用于对流式数据(Streams)的持续性查询,目的是在常见流计算平台和框架(如Storm.Spark Streaming.F ...

  8. 大数据开发实战:MapReduce内部原理实践

    下面结合具体的例子详述MapReduce的工作原理和过程. 以统计一个大文件中各个单词的出现次数为例来讲述,假设本文用到输入文件有以下两个: 文件1: big data offline data on ...

  9. 百度网络监控实战:NetRadar横空出世(下)

    原文:https://mp.weixin.qq.com/s/CvCs-6rX8Lb5vSTSjYQaBg 转自订阅号「AIOps智能运维」,已授权运维帮转发 作者简介:运小贝,百度高级研发工程师 负责 ...

随机推荐

  1. MVC、MVP、MVVM

    1 简介 演变:MVC ——> MVP ——> MVVM 英文原文:MVC vs. MVP vs. MVVM 三者的目的都是分离关注,使得UI更容易变换(从Winform变为Webform ...

  2. AtCoder Tak and Hotels

    题目链接:传送门 题目大意:有 n 个点排成一条直线,每次行动可以移动不超过 L 的距离,每次行动完成必须停在点上, 数据保证有解,有 m 组询问,问从 x 到 y 最少需要几次行动? 题目思路:倍增 ...

  3. 洛谷oj U3936(分成回文串) 邀请码:a0c9

    题目链接:传送门 题目大意:略 题目思路:DP 先预处理,分别以每个字母为中心处理能形成的回文串,再以两个字母为中心处理能形成的回文串. 然后 dp[i] 表示1~i 能形成的数目最少的回文串. 转移 ...

  4. 智力大冲浪(洛谷P1230)

    题目描述 小伟报名参加中央电视台的智力大冲浪节目.本次挑战赛吸引了众多参赛者,主持人为了表彰大家的勇气,先奖励每个参赛者m元.先不要太高兴!因为这些钱还不一定都是你的?!接下来主持人宣布了比赛规则: ...

  5. 记录--常用的linux命令

    mysql event /*查询event是否开启(查询结果Off为关闭 On为开启)*/ show variables like '%sche%'; /*开启/关闭命令(1开启--0关闭)*/ se ...

  6. Web Tracking

    采集方式_数据采集_用户指南_日志服务-阿里云 https://help.aliyun.com/document_detail/28981.html http://docs-aliyun.cn-han ...

  7. 9.Query on Embedded/Nested Documents-官方文档摘录

    1.插入案例 db.inventory.insertMany( [ { item: "journal", qty: 25, size: { h: 14, w: 21, uom: & ...

  8. Log4j:log4j.properties 配置解析

    Log4j 三个主要组件 Loggers(记录器):记录日志的工具,程序中就是用它来记录我们想要的日志信息. Appenders (输出源):日志输出到什么地方,可以是控制台.文件.流位置.数据库,等 ...

  9. If 条件控制 & while循环语句

    Python条件语句是通过一条或多条语句的执行结果(True或者False)来决定执行的代码块. 可以通过下图来简单了解条件语句的执行过程: if 语句 Python中if语句的一般形式如下所示: i ...

  10. linux c编程:Posix信号量

    POSIX信号量接口,意在解决XSI信号量接口的几个不足之处: POSIX信号量接口相比于XSI信号量接口,允许更高性能的实现. POSIX信号量接口简单易用:没有信号量集,其中一些接口模仿了我们熟悉 ...