版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hxcaifly/article/details/80073029
前言
阿里云大数据开发套件是一个功能强大的大数据计算PaaS平台。基于复杂的数据业务,常规的odps SQL语句不能解决。所以这个时候需要mapreduce来解决。
业务场景
项目中需要计算出涉黄人员黑名单的旅馆开房数据的异性同住特征。提供的数据表有两张:近三个月的旅馆开房数据和涉黄人员黑名单数据。其中旅馆开房数据记录着某地区的旅馆开放数据,数据量是4.6亿。涉黄人员黑名单是有涉黄前科的人员开房记录,数据量是4000w。对于涉黄人员,与异性的开房的同住次数是一个很重要的特征。通常来说,与不同异性开房越频繁,那么越有可能这个人是有涉黄嫌疑。所以与异性同住这个特征就显得很重要。
近三个月旅馆开房数据和涉黄人员黑名单的需要用到的字段是一样的,罗列如下:
1. gmsfhm (公民身份号码)
2. zslgdm (住宿旅馆代码)
3. fh (房间号)
4. rzsj (入住时间)
5. ldsj (离店时间)
6. xbdm (性别代码;1表示男性,2表示女性)。
7. tag (标签,这个字段是我伪造的,reduce时需要区分数据来源,0,表示近三个月的开房数据;1,表示涉黄黑名单开房数据来源)
对于这个业务场景,最开始我们考虑到的使用odps的mapjoin来直接碰撞,因为是不等值碰撞,在实际操作的时候报出异常,因为odps的mapjoin时,小表的数据量最大不能超过520M,我们这里很明显超过了,于是用mapreduce终于解决了我们的问题。
实操代码
主要逻辑如下
/**
* 实现住宿旅馆黑名单和住宿旅馆近三年数据相碰撞,提取出住宿旅馆黑名单里异性同住的特征。
* 参考odps官网的mapreduce案例, join示例。
* 住宿旅馆近三年数据: gmsfhm, xbdm, zslgdm, fh,rzsj,ldsj
* 住宿旅馆黑名单数据选取字段: gmsfhm, xbdm, zslgdm, fh, rzsj,ldsj
*/
package com.hxcaifly;
import com.aliyun.odps.OdpsException;
import com.aliyun.odps.data.Record;
import com.aliyun.odps.data.TableInfo;
import com.aliyun.odps.mapred.JobClient;
import com.aliyun.odps.mapred.MapperBase;
import com.aliyun.odps.mapred.ReducerBase;
import com.aliyun.odps.mapred.conf.JobConf;
import com.aliyun.odps.mapred.utils.InputUtils;
import com.aliyun.odps.mapred.utils.OutputUtils;
import com.aliyun.odps.mapred.utils.SchemaUtils;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class NumOfRoomStatistics {
//输入表名称,住宿旅馆表近三个月的数据
private String lgzs_three_year;
//输入表名称,住宿旅馆黑名单
private String lgzs_bad;
//输出表名称
private String outTableName;
//身份证信息
private static IdentityCardInfo IdInfo = new IdentityCardInfo();
/*
* inTableName1是住宿旅馆表近3个月的数据,
* inTableName2是住宿旅馆黑名单表
*/
public NumOfRoomStatistics(String lgzs_bad, String lgzs_three_year, String outTableName){
this.lgzs_bad = lgzs_bad;
this.lgzs_three_year = lgzs_three_year;
this.outTableName = outTableName;
}
public NumOfRoomStatistics( String lgzs_bad, String lgzs_three_year){
this.lgzs_bad = lgzs_bad;
this.lgzs_three_year = lgzs_three_year;
}
public void executeMR() throws OdpsException {
JobConf aliJob = new JobConf();
aliJob.setMapperClass(NumOfRoomStatisMapper.class);
aliJob.setReducerClass(NumOfRoomStatisReducer.class);
//设置Mapper输出到Reducer的Key属性
aliJob.setMapOutputKeySchema(SchemaUtils.fromString("zslgdm: String, fh: String"));
//设置Mapper输出到Reducer的Value属性
aliJob.setMapOutputValueSchema(SchemaUtils.fromString("rzsj: String, ldsj: String, gmsfhm: String, xbdm: String, tag: String"));
//设置Key分组列
aliJob.setOutputGroupingColumns(new String[]{"zslgdm", "fh"});
//设置Mapper输出到Reducer的Key排序列
aliJob.setOutputKeySortColumns(new String[]{"zslgdm","fh"});
//设置作业指定的分区列. 默认是Mapper输出Key的所有列
aliJob.setPartitionColumns(new String[]{"zslgdm","fh"});
//这里是输入两张表
InputUtils.addTable(TableInfo.builder().tableName(lgzs_bad).label("lgzs_bad").build(), aliJob);
InputUtils.addTable(TableInfo.builder().tableName(lgzs_three_year).label("lgzs_three_year").build(), aliJob);
OutputUtils.addTable(TableInfo.builder().tableName(outTableName).build(), aliJob);
JobClient.runJob(aliJob);
}
public static class NumOfRoomStatisMapper extends MapperBase {
private Record key;
private Record value;
@Override
public void setup(TaskContext context) throws IOException {
key = context.createMapOutputKeyRecord();
value = context.createMapOutputValueRecord();
System.out.println("mapsetup成功");
//这个tag是用来区分 input是输入的 哪个数据。
}
@Override
public void map(long recordNum, Record record, TaskContext context)throws IOException {
// 对于住宿旅馆近三年的数据输入 ,以及住宿旅馆黑名单的数据输入,都是获取到相同的字段。
// 只是加了一个tag的区分。tag=0,表示近三年的数据输入;tag=1表示住宿旅馆黑名单的数据输入。
// 住宿旅馆黑名单表才是我们的主表。
// 总共只需要读进这些数据
//按照 zslgdm,fh来聚合
key.set(0, record.getString("zslgdm"));
key.set(1, record.getString("fh"));
value.set(0, record.getString("rzsj"));
value.set(1, record.getString("ldsj"));
value.set(2, record.getString("gmsfhm"));
value.set(3, record.getString("xbdm"));
value.set(4, record.getString("tag"));
context.write(key, value);
}
}
public static class NumOfRoomStatisReducer extends ReducerBase {
private SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
private Record result;
@Override
public void setup(TaskContext context) throws IOException {
//result是输出的数据
result = context.createOutputRecord();
}
//key 是map里面的key。
//values 是该key下对应的values,是个迭代器数据,因为数据可能不只一条。
@Override
public void reduce(Record key, Iterator<Record> values, TaskContext context) throws IOException {
ArrayList<HoteRecord> bads_in_zslg = new ArrayList<HoteRecord>();
ArrayList<HoteRecord> zslg_three_year = new ArrayList<HoteRecord>();
result.set(0,key.getString("zslgdm"));
result.set(1,key.getString("fh"));
while(values.hasNext()){
Record value= values.next();
String tag = value.getString("tag");
System.out.println("reduce的tag:"+tag);
HoteRecord hoteRecord = new HoteRecord(value.getString("gmsfhm"), value.getString("xbdm"), value.getString("rzsj"), value.getString("ldsj"), value.getString("tag"));
//表示数据来源是住宿旅馆黑名单表
if ("1".equals(tag)){
bads_in_zslg.add(hoteRecord);
//表示数据来源是近三年开房数据
}else{
zslg_three_year.add(hoteRecord);
}
}
for ( int i=0;i<bads_in_zslg.size();i++){
HoteRecord bad_in_zslg = bads_in_zslg.get(i);
long ldsj = 0;
long rzsj = 0;
String xb = bad_in_zslg.getXbdm();
ldsj = parseStringTimeToLong(bad_in_zslg.getLdsj());
rzsj = parseStringTimeToLong(bad_in_zslg.getRzsj());
//为了过滤掉脏数据,就是开房时间间隔不能小于5分钟
if((ldsj - rzsj) / (1000 * 60) >= 5 && rzsj > -1){
long yxldsj = 0;
long yxrzsj = 0;
for (int j = 0; j < zslg_three_year.size(); j++) {
HoteRecord zslg_3_year = zslg_three_year.get(j);
int count = 2;
yxldsj=parseStringTimeToLong(zslg_3_year.getLdsj());
yxrzsj=parseStringTimeToLong(zslg_3_year.getRzsj());
String yxxb = zslg_3_year.getXbdm();
if (((rzsj <= yxrzsj && ((ldsj - yxrzsj) / (1000 * 60) >= 10))
|| (yxrzsj <= rzsj && ((yxldsj - rzsj) / (1000 * 60) >= 10))) && !xb.equals(yxxb)) {
result.set(count++, bad_in_zslg.getGmsfhm());
result.set(count++, bad_in_zslg.getXbdm());
result.set(count++, bad_in_zslg.getRzsj());
result.set(count++, bad_in_zslg.getLdsj());
result.set(count++, zslg_3_year.getGmsfhm());
result.set(count++, zslg_3_year.getXbdm());
result.set(count++, zslg_3_year.getRzsj());
result.set(count++, zslg_3_year.getLdsj());
System.out.println("reduce 开始写入数据");
context.write(result);
}
}
}
}
}
private void groupAccordSex(Map<String,String> record, ArrayList<HoteRecord> maleRecord, ArrayList<HoteRecord> femaleRecord ){
HoteRecord hoteRecord = new HoteRecord(record.get("gmsfhm"), record.get("xbdm"), record.get("rzsj"), record.get("ldsj"), record.get("tag"));
String xbdm = IdInfo.parseSexFromID(record.get("gmsfhm"), record.get("xbdm"));
if (xbdm != null) {
if (xbdm.equals("1")){
maleRecord.add(hoteRecord);
}else{
femaleRecord.add(hoteRecord);
}
}
}
private long parseStringTimeToLong(String time){
try{
return sdf.parse(time).getTime();
}catch ( Exception e){
System.out.println("time exception, "+ e.getMessage());
}
return -1;
}
}
}
依赖的bean
package com.hxcaifly;
public class HoteRecord {
String gmsfhm;
String xbdm;
String rzsj;
String ldsj;
String tag;
public HoteRecord(String gmsfhm, String xbdm,String rzsj, String ldsj, String tag){
this.gmsfhm = gmsfhm;
this.xbdm = xbdm;
this.rzsj = rzsj;
this.ldsj = ldsj;
this.tag = tag;
}
public String getGmsfhm() {
return gmsfhm;
}
public void setGmsfhm(String gmsfhm) {
this.gmsfhm = gmsfhm;
}
public String getXbdm() {
return xbdm;
}
public void setXbdm(String xbdm) {
this.xbdm = xbdm;
}
public String getRzsj() {
return rzsj;
}
public void setRzsj(String rzsj) {
this.rzsj = rzsj;
}
public String getLdsj() {
return ldsj;
}
public void setLdsj(String ldsj) {
this.ldsj = ldsj;
}
public String getTag() {
return tag;
}
public void setTag(String tag) {
this.tag = tag;
}
}
最后的main函数
package com.hxcaifly;
public class Shmx {
public static void main(String[] args) throws Exception{
if (args.length < 3){
System.err.println("Usage: Shmx <model key> Join <in_table> <in_table> <out_table> <threshold>");
System.exit(2);
}
new NumOfRoomStatistics(args[0], args[1], args[2]).executeMR();
}
}
最后maven install成jar包上传到 阿里云的odps资源里,执行:
jar -classpath shmx.jar zslg_bad zslg_three_year result_yxtz
这里的参数说明:
- zslg_bad (涉黄黑名单开房数据表)
- zslg_three_year (近三年开放数据)
- result_yxtz (计算出的异性同住数据储存表)