大数据项目之电商数仓(用户行为数据采集)

第1章 数据仓库概念

第2章 项目需求

2.1 项目需求分析

2.2 项目框架

2.2.1 技术选型

2.2.2 系统架构图设计

2.2.3 系统数据流程设计

2.2.4 框架版本选型

产品

版本

Hadoop

2.7.2

Flume

1.7.0

Kafka

0.11.0.2

Kafka Manager

1.3.3.22

Hive

1.2.1

Sqoop

1.4.6

Mysql

5.6.24

Azkaban

2.5.0

Java

1.8

Zookeeper

3.4.10

注意事项:框架选型尽量不要选择最新的框架,选择最新框架半年前左右的稳定版。

2.2.5 集群资源规划设计

 

服务器1

服务器2

服务器3

HDFS

NameNode

DataNode

DataNode

DataNode

Yarn

NodeManager

Resourcemanager

NodeManager

NodeManager

Zookeeper

Zookeeper

Zookeeper

Zookeeper

Flume(采集日志)

Flume

Flume

 

Kafka

Kafka

Kafka

Kafka

Flume(消费Kafka)

 

 

Flume

Hive

Hive

 

 

Mysql

Mysql

 

 

第3章 数据生成模块

3.1 埋点数据基本格式

  1. 公共字段:基本所有安卓手机都包含的字段
  2. 业务字段:埋点上报的字段,有具体的业务类型

下面就是一个示例,表示业务字段的上传。

{
"ap":"xxxxx",//产品字段 app key
"cm": {  //公共字段
		"mid": "",  // (String) 设备唯一标识
        "uid": "",  // (String) 用户标识
        "vc": "1",  // (String) versionCode,程序版本号
        "vn": "1.0",  // (String) versionName,程序版本名
        "l": "zh",  // (String) 系统语言
        "sr": "",  // (String) 渠道号,应用从哪个渠道来的。
        "os": "7.1.1",  // (String) Android系统版本
        "ar": "CN",  // (String) 区域
        "md": "BBB100-1",  // (String) 手机型号
        "ba": "blackberry",  // (String) 手机品牌
        "sv": "V2.2.1",  // (String) sdkVersion
        "g": "",  // (String) gmail
        "hw": "1620x1080",  // (String) heightXwidth,屏幕宽高
        "t": "1506047606608",  // (String) 客户端日志产生时的时间
        "nw": "WIFI",  // (String) 网络模式
        "ln": 0,  // (double) lng经度
        "la": 0  // (double) lat 纬度
    },
"et":  [  //事件
            {
                "ett": "1506047605364",  //客户端事件产生时间
                "en": "request",  //事件名称
                "kv": {  //事件结果,以key-value形式自行定义
                    "your key1": "your value1",
                    "your key2": "your value2",
                    "your key n": "your value n"
                }
            }
        ]
}

示例日志(服务器时间戳 | 日志):

1540934156385|{ 
    "ap": "gmall", 
    "cm": { 
        "uid": "1234", 
        "vc": "2", 
        "vn": "1.0", 
        "la": "EN", 
        "sr": "", 
        "os": "7.1.1", 
        "ar": "CN", 
        "md": "BBB100-1", 
        "ba": "blackberry", 
        "sv": "V2.2.1", 
        "g": "[email protected]", 
        "hw": "1620x1080", 
        "t": "1506047606608", 
        "nw": "WIFI", 
        "ln": 0
    }, 
        "et": [ 
            { 
                "ett": "1506047605364", 
                "en": "request", 
                "kv": { 
                    "url": "www.baidu.com", 
                    "click": "1"
                }
            }
        ]
    }
}

下面是各个埋点日志格式。其中商品点击属于信息流的范畴

3.2 事件日志数据

3.2.1 商品点击(display)

事件标签:display

标签

含义

action

动作:曝光商品=1,点击商品=2

newsid

商品ID(服务端下发的ID

place

顺序(第几条商品,第一条为0,第二条为1,如此类推)

extend1

曝光类型:1 - 首次曝光 2-重复曝光(没有使用)

category

分类ID(服务端定义的分类ID

3.2.2 商品详情页(newsdetail)

事件标签:newsdetail

标签

含义

entry

页面入口来源:应用首页=1push=2、详情页相关推荐=3

action

动作:开始加载=1,加载成功=2pv),加载失败=3, 退出页面=4

newsid

商品ID(服务端下发的ID

show_style

商品样式:0、无图

 

1、一张大图

 

2、两张图

 

3、三张小图

 

4、一张小图

 

5、一张大图两张小图

 

来源于详情页相关推荐的商品,上报样式都为0(因为都是左文右图)

news_staytime

页面停留时长:从商品开始加载时开始计算,到用户关闭页面所用的时间。若中途用跳转到其它页面了,则暂停计时,待回到详情页时恢复计时。或中途划出的时间超过10分钟,则本次计时作废,不上报本次数据。如未加载成功退出,则报空。

loading_time

加载时长:计算页面开始加载到接口返回数据的时间 (开始加载报0,加载成功或加载失败才上报时间)

type1

加载失败码:把加载失败状态码报回来(报空为加载成功,没有失败)

category

分类ID(服务端定义的分类ID

3.2.3 商品列表页(loading)

事件名称:loading

标签

含义

action

动作:开始加载=1,加载成功=2,加载失败=3

loading_time

加载时长:计算下拉开始到接口返回数据的时间,(开始加载报0,加载成功或加载失败才上报时间)

loading_way

加载类型:1-读取缓存,2-从接口拉新数据
(加载成功才上报加载类型)

extend1

扩展字段 Extend1

extend2

扩展字段 Extend2

type

加载类型:自动加载=1,用户下拽加载=2,底部加载=3(底部条触发点击底部提示条/点击返回顶部加载)

type1

加载失败码:把加载失败状态码报回来(报空为加载成功,没有失败)

3.2.4 广告(ad)

事件名称:ad

标签

含义

entry

入口:商品列表页=1  应用首页=2 商品详情页=3

action

动作:请求广告=1 取缓存广告=2  广告位展示=3 广告展示=4 广告点击=5 

content

状态:成功=1  失败=2  

detail

失败码(没有则上报空)

source

广告来源:admob=1 facebook=2  ADX(百度)=3 VK(俄罗斯)=4

behavior

用户行为:
主动获取广告=1  
被动获取广告=2

newstype

Type: 1- 图文 2-图集 3-段子 4-GIF 5-视频 6-调查 7-纯文 8-视频+图文  9-GIF+图文  0-其他

show_style

内容样式:无图(纯文字)=6 一张大图=1  三站小图+=4 一张小图=2 一张大图两张小图+=3 图集+ = 5 
一张大图+=11   GIF大图+=12  视频(大图)+ = 13
来源于详情页相关推荐的商品,上报样式都为0(因为都是左文右图)

3.2.5 消息通知(notification)

事件标签:notification

标签

含义

action

动作:通知产生=1,通知弹出=2,通知点击=3,常驻通知展示(不重复上报,一天之内只报一次)=4

type

通知id:预警通知=1,天气预报(早=2,晚=3),常驻=4

ap_time

客户端弹出时间

content

备用字段

3.2.6 用户前台活跃(active_foreground)

事件标签: active_foreground

标签

含义

push_id

推送的消息的id,如果不是从推送消息打开,传空

access

1.push 2.icon 3.其他

3.2.7 用户后台活跃(active_background)

事件标签: active_background

标签

含义

active_source

1=upgrade,2=download(下载),3=plugin_upgrade

3.2.8 评论(comment)

描述:评论表

序号

字段名称

字段描述

字段类型

长度

允许空

缺省值

1

comment_id

评论表

int

10,0

 

 

2

userid

用户id

int

10,0

0

3

p_comment_id

父级评论id(为0则是一级评论,不为0则是回复)

int

10,0

 

4

content

评论内容

string

1000

 

5

addtime

创建时间

string

 

 

6

other_id

评论的相关id

int

10,0

 

7

praise_count

点赞数量

int

10,0

0

8

reply_count

回复数量

int

10,0

0

3.2.9 收藏(favorites)

描述:收藏

序号

字段名称

字段描述

字段类型

长度

允许空

缺省值

1

id

主键

int

10,0

 

 

2

course_id

商品id

int

10,0

0

3

userid

用户ID

int

10,0

0

4

add_time

创建时间

string

 

 

3.2.10 点赞(praise)

描述:所有的点赞表

序号

字段名称

字段描述

字段类型

长度

允许空

缺省值

1

id

主键id

int

10,0

 

 

2

userid

用户id

int

10,0

 

3

target_id

点赞的对象id

int

10,0

 

4

type

点赞类型 1问答点赞 2问答评论点赞 3 文章点赞数4 评论点赞

int

10,0

 

5

add_time

添加时间

string

 

 

3.2.11 错误日志数据

errorBrief

错误摘要

errorDetail

错误详情

3.3 启动日志数据

事件标签: start  action=1可以算成前台活跃

标签

含义

entry

入口: push=1widget=2icon=3notification=4, lockscreen_widget =5

open_ad_type

开屏广告类型开屏原生广告=1, 开屏插屏广告=2

action

状态:成功=1  失败=2

loading_time

加载时长:计算下拉开始到接口返回数据的时间,(开始加载报0,加载成功或加载失败才上报时间)

detail

失败码(没有则上报空)

extend1

失败的message(没有则上报空)

3.5 数据生成脚本

用户行为数据生成

3.5.1 创建Maven工程

1)创建log-collector

2)创建一个包名:com.newbies.appclient

3)在com.newbies.appclient包下创建一个类,AppMain。

4)在pom.xml文件中添加如下内容

<!--版本号统一-->
<properties>
    <slf4j.version>1.7.20</slf4j.version>
    <logback.version>1.0.7</logback.version>
</properties>

<dependencies>
    <!--阿里巴巴开源json解析框架-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.51</version>
    </dependency>

    <!--日志生成框架-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>${slf4j.version}</version>
    </dependency>

    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
        <version>${logback.version}</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>${logback.version}</version>
    </dependency>
</dependencies>

<!--编译打包插件-->
<build>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.3.2</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
        <plugin>
            <artifactId>maven-assembly-plugin </artifactId>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <archive>
                    <manifest>
                        <mainClass>com.newbies.appclient.AppMain</mainClass>
                    </manifest>
                </archive>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

注意:com.newbies.appclient.AppMain要和自己建的全类名一致。

3.5.2 公共字段Bean

1)创建包名:com.newbies.bean

2)在com.newbies.bean包下依次创建如下bean对象

package com.newbies.bean;

import java.io.Serializable;

/**
 * 公共日志
 */
public class AppBase implements Serializable{

    private String mid; // (String) 设备唯一标识
    private String uid; // (String) 用户uid
    private String vc;  // (String) versionCode,程序版本号
    private String vn;  // (String) versionName,程序版本名
    private String l;   // (String) 系统语言
    private String sr;  // (String) 渠道号,应用从哪个渠道来的。
    private String os;  // (String) Android系统版本
    private String ar;  // (String) 区域
    private String md;  // (String) 手机型号
    private String ba;  // (String) 手机品牌
    private String sv;  // (String) sdkVersion
    private String g;   // (String) gmail
    private String hw;  // (String) heightXwidth,屏幕宽高
    private String t;   // (String) 客户端日志产生时的时间
    private String nw;  // (String) 网络模式
    private String ln;  // (double) lng经度
    private String la;  // (double) lat 纬度

    public String getMid() {
        return mid;
    }

    public void setMid(String mid) {
        this.mid = mid;
    }

    public String getUid() {
        return uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    public String getVc() {
        return vc;
    }

    public void setVc(String vc) {
        this.vc = vc;
    }

    public String getVn() {
        return vn;
    }

    public void setVn(String vn) {
        this.vn = vn;
    }

    public String getL() {
        return l;
    }

    public void setL(String l) {
        this.l = l;
    }

    public String getSr() {
        return sr;
    }

    public void setSr(String sr) {
        this.sr = sr;
    }

    public String getOs() {
        return os;
    }

    public void setOs(String os) {
        this.os = os;
    }

    public String getAr() {
        return ar;
    }

    public void setAr(String ar) {
        this.ar = ar;
    }

    public String getMd() {
        return md;
    }

    public void setMd(String md) {
        this.md = md;
    }

    public String getBa() {
        return ba;
    }

    public void setBa(String ba) {
        this.ba = ba;
    }

    public String getSv() {
        return sv;
    }

    public void setSv(String sv) {
        this.sv = sv;
    }

    public String getG() {
        return g;
    }

    public void setG(String g) {
        this.g = g;
    }

    public String getHw() {
        return hw;
    }

    public void setHw(String hw) {
        this.hw = hw;
    }

    public String getT() {
        return t;
    }

    public void setT(String t) {
        this.t = t;
    }

    public String getNw() {
        return nw;
    }

    public void setNw(String nw) {
        this.nw = nw;
    }

    public String getLn() {
        return ln;
    }

    public void setLn(String ln) {
        this.ln = ln;
    }

    public String getLa() {
        return la;
    }

    public void setLa(String la) {
        this.la = la;
    }
}

3.5.3 启动日志Bean

package com.newbies.bean;

/**
 * 启动日志
 */
public class AppStart {
    private String entry;//入口: push=1,widget=2,icon=3,notification=4, lockscreen_widget =5
    private String open_ad_type;//开屏广告类型:  开屏原生广告=1, 开屏插屏广告=2
    private String action;//状态:成功=1  失败=2
    private String loading_time;//加载时长:计算下拉开始到接口返回数据的时间,(开始加载报0,加载成功或加载失败才上报时间)
    private String detail;//失败码(没有则上报空)
    private String extend1;//失败的message(没有则上报空)

    public String getEntry() {
        return entry;
    }

    public void setEntry(String entry) {
        this.entry = entry;
    }

    public String getOpen_ad_type() {
        return open_ad_type;
    }

    public void setOpen_ad_type(String open_ad_type) {
        this.open_ad_type = open_ad_type;
    }

    public String getAction() {
        return action;
    }

    public void setAction(String action) {
        this.action = action;
    }

    public String getLoading_time() {
        return loading_time;
    }

    public void setLoading_time(String loading_time) {
        this.loading_time = loading_time;
    }

    public String getDetail() {
        return detail;
    }

    public void setDetail(String detail) {
        this.detail = detail;
    }

    public String getExtend1() {
        return extend1;
    }

    public void setExtend1(String extend1) {
        this.extend1 = extend1;
    }
}

3.5.3 错误日志Bean

package com.newbies.bean;

/**
 * 错误日志
 */
public class AppErrorLog {

    private String errorBrief;    //错误摘要
    private String errorDetail;       //错误详情

    public String getErrorBrief() {
        return errorBrief;
    }

    public void setErrorBrief(String errorBrief) {
        this.errorBrief = errorBrief;
    }

    public String getErrorDetail() {
        return errorDetail;
    }

    public void setErrorDetail(String errorDetail) {
        this.errorDetail = errorDetail;
    }
}

3.5.4 事件日志Bean之商品点击

package com.newbies.bean;

/**
 * 商品点击日志
 */
public class AppDisplay {

    private String action;//动作:曝光商品=1,点击商品=2,
    private String newsid;//商品ID(服务端下发的ID)
    private String place;//顺序(第几条商品,第一条为0,第二条为1,如此类推)
    private String extend1;//曝光类型:1 - 首次曝光 2-重复曝光(没有使用)
    private String category;//分类ID(服务端定义的分类ID)

    public String getAction() {
        return action;
    }

    public void setAction(String action) {
        this.action = action;
    }

    public String getNewsid() {
        return newsid;
    }

    public void setNewsid(String newsid) {
        this.newsid = newsid;
    }

    public String getPlace() {
        return place;
    }

    public void setPlace(String place) {
        this.place = place;
    }

    public String getExtend1() {
        return extend1;
    }

    public void setExtend1(String extend1) {
        this.extend1 = extend1;
    }

    public String getCategory() {
        return category;
    }

    public void setCategory(String category) {
        this.category = category;
    }
}

3.5.5 事件日志Bean之商品详情页

package com.newbies.bean;

/**
 * 商品详情
 */
public class AppNewsDetail {

    private String entry;//页面入口来源:应用首页=1、push=2、详情页相关推荐=3
    private String action;//动作:开始加载=1,加载成功=2(pv),加载失败=3, 退出页面=4
    private String newsid;//商品ID(服务端下发的ID)
    private String showtype;//商品样式:0、无图1、一张大图2、两张图3、三张小图4、一张小图5、一张大图两张小图    来源于详情页相关推荐的商品,上报样式都为0(因为都是左文右图)
    private String news_staytime;//页面停留时长:从商品开始加载时开始计算,到用户关闭页面所用的时间。若中途用跳转到其它页面了,则暂停计时,待回到详情页时恢复计时。或中途划出的时间超过10分钟,则本次计时作废,不上报本次数据。如未加载成功退出,则报空。
    private String loading_time;//加载时长:计算页面开始加载到接口返回数据的时间 (开始加载报0,加载成功或加载失败才上报时间)
    private String type1;//加载失败码:把加载失败状态码报回来(报空为加载成功,没有失败)
    private String category;//分类ID(服务端定义的分类ID)

    public String getEntry() {
        return entry;
    }

    public void setEntry(String entry) {
        this.entry = entry;
    }

    public String getAction() {
        return action;
    }

    public void setAction(String action) {
        this.action = action;
    }

    public String getNewsid() {
        return newsid;
    }

    public void setNewsid(String newsid) {
        this.newsid = newsid;
    }

    public String getShowtype() {
        return showtype;
    }

    public void setShowtype(String showtype) {
        this.showtype = showtype;
    }

    public String getNews_staytime() {
        return news_staytime;
    }

    public void setNews_staytime(String news_staytime) {
        this.news_staytime = news_staytime;
    }

