版权申明:转载请注明出处。
文章来源: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. CKEDITOR 4.6.X 版本 插件 弹出对话框 Dialog中 表格 Table 自定义样式Style 问题

    项目开发过程中,发现CKEDITOR 插件的弹出框 内 如果跟据项目需要写表格(table tr td),表格的边框等属性会被 CKEDITOR的清除或覆盖,导致表格很难看. 问题关键: 插件弹出框d ...

  2. 170120、java 如何在pdf中生成表格

    1.目标 在pdf中生成一个可变表头的表格,并向其中填充数据.通过泛型动态的生成表头,通过反射动态获取实体类(我这里是User)的get方法动态获得数据,从而达到动态生成表格. 每天生成一个文件夹存储 ...

  3. Openstack创建镜像

    如何创建生产用的Openstack镜像 参考官方文档https://docs.openstack.org/image-guide/centos-image.html 1,创建虚拟机硬盘 qemu-im ...

  4. Spoken English Practice(appetite,ache,meds,change, stop)Look like you got your appetite back, that's a good sign.

    绿色:连读:                  红色:略读:               蓝色:浊化:               橙色:弱读     下划线_为浊化 口语蜕变(2017/6/27) ...

  5. Setting IE11 with Group Policy Preferences

    一.Setting Home Page with Group Policy Preferences 1.Open the Group Policy Management Console and cre ...

  6. IO流入门-第五章-FileWriter

    FileWriter基本用法和方法示例 /* java.io.Writer java.io.OutputStreamWriter 转换流(字节输出流--->字符输出流) java.io.File ...

  7. BaseDao 接口

    // 以后所有的 Dao 接口都需要继承 BaseDao 接口; // 自定义泛型接口 public interface BaseDao<T>{ public void save(T t) ...

  8. Struts2 框架的值栈

    1. OGNL 表达式 1.1 概述 OGNL(Object Graphic Navigation Language),即对象图导航语言; 所谓对象图,即以任意一个对象为根,通过OGNL可以访问与这个 ...

  9. HTML5 canvas绘图基本使用方法

    <canvas></canvas>是HTML5中新增的标签,用于绘制图形,实际上,这个标签和其他的标签一样,其特殊之处在于该标签可以获取一个CanvasRenderingCon ...

  10. Redis一(Redis-py与String操作)

    Redis 介绍 redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(so ...