策略設計模式的應用

       設計模式:設計模式(Design pattern)是一套被反覆使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是爲了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 毫無疑問,設計模式於己於他人於系統都是多贏的;設計模式使代碼編制真正工程化;設計模式是軟件工程的基石脈絡,如同大廈的結構一樣。

       在最開始學習設計模式時,看着書籍,總是覺得設計模式很簡單。可是,每次在實戰的時候,經常渴望能夠用上設計模式,但總是無法第一時間應用上某種設計模式。明明自己已經知曉了多個設計模式,看書也覺得通俗易懂,爲啥呢?好吧,經過不斷學習,總算找到了一點原因:寫書使用的例子比較簡單以及理想化,並且作者牛逼。好啦,廢話不多說,看看自己應用策略設計模式的具體案例。

       在這裏,使用的是策略設計模式。什麼是策略設計模式呢?策略模式定義了一系列的算法,並將每一個算法封裝起來,而且使它們還可以相互替換。策略模式讓算法獨立於使用它的客戶而獨立變化。

       這裏的代碼主要是在分析hdfs中的nginxlog日誌文件,將每個日誌文件的參數抽取出來。比如下面四個鏈接:

<span style="font-size:18px;">/index.html?q=download&bid=0&app_id=857&version_id=9736&channel_id=1&down_time=1425889447&platform=1&ip=1921718005&down_from=1 HTTP/1.1

index.html?q=action&time=REQUEST_TIME&device_id=1764126fa512b2dd&app_id=7759&action=showtoolbar&package=com.good.world2&platform=1&sdk_version=&addon_version=1.3.6.160  HTTP/1.1";

GET /index.html?aid=5905&android_id=AE66F243-9F6A-491A-AD16-8EC7652DBAEC&product_model=iPhone+6+Plus&system_version=8.1.3&is_supported=1&add_time=1425538167&channel_id=1&platform=2&sdk_version=&addon_version=&q=active HTTP/1.1\" 200 612 \"-\" \"-\" \"-\"

/index.html?q=upload&asset_id=24726878&title=%E6%88%91%E7%9A%84%EF%BB%BF%E6%80%92%E7%81%AB%E6%88%98%E7%A5%9E%E8%A7%86%E9%A2%91%EF%BC%8C%E4%B8%8D%E7%9C%8B%E5%88%AB%E5%90%8E%E6%82%94%E5%93%A6%EF%BC%81&bid=33756344&upload_net=wifi&product_model=GT-N7100&system_version=4.1.2&app_id=5460&save_time=1425890861&device_id=f8ee017774daaf98&channel_id=1&platform=1&sdk_version=&addon_version= HTTP/1.1</span>


       抽取出來的結果:

<span style="font-size:18px;">{platform=1, down_time=1425889447, q=download, app_id=857, down_from=1, version_id=9736, bid=0, channel_id=1, ip=1921718005}

{platform=1, time=request_time, q=action, package=com.good.world2, action=showtoolbar, sdk_version=, app_id=7759, device_id=1764126fa512b2dd, addon_version=1.3.6.160 }

{platform=2, android_id=ae66f243-9f6a-491a-ad16-8ec7652dbaec, q=active, product_model=iphone+6+plus, is_supported=1, sdk_version=, add_time=1425538167, aid=5905, channel_id=1, addon_version=, system_version=8.1.3}

{asset_id=24726878, platform=1, product_model=gt-n7100, sdk_version=, device_id=f8ee017774daaf98, channel_id=1, addon_version=, title=%e6%88%91%e7%9a%84%ef%bb%bf%e6%80%92%e7%81%ab%e6%88%98%e7%a5%9e%e8%a7%86%e9%a2%91%ef%bc%8c%e4%b8%8d%e7%9c%8b%e5%88%ab%e5%90%8e%e6%82%94%e5%93%a6%ef%bc%81, save_time=1425890861, q=upload, upload_net=wifi, app_id=5460, bid=33756344, system_version=4.1.2}</span>
       

       分析的原理很簡單,通過遞歸方式,讀取所有的nginxlog文件,然後解析每一條url,取出參數,最後將數據進行歸類,最後重新寫入到各自的文件中。在最開始寫代碼是,很自然的,自己使用了多個if{}else if{}來解決問題。

       