    public String getLoading_time() {
        return loading_time;
    }

    public void setLoading_time(String loading_time) {
        this.loading_time = loading_time;
    }

    public String getType1() {
        return type1;
    }

    public void setType1(String type1) {
        this.type1 = type1;
    }

    public String getCategory() {
        return category;
    }

    public void setCategory(String category) {
        this.category = category;
    }
}

3.5.6 事件日志Bean之商品列表页

package com.newbies.bean;

/**
 * 商品列表
 */
public class AppLoading {
    private String action;//动作:开始加载=1,加载成功=2,加载失败=3
    private String loading_time;//加载时长:计算下拉开始到接口返回数据的时间,(开始加载报0,加载成功或加载失败才上报时间)
    private String loading_way;//加载类型:1-读取缓存,2-从接口拉新数据   (加载成功才上报加载类型)
    private String extend1;//扩展字段 Extend1
    private String extend2;//扩展字段 Extend2
    private String type;//加载类型:自动加载=1,用户下拽加载=2,底部加载=3(底部条触发点击底部提示条/点击返回顶部加载)
    private String type1;//加载失败码:把加载失败状态码报回来(报空为加载成功,没有失败)

    public String getAction() {
        return action;
    }

    public void setAction(String action) {
        this.action = action;
    }

    public String getLoading_time() {
        return loading_time;
    }

    public void setLoading_time(String loading_time) {
        this.loading_time = loading_time;
    }

    public String getLoading_way() {
        return loading_way;
    }

    public void setLoading_way(String loading_way) {
        this.loading_way = loading_way;
    }

    public String getExtend1() {
        return extend1;
    }

    public void setExtend1(String extend1) {
        this.extend1 = extend1;
    }

    public String getExtend2() {
        return extend2;
    }

    public void setExtend2(String extend2) {
        this.extend2 = extend2;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getType1() {
        return type1;
    }

    public void setType1(String type1) {
        this.type1 = type1;
    }
}

3.5.7 事件日志Bean之广告

package com.newbies.bean;

/**
 * 广告
 */
public class AppAd {

    private String entry;//入口:商品列表页=1  应用首页=2 商品详情页=3
    private String action;//动作:请求广告=1 取缓存广告=2  广告位展示=3 广告展示=4 广告点击=5
    private String content;//状态:成功=1  失败=2
    private String detail;//失败码(没有则上报空)
    private String source;//广告来源:admob=1 facebook=2  ADX(百度)=3 VK(俄罗斯)=4
    private String behavior;//用户行为:    主动获取广告=1    被动获取广告=2
    private String newstype;//Type: 1- 图文 2-图集 3-段子 4-GIF 5-视频 6-调查 7-纯文 8-视频+图文  9-GIF+图文  0-其他
    private String show_style;//内容样式:无图(纯文字)=6 一张大图=1  三站小图+文=4 一张小图=2 一张大图两张小图+文=3 图集+文 = 5
                                //一张大图+文=11   GIF大图+文=12  视频(大图)+文 = 13
                                //来源于详情页相关推荐的商品,上报样式都为0(因为都是左文右图)

    public String getEntry() {
        return entry;
    }

    public void setEntry(String entry) {
        this.entry = entry;
    }

    public String getAction() {
        return action;
    }

    public void setAction(String action) {
        this.action = action;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getDetail() {
        return detail;
    }

    public void setDetail(String detail) {
        this.detail = detail;
    }

    public String getSource() {
        return source;
    }

    public void setSource(String source) {
        this.source = source;
    }

    public String getBehavior() {
        return behavior;
    }

    public void setBehavior(String behavior) {
        this.behavior = behavior;
    }

    public String getNewstype() {
        return newstype;
    }

    public void setNewstype(String newstype) {
        this.newstype = newstype;
    }

    public String getShow_style() {
        return show_style;
    }

    public void setShow_style(String show_style) {
        this.show_style = show_style;
    }
}

3.5.8 事件日志Bean之消息通知

package com.newbies.bean;

/**
 * 消息通知日志
 */
public class AppNotification {
    private String action;//动作:通知产生=1,通知弹出=2,通知点击=3,常驻通知展示(不重复上报,一天之内只报一次)=4
    private String type;//通知id:预警通知=1,天气预报(早=2,晚=3),常驻=4
    private String ap_time;//客户端弹出时间
    private String content;//备用字段

    public String getAction() {
        return action;
    }

    public void setAction(String action) {
        this.action = action;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getAp_time() {
        return ap_time;
    }

    public void setAp_time(String ap_time) {
        this.ap_time = ap_time;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

3.5.9 事件日志Bean之用户前台活跃

package com.newbies.bean;

/**
 * 用户前台活跃
 */
public class AppActive_foreground {
    private String push_id;//推送的消息的id,如果不是从推送消息打开,传空
    private String access;//1.push 2.icon 3.其他

    public String getPush_id() {
        return push_id;
    }

    public void setPush_id(String push_id) {
        this.push_id = push_id;
    }

    public String getAccess() {
        return access;
    }

    public void setAccess(String access) {
        this.access = access;
    }
}

3.5.10 事件日志Bean之用户后台活跃

package com.newbies.bean;

/**
 * 用户后台活跃
 */
public class AppActive_background {
    private String active_source;//1=upgrade,2=download(下载),3=plugin_upgrade

    public String getActive_source() {
        return active_source;
    }

    public void setActive_source(String active_source) {
        this.active_source = active_source;
    }
}

3.5.11 事件日志Bean之用户评论

package com.newbies.bean;

/**
 * 评论
 */
public class AppComment {

    private int comment_id;//评论表
    private int userid;//用户id
    private  int p_comment_id;//父级评论id(为0则是一级评论,不为0则是回复)
    private String content;//评论内容
    private String addtime;//创建时间
    private int other_id;//评论的相关id
    private int praise_count;//点赞数量
    private int reply_count;//回复数量

    public int getComment_id() {
        return comment_id;
    }

    public void setComment_id(int comment_id) {
        this.comment_id = comment_id;
    }

    public int getUserid() {
        return userid;
    }

    public void setUserid(int userid) {
        this.userid = userid;
    }

    public int getP_comment_id() {
        return p_comment_id;
    }

    public void setP_comment_id(int p_comment_id) {
        this.p_comment_id = p_comment_id;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getAddtime() {
        return addtime;
    }

    public void setAddtime(String addtime) {
        this.addtime = addtime;
    }

    public int getOther_id() {
        return other_id;
    }

    public void setOther_id(int other_id) {
        this.other_id = other_id;
    }

    public int getPraise_count() {
        return praise_count;
    }

    public void setPraise_count(int praise_count) {
        this.praise_count = praise_count;
    }

    public int getReply_count() {
        return reply_count;
    }

    public void setReply_count(int reply_count) {
        this.reply_count = reply_count;
    }
}

3.5.12 事件日志Bean之用户收藏

package com.newbies.bean;

/**
 * 收藏
 */
public class AppFavorites {
    private int id;//主键
    private int course_id;//商品id
    private int userid;//用户ID
    private String add_time;//创建时间

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getCourse_id() {
        return course_id;
    }

    public void setCourse_id(int course_id) {
        this.course_id = course_id;
    }

    public int getUserid() {
        return userid;
    }

    public void setUserid(int userid) {
        this.userid = userid;
    }

    public String getAdd_time() {
        return add_time;
    }

    public void setAdd_time(String add_time) {
        this.add_time = add_time;
    }
}

3.5.13 事件日志Bean之用户点赞

package com.newbies.bean;

/**
 * 点赞
 */
public class AppPraise {
    private int id; //主键id
    private int userid;//用户id
    private int target_id;//点赞的对象id
    private int type;//点赞类型 1问答点赞 2问答评论点赞 3 文章点赞数4 评论点赞
    private String add_time;//添加时间

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getUserid() {
        return userid;
    }

    public void setUserid(int userid) {
        this.userid = userid;
    }

    public int getTarget_id() {
        return target_id;
    }

    public void setTarget_id(int target_id) {
        this.target_id = target_id;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public String getAdd_time() {
        return add_time;
    }

    public void setAdd_time(String add_time) {
        this.add_time = add_time;
    }
}

3.5.14 主函数

日志行为数据模拟

AppMain类中添加如下内容:

package com.newbies.appclient;

import java.io.UnsupportedEncodingException;
import java.util.Random;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.newbies.bean.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 日志行为数据模拟
 */
public class AppMain {

    private final static Logger logger = LoggerFactory.getLogger(AppMain.class);
    static Random rand = new Random();

    /**
     * 1.appkey
     * 2.循环遍历次数
     * 3.uid的长度,默认是4
     * 4.商品id的长度,默认是4
     *
     * @param args
     */
    public static void main(String[] args) {

//      appkey的名称
        String appkey = args.length > 0 ? args[0] : "gmall";
//    循环遍历次数
        int loop_len = args.length > 1 ? Integer.parseInt(args[1]) : 10 * 100;
//    mid的长度
        int mid_length = args.length > 2 ? Integer.parseInt(args[2]) : 3;
//    uid的长度
        int uid_length = args.length > 3 ? Integer.parseInt(args[3]) : 3;
//    商品id的长度
        int newsid_length = args.length > 4 ? Integer.parseInt(args[4]) : 3;

        for (int i = 0; i < loop_len; i++) {

            JSONObject json = new JSONObject();
            json.put("ap", appkey);
            json.put("cm", generateComFields(mid_length,uid_length));

            JSONArray eventsArray = new JSONArray();

            int flag = rand.nextInt(2);
            switch (flag) {
                case (0):
                    //应用启动
                    if (rand.nextBoolean()) {
                        eventsArray.add(generateStart());
                    }
                    break;

                case (1):
                    // 事件日志
                    // 商品点击,展示
                    if (rand.nextBoolean()) {
                        eventsArray.add(generateDisplay(newsid_length));
                    }

                    // 商品详情页
                    if (rand.nextBoolean()) {
                        eventsArray.add(generateNewsDetail(newsid_length));
                    }

                    // 商品列表页
                    if (rand.nextBoolean()) {
                        eventsArray.add(generateNewList());
                    }

                    // 广告
                    if (rand.nextBoolean()) {
                        eventsArray.add(generateAd());
                    }

                    // 消息通知
                    if (rand.nextBoolean()) {
                        eventsArray.add(generateNotification());
                    }

                    // 用户前台活跃
                    if (rand.nextBoolean()) {
                        eventsArray.add(generatbeforeground());
                    }

                    // 用户后台活跃
                    if (rand.nextBoolean()) {
                        eventsArray.add(generateBackground());
                    }

                    //故障日志
                    if (rand.nextBoolean()) {
                        eventsArray.add(generateError());
                    }

                    // 用户评论
                    if (rand.nextBoolean()) {
                        eventsArray.add(generateComment());
                    }

                    // 用户收藏
                    if (rand.nextBoolean()) {
                        eventsArray.add(generateFavorites());
                    }

                    // 用户点赞
                    if (rand.nextBoolean()) {
                        eventsArray.add(generatePraise());
                    }
                    break;
            }

            json.put("et", eventsArray);

//          时间
            long millis = System.currentTimeMillis();

//          控制台打印
            logger.info(millis + "|" + json.toJSONString());
        }
    }

    /**
     * 公共字段设置
     *
     * @param uid_length
     * @return
     */
    static JSONObject generateComFields(int mid_length, int uid_length) {

        AppBase appBase = new AppBase();

//      设备id
        appBase.setMid('m' + getRandomDigits(mid_length));
//      用户id
        appBase.setUid('u' + getRandomDigits(uid_length));
//    程序版本号 5,6等
        appBase.setVc("" + rand.nextInt(20));
//    程序版本名 v1.1.1
        appBase.setVn("1." + rand.nextInt(4) + "." + rand.nextInt(10));
//      安卓系统版本
        appBase.setOs("8." + rand.nextInt(3) + "." + rand.nextInt(10));

//    语言  es,en,pt
        int flag = rand.nextInt(3);
        switch (flag) {
            case (0):
                appBase.setL("es");
                break;
            case (1):
                appBase.setL("en");
                break;
            case (2):
                appBase.setL("pt");
                break;
        }

//      渠道号   从哪个渠道来的
        appBase.setSr(getRandomChar(1));

//      区域
        flag = rand.nextInt(2);
        switch (flag) {
            case 0:
                appBase.setAr("BR");
            case 1:
                appBase.setAr("MX");
        }

//      手机品牌 ba ,手机型号 md,就取2位数字了
        flag = rand.nextInt(3);
        switch (flag) {
            case 0:
                appBase.setBa("Sumsung");
                appBase.setMd("sumsung-" + rand.nextInt(20));
                break;
            case 1:
                appBase.setBa("Huawei");
                appBase.setMd("Huawei-" + rand.nextInt(20));
                break;
            case 2:
                appBase.setBa("HTC");
                appBase.setMd("HTC-" + rand.nextInt(20));
                break;
        }

//      嵌入sdk的版本
        appBase.setSv("V2." + rand.nextInt(10) + "." + rand.nextInt(10));
//      gmail
        appBase.setG(getRandomCharAndNumr(8) + "@gmail.com");

//      屏幕宽高 hw
        flag = rand.nextInt(4);
        switch (flag) {
            case 0:
                appBase.setHw("640*960");
                break;
            case 1:
                appBase.setHw("640*1136");
                break;
            case 2:
                appBase.setHw("750*1134");
                break;
            case 3:
                appBase.setHw("1080*1920");
                break;
        }

//      客户端产生日志时间
        long millis = System.currentTimeMillis();
        appBase.setT("" + (millis - rand.nextInt(99999999)));

//    手机网络模式 3G,4G,WIFI
        flag = rand.nextInt(3);
        switch (flag) {
            case 0:
                appBase.setNw("3G");
                break;
            case 1:
                appBase.setNw("4G");
                break;
            case 2:
                appBase.setNw("WIFI");
                break;
        }

//      拉丁美洲 西经34°46′至西经117°09;北纬32°42′至南纬53°54′
//      经度
        appBase.setLn((-34 - rand.nextInt(83) - rand.nextInt(60) / 10.0) + "");
//      纬度
        appBase.setLa((32 - rand.nextInt(85) - rand.nextInt(60) / 10.0) + "");

        JSONObject common = (JSONObject) JSON.toJSON(appBase);

        return common;
    }

    /**
     * 商品展示事件
     *
     * @return
     */
    static JSONObject generateDisplay(int newsid_length) {

        AppDisplay appDisplay = new AppDisplay();

        boolean boolFlag = rand.nextInt(10) < 7 ? true : false;
//     动作:曝光商品=1,点击商品=2,
        if (boolFlag) {
            appDisplay.setAction("1");
        } else {
            appDisplay.setAction("2");
        }

//      商品id
        String newsId = 'n' + getRandomDigits(newsid_length);
        appDisplay.setNewsid(newsId);

//      顺序  设置成6条吧
        int flag = rand.nextInt(6);
        appDisplay.setPlace("" + flag);

//      曝光类型
        flag = 1 + rand.nextInt(2);
        appDisplay.setExtend1("" + flag);

//      分类
        flag = 1 + rand.nextInt(100);
        appDisplay.setCategory("" + flag);

        JSONObject jsonObject = (JSONObject) JSON.toJSON(appDisplay);

        return packEventJson("display", jsonObject);
    }

    /**
     * 商品详情页
     *
     * @param newsid_length
     * @return
     */
    static JSONObject generateNewsDetail(int newsid_length) {

        AppNewsDetail appNewsDetail = new AppNewsDetail();

//      页面入口来源
        int flag = 1 + rand.nextInt(3);
        appNewsDetail.setEntry(flag + "");

//      动作
        appNewsDetail.setAction("" + (rand.nextInt(4) + 1));

//      商品id
        appNewsDetail.setNewsid('n' + getRandomDigits(newsid_length));

//      商品来源类型
        flag = 1 + rand.nextInt(3);
        appNewsDetail.setShowtype(flag + "");

//      商品样式
        flag = rand.nextInt(6);
        appNewsDetail.setShowtype("" + flag);

//      页面停留时长
        flag = rand.nextInt(10) * rand.nextInt(7);
        appNewsDetail.setNews_staytime(flag + "");

//      加载时长
        flag = rand.nextInt(10) * rand.nextInt(7);
        appNewsDetail.setLoading_time(flag + "");

//       加载失败码
        flag = rand.nextInt(10);
        switch (flag) {
            case 1:
                appNewsDetail.setType1("102");
                break;
            case 2:
                appNewsDetail.setType1("201");
                break;
            case 3:
                appNewsDetail.setType1("325");
                break;
            case 4:
                appNewsDetail.setType1("433");
                break;
            case 5:
                appNewsDetail.setType1("542");
                break;
            default:
                appNewsDetail.setType1("");
                break;
        }

//      分类
        flag = 1 + rand.nextInt(100);
        appNewsDetail.setCategory("" + flag);

        JSONObject eventJson = (JSONObject) JSON.toJSON(appNewsDetail);

        return packEventJson("newsdetail", eventJson);
    }

    /**
     * 商品列表
     *
     * @return
     */
    static JSONObject generateNewList() {

        AppLoading appLoading = new AppLoading();

//      动作
        int flag = rand.nextInt(3) + 1;
        appLoading.setAction(flag + "");

//      加载时长
        flag = rand.nextInt(10) * rand.nextInt(7);
        appLoading.setLoading_time(flag + "");

//      失败码
        flag = rand.nextInt(10);
        switch (flag) {
            case 1:
                appLoading.setType1("102");
                break;
            case 2:
                appLoading.setType1("201");
                break;
            case 3:
                appLoading.setType1("325");
                break;
            case 4:
                appLoading.setType1("433");
                break;
            case 5:
                appLoading.setType1("542");
                break;
            default:
                appLoading.setType1("");
                break;
        }

//      页面  加载类型
        flag = 1 + rand.nextInt(2);
        appLoading.setLoading_way("" + flag);

//      扩展字段1
        appLoading.setExtend1("");

//      扩展字段2
        appLoading.setExtend2("");

//      用户加载类型
        flag = 1 + rand.nextInt(3);
        appLoading.setType("" + flag);

        JSONObject jsonObject = (JSONObject) JSON.toJSON(appLoading);

        return packEventJson("loading", jsonObject);
    }

    /**
     * 广告相关字段
     *
     * @return
     */
    static JSONObject generateAd() {

        AppAd appAd = new AppAd();

//      入口
        int flag = rand.nextInt(3) + 1;
        appAd.setEntry(flag + "");

//      动作
        flag = rand.nextInt(5) + 1;
        appAd.setAction(flag + "");

//      状态
        flag = rand.nextInt(10) > 6 ? 2 : 1;
        appAd.setContent(flag + "");

//      失败码
        flag = rand.nextInt(10);
        switch (flag) {
            case 1:
                appAd.setDetail("102");
                break;
            case 2:
                appAd.setDetail("201");
                break;
            case 3:
                appAd.setDetail("325");
                break;
            case 4:
                appAd.setDetail("433");
                break;
            case 5:
                appAd.setDetail("542");
                break;
            default:
                appAd.setDetail("");
                break;
        }

//      广告来源
        flag = rand.nextInt(4) + 1;
        appAd.setSource(flag + "");

//      用户行为
        flag = rand.nextInt(2) + 1;
        appAd.setBehavior(flag + "");

//      商品类型
        flag = rand.nextInt(10);
        appAd.setNewstype("" + flag);

//      展示样式
        flag = rand.nextInt(6);
        appAd.setShow_style("" + flag);

        JSONObject jsonObject = (JSONObject) JSON.toJSON(appAd);

        return packEventJson("ad", jsonObject);
    }

    /**
     * 启动日志
     *
     * @return
     */
    static JSONObject generateStart() {

        AppStart appStart = new AppStart();

//      入口
        int flag = rand.nextInt(5) + 1;
        appStart.setEntry(flag + "");

//      开屏广告类型
        flag = rand.nextInt(2) + 1;
        appStart.setOpen_ad_type(flag + "");

//      状态
        flag = rand.nextInt(10) > 8 ? 2 : 1;
        appStart.setAction(flag + "");

//      加载时长
        appStart.setLoading_time(rand.nextInt(20) + "");

//      失败码
        flag = rand.nextInt(10);
        switch (flag) {
            case 1:
                appStart.setDetail("102");
                break;
            case 2:
                appStart.setDetail("201");
                break;
            case 3:
                appStart.setDetail("325");
                break;
            case 4:
                appStart.setDetail("433");
                break;
            case 5:
                appStart.setDetail("542");
                break;
            default:
                appStart.setDetail("");
                break;
        }

        JSONObject jsonObject = (JSONObject) JSON.toJSON(appStart);

        return packEventJson("start", jsonObject);
    }

    /**
     * 消息通知
     *
     * @return
     */
    static JSONObject generateNotification() {

        AppNotification appNotification = new AppNotification();

        int flag = rand.nextInt(4) + 1;

//      动作
        appNotification.setAction(flag + "");

//      通知id
        flag = rand.nextInt(4) + 1;
        appNotification.setType(flag + "");

//      客户端弹时间
        appNotification.setAp_time((System.currentTimeMillis() - rand.nextInt(99999999)) + "");

//      备用字段
        appNotification.setContent("");

        JSONObject jsonObject = (JSONObject) JSON.toJSON(appNotification);

        return packEventJson("notification", jsonObject);
    }

    /**
     * 前台活跃
     *
     * @return
     */
    static JSONObject generatbeforeground() {

        AppActive_foreground appActive_foreground = new AppActive_foreground();

//      推送消息的id
        int flag = rand.nextInt(2);
        switch (flag) {
            case 1:
                appActive_foreground.setAccess(flag + "");
                break;
            default:
                appActive_foreground.setAccess("");
                break;
        }

//      1.push 2.icon 3.其他
        flag = rand.nextInt(3) + 1;
        appActive_foreground.setPush_id(flag + "");

        JSONObject jsonObject = (JSONObject) JSON.toJSON(appActive_foreground);

        return packEventJson("active_foreground", jsonObject);
    }

    /**
     * 后台活跃
     *
     * @return
     */
    static JSONObject generateBackground() {

        AppActive_background appActive_background = new AppActive_background();

//      启动源
        int flag = rand.nextInt(3) + 1;
        appActive_background.setActive_source(flag + "");

        JSONObject jsonObject = (JSONObject) JSON.toJSON(appActive_background);

        return packEventJson("active_background", jsonObject);
    }


    /**
     * 错误日志数据
     *
     * @return
     */
    static JSONObject generateError() {

        AppErrorLog appErrorLog = new AppErrorLog();

        String[] errorBriefs = {"at cn.lift.dfdf.web.AbstractBaseController.validInbound(AbstractBaseController.java:72)", "at cn.lift.appIn.control.CommandUtil.getInfo(CommandUtil.java:67)"};        //错误摘要
        String[] errorDetails = {"java.lang.NullPointerException\\n    " + "at cn.lift.appIn.web.AbstractBaseController.validInbound(AbstractBaseController.java:72)\\n " + "at cn.lift.dfdf.web.AbstractBaseController.validInbound", "at cn.lift.dfdfdf.control.CommandUtil.getInfo(CommandUtil.java:67)\\n " + "at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\\n" + " at java.lang.reflect.Method.invoke(Method.java:606)\\n"};        //错误详情

        //错误摘要
        appErrorLog.setErrorBrief(errorBriefs[rand.nextInt(errorBriefs.length)]);
        //错误详情
        appErrorLog.setErrorDetail(errorDetails[rand.nextInt(errorDetails.length)]);

        JSONObject jsonObject = (JSONObject) JSON.toJSON(appErrorLog);

        return packEventJson("error", jsonObject);
    }


    /**
     * 为各个事件类型的公共字段(时间、事件类型、Json数据)拼接
     *
     * @param eventName
     * @param jsonObject
     * @return
     */
    static JSONObject packEventJson(String eventName, JSONObject jsonObject) {

        JSONObject eventJson = new JSONObject();

        eventJson.put("ett", (System.currentTimeMillis() - rand.nextInt(99999999)) + "");
        eventJson.put("en", eventName);
        eventJson.put("kv", jsonObject);

        return eventJson;
    }

    /**
     * 获取定长度的数字
     *
     * @param leng
     * @return
     */
    static String getRandomDigits(int leng) {

        String result = "";
        for (int i = 0; i < leng; i++) {
            result += rand.nextInt(10);
        }

        return result;
    }

    /**
     * 获取随机字母组合
     *
     * @param length 字符串长度
     * @return
     */
    public static String getRandomChar(Integer length) {

        String str = "";
        Random random = new Random();

        for (int i = 0; i < length; i++) {
            // 字符串
            str += (char) (65 + random.nextInt(26));// 取得大写字母
        }

        return str;
    }

    /**
     * 获取随机字母数字组合
     *
     * @param length 字符串长度
     * @return
     */
    public static String getRandomCharAndNumr(Integer length) {

        String str = "";
        Random random = new Random();

        for (int i = 0; i < length; i++) {

            boolean b = random.nextBoolean();

            if (b) { // 字符串
                // int choice = random.nextBoolean() ? 65 : 97; 取得65大写字母还是97小写字母
                str += (char) (65 + random.nextInt(26));// 取得大写字母
            } else { // 数字
                str += String.valueOf(random.nextInt(10));
            }
        }

        return str;
    }

    /**
     * 收藏
     * @return
     */
    static public JSONObject generateFavorites() {

        AppFavorites favorites = new AppFavorites();

        favorites.setCourse_id(rand.nextInt(10));
        favorites.setUserid(rand.nextInt(10));
        favorites.setAdd_time((System.currentTimeMillis() - rand.nextInt(99999999))+"");

        JSONObject jsonObject = (JSONObject) JSON.toJSON(favorites);

        return packEventJson("favorites",jsonObject);
    }

    /**
     * 点赞
     * @return
     */
    static public JSONObject generatePraise() {

        AppPraise praise=new AppPraise();

        praise.setId(rand.nextInt(10));
        praise.setUserid(rand.nextInt(10));
        praise.setTarget_id(rand.nextInt(10));
        praise.setType(rand.nextInt(4)+1);
        praise.setAdd_time((System.currentTimeMillis() - rand.nextInt(99999999))+"" );

        JSONObject jsonObject = (JSONObject) JSON.toJSON(praise);

        return packEventJson("praise",jsonObject);
    }

    /**
     * 评论
     * @return
     */
    static public JSONObject generateComment() {

        AppComment comment = new AppComment();

        comment.setComment_id(rand.nextInt(10));
        comment.setUserid(rand.nextInt(10));
        comment.setP_comment_id(rand.nextInt(5));

        comment.setContent(getCONTENT());
        comment.setAddtime((System.currentTimeMillis() - rand.nextInt(99999999))+"");

        comment.setOther_id(rand.nextInt(10));
        comment.setPraise_count(rand.nextInt(1000));
        comment.setReply_count(rand.nextInt(200));

        JSONObject jsonObject = (JSONObject) JSON.toJSON(comment);

        return packEventJson("comment",jsonObject);
    }

    /**
     * 生成单个汉字
     *
     * @return
     */
    private static char getRandomChar() {

        String str = "";
        int hightPos; //
        int lowPos;

        Random random = new Random();

        //随机生成汉子的两个字节
        hightPos = (176 + Math.abs(random.nextInt(39)));
        lowPos = (161 + Math.abs(random.nextInt(93)));

        byte[] b = new byte[2];
        b[0] = (Integer.valueOf(hightPos)).byteValue();
        b[1] = (Integer.valueOf(lowPos)).byteValue();

        try {
            str = new String(b, "GBK");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            System.out.println("错误");
        }

        return str.charAt(0);
    }

    /**
     * 拼接成多个汉字
     * @return
     */
    static public String getCONTENT() {

        String str = "";

        for (int i = 0; i < rand.nextInt(100);i++) {
            str+=getRandomChar();
        }

        return str;
    }
}

3.5.15 配置日志打印Logback

Spring boot x集成logback与SL4J一起使用,在磁盘打印日志,然后收集。Logback的配置文件由一个个appender组成。一个appender包括:
    日志打印的方式(控制台,文件,远程)。其中远程的方式是将所有日志发送到日志服务器,然后统一管理。
    日志的格式。(线程,调用方法类,级别,日志内容大小。)
    日志的路径。
    保留的时间长度。
    日志切分的大小
异步日志打印非常关键,这样在高并发的时候不会让请求等待就可以返回。其中日志分为了3种。
    控制台
    错误日志输出(后台需要查看)
    标准日志输出
具体使用:
1)在resources文件夹下创建logback.xml文件。
2)在logback.xml文件中填写如下配置
 

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
   <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径 -->
   <property name="LOG_HOME" value="/tmp/logs/" />
    <!--配置规则类的位置-->
    <!--<conversionRule conversionWord="ip" converterClass="com.newbies.appclient.IPLogConfig" />-->
   <!-- 控制台输出 -->
   <appender name="STDOUT"
      class="ch.qos.logback.core.ConsoleAppender">
      <encoder
         class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
         <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
         <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
      </encoder>
   </appender>
   
   <!-- 按照每天生成日志文件。存储事件日志 -->
   <appender name="FILE"
      class="ch.qos.logback.core.rolling.RollingFileAppender">
      <!-- <File>${LOG_HOME}/app.log</File>设置日志不超过${log.max.size}时的保存路径,注意,如果是web项目会保存到Tomcat的bin目录 下 -->  
      <rollingPolicy
         class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
         <!--日志文件输出的文件名 -->
         <FileNamePattern>${LOG_HOME}/app-%d{yyyy-MM-dd}.log</FileNamePattern>
         <!--日志文件保留天数 -->
         <MaxHistory>30</MaxHistory>
      </rollingPolicy>
      <encoder
         class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
         <!--<pattern>%ip|%msg%n</pattern>-->
         <pattern>%msg%n</pattern>
      </encoder>
      <!--日志文件最大的大小 -->
      <triggeringPolicy
         class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
         <MaxFileSize>10MB</MaxFileSize>
      </triggeringPolicy>
   </appender>

    <!--异步打印日志-->
    <appender name ="ASYNC_FILE" class= "ch.qos.logback.classic.AsyncAppender">
        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
        <discardingThreshold >0</discardingThreshold>
        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
        <queueSize>512</queueSize>
        <!-- 添加附加的appender,最多只能添加一个 -->
        <appender-ref ref = "FILE"/>
    </appender>

    <!-- 日志输出级别 -->
   <root level="INFO">
      <appender-ref ref="STDOUT" />
      <appender-ref ref="ASYNC_FILE" />
      <appender-ref ref="error" />
   </root>
</configuration>

3.5.16 打包

1)采用maven对程序打包

2)采用带依赖的jar包。包含了程序运行需要的所有依赖。

3.5.17 启动

1)代码参数说明

//  appkey的名称
String appkey = args.length > 0 ? args[0] : "gmall";
//  循环遍历次数
int loop_len = args.length > 1 ? Integer.parseInt(args[1]) : 10 * 100;
//  mid的长度
int mid_length = args.length > 2 ? Integer.parseInt(args[2]) : 3;
//  uid的长度
int uid_length = args.length > 3 ? Integer.parseInt(args[3]) : 3;
//  商品id的长度
int newsid_length = args.length > 4 ? Integer.parseInt(args[4]) : 3;

这里面有四个参数:
    Appkey的名称,比如我有多个应用,那么我可以生成多个应用的日志
    循环遍历次数,就是生成多少条日志
    mid的长度,设备的长度,决定有多少设备(数字)
    Uid的长度,用户的长度,决定有多少用户(数字)
    商品id的长度,决定有多少商品(数字)
2)将生成的jar包log-collector-0.0.1-SNAPSHOT-jar-with-dependencies.jar拷贝到hadoop102、服务器上,并同步到hadoop103的/opt/module路径下,
 

[newbies@hadoop102 module]$ xsync log-collector-1.0-SNAPSHOT-jar-with-dependencies.jar

3)在hadoop102上执行jar程序

[newbies@hadoop102 module]$ java -classpath log-collector-1.0-SNAPSHOT-jar-with-dependencies.jar com.newbies.appclient.AppMain  >/opt/module/test.log

4)在/tmp/logs路径下查看生成的日志文件

[newbies@hadoop102 module]$ cd /tmp/logs/
[newbies@hadoop102 logs]$ ls
app-2019-02-10.log

3.5.18 日志生成集群启动脚本

1)Linux环境变量配置:

(1)修改/etc/profile文件:所有用户的shell都有权使用这些环境变量。

(2)修改~/.bashrc文件:针对某一个特定的用户,如果你需要给某个用户权限使用这些环境变量,你只需要修改其个人用户主目录下的.bashrc文件就可以了。

(3)配置登录远程服务器立即source一下环境变量

[newbies@hadoop102 ~]$ cat /etc/profile >> .bashrc
[newbies@hadoop103 ~]$ cat /etc/profile >> .bashrc
[newbies@hadoop104 ~]$ cat /etc/profile >> .bashrc

2)具体脚本编写

       在/home/newbies/bin目录下创建脚本lg.sh

[newbies@hadoop102 bin]$ vim lg.sh

       在脚本中编写如下内容

#! /bin/bash

	for i in hadoop102 hadoop103 
	do
		ssh $i "java -classpath /opt/module/log-collector-1.0-SNAPSHOT-jar-with-dependencies.jar com.newbies.appclient.AppMain  >/opt/module/test.log &"
	done

修改脚本执行权限

[newbies@hadoop102 bin]$ chmod 777 lg.sh

启动脚本

[newbies@hadoop102 module]$ lg.sh 

分别在hadoop102、hadoop103的/tmp/logs目录上查看生成的数据

[newbies@hadoop102 logs]$ ls
app-2019-02-10.log
[newbies@hadoop103 logs]$ ls
app-2019-02-10.log

3.5.19 集群时间同步修改脚本

       在/home/newbies/bin目录下创建脚本dt.sh

[newbies@hadoop102 bin]$ vim dt.sh

       在脚本中编写如下内容

#!/bin/bash

log_date=$1

for i in hadoop102 hadoop103 hadoop104
do
	ssh -t $i "sudo date -s $log_date"
done

修改脚本执行权限

[newbies@hadoop102 bin]$ chmod 777 dt.sh

启动脚本

[newbies@hadoop102 bin]$ dt.sh 2019-2-10

3.5.20 集群所有进程查看脚本

       在/home/newbies/bin目录下创建脚本xcall.sh

[newbies@hadoop102 bin]$ vim xcall.sh

       在脚本中编写如下内容

#! /bin/bash

for i in hadoop102 hadoop103 hadoop104
do
        echo --------- $i ----------
        ssh $i "$*"
done

修改脚本执行权限

[newbies@hadoop102 bin]$ chmod 777 xcall.sh

启动脚本

[newbies@hadoop102 bin]$ xcall.sh jps

第4章 数据采集模块

4.1 Hadoop安装

==============================Begin======================

第3章 Hadoop运行环境搭建(开发重点)

3.1 虚拟机环境准备

1.    克隆虚拟机                                                     

2.    修改克隆虚拟机的静态IP

3.    修改主机名

4.    关闭防火墙

5.    创建atguigu用户

6.    配置atguigu用户具有root权限(详见《尚硅谷大数据技术之Linux》)

7.在/opt目录下创建文件夹

(1)在/opt目录下创建module、software文件夹