<span style="color:#333333;">package net.itaem.fs;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.URI;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;

/**
 * 使用hdfs來etl出hive的數據格式
 * 這裏面需要區分出多種接口的數據,目前一共有四個接口數據
 * 
 * 由於有可能會重啓這個類,所以必須進行每個文件只被etl一次的保證
 * 問了達到該目的,這裏使用日誌的策略,也就是被etl過的文件名,保存到一個日誌文件中
 * 如果某個文件夾已經被etl完畢,那麼就將整個文件記錄到日誌文件夾
 * 
 * 每次啓動時,程序讀取日誌文件
 * 每次進行數據解析時,會首先判斷一個文件夾是否被etl,如果沒有,遞歸讀取該文件夾下面的所有文件,並且判斷該文件是否被etl過,如果沒有,進行etl;
 * 如果已經被etl過,直接跳過
 * 
 * 
 * 所以這裏使用兩個日誌文件,一個用於保存被etl過的文件夾,另外一個保存被etl過的文件名
 * 
 * @author luohong
 * @author 2015-03-06
 * @author [email protected]
 * */
public class ETLLog {

	/**
	 * hdfs配置
	 * */
	private Configuration conf = null;

	private FileSystem fs = null;

	/**
	 * 日期格式化類,主要用來對數據進行按照日期分類
	 * */
	private SimpleDateFormat sdf = null;

	/**
	 * 被讀取過的文件夾日誌文件
	 * */
	private String readedDirectoryPath = "E:\\readedDirectory.txt";

	/**
	 * 被讀取過的文件日誌文件
	 * */
	private String readedFilePath = "E:\\readedFile.txt";

	/**
	 * 將日誌存入到list中
	 * */
	private Set<String> readedDirectorySet;
	private Set<String> readedFileSet;

	/**
	 * 輸出日誌
	 * */
	private LogWriter writer;

	/**
	 * 讀取日誌
	 * */
	private LogReader reader;


	/**
	 * hdfs根路徑
	 * */
	private String basePath = "hdfs://192.168.1.111:9000/data/flume/flume";

	/**
	 * hdfs輸出路徑
	 * 這裏的路徑是輸出數據的根路徑
	 * 目前一共有四個接口數據,所以使用四個文件夾來存放數據
	 * */
	private String outputPath = "hdfs://192.168.1.111:9000/output/hive/aaa.txt";

	/**
	 * 接口文件夾名稱
	 * */
	private String installation = "installation";
	private String video = "video";
	private String down = "down";
	private String from_app = "from_app";

	/**
	 * 安裝與活躍統計接口 installation 接口文件夾路徑
	 * */
	private String installationOutputPath = outputPath + "/" + installation;


	/**
	 * 行爲統計接口 video  接口文件夾路徑
	 * */
	private String videoOutputPath = outputPath + "/" + video;

	/**
	 * 下載統計接口 down 接口文件夾路徑
	 * */
	private String downOutputPath = outputPath + "/" + down;

	/**
	 * 上傳視頻接口 from_app 接口文件夾路徑
	 * */
	private String from_appOutputPath = outputPath + "/" + from_app;


	/**
	 * 用來存放所有解析後的數據
	 * */
	private List<String> cache = null;

	/**
	 * 使用map來存放所有的數據,這裏數據一共有四種接口數據,所以key的值也共有四種
	 * 
	 * */
	private Map<String, String> dataMap = null;

	/**
	 * 使用模板方法來簡化調用
	 * @throws IOException 
	 * */
	private void process() throws IOException{
		System.out.println("begin setup");
		setUp();
		System.out.println("finish setup");

		System.out.println("begin run");
		run();
		System.out.println("finish run");

		System.out.println("begin finish");
		finish();
		fs.close();
		writer.close();  //這時候整個遞歸已經完成,關閉輸出流
		System.out.println("finish finish");
	}