[newbies@hadoop101 opt]$ sudo mkdir module
[newbies@hadoop101 opt]$ sudo mkdir software

(2)修改module、software文件夹的所有者cd

[newbies@hadoop101 opt]$ sudo chown newbies:newbies module/ software/
[newbies@hadoop101 opt]$ ll
总用量 8
drwxr-xr-x. 2 newbies newbies 4096 1月  17 14:37 module
drwxr-xr-x. 2 newbies newbies 4096 1月  17 14:38 software

3.2 安装JDK

1.    卸载现有JDK

(1)查询是否安装Java软件:

[newbies@hadoop101 opt]$ rpm -qa | grep java

(2)如果安装的版本低于1.7,卸载该JDK:

[newbies@hadoop101 opt]$ sudo rpm -e 软件包

(3)查看JDK安装路径:

[newbies@hadoop101 ~]$ which java

2.    用SecureCRT工具将JDK导入到opt目录下面的software文件夹下面

导入JDK

 

“alt+p”进入sftp模式

选择jdk1.8拖入

1.    在Linux系统下的opt目录中查看软件包是否导入成功

[newbies@hadoop101 opt]$ cd software/
[newbies@hadoop101 software]$ ls
hadoop-2.7.2.tar.gz  jdk-8u144-linux-x64.tar.gz

2.    解压JDK到/opt/module目录下

[newbies@hadoop101 software]$ tar -zxvf jdk-8u144-linux-x64.tar.gz -C /opt/module/

3.    配置JDK环境变量
    (1)先获取JDK路径

[newbies@hadoop101 jdk1.8.0_144]$ pwd
/opt/module/jdk1.8.0_144

  (2)打开/etc/profile文件

[newbies@hadoop101 software]$ sudo vi /etc/profile

在profile文件末尾添加JDK路径

#JAVA_HOME
export JAVA_HOME=/opt/module/jdk1.8.0_144
export PATH=$PATH:$JAVA_HOME/bin

 (3)保存后退出

:wq

 (4)让修改后的文件生效

[newbies@hadoop101 jdk1.8.0_144]$ source /etc/profile

4.    测试JDK是否安装成功

[newbies@hadoop101 jdk1.8.0_144]# java -version
java version "1.8.0_144"

    注意:重启(如果java -version可以用就不用重启)
 

[newbies@hadoop101 jdk1.8.0_144]$ sync
[newbies@hadoop101 jdk1.8.0_144]$ sudo reboot

3.3 安装Hadoop

0.  Hadoop下载地址:

https://archive.apache.org/dist/hadoop/common/hadoop-2.7.2/

  1. 用SecureCRT工具将hadoop-2.7.2.tar.gz导入到opt目录下面的software文件夹下面

切换到sftp连接页面,选择Linux下编译的hadoop jar包拖入

2.    进入到Hadoop安装包路径下

[newbies@hadoop101 ~]$ cd /opt/software/

3.    解压安装文件到/opt/module下面

[newbies@hadoop101 software]$ tar -zxvf hadoop-2.7.2.tar.gz -C /opt/module/

4.    查看是否解压成功

[newbies@hadoop101 software]$ ls /opt/module/
hadoop-2.7.2

5.    将Hadoop添加到环境变量
    (1)获取Hadoop安装路径

[newbies@hadoop101 hadoop-2.7.2]$ pwd
/opt/module/hadoop-2.7.2

 (2)打开/etc/profile文件

[newbies@hadoop101 hadoop-2.7.2]$ sudo vi /etc/profile

在profile文件末尾添加JDK路径:(shitf+g)

##HADOOP_HOME
export HADOOP_HOME=/opt/module/hadoop-2.7.2
export PATH=$PATH:$HADOOP_HOME/bin
export PATH=$PATH:$HADOOP_HOME/sbin

(3)保存后退出

:wq

(4)让修改后的文件生效

[newbies@ hadoop101 hadoop-2.7.2]$ source /etc/profile

6.    测试是否安装成功

[newbies@hadoop101 hadoop-2.7.2]$ hadoop version
Hadoop 2.7.2

7.    重启(如果Hadoop命令不能用再重启)

[newbies@ hadoop101 hadoop-2.7.2]$ sync
[newbies@ hadoop101 hadoop-2.7.2]$ sudo reboot

3.4 Hadoop目录结构

1、查看Hadoop目录结构

[newbies@hadoop101 hadoop-2.7.2]$ ll
总用量 52
drwxr-xr-x. 2 newbies newbies  4096 5月  22 2017 bin
drwxr-xr-x. 3 newbies newbies  4096 5月  22 2017 etc
drwxr-xr-x. 2 newbies newbies  4096 5月  22 2017 include
drwxr-xr-x. 3 newbies newbies  4096 5月  22 2017 lib
drwxr-xr-x. 2 newbies newbies  4096 5月  22 2017 libexec
-rw-r--r--. 1 newbies newbies 15429 5月  22 2017 LICENSE.txt
-rw-r--r--. 1 newbies newbies   101 5月  22 2017 NOTICE.txt
-rw-r--r--. 1 newbies newbies  1366 5月  22 2017 README.txt
drwxr-xr-x. 2 newbies newbies  4096 5月  22 2017 sbin
drwxr-xr-x. 4 newbies newbies  4096 5月  22 2017 share

2、重要目录

(1)bin目录:存放对Hadoop相关服务(HDFS,YARN)进行操作的脚本

(2)etc目录:Hadoop的配置文件目录,存放Hadoop的配置文件

(3)lib目录:存放Hadoop的本地库(对数据进行压缩解压缩功能)

(4)sbin目录:存放启动或停止Hadoop相关服务的脚本

(5)share目录:存放Hadoop的依赖jar包、文档、和官方案例

第4章 Hadoop运行模式

Hadoop运行模式包括:本地模式、伪分布式模式以及完全分布式模式。

Hadoop官方网站:http://hadoop.apache.org/

4.2 伪分布式运行模式

4.2.1 启动HDFS并运行MapReduce程序

1.    分析

       (1)配置集群

       (2)启动、测试集群增、删、查

       (3)执行WordCount案例

2.    执行步骤

(1)配置集群

              (a)配置:hadoop-env.sh

Linux系统中获取JDK的安装路径:

[newbies@ hadoop101 ~]# echo $JAVA_HOME
/opt/module/jdk1.8.0_144

修改JAVA_HOME 路径:

export JAVA_HOME=/opt/module/jdk1.8.0_144

(b)配置:core-site.xml

<!-- 指定HDFS中NameNode的地址 -->
<property>
<name>fs.defaultFS</name>
    <value>hdfs://hadoop101:9000</value>
</property>

<!-- 指定Hadoop运行时产生文件的存储目录 -->
<property>
	<name>hadoop.tmp.dir</name>
	<value>/opt/module/hadoop-2.7.2/data/tmp</value>
</property>

(c)配置:hdfs-site.xml

<!-- 指定HDFS副本的数量 -->
<property>
	<name>dfs.replication</name>
	<value>1</value>
</property>

(2)启动集群

(a)格式化NameNode(第一次启动时格式化,以后就不要总格式化)

[newbies@hadoop101 hadoop-2.7.2]$ bin/hdfs namenode -format

(b)启动NameNode

[newbies@hadoop101 hadoop-2.7.2]$ sbin/hadoop-daemon.sh start namenode

(c)启动DataNode

[newbies@hadoop101 hadoop-2.7.2]$ sbin/hadoop-daemon.sh start datanode

(3)查看集群

              (a)查看是否启动成功

[newbies@hadoop101 hadoop-2.7.2]$ jps
13586 NameNode
13668 DataNode
13786 Jps

注意:jpsJDK中的命令,不是Linux命令。不安装JDK不能使用jps

              (b)web端查看HDFS文件系统

http://hadoop101:50070/dfshealth.html#tab-overview

注意:如果不能查看,看如下帖子处理

http://www.cnblogs.com/zlslch/p/6604189.html

(c)查看产生的Log日志

                说明:在企业中遇到Bug时,经常根据日志提示信息去分析问题、解决Bug

当前目录:/opt/module/hadoop-2.7.2/logs

[newbies@hadoop101 logs]$ ls
hadoop-newbies-datanode-hadoop.newbies.com.log
hadoop-newbies-datanode-hadoop.newbies.com.out
hadoop-newbies-namenode-hadoop.newbies.com.log
hadoop-newbies-namenode-hadoop.newbies.com.out
SecurityAuth-root.audit
[newbies@hadoop101 logs]# cat hadoop-newbies-datanode-hadoop101.log

d)思考:为什么不能一直格式化NameNode,格式化NameNode,要注意什么?

[newbies@hadoop101 hadoop-2.7.2]$ cd data/tmp/dfs/name/current/
[newbies@hadoop101 current]$ cat VERSION
clusterID=CID-f0330a58-36fa-4a2a-a65f-2688269b5837

[newbies@hadoop101 hadoop-2.7.2]$ cd data/tmp/dfs/data/current/
clusterID=CID-f0330a58-36fa-4a2a-a65f-2688269b5837

注意:格式化NameNode,会产生新的集群id,导致NameNodeDataNode的集群id不一致,集群找不到已往数据。所以,格式NameNode时,一定要先删除data数据和log日志,然后再格式化NameNode

(4)操作集群

              (a)在HDFS文件系统上创建一个input文件夹

[newbies@hadoop101 hadoop-2.7.2]$ bin/hdfs dfs -mkdir -p /user/newbies/input

(b)将测试文件内容上传到文件系统上

[newbies@hadoop101 hadoop-2.7.2]$bin/hdfs dfs -put wcinput/wc.input
  /user/newbies/input/

(c)查看上传的文件是否正确

[newbies@hadoop101 hadoop-2.7.2]$ bin/hdfs dfs -ls  /user/newbies/input/
[newbies@hadoop101 hadoop-2.7.2]$ bin/hdfs dfs -cat  /user/newbies/ input/wc.input

(d)运行MapReduce程序

[newbies@hadoop101 hadoop-2.7.2]$ bin/hadoop jar
share/hadoop/mapreduce/hadoop-mapreduce-examples-2.7.2.jar wordcount /user/newbies/input/ /user/newbies/output

(e)查看输出结果

命令行查看:

[newbies@hadoop101 hadoop-2.7.2]$ bin/hdfs dfs -cat /user/newbies/output/*

浏览器查看

(f)将测试文件内容下载到本地

[newbies@hadoop101 hadoop-2.7.2]$ hdfs dfs -get /user/newbies/output/part-r-00000 ./wcoutput/

(g)删除输出结果

[newbies@hadoop101 hadoop-2.7.2]$ hdfs dfs -rm -r /user/newbies/output

4.2.2 启动YARN并运行MapReduce程序

1.    分析

       (1)配置集群在YARN上运行MR

       (2)启动、测试集群增、删、查

       (3)在YARN上执行WordCount案例

2.    执行步骤     

       (1)配置集群

              (a)配置yarn-env.sh

配置一下JAVA_HOME

export JAVA_HOME=/opt/module/jdk1.8.0_144

(b)配置yarn-site.xml

<!-- Reducer获取数据的方式 -->
<property>
 		<name>yarn.nodemanager.aux-services</name>
 		<value>mapreduce_shuffle</value>
</property>

<!-- 指定YARN的ResourceManager的地址 -->
<property>
<name>yarn.resourcemanager.hostname</name>
<value>hadoop101</value>
</property>

(c)配置:mapred-env.sh

配置一下JAVA_HOME

export JAVA_HOME=/opt/module/jdk1.8.0_144

(d)配置: (对mapred-site.xml.template重新命名为) mapred-site.xml

[newbies@hadoop101 hadoop]$ mv mapred-site.xml.template mapred-site.xml
[newbies@hadoop101 hadoop]$ vi mapred-site.xml

<!-- 指定MR运行在YARN上 -->
<property>
		<name>mapreduce.framework.name</name>
		<value>yarn</value>
</property>

(2)启动集群

(a)启动前必须保证NameNode和DataNode已经启动

(b)启动ResourceManager

[newbies@hadoop101 hadoop-2.7.2]$ sbin/yarn-daemon.sh start resourcemanager

(c)启动NodeManager

[newbies@hadoop101 hadoop-2.7.2]$ sbin/yarn-daemon.sh start nodemanager

3)集群操作

(a)YARN的浏览器页面查看,如图2-35所示

http://hadoop101:8088/cluster

YARN的浏览器页面

 

(b)删除文件系统上的output文件

[newbies@hadoop101 hadoop-2.7.2]$ bin/hdfs dfs -rm -R /user/newbies/output

(c)执行MapReduce程序

[newbies@hadoop101 hadoop-2.7.2]$ bin/hadoop jar
 share/hadoop/mapreduce/hadoop-mapreduce-examples-2.7.2.jar wordcount /user/newbies/input  /user/newbies/output

(d)查看运行结果,如图2-36所示

[newbies@hadoop101 hadoop-2.7.2]$ bin/hdfs dfs -cat /user/newbies/output/*
查看运行结果

 

4.2.3 配置历史服务器

为了查看程序的历史运行情况,需要配置一下历史服务器。具体配置步骤如下:

1.    配置mapred-site.xml

[newbies@hadoop101 hadoop]$ vi mapred-site.xml

在该文件里面增加如下配置。

<!-- 历史服务器端地址 -->
<property>
<name>mapreduce.jobhistory.address</name>
<value>hadoop101:10020</value>
</property>

<!-- 历史服务器web端地址 -->
<property>
    <name>mapreduce.jobhistory.webapp.address</name>
    <value>hadoop101:19888</value>
</property>

2.    启动历史服务器

[newbies@hadoop101 hadoop-2.7.2]$ sbin/mr-jobhistory-daemon.sh start historyserver

3.    查看历史服务器是否启动

[newbies@hadoop101 hadoop-2.7.2]$ jps

4.    查看JobHistory

http://hadoop101:19888/jobhistory

4.2.4 配置日志的聚集

日志聚集概念:应用运行完成以后,将程序运行日志信息上传到HDFS系统上。

日志聚集功能好处:可以方便的查看到程序运行详情,方便开发调试。

注意:开启日志聚集功能,需要重新启动NodeManager ResourceManagerHistoryManager

开启日志聚集功能具体步骤如下:

  1. 配置yarn-site.xml
[newbies@hadoop101 hadoop]$ vi yarn-site.xml

在该文件里面增加如下配置。

<!-- 日志聚集功能使能 -->
<property>
<name>yarn.log-aggregation-enable</name>
<value>true</value>
</property>

<!-- 日志保留时间设置7天 -->
<property>
<name>yarn.log-aggregation.retain-seconds</name>
<value>604800</value>
</property>

2.    关闭NodeManager 、ResourceManager和HistoryManager

[newbies@hadoop101 hadoop-2.7.2]$ sbin/yarn-daemon.sh stop resourcemanager
[newbies@hadoop101 hadoop-2.7.2]$ sbin/yarn-daemon.sh stop nodemanager
[newbies@hadoop101 hadoop-2.7.2]$ sbin/mr-jobhistory-daemon.sh stop historyserver

3.    启动NodeManager 、ResourceManager和HistoryManager

[newbies@hadoop101 hadoop-2.7.2]$ sbin/yarn-daemon.sh start resourcemanager
[newbies@hadoop101 hadoop-2.7.2]$ sbin/yarn-daemon.sh start nodemanager
[newbies@hadoop101 hadoop-2.7.2]$ sbin/mr-jobhistory-daemon.sh start historyserver

4.    删除HDFS上已经存在的输出文件

[newbies@hadoop101 hadoop-2.7.2]$ bin/hdfs dfs -rm -R /user/newbies/output

5.    执行WordCount程序

[newbies@hadoop101 hadoop-2.7.2]$ hadoop jar
 share/hadoop/mapreduce/hadoop-mapreduce-examples-2.7.2.jar wordcount /user/newbies/input /user/newbies/output

6.    查看日志
http://hadoop101:19888/jobhistory

job运行情况
查看日志

 

4.2.5 配置文件说明

Hadoop配置文件分两类:默认配置文件和自定义配置文件,只有用户想修改某一默认配置值时,才需要修改自定义配置文件,更改相应属性值。

(1)默认配置文件:

要获取的默认文件

文件存放在Hadoop的jar包中的位置

[core-default.xml]

hadoop-common-2.7.2.jar/ core-default.xml

[hdfs-default.xml]

hadoop-hdfs-2.7.2.jar/ hdfs-default.xml

[yarn-default.xml]

hadoop-yarn-common-2.7.2.jar/ yarn-default.xml

[mapred-default.xml]

hadoop-mapreduce-client-core-2.7.2.jar/ mapred-default.xml

(2)自定义配置文件:

       core-site.xml、hdfs-site.xml、yarn-site.xml、mapred-site.xml四个配置文件存放在$HADOOP_HOME/etc/hadoop这个路径上,用户可以根据项目需求重新进行修改配置。

4.3 完全分布式运行模式(开发重点)

分析:

       1)准备3台客户机(关闭防火墙、静态ip、主机名称

       2)安装JDK

       3)配置环境变量

       4)安装Hadoop

       5)配置环境变量

6)配置集群

7)单点启动

       8)配置ssh

       9)群起并测试集群

4.3.1 虚拟机准备

4.3.2 编写集群分发脚本xsync

1.    scp(secure copy)安全拷贝

(1)scp定义:

scp可以实现服务器与服务器之间的数据拷贝。(from server1 to server2)

       (2)基本语法

scp    -r          $pdir/$fname              $user@hadoop$host:$pdir/$fname

命令   递归       要拷贝的文件路径/名称    目的用户@主机:目的路径/名称

(3)案例实操

(a)在hadoop101上,将hadoop101中/opt/module目录下的软件拷贝到hadoop102上。

[newbies@hadoop101 /]$ scp -r /opt/module  root@hadoop102:/opt/module

(b)在hadoop103上,将hadoop101服务器上的/opt/module目录下的软件拷贝到hadoop103上。

[newbies@hadoop103 opt]$sudo scp -r newbies@hadoop101:/opt/module root@hadoop103:/opt/module

(c)在hadoop103上操作将hadoop101中/opt/module目录下的软件拷贝到hadoop104上。

[newbies@hadoop103 opt]$ scp -r newbies@hadoop101:/opt/module root@hadoop104:/opt/module

注意:拷贝过来的/opt/module目录,别忘了在hadoop102hadoop103hadoop104上修改所有文件的,所有者和所有者组。sudo chown newbies:newbies -R /opt/module

(d)将hadoop101中/etc/profile文件拷贝到hadoop102的/etc/profile上。

[newbies@hadoop101 ~]$ sudo scp /etc/profile root@hadoop102:/etc/profile

(e)将hadoop101中/etc/profile文件拷贝到hadoop103的/etc/profile上。

[newbies@hadoop101 ~]$ sudo scp /etc/profile root@hadoop103:/etc/profile

(f)将hadoop101中/etc/profile文件拷贝到hadoop104的/etc/profile上。

[newbies@hadoop101 ~]$ sudo scp /etc/profile root@hadoop104:/etc/profile

注意:拷贝过来的配置文件别忘了source一下/etc/profile,。

2.    rsync 远程同步工具

rsync主要用于备份和镜像。具有速度快、避免复制相同内容和支持符号链接的优点。

rsyncscp区别:rsync做文件的复制要比scp的速度快,rsync只对差异文件做更新。scp是把所有文件都复制过去。

       (1)基本语法

rsync    -rvl       $pdir/$fname              $user@hadoop$host:$pdir/$fname

命令   选项参数   要拷贝的文件路径/名称    目的用户@主机:目的路径/名称

         选项参数说明

选项

功能

-r

递归

-v

显示复制过程

-l

拷贝符号连接

(2)案例实操

              (a)把hadoop101机器上的/opt/software目录同步到hadoop102服务器的root用户下的/opt/目录

[newbies@hadoop101 opt]$ rsync -rvl /opt/software/ root@hadoop102:/opt/software

3.    xsync集群分发脚本

(1)需求:循环复制文件到所有节点的相同目录下

       (2)需求分析:

(a)rsync命令原始拷贝:

rsync  -rvl     /opt/module              root@hadoop103:/opt/

              (b)期望脚本:

xsync要同步的文件名称

              c)说明:在/home/newbies/bin这个目录下存放的脚本,newbies用户可以在系统任何地方直接执行。

(3)脚本实现

(a)在/home/newbies目录下创建bin目录,并在bin目录下xsync创建文件,文件内容如下:

[newbies@hadoop102 ~]$ mkdir bin
[newbies@hadoop102 ~]$ cd bin/
[newbies@hadoop102 bin]$ touch xsync
[newbies@hadoop102 bin]$ vi xsync

在该文件中编写如下代码

#!/bin/bash
#1 获取输入参数个数,如果没有参数,直接退出
pcount=$#
if((pcount==0)); then
echo no args;
exit;
fi

#2 获取文件名称
p1=$1
fname=`basename $p1`
echo fname=$fname

#3 获取上级目录到绝对路径
pdir=`cd -P $(dirname $p1); pwd`
echo pdir=$pdir

#4 获取当前用户名称
user=`whoami`

#5 循环
for((host=103; host<105; host++)); do
        echo ------------------- hadoop$host --------------
        rsync -rvl $pdir/$fname $user@hadoop$host:$pdir
done

(b)修改脚本 xsync 具有执行权限

[newbies@hadoop102 bin]$ chmod 777 xsync

(c)调用脚本形式:xsync 文件名称

[newbies@hadoop102 bin]$ xsync /home/newbies/bin

注意:如果将xsync放到/home/newbies/bin目录下仍然不能实现全局使用,可以将xsync移动到/usr/local/bin目录下。

4.3.3 集群配置

1.    集群部署规划

 

hadoop102

hadoop103

hadoop104

HDFS

 

NameNode

DataNode

 

DataNode

SecondaryNameNode

DataNode

YARN

 

NodeManager

ResourceManager

NodeManager

 

NodeManager

2.    配置集群

       (1)核心配置文件

配置core-site.xml

[newbies@hadoop102 hadoop]$ vi core-site.xml

在该文件中编写如下配置

<!-- 指定HDFS中NameNode的地址 -->
<property>
		<name>fs.defaultFS</name>
      <value>hdfs://hadoop102:9000</value>
</property>

<!-- 指定Hadoop运行时产生文件的存储目录 -->
<property>
		<name>hadoop.tmp.dir</name>
		<value>/opt/module/hadoop-2.7.2/data/tmp</value>
</property>

(2)HDFS配置文件

配置hadoop-env.sh

[newbies@hadoop102 hadoop]$ vi hadoop-env.sh
export JAVA_HOME=/opt/module/jdk1.8.0_144

配置hdfs-site.xml

[newbies@hadoop102 hadoop]$ vi hdfs-site.xml

在该文件中编写如下配置

<property>
		<name>dfs.replication</name>
		<value>3</value>
</property>

<!-- 指定Hadoop辅助名称节点主机配置 -->
<property>
      <name>dfs.namenode.secondary.http-address</name>
      <value>hadoop104:50090</value>
</property>

(3)YARN配置文件

配置yarn-env.sh

[newbies@hadoop102 hadoop]$ vi yarn-env.sh
export JAVA_HOME=/opt/module/jdk1.8.0_144

配置yarn-site.xml

[newbies@hadoop102 hadoop]$ vi yarn-site.xml

在该文件中增加如下配置

<!-- Reducer获取数据的方式 -->
<property>
		<name>yarn.nodemanager.aux-services</name>
		<value>mapreduce_shuffle</value>
</property>

<!-- 指定YARN的ResourceManager的地址 -->
<property>
		<name>yarn.resourcemanager.hostname</name>
		<value>hadoop103</value>
</property>

(4)MapReduce配置文件

配置mapred-env.sh

[newbies@hadoop102 hadoop]$ vi mapred-env.sh
export JAVA_HOME=/opt/module/jdk1.8.0_144

配置mapred-site.xml

[newbies@hadoop102 hadoop]$ cp mapred-site.xml.template mapred-site.xml

[newbies@hadoop102 hadoop]$ vi mapred-site.xml

在该文件中增加如下配置

<!-- 指定MR运行在Yarn上 -->
<property>
		<name>mapreduce.framework.name</name>
		<value>yarn</value>
</property>

3.在集群上分发配置好的Hadoop配置文件

[newbies@hadoop102 hadoop]$ xsync /opt/module/hadoop-2.7.2/

4.查看文件分发情况

[newbies@hadoop103 hadoop]$ cat /opt/module/hadoop-2.7.2/etc/hadoop/core-site.xml

4.3.4 集群单点启动

(1)如果集群是第一次启动,需要格式化NameNode

[newbies@hadoop102 hadoop-2.7.2]$ hadoop namenode -format

(2)在hadoop102上启动NameNode

[newbies@hadoop102 hadoop-2.7.2]$ hadoop-daemon.sh start namenode
[newbies@hadoop102 hadoop-2.7.2]$ jps
3461 NameNode

(3)在hadoop102、hadoop103以及hadoop104上分别启动DataNode

[newbies@hadoop102 hadoop-2.7.2]$ hadoop-daemon.sh start datanode
[newbies@hadoop102 hadoop-2.7.2]$ jps
3461 NameNode
3608 Jps
3561 DataNode
[newbies@hadoop103 hadoop-2.7.2]$ hadoop-daemon.sh start datanode
[newbies@hadoop103 hadoop-2.7.2]$ jps
3190 DataNode
3279 Jps
[newbies@hadoop104 hadoop-2.7.2]$ hadoop-daemon.sh start datanode
[newbies@hadoop104 hadoop-2.7.2]$ jps
3237 Jps
3163 DataNode

4)思考:每次都一个一个节点启动,如果节点数增加到1000个怎么办?

       早上来了开始一个一个节点启动,到晚上下班刚好完成,下班

4.3.5 SSH无密登录配置

1.    配置ssh

(1)基本语法

ssh另一台电脑的ip地址

(2)ssh连接时出现Host key verification failed的解决方法

[newbies@hadoop102 opt] $ ssh 192.168.1.103
The authenticity of host '192.168.1.103 (192.168.1.103)' can't be established.
RSA key fingerprint is cf:1e:de:d7:d0:4c:2d:98:60:b4:fd:ae:b1:2d:ad:06.
Are you sure you want to continue connecting (yes/no)? 
Host key verification failed.

(3)解决方案如下:直接输入yes

2.    无密钥配置

(1)免密登录原理

(2)生成公钥和私钥:

[newbies@hadoop102 .ssh]$ ssh-keygen -t rsa

然后敲(三个回车),就会生成两个文件id_rsa(私钥)、id_rsa.pub(公钥)

(3)将公钥拷贝到要免密登录的目标机器上

[newbies@hadoop102 .ssh]$ ssh-copy-id hadoop102
[newbies@hadoop102 .ssh]$ ssh-copy-id hadoop103
[newbies@hadoop102 .ssh]$ ssh-copy-id hadoop104

注意:

还需要在hadoop102上采用root账号,配置一下无密登录到hadoop102hadoop103hadoop104

还需要在hadoop103上采用newbies账号配置一下无密登录到hadoop102hadoop103hadoop104服务器上。

3.    .ssh文件夹下(~/.ssh)的文件功能解释

known_hosts

记录ssh访问过计算机的公钥(public key)

id_rsa

生成的私钥

id_rsa.pub

生成的公钥

authorized_keys

存放授权过得无密登录服务器公钥

4.3.6 群起集群

1.    配置slaves

/opt/module/hadoop-2.7.2/etc/hadoop/slaves
[newbies@hadoop102 hadoop]$ vi slaves

在该文件中增加如下内容:

hadoop102
hadoop103
hadoop104

注意:该文件中添加的内容结尾不允许有空格,文件中不允许有空行。

同步所有节点配置文件

[newbies@hadoop102 hadoop]$ xsync slaves

2.    启动集群

       (1)如果集群是第一次启动,需要格式化NameNode(注意格式化之前,一定要先停止上次启动的所有namenodedatanode进程,然后再删除datalog数据)

[newbies@hadoop102 hadoop-2.7.2]$ bin/hdfs namenode -format

2)启动HDFS

[newbies@hadoop102 hadoop-2.7.2]$ sbin/start-dfs.sh
[newbies@hadoop102 hadoop-2.7.2]$ jps
4166 NameNode
4482 Jps
4263 DataNode
[newbies@hadoop103 hadoop-2.7.2]$ jps
3218 DataNode
3288 Jps





[newbies@hadoop104 hadoop-2.7.2]$ jps
3221 DataNode
3283 SecondaryNameNode
3364 Jps

(3)启动YARN

[newbies@hadoop103 hadoop-2.7.2]$ sbin/start-yarn.sh

注意:NameNodeResourceManger如果不是同一台机器,不能在NameNode上启动 YARN,应该在ResouceManager所在的机器上启动YARN

(4)Web端查看SecondaryNameNode

(a)浏览器中输入:http://hadoop104:50090/status.html

              (b)查看SecondaryNameNode信息

SecondaryNameNode的Web端

 

3.    集群基本测试

(1)上传文件到集群

         上传小文件

[newbies@hadoop102 hadoop-2.7.2]$ hdfs dfs -mkdir -p /user/newbies/input
[newbies@hadoop102 hadoop-2.7.2]$ hdfs dfs -put wcinput/wc.input /user/newbies/input

 上传大文件

[newbies@hadoop102 hadoop-2.7.2]$ bin/hadoop fs -put
 /opt/software/hadoop-2.7.2.tar.gz  /user/newbies/input

(2)上传文件后查看文件存放在什么位置

(a)查看HDFS文件存储路径

[newbies@hadoop102 subdir0]$ pwd
/opt/module/hadoop-2.7.2/data/tmp/dfs/data/current/BP-938951106-192.168.10.107-1495462844069/current/finalized/subdir0/subdir0

(b)查看HDFS在磁盘存储文件内容

[newbies@hadoop102 subdir0]$ cat blk_1073741825
hadoop yarn
hadoop mapreduce 
newbies
newbies

(3)拼接

-rw-rw-r--. 1 newbies newbies 134217728 5月  23 16:01 blk_1073741836

-rw-rw-r--. 1 newbies newbies   1048583 5月  23 16:01 blk_1073741836_1012.meta

-rw-rw-r--. 1 newbies newbies  63439959 5月  23 16:01 blk_1073741837

-rw-rw-r--. 1 newbies newbies    495635 5月  23 16:01 blk_1073741837_1013.meta
[newbies@hadoop102 subdir0]$ cat blk_1073741836>>tmp.file
[newbies@hadoop102 subdir0]$ cat blk_1073741837>>tmp.file
[newbies@hadoop102 subdir0]$ tar -zxvf tmp.file

(4)下载

[newbies@hadoop102 hadoop-2.7.2]$ bin/hadoop fs -get
 /user/newbies/input/hadoop-2.7.2.tar.gz ./

4.3.7 集群启动/停止方式总结

1.    各个服务组件逐一启动/停止

       (1)分别启动/停止HDFS组件    

  hadoop-daemon.sh  start / stop  namenode / datanode / secondarynamenode

       (2)启动/停止YARN             

 yarn-daemon.sh  start / stop  resourcemanager / nodemanager

2.    各个模块分开启动/停止(配置ssh是前提)常用

       (1)整体启动/停止HDFS

              start-dfs.sh   /  stop-dfs.sh

       (2)整体启动/停止YARN

              start-yarn.sh  /  stop-yarn.sh

4.3.8 集群时间同步

时间同步的方式:找一个机器,作为时间服务器,所有的机器与这台集群时间进行定时的同步,比如,每隔十分钟,同步一次时间。

集群时间同步

配置时间同步具体实操:

1.    时间服务器配置(必须root用户)

(1)检查ntp是否安装

[root@hadoop102 桌面]# rpm -qa|grep ntp
ntp-4.2.6p5-10.el6.centos.x86_64
fontpackages-filesystem-1.41-1.1.el6.noarch
ntpdate-4.2.6p5-10.el6.centos.x86_64

(2)修改ntp配置文件

[root@hadoop102 桌面]# vi /etc/ntp.conf

修改内容如下

a)修改1(授权192.168.1.0-192.168.1.255网段上的所有机器可以从这台机器上查询和同步时间)

#restrict 192.168.1.0 mask 255.255.255.0 nomodify notrap为

restrict 192.168.1.0 mask 255.255.255.0 nomodify notrap

              b)修改2(集群在局域网中,不使用其他互联网上的时间)

server 0.centos.pool.ntp.org iburst

server 1.centos.pool.ntp.org iburst

server 2.centos.pool.ntp.org iburst

server 3.centos.pool.ntp.org iburst为

#server 0.centos.pool.ntp.org iburst

#server 1.centos.pool.ntp.org iburst

#server 2.centos.pool.ntp.org iburst

#server 3.centos.pool.ntp.org iburst

c)添加3当该节点丢失网络连接,依然可以采用本地时间作为时间服务器为集群中的其他节点提供时间同步

server 127.127.1.0

fudge 127.127.1.0 stratum 10

(3)修改/etc/sysconfig/ntpd 文件

[root@hadoop102 桌面]# vim /etc/sysconfig/ntpd

增加内容如下(让硬件时间与系统时间一起同步)

SYNC_HWCLOCK=yes

(4)重新启动ntpd服务

[root@hadoop102 桌面]# service ntpd status

ntpd 已停
[root@hadoop102 桌面]# service ntpd start

正在启动 ntpd:                                            [确定]

(5)设置ntpd服务开机启动

[root@hadoop102 桌面]# chkconfig ntpd on

2.    其他机器配置(必须root用户)

(1)在其他机器配置10分钟与时间服务器同步一次

[root@hadoop103桌面]# crontab -e

编写定时任务如下:

*/10 * * * * /usr/sbin/ntpdate hadoop102

(2)修改任意机器时间

[root@hadoop103桌面]# date -s "2017-9-11 11:11:11"

(3)十分钟后查看机器是否与时间服务器同步

[root@hadoop103桌面]# date

说明:测试的时候可以将10分钟调整为1分钟,节省时间。

=============================End========================

 

 

 

服务器1

服务器2

服务器3

HDFS

NameNode

DataNode

DataNode

DataNode

Yarn

NodeManager

Resourcemanager

NodeManager

NodeManager

4.1.1 添加LZO支持包

1)先下载lzo的jar项目

https://github.com/twitter/hadoop-lzo/archive/master.zip

2)下载后的文件名是hadoop-lzo-master,它是一个zip格式的压缩包,先进行解压,然后用maven编译。生成hadoop-lzo-0.4.20。

3)将编译好后的hadoop-lzo-0.4.20.jar 放入hadoop-2.7.2/share/hadoop/common/

[newbies@hadoop102 common]$ pwd
/opt/module/hadoop-2.7.2/share/hadoop/common
[newbies@hadoop102 common]$ ls
hadoop-lzo-0.4.20.jar

4)同步hadoop-lzo-0.4.20.jar到hadoop103、hadoop104

[newbies@hadoop102 common]$ xsync hadoop-lzo-0.4.20.jar

4.1.2 添加配置

1)core-site.xml增加配置支持LZO压缩

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>

<configuration>

<property>
<name>io.compression.codecs</name>
<value>
org.apache.hadoop.io.compress.GzipCodec,
org.apache.hadoop.io.compress.DefaultCodec,
org.apache.hadoop.io.compress.BZip2Codec,
org.apache.hadoop.io.compress.SnappyCodec,
com.hadoop.compression.lzo.LzoCodec,
com.hadoop.compression.lzo.LzopCodec
</value>
</property>
<property>
    <name>io.compression.codec.lzo.class</name>
    <value>com.hadoop.compression.lzo.LzoCodec</value>
</property>

</configuration>

2)同步core-site.xml到hadoop103、hadoop104

[newbies@hadoop102 hadoop]$ xsync core-site.xml

4.1.2 启动集群

[newbies@hadoop102 hadoop-2.7.2]$ sbin/start-dfs.sh
[newbies@hadoop103 hadoop-2.7.2]$ sbin/start-yarn.sh

4.1.3 验证

1)web和进程查看

2)当启动发生错误的时候:

  • 查看日志:/home/newbies/module/hadoop-2.7.2/logs
  • 如果进入安全模式,可以通过hdfs dfsadmin -safemode leave
  • 停止所有进程,删除data和log文件夹,然后hdfs namenode -format 来格式化

4.2 Zookeeper安装

4.2.1 安装ZK

======================== Begin ==============

第2章 Zookeeper安装

2.1 本地模式安装部署

1.安装前准备

(1)安装Jdk

(2)拷贝Zookeeper安装包到Linux系统下

(3)解压到指定目录

[newbies@hadoop102 software]$ tar -zxvf zookeeper-3.4.10.tar.gz -C /opt/module/

2.配置修改

(1)将/opt/module/zookeeper-3.4.10/conf这个路径下的zoo_sample.cfg修改为zoo.cfg;

[newbies@hadoop102 conf]$ mv zoo_sample.cfg zoo.cfg

(2)打开zoo.cfg文件,修改dataDir路径:

[newbies@hadoop102 zookeeper-3.4.10]$ vim zoo.cfg

修改如下内容:

dataDir=/opt/module/zookeeper-3.4.10/zkData

(3)在/opt/module/zookeeper-3.4.10/这个目录上创建zkData文件夹

[newbies@hadoop102 zookeeper-3.4.10]$ mkdir zkData

3.操作Zookeeper

(1)启动Zookeeper

[newbies@hadoop102 zookeeper-3.4.10]$ bin/zkServer.sh start

(2)查看进程是否启动

[newbies@hadoop102 zookeeper-3.4.10]$ jps
4020 Jps
4001 QuorumPeerMain

(3)查看状态:

[newbies@hadoop102 zookeeper-3.4.10]$ bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/module/zookeeper-3.4.10/bin/../conf/zoo.cfg
Mode: standalone

(4)启动客户端:

[newbies@hadoop102 zookeeper-3.4.10]$ bin/zkCli.sh

(5)退出客户端:

[zk: localhost:2181(CONNECTED) 0] quit

(6)停止Zookeeper

[newbies@hadoop102 zookeeper-3.4.10]$ bin/zkServer.sh stop

2.2 配置参数解读

Zookeeper中的配置文件zoo.cfg中参数含义解读如下:

1.tickTime =2000:通信心跳数,Zookeeper服务器与客户端心跳时间,单位毫秒

Zookeeper使用的基本时间,服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个tickTime时间就会发送一个心跳,时间单位为毫秒。

它用于心跳机制,并且设置最小的session超时时间为两倍心跳时间。(session的最小超时时间是2*tickTime)

2.initLimit =10:LF初始通信时限

集群中的Follower跟随者服务器与Leader领导者服务器之间初始连接时能容忍的最多心跳数(tickTime的数量),用它来限定集群中的Zookeeper服务器连接到Leader的时限。

3.syncLimit =5:LF同步通信时限

集群中Leader与Follower之间的最大响应时间单位,假如响应超过syncLimit * tickTime,Leader认为Follwer死掉,从服务器列表中删除Follwer。

4.dataDir:数据文件目录+数据持久化路径

主要用于保存Zookeeper中的数据。

5.clientPort =2181:客户端连接端口

监听客户端连接的端口。

第4章 Zookeeper实战(开发重点)

4.1 分布式安装部署

1.集群规划

在hadoop102、hadoop103和hadoop104三个节点上部署Zookeeper。

2.解压安装

(1)解压Zookeeper安装包到/opt/module/目录下

[newbies@hadoop102 software]$ tar -zxvf zookeeper-3.4.10.tar.gz -C /opt/module/

(2)同步/opt/module/zookeeper-3.4.10目录内容到hadoop103、hadoop104

[newbies@hadoop102 module]$ xsync zookeeper-3.4.10/

3.配置服务器编号

(1)在/opt/module/zookeeper-3.4.10/这个目录下创建zkData

[newbies@hadoop102 zookeeper-3.4.10]$ mkdir -p zkData

(2)在/opt/module/zookeeper-3.4.10/zkData目录下创建一个myid的文件

[newbies@hadoop102 zkData]$ touch myid

添加myid文件,注意一定要在linux里面创建,在notepad++里面很可能乱码

(3)编辑myid文件

[newbies@hadoop102 zkData]$ vi myid

在文件中添加与server对应的编号:

2

(4)拷贝配置好的zookeeper到其他机器上

[newbies@hadoop102 zkData]$ xsync myid

并分别在hadoop102、hadoop103上修改myid文件中内容为3、4

4.配置zoo.cfg文件

(1)重命名/opt/module/zookeeper-3.4.10/conf这个目录下的zoo_sample.cfg为zoo.cfg

[newbies@hadoop102 conf]$ mv zoo_sample.cfg zoo.cfg

(2)打开zoo.cfg文件

[newbies@hadoop102 conf]$ vim zoo.cfg

修改数据存储路径配置

dataDir=/opt/module/zookeeper-3.4.10/zkData

增加如下配置

#######################cluster##########################
server.2=hadoop102:2888:3888
server.3=hadoop103:2888:3888
server.4=hadoop104:2888:3888

(3)同步zoo.cfg配置文件

[newbies@hadoop102 conf]$ xsync zoo.cfg

(4)配置参数解读

server.A=B:C:D。

A是一个数字,表示这个是第几号服务器;

集群模式下配置一个文件myid,这个文件在dataDir目录下,这个文件里面有一个数据就是A的值,Zookeeper启动时读取此文件,拿到里面的数据与zoo.cfg里面的配置信息比较从而判断到底是哪个server

B是这个服务器的ip地址;

C是这个服务器与集群中的Leader服务器交换信息的端口;

D是万一集群中的Leader服务器挂了,需要一个端口来重新进行选举,选出一个新的Leader,而这个端口就是用来执行选举时服务器相互通信的端口。

4.集群操作

(1)分别启动Zookeeper

[newbies@hadoop102 zookeeper-3.4.10]$ bin/zkServer.sh start
[newbies@hadoop103 zookeeper-3.4.10]$ bin/zkServer.sh start
[newbies@hadoop104 zookeeper-3.4.10]$ bin/zkServer.sh start

(2)查看状态

[newbies@hadoop102 zookeeper-3.4.10]# bin/zkServer.sh status
JMX enabled by default
Using config: /opt/module/zookeeper-3.4.10/bin/../conf/zoo.cfg
Mode: follower
[newbies@hadoop103 zookeeper-3.4.10]# bin/zkServer.sh status
JMX enabled by default
Using config: /opt/module/zookeeper-3.4.10/bin/../conf/zoo.cfg
Mode: leader
[newbies@hadoop104 zookeeper-3.4.5]# bin/zkServer.sh status
JMX enabled by default
Using config: /opt/module/zookeeper-3.4.10/bin/../conf/zoo.cfg
Mode: follower

4.2 客户端命令行操作

命令基本语法

功能描述

help

显示所有操作命令

ls path [watch]

使用 ls 命令来查看当前znode中所包含的内容

ls2 path [watch]

查看当前节点数据并能看到更新次数等数据

create

普通创建

-s  含有序列

-e  临时(重启或者超时消失)

get path [watch]

获得节点的值

set

设置节点的具体值

stat

查看节点状态

delete

删除节点

rmr

递归删除节点

1.启动客户端

[newbies@hadoop103 zookeeper-3.4.10]$ bin/zkCli.sh

2.显示所有操作命令

[zk: localhost:2181(CONNECTED) 1] help

3.查看当前znode中所包含的内容

[zk: localhost:2181(CONNECTED) 0] ls /
[zookeeper]

4.查看当前节点详细数据

[zk: localhost:2181(CONNECTED) 1] ls2 /
[zookeeper]
cZxid = 0x0
ctime = Thu Jan 01 08:00:00 CST 1970
mZxid = 0x0
mtime = Thu Jan 01 08:00:00 CST 1970
pZxid = 0x0
cversion = -1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 1

5.分别创建2个普通节点

[zk: localhost:2181(CONNECTED) 3] create /sanguo "jinlian"
Created /sanguo
[zk: localhost:2181(CONNECTED) 4] create /sanguo/shuguo "liubei"
Created /sanguo/shuguo

6.获得节点的值

[zk: localhost:2181(CONNECTED) 5] get /sanguo
jinlian
cZxid = 0x100000003
ctime = Wed Aug 29 00:03:23 CST 2018
mZxid = 0x100000003
mtime = Wed Aug 29 00:03:23 CST 2018
pZxid = 0x100000004
cversion = 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 7
numChildren = 1
[zk: localhost:2181(CONNECTED) 6]
[zk: localhost:2181(CONNECTED) 6] get /sanguo/shuguo
liubei
cZxid = 0x100000004
ctime = Wed Aug 29 00:04:35 CST 2018
mZxid = 0x100000004
mtime = Wed Aug 29 00:04:35 CST 2018
pZxid = 0x100000004
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 6
numChildren = 0

7.创建短暂节点

[zk: localhost:2181(CONNECTED) 7] create -e /sanguo/wuguo "zhouyu"
Created /sanguo/wuguo

(1)在当前客户端是能查看到的

[zk: localhost:2181(CONNECTED) 3] ls /sanguo 
[wuguo, shuguo]

(2)退出当前客户端然后再重启客户端

[zk: localhost:2181(CONNECTED) 12] quit
[newbies@hadoop104 zookeeper-3.4.10]$ bin/zkCli.sh

(3)再次查看根目录下短暂节点已经删除

[zk: localhost:2181(CONNECTED) 0] ls /sanguo
[shuguo]

8.创建带序号的节点

       (1)先创建一个普通的根节点/sanguo/weiguo

[zk: localhost:2181(CONNECTED) 1] create /sanguo/weiguo "caocao"
Created /sanguo/weiguo

       (2)创建带序号的节点

[zk: localhost:2181(CONNECTED) 2] create -s /sanguo/weiguo/xiaoqiao "jinlian"
Created /sanguo/weiguo/xiaoqiao0000000000
[zk: localhost:2181(CONNECTED) 3] create -s /sanguo/weiguo/daqiao "jinlian"
Created /sanguo/weiguo/daqiao0000000001
[zk: localhost:2181(CONNECTED) 4] create -s /sanguo/weiguo/diaocan "jinlian"
Created /sanguo/weiguo/diaocan0000000002

如果原来没有序号节点,序号从0开始依次递增。如果原节点下已有2个节点,则再排序时从2开始,以此类推。

9.修改节点数据值

[zk: localhost:2181(CONNECTED) 6] set /sanguo/weiguo "simayi"

10.节点的值变化监听

       (1)在hadoop104主机上注册监听/sanguo节点数据变化

[zk: localhost:2181(CONNECTED) 26] [zk: localhost:2181(CONNECTED) 8] get /sanguo watch

       (2)在hadoop103主机上修改/sanguo节点的数据

[zk: localhost:2181(CONNECTED) 1] set /sanguo "xisi"

       (3)观察hadoop104主机收到数据变化的监听

WATCHER::
WatchedEvent state:SyncConnected type:NodeDataChanged path:/sanguo

11.节点的子节点变化监听(路径变化)

       (1)在hadoop104主机上注册监听/sanguo节点的子节点变化

[zk: localhost:2181(CONNECTED) 1] ls /sanguo watch
[aa0000000001, server101]

       (2)在hadoop103主机/sanguo节点上创建子节点

[zk: localhost:2181(CONNECTED) 2] create /sanguo/jin "simayi"
Created /sanguo/jin

       (3)观察hadoop104主机收到子节点变化的监听

WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/sanguo

12.删除节点

[zk: localhost:2181(CONNECTED) 4] delete /sanguo/jin

13.递归删除节点

[zk: localhost:2181(CONNECTED) 15] rmr /sanguo/shuguo

14.查看节点状态

[zk: localhost:2181(CONNECTED) 17] stat /sanguo
cZxid = 0x100000003
ctime = Wed Aug 29 00:03:23 CST 2018
mZxid = 0x100000011
mtime = Wed Aug 29 00:21:23 CST 2018
pZxid = 0x100000014
cversion = 9
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 1

======================== End ==============

 

服务器1

服务器2

服务器3

Zookeeper

Zookeeper

Zookeeper

Zookeeper

4.2.2 ZK集群启动停止脚本

1)在hadoop102的/home/newbies/bin目录下创建脚本

[newbies@hadoop102 bin]$ vim zk.sh

在脚本中编写如下内容

#! /bin/bash

case $1 in
"start"){
	for i in hadoop102 hadoop103 hadoop104
	do
		ssh $i "/opt/module/zookeeper-3.4.10/bin/zkServer.sh start"
	done
	};;
"stop"){
	for i in hadoop102 hadoop103 hadoop104
	do
		ssh $i "/opt/module/zookeeper-3.4.10/bin/zkServer.sh stop"
	done
	};;
esac

2)增加脚本执行权限

[newbies@hadoop102 bin]$ chmod 777 zk.sh

3)Zookeeper集群启动脚本

[newbies@hadoop102 module]$ zk.sh start

4)Zookeeper集群停止脚本

[newbies@hadoop102 module]$ zk.sh stop

4.3 Flume安装

Flume数据采集

集群规划

 

服务器1

服务器2

服务器3

Flume(采集日志)

Flume

Flume

 

4.3.1 日志采集Flume安装

===============================Begin=============================

第2章 快速入门

2.1 Flume安装地址

1) Flume官网地址

http://flume.apache.org/

2)文档查看地址

http://flume.apache.org/FlumeUserGuide.html

3)下载地址

http://archive.apache.org/dist/flume/

2.2 安装部署

1)将apache-flume-1.7.0-bin.tar.gz上传到linux的/opt/software目录下

2)解压apache-flume-1.7.0-bin.tar.gz到/opt/module/目录下

[newbies@hadoop102 software]$ tar -zxf apache-flume-1.7.0-bin.tar.gz -C /opt/module/

3)修改apache-flume-1.7.0-bin的名称为flume

[newbies@hadoop102 module]$ mv apache-flume-1.7.0-bin flume

4) 将flume/conf下的flume-env.sh.template文件修改为flume-env.sh,并配置flume-env.sh文件

[newbies@hadoop102 conf]$ mv flume-env.sh.template flume-env.sh
[newbies@hadoop102 conf]$ vi flume-env.sh
export JAVA_HOME=/opt/module/jdk1.8.0_144

第4章 Flume监控之Ganglia

4.1 Ganglia的安装与部署

1) 安装httpd服务与php

[newbies@hadoop102 flume]$ sudo yum -y install httpd php

2) 安装其他依赖

[newbies@hadoop102 flume]$ sudo yum -y install rrdtool perl-rrdtool rrdtool-devel
[newbies@hadoop102 flume]$ sudo yum -y install apr-devel

3) 安装ganglia

[newbies@hadoop102 flume]$ sudo rpm -Uvh http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
[newbies@hadoop102 flume]$ sudo yum -y install ganglia-gmetad 
[newbies@hadoop102 flume]$ sudo yum -y install ganglia-web
[newbies@hadoop102 flume]$ sudo yum install -y ganglia-gmond

4) 修改配置文件/etc/httpd/conf.d/ganglia.conf

[newbies@hadoop102 flume]$ sudo vim /etc/httpd/conf.d/ganglia.conf

修改为红颜色的配置:

# Ganglia monitoring system php web frontend
Alias /ganglia /usr/share/ganglia
<Location /ganglia>
  Order deny,allow
  Deny from all
  Allow from all
  # Allow from 127.0.0.1
  # Allow from ::1
  # Allow from .example.com
</Location>

5) 修改配置文件/etc/ganglia/gmetad.conf

[newbies@hadoop102 flume]$ sudo vim /etc/ganglia/gmetad.conf

修改为:

data_source "hadoop102" 192.168.1.102

6) 修改配置文件/etc/ganglia/gmond.conf

[newbies@hadoop102 flume]$ sudo vim /etc/ganglia/gmond.conf 

修改为:

cluster {
  name = "hadoop102"
  owner = "unspecified"
  latlong = "unspecified"
  url = "unspecified"
}
udp_send_channel {
  #bind_hostname = yes # Highly recommended, soon to be default.
                       # This option tells gmond to use a source address
                       # that resolves to the machine's hostname.  Without
                       # this, the metrics may appear to come from any
                       # interface and the DNS names associated with
                       # those IPs will be used to create the RRDs.
  # mcast_join = 239.2.11.71
  host = 192.168.1.102
  port = 8649
  ttl = 1
}
udp_recv_channel {
  # mcast_join = 239.2.11.71
  port = 8649
  bind = 192.168.1.102
  retry_bind = true
  # Size of the UDP buffer. If you are handling lots of metrics you really
  # should bump it up to e.g. 10MB or even higher.
  # buffer = 10485760
}

7) 修改配置文件/etc/selinux/config

[newbies@hadoop102 flume]$ sudo vim /etc/selinux/config

修改为:

# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
#     enforcing - SELinux security policy is enforced.
#     permissive - SELinux prints warnings instead of enforcing.
#     disabled - No SELinux policy is loaded.
SELINUX=disabled
# SELINUXTYPE= can take one of these two values:
#     targeted - Targeted processes are protected,
#     mls - Multi Level Security protection.
SELINUXTYPE=targeted