	/**
	 * 初始化
	 * @throws IOException 
	 * */
	private void setUp() throws IOException{

		conf = new Configuration();
		fs = FileSystem.get(URI.create(basePath), conf);

		cache = new ArrayList<String>();
		sdf = new SimpleDateFormat("yyyy-MM--dd");


		//初始化reader
		reader = new LogReader(readedDirectoryPath, readedFilePath);
		System.out.println(reader);

		//初始化writer
		writer = new LogWriter(readedDirectoryPath, readedFilePath);
		System.out.println(writer);
		//初始化日誌
		readedFileSet = reader.readFile();
		readedDirectorySet = reader.readDirectory();

		System.out.println("========================readed directory==============================");
		System.out.println("readed directory size:" + readedDirectorySet.size());
		System.out.println("========================readed directory==============================");

		System.out.println("===========================readed file================================");
		System.out.println("readed file size:" + readedFileSet.size());
		System.out.println("===========================readed file================================");
	}

	/**
	 * 讀取文件,並且解析出數據內容
	 * */
	private void run(){
		try {
			read(basePath);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 結束
	 * 
	 * 這裏將解析到的所有數據放入到hdfs,然後再使用hive建立一個對應的表格即可
	 * @throws IOException 
	 * 
	 * */
	private void finish() throws IOException{
		fs = FileSystem.get(URI.create(basePath), conf);

		OutputStream outStream = fs.create(new Path(outputPath));
		PrintStream out = new PrintStream(outStream);

		for(String line: cache){
			out.println(line);
		}

		out.close();
		outStream.close();
		fs.close();
	}

	/**
	 * 
	 * 讀取一個文件夾下面的所有數據
	 * 這裏會遞歸調用
	 * 並且將數據進行etl,然後保存在cache集合中
	 * 
	 * @param path文件夾的路徑
	 * */
	private void read(String path) throws IOException{
		System.out.println(path);
		if(path == null || "".equals(path)) return;

		fs = FileSystem.get(URI.create(path), conf);

		if(!fs.exists(new Path(path))) return;

		FileStatus fileList[] = fs.listStatus(new Path(path));

		if(fileList.length == 0) return;

		int fileNum = fileList.length;
		InputStream hdfsInStream = null;
		BufferedReader reader = null;
		String line = null;
		StringBuilder sb = null;
		Map<String, String> paramMap = null;

		// 遍歷一個文件夾下面的所有文件
		for(int fileCount = 0; fileCount < fileNum; fileCount++){
			String fileName = path + "/" + fileList[fileCount].getPath().getName();

			//該文件是文件夾,遞歸調用
			if(fs.isDirectory(new Path(fileName))) {
				//判斷該文件夾是否已經被解析過,如果是,那麼跳過該文件夾
//				if(readedDirectorySet.contains(fileName)){
//					continue;
//				}
				read(fileName);
				
				//記錄下一個文件夾被etl,但是不能記錄下根目錄
//				if(!fileName.equals(basePath)){
//					writer.pintlnDirectory(fileName);
//				}
			}else{
				if(!fs.exists(new Path(fileName))) continue;
                
				
				//如果文件被解析過了,那麼跳過該文件
				if(readedFileSet.contains(fileName)){
					continue;
				}
				
				//普通文件,讀取文件內容
				hdfsInStream = fs.open(new Path(fileName));
				reader = new BufferedReader(new InputStreamReader(hdfsInStream));
                
				while((line = reader.readLine()) != null){
					if(line == null || line.equals("") || line.isEmpty()){
						continue;
					}
                    
					//取出一個請求的數據
					paramMap = CRequest.URLRequest(line);
					sb = new StringBuilder(); 
					String type = "";
					for(String param: paramMap.keySet()) {
						if(param.equals("q") && paramMap.get(param).equals("down")){
							type = "down";
							continue;
						}else if(param.equals("q") && paramMap.get(param).equals("from_app")){
							type = "from_app";
							continue;
						}else if(param.equals("q") && paramMap.get(param).equals("action")){
							type = "action";
							continue;
						}else if(param.equals("q") && paramMap.get(param).equals("active")){
							type = "active";
							continue;
						}else{
							System.out.println("other type");
						}
						
						String value = paramMap.get(param);
						sb.append(value + " ");
					}
					
					
					if(type.equals("down")){
						//save the param value to down file
					}else if(type.equals("from_app")){
						//save the param value to from_app
					}else if(type.equals("action")){
						//save the param value to action
					}else if(type.equals("active")){
					    //save the param value to active
					}else{
						System.out.println("other type");
					}
					
					//將數據存放入cache,下次一次性刷入一個hdfs文件中
					cache.add(sb.toString());
				}

				//關閉stream
				hdfsInStream.close();
				//關閉reader
				reader.close();
				//記錄下一個文件被etl
				writer.pintlnFile(fileName);
			}
		}
	}

	/**
	 * 啓動定時器,定期執行數據的etl,這裏設置在每天凌晨1點鐘執行數據轉換
	 * */
	public static void main(String[] args) throws IOException {
		new ETLLog().process();
	}
}
</span>
       從紅色區域我們可以看到,代碼中使用了多個if{}else if{}。如果url的數據一直都只有四種類型,那麼這還沒有啥,但是如果url類型加多10個呢?那我們的代碼改動將會非常多,非常不利於後期維護。

       於是乎,我就想起了設計模式。從代碼中我們可以發現,很適合使用策略設計模式。哈哈,爲什麼會很適合策略設計模式呢?這個大家可以看看《代碼重構》,裏面教授了幾種方式,讓我們發現壞代碼的味道。在這裏,將全部url類別抽象爲一個抽象Event,然後每一個具體的url類別作爲一個Event子類即可。在代碼中,不再使用if{}else if{}來判斷,而是使用面向接口:Event event = new EventImple()的變成方式來表示每一個Event,然後將event放入List<Event>即可。

       

<span style="font-size:18px;">package com.aipai.luohong.event;

/**
 * 定義一個類,用來表示所有的接口數據,每個接口都屬於一個event
 * 每個event通過type來區分
 * 由於每個event的保存路徑都是不一樣的,所以這裏使用抽象方法來定義一個統一接口
 * @author luohong
 * @author 2015-03-09
 * @author [email protected]
 * 
 * */
public abstract class Event {
    
	//默認輸出路徑
	private String basePath = "hdfs://ip-192-168-1-203:8020";
	
	private String type;
	
	public Event() {
	}
	
	public Event(String basePath){
		this.basePath = basePath;
	}
	
	public String getBasePath() {
		return basePath;
	}

	public void setBasePath(String basePath) {
		this.basePath = basePath;
	}
    

	public String getType(){
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}
	
	public abstract String getOutputPath();
}</span>


       

package com.aipai.luohong.event;
/**
 * 下載統計接口 q=download
 * @author luohong
 * @author 2015-03-09
 * @author [email protected]
 * 
 * */
public class DownloadEvent extends Event {
	private String outputPath = "/download/" + "current.txt";
    
	public DownloadEvent(){
		
	}
	
	public DownloadEvent(String outputPath){
		this.outputPath = outputPath;
	}
	
	@Override
	public String getOutputPath() {
		return super.getBasePath() + outputPath;
	}
}
      每一個url類別,就建立一個Event子類即可。但是經過這樣子修改之後,我們可以寫一個配置文件,然後將每個Event,以及event對應的參數內容等信息配置在配置文件中,爲以後遷移代碼提供更大的靈活性。properties文件如下:

       

<span style="font-size:18px;">basePath=hdfs://192.168.1.111:9000
   flumeDataPath=hdfs://192.168.1.111:9000/usr
   interval=5
   separator=,
   fileSize=5
   events=DownloadEvent,InstallationEvent,UploadEvent,VideoEvent
   eventTypes=down,active,from_app,action
</span>
      具體參數意義大概就是用於配置hdfs的url,數據源,間隔時間,分隔符,文件大小,events就是有多少種Event,eventTypes對應events。通過這種方式,代碼的靈活性將會高不少,下面是具體代碼。

有了這個配置文件,以及框架代碼,我們假設新添加一個DeleteEvent,那我們不需要改動任何代碼,只需要添加一個DeleteEvent,然後修改配置文件爲:

<span style="font-size:18px;">basePath=hdfs://192.168.1.111:9000
   flumeDataPath=hdfs://192.168.1.111:9000/usr
   interval=5
   separator=,
   fileSize=5
   events=DownloadEvent,InstallationEvent,UploadEvent,VideoEvent,DeleteEvent
   eventTypes=down,active,from_app,action,delete</span>
       程序會自動檢測到Event以及對應的參數類型,大大簡便我們的後期開發和維護。

      

<span style="font-size:18px;"><span style="color:#333333;">package com.aipai.luohong;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;

import com.aipai.luohong.event.Event;

/**
 * 使用hdfs來etl出hive的數據格式
 * 這裏面需要區分出多種接口的數據,目前一共有四個接口數據
 * 
 * 由於有可能會重啓這個類,所以必須進行每個文件只被etl一次的保證
 * 問了達到該目的,這裏使用日誌的策略,也就是被etl過的文件名,保存到一個日誌文件中
 * 如果某個文件夾已經被etl完畢,那麼就將整個文件記錄到日誌文件夾
 * 
 * 每次啓動時,程序讀取日誌文件
 * 每次進行數據解析時,會首先判斷一個文件夾是否被etl,如果沒有,遞歸讀取該文件夾下面的所有文件,並且判斷該文件是否被etl過,如果沒有,進行etl;
 * 如果已經被etl過,直接跳過
 * 
 * 所以這裏使用兩個日誌文件,一個用於保存被etl過的文件夾,另外一個保存被etl過的文件名
 * 
 * 
 * 目前使用每間隔一段時間便解析數據,所以目前會遍歷所有的文件夾下面的數據,判斷文件是否被解析過,如果是,那麼就跳過文件
 * 
 * 輸出文件策略使用的是:每個文件大小由參數配置,默認爲128M,達到閥值時,生成新文件
 * 
 * @author luohong
 * @author 2015-03-06
 * @author [email protected]
 * */
public class ETLLog {

	/**
	 * hdfs配置
	 * */
	private Configuration conf = null;

	/**
	 * hdfs文件系統
	 * */
	private FileSystem fs = null;

	/**
	 * 被讀取過的文件夾日誌文件
	 * */
	private String readedDirectoryPath = "/var/log/etl_log/readedDirectory.txt";

	/**
	 * 被讀取過的文件日誌文件
	 * */
	private String readedFilePath = "/var/log/etl_log/readedFile.txt";

	/**
	 * 將日誌存入到list中
	 * */
	private Set<String> readedDirectorySet;
	private Set<String> readedFileSet;

	/**
	 * 輸出日誌
	 * */
	private LogWriter writer;

	/**
	 * 讀取日誌
	 * */
	private LogReader reader;

	/**
	 * 分隔符,默認使用‘,’
	 * */
	private String separator = ",";

	/**
	 * 輸出文件大小
	 * */
	private int fileSize = 128;

	/**
	 * 間隔時間
	 * */
	private int interval = 5;

	/**
	 * hdfs根路徑
	 * */
	private String basePath = "hdfs://ip-192-168-1-203:8020";

	private String flumeDataPath = "hdfs://ip-192-168-1-203:8020/usr/flume";

	private String currentFile = "current.txt";

	/**
	 * 事件接口列表
	 * */
	private List<Class<? extends Event>> eventList = null;
	/**
	 * 事件類型
	 * */
	private List<String> eventTypeList = null;

	/**
	 * 使用map來存放所有的數據,這裏數據一共有四種接口數據,所以key的值也共有四種
	 * 
	 * */
	private Map<String, List<String>> dataMap = null;

	/**
	 * 計算即將要添加數據的字節長度
	 * */
	private Map<String, Long> dataLengthMap = null;

	//提供getXxx接口,用於訪問配置信息
	public int getInterval() {
		return interval;
	}

	public String getBasePath() {
		return basePath;
	}

	public String getSeparator(){
		return separator;
	}

	public int getFileSize(){
		return fileSize;
	}

	public String getFlumeDataPath() {
		return flumeDataPath;
	}


	/**
	 * 使用模板方法來簡化調用
	 * @throws IOException 
	 * @throws ClassNotFoundException 
	 * @throws IllegalAccessException 
	 * @throws InstantiationException 
	 * */
	private void process() throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException{

		System.out.println("begin setup");
		setUp();
		System.out.println("finish setup");

		System.out.println("begin run");
		run();
		System.out.println("finish run");

		System.out.println("begin finish");
		finish();
		fs.close();
		writer.close();  //這時候整個遞歸已經完成,關閉輸出流
		System.out.println("finish finish");
	}

	/**
	 * 初始化
	 * @throws IOException 
	 * @throws ClassNotFoundException 
	 * */
	@SuppressWarnings("unchecked")
	private void setUp() throws IOException, ClassNotFoundException{
		dataMap = new HashMap<String, List<String>>();
		dataLengthMap = new HashMap<String, Long>();

		</span><span style="color:#ff0000;">eventList = new ArrayList<Class<? extends Event>>();
		eventTypeList = new ArrayList<String>();
		
		//加載配置文件
		InputStream inStream = new FileInputStream("/var/log/etl_log/properties");
		Properties properties = new Properties();
		properties.load(inStream);

		String basePath = properties.getProperty("basePath");
		String flumeDataPath = properties.getProperty("flumeDataPath");
		String interval = properties.getProperty("interval");
		String separator = properties.getProperty("separator");
		String fileSize = properties.getProperty("fileSize");
		String events = properties.getProperty("events"); 
		String eventTypes = properties.getProperty("eventTypes");</span><span style="color:#333333;">

		if(basePath != null && !"".equals(basePath)){
			this.basePath = basePath;
		}

		if(interval != null && !"".equals(interval)){
			this.interval = Integer.valueOf(interval);
		}

		if(separator != null && !"".equals(separator)){
			this.separator = separator;
		}

		if(fileSize != null && !"".equals(fileSize)){
			this.fileSize = Integer.valueOf(fileSize);
		}

		if(flumeDataPath != null && !"".equals(flumeDataPath)){
			this.flumeDataPath = flumeDataPath;
		}

		if(events != null && !"".equals(events)){
			for(String eventStr: events.split(",")){
				String eventClassName = "com.aipai.luohong.event." + eventStr;

				Class<? extends Event> event = (Class<? extends Event>) Class.forName(eventClassName);
				eventList.add(event);
				List<String> cache = new ArrayList<String>();
				//每種event都會使用一種緩存和一個計數器
				dataMap.put(eventClassName, cache);
				dataLengthMap.put(eventClassName, 0l);
			}
			System.out.println(eventList);
		}

		if(eventTypes != null && !"".equals(eventTypes)){
			for(String eventType: eventTypes.split(",")){
				eventTypeList.add(eventType);
			}
			System.out.println(eventTypeList);
		}
		conf = new Configuration();
		fs = FileSystem.get(URI.create(basePath), conf);

		



		//初始化reader
		reader = new LogReader(readedDirectoryPath, readedFilePath);
		System.out.println(reader);

		//初始化writer
		writer = new LogWriter(readedDirectoryPath, readedFilePath);
		System.out.println(writer);
		//初始化日誌
		readedFileSet = reader.readFile();
		readedDirectorySet = reader.readDirectory();

		System.out.println("========================readed directory==============================");
		System.out.println(readedDirectorySet.size());
		System.out.println("========================readed directory==============================");

		System.out.println("===========================readed file================================");
		System.out.println(readedFileSet.size());
		System.out.println("===========================readed file================================");
	}

	/**
	 * 讀取文件,並且解析出數據內容
	 * */
	private void run(){
		try {
			read(flumeDataPath);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 將緩存數據寫入到hdfs
	 * 
	 * @throws IOException 
	 * @throws ClassNotFoundException 
	 * @throws IllegalAccessException 
	 * @throws InstantiationException 
	 * 
	 * */
	@SuppressWarnings("deprecation")
	private void finish() throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException{
		fs = FileSystem.get(URI.create(basePath), conf);

		OutputStream outStream = null;
		PrintStream out = null;

		/**
		 * 
		 * 
		 * */
		for(String key: dataMap.keySet()){
			List<String> dataList = dataMap.get(key);
			Long dataSize = dataLengthMap.get(key);

			System.out.println(key + " data size is " + dataList.size());

			Class<?> eventClass = Class.forName(key);
			Event event = (Event) eventClass.newInstance();
			event.setBasePath(basePath + "/output/hive");

			if(!fs.exists(new Path(event.getOutputPath()))){  //如果數據不存在,創建新文件					
				outStream = fs.create(new Path(event.getOutputPath()));  
			}else{  //追加數據
				long length = fs.getLength(new Path(event.getOutputPath()));
				if(length + dataSize <= fileSize * 1000 * 1000){  //如果數據沒有超過閥值,那麼添加到一個文件中,如果超過了,新建一個新文件						
					outStream = fs.append(new Path(event.getOutputPath()));
				}else{  //數據超過閥值,那麼新建一個文件,將原來current.txt文件內容複製過去,清空current.txt
					String str = event.getOutputPath().replaceAll(currentFile, "") + new Date().getTime() + ".txt";  //使用時間戳來生成文件
					fs.rename(new Path(event.getOutputPath()), new Path(str));  //複製文件
					//將current.txt清空,數據重新寫入到該文件
					fs.delete(new Path(event.getOutputPath()));
					outStream = fs.create(new Path(event.getOutputPath()));
				}
			}

			out = new PrintStream(outStream);
			for(String data: dataList){
				out.println(data);
			}	
		}

		out.close();
		outStream.close();

		fs.close();
	}

	/**
	 * 
	 * 讀取一個文件夾下面的所有數據
	 * 這裏會遞歸調用
	 * 並且將數據進行etl,然後保存在cache集合中
	 * 
	 * @param path文件夾的路徑
	 * */
	private void read(String path) throws IOException{
		if(path == null || "".equals(path)) return;

		fs = FileSystem.get(URI.create(path), conf);
		if(!fs.exists(new Path(path))) return;

		FileStatus fileList[] = fs.listStatus(new Path(path));
		if(fileList.length == 0) return;

		int fileNum = fileList.length;
		InputStream hdfsInStream = null;
		BufferedReader reader = null;
		String line = null;
		StringBuilder sb = null;
		Map<String, String> paramMap = null;

		// 遍歷一個文件夾下面的所有文件
		for(int fileCount = 0; fileCount < fileNum; fileCount++){
			String fileName = path + "/" + fileList[fileCount].getPath().getName();

			//該文件是文件夾,遞歸調用
			if(fs.isDirectory(new Path(fileName))) {
				//				if(readedDirectorySet.contains(fileName)){
				//				continue;
				//			}
				read(fileName);
				//記錄下一個文件夾被etl
				//writer.pintlnDirectory(fileName);
			}else{
				if(!fs.exists(new Path(fileName))) continue;

				//如果文件被解析過了,那麼跳過該文件
				if(readedFileSet.contains(fileName)){
					continue;
				}

				//普通文件,讀取文件內容
				hdfsInStream = fs.open(new Path(fileName));
				reader = new BufferedReader(new InputStreamReader(hdfsInStream));

				while((line = reader.readLine()) != null){
					if(line == null || line.equals("") || line.isEmpty()){
						continue;
					}

					paramMap = CRequest.URLRequest(line);

					sb = new StringBuilder(); 

					int index = 0;
					for(String param: paramMap.keySet()) {
						if(param.equals("q")){  //用於劃分接口參數
							index = eventTypeList.indexOf(paramMap.get(param));
							if(index == -1) System.out.println(paramMap.get(param));
						}else{  //其他參數
							String value = paramMap.get(param);
							sb.append(value + separator);	
						}	
					}

					String str = "";
					if(sb.length() > 1){
						str = sb.substring(0, sb.length() - 1);
					}

					if(index == -1){
						continue;
					}

					Class<? extends Event> event = eventList.get(index);
					dataMap.get(event.getCanonicalName()).add(str);
					dataLengthMap.put(event.getCanonicalName(), dataLengthMap.get(event.getCanonicalName()) + str.getBytes().length);
				}

				//關閉stream
				hdfsInStream.close();
				//關閉reader
				reader.close();
				//記錄下一個文件被etl
				writer.pintlnFile(fileName);
			}
		}
	}

	public static void main(String[] args) throws IOException {
		final ETLLog etlLog = new ETLLog();

		//啓動定時器
		new Timer().schedule(new TimerTask(){
			@Override
			public void run() {
				try {
					etlLog.process();
				} catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException e) {
					e.printStackTrace();
				}
			}
		}, 1000, etlLog.getInterval() * 60 * 1000);
	}
}
</span></span>

       總結:在開發過程中,設計模式並不是那麼清晰明顯的,需要我們不斷的去重構代碼。好吧,重構花了我大概一個下午的時間,但是最後看到代碼變得好起來之後,還是值得的,好吧,繼續努力,繼續逗比去。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章