尖叫提示:selinux本次生效关闭必须重启,如果此时不想重启,可以临时生效之:

[newbies@hadoop102 flume]$ sudo setenforce 0

5) 启动ganglia

[newbies@hadoop102 flume]$ sudo service httpd start
[newbies@hadoop102 flume]$ sudo service gmetad start
[newbies@hadoop102 flume]$ sudo service gmond start

6) 打开网页浏览ganglia页面

http://192.168.1.102/ganglia

尖叫提示:如果完成以上操作依然出现权限不足错误,请修改/var/lib/ganglia目录的权限:

[newbies@hadoop102 flume]$ sudo chmod -R 777 /var/lib/ganglia

4.2 操作Flume测试监控

1) 修改/opt/module/flume/conf目录下的flume-env.sh配置:

JAVA_OPTS="-Dflume.monitoring.type=ganglia
-Dflume.monitoring.hosts=192.168.1.102:8649
-Xms100m
-Xmx200m"

2) 启动Flume任务

[newbies@hadoop102 flume]$ bin/flume-ng agent \
--conf conf/ \
--name a1 \
--conf-file job/flume-telnet-logger.conf \
-Dflume.root.logger==INFO,console \
-Dflume.monitoring.type=ganglia \
-Dflume.monitoring.hosts=192.168.1.102:8649

3) 发送数据观察ganglia监测图

[newbies@hadoop102 flume]$ telnet localhost 44444

样式如图:

图例说明:

字段(图表名称)

字段含义

EventPutAttemptCount

source尝试写入channel的事件总数量

EventPutSuccessCount

成功写入channel且提交的事件总数量

EventTakeAttemptCount

sink尝试从channel拉取事件的总数量。这不意味着每次事件都被返回,因为sink拉取的时候channel可能没有任何数据。

EventTakeSuccessCount

sink成功读取的事件的总数量

StartTime

channel启动的时间(毫秒)

StopTime

channel停止的时间(毫秒)

ChannelSize

目前channel中事件的总数量

ChannelFillPercentage

channel占用百分比

ChannelCapacity

channel的容量

 

===============================End============================

4.3.2 日志采集Flume配置

1)Flume配置分析

注意,TailDirSourceFlume 1.7提供的Source组件,在1.6中并没有

Flume一般都是部署在服务器上,由运维统一配置部署。此处直接读log日志的数据,log日志的格式是app-yyyy-mm-dd.log,可以直接读取。

2)Flume的具体配置如下:

       (1)在/opt/module/flume/conf目录下创建file-flume-kafka.conf文件

[newbies@hadoop102 conf]$ vim file-flume-kafka.conf

在文件配置如下内容

a1.sources=r1
a1.channels=c1 c2 
a1.sinks=k1 k2 

# configure source
a1.sources.r1.type = TAILDIR
a1.sources.r1.positionFile = /opt/module/flume/log_position.json
a1.sources.r1.filegroups = f1
a1.sources.r1.filegroups.f1 = /tmp/logs/app.+
a1.sources.r1.fileHeader = true
a1.sources.r1.channels = c1 c2

#interceptor
a1.sources.r1.interceptors = i1 i2
a1.sources.r1.interceptors.i1.type = com.newbies.flume.interceptor.LogETLInterceptor$Builder
a1.sources.r1.interceptors.i2.type = com.newbies.flume.interceptor.LogTypeInterceptor$Builder

# selector
a1.sources.r1.selector.type = multiplexing
a1.sources.r1.selector.header = logType
a1.sources.r1.selector.mapping.start = c1
a1.sources.r1.selector.mapping.event = c2

# configure channel
a1.channels.c1.type = memory
a1.channels.c1.capacity=10000
a1.channels.c1.byteCapacityBufferPercentage=20

a1.channels.c2.type = memory
a1.channels.c2.capacity=10000
a1.channels.c2.byteCapacityBufferPercentage=20

# configure sink
# start-sink
a1.sinks.k1.type = org.apache.flume.sink.kafka.KafkaSink
a1.sinks.k1.kafka.topic = topic_start
a1.sinks.k1.kafka.bootstrap.servers = hadoop102:9092,hadoop103:9092,hadoop104:9092
a1.sinks.k1.kafka.flumeBatchSize = 2000
a1.sinks.k1.kafka.producer.acks = 1
a1.sinks.k1.channel = c1

# event-sink
a1.sinks.k2.type = org.apache.flume.sink.kafka.KafkaSink
a1.sinks.k2.kafka.topic = topic_event
a1.sinks.k2.kafka.bootstrap.servers = hadoop102:9092,hadoop103:9092,hadoop104:9092
a1.sinks.k2.kafka.flumeBatchSize = 2000
a1.sinks.k2.kafka.producer.acks = 1
a1.sinks.k2.channel = c2

         注意:com.newbies.flume.interceptor.LogETLInterceptorcom.newbies.flume.interceptor.LogTypeInterceptor是自定义的拦截器的全类名。需要根据用户自定义的拦截器做相应修改。

4.3.3 Flume拦截器

本项目中自定义了两个拦截器,分别是:ETL拦截器、日志类型区分拦截器。

ETL拦截器主要用于,过滤时间戳不合法和json数据不完整的日志

日志类型区分拦截器主要用于,将错误日志、启动日志和事件日志区分开来,方便发往kafka的不同topic。

1)创建maven工程flume-interceptor

2)创建包名:com.newbies.flume.interceptor

3)在pom.xml文件中添加如下配置

<dependencies>
    <dependency>
        <groupId>org.apache.flume</groupId>
        <artifactId>flume-ng-core</artifactId>
        <version>1.7.0</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.3.2</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
        <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <archive>
                    <manifest>
                        <mainClass>com.newbies.appclient.AppMain</mainClass>
                    </manifest>
                </archive>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

4)在com.newbies.flume.interceptor包下创建LogETLInterceptor类名

Flume ETL拦截器LogETLInterceptor

package com.newbies.flume.interceptor;

import org.apache.flume.Context;
import org.apache.flume.Event;
import org.apache.flume.interceptor.Interceptor;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

public class LogETLInterceptor implements Interceptor {

    @Override
    public void initialize() {

    }

    @Override
    public Event intercept(Event event) {

        String body = new String(event.getBody(), Charset.forName("UTF-8"));

        // body为原始数据,newBody为处理后的数据,判断是否为display的数据类型
        if (LogUtils.validateReportLog(body)) {
            return event;
        }

        return null;
    }

    @Override
    public List<Event> intercept(List<Event> events) {

        ArrayList<Event> intercepts = new ArrayList<>();

        // 遍历所有Event,将拦截器校验不合格的过滤掉
        for (Event event : events) {
            
            Event interceptEvent = intercept(event);

            if (interceptEvent != null){
                intercepts.add(interceptEvent);
            }
        }

        return intercepts;
    }

    @Override
    public void close() {

    }

    public static class Builder implements Interceptor.Builder {

        public Interceptor build() {
            return new LogETLInterceptor();
        }


        @Override
        public void configure(Context context) {

        }
    }
}

4)Flume日志过滤工具类

package com.newbies.flume.interceptor;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogUtils {

    private static Logger logger = LoggerFactory.getLogger(LogUtils.class);

    /**
     * 日志检查,正常的log会返回true,错误的log返回false
     *
     * @param log
     */
    public static boolean validateReportLog(String log) {

        try {
//          日志的格式是:时间戳| json串
//          1549696569054 | {"cm":{"ln":"-89.2","sv":"V2.0.4","os":"8.2.0","g":"[email protected]","nw":"4G","l":"en","vc":"18","hw":"1080*1920","ar":"MX","uid":"u8678","t":"1549679122062","la":"-27.4","md":"sumsung-12","vn":"1.1.3","ba":"Sumsung","sr":"Y"},"ap":"weather","et":[]}
            String[] logArray = log.split("\\|");

            if (logArray.length < 2) {
                return false;
            }
//          检查第一串是否为时间戳 或者不是全数字
            if (logArray[0].length() != 13 || !NumberUtils.isDigits(logArray[0])) {
                return false;
            }

//          第二串是否为正确的json,这里我们就粗略的检查了,有时候我们需要从后面来发现json传错的数据,做分析
            if (!logArray[1].trim().startsWith("{") || !logArray[1].trim().endsWith("}")) {
                return false;
            }
        } catch (Exception e) {
//          错误日志打印,需要查看
            logger.error("parse error,message is:" + log);
            logger.error(e.getMessage());

            return false;
        }

        return true;
    }
}

5)Flume日志类型区分拦截器LogTypeInterceptor

package com.newbies.flume.interceptor;

import org.apache.flume.Context;
import org.apache.flume.Event;
import org.apache.flume.interceptor.Interceptor;

import java.util.List;
import java.util.Map;

/**
 * Created by Administrator on 2019/1/18 0018.
 */
public class LogTypeInterceptor implements Interceptor {

    @Override
    public void initialize() {

    }

    @Override
    public Event intercept(Event event) {

        // 1获取flume接收消息头
        Map<String, String> headers = event.getHeaders();

        // 2获取flume接收的json数据数组
        byte[] json = event.getBody();

        // 将json数组转换为字符串
        String jsonStr = new String(json);

        String logType = "" ;

// startLog
        if (jsonStr.contains("start")) {
            logType = "start";
        }
        // eventLog
        else {
            logType = "event";
        }

        // 3将日志类型存储到flume头中
        headers.put("logType", logType);

        return event;
    }

    @Override
    public List<Event> intercept(List<Event> events) {

        ArrayList<Event> interceptors = new ArrayList<>();

for (Event event : events) {
     Event interceptEvent = intercept(event);

     interceptors.add(interceptEvent);
}

return interceptors;
    }

    @Override
    public void close() {

    }

    public static class Builder implements Interceptor.Builder {

        public Interceptor build() {
            return new LogTypeInterceptor();
        }

        @Override
        public void configure(Context context) {

        }
    }
}

6)打包

拦截器打包之后,只需要单独包,不需要将依赖的包上传。打包之后要放入flume的lib文件夹下面。

注意:为什么不需要依赖包?因为依赖包在flume的lib目录下面已经存在了。

7)需要先将打好的包放入到hadoop102的/opt/module/flume/lib文件夹下面。

[newbies@hadoop102 lib]$ ls | grep interceptor
flume-interceptor-1.0-SNAPSHOT.jar

8)分发flume到hadoop103、hadoop104

[newbies@hadoop102 module]$ xsync flume/

[newbies@hadoop102 flume]$ bin/flume-ng agent --conf conf/ --name a1 --conf-file conf/file-flume-kafka.conf &

4.3.4 日志采集Flume启动停止脚本

1)在/home/newbies/bin目录下创建脚本f1.sh

[newbies@hadoop102 bin]$ vim f1.sh

       在脚本中填写如下内容

#! /bin/bash

case $1 in
"start"){
        for i in hadoop102 hadoop103
        do
                echo " --------启动 $i 采集flume-------"
                ssh $i "nohup /opt/module/flume/bin/flume-ng agent --conf-file /opt/module/flume/conf/file-flume-kafka.conf --name a1 -Dflume.root.logger=INFO,LOGFILE >/dev/null 2>&1 &"
        done
};;
"stop"){
        for i in hadoop102 hadoop103
        do
                echo " --------停止 $i 采集flume-------"
                ssh $i "ps -ef | grep file-flume-kafka | grep -v grep |awk '{print \$2}' | xargs kill"
        done

};;
esac

说明:nohup,该命令可以在你退出帐户/关闭终端之后继续运行相应的进程。nohup就是不挂起的意思。

2)增加脚本执行权限

[newbies@hadoop102 bin]$ chmod 777 f1.sh

3)f1集群启动脚本

[newbies@hadoop102 module]$ f1.sh start

4)f1集群停止脚本

[newbies@hadoop102 module]$ f1.sh stop

4.3.5 小结

1Flume采集系统组件解析

1.  Source

  1. Taildir Source

在Flume1.7之前如果想要监控一个文件新增的内容,我们一般采用的source 为 exec tail,但是这会有一个弊端,就是当你的服务器宕机重启后,此时数据读取还是从头开始,这显然不是我们想看到的! 在Flume1.7 没有出来之前我们一般的解决思路为:当读取一条记录后,就把当前的记录的行号记录到一个文件中,宕机重启时,我们可以先从文件中获取到最后一次读取文件的行数,然后继续监控读取下去。保证数据不丢失、不重复。

Flume1.7时新增了一个source 的类型为taildir它可以监控一个目录下的多个文件,并且实现了实时读取记录保存的断点续传功能

但是Flume1.7中如果文件重命名,那么会被当成新文件而被重新采集

2.  Channel

  1. Memory Channel

Memory Channel把Event保存在内存队列中,该队列能保存的Event数量有最大值上限。由于Event数据都保存在内存中,Memory Channel有最好的性能,不过也有数据可能会丢失的风险,如果Flume崩溃或者重启,那么保存在Channel中的Event都会丢失。同时由于内存容量有限,当Event数量达到最大值或者内存达到容量上限,Memory Channel会有数据丢失。

  1. File Channel

File Channel把Event保存在本地硬盘中,比Memory Channel提供更好的可靠性和可恢复性,不过要操作本地文件,性能要差一些。

  1. Kafka Channel

Kafka Channel把Event保存在Kafka集群中,能提供比File Channel更好的性能和比Memory Channel更高的可靠性。

3. Sink

  1. Avro Sink

Avro SinkFlume的分层收集机制的重要组成部分。 发送到此接收器的Flume事件变为Avro事件,并发送到配置指定的主机名/端口对。事件将从配置的通道中按照批量配置的批量大小取出。

  1. Kafka Sink

Kafka Sink将会使用FlumeEvent header中的topickey属性来将event发送给Kafka。如果FlumeEvent的header中有topic属性,那么此event将会发送到header的topic属性指定的topic中。如果FlumeEvent的header中有key属性,此属性将会被用来对此event中的数据指定分区,具有相同key的event将会被划分到相同的分区中,如果key属性null,那么event将会被发送到随机的分区中。

可以通过自定义拦截器来设置某个event的header中的key或者topic属性。

4.4 Kafka安装

集群规划

 

服务器1

服务器2

服务器3

Kafka

Kafka

Kafka

Kafka

 ================================Begin============================

第2章 Kafka集群部署

2.1 环境准备

2.1.1 集群规划

hadoop102                                  hadoop103                           hadoop104

zk                                              zk                                       zk

kafka                                         kafka                                  kafka

2.1.2 jar包下载

http://kafka.apache.org/downloads.html

.2 Kafka集群部署

1)解压安装包

[newbies@hadoop102 software]$ tar -zxvf kafka_2.11-0.11.0.0.tgz -C /opt/module/

2)修改解压后的文件名称

[newbies@hadoop102 module]$ mv kafka_2.11-0.11.0.0/ kafka

3)在/opt/module/kafka目录下创建logs文件夹

[newbies@hadoop102 kafka]$ mkdir logs

4)修改配置文件

[newbies@hadoop102 kafka]$ cd config/
[newbies@hadoop102 config]$ vi server.properties

输入以下内容:

#broker的全局唯一编号,不能重复
broker.id=0
#删除topic功能使能
delete.topic.enable=true
#处理网络请求的线程数量
num.network.threads=3
#用来处理磁盘IO的现成数量
num.io.threads=8
#发送套接字的缓冲区大小
socket.send.buffer.bytes=102400
#接收套接字的缓冲区大小
socket.receive.buffer.bytes=102400
#请求套接字的缓冲区大小
socket.request.max.bytes=104857600
#kafka运行日志存放的路径	
log.dirs=/opt/module/kafka/logs
#topic在当前broker上的分区个数
num.partitions=1
#用来恢复和清理data下数据的线程数量
num.recovery.threads.per.data.dir=1
#segment文件保留的最长时间,超时将被删除
log.retention.hours=168
#配置连接Zookeeper集群地址
zookeeper.connect=hadoop102:2181,hadoop103:2181,hadoop104:2181

5)配置环境变量

[newbies@hadoop102 module]$ sudo vi /etc/profile

#KAFKA_HOME
export KAFKA_HOME=/opt/module/kafka
export PATH=$PATH:$KAFKA_HOME/bin

[newbies@hadoop102 module]$ source /etc/profile

6)分发安装包

[newbies@hadoop102 module]$ xsync kafka/

注意:分发之后记得配置其他机器的环境变量

7)分别在hadoop103和hadoop104上修改配置文件/opt/module/kafka/config/server.properties中的broker.id=1broker.id=2

       注:broker.id不得重复

8)启动集群

依次在hadoop102、hadoop103、hadoop104节点上启动kafka

[newbies@hadoop102 kafka]$ bin/kafka-server-start.sh config/server.properties &
[newbies@hadoop103 kafka]$ bin/kafka-server-start.sh config/server.properties &
[newbies@hadoop104 kafka]$ bin/kafka-server-start.sh config/server.properties &

9)关闭集群

[newbies@hadoop102 kafka]$ bin/kafka-server-stop.sh stop
[newbies@hadoop103 kafka]$ bin/kafka-server-stop.sh stop
[newbies@hadoop104 kafka]$ bin/kafka-server-stop.sh stop

2.3 Kafka命令行操作

1)查看当前服务器中的所有topic

[newbies@hadoop102 kafka]$ bin/kafka-topics.sh --zookeeper hadoop102:2181 --list

2)创建topic

[newbies@hadoop102 kafka]$ bin/kafka-topics.sh --zookeeper hadoop102:2181 \
--create --replication-factor 3 --partitions 1 --topic first


选项说明:
--topic 定义topic名
--replication-factor  定义副本数
--partitions  定义分区数

3)删除topic

[newbies@hadoop102 kafka]$ bin/kafka-topics.sh --zookeeper hadoop102:2181 \
--delete --topic first

需要server.properties中设置delete.topic.enable=true否则只是标记删除或者直接重启。

4)发送消息

[newbies@hadoop102 kafka]$ bin/kafka-console-producer.sh \
--broker-list hadoop102:9092 --topic first
>hello world
>newbies  newbies

5)消费消息

[newbies@hadoop103 kafka]$ bin/kafka-console-consumer.sh \
--zookeeper hadoop102:2181 --from-beginning --topic first

--from-beginning:会把first主题中以往所有的数据都读取出来。根据业务场景选择是否增加该配置。

6)查看某个Topic的详情

[newbies@hadoop102 kafka]$ bin/kafka-topics.sh --zookeeper hadoop102:2181 \
--describe --topic first

================================End==============================

4.4.1 Kafka集群启动停止脚本

1)在/home/newbies/bin目录下创建脚本kf.sh

[newbies@hadoop102 bin]$ vim kf.sh

在脚本中填写如下内容

#! /bin/bash

case $1 in
"start"){
        for i in hadoop102 hadoop103 hadoop104
        do
                echo " --------启动 $i kafka-------"
                # 用于KafkaManager监控

                ssh $i "export JMX_PORT=9988 && /opt/module/kafka/bin/kafka-server-start.sh -daemon /opt/module/kafka/config/server.properties "
        done
};;
"stop"){
        for i in hadoop102 hadoop103 hadoop104
        do
                echo " --------停止 $i kafka-------"
                ssh $i "ps -ef | grep server.properties | grep -v grep| awk '{print $2}' | xargs kill >/dev/null 2>&1 &"
        done
};;
esac

注意:启动Kafka时要先开启JMX端口,是用于后续KafkaManager监控。

2)增加脚本执行权限

[newbies@hadoop102 bin]$ chmod 777 kf.sh

3)kf集群启动脚本

[newbies@hadoop102 module]$ kf.sh start

4)kf集群停止脚本

[newbies@hadoop102 module]$ kf.sh stop

4.4.2 查看所有Kafka topic

[newbies@hadoop102 kafka]$ bin/kafka-topics.sh --zookeeper hadoop102:2181 --list

4.4.3 创建 Kafka topic

进入到/opt/module/kafka/目录下分别创建:启动日志主题、事件日志主题。

1)创建启动日志主题

[newbies@hadoop102 kafka]$ bin/kafka-topics.sh --zookeeper hadoop102:2181,hadoop103:2181,hadoop104:2181  --create --replication-factor 1 --partitions 1 --topic topic_start

2)创建事件日志主题

[newbies@hadoop102 kafka]$ bin/kafka-topics.sh --zookeeper hadoop102:2181,hadoop103:2181,hadoop104:2181  --create --replication-factor 1 --partitions 1 --topic topic_event

4.4.4 删除 Kafka topic

[newbies@hadoop102 kafka]$ bin/kafka-topics.sh --delete --zookeeper hadoop102:2181,hadoop103:2181,hadoop104:2181 --topic topic_start

[newbies@hadoop102 kafka]$ bin/kafka-topics.sh --delete --zookeeper hadoop102:2181,hadoop103:2181,hadoop104:2181 --topic topic_event

4.4.5 生产消息

[newbies@hadoop102 kafka]$ bin/kafka-console-producer.sh \
--broker-list hadoop102:9092 --topic topic_start
>hello world
>newbies  newbies

4.4.6 消费消息

[newbies@hadoop103 kafka]$ bin/kafka-console-consumer.sh \
--zookeeper hadoop102:2181 --from-beginning --topic topic_start

--from-beginning:会把first主题中以往所有的数据都读取出来。根据业务场景选择是否增加该配置。

4.4.7 查看某个Topic的详情

[newbies@hadoop102 kafka]$ bin/kafka-topics.sh --zookeeper hadoop102:2181 \
--describe --topic topic_start

.4.8 Kafka Manager安装

Kafka Manager是yahoo的一个Kafka监控管理项目。

1)下载地址

https://github.com/yahoo/kafka-manager

下载之后编译源码,编译完成后,拷贝出:kafka-manager-1.3.3.22.zip

2)拷贝kafka-manager-1.3.3.22.zip到hadoop102的/opt/module目录

[newbies@hadoop102 module]$ pwd
/opt/module

3)解压kafka-manager-1.3.3.22.zip到/opt/module目录

[newbies@hadoop102 module]$ unzip kafka-manager-1.3.3.22.zip

4)进入到/opt/module/kafka-manager-1.3.3.22

[newbies@hadoop102 module]$ cd /opt/module/kafka-manager-1.3.3.22/

5)启动KafkaManager

[newbies@hadoop102 kafka-manager-1.3.3.22]$ export ZK_HOSTS="hadoop102:2181,hadoop103:2181,hadoop104:2181"
nohup bin/kafka-manager   -Dhttp.port=7456 >start.log 2>&1 &

6)在浏览器中打开

http://hadoop102:7456

可以看到这个界面,选择添加 cluster;

我们要配置好zk的hosts,cluster的name,kafka的版本,点确定。

至此,就可以查看整个kafka集群的状态,包括:topic的状态、brokers的状态、cosumer的状态。

在kafka的/opt/module/kafka-manager-1.3.3.22/application.home_IS_UNDEFINED 目录下面,可以看到kafka-manager的日志。

4.4.9 Kafka Manager启动停止脚本

1)在/home/newbies/bin目录下创建脚本km.sh

[newbies@hadoop102 bin]$ vim km.sh

       在脚本中填写如下内容

#! /bin/bash

case $1 in
"start"){
        echo " -------- 启动 KafkaManager -------"
        cd /opt/module/kafka-manager-1.3.3.22/
        export ZK_HOSTS="hadoop102:2181,hadoop103:2181,hadoop104:2181"
        nohup bin/kafka-manager   -Dhttp.port=7456 >start.log 2>&1 &
};;
"stop"){
        echo " -------- 停止 KafkaManager -------"
        ps -ef | grep ProdServerStart | grep -v grep |awk '{print $2}' | xargs kill
};;
esac

2)增加脚本执行权限

[newbies@hadoop102 bin]$ chmod 777 km.sh

3)km集群启动脚本

[newbies@hadoop102 module]$ km.sh start

4)km集群停止脚本

[newbies@hadoop102 module]$ km.sh stop

4.4.10 小结

https://www.cnblogs.com/xiaodf/p/6023531.html

1)Kafka压测

用kafka官方自带的脚本,对kafka进行压测。Kafka压测时,可以查看到哪个地方出现了瓶颈(iocpu,内存,网络)。一般都是网络io达到瓶颈。结果一般都是M/s

kafka-consumer-perf-test.sh

kafka-producer-perf-test.sh

2)Kafka Producer压力测试

(1)在/opt/module/kafka/bin目录下面有这两个文件。我们来测试一下

[newbies@hadoop102 kafka]$ bin/kafka-producer-perf-test.sh  --topic test --record-size 100 --num-records 100000 --throughput 1000 --producer-props bootstrap.servers=hadoop102:9092,hadoop103:9092,hadoop104:9092

说明:throughput 是每秒多少条信息,record size是一条信息有多大,单位是字节。num-records是总共发送多少条信息。

(2)Kafka会打印下面的信息

5000 records sent, 999.4 records/sec (0.10 MB/sec), 1.9 ms avg latency, 254.0 max latency.
5002 records sent, 1000.4 records/sec (0.10 MB/sec), 0.7 ms avg latency, 12.0 max latency.
5001 records sent, 1000.0 records/sec (0.10 MB/sec), 0.8 ms avg latency, 4.0 max latency.
5000 records sent, 1000.0 records/sec (0.10 MB/sec), 0.7 ms avg latency, 3.0 max latency.
5000 records sent, 1000.0 records/sec (0.10 MB/sec), 0.8 ms avg latency, 5.0 max latency.

参数解析:本例中一共写入10w条消息,每秒向kafka写入了0.10MB的数据,平均是1000条消息/秒,每次写入的平均延迟为0.8毫秒,最大的延迟为254毫秒。

3)Kafka Consumer压力测试

Consumer的测试同样如此。如果这四个指标(io,cpu,内存,网络)都未满,考虑增加分区数来提升性能。

[newbies@hadoop102 kafka]$ 
bin/kafka-consumer-perf-test.sh --zookeeper hadoop102:2181 --topic test --fetch-size 10000 --messages 10000000 --threads 1

参数说明:

--zookeeper 指定zookeeper的链接信息,

--topic 指定topic的名称,

--fetch-size 指定每次fetch的数据的大小,

--messages 总共要消费的消息个数,

测试结果说明:

start.time, end.time, data.consumed.in.MB, MB.sec, data.consumed.in.nMsg, nMsg.sec

2019-02-19 20:29:07:566, 2019-02-19 20:29:12:170, 9.5368, 2.0714, 100010, 21722.4153

开始测试时间,测试结束数据,最大吞吐率9.5368MB/s,平均每秒消费2.0714MB/s,最大每秒消费100010条,平均每秒消费21722.4153

4)如何设计kafka集群的大小

(1)Kafka的机器数量。

先要预估一天大概有多少数据量,算出峰值是多少数据的速度。然后再去用压测的标准计算一天能承受多少的负载。在上面乘以一个n倍(考虑写一份,存在n倍的同时冗余,ISR,如果我们的冗余是2,那么就是2),就是大概的数据量。数据量我们都是算多少M每秒。

一般我们压只压一下写的速度,不让数据在业务层积压。比如我压测测出写入的速度是100M/s一台,峰值的业务数据的速度是200M/s,如果我们追求实时性,那么我们需要(200*2/100=4,然后需要2n+1台,就是9台,能达到实时性)。

参数说明:200是峰值速率;2 是副本个数,100是经验值。2n+1是经验值

(2)Kafka的硬盘大小

Kafka的硬盘大小设置成能至少存一周的数据就可以满足了。

5)Kafka的日志留存设置。

Kafka的日志留存一般有两种,一种是时间,一种是大小。我们设置至少保留3天的数据量,取中间的最大值。大小和性能并无太大关系,都是O(1) ,所以越大越好。

6)Kafka监控

公司自带的agent部署在每台服务器上面,出现程序奔溃则会报警。

负载过高也会报警(磁盘,cpu,内存等)

Kafka监控软件。https://github.com/yahoo/kafka-manager

7)Kakfa 分区数。

分区数并不是越多越好,一般分区数不要超过集群机器数量。分区数越多占用内存越大(ISR等),一个节点集中的分区也就越多,当它宕机的时候,对系统的影响也就越大。

8)分区数和冗余数的设定?

冗余数一般我们设置成2个。根据ISR,分区数的话一般是3个。

9)Kafka一天的数据量有多少?

根据所有日志的数据量,8亿左右。大小60G左右(压缩后)。

10)我们的集群有多大?

3台16g的Kafka,绰绰有余。

4.5 Flume消费Kafka数据写到HDFS

集群规划

 

服务器1

服务器2

服务器3

Flume(消费Kafka)

 

 

Flume

4.5.1 日志消费Flume配置

1)Flume配置分析

2)Flume的具体配置如下:

       (1)在hadoop104的/opt/module/flume/conf目录下创建kafka-flume-hdfs.conf文件

[newbies@hadoop102 conf]$ vim kafka-flume-hdfs.conf

在文件配置如下内容

## 组件
a1.sources=r1 r2
a1.channels=c1 c2
a1.sinks=k1 k2

## source1
a1.sources.r1.type = org.apache.flume.source.kafka.KafkaSource
a1.sources.r1.batchSize = 5000
a1.sources.r1.batchDurationMillis = 2000
a1.sources.r1.kafka.bootstrap.servers = hadoop102:9092,hadoop103:9092,hadoop104:9092
a1.sources.r1.kafka.zookeeperConnect = hadoop102:2181,hadoop103:2181,hadoop104:2181
a1.sources.r1.kafka.topics=topic_start

## source2
a1.sources.r2.type = org.apache.flume.source.kafka.KafkaSource
a1.sources.r2.batchSize = 5000
a1.sources.r2.batchDurationMillis = 2000
a1.sources.r2.kafka.bootstrap.servers = hadoop102:9092,hadoop103:9092,hadoop104:9092
a1.sources.r2.kafka.zookeeperConnect = hadoop102:2181,hadoop103:2181,hadoop104:2181
a1.sources.r2.kafka.topics=topic_event

## channel1
a1.channels.c1.type=memory
a1.channels.c1.capacity=100000
a1.channels.c1.transactionCapacity=10000

## channel2
a1.channels.c2.type=memory
a1.channels.c2.capacity=100000
a1.channels.c2.transactionCapacity=10000

## sink1
a1.sinks.k1.type = hdfs
a1.sinks.k1.hdfs.path = /origin_data/gmall/log/topic_start/%Y-%m-%d
a1.sinks.k1.hdfs.filePrefix = logstart-
a1.sinks.k1.hdfs.round = true
a1.sinks.k1.hdfs.roundValue = 30
a1.sinks.k1.hdfs.roundUnit = second

##sink2
a1.sinks.k2.type = hdfs
a1.sinks.k2.hdfs.path = /origin_data/gmall/log/topic_event/%Y-%m-%d
a1.sinks.k2.hdfs.filePrefix = logevent-
a1.sinks.k2.hdfs.round = true
a1.sinks.k2.hdfs.roundValue = 30
a1.sinks.k2.hdfs.roundUnit = second

## 不要产生大量小文件
a1.sinks.k1.hdfs.rollInterval = 30
a1.sinks.k1.hdfs.rollSize = 0
a1.sinks.k1.hdfs.rollCount = 0

a1.sinks.k2.hdfs.rollInterval = 30
a1.sinks.k2.hdfs.rollSize = 0
a1.sinks.k2.hdfs.rollCount = 0

## 控制输出文件是原生文件。
a1.sinks.k1.hdfs.fileType = CompressedStream 
a1.sinks.k2.hdfs.fileType = CompressedStream 

a1.sinks.k1.hdfs.codeC = lzop
a1.sinks.k2.hdfs.codeC = lzop

## 拼装
a1.sources.r1.channels = c1
a1.sinks.k1.channel= c1

a1.sources.r2.channels = c2
a1.sinks.k2.channel= c2

4.5.2 Flume异常处理

1)问题描述:如果启动消费Flume抛出如下异常

ERROR hdfs.HDFSEventSink: process failed
java.lang.OutOfMemoryError: GC overhead limit exceeded

2)解决方案步骤:

(1)在hadoop102服务器的/opt/module/flume/conf/flume-env.sh文件中增加如下配置

export JAVA_OPTS="-Xms100m -Xmx2000m -Dcom.sun.management.jmxremote"

(2)同步配置到hadoop103、hadoop104服务器

[newbies@hadoop102 conf]$ xsync flume-env.sh

4.5.2 日志消费Flume启动停止脚本

1)在/home/newbies/bin目录下创建脚本f2.sh

[newbies@hadoop102 bin]$ vim f2.sh

       在脚本中填写如下内容

#! /bin/bash

case $1 in
"start"){
        for i in hadoop104
        do
                echo " --------启动 $i 消费flume-------"
                ssh $i "nohup /opt/module/flume/bin/flume-ng agent --conf-file /opt/module/flume/conf/kafka-flume-hdfs.conf --name a1 -Dflume.root.logger=INFO,LOGFILE >/opt/module/flume/log.txt   2>&1 &"
        done
};;
"stop"){
        for i in hadoop104
        do
                echo " --------停止 $i 消费flume-------"
                ssh $i "ps -ef | grep kafka-flume-hdfs | grep -v grep |awk '{print \$2}' | xargs kill"
        done

};;
esac

2)增加脚本执行权限

[newbies@hadoop102 bin]$ chmod 777 f2.sh

3)f2集群启动脚本

[newbies@hadoop102 module]$ f2.sh start

4)f2集群停止脚本

[newbies@hadoop102 module]$ f2.sh stop

4.6 采集通道启动/停止脚本

1)在/home/newbies/bin目录下创建脚本cluster.sh

[newbies@hadoop102 bin]$ vim cluster.sh

在脚本中填写如下内容

#! /bin/bash

case $1 in
"start"){
	echo " -------- 启动 集群 -------"

	echo " -------- 启动 hadoop集群 -------"
	/opt/module/hadoop-2.7.2/sbin/start-dfs.sh 
	ssh hadoop103 "/opt/module/hadoop-2.7.2/sbin/start-yarn.sh"

	#启动 Zookeeper集群
	zk.sh start

	#启动 Flume采集集群
	f1.sh start

	#启动 Kafka采集集群
	kf.sh start

sleep 4s;

	#启动 Flume消费集群
	f2.sh start

	#启动 KafkaManager
	km.sh start
};;
"stop"){
        echo " -------- 停止 集群 -------"

	#停止 KafkaManager
	km.sh stop

    #停止 Flume消费集群
	f2.sh stop

	#停止 Kafka采集集群
	kf.sh stop

    sleep 4s;

	#停止 Flume采集集群
	f1.sh stop

	#停止 Zookeeper集群
	zk.sh stop

	echo " -------- 停止 hadoop集群 -------"
	ssh hadoop103 "/opt/module/hadoop-2.7.2/sbin/stop-yarn.sh"
	/opt/module/hadoop-2.7.2/sbin/stop-dfs.sh 
};;
esac

2)增加脚本执行权限

[newbies@hadoop102 bin]$ chmod 777 cluster.sh

3)cluster集群启动脚本

[newbies@hadoop102 module]$ cluster.sh start

4)cluster集群停止脚本

[newbies@hadoop102 module]$ cluster.sh stop
发布了322 篇原创文章 · 获赞 46 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/qq_31784189/article/details/105166765
今日推荐