一个小兔子的大数据见解2


Big Data


阿里的大数据解决方案
MAXCOMPUTE
DATAWORKS
QUICKBI
1、Vmware增强
2、

1.1、VMware 虚拟网络设备
1.1.1、虚拟网卡、虚拟交换机
虚拟网卡:宿主机有自己的网卡,通常在这个路径在(控制面板\网络和 Internet\中可以找到),可以看到好几个网卡,我们可以配置网卡中的信息,例如ip地址等。
而虚拟机中的网卡,就叫做虚拟网卡,同样,我们可以配置虚拟网卡的ip地址等信息。 虚拟机的虚拟网卡所在的路径是:
/etc/sysconfig/network-scripts/ifcfg-eth0
虚拟交换机:电脑和电脑之间,需要通过一种设备进行连接,这种设备就叫做交换机。真实的电脑和虚拟的电脑进行连接,也需要这种设备来进行连接,只不过这种设备不是真实存在的,而是通过软件来模拟了,所以这个交换机就叫做虚拟交换机。

1.1.2、虚拟 DHCP 服务器
DHCP服务器: (Dynamic Host Configuration Protocol, 动态主机配置协议),实现了dhcp协议的进程,就叫做dhcp服务器。你也可以把某个设备称之为dhcp服务器,只要这个设备运行了一个进程,这个进程实现了dhcp协议即可。dhcp协议称为“动态主机配置协议”。这个协议的作用是,会动态的为内部网络的主机,分配ip地址。

1.1.3、虚拟 NAT 服务器
NAT服务器: (Network Address Translation, 网络地址转换)提供NAT 功能的服务器:就是把在内部网络中使用的IP地址转换成外部网络中使用的IP 地址,把不可路由的IP地址转化成可路由的IP地址,对外部网络隐蔽内部网。

1.1.4、虚拟网卡适配器
当我们安装VMware Workstation 12时,就会在我们的实体PC机器上安装主机虚拟网卡适配器。

1.2、VMware 网络连接模式
真实主机、虚拟机以及其他的网络设备,他们进行连接时,有3种线路图都可以连接,按照不同的线路图来进行连接,各种设备的配置也会不一样。这里所说的线路图,就叫做VMware的网络连接模式。这3种网络连接模式分别时:bridged模式、host-only模式、nat模式。
不管采用哪一种线路图,只要保证虚拟机和虚拟机之间能够ping通,主机和虚拟机之间能够ping通,虚拟机能够联网即可。我这里就采用nat模式了。

1.2.1、NAT(网络地址转换)

注意:NAT模式下,宿主机需要开启VMware NAT Service和VMware DHCP Service。
1.2.2、Bridged(桥接)


1.2.3、Host-only (主机)


1.3、VMware 虚拟机克隆
一般使用虚拟机克隆大量复制虚拟机,用来进行集群服务器的搭建。克隆后,由于是克隆,所以ip地址,硬件地址、主机名、uuid都是一样的,所以需要对克隆出来的虚拟机,进行一些修改,需要修改的地方如下:

1、硬件地址需要修改
第一步:关机状态下,生成新的MAC地址


第二步:开机,删除文件:/etc/udev/rules.d/70-persistent-net.rules。大方删除即可,待会配置好之后,重启系统,这个文件会自动创建。

第三步:删除网卡中,hwaddr所在的行,网卡的路径是:
/etc/sysconfig/network-scripts/ifcfg-eth0

第四步:删除网卡中,UUID所在的行

2、IP地址需要修改
[root@node1 ~]# vi /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth0
TYPE=Ethernet
ONBOOT=yes
NM_CONTROLLED=yes
BOOTPROTO=static
DEFROUTE=yes
PEERDNS=yes
PEERROUTES=yes
IPV4_FAILURE_FATAL=yes
IPV6INIT=no
NAME="System eth0"
IPADDR=192.168.5.201
NETMASK=255.255.255.0
GATEWAY=192.168.5.2
DNS1=192.168.5.2

修改完之后,重启机器。新的虚拟机就可以使用了。
reboot


3、修改主机名
vi /etc/sysconfig/network
这个文件就是修改主机名的地方,修改完成后,exit退出登录,重新登录一次即可

4、修改域名映射(这个放在后面再说)
1、修改真实主机的域名映射
C:\Windows\System32\drivers\etc\hosts

2、修改虚拟机里的域名映射
vi /etc/hosts
192.168.5.201    node1
192.168.5.202    node2
192.168.5.203    node3

1.4、SSH(安全外壳协议)
SSH为Secure Shell的缩写,是一种网络安全协议,专为远程登录会话和其他网络服务提供安全性的协议。生产中,我们经常使用ssh来登录、免密码登录远程linux机器。

1.4.1、免密码登录的流程
在linux主机node1上,安装ssh的客户端,在linux主机node2上,安装ssh的服务端。那么下图可以理解为配置node1到node2的免密码登录的流程。通俗说就是,在node1主机上的命令窗口中,不用输入node2主机的密码,就能登录node2主机。这就是免密码登录。

1.4.2、OpenSSH 使用
OpenSSH是SSH协议的免费开源实现,也就是说,OpenSSH是一套软件,这套软件实现了SSH协议。
OpenSSH 由客户端和服务端的软件组成。
服务端是一个守护进程(daemon),他在后台运行并响应来自客户端的连接请求。
客户端包含ssh程序、scp程序、sftp程序等
默认情况下,CentOS系统会自带安装OpenSSH。


使用示例: 配置node1至node2机器的免密登录。
a) 在node1机器上
ssh-keygen -t rsa  按四下回车
生成密钥文件和私钥文件 id_rsa,id_rsa.pub

b) 将公钥拷贝给node2机器
ssh-copy-id node2
首次链接需要输入用户密码、验证成功后后续免密登陆。

若出现command not found可以执行下面,去掉最小化安装的问题.
yum -y install openssh-clients

1.5、文件上传、下载
在涉及Linux相关的开发中,经常需要进行linux和Windows之间的文件、安装包等上传和下载操作。sftp和lrzsz是使用比较广泛的两种方式。
1.5.1、SFTP
sftp是Secure File Transfer Protocol的缩写,安全文件传送协议,是SSH协议的一部分。我们可以利用openssh中的sftp功能,把本地的文件上传到远程linux主机上,或者把远程linux主机中的文件,下载到本地。
SecureCRT远程连接至CentOS后,按alt+p即可打开sftp会话窗口。
常用的sftp命令有:
pwd和lpwd
pwd是看sftp服务所在机器(即CentOS)默认的当前目录 lpwd是看Windows本地默认目录。


ls和lls
ls查看sftp服务器默认当前目录下内容 lls是看Windows默认当前目录下内容
put d:/sparksql_textdata.csv
把Windows上文件上传到sftp服务器的默认当前目录下

get install.log.syslog
把sftp服务器当前目录下的文件下载到windows当前目录下


1.5.2、lrzsz
lrzsz是一款在linux里可代替sftp上传和下载的程序。
Centos系统中,可直接yum -y install lrzsz 程序会自动安装好,也可以下载安装包离线进行安装,详细参考附件资料。
上传文件:命令行输入rz,打开上传文件会话窗口。


下载文件:sz  下载文件路径

2、Linux 增强
2.1、查找命令
2.1.1、grep

grep 命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。
格式:grep [option] pattern [file]
可使用 —help 查看更多参数。
使用实例:
ps -ef | grep sshd  查找指定ssh服务进程
ps -ef | grep sshd | grep -v grep 查找指定服务进程,排除gerp身
ps -ef | grep sshd –c 查找指定进程个数

2.1.2、find
find命令在目录结构中搜索文件,并对搜索结果执行指定的操作。
find 默认搜索当前目录及其子目录,并且不过滤任何结果(也就是返回所有文件),将它们全都显示在屏幕上。
实际参数很多,可使用 —help 查看。
使用实例:
find  . -name "*.log" -ls  在当前目录查找以.log结尾的文件,并显示详细信息。
find /root/ -perm 777   查找/root/目录下权限为777的文件
find  . -type f -name "*.log"  查找当目录,以.log结尾的普通文件
find  . -type d | sort   查找当前所有目录并排序
find  . -size +100M  查找当前目录大于100M的文件
2.1.3、locate
locate 让使用者可以很快速的搜寻档案系统内是否有指定的档案。其方法是先建立一个包括系统内所有档案名称及路径的数据库。Linux系统自动创建这个数据库,默认每天自动更新一次,所以使用locate 命令查不到最新变动过的文件。为了避免这种情况,可以在使用locate之前,先使用updatedb命令,手动更新数据库。
yum -y install mlocate 如果是精简版CentOS系统需要安装locate命令
updatedb 命令来创建locate命令依赖的数据库

使用实例:
updatedb
locate /etc/sh 搜索etc目录下所有以sh开头的文件
locate pwd 查找和pwd相关的所有文件

2.1.4、whereis
whereis命令是定位可执行文件、源代码文件、帮助文件在文件系统中的位置。这些文件的属性应属于原始代码,二进制文件,或是帮助文件。
whereis和下locate一样,会从数据库中查找数据,而不是像find命令那样,通过遍历硬盘来查找。
使用实例:
whereis ls    将和ls文件相关的文件都查找出来
whereis -m ls 查找ls命令说明文档路径
whereis -s ls  查找ls源文件
2.1.5、which
which命令的作用是在PATH变量指定的路径中,搜索某个系统命令的位置,并且返回第一个搜索结果。
使用which命令,就可以看到某个系统命令是否存在,以及执行的到底是哪一个位置的命令。
使用实例:
which pwd  查找pwd命令所在路径
which java  查找path中java的路径
2.2、su、sudo
2.2.1、su
su用于用户之间的切换。但是切换前的用户依然保持登录状态。如果是root 向普通或虚拟用户切换不需要密码,反之普通用户切换到其它任何用户都需要密码验证。
su在不加任何参数,默认为切换到root用户,但没有转到root用户根目录下;su 加参数 - ,表示默认切换到root用户,并转到root用户根目录下。

su不足:如果某个用户需要使用root权限、则必须要把root密码告诉此用户。
退出返回之前的用户:exit


2.2.2、sudo
sudo是为所有想使用root权限的普通用户设计的。可以让普通用户具有临时使用root权限的权利。只需输入自己账户的密码即可。当然这个普通用户必须在
/etc/sudoers文件中有配置项,才具有使用sudo的权利。
没有配置权限之前,普通用户无法进行root权限操作,不信你看下图:


使用root用户编辑/etc/sudoers文件,给普通用户授权命令行输入visudo,打开/etc/sudoers文件,加入如下的内容,保存。


这样普通用户就可以使用sudo执行root权限的命令了。


useradd test        添加用户
passwd test            修改密码

删除
userdel test            删除账户
rm -rf /home/test        删除家目录
rm -rf /var/mail/test      删除test用户相关的文件


案例:允许hadoop用户以root身份在任意主机上执行各种应用命令,需要输入hadoop用户的密码。 sudo  ls  /
hadoop  ALL=(ALL)   ALL
案例:只允许hadoop用户以root身份在node-23上执行ls 、cat命令,并且执行时候免输入密码。
配置文件中:
hadoop  node-23=NOPASSWD:  /bin/ls, /bin/cat


执行非ls 、cat命令也会禁止。


而cat命令就可以执行。


2.3、挂载(mount)命令
在linux操作系统中,挂载是指将一个设备挂接到一个已存在的目录上。要访问设备中的文件,通过访问这个挂载目录来访问。
什么是挂载?
插入一个硬盘,就会在文件系统中,多出现了一个盘符(假设是k盘)。这种现象,可以这么来描述:把一个移动设备(硬盘),挂载到了k盘。
挂载:

mount -t iso9660  -o ro  /dev/cdrom   /mnt/cdrom
把  iso9660类型的移动设备(/dev/cdrom, 这种类型的设备就是光盘)
挂载到 /mnt/cdrom 目录中,采用只读的方式来挂载。
查看/mnt/cdrom目录中的内容,就相当于,查看光盘的内容
注意:/mnt/cdrom,必须手动提前创建好

卸载:
umount /dev/cdrom

命令格式:mount [-t vfstype] [-o options] device dir
-t vfstype 指定文件系统的类型。mount 会自动选择正确的类型。常用类型有:光盘镜像iso9660、linux文件网络共享nfs等等。
-o options 主要用来描述设备或档案的挂接方式。常用的参数有:
loop:用来把一个文件当成硬盘分区挂接上系统
ro:采用只读方式挂接设备
rw:采用读写方式挂接设备
device: 要挂接(mount)的设备 dir设备在系统上的挂接点(mount point)

案例:挂载光驱
mkdir   /mnt/cdrom
mount -t iso9660 -o ro /dev/cdrom /mnt/cdrom/
将设备/dev/cdrom挂载到 挂载点/mnt/cdrom中

案例:挂载光盘镜像文件(.iso)
mkdir   /mnt/centos mount -o loop  /root/Centos-6.7.DVD1.iso
/mnt/centos

案例:卸载umount
umount /mnt/cdrom


2.4、本地 yum 源
yum就是一个软件管家,可以使用它来安装、卸载软件。
yum机制的强大之处在于yum源。yum源相当是一个目录项,当我们使用yum 机制安装软件时,若需要安装依赖软件,则yum机制就会根据在yum源中定义好的路径查找依赖软件,并将依赖软件安装好。通俗说,yum源指的就是一个路径,这个路径指定了yum在安装软件的时候,在哪里取得这些软件来安装。
yum源分为网络yum源和本地yum源。
yum源配置文件有两个。一是直接配置在/etc/yum.conf中,其中包含一些主要的配置信息。另外就是/etc/yum.repos.d/下的 xx.repo后缀文件, 默认都会被加载进来。
案例:使用CentOS镜像创建本地yum源
挂载iso镜像,拷贝所有文件至本地yum目录
mkdir /dev/centios /mnt/local_yum
mount -o loop /root/CentOS-6.7-x86_64-bin-DVD1.iso /dev/centios
cp -r /dev/centios/* /mnt/local_yum/

修改yum源配置
cd /etc/yum.repos.d/
rename .repo .repo.bak *.repo
cp CentOS-Base.repo.bak CentOS-Local.repo
vi CentOS-Local.repo
[local_yum]
name=This is a local repo
baseurl=file:///mnt/local_yum
enabled=1
gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6

更新yum配置
yum clean all
yum repolist all


通过上面的步骤操作,已经可以使用本地yum源了。有需要进行安装的软件
包就可以直接运行”yum install xxx“进行安装了。
网络yum的配置

1、去到目录
cd /etc/yum.repos.d

2、备份里面的所有文件
rename .repo .repo.bak *.repo

3、下载国内的某个yum源文件  国内yum
wget http://mirrors.163.com/.help/CentOS6-Base-163.repo

4、执行下边命令更新yum配置,使操作立即生效
yum makecache
2.5、 系统服务
service --status-all   # 查看系统所有的后台服务进程
service sshd status   # 查看指定的后台服务进程的状态
service sshd stop
service sshd start
service sshd restart

配置后台服务进程的开机自启
chkconfig httpd on   ## 让httpd服务开机自启
chkconfig httpd off  ## 让httpd服务开机不要自启

防火墙:关闭状态,并且开机的时候,不要自己启动防火墙
防火墙的服务名称:iptables
步骤:
service iptables stop
service iptables status    检查停止了没
chkconfig iptables off  让linux启动的时候,防火墙不要自动开启

Why?
后面会装很多软件,这些软件会访问其他机器,比如说,node2的进程,访问node1的进程,如果防火墙没关,那就访问不了这些进程


2.6、网络管理
2.6.1、主机名配置
查看主机名
hostname
修改主机名(重启后无效)
hostname hadoop
修改主机名(重启后永久生效)
vi /ect/sysconfig/network
2.6.2、IP 地址配置
修改配置文件(重启后永久生效)
vi /etc/sysconfig/network-scripts/ifcfg-eth0
使修改文件的网络配置生效:
service network restart重启网络服务
reboot 重启计算机

2.6.3、域名映射
/etc/hosts文件用于在通过主机名进行访问时做ip地址解析之用。所以,你想访问一个什么样的主机名,就需要把这个主机名和它对应的ip地址
配置需要在/etc/hosts文件中进行。

2.6.4、网络端口监听
netstat是一款命令行工具,用于列出系统上所有的网络socket连接情况,包括 tcp, udp 以及 unix socket,另外它还能列出处于监听状态(即等待接入请求)的socket。如想确认Web 服务有没有起来,可查看80端口有没有打开。
示例:
常见参数:
-a (all)显示所有选项,默认不显示LISTEN相关
-t (tcp)仅显示tcp相关选项 -u (udp)仅显示udp相关选项
-n 禁用域名反向解析功能,只显示ip
-l 仅列出有在 Listen (监听) 的服务状态
-p 显示建立相关链接的进程信息
-ep 可以同时查看进程名和用户名
netstat –nlpt 获取进程名、进程号以及用户 ID


tail -f文件名(/root/data.txt :root文件夹下面的data.txt文件)     可以监听文件内容的变化,当有新增的内容时会继续打印到屏幕上,因此在处理日志文件时常常会使用到它来跟踪文件变化。

2.7、crontab 配置
crontab是Unix和Linux用于设置定时任务的指令。通过crontab命令,可以在固定间隔时间执行指定的系统指令或shell脚本。时间间隔的单位可以是分钟、小时、日、月、周及以上的任意组合。
crontab安装:
yum install crontabs
服务操作说明:
service crond start   ## 启动服务
service crond stop    ## 关闭服务
service crond restart ## 重启服务

service crond reload   ## 重新载入配置
service crond status    ## 查看crontab服务状态:
chkconfig crond --list  ## 查看crontab服务是否已设置为开机启动
chkconfig crond on      ## 加入开机自动启动
2.7.1、命令格式
crontab [-u user] file
crontab [-u user] [ -e | -l | -r ]
参数说明:
-u user:用来设定某个用户的crontab服务
file:file是命令文件的名字,表示将file做为crontab的任务列表文件
并载入crontab。
-e:编辑某个用户的crontab文件内容。如果不指定用户,则表示编辑当前
用户的crontab文件。
-l:显示某个用户的crontab文件内容。如果不指定用户,则表示显示当前
用户的crontab文件内容。
-r:删除定时任务配置,从/var/spool/cron目录中删除某个用户的crontab
文件,如果不指定用户,则默认删除当前用户的crontab文件。

命令示例:
crontab file [-u user] ## 用指定的文件替代目前的crontab。
crontab -l [-u user]  ## 列出用户目前的crontab
crontab -e [-u user]  ## 编辑用户目前的crontab.
2.7.2、配置说明、实例
*   *    *   *   5   command
分  时  日  月  周  命令
第1列表示分钟1~59 每分钟用*或者 */1表示
第2列表示小时0~23(0表示0点)
第3列表示日期1~31
第4列表示月份1~12
第5列标识号星期0~6(0表示星期天)
第6列要运行的命令

配置实例:
*/1 * * * * date >> /root/date.txt
每分钟执行一次date命令
30 21 * * * service httpd restart
每晚的21:30重启apache。
45 4 1,10,22 * * /usr/local/etc/rc.d/httpd restart
每月1、10、22日的4 : 45重启apache。
10 1 * * 6,0 /usr/local/etc/rc.d/httpd restart
每周六、周日的1 : 10重启apache。
0,30 18-23 * * * /usr/local/etc/rc.d/httpd restart
每天18 : 00至23 : 00之间每隔30分钟重启apache。
* 23-7/1 * * * /usr/local/etc/rc.d/httpd restart
晚上11点到早上7点之间,每隔一小时重启apache

Shell

3、Shell 编程
Shell是一个用C语言编写的程序,通过Shell用户可以访问操作系统内核服务。它类似于DOS下的command和后来的cmd.exe。Shell既是一种命令语言,又是一种程序设计语言。
Shell script是一种为shell编写的脚本程序。Shell 编程一般指 shell 脚本编程,不是指开发shell自身。
Linux 的 Shell 种类众多,一个系统可以存在多个 shell,可以通过 cat
/etc/shells命令查看系统中安装的shell。
Bash由于易用和免费,在日常工作中被广泛使用。同时,Bash也是大多数
Linux系统默认的Shell。
1.1、HelloWorld
使用vi编辑器新建一个文件 hello.sh。扩展名并不影响脚本执行,见名知意。比如用 php写shell脚本,扩展名就用 .php。
#!/bin/bash
echo "Hello World !"
解释:echo 命令用于向窗口输出文本。
Shell脚本的执行:
第一种方式:
chmod +x ./hello.sh    #使脚本具有执行权限
./hello.sh              #执行脚本
第二种方式:
Shell脚本作为解释器参数运行。直接运行解释器,其参数就是 shell 脚本的文件名,如:
/bin/sh /root/hello.sh
1.2、Shell 变量
1.2.1、语法格式
变量=值,如:your_name="krist.cn"
注意:变量名和等号之间不能有空格,同时,变量名的命名须遵循如下规则:
首个字符必须为字母(a-z,A-Z)
中间不能有空格,可以使用下划线(_)
不能使用标点符号
不能使用 bash 里的关键字(可用 help 命令查看保留关键字)
1.2.2、变量使用
使用一个定义过的变量,只要在变量名前面加 $ 即可。
your_name="krist.cn"
echo $your_name
echo ${your_name}
花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界。
已定义的变量,可以被重新定义。
使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。
使用 unset 命令可以删除变量。不能删除只读变量。
readonly variable_name
unset variable_name
1.2.3、变量类型
局部变量:局部变量在脚本或命令中定义,仅在当前 shell 实例中有效,其
他shell启动的程序不能访问局部变量。
1、在第一个shell实例中有如下代码,可以发现的确输出正确的结果
name=zhangsan
age=23
echo $name
echo $age

2、然后在第二个shell实例中有如下代码,发现没有输出想要看的结果
echo $name
echo $age

环境变量: 所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。可以用过set命令查看当前环境变量。
环境变量的作用范围非常的难记忆,记录在下面4点:
1、export的变量,会被子进程继承下去,子进程修改了该变量,父进程不受影响。
2、export所在的进程结束,该变量就消失。
3、./abc.sh        : 在abc.sh中,使用export定义的变量,其实就在当前shell有效,当前shell其实就是执行命令“./abc.sh”所在的shell的子shell。
4、source ./abc2.sh    : 如果这样执行,在abc2.sh中定义的环境变量,相当于在本shell中,定义了export变量。

现在,就针对这4点,写一些代码来证明,如下所示:
1、export的变量,会被子进程继承下去,子进程修改了该变量,父进程不受影响
fu.sh文件
#! /bin/bash
export name=zhangsan    ##定义了一个环境变量name,值为zhangsan
echo 我是父进程,我在打印name的值,name=$name
echo                    ##换行
./zi.sh    ##调用zi.sh脚本,其实是执行fu.sh脚本的进程,开启了一个子进程,来执行zi.sh脚本
echo 子进程执行完了,我是父进程,我在打印name的值,name=$name

zi.sh文件
#! /bin/bash
echo ....我是子进程,继承下来的值是,name=$name
echo ....我是子进程,我要更改name的值
name=lisi
echo ....我是子进程,更改name的值后,再打印你,得到$name
echo                     #换行

说明:把fu.sh和zi.sh放在同一个目录中,记得添加执行权限,测试命令:./fu.sh

2、export所在的进程结束,该变量就消失
fu1.sh文件
#! /bin/bash
echo 子进程还没开始,打印name的值为:$name
echo 开启子进程。。。
./zi1.sh
echo
echo 子进程结束了,打印name的值为:$name

zi1.sh文件
#! /bin/bash
echo 子进程启动了,要开始设置环境变量了
export name=我爱你
echo 子进程打印name的值,name=$name

说明:把fu1.sh和zi1.sh放在同一个目录中,记得添加执行权限,测试命令:./fu1.sh

3、./abc.sh        : 在abc.sh中,使用export定义的变量,其实就在当前shell有效,当前shell其实就是执行命令“./abc.sh”所在的shell的子shell
例如:
在shell-1中,执行命令:./abc.sh
实际上,是shell-1实例,开启了一个子shell,让子shell来执行abc.sh脚本的,我们给这个子shell起一个名字:A。
那么,在abc.sh脚本中,所定义的环境变量的作用范围,就是 A shell以及 由A shell开启的子shell

测试如下:
在当前窗口(shell-1)中:
export JAVA_HOME=/export/server/jdk
echo 我在打印:JAVA_HOME=$JAVA_HOME

abc.sh文件:
#! /bin/bash
echo 我是shell-1的子进程,我在打印:JAVA_HOME=$JAVA_HOME

写好之后,执行:./abc.sh

4、source ./abc2.sh    : 如果这样执行,在abc.sh中定义的环境变量,相当于在本shell中,定义了export变量。
abc2.sh文件
#! /bin/bash
export ZOOKEEPER_HOME=/export/server/zookeeper
echo 我在abc2.sh中打印:ZOOKEEPER_HOME=$ZOOKEEPER_HOME

在当前窗口:
source ./abc2.sh
echo ZOOKEEPER_HOME=$ZOOKEEPER_HOME


1.3、Shell 参数传递
在执行 Shell 脚本时,可以向脚本传递参数。
脚本内获取参数的格式为:$n。n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,以此类推…… $0表示当前脚本名称。
1.3.1、特殊字符
$#    传递到脚本的参数个数
$*    以一个单字符串显示所有向脚本传递的参数。
$$    脚本运行的当前进程ID号
$@    与$*相同,但是使用时加引号,并在引号中返回每个参数。
$?    显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。
#! /bin/bash
echo 哪个进程执行了我? $$
#pid=`echo $$`    #``的作用:把里面的命令的输出结果捕获到
pid=$(echo $$)    #作用同上
kill -9 $pid
echo 我执行完了
1.3.2、$*和$@区别
相同点:都表示传递给脚本的所有参数。
不同点:
不被" "包含时,$*和$@都以$1 $2… $n 的形式组成参数列表。
被" "包含时,"$*" 会将所有的参数作为一个整体,以"$1 $2 … $n" 的形式组成一个整串;"$@" 会将各个参数分开,以"$1" "$2" … "$n" 的形式组成一个参数列表。
#! /bin/bash

for item in "$@"
do
echo root@node$item;
Done

上面的$@改成$*,再运行观察一下。
测试:./8.sh 1 2 3
1.4、Shell 运算符
Shell和其他编程语言一样,支持包括:算术、关系、布尔、字符串等运算符。原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 expr。expr 是一款表达式计算工具,使用它能完成表达式的求值操作。
例如,两个数相加:
val=`expr 2 + 2`
echo $val
注意:表达式和运算符之间要有空格,例如 2+2 是不对的,必须写成 2 + 2。完整的表达式要被 ` ` 包含,注意不是单引号,在 Esc 键下边。详细请参考附件资料《shell运算符》。
=和==的区别
=和==都可以做判断
1、比较数字或者纯数字的字符串的时候,用=和==都可以,用[ ]或者[[ ]] 都可以做判断
2、比较非纯数字字符串时,
a、用=做比较,不管用[ ]还是[[ ]],都必须=号左右两边完全一样,才为true,否则false
b、用==做比较时,想要用正则表达式,那就必须用 [[ ]] 括起来

此外,还可以通过(())、$[]进行算术运算。
count=1
((count++))
echo $count
a=$((1+2))
a=$[1+2]


--------------------------------------------------------
算术运算符
#!/bin/bash

a=10
b=20

val=`expr $a + $b`
echo "a + b : $val"

val=`expr $a - $b`
echo "a - b : $val"

val=`expr $a \* $b`
echo "a * b : $val"

val=`expr $b / $a`
echo "b / a : $val"

val=`expr $b % $a`
echo "b % a : $val"

if [ $a == $b ]
then
echo "a 等于 b"
fi
if [ $a != $b ]
then
echo "a 不等于 b"
fi


执行脚本,输出结果如下所示
a + b : 30
a - b : -10
a * b : 200
b / a : 2
b % a : 0
a 不等于 b

乘号(*)前边必须加反斜杠(\)才能实现乘法运算;

--------------------------------------------------------
关系运算符

关系运算符只支持数字,不支持字符串,除非字符串的值是数字。

EQ 就是 EQUAL等于
NQ 就是 NOT EQUAL不等于
GT 就是 GREATER THAN大于
LT 就是 LESS THAN小于
GE 就是 GREATER THAN OR EQUAL 大于等于
LE 就是 LESS THAN OR EQUAL 小于等


#!/bin/bash

a=10
b=20

if [ $a -eq $b ]
then
echo "$a -eq $b : a 等于 b"
else
echo "$a -eq $b: a 不等于 b"
fi
if [ $a -ne $b ]
then
echo "$a -ne $b: a 不等于 b"
else
echo "$a -ne $b : a 等于 b"
fi
if [ $a -gt $b ]
then
echo "$a -gt $b: a 大于 b"
else
echo "$a -gt $b: a 不大于 b"
fi
if [ $a -lt $b ]
then
echo "$a -lt $b: a 小于 b"
else
echo "$a -lt $b: a 不小于 b"
fi
if [ $a -ge $b ]
then
echo "$a -ge $b: a 大于或等于 b"
else
echo "$a -ge $b: a 小于 b"
fi
if [ $a -le $b ]
then
echo "$a -le $b: a 小于或等于 b"
else
echo "$a -le $b: a 大于 b"
fi
-----------------------------------------------------------------
布尔运算符

#!/bin/bash

a=10
b=20

if [ $a != $b ]
then
echo "$a != $b : a 不等于 b"
else
echo "$a != $b: a 等于 b"
fi
if [ $a -lt 100 -a $b -gt 15 ]
then
echo "$a 小于 100 且 $b 大于 15 : 返回 true"
else
echo "$a 小于 100 且 $b 大于 15 : 返回 false"
fi
if [ $a -lt 100 -o $b -gt 100 ]
then
echo "$a 小于 100 或 $b 大于 100 : 返回 true"
else
echo "$a 小于 100 或 $b 大于 100 : 返回 false"
fi
if [ $a -lt 5 -o $b -gt 100 ]
then
echo "$a 小于 5 或 $b 大于 100 : 返回 true"
else
echo "$a 小于 5 或 $b 大于 100 : 返回 false"
fi

-------------------------------------------------------------
逻辑运算符

什么时候使用双中括号:
if语句中
>
<
&&
||
用 [[  ]]  转义
其他地方用 [ ]


#!/bin/bash

a=10
b=20

if [[ $a -lt 100 && $b -gt 100 ]]
then
echo "返回 true"
else
echo "返回 false"
fi

if [[ $a -lt 100 || $b -gt 100 ]]
then
echo "返回 true"
else
echo "返回 false"
fi

----------------------------------------------------------------
字符串运算符
下表列出了常用的字符串运算符,假定变量 a 为 "abc",变量 b 为 "efg":
#! /bin/bash
a=access.log.1
b=access.log.2
if [ $a = $b  ];then
echo 相等
else echo 不相等
fi

c=access.log.*
if [ $a = $c  ];then
echo aaaaaa
else echo bbbbbb
fi


if [[ $a == access.log.*  ]];then
echo 777777
else echo 8888
Fi

#!/bin/bash
a="abc"
b="efg"
if [ $a = $b ]
then
echo "$a = $b : a 等于 b"
else
echo "$a = $b: a 不等于 b"
fi
if [ $a != $b ]
then
echo "$a != $b : a 不等于 b"
else
echo "$a != $b: a 等于 b"
fi
if [ -z $a ]
then
echo "-z $a : 字符串长度为 0"
else
echo "-z $a : 字符串长度不为 0"
fi
if [ -n $a ]
then
echo "-n $a : 字符串长度不为 0"
else
echo "-n $a : 字符串长度为 0"
fi
if [ $a ]
then
echo "$a : 字符串不为空"
else
echo "$a : 字符串为空"
fi

--------------------------------------
文件测试运算符
文件测试运算符用于检测 Unix 文件的各种属性。
属性检测描述如下:
#!/bin/bash

file="/var/www/krist/test.sh"
if [ -r $file ]
then
echo "文件可读"
else
echo "文件不可读"
fi


if [ -w $file ]
then
echo "文件可写"
else
echo "文件不可写"
fi


if [ -x $file ]
then
echo "文件可执行"
else
echo "文件不可执行"
fi


if [ -f $file ]
then
echo "文件为普通文件"
else
echo "文件为特殊文件"
fi


if [ -d $file ]
then
echo "文件是个目录"
else
echo "文件不是个目录"
fi


if [ -s $file ]
then
echo "文件不为空"
else
echo "文件为空"
fi


if [ -e $file ]
then
echo "文件存在"
else
echo "文件不存在"
fi

1.5、流程控制

-----------------------------------------------------------
if else
if:
if condition
then
command1
command2
...
commandN
fi

写成一行:if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then echo "true"; fi
if else:
if condition
then
command1
command2
...
commandN
else
command
fi

if else-if else:
if condition1
then
command1
elif condition2
then
command2
else
commandN
fi

例子:
a=10
b=20
if [ $a == $b ]
then
echo "a 等于 b"
elif [ $a -gt $b ]
then
echo "a 大于 b"
elif [ $a -lt $b ]
then
echo "a 小于 b"
else
echo "没有符合的条件"
fi


---------------------------------------------------

case语句

echo '输入 1 到 4 之间的数字:'
echo '你输入的数字为:'
read aNum
case $aNum in
1)  echo '你选择了 1'
;;
2)  echo '你选择了 2'
;;
3)  echo '你选择了 3'
;;
4)  echo '你选择了 4'
;;
*)  echo '你没有输入 1 到 4 之间的数字'
;;
esac
1.5.1. if else
if condition1
then     command1
elif condition2
then      command2
else commandN
fi
1.5.2、for
方式一
for N in 1 2 3
do
echo $N
done


for N in 1 2 3; do echo $N; done

for N in {1..3}; do echo $N; done

方式二
for ((i = 0; i <= 5; i++))
do
echo "welcome $i times"
done

for ((i = 0; i <= 5; i++)); do echo "welcome $i times"; done
1.5.3、while
方式一
while expression
do
command

done

方式二
i=1
while ((i<=3))
do
echo $i
let i++
done
let 命令是 BASH 中用于计算的工具,用于执行一个或多个表达式,变量计算中不需要加上 $ 来表示变量。自加操作:let no++  自减操作:let no—

方式三:无限循环
while true
do
command
done
1.5.4、case
case 值 in
模式 1)
command1     command2
...
commandN
;;
模式 2)
command1     command2
...
commandN
;;
esac
1.6、函数使用

#!/bin/bash
demoFun(){
echo "这是我的第一个 shell 函数!"
}
echo "-----函数开始执行-----"
demoFun
echo "-----函数执行完毕-----"
#!/bin/bash
funWithReturn(){
echo "这个函数会对输入的两个数字进行相加运算..."
echo "输入第一个数字: "
read aNum
echo "输入第二个数字: "
read anotherNum
echo "两个数字分别为 $aNum 和 $anotherNum !"
return $(($aNum+$anotherNum))
}
funWithReturn
echo "输入的两个数字之和为 $? !"
函数返回值在调用该函数后通过 $? 来获得。
#!/bin/bash
funWithParam(){
echo "第一个参数为 $1 !"
echo "第二个参数为 $2 !"
echo "第十个参数为 $10 !"
echo "第十个参数为 ${10} !"
echo "第十一个参数为 ${11} !"
echo "参数总数有 $# 个!"
echo "作为一个字符串输出所有参数 $* !"
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73
所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至
shell解释器首次发现它时,才可以使用。调用函数仅使用其函数名即可。
[ function ] funname [()] {
action;
[return int;]
}
可以带function fun()定义,也可以直接fun() 定义,不带任何参数。
参数返回,可以显示加return ,如果不加,将以最后一条命令运行结
果,作为返回值。 return后跟数值n(0-255)。
1.6.1. 函数参数
在Shell中,调用函数时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数... 注意,当n>=10时,需要使用${n}来获取参数。
funWithParam(){
echo "第一个参数为 $1 !"
echo "第二个参数为 $2 !"
echo "第十个参数为 $10 !"
echo "第十个参数为 ${10} !"
echo "第十一个参数为 ${11} !"
echo "参数总数有 $# 个!"
echo "作为一个字符串输出所有参数 $* !"
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73

1.7. shell案例
1.7.1 收集日志
CreateLog.sh : 模拟在不断的收集用户的信息,放到access.log文件中。
这个脚本放在这个路径中:/export/server/test
#! /bin/bash
while true
do
echo 我是用户的个人信息 >> /export/server/test/logs/access.log
sleep 0.5
done

Mv.sh : 移动access.log文件并且改名。这个脚本放在这个路径中:/export/server/test
#! /bin/bash
nums=1
countPath=/export/server/test/logs/count.txt
logPath=/export/server/test/logs/access.log
if [ -e $logPath ]
then
if [ -e $countPath ]
then
nums=`cat $countPath`
((nums++))
echo $nums > $countPath
else echo $nums > $countPath
fi
mv $logPath /export/server/test/toupload/access.log.$nums
fi

Crontab定时调度:* * * * * /export/server/test/mv.sh
1.7.2 在指定目录下创建对个文件
#!/bin/bash
#
for filename in 1 2 3 4 5 6 7 8 9 10
do
touch /tmp/f$filename
done
4、网络编程
2.1、概述
网络编程是指用来实现网络互联的不同计算机上运行的程序间可以进行数据交换。对我们来说即如何用编程语言java实现计算机网络中不同计算机之间的通信。
2.2、网络通信三要素
IP地址 : ip地址有2个版本,ipV4和ipV6,以ipV4为例,一个ip包含32位,简单用4段来表示,例如 192.168.5.201。简单来说,一个ip地址,代表一台电脑
端口号: 用于标识进程的逻辑地址,不同进程的标识。端口号的范围:0-65535,其中0-1024系统使用或保留端口。
网络协议:简单说,就是通讯的规则。
常见协议: UDP(用户数据报协议)、TCP(传输控制协议)

2.3、Socket 机制
2.3.1、Socket 概述
Socket,又称为套接字,用于描述IP地址和端口。应用程序通常通过socke向网络发出请求或者应答网络请求。Socket就是为网络编程提供的
一种机制:通信两端都有socket;
网络通信其实就是socket之间的通信;数据在两个socket之间通过IO传输。
网络编程也称作为Socket编程,套接字编程。
2.3.2、基于 UDP 协议的 Socket 通信
发送端:
@Test
public void udpSend() throws Exception {

    //创建了一个udp的发送端对象
    DatagramSocket ds = new DatagramSocket();

    //DatagramPacket(byte[] buf, int length, InetAddress address, int port)

    byte[] buf = "你爱我".getBytes();
    DatagramPacket dp = new DatagramPacket(buf, buf.length, InetAddress.getByName("localhost"), 10086);

    ds.send(dp);

    ds.close();
}

接收端:
@Test
public void udpRecei() throws Exception {

    //创建了一个udp的接收端对象
    DatagramSocket ds = new DatagramSocket(10086);

    //DatagramPacket(byte[] buf, int length)
    byte[] buf = new byte[1024];
    DatagramPacket dp = new DatagramPacket(buf, buf.length);
    ds.receive(dp);

    byte[] data = dp.getData();
    int length = dp.getLength();

    System.out.println(new String(data,0,length));


    ds.close();
}


2.3.3、基于 TCP 协议的 Socket 通信
服务端

@Test
 public void tcpServer()throws  Exception{
    serverocket ss = new serverocket(10086);
    final Socket s = ss.accept();
    new Thread(){
        @Override
        public void run() {
            dosomething(s);
        }
    }.start();
}

 private void dosomething(Socket s) {
    try{
        byte[] arr = new byte[1024];
        int len = s.getInputStream().read(arr);
        String big = new String(arr, 0, len).toUpperCase();
        s.getOutputStream().write(big.getBytes());
        
    }catch (Exception e){
        e.printStackTrace();
    }
 }

客户端
@Test
public void tcpClient()throws  Exception{
    Socket s = new Socket("127.0.0.1", 10086);
    s.getOutputStream().write("abc".getBytes());
    byte[] arr = new byte[1024];
    int len = s.getInputStream().read(arr);
    System.out.println("收到服务器返回的数据:" + new String(arr, 0, len));
    s.close();
}


2.4、IO 通信模型
2.4.1、BIO(阻塞模式)


2.4.2、NIO(非阻塞模式)


2.5、RPC
2.5.1、什么是 RPC
RPC(Remote Procedure Call Protocol)远程过程调用协议。
通俗的描述是:客户端在不知道调用细节的情况下,调用存在于远程计算机上的某个过程或函数,就像调用本地应用程序中的一样。
正式的描述是:一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。

2.5.2、RPC 结构图
实现 RPC 的程序包括 5 个部分:User、User-stub、RPCRuntime、servertub、Server。

User: 就是发起RPC 调用的client ,当 user 想发起一个远程调用时,它实际是通过本地调用 user-stub。 user-stub 负责将调用的接口、方法和参数通过约定的协议规范进行编码并通过本地的 RPCRuntime 实例传输到远端的实例。远端RPCRuntim实例收到请求后交给server-stub进行解码后发起本地端调用,调用结果再返回给 user 端。
stub:为屏蔽客户调用远程主机上的对象,必须提供某种方式来模拟本地对象,这种本地对象称为存根(stub),存根负责接收本地方法调用,并将它们委派给各自的具体实现对象。


Zookeeper
1.1、ZooKeeper概述
Zookeeper是一个分布式协调服务的开源框架。这个框架是用java来运行的,这个框架有两个最基本的功能:存储小数据和监听机制。由这两个功能,就可以延伸出很多其他的功能,例如:分布式锁、分布式配置管理、集群选主等。

1.2、ZooKeeper 特性
全局数据一致:集群中每个服务器保存一份相同的数据副本,client无论连接到哪个服务器,展示的数据都是一致的,这是最重要的特征;
可靠性:如果消息被其中一台服务器接受,那么将被所有的服务器接受。
顺序性:包括全局有序和偏序两种:全局有序是指如果在一台服务器上消息a在消息b前发布,则在所有Server上消息a都将在消息b前被发布;偏序是指如果一个消息b在消息a后被同一个发送者发布,a必将排在b前面。
数据更新原子性:一次数据更新要么成功(半数以上节点成功),要么失败,不存在中间状态;
1.3、 ZooKeeper集群角色


Leader:  Zookeeper集群工作的核心
事务请求(写操作)的唯一调度和处理者,保证集群事务处理的顺序性;集群内部各个服务器的调度者。
Follower: 处理客户端非事务(读操作)请求,转发写请求给Leader;参与集群Leader选举投票。
此外,针对访问量比较大的zookeeper集群,还可新增观察者角色。
Observer: 观察者角色,观察Zookeeper集群的最新状态变化并将这些状态同步过来,其对于非事务请求可以进行独立处理,对于事务请求,则会转发给Leader 服务器进行处理。不会参与任何形式的投票只提供非事务服务,通常用于在不影响集群事务、处理能力的前提下提升集群的非事务处理能力。

1.4、 ZooKeeper集群搭建
Zookeeper集群搭建指的是ZooKeeper分布式模式安装。通常由2n+1 台server组成。这是因为为了保证Leader选举(基于Paxos算法的实现)能过得到多数的支持,所以ZooKeeper集群的数量一般为奇数。
Zookeeper运行需要java环境,所以需要提前安装jdk。
安装jdk的步骤
1、检查当前系统中,存不存在已经安装好的java,如果有那就卸载,没有就不用

2、安装jdk
在node1、node2、node3机器上,创建目录:/export/server
上传jdk的压缩包到node1机器的 /export/server目录中,并且解压
cd /export/server
tar -zxvf jdk-8u65-linux-x64.tar.gz
配置环境变量
vi /etc/profile
export JAVA_HOME=/export/server/jdk1.8.0_65
export PATH=$JAVA_HOME/bin:$PATH
配置好后,刷新环境变量:source /etc/profile
然后把node1的/etc/profile文件和jdk的解压包,发送给node2和node3,不要忘记在node2和node3中刷新配置
scp -r jdk1.8.0_65 root@node2:/export/server/
scp -r jdk1.8.0_65 root@node3:/export/server/
scp -r /etc/profile root@node2:/etc/
scp -r /etc/profile root@node3:/etc/

对于安装leader+follower模式的集群,过程如下:
1、把压缩包,解压到这个路径:/export/server/zookeeper。解压后,修改解压路径下的conf目录中的zoo_sample.cfg文件,把这个文件改名为zoo.cfg

2、在zoo.cfg文件的最后,添加如下内容:
dataDir=/export/data/zookeeper/data(要把原有的删除)
server.1=node1:2888:3888
server.2=node2:2888:3888
server.3=node3:2888:3888

3、修改环境变量
vi /etc/profile
export ZOOKEEPER_HOME=/export/server/zookeeper
export PATH=$PATH:$ZOOKEEPER_HOME/bin
改完后,记得:source /etc/profile

4、创建文件夹并且设置机器的myid:
mkdir -p /export/data/zookeeper/data
echo 1 > /export/data/zookeeper/data/myid

5、以上流程,分别在node2和node3机器上,重复做一遍。

6、以上5个步骤就可以搭建成一个集群了。其中,第4步的echo 1,在node2机器上,应改为 echo 2,在node3机器上,改为 echo 3。也就是3台机器的myid分别是1、2、3

7、技巧,如果不想每台机器都上传压缩包,然后再解压安装。那就可以直接把node1的解压、配置好的zookeeper,发送到node2和node3中。命令如下:

发给node2:
scp -r /export/server/zookeeper root@node2:/export/server/

发给node3
scp -r /export/server/zookeeper root@node3:/export/server/

8、启动集群
首先在node1机器上:zkServer.sh start
然后在node2机器上:zkServer.sh start
最后在node3机器上:zkServer.sh start
另外还可以写个脚本,一键启动集群,代码如下:

#! /bin/bash
echo "start zkServer..."
for i in 1 2 3
do
ssh node$i "source /etc/profile; /export/server/zookeeper/bin/zkServer.sh start"
done


9、验证
可以在node1、node2、node3机器上,分别执行命令:jps ,可以发现zookeeper的进程已经出来了
还可以,在3台机器上,分别执行:zkServer.sh status,可以发现,他们都有自己的角色
常见错误 :防火墙未关闭
service iptables status    查看防火墙状态
service iptables stop      关闭防火墙
chkconfig iptables off     禁止防火墙开机自启动

10、停止集群:在3台机器上都执行  zkServer.sh stop

1.5、ZooKeeper shell
1.5.1、客户端连接
运行 zkCli.sh -server ip   进入命令行工具。输入help,输出zk shell提示:

1.5.2、shell 基本操作
创建节点
create [-s] [-e] path data acl
其中,-s或-e分别指定节点特性,顺序或临时节点,若不指定,则表示持久节点;acl用来进行权限控制。创建顺序节点:


创建临时节点:


创建永久节点:

读取节点
与读取相关的命令有ls 命令和get 命令,ls命令可以列出Zookeeper指
定节点下的所有子节点,只能查看指定节点下的第一级的所有子节点;get命令
可以获取Zookeeper指定节点的数据内容和属性信息。
ls path [watch]
get path [watch]
ls2 path [watch]


更新节点
set path data [version]
data就是要更新的新内容,version表示数据版本。

现在dataVersion已经变为1了,表示进行了更新。

删除节点
delete path [version]
若删除节点存在子节点,那么无法删除该节点,必须先删除子节点,再删除父节点。
Rmr path
可以递归删除节点。

1.6、ZooKeeper 数据模型
ZooKeeper 的数据模型,在结构上和标准文件系统的非常相似,拥有一个层次的命名空间,都是采用树形层次结构,ZooKeeper 树中的每个节点被称为— Znode。和文件系统的目录树一样,ZooKeeper树中的每个节点可以拥有子节点。
但也有不同之处:
Znode 节点,既可以存数据,又可以有子节点。类似于综合了文件和文件夹的特点。
Znode 具有原子性操作。多个节点的数据是完全一样的,不会有的节点的数据是a,另一个节点的同路径下的数据是b。
Znode 存储数据大小有限制。ZooKeeper 虽然可以关联一些数据,但并没有被设计为常规的数据库或者大数据存储,相反的是,它用来管理调度数据,比如分布式应用中的配置文件信息、状态信息、汇集位置等等。这些数据的共同特性就是它们都是很小的数据,通常以KB为大小单位。ZooKeeper的服务器和客户端都被设计为严格检查并限制每个Znode的数据大小至多1M,当时常规使用中应该远小于此值。
Znode通过路径引用,如同Unix中的文件路径。路径必须是绝对的,因此他们必须由斜杠字符来开头。除此以外,他们必须是唯一的,也就是说每一个路径只有一个表示,因此这些路径不能改变。在 ZooKeeper 中,路径由 Unicode 字符串组成,并且有一些限制。字符串"/zookeeper"用以保存管理信息,比如关键配额信息。
1.6.1、数据结构图

图中的每个节点称为一个Znode。 每个Znode由3部分组成:
①stat:此为状态信息, 描述该Znode的版本, 权限等信息
②data:与该Znode关联的数据
③children:该Znode下的子节点
1.6.2、节点类型
Znode有两种,分别为临时节点和永久节点。
节点的类型在创建时即被确定,并且不能改变。
临时节点:该节点的生命周期依赖于创建它们的会话。一旦会话结束,临时节点将被自动删除,当然可以也可以手动删除。临时节点不允许拥有子节点。
永久节点:该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作的时候,他们才能被删除。
Znode还有一个序列化的特性,如果创建的时候指定的话,该Znode的名字后面会自动追加一个不断增加的序列号。序列号对于此节点的父节点来说是唯一的,这样便会记录每个子节点创建的先后顺序。


这样便会存在四种类型的Znode节点,分别对应:
PERSISTENT:永久节点
EPHEMERAL:临时节点
PERSISTENT_SEQUENTIAL:永久节点、序列化
EPHEMERAL_SEQUENTIAL:临时节点、序列化
1.6.3、节点属性
每个znode都包含了一系列的属性,通过命令get,可以获得节点的属性。

dataVersion:数据版本号,每次对节点进行set操作,dataVersion的值都会增加 1(即使设置的是相同的数据),可有效避免了数据更新时出现的先后顺序问题。
cversion :子节点的版本号。当znode的子节点有增减变化时,cversion 的值就会增加1。
cZxid :Znode创建的事务id。
mZxid :Znode被修改的事务id,即每次对znode的修改都会更新mZxid。
ctime:节点创建时的时间戳.
mtime:节点最新一次更新发生时的时间戳.
ephemeralOwner:如果该节点为临时节点, ephemeralOwner值表示与该节点绑定的session id. 如果不是, ephemeralOwner值为0.


1.7、ZooKeeper Watcher
ZooKeeper提供了分布式数据发布/订阅功能,一个典型的发布/订阅模型系统定义了一种一对多的订阅关系,能让多个订阅者同时监听某一个主题对象,当这个主题对象自身状态变化时,会通知所有订阅者,使他们能够做出相应的处理。
ZooKeeper 中,引入了 Watcher 机制来实现这种分布式的通知功能。 ZooKeeper允许客户端向服务端注册一个Watcher监听,当服务端的一些事件触发了这个Watcher,那么就会向指定客户端发送一个事件通知来实现分布式的通知功能。
触发事件种类很多,如:节点创建,节点删除,节点改变,子节点改变等。
总的来说可以概括Watcher为以下三个过程:客户端向服务端注册Watcher、服务端事件发生触发Watcher、客户端回调Watcher得到触发事件情况
1.7.1、Watch 机制特点
一次性触发
事件发生触发监听,一个watcher event就会被发送到设置监听的客户端,这种效果是一次性的,后续再次发生同样的事件,不会再次触发。
事件封装
ZooKeeper使用WatchedEvent对象来封装服务端事件并传递。
WatchedEvent包含了每一个事件的三个基本属性:
通知状态(keeperState),事件类型(EventType)和节点路径(path) event异步发送
watcher的通知事件从服务端发送到客户端是异步的。
先注册再触发
Zookeeper中的watch机制,必须客户端先去服务端注册监听,这样事件发
送才会触发监听,通知给客户端。


1.7.2、Shell 客户端设置 watcher
设置节点数据变动监听:

通过另一个客户端更改节点数据:


此时设置监听的节点收到通知:


1.8、典型应用
1.8.1、数据发布与订阅(配置中心)
发布与订阅模型,即所谓的配置中心,顾名思义就是发布者将数据发布到ZK 节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新。
应用在启动的时候会主动来获取一次配置,同时,在节点上注册一Watcher,这样一来,以后每次配置有更新的时候,都会实时通知到订阅的客户端,从来达到获取最新配置信息的目的。

1.8.2、集群选主
在高可用的系统中,往往有一个active的节点,多个standby节点。正常服务时,由active节点对外服务,standby节点作为备用节点。当active节点down机时,standby节点就接替active节点,继续对外服务,从而实现了系统的高可用。

1.8.3、分布式锁
如下图所示,所有客户端都想要获得执行权,那就必须都去/locked节点下,创建一个子节点(/locked/kkk),如果/locked/kkk节点已经存在了,那么后面再创建是会创建不成功的,跟java的创建文件夹是一样的道理,已存在则创建失败。那么,最终只有一个客户端可以成功创建这个节点,也就是只有一个客户端获得这把锁(获得唯一的执行权)。当客户端应用执行完成后,只要删除/locked/kkk节点,就相当于释放了锁,然后其他客户端监听到节点被删除,就能触发事件,又开始抢着创建锁节点了。


1.9、选举机制
zookeeper默认的算法是FastLeaderElection,采用投票数大于半数则胜出的逻辑。

1.9.1、全新集群选举
假设目前有 5 台服务器,每台服务器均没有数据,它们的编号分别1,2,3,4,5,按编号依次启动,它们的选择举过程如下:
服务器1启动,给自己投票,然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息,服务器1的状态一直属于Looking。
服务器2启动,给自己投票,同时与之前启动的服务器1交换结果,由
于服务器2的编号大所以服务器2胜出,但此时投票数没有大于半数,所以两个服务器的状态依然是LOOKING。
服务器3启动,给自己投票,同时与之前启动的服务器1,2交换信息,由于服务器3的编号最大所以服务器3胜出,此时投票数正好大于半数,所以服务器3成为领导者,服务器1,2成为小弟。
服务器4启动,给自己投票,同时与之前启动的服务器1,2,3交换信息,尽管服务器4的编号大,但之前服务器3已经胜出,所以服务器4只能成为小弟。
服务器5启动,后面的逻辑同服务器4成为小弟。
1.9.2、非全新集群选举
对于运行正常的zookeeper集群,中途leader节点down掉,就会重新选举,关于这种情况,新的leader节点怎么被选出来,又是另一套规则了,至于怎么选举,zookeeper内部自动完成。


一、 数据分析
1. 数据分析定义

数据分析离不开数据,计量和记录一起促成了数据的诞生。伴随着数据记录的发展(尤其是技术),人类受益也越来越多,计算机出现带来的数字测量,更加大大的提高了数据化的效率。人们的重点也逐渐移向了记录下来的庞大数据,对这些数据进行研究、分析,以期获取更大的利益。
数据分析是指用适当的统计分析方法对收集来的数据进行分析,将它们加以汇总和理解并消化,以求最大化地开发数据的功能,发挥数据的作用。数据分析的目的是把隐藏在一大批看似杂乱无章的数据背后的信息集中和提炼出来,总结出所研究对象的内在规律。
商业领域中,数据分析能够给帮助企业进行判断和决策,以便采取相应的策略与行动。例如,企业高层希望通过市场分析和研究,把握当前产品的市场动向,从而指定合理的产品研发和销售计划,这就必须依赖数据分析才能完成。生活中最著名的例子便是天气专家通过对气象数据进行分析,并且制作出天气预报,根据预报,我们会做出相应的策略,是带伞还是加件毛衣。
数据分析可划分为:描述性数据分析、探索性数据分析、验证性数据分析。描述性数据分析属于初级数据分析,另两个属于高级数据分析。其中探索性分析侧重于在数据之中发现新的特征,而验证性数据分析则侧重于验证已有假设的真伪证明。我们日常学习和工作中所涉及的数据分析主要是描述性数据分析。

2. 数据分析作用
在商业领域中,数据分析的目的是把隐藏在数据背后的信息集中和提炼出来,总结出所研究对象的内在规律,帮助管理者进行有效的判断和决策。数据分析在企业日常经营分析中主要有三大作用。
2.1. 现状分析
简单来说就是告诉你当前的状况。具体体现在:
第一,告诉你企业现阶段的整体运营情况,通过各个指标的完成情况来衡量企业的运营状态,以说明企业整天运营是好了还是坏了,好的程度如何,坏的程度又到哪里。
第二,告诉你企业各项业务的构成,让你了解企业各项业务的发展以及变动情况,对企业运营状况有更深入的了解。
2.2. 原因分析
简单来说就是告诉你,某一现状为什么会发生。
经过现状分析,我们对企业的运营情况有了基本了解,但不知道运营情况具体好在哪里,差在哪里,是什么原因引起的。这时就需要开展原因分析,以进一步确定业务变动的具体原因。例如 2016 年 2 月运营收入下降 5%,是什么原因导致的呢,是各项业务收入都出现下降,还是个别业务收入下降引起的,是各个地区业务收入都出现下降,还是个别地区业务收入下降引起的。这就需要我们开展原因分析,进一步确定收入下降的具体原因,对运营策略做出调整与优化。
2.3. 预测分析
简单来说就是告诉你将来会发生什么。
在了解企业运营现状后,有时还需要对企业未来发展趋势做出预测,为制订企业运营目标及策略提供有效的参考与决策依据,以保证企业的可持续健康发展。
预测分析一般通过专题分析来完成,通常在制订企业季度、年度等计划时进行,其开展的频率没有现状分析及原因分析高。
3. 数据分析基本步骤
典型的数据分析包含以下几个步骤:
 图:数据分析典型流程图
3.1. 明确分析目的和思路
明确数据分析目的以及确定分析思路,是确保数据分析过程有效进行的先决条件,它可以为数据的收集、处理及分析提供清晰的指引方向。
目的是整个分析流程的起点。目的不明确则会导致方向性的错误。即思考: 为什么要开展数据分析,通过这次数据分析要解决什么问题?
当明确目的后,就要校理分析思路,并搭建分析框架,把分析目的分解成若干个不同的分析要点,即如何具体开展数据分析,需要从哪几个角度进行分析,采用哪些分析指标。只有明确了分析目的,分析框架才能跟着确定下来,最后还要确保分析框架的体系化,使分析更具有说服力。
体系化也就是逻辑化,简单来说就是先分析什么,后分析什么,使得各个分析点之间具有逻辑联系。避免不知从哪方面入手以及分析的内容和指标被质疑是否合理、完整。所以体系化就是为了让你的分析框架具有说服力。
要想使分析框架体系化,就需要一些营销、管理等理论为指导,结合着实际的业务情况进行构建,这样才能保证分析维度的完整性,分析结果的有效性以及正确性。比如以用户行为理论为指导,搭建的互联网网站分析指标框架如下:


把跟数据分析相关的营销、管理等理论统称为数据分析方法论。比如用户行为理论、PEST分析法、5W2H分析法等等,详细请查阅附件资料。
3.2. 数据收集
数据收集是按照确定的数据分析框架,收集相关数据的过程,它为数据分析提供了素材和依据。
这里所说的数据包括第一手数据与第二手数据,第一手数据主要指可直接获取的数据,第二手数据主要指经过加工整理后得到的数据。一般数据来源主要有以下几种方式:
数据库:每个公司都有自己的业务数据库,存放从公司成立以来产生的相关业务数据。这个业务数据库就是一个庞大的数据资源,需要有效地利用起来。
公开出版物:可以用于收集数据的公开出版物包括《中国统计年鉴》《中国社会统计年鉴》《中国人口统计年鉴》《世界经济年鉴》《世界发展报告》等统计年鉴或报告。
互联网:随着互联网的发展,网络上发布的数据越来越多,特别是搜索引擎可以帮助我们快速找到所需要的数据,例如国家及地方统计局网站、行业组织网站、政府机构网站、传播媒体网站、大型综合门户网站等上面都可能有我们需要的数据。
市场调查:进行数据分析时,需要了解用户的想法与需求,但是通过以上三种方式获得此类数据会比较困难,因此可以尝试使用市场调查的方法收集用户的想法和需求数据。市场调查就是指运用科学的方法,有目的、有系统地收集、记录、整理有关市场营销的信息和资料,分析市场情况,了解市场现状及其发展趋势,为市场预测和营销决策提供客观、正确的数据资料。市场调查可以弥补其他数据收集方式的不足,但进行市场调查所需的费用较高,而且会存在一定的误差,故仅作参考之用。
3.3. 数据处理
数据处理是指对收集到的数据进行加工整理,形成适合数据分析的样式,它是数据分析前必不可少的阶段。数据处理的基本目的是从大量的、杂乱无章、难以理解的数据中,抽取并推导出对解决问题有价值、有意义的数据。
数据处理主要包括数据清洗、数据转化、数据提取、数据计算等处理方法。一般拿到手的数据都需要进行一定的处理才能用于后续的数据分析工作,即使再“干净”的原始数据也需要先进行一定的处理才能使用。
数据处理是数据分析的基础。通过数据处理,将收集到的原始数据转换为可以分析的形式,并且保证数据的一致性和有效性。


3.4. 数据分析
数据分析是指用适当的分析方法及工具,对处理过的数据进行分析,提取有价值的信息,形成有效结论的过程。由于数据分析多是通过软件来完成的,这就要求数据分析师不仅要掌握各种数据分析方法,还要熟悉数据分析软件的操作。
数据挖掘其实是一种高级的数据分析方法,就是从大量的数据中挖掘出有用的信息,它是根据用户的特定要求,从浩如烟海的数据中找出所需的信息,以满足用户的特定需求。数据挖掘技术是人们长期对数据库技术进行研究和开发的结果。一般来说,数据挖掘侧重解决四类数据分析问题:分类、聚类、关联和预测,重点在寻找模式和规律。数据分析与数据挖掘的本质是一样的,都是从数据里面发现关于业务的知识。
3.5. 数据展现

一般情况下,数据是通过表格和图形的方式来呈现的,我们常说用图表说话就是这个意思。常用的数据图表包括饼图、柱形图、条形图、折线图、散点图、雷达图等,当然可以对这些图表进一步整理加工,使之变为我们所需要的图形,例如金字塔图、矩阵图、漏斗图等。
大多数情况下,人们更愿意接受图形这种数据展现方式,因为它能更加有效、直观地传递出分析所要表达的观点。记位,一般情况不,能用图说明问题的就不用表格,能用表格说明问题的就不要用文字。
3.6. 报告撰写
数据分析报告其实是对整个数据分析过程的一个总结与呈现。通过报告,把数据分析的起因、过程、结果及建议完整地呈现出来,供决策者参考。
一份好的数据分析报告,首先需要有一个好的分析框架,并且图文并茂,层次明晰,能够让阅读者一目了然。结构清晰、主次分明可以使阅读者正确理解报告内容;图文并茂,可以令数据更加生动活泼 ,提供视觉冲击力,有助于阅读者更形象、直观地看清楚问题和结论,从而产生思考。
另外,数据分析报告需要有明确的结论,没有明确结论的分析称不上分析,同时也失去了报告的意义,因为我们最初就是为寻找或者求证一个结论才进行分析的,所以千万不要舍本求末。
最后,好的分析报告一定要有建议或解决方案。作为决策者,需要的不仅仅是找出问题,更重要的是建议或解决方案,以便他们做决策时作参考。所以,数据分析师不仅需要掌握数据分析方法,而且还要了解和熟悉业务,这样才能根据发现的业务问题,提出具有可行性的建议或解决方案。

4. 数据分析行业前景
4.1. 蓬勃发展的趋势


从20世纪90年代起,欧美国家开始大量培养数据分析师,直到现在,对数据分析师的需求仍然长盛不衰,而且还有扩展之势。
对于中国数据分析行业前景和特点,一面网络创始人何明科指出:
一是:市场巨大,许多企业(无论是互联网的新锐还是传统的企业)都在讨论这个,也有实际的需求并愿意为此付钱,但是比较零碎尚不系统化。目前对数据需求最强烈的行业依次是:金融机构(从基金到银行到保险公司到P2P公司),以广告投放及电商为代表的互联网企业等;
二是:尚没出现平台级公司的模式(这或许往往是大市场或者大机会出现之前的混沌期);
三是:企业技术外包的氛围在国内尚没完全形成,对于一些有能力的技术公司,如果数据需求强烈的话,考虑到自身能力的健全以及数据安全性,往往不会外包或者采用外部模块,而倾向于自建这块业务;
四是:未来BAT及京东、58和滴滴打车等企业,凭借其自身产生的海量数据,必然是数据领域的大玩家。但是整个行业很大而且需求旺盛,即使没有留给创业公司出现平台级巨型企业的机会,也将留出各种各样的细分市场机会让大家可以获得自己的领地。
4.2. 数据分析师的职业要求
懂业务:从事数据分析工作的前提就是需要懂业务,即熟悉行业知识、公司业务及流程,最好有自己独特见解,若脱离行业认知和公司业务背景,分析的结果只会是脱了线的风筝,没有太大的实用价值。
从另外一个角度来说,懂业务也是数据敏感的体现。不懂业务的数据分析师,看到的只是一个个数字;懂业务的数据分析师,则看到的不仅仅是数字,他明白数字代表什么意义,知道数字是大了还是小了,心中有数,这才是真正意义的数据敏感性。
懂管理:一方面是搭建数据分析框架的要求,比如数据分析第一步确定分析思路就需要用到营销、管理等理论知识来指导,如果不熟悉管理理论,那你如何指导数据分析框架的搭建,以及开展后续的数据分析呢?
懂管理另一方面的作用是针对数据分析结论提出有指导意义的分析建议,如果没有管理理论的支撑,就难以确保分析建议的有效性。
懂分析:是指掌握数据分析的基本原理与一些有效的数据分析方法,并能灵活运用到实践工作中,以便有效地开展数据分析。
懂工具:是指掌握数据分析相关的常用工具。数据分析工具就是实现数据分析方法理论的工具,面对越来越庞大的数据,依靠计算器进行分析是不现实的,必须利用强大的数据分析工具完成数据分析工作。
同样,应该根据研究的问题选择合适的工具,只要能解决问题的工具就是好工具。
懂设计:是指运用图表有效表达数据分析师的分析观点,使分析结果一目了然。图表的设计是门大学问,如图形的选择、版式的设计、颜色的搭配等,都需要掌握一定的设计原则。

二、 科技发展带来的挑战
在科技的快速发展推动下,在IT领域,企业会面临两个方面的问题。
一是如何实现网站的高可用、易伸缩、可扩展、高安全等目标。为了解决这样一系列问题,迫使网站的架构在不断发展。从单一架构迈向高可用架构,这过程中不得不提的就是分布式。
二是用户规模越来越大,由此产生的数据也在以指数倍增长,俗称数据大爆炸。海量数据处理的场景也越来越多。技术上该如何面对?
1. 分布式系统
1.1. 概述
分布式系统是一个硬件或软件组件分布在不同的网络计算机上,彼此之间仅仅通过消息传递进行通信和协调的系统。简单来说就是一群独立计算机集合共同对外提供服务,但是对于系统的用户来说,就像是一台计算机在提供服务一样。
分布式意味着可以采用更多的普通计算机(相对于昂贵的大型机)组成分布式集群对外提供服务。计算机越多,CPU、内存、存储资源等也就越多,能够处理的并发访问量也就越大。
初代的web服务网站架构往往比较简单,应用程序、数据库、文件等所有的资源都在一台服务器上。

图:互联网初始阶段的网站架构


图:现在互联网网站常用的架构

从分布式系统的概念中我们知道,各个主机之间通信和协调主要通过网络进行,所以,分布式系统中的计算机在空间上几乎没有任何限制,这些计算机可能被放在不同的机柜上,也可能被部署在不同的机房中,还可能在不同的城市中,对于大型的网站甚至可能分布在不同的国家和地区。
1.2. 特征
分布性:分布式系统中的多台计算机之间在空间位置上可以随意分布,系统中的多台计算机之间没有主、从之分,即没有控制整个系统的主机,也没有受控的从机。
透明性:系统资源被所有计算机共享。每台计算机不仅可以使用本机的资源,还可以使用分布式系统中其他计算机的资源(包括 CPU、文件、打印机等)。
同一性:系统中的若干台计算机可以互相协作来完成一个共同的任务,或者说一个程序可以分布在几台计算机上并行地运行。
通信性:系统中任意两台计算机都可以通过通信来交换信息。
1.3. 常用分布式方案
分布式应用和服务
将应用和服务进行分层和分割,然后将应用和服务模块进行分布式部署。这样做不仅可以提高并发访问能力、减少数据库连接和资源消耗,还能使不同应用复用共同的服务,使业务易于扩展。比如:分布式服务框架Dubbo。
分布式静态资源
对网站的静态资源如JS、CSS、图片等资源进行分布式部署可以减轻应用服务器的负载压力,提高访问速度。比如:CDN。
分布式数据和存储
大型网站常常需要处理海量数据,单台计算机往往无法提供足够的内存空间,可以对这些数据进行分布式存储。比如Apache Hadoop HDFS。
分布式计算
随着计算技术的发展,有些应用需要非常巨大的计算能力才能完成,如果采用集中式计算,需要耗费相当长的时间来完成。分布式计算将该应用分解成许多小的部分,分配给多台计算机进行处理。这样可以节约整体计算时间,大大提高计算效率。比如Apache Hadoop MapReduce。
1.4. 分布式、集群
分布式(distributed)是指在多台不同的服务器中部署不同的服务模块,通过远程调用协同工作,对外提供服务。
集群(cluster)是指在多台不同的服务器中部署相同应用或服务模块,构成一个集群,通过负载均衡设备对外提供服务。

2. 海量数据处理
公开数据显示,互联网搜索巨头百度 2013 年拥有数据量接近 EB 级别。阿里、腾讯都声明自己存储的数据总量都达到了百 PB 以上。此外,电信、医疗、金融、公共安全、交通、气象等各个方面保存的数据量也都达到数十或者上百 PB 级别。全球数据量以每两年翻倍的速度增长,在 2010 年已经正式进入 ZB 时代,到 2020 年全球数据总量将达到 44ZB。

数据分析的前提是有数据,数据存储的目的是支撑数据分析。究竟怎么去存储庞大的数据量,是开展数据分析的企业在当下面临的一个问题。传统的数据存储模式存储容量是有大小限制或者空间局限限制的,怎么去设计出一个可以支撑大量数据的存储方案是开展数据分析的首要前提。
当解决了海量数据的存储问题,接下来面临的海量数据的计算问题也是比较让人头疼,因为企业不仅追求可以计算,还会追求计算的速度、效率。
以目前互联网行业产生的数据量级别,要处理这些数据,就需要一个更好、更便捷的分析计算方式了。传统的显然力不从心了,而且效率也会非常低下。这正是传统数据分析领域面临的另一个挑战,如何让去分析、计算。

三、 大数据时代
1. 概述
最早提出“大数据”时代到来的是全球知名咨询公司麦肯锡,麦肯锡称:“数据,已经渗透到当今每一个行业和业务职能领域,成为重要的生产因素。人们对于海量数据的挖掘和运用,预示着新一波生产率增长和消费者盈余浪潮的到来。” 随着互联网快速发展、智能手机以及“可佩带”计算设备的出现,我们的行为、位置,甚至身体生理数据等每一点变化都成为了可被记录和分析的数据。这些新技术推动着大数据时代的来临,各行各业每天都在产生数量巨大的数据碎片,数据计量单位已从 Byte、KB、MB、GB、TB 发展到 PB、EB、ZB、YB 甚至 BB 来衡量。


大数据到底是什么,如果简单来理解大数据就是4V的特征:
Volume(大量)、Velocity(高速)、Variety(多样)、Value(价值),即数据体量巨大、数据类型繁多、价值密度低、处理速度快。
但是这样理解会显得太浅显,要想更加全面了解大数据概念可以查看附件资料《大数据时代》。
2. 大数据分析
当数据分析遇到大数据时代,于是就产生了完美的契合:大数据分析。你可以理解大数据分析是指对规模巨大的数据进行分析。大数据被称为当今最有潜质的IT词汇,接踵而来的数据挖掘、数据安全、数据分析、数据存储等等围绕大数据的商业价值的利用逐渐成为行业人士争相追捧的利润焦点。随着大数据时代的来临,大数据分析也应运而生。


大数据分析具体含义可以分为以下几个方面:
一是大数据分析可以让人们对数据产生更加优质的诠释,而具有预知意义的分析可以让分析员根据可视化分析和数据分析后的结果做出一些预测性的推断。
二是大数据的分析与存储和数据的管理是一些数据分析层面的最佳实践。通过按部就班的流程和工具对数据进行分析可以保证一个预先定义好的高质量的分析结果。 此外需要注意的是: 传统的数据分析就是在数据中寻找有价值的规律,这和现在的大数据在方向上是一致的。

四、 大数据分析系统
1. 概念、分类
数据分析系统的主要功能是从众多外部系统中,采集相关的业务数据,集中存储到系统的数据库中。系统内部对所有的原始数据通过一系列处理转换之后,存储到数据仓库的基础库中;然后,通过业务需要进行一系列的数据转换到相应的数据集市,供其他上层数据应用组件进行专题分析或者展示。
根据数据的流转流程,一般会有以下几个模块:数据收集(采集)、数据存储、数据计算、数据分析、数据展示等等。当然也会有在这基础上进行相应变化的系统模型。
按照数据分析的时效性,我们一般会把大数据分析系统分为实时、离线两种类型。实时数据分析系统在时效上有强烈的保证,数据是实时流动的,相应的一些分析情况也是实时的。而离线数据分析系统更多的是对已有的数据进行分析,时效性上的要求会相对低一点。时效性的标准都是以人可以接受来划分的。

2. 网站流量日志数据分析系统

2.1. 系统的意义
网站流量数据统计分析,可以帮助网站管理员、运营人员、推广人员等实时获取网站流量信息,并从流量来源、网站内容、网站访客特性等多方面提供网站分析的数据依据。从而帮助提高网站流量,提升网站用户体验,让访客更多的沉淀下来变成会员或客户,通过更少的投入获取最大化的收入。
技术上
可以合理修改网站结构及适度分配资源,构建后台服务器群组,比如
1、辅助改进网络的拓扑设计,提高性能
2、在有高度相关性的节点之间安排快速有效的访问路径
3、帮助企业更好地设计网站主页和安排网页内容
业务上
1、帮助企业改善市场营销决策,如把广告放在适当的 Web 页面上。
2、优化页面及业务流程设计,提高流量转化率。
3、帮助企业更好地根据客户的兴趣来安排内容。
4、帮助企业对客户群进行细分,针对不同客户制定个性化的促销策略等。
终极目标是:改善网站的运营,获取更高投资回报率(ROI)。也就是赚更多的钱。

2.2. 背景知识—Web 访问日志
访问日志指用户访问网站时的所有访问、浏览、点击行为数据。比如点击了哪一个链接,打开了哪一个页面,采用了哪个搜索项、总体会话时间等。而所有这些信息都可通过网站日志保存下来。通过分析这些数据,可以获知许多对网站运营至关重要的信息。采集的数据越全面,分析就能越精准。
日志的生成渠道分为以下两种:
一是:web 服务器软件(httpd、nginx、tomcat)自带的日志记录功能,Nginx
的 access.log 日志;
二是:自定义采集用户行为数据,通过在页面嵌入自定义的 javascript 代码来获取用户的访问行为(比如鼠标悬停的位置,点击的页面组件等),然后通过 ajax 请求到后台记录日志,这种方式所能采集的信息会更加全面。
在实际操作中,有以下几个方面的数据可以自定义的采集:
系统特征:比如所采用的操作系统、浏览器、域名和访问速度等。
访问特征:包括停留时间、点击的 URL、所点击的“页面标签<a>”及标签的属性等。
来源特征:包括来访 URL,来访 IP 等。
产品特征:包括所访问的产品编号、产品类别、产品颜色、产品价格、产品利润、产品数量和特价等级等。
以电商某东为例,其自定义采集的数据日志格式如下:

GET     /log.gif?t=item.010001&m=UA-J2011-1&pin=-&uid=1679790178&sid=1679790178|12&v=je=1$sc=24-
bit$sr=1600x900$ul=zh-cn$cs=GBK$dt=【云南白药套装】云南白药 牙膏 180g×3 (留兰香型)【行情 报价
价格评测】-京东
$hn=item.jd.com$fl=16.0r0$os=win$br=chrome$bv=39.0.2171.95$wb=1437269412$xb=1449548587$yb=145618
6252$zb=12$cb=4$usc=direct$ucp=-$umd=none$uct=-$ct=1456186505411$lt=0$tad=-
$sku=1326523$cid1=1316$cid2=1384$cid3=1405$brand=20583$pinid=-&ref=&rm=1456186505411 HTTP/1.1

五、 网站流量日志数据自定义采集
1. 原理分析
首先,用户的行为会触发浏览器对被统计页面的一个 http 请求,比如打开某网页。当网页被打开,页面中的埋点 javascript 代码会被执行。

埋点是指:在网页中预先加入小段javascript代码,这个代码片段一般会动态创建一个script标签,并将src属性指向一个单独的js文件,此时这个单独的js文件(图中绿色节点)会被浏览器请求到并执行,这个js往往就是真正的数据收集脚本。
数据收集完成后,js会请求一个后端的数据收集脚本(图中的backend),这个脚本一般是一个伪装成图片的动态脚本程序,js 会将收集到的数据通过 http 参数的方式传递给后端脚本,后端脚本解析参数并按固定格式记录到访问日志,同时可能会在http响应中给客户端种植一些用于追踪的cookie。

2. 设计实现
根据原理分析并结合Google Analytics,想搭建一个自定义日志数据采集系统,要做以下几件事:

1、在页面中,写入埋点代码

2、埋点代码,会下载后端脚本(ma.js)

3、后端脚本,会收集浏览器的数据(也就是客户端数据),并且还会收集页面的数据,
然后将想要的数据,封装成键值对的形式,放到 一个 1x1 大小的img 标签的 src 属性值后面 ,类似如下所示:
var img = new Image(1, 1);
img.src = http://192.168.158.100/log.gif?name=xxx&age=123&.......

4、后台代码会过滤这种请求,拦截到这个情况,就可以拿到这个请求的请求参数了,然后分类保存

5、这里使用 img 标签的原因是:可以跨域访问
2.1. 确定收集信息
名称    途径    备注
访问时间    web server    Nginx $msec
IP    web server    Nginx $remote_addr
域名    javascript    document.domain
URL    javascript    document.URL
页面标题    javascript    document.title
分辨率    javascript    window.screen.height & width
颜色深度    javascript    window.screen.colorDepth
Referrer    javascript    document.referrer
浏览客户端    web server    Nginx $http_user_agent
客户端语言    javascript    navigator.language
访客标识    cookie    Nginx $http_cookie
网站标识    javascript    自定义对象
状态码    web server    Nginx $status
发送内容量    web server    Nginx $body_bytes_sent

2.2. 确定埋点代码
埋点,是网站分析的一种常用的数据采集方法。核心就是在需要进行数据采集的关键点植入统计代码,进行数据的采集。比如以谷歌分析原型来说,需要在页面中插入一段它提供的javascript片段,这个片段往往被称为埋点代码。
<script type="text/javascript">   var _maq = _maq || [];
_maq.push(['_setAccount', 'UA-XXXXX-X']);
(function() {     var ma = document.createElement('script'); ma.type =
'text/javascript'; ma.async = true;     ma.src = ('https:' == document.location.protocol ?
'https://ssl' : 'http://www') + '.google-analytics.com/ma.js';     var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ma, s);
})();
</script>
其中_maq 是全局数组,用于放置各种配置,其中每一条配置的格式为:
_maq.push(['Action', 'param1', 'param2', ...]);
_maq 的机制不是重点,重点是后面匿名函数的代码,这段代码的主要目的就是引入一个外部的 js 文件(ma.js),方式是通过 document.createElement 方法创建一个 script 并根据协议(http 或 https)将 src 指向对应的 ma.js,最后将这个元素插入页面的 dom 树上。
注意 ma.async = true 的意思是异步调用外部 js 文件,即不阻塞浏览器的解析,待外部 js 下载完成后异步执行。这个属性是 HTML5 新引入的。
扩展知识:js 自调用匿名函数格式: (function(){})();
第一对括号向脚本返回未命名的函数;后一对空括号立即执行返回的未命名函数,括号内为匿名函数的参数。
自调用匿名函数的好处是,避免重名,自调用匿名函数只会在运行时执行一次,一般用于初始化。
2.3. 前端数据收集脚本
数据收集脚本(ma.js)被请求后会被执行,一般要做如下几件事:
1、通过浏览器内置javascript对象收集信息,如页面title(通过document.title)、referrer(上一跳 url,通过 document.referrer)、用户显示器分辨率(通过 windows.screen)、cookie 信息(通过 document.cookie)等等一些信息。
2、解析_maq 数组,收集配置信息。这里面可能会包括用户自定义的事件跟踪、业务数据(如电子商务网站的商品编号等)等。
3、将上面两步收集的数据按预定义格式解析并拼接(get 请求参数)。
4、请求一个后端脚本,将信息放在 http request 参数中携带给后端脚本。
这里唯一的问题是步骤 4,javascript 请求后端脚本常用的方法是 ajax,但是 ajax是不能跨域请求的。一种通用的方法是 js 脚本创建一个 Image 对象,将 Image 对象的 src 属性指向后端脚本并携带参数,此时即实现了跨域请求后端。这也是后端脚本为什么通常伪装成 gif 文件的原因。
示例代码:
(function () {
var params = {};     //Document对象数据     if(document) {
params.domain = document.domain || '';          params.url = document.URL || '';          params.title = document.title || '';
params.referrer = document.referrer || '';
}
//Window对象数据
if(window && window.screen) {
params.sh = window.screen.height || 0;         params.sw = window.screen.width || 0;
params.cd = window.screen.colorDepth || 0;
}
//navigator对象数据     if(navigator) {
params.lang = navigator.language || '';
}        //解析_maq配置     if(_maq) {
for(var i in _maq) {
switch(_maq[i][0]) {                 case '_setAccount':
params.account = _maq[i][1];
break;                 default:                   break;
}
}
}
//拼接参数串
var args = '';      for(var i in params) {         if(args != '') {
args += '&';
}
args += i + '=' + encodeURIComponent(params[i]);
}

//通过Image对象请求后端脚本
var img = new Image(1, 1);
img.src = 'http://xxx.xxxxx.xxxxx/log.gif?' + args; })();
整个脚本放在匿名函数里,确保不会污染全局环境。其中log.gif是后端脚本。
2.4. 后端脚本
log.gif是后端脚本,是一个伪装成gif图片的脚本。后端脚本一般需要完成以下几件事情:
1、解析http请求参数得到信息。
2、从Web服务器中获取一些客户端无法获取的信息,如访客ip等。
3、将信息按格式写入log。
4、生成一副 1×1 的空 gif 图片作为响应内容并将响应头的 Content-type 设为 image/gif。
5、在响应头中通过 Set-cookie 设置一些需要的 cookie 信息。
之所以要设置 cookie 是因为如果要跟踪唯一访客,通常做法是如果在请求时发现客户端没有指定的跟踪 cookie,则根据规则生成一个全局唯一的 cookie 并种植给用户,否则 Set-cookie 中放置获取到的跟踪 cookie 以保持同一用户 cookie 不变。这种做法虽然不是完美的(例如用户清掉 cookie 或更换浏览器会被认为是两个用户),但是目前被广泛使用的手段。
我们使用 nginx 的 access_log 做日志收集,不过有个问题就是 nginx 配置本
身的逻辑表达能力有限,所以选用 OpenResty 做这个事情。
OpenResty 是一个基于 Nginx 扩展出的高性能应用开发平台,内部集成了诸多有用的模块,其中的核心是通过 ngx_lua 模块集成了 Lua,从而在 nginx 配置文件中可以通过 Lua 来表述业务。
Lua 是一种轻量小巧的脚本语言,用标准 C 语言编写并以源代码形式开放,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
首先,需要在 nginx 的配置文件中定义日志格式:
log_format tick
"$msec||$remote_addr||$status||$body_bytes_sent||$u_domain||$u_url| |$u_title||$u_referrer||$u_sh||$u_sw||$u_cd||$u_lang||$http_user_ag
ent||$u_account";
注意这里以 u_开头的是我们待会会自己定义的变量,其它的是 nginx 内置变
量。然后是核心的两个 location:
location / log.gif {
#伪装成gif文件
default_type image/gif;
#本身关闭access_log,通过subrequest记录log     access_log off;
access_by_lua "
-- 用户跟踪cookie名为__utrace
local uid = ngx.var.cookie___utrace                 if not uid then
-- 如果没有则生成一个跟踪cookie,算法为 md5(时间戳+IP+客户端信息)             uid = ngx.md5(ngx.now() ..
ngx.var.remote_addr .. ngx.var.http_user_agent)
end          ngx.header['Set-Cookie'] = {'__utrace=' .. uid ..
'; path=/'}
if ngx.var.arg_domain then
-- 通过subrequest子请求到/i-log记录日志,将参数和用户跟踪cookie带过去             ngx.location.capture('/i-log?' ..
ngx.var.args .. '&utrace=' .. uid)
end
";

#此请求资源本地不缓存
add_header Expires "Fri, 01 Jan 1980 00:00:00 GMT";     add_header Pragma "no-cache";
add_header Cache-Control "no-cache, max-age=0, mustrevalidate";
#返回一个1×1的空gif图片
empty_gif;
}

location /i-log {
#内部location,不允许外部直接访问     internal;

#设置变量,注意需要unescape,来自ngx_set_misc模块
set_unescape_uri $u_domain $arg_domain;     set_unescape_uri $u_url $arg_url;     set_unescape_uri $u_title $arg_title;     set_unescape_uri $u_referrer $arg_referrer;     set_unescape_uri $u_sh $arg_sh;     set_unescape_uri $u_sw $arg_sw;     set_unescape_uri $u_cd $arg_cd;     set_unescape_uri $u_lang $arg_lang;     set_unescape_uri $u_account $arg_account;
#打开日志
log_subrequest on;
#记录日志到ma.log 格式为tick
access_log /path/to/logs/directory/ma.log tick;

#输出空字符串
echo '';
}

要完全掌握这段脚本的每一个细节还是比较吃力的,用到了诸多第三方 ngxin 模块(全都包含在 OpenResty 中了),重点都用注释标出来,可以不用完全理解每一行的意义,只要大约知道这个配置完成了我们提到的后端逻辑就可以了。

2.5. 日志格式
日志格式主要考虑日志分隔符,一般会有以下几种选择:固定数量的字符、制表符分隔符、空格分隔符、其他一个或多个字符、特定的开始和结束文本。
2.6. 日志切分
日志收集系统访问日志时间一长文件变得很大,而且日志放在一个文件不便于管理。通常要按时间段将日志切分,例如每天或每小时切分一个日志。通过
crontab定时调用一个shell脚本实现,如下:

_prefix="/usr/local/nginx"(nginx的安装路径)
time=`date +%Y%m%d%H%M`
mv ${_prefix}/logs/access.log ${_prefix}/logs/access-${time}.log
kill -USR1 `cat ${_prefix}/logs/nginx.pid `

这个脚本将 ma.log 移动到指定文件夹并重命名为 ma-{yyyymmddhh}.log,
然后向nginx发送USR1信号令其重新打开日志文件。
USR1通常被用来告知应用程序重载配置文件, 向服务器发送一个USR1信号将导致以下步骤的发生:停止接受新的连接,等待当前连接停止,重新载入配置文件,重新打开日志文件,重启服务器,从而实现相对平滑的不关机的更改。
cat ${_prefix}/logs/nginx.pid  取 nginx 的进程号
然后再/etc/crontab 里加入一行:
59  *  *  *  * root /path/to/directory/rotatelog.sh 在每个小时的 59 分启动这个脚本进行日志轮转操作。

3. 系统环境部署
服务器中安装依赖
yum -y install gcc perl pcre-devel openssl openssl-devel

上传LuaJIT-2.0.4.tar.gz并安装LuaJIT
tar -zxvf LuaJIT-2.0.4.tar.gz -C /usr/local/src/
cd /usr/local/src/LuaJIT-2.0.4/
make && make install PREFIX=/usr/local/luajit

设置LuaJIT环境变量
vi /etc/profile
export LUAJIT_LIB=/usr/local/luajit/lib
export LUAJIT_INC=/usr/local/luajit/include/luajit-2.0
source   /etc/profile

创建modules文件夹,保存nginx依赖的模块
mkdir -p /usr/local/nginx/modules

上传nginx依赖的模块
set-misc-nginx-module-0.29.tar.gz
lua-nginx-module-0.10.0.tar.gz
ngx_devel_kit-0.2.19.tar.gz
echo-nginx-module-0.58.tar.gz

将依赖的模块直接解压到modules目录
tar -zxvf lua-nginx-module-0.10.0.tar.gz -C /usr/local/nginx/modules/
tar -zxvf set-misc-nginx-module-0.29.tar.gz -C /usr/local/nginx/modules/
tar -zxvf ngx_devel_kit-0.2.19.tar.gz -C /usr/local/nginx/modules/
tar -zxvf echo-nginx-module-0.58.tar.gz -C /usr/local/nginx/modules/

安装openresty
tar -zxvf openresty-1.9.7.3.tar.gz -C /usr/local/src/
cd /usr/local/src/openresty-1.9.7.3/
./configure --prefix=/usr/local/openresty --with-luajit && make && make install
安装nginx
tar -zxvf nginx-1.8.1.tar.gz -C /usr/local/src/

编译nginx并支持其他模块
cd /usr/local/src/nginx-1.8.1/
./configure --prefix=/usr/local/nginx \
--with-ld-opt="-Wl,-rpath,/usr/local/luajit/lib" \
--add-module=/usr/local/nginx/modules/ngx_devel_kit-0.2.19 \
--add-module=/usr/local/nginx/modules/lua-nginx-module-0.10.0 \
--add-module=/usr/local/nginx/modules/set-misc-nginx-module-0.29\   --add-module=/usr/local/nginx/modules/echo-nginx-module-0.58
make -j2
make install

备注:如果对linux相关操作不熟,请严格按照上述步骤搭建环境,切记心细,心细,再心细。
a) 创建页面 index.html, 添加埋点代码, 放入 nginx 默认目录 nginx/html 下。

b) 在默认目录 nginx/html 下添加一个数据采集脚本 ma.js。

c) 修改 nginx 的配置文件,添加自定义相关业务逻辑。

d) 启动 nginx
sbin/nginx -c conf/nginx.conf

e) 通过游览器访问 nginx

f) 观察自定义日志采集文件是否有对应的内容输出
tail  -f  logs/user_defined.log

此时还可以观察 nginx 默认的输出日志文件
tail  -f  logs/access.log

停止 nginx:
sbin/nginx -s stop 

4. 自定义采集数据实现
4.1. 方案一:基本功能实现
a)创建页面index.html,添加埋点代码,放入nginx默认目录nginx/html下。
b)在默认目录nginx/html下添加一个数据采集脚本ma.js。
c)修改nginx的配置文件,添加自定义相关业务逻辑。
d)启动nginx
sbin/nginx -c conf/nginx.conf
e)通过游览器访问nginx
f)观察自定义日志采集文件是否有对应的内容输出
tail  -f  logs/user_defined.log
此时还可以观察nginx默认的输出日志文件
tail  -f  logs/access.log
停止nginx:
sbin/nginx –s stop
4.2. 方案二:页面点击事件
详细步骤请参考附件资料。

Hadoop


一、 Apache Hadoop
1. Hadoop 介绍
Hadoop是Apache旗下的一个用java语言实现开源软件框架,是一个开发和运行处理大规模数据的软件平台。允许使用简单的编程模型在大量计算机集群上对大型数据集进行分布式处理。


狭义上说,Hadoop指Apache这款开源框架,它的核心组件有:
HDFS(分布式文件系统):解决海量数据存储
YARN(作业调度和集群资源管理的框架):解决资源任务调度
MAPREDUCE(分布式运算编程框架):解决海量数据计算
广义上来说,Hadoop通常是指一个更广泛的概念——Hadoop生态圈。

当下的Hadoop已经成长为一个庞大的体系,随着生态系统的成长,新出现的项目越来越多,其中不乏一些非Apache主管的项目,这些项目对HADOOP是很好的补充或者更高层的抽象。比如:
HDFS:分布式文件系统
MAPREDUCE:分布式运算程序开发框架
HIVE:基于HADOOP的分布式数据仓库,提供基于SQL的查询数据操作
HBASE:基于HADOOP的分布式海量数据库
ZOOKEEPER:分布式协调服务基础组件
Mahout:基于mapreduce/spark/flink等分布式运算框架的机器学习算法库
Oozie:工作流调度框架
Sqoop:数据导入导出工具(比如用于mysql和HDFS之间)
Flume:日志数据采集框架
Impala:基于Hadoop的实时分析

2. Hadoop 发展简史
Hadoop是Apache Lucene创始人 Doug Cutting 创建的。最早起源于Nutch,它是 Lucene 的子项目。Nutch 的设计目标是构建一个大型的全网搜索引擎,包括网页抓取、索引、查询等功能,但随着抓取网页数量的增加,遇到了严重的可扩展性问题:如何解决数十亿网页的存储和索引问题。
2003年Google发表了一篇论文为该问题提供了可行的解决方案。论文中描述的是谷歌的产品架构,该架构称为:谷歌分布式文件系统(GFS),可以解决他们在网页爬取和索引过程中产生的超大文件的存储需求。
2004年 Google发表论文向全世界介绍了谷歌版的MapReduce系统。
同时期,Nutch的开发人员完成了相应的开源实现HDFS和MAPREDUCE,并从 Nutch中剥离成为独立项目HADOOP,到2008年1月,HADOOP成为Apache顶级项目,迎来了它的快速发展期。
2006年Google发表了论文是关于BigTable的,这促使了后来的Hbase的发展。
因此,Hadoop及其生态圈的发展离不开Google的贡献。
3. Hadoop 特性优点
扩容能力(Scalable):Hadoop是在可用的计算机集群间分配数据并完成计算任务的,这些集群可用方便的扩展到数以千计的节点中。
成本低(Economical):Hadoop通过普通廉价的机器组成服务器集群来分发以及处理数据,以至于成本很低。
高效率(Efficient):通过并发数据,Hadoop可以在节点之间动态并行的移动数据,使得速度非常快。
可靠性(Rellable):能自动维护数据的多份复制,并且在任务失败后能自动地重新部署(redeploy)计算任务。所以Hadoop的按位存储和处理数据的能力值得人们信赖。
4. Hadoop 国内外应用
不管是国内还是国外,Hadoop 最受青睐的行业是互联网领域,可以说互联网公司是hadoop的主要使用力量。
国外来说,Yahoo、Facebook、IBM等公司都大量使用hadoop集群来支撑业务。比如:
Yahoo的Hadoop应用在支持广告系统、用户行为分析、支持Web搜索等。
Facebook主要使用Hadoop存储内部日志与多维数据,并以此作为报告、分析和机器学习的数据源。
国内来说,BAT 领头的互联网公司是当仁不让的 Hadoop 使用者、维护者。比如Ali云梯(14年国内最大Hadoop集群)、百度的日志分析平台、推荐引擎系统等。


国内其他非互联网领域也有不少hadoop的应用,比如:
金融行业: 个人征信分析
证券行业: 投资模型分析
交通行业: 车辆、路况监控分析
电信行业: 用户上网行为分析
总之:hadoop并不会跟某种具体的行业或者某个具体的业务挂钩,它只是
一种用来做海量数据分析处理的工具。
二、 Hadoop 集群搭建
1. 发行版本
Hadoop发行版本分为开源社区版和商业版,社区版是指由Apache软件基金会维护的版本,是官方维护的版本体系。商业版Hadoop是指由第三方商业公司在社区版Hadoop基础上进行了一些修改、整合以及各个服务组件兼容性测试而发行的版本,比较著名的有cloudera的CDH、mapR等。
我们学习的是社区版:Apache Hadoop。后续如未说明都是指Apache版本。
Hadoop 的版本很特殊,是由多条分支并行的发展着。大的来看分为 3 个大的系列版本:1.x、2.x、3.x。
Hadoop1.0由一个分布式文件系统HDFS和一个离线计算框架MapReduce组成。
Hadoop 2.0则包含一个支持NameNode横向扩展的HDFS,一个资源管理系统 YARN 和一个运行在 YARN 上的离线计算框架 MapReduce。相比Hadoop1.0,Hadoop 2.0功能更加强大,且具有更好的扩展性、性能,并支持多种计算框架。
Hadoop 3.0 相比之前的 Hadoop 2.0 有一系列的功能增强。但目前还是个 alpha版本,有很多bug,且不能保证API的稳定和质量。
我们课程中使用的是当前2系列最稳定版本:Apache Hadoop 2.7.4。
2. 集群简介
HADOOP集群具体来说包含两个集群:HDFS集群和YARN集群,两者逻辑上分离,但物理上常在一起。
HDFS集群负责海量数据的存储,集群中的角色主要有:
NameNode、DataNode、SecondaryNameNode
YARN集群负责海量数据运算时的资源调度,集群中的角色主要有:
ResourceManager、NodeManager
那mapreduce是什么呢?它其实是一个分布式运算编程框架,是应用程序开发包,由用户按照编程规范进行程序开发,后打包运行在HDFS集群上,并且受到YARN集群的资源调度管理。
Hadoop部署方式分三种,Standalone mode(独立模式)、Pseudo-Distributed
mode(伪分布式模式)、Cluster mode(集群模式),其中前两种都是在单机部署。独立模式又称为单机模式,仅1个机器运行1个java进程,主要用于调试。
伪分布模式也是在1个机器上运行HDFS的NameNode和DataNode、YARN的 ResourceManger和NodeManager,但分别启动单独的java进程,主要用于调试。
集群模式主要用于生产环境部署。会使用N台主机组成一个Hadoop集群。这种部署模式下,主节点和从节点会分开部署在不同的机器上。我们以3节点为例进行搭建,角色分配如下:
node-01      NameNode   DataNode    ResourceManager
node-02     DataNode NodeManager  SecondaryNameNode
node-03     DataNode  NodeManager
3. 服务器准备
本案例使用VMware Workstation Pro虚拟机创建虚拟服务器来搭建HADOOP
集群,所用软件及版本如下:
VMware Workstation Pro 12.0
Centos  6.7  64bit
4. 网络环境准备
采用NAT方式联网。
如果创建的是桌面版的Centos系统,可以在安装完毕后通过图形页面进行
编辑。如果是mini版本的,可通过编辑ifcfg-eth*配置文件进行配置。
注意BOOTPROTO、GATEWAY、NETMASK。

5. 服务器系统设置
同步时间 #手动同步集群各机器时间
date -s "2017-03-03 03:03:03"
yum install ntpdate
#网络同步时间
ntpdate cn.pool.ntp.org
#同步node1 时间
1、yum -y install ntp
2、Vi /etc/ntp.conf
3、在master节点更改ntp.conf文件,设置server为其自身,再新增restrict表示可接受网段 

4、重启ntp服务

5、在node2 节点同步node1 的时间

成功!!!!!!!
设置主机名
vi /etc/sysconfig/network
NETWORKING=yes
HOSTNAME=node1

配置IP、主机名映射
vi /etc/hosts
192.168.33.101     node1
192.168.33.102     node2
192.168.33.103     node3

配置ssh免密登陆 #生成ssh免登陆密钥
ssh-keygen -t rsa (四个回车)
执行完这个命令后,会生成id_rsa(私钥)、id_rsa.pub(公钥)
将公钥拷贝到要免密登陆的目标机器上
ssh-copy-id node-02

配置防火墙 #查看防火墙状态
service iptables status
#关闭防火墙
service iptables stop
#查看防火墙开机启动状态
chkconfig iptables --list
#关闭防火墙开机启动
chkconfig iptables off
6. JDK 环境安装
#上传jdk安装包
jdk-8u65-linux-x64.tar.gz
#解压安装包
tar zxvf jdk-8u65-linux-x64.tar.gz -C /root/apps
#配置环境变量 /etc/profile
export JAVA_HOME=/root/apps/jdk1.8.0_65 export PATH=$PATH:$JAVA_HOME/bin export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
#刷新配置
source /etc/profile
7. Hadoop 安装包目录结构
解压hadoop-2.7.4-with-centos-6.7.tar.gz,目录结构如下:
bin:Hadoop 最基本的管理脚本和使用脚本的目录,这些脚本是 sbin 目录下管理脚本的基础实现,用户可以直接使用这些脚本管理和使用Hadoop。
etc:Hadoop配置文件所在的目录,包括core-site,xml、hdfs-site.xml、 mapred-site.xml 等从 Hadoop1.0 继承而来的配置文件和 yarn-site.xml 等 Hadoop2.0新增的配置文件。
include:对外提供的编程库头文件(具体动态库和静态库在lib目录中),这些头文件均是用C++定义的,通常用于C++程序访问HDFS或者编写MapReduce 程序。
lib:该目录包含了Hadoop对外提供的编程动态库和静态库,与include目
录中的头文件结合使用。
libexec:各个服务对用的shell配置文件所在的目录,可用于配置日志输出、启动参数(比如JVM参数)等基本信息。
sbin:Hadoop管理脚本所在的目录,主要包含HDFS和YARN中各类服务的启动/关闭脚本。
share:Hadoop各个模块编译后的jar包所在的目录。
8. Hadoop 配置文件修改
Hadoop安装主要就是配置文件的修改,一般在主节点进行修改,完毕后scp 下发给其他各个从节点机器。
8.1. hadoop-env.sh
文件中设置的是Hadoop运行时需要的环境变量。JAVA_HOME是必须设置的,即使我们当前的系统中设置了 JAVA_HOME,它也是不认识的,因为 Hadoop 即使
是在本机上执行,它也是把当前的执行环境当成远程服务器。
cd  /export/server/hadoop-2.7.4/etc/hadoop
vi hadoop-env.sh
export JAVA_HOME=/root/apps/jdk1.8.0_65
8.2. core-site.xml
hadoop的核心配置文件,有默认的配置项core-default.xml。
core-default.xml 与 core-site.xml 的功能是一样的,如果在 core-site.xml里没有配置的属性,则会自动会获取core-default.xml里的相同属性
的值。
在<configuration>标签中添加
<!-- 用于设置 Hadoop 的文件系统,由 URI 指定 -->
<property>
<name>fs.defaultFS</name>
<value>hdfs://node1:9000</value>
</property>
<!-- 配置 Hadoop 的临时目录,默认/tmp/hadoop-${user.name} -->
<property>
<name>hadoop.tmp.dir</name>
<value>/home/hadoop/hadoop-2.4.1/tmp</value>
</property>
8.3. hdfs-site.xml
HDFS的核心配置文件,有默认的配置项hdfs-default.xml。
hdfs-default.xml 与 hdfs-site.xml 的功能是一样的,如果在 hdfs-site.xml里没有配置的属性,则会自动会获取hdfs-default.xml里的相同属性
的值。
在<configuration>标签中添加
<!-- 指定 HDFS 副本的数量 -->
<property>
<name>dfs.replication</name>
<value>2</value>
</property>
<!-- secondary namenode 所在主机的 ip 和端口->
<property>
<name>dfs.namenode.secondary.http-address</name>
<value>192.168.1.152:50090</value>
</property>
8.4. mapred-site.xml
MapReduce的核心配置文件,有默认的配置项mapred-default.xml。 mapred-default.xml与mapred-site.xml的功能是一样的,如果在mapred-site.xml里没有配置的属性,则会自动会获取mapred-default.xml里的相同属性的值。

mv mapred-site.xml.template mapred-site.xml(先改文件名)
在<configuration>标签中添加
<!-- 指定 mr 运行时框架,这里指定在 yarn 上,默认是 local -->
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
8.5. yarn-site.xml
YARN的核心配置文件,有默认的配置项yarn-default.xml。
yarn-default.xml 与 yarn-site.xml 的功能是一样的,如果在 yarnsite.xml里没有配置的属性,则会自动会获取yarn-default.xml里的相同属性
的值。
<!-- 指定 YARN 的老大(ResourceManager)的地址 -->
<property>
<name>yarn.resourcemanager.hostname</name>
<value>node1</value>
</property>
<!-- NodeManager上运行的附属服务。需配置成mapreduce_shuffle,才可运行MapReduce 程序默认值:"" -->
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
8.6. slaves
slaves文件里面记录的是集群主机名。一般有以下两种作用:
一是:配合一键启动脚本如start-dfs.sh、stop-yarn.sh用来进行集群启
动。这时候slaves文件里面的主机标记的就是从节点角色所在的机器。
二是:可以配合hdfs-site.xml里面dfs.hosts属性形成一种白名单机制。
dfs.hosts指定一个文件,其中包含允许连接到NameNode的主机列表。必
须指定文件的完整路径名。如果值为空,则允许所有主机。例如:
<property>
<name> dfs.hosts </name>
<value>/root/apps/hadoop/etc/hadoop/slaves </value>
</property>
那么所有在slaves中的主机才可以加入的集群中。
9. Hadoop 环境变量
编辑环境变量的配置文件:

vi /etc/profile
export JAVA_HOME=/export/server/jdk1.8.0_65
export HADOOP_HOME=/export/server/hadoop-2.7.4
export PATH=$PATH:$JAVA_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin

保存配置文件,刷新配置文件:
source /etc/profile       

将配置好的hadoop以及其环境变量发送到node2和node3中

scp -r /etc/profile root@node2:/etc/
scp -r /etc/profile root@node3:/etc/

scp -r /export/server/hadoop-2.7.4/ root@node2:/export/server/
三、 Hadoop 集群启动、初体验
1. 启动方式
要启动Hadoop集群,需要启动HDFS和YARN两个集群。注意:首次启动HDFS时,必须对其进行格式化操作。本质上是一些清理和准备工作,因为此时的HDFS在物理上还是不存在的。
hdfs namenode–format或者hadoop namenode –format
1.1. 单节点逐个启动
在主节点上使用以下命令启动HDFS NameNode:
hadoop-daemon.sh start namenode
在每个从节点上使用以下命令启动HDFS DataNode:
hadoop-daemon.sh start datanode
在主节点上使用以下命令启动YARN ResourceManager:
yarn-daemon.sh  start resourcemanager
在每个从节点上使用以下命令启动YARN nodemanager:
yarn-daemon.sh start nodemanager
以上脚本位于$HADOOP_PREFIX/sbin/目录下。如果想要停止某个节点上某个角色,只需要把命令中的start改为stop即可。
1.2. 脚本一键启动
如果配置了etc/hadoop/slaves和ssh免密登录,则可以使用程序脚本启动
所有Hadoop两个集群的相关进程,在主节点所设定的机器上执行。
hdfs:$HADOOP_PREFIX/sbin/start-dfs.sh
yarn: $HADOOP_PREFIX/sbin/start-yarn.sh
停止集群:stop-dfs.sh、stop-yarn.sh
过时的脚本:一键启动hdfs和yarn:start-all.sh
一键停止hdfs和yarn:stop-all.sh
2. 集群 web-ui
一旦Hadoop集群启动并运行,可以通过web-ui进行集群查看,如下所述:
NameNode http://nn_host:port/ 默认50070.
ResourceManager http://rm_host:port/ 默认 8088.


3. Hadoop 初体验
3.1. HDFS 使用
从Linux本地上传一个文本文件到hdfs的/test/input目录下
hadoop fs -mkdir -p /wordcount/input
hadoop fs -put /root/somewords.txt  /test/input
3.2. 运行 mapreduce 程序
在Hadoop安装包的hadoop-2.7.4/share/hadoop/mapreduce下有官方自带
的mapreduce程序。我们可以使用如下的命令进行运行测试。
示例程序jar:
hadoop-mapreduce-examples-2.7.4.jar
3.2.1 计算圆周率:
hadoop jar hadoop-mapreduce-examples-2.7.4.jar pi 2 5
关于圆周率的估算,感兴趣的可以查询资料Monte Carlo方法来计算Pi值。

3.2.2. wordcount程序
建议使用下面的wordcount程序,先去到这个目录中
/export/server/hadoop-2.7.4/share/hadoop/mapreduce:
1、在本地创建一个wordcount.log文件,文件中输入一些单词,内容如下:

Abc abc hadoop
hadoop abc spark streaming
Lisi lilei

2、在hdfs集群中,创建文件夹:
hadoop fs -mkdir -p /wordcount/input

3、把wordcount.log上传到hdfs集群中的相应文件夹中
hadoop fs -put wordcount.log /wordcount/input/

4、测试wordcount程序,记得检查yarn集群是否启动

hadoop jar hadoop-mapreduce-examples-2.7.4.jar wordcount /wordcount/input /wordcount/output


5、等待结果,也可以页面查看
http://node1:8088
http://node1:50070

1、HDFS
1.1、HDFS 介绍
HDFS是Hadoop Distribute File System 的简称,意为:Hadoop分布式文件系统。是Hadoop核心组件之一,作为最底层的分布式存储服务而存在。

1.2、HDFS 设计目标
硬件故障是常态, HDFS将有成百上千的服务器组成,每一个组成部分都有可能出现故障。因此故障的检测和自动快速恢复是HDFS的核心架构目标。

HDFS 上的应用与一般的应用不同。HDFS 被设计成适合批量处理,而不是用户交互式的。相较于数据访问的反应时间,更注重数据访问的高吞吐量。

典型的HDFS文件大小是GB到TB的级别。所以,HDFS被设计成支持大文件。它应该提供很高的聚合数据带宽,一个集群中支持数百个节点,一个集群中还应该支持千万级别的文件。

大部分HDFS应用对文件要求的是write-one-read-many访问模型。一个文件一旦创建、写入、关闭之后就不需要修改了。这一假设简化了数据一致性问题,使高吞吐量的数据访问成为可能。

移动计算的代价比移动数据的代价低。一个应用请求的计算,离它操作的数据越近就越高效,这在数据达到海量级别的时候更是如此。将计算移动到数据附近,比之将数据移动到应用所在显然更好。

1.3、HDFS 重要特性
首先,它是一个文件系统,用于存储文件,通过唯一的路径来定位文件;
其次,它是分布式的,由很多服务器联合起来实现其功能,集群中的服务器有各自的角色。

1.3.1、master/slave 架构
HDFS采用master/slave架构。一般一个HDFS集群是有一个Namenode和一定数目的Datanode组成。Namenode是HDFS集群主节点,Datanode是HDFS集群从节点,两种角色各司其职,共同协调完成分布式的文件存储服务。

1.3.2、分块存储
HDFS中的文件在物理上是分块存储(block)的,块的大小可以通过配置参数来规定,默认大小在hadoop2.x版本中是128M。

1.3.3、名字空间(NameSpace)
HDFS 支持传统的层次型文件组织结构。用户或者应用程序可以创建目录,然后将文件保存在这些目录里。文件系统名字空间的层次结构和大多数现有的文件系统类似:用户可以创建、删除、移动或重命名文件。

Namenode 负责维护文件系统的名字空间,任何对文件系统名字空间或属性的修改都将被Namenode记录下来。

HDFS会给客户端提供一个统一的目录树,客户端通过路径来访问文件,形如:hdfs://namenode:port/dir-a/dir-b/dir-c/file.data。

1.3.4、Namenode 元数据管理
我们把目录结构及文件分块位置信息叫做元数据。Namenode 负责维护整个 hdfs文件系统的目录树结构,以及每一个文件所对应的block块信息(block的 id,及所在的datanode服务器)。

1.3.5、Datanode 数据存储
文件的各个block的具体存储管理由datanode节点承担。每一个block都可以在多个datanode上。Datanode需要定时向Namenode汇报自己持有的block 信息。


1.3.6、副本机制
为了容错,文件的所有block都会有副本。每个文件的block大小和副本系数都是可配置的。应用程序可以指定某个文件的副本数目。副本系数可以在文件创建的时候指定,也可以在之后改变。

1.3.7、一次写入,多次读出
HDFS是设计成适应一次写入,多次读出的场景,且不支持文件的修改。 正因为如此,HDFS 适合用来做大数据分析的底层存储服务,并不适合用来做文件修改等应用,因为修改不方便,延迟大,网络开销大,成本太高。


2、读写数据
2.1、NameNode 概述
a、NameNode是HDFS的核心, NameNode也称为Master。

b、NameNode仅存储HDFS的元数据:文件系统中所有文件的目录树,并跟踪整个集群中的文件。

c、NameNode不存储实际数据或数据集。数据本身实际存储在DataNodes中。

e、NameNode 知道 HDFS 中任何给定文件的块列表及其位置。使用此信息NameNode知道如何从块中构建文件。

f、NameNode并不持久化存储每个文件中各个块所在的DataNode的位置信息,这些信息会在系统启动时从DataNode节点重建。

g、NameNode对于HDFS至关重要,当NameNode关闭时,HDFS / Hadoop集群无法访问。

h、NameNode是Hadoop集群中的单点故障。
解决: Hadoop提供的另一种方案就是NFS,一种即时备份namenode元数据的方案,设置多个data目录(包括NFS目录),让namenode在持 久化元数据的时候同时写入多个目录,这种方案较第一种方案的优势是能避免数据的丢失(这里我们暂时不讨论NFS本身会丢失数据的可能性,毕竟这种几率很小 很小)。既然可以解决数据丢失的问题,说明这套方案在原理上是可行的

i、NameNode所在机器通常会配置有大量内存(RAM)。

2.2、DataNode 概述
a、 DataNode也称为Slave,负责将实际数据存储在HDFS中。

b、 NameNode和DataNode会保持不断通信。

c、 DataNode启动时,会向NameNode汇报自己负责持有的块列表。

d、当某个DataNode关闭时,它不会影响数据或集群的可用性。NameNode将安排由其他DataNode管理的块进行副本复制。

e、DataNode 所在机器通常配置有大量的硬盘空间。因为实际数据存储在DataNode中。

f、DataNode会定期(dfs.heartbeat.interval配置项配置,默认是3秒)向 NameNode 发送心跳,如果 NameNode 长时间没有接受到 DataNode 发送的心跳, NameNode就会认为该DataNode失效。block 汇报时间间隔取参数
dfs.blockreport.intervalMsec,参数未配置的话默认为6小时.

出处:http://blog.csdn.net/l1028386804/article/details/51935091
在日常维护hadoop集群的过程中发现这样一种情况:
某个节点由于网络故障或者DataNode进程死亡,被NameNode判定为死亡,HDFS马上自动开始数据块的容错拷贝;当该节点重新添加到集群中时,由于该节点上的数据其实并没有损坏,所以造成了HDFS上某些block的备份数超过了设定的备份数。通过观察发现,这些多余的数据块经过很长的一段时间才会被完全删除掉,那么这个时间取决于什么呢?该时间的长短跟数据块报告的间隔时间有关。Datanode会定期将当前该结点上所有的BLOCK信息报告给Namenode,参数dfs.blockreport.intervalMsec就是控制这个报告间隔的参数。
hdfs-site.xml文件中有一个参数:
<property>
<name>dfs.blockreport.intervalMsec</name>
<value>10000</value>
<description>Determines block reporting interval in milliseconds.</description>
</property>
其中6小时为默认设置,也就是说,块报告的时间间隔为6个小时,所以经过了很长时间这些多余的块才被删除掉。通过实际测试发现,当把该参数调整的稍小一点的时候(60秒),多余的数据块确实很快就被删除了。


g、NameNode 负责管理整个文件系统元数据;DataNode 负责管理具体文件数据块存储;Secondary NameNode协助NameNode进行元数据的备份。
HDFS的内部工作机制对客户端保持透明,客户端请求访问HDFS都是通过向
NameNode申请来进行。


2.3、HDFS 写数据流程

2.4、HDFS 读数据流程


3、Shell

-3.1、Shell 常用hadoop命令介绍
-ls 使用方法:hadoop fs -ls [-R] <args>
功能:显示文件、目录信息。
示例:hadoop fs -ls /user/hadoop/file1


-mkdir 使用方法:hadoop fs -mkdir [-p] <paths>
功能:在hdfs上创建目录,-p表示会创建路径中的各级父目录。
示例:hadoop fs -mkdir –p /user/hadoop/dir1


-put 使用方法:hadoop fs -put [-f] [-p]  <localsrc1> [ localsrc2. ] <dst>
功能:将单个src或多个srcs从本地文件系统复制到目标文件系统。
-p:保留访问和修改时间,所有权和权限。
-f:覆盖目的地(如果已经存在)
示例:hadoop fs -put -f  localfile1 localfile2 /user/hadoop/hadoopdir


-get 使用方法:hadoop fs -get  [-p]  <src> <localdst>
功能:将文件复制到本地文件系统。
示例:hadoop fs -get hdfs://host:port/user/hadoop/file localfile


-appendToFile  使用方法:hadoop fs -appendToFile <localsrc> [localsrc]... <dst>
功能:追加一个文件到已经存在的文件末尾
示例:hadoop fs -appendToFile localfile  /hadoop/hadoopfile


-cat   使用方法:hadoop fs -cat  URI [URI ...]
功能:显示文件内容到stdout
示例:hadoop fs -cat  /hadoop/hadoopfile


-tail 使用方法:hadoop fs -tail [-f] URI
功能:将文件的最后一千字节内容显示到stdout。
-f选项将在文件增长时输出附加数据。
示例:hadoop  fs  -tail  /hadoop/hadoopfile


-copyFromLocal 使用方法:hadoop fs -copyFromLocal <localsrc> URI
功能:从本地文件系统中拷贝文件到hdfs路径去
示例:hadoop  fs  -copyFromLocal  /root/1.txt  /


-copyToLocal
功能:从hdfs拷贝到本地
示例:hadoop fs -copyToLocal /aaa/jdk.tar.gz


-cp
功能:从hdfs的一个路径拷贝hdfs的另一个路径
示例: hadoop  fs  -cp  /aaa/jdk.tar.gz  /bbb/jdk.tar.gz.2


-mv
功能:在hdfs目录中移动文件
示例: hadoop  fs  -mv  /aaa/jdk.tar.gz  /


-getmerge
功能:合并下载多个文件
示例:比如hdfs的目录 /aaa/下有多个文件:log.1, log.2,log.3,...
hadoop fs -getmerge /aaa/log.*  ./log.sum


-rm
功能:删除指定的文件。只删除非空目录和文件。-r 递归删除。
示例:hadoop fs -rm -r /aaa/bbb/


-df
功能:统计文件系统的可用空间信息
示例:hadoop  fs  -df  -h  /


-du
功能:显示目录中所有文件大小,当只指定一个文件时,显示此文件的大小。
示例:hadoop fs -du /user/hadoop/dir1


-setrep
功能:改变一个文件的副本系数。-R 选项用于递归改变目录下所有文件的副本
系数。
示例:hadoop fs -setrep -w 3 -R /user/hadoop/dir1

3.2、案例:shell 定时采集数据至 HDFS
上线的网站每天都会产生日志数据。假如有这样的需求:要求在凌晨24点开始操作前一天产生的日志文件,准实时上传至HDFS集群上。该如何实现?实现后能否实现周期性上传需求?如何定时?

3.2.1、技术分析
HDFS SHELL:
hadoop fs –put  //满足上传文件,不能满足定时、周期性传入。

Linux crontab:
crontab -e
0 0 * * * /shell/ uploadFile2Hdfs.sh   //每天凌晨12:00执行一次

3.2.2、实现流程
一般日志文件生成的逻辑由业务系统决定,比如每小时滚动一次,或者一定大小滚动一次,避免单个日志文件过大不方便操作。
比如滚动后的文件命名为access.log.x,其中x为数字。正在进行写的日志文件叫做 access.log。这样的话,如果日志文件后缀是 1\2\3 等数字,则该文件满足需求可以上传,就把该文件移动到准备上传的工作区间目录。工作区间有文件之后,可以使用hadoop put命令将文件上传。

3.2.3、代码实现
#!/bin/bash

#set java env
export JAVA_HOME=/export/server/jdk1.8.0_65
export PATH=${JAVA_HOME}/bin:$PATH

#set hadoop env
export HADOOP_HOME=/export/server/hadoop-2.7.4
export PATH=${HADOOP_HOME}/bin:${HADOOP_HOME}/sbin:$PATH


#日志文件存放的目录   
log_src_dir=/root/logs/log/

#待上传文件存放的目录
log_toupload_dir=/root/logs/toupload/


#日志文件上传到hdfs的根路径
date1=`date -d last-day +%Y_%m_%d`
hdfs_root_dir=/data/clickLog/$date1/

#打印环境变量信息
echo "envs: hadoop_home: $HADOOP_HOME"


#读取日志文件的目录,判断是否有需要上传的文件
echo "log_src_dir:"$log_src_dir

# access.log.1 access.log.2  access.log.3
ls $log_src_dir | while read fileName
do
        if [[ "$fileName" == access.log.* ]]; then
                date=`date +%Y_%m_%d_%H_%M_%S`
                #将文件移动到待上传目录并重命名
                #打印信息

                echo "moving $log_src_dir$fileName to $log_toupload_dir"xxxxx_click_log_$fileName"$date"

                mv $log_src_dir$fileName $log_toupload_dir"xxxxx_click_log_$fileName"$date
                #将待上传的文件path写入一个列表文件willDoing          >> /root/logs/toupload/willDoing.2018_07_24_09_59_43
                echo $log_toupload_dir"xxxxx_click_log_$fileName"$date >> $log_toupload_dir"willDoing."$date
        fi

done
#找到列表文件willDoing
ls $log_toupload_dir | grep will |grep -v "_COPY_" | grep -v "_DONE_" | while read line
do
        #打印信息
        echo "toupload is in file:"$line
        #将待上传文件列表willDoing改名为willDoing_COPY_
        mv $log_toupload_dir$line $log_toupload_dir$line"_COPY_"
        #读列表文件willDoing_COPY_的内容(一个一个的待上传文件名)  ,此处的line 就是列表中的一个待上传文件的path
        cat $log_toupload_dir$line"_COPY_" | while read line
        do
                #打印信息
                echo "puting...$line to hdfs path.....$hdfs_root_dir"
                hadoop fs -mkdir -p $hdfs_root_dir
                hadoop fs -put $line $hdfs_root_dir
        done
        mv $log_toupload_dir$line"_COPY_"  $log_toupload_dir$line"_DONE_"
done 


4、Java api

4.1、HDFS 的 JAVA API 操作
HDFS在生产应用中主要是客户端的开发,其核心步骤是从HDFS提供的api 中构造一个HDFS的访问客户端对象,然后通过该客户端对象操作(增删改查) HDFS上的文件。
4.1.1、搭建开发环境
创建Maven工程,引入pom依赖
<dependencies>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>2.7.4</version>
</dependency>
</dependencies>

配置 windows 平台 Hadoop 环境在windows上做HDFS客户端应用开发,需要设置Hadoop环境,而且要求是 windows平台编译的Hadoop,不然会报以下的错误:
Failed to locate the winutils binary in the hadoop binary path java.io.IOException: Could not locate executable null\bin\winutils.exe in the Hadoop binaries.

为此我们需要进行如下的操作:
a、在windows平台下编译Hadoop源码(可以参考资料编译,但不推荐)
b、使用已经编译好的Windows版本Hadoop:hadoop-2.7.4-with-windows.tar.gz,解压一份到windows的任意一个目录下,在windows系统中配置HADOOP_HOME指向你解压的安装包目录,在windows系统的path变量中加入HADOOP_HOME的bin目录

4.1.2、构造客户端对象
在java中操作HDFS,主要涉及以下Class:

Configuration:该类的对象封转了客户端或者服务器的配置;

FileSystem:该类的对象是一个文件系统对象,可以用该对象的一些方法来
对文件进行操作,通过FileSystem的静态方法get获得该对象。

FileSystem fs = FileSystem.get(conf);

解释:get方法从conf中的一个参数 fs.defaultFS的配置值判断具体是什么类型的文件系统。如果我们的代码中没有指定fs.defaultFS,并且工程classpath 下也没有给定相应的配置,conf中的默认值就来自于hadoop的jar包中的core-default.xml , 默 认 值 为 : file:/// , 则获取的将不是一个DistributedFileSystem的实例,而是一个本地文件系统的客户端对象。

4.1.3、示例代码

public class Demo {

@Test  //需求:创建一个目录,测试以下环境
public void demo1() throws Exception{
Configuration con = new Configuration();
FileSystem fs = FileSystem.get(new URI("hdfs://node1:9000"), con, "root");

boolean mkdirs = fs.mkdirs(new Path("/ppp/bbb/ccc"));
System.out.println("mkdirs = " + mkdirs);


fs.close();
}

@Test  //需求:把数据写入 /www.log
public void demo2() throws Exception{
Configuration con = new Configuration();
FileSystem fs = FileSystem.get(new URI("hdfs://node1:9000"), con, "root");

FSDataOutputStream os = fs.create(new Path("/www.log"));
os.write("hello , world".getBytes());
fs.close();
}


}


5、初识 MapReduce(讲不完下一天)
1. MapReduce 计算模型介绍
1.1. 理解 MapReduce 思想
MapReduce 思想在生活中处处可见。或多或少都曾接触过这种思想。 MapReduce的思想核心是“分而治之”,适用于大量复杂的任务处理场景(大规模数据处理场景)。即使是发布过论文实现分布式计算的谷歌也只是实现了这种思想,而不是自己原创。

Map负责“分”,即把复杂的任务分解为若干个“简单的任务”来并行处理。可以进行拆分的前提是这些小任务可以并行计算,彼此间几乎没有依赖关系。
Reduce负责“合”,即对map阶段的结果进行全局汇总。
这两个阶段合起来正是MapReduce思想的体现。

还有一个比较形象的语言解释MapReduce:
我们要数图书馆中的所有书。你数1号书架,我数2号书架。这就是“Map”。我们人越多,数书就更快。 现在我们到一起,把所有人的统计数加在一起。这就是“Reduce”。
1.2. Hadoop MapReduce 设计构思
MapReduce是一个分布式运算程序的编程框架,核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在Hadoop集群上。

既然是做计算的框架,那么表现形式就是有个输入(input),MapReduce操作这个输入(input),通过本身定义好的计算模型,得到一个输出(output)。对许多开发者来说,自己完完全全实现一个并行计算程序难度太大,而MapReduce就是一种简化并行计算的编程模型,降低了开发并行应用的入门门槛。

对相互间不具有计算依赖关系的大数据,实现并行最自然的办法就是采取分而治之的策略。

并行计算的第一个重要问题是如何划分计算任务或者计算数据以便对划分的子任务或数据块同时进行计算。不可分拆的计算任务或相互间有依赖关系的数据无法进行并行计算!

1.3. MapReduce 框架结构
一个完整的mapreduce程序在分布式运行时有三类实例进程:
1、MRAppMaster:负责整个程序的过程调度及状态协调
2、MapTask:负责map阶段的整个数据处理流程
3、ReduceTask:负责reduce阶段的整个数据处理流程

2. MapReduce 编程规范及示例编写
2.1. 编程规范
(1)用户编写的程序分成三个部分:Mapper,Reducer,Driver(提交运行mr程序的客户端)

(2)Mapper的输入数据是KV对的形式(KV的类型可自定义)

(3)Mapper的输出数据是KV对的形式(KV的类型可自定义)

(4)Mapper中的业务逻辑写在map()方法中

(5)map()方法(maptask进程)对每一个<K,V>调用一次

(6)Reducer的输入数据类型对应Mapper的输出数据类型,也是KV

(7)Reducer的业务逻辑写在reduce()方法中

(8)Reducetask进程对每一组相同k的<k,v>组调用一次reduce()方法

(9)用户自定义的Mapper和Reducer都要继承各自的父类

(10)整个程序需要一个Drvier来进行提交,提交的是一个描述了各种必要信息的job对象

2.2. WordCount示例编写
需求:在一堆给定的文本文件中统计输出每一个单词出现的总次数

public class Demo1_wordcount {

private static class MyMapper extends Mapper<LongWritable, Text, Text, IntWritable>{
IntWritable v = new IntWritable(1);
protected void map(LongWritable key, Text value, Context context)throws IOException, InterruptedException {
String[] arr = value.toString().split("\\s+");
for (String item : arr) {
value.set(item);
context.write(value, v);
}
}
}

private static class MyReducer extends Reducer<Text, IntWritable, Text, IntWritable>{
IntWritable v = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> value, Context context)throws IOException, InterruptedException {
int count = 0;
Iterator<IntWritable> it = value.iterator();
while(it.hasNext()){
IntWritable next = it.next();
count += next.get();
}
v.set(count);
context.write(key, v);
}
}

public static void main(String[] args) throws Exception {
Job job = Job.getInstance();
job.setJarByClass(Demo1_wordcount.class);

job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);

job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);

job.setMapperClass(MyMapper.class);
job.setReducerClass(MyReducer.class);

FileInputFormat.setInputPaths(job, new Path("/wordcount/input"));
FileOutputFormat.setOutputPath(job, new Path("/wordcount/output"));
boolean b = job.waitForCompletion(true);
System.exit(b?0:1);
}

}


3. MapReduce 程序运行模式
3.1. 本地运行模式
(1)mapreduce 程序是被提交给 LocalJobRunner 在本地以单进程的形式运行
(2)而处理的数据及输出结果可以在本地文件系统,也可以在 hdfs 上
(3)怎样实现本地运行?写一个程序,不要带集群的配置文件
本质是程序的 conf 中是否有 mapreduce.framework.name=local 以及
yarn.resourcemanager.hostname 参数
(4)本地模式非常便于进行业务逻辑的 debug,只要在 eclipse 中打断点即可

3.2. 集群运行模式
(1)将 mapreduce 程序提交给 yarn 集群,分发到很多的节点上并发执行
(2)处理的数据和输出结果应该位于 hdfs 文件系统
(3)提交集群的实现步骤:
将程序打成 JAR 包,然后在集群的任意一个节点上用 hadoop 命令启动
hadoop jar wordcount.jar cn.krist.bigdata.mrsimple.WordCountDriver args

一、初识 MapReduce
1. MapReduce 计算模型介绍 
1.1. 理解 MapReduce 思想 
MapReduce是一种思想,简单的说,就是“分而治之”。举个例子,统计每种单词出现的次数。

如果一个文件才几M的大小,那要统计这个文件里,每种单词的个数,那我们只需要在一台电脑中,运行一个程序,让这个程序帮我们统计即可。

但是如果这个文件的大小是10000M,就不适合用一台电脑来统计了,得需要用很多台电脑,并行地去统计。具体的做法是,把这个文件划分为100份,每份大小为100M。然后让100台电脑去分别统计这100份的单词个数,最后会每台电脑都有一份统计结果,我们只需要汇总这100份结果即可。

在这里,我们把每一份文件分块,称为一个分片;把每台电脑的统计工作,称为map任务;把100台电脑的结果进行汇总,称为reduce任务。

1.2. Hadoop MapReduce 设计构思 
Hadoop集群中的MapReduce框架,就实现了这种思想。
作为程序员只需要做的是:写一个map类,代表map任务;写一个reduce类,代表reduce任务;写一个任务描述类,用于描述map、reduce任务的输入输出类型,配对map和reduce等,剩下所有的事情,我们就不需要管了,都是MapReduce框架去完成的。


1.3. MapReduce 框架结构 
一个完整的mapreduce程序在分布式运行时有三类实例进程: 
1、MRAppMaster:负责整个程序的过程调度及状态协调 
2、MapTask:负责map阶段的整个数据处理流程 
3、ReduceTask:负责reduce阶段的整个数据处理流程 

2. MapReduce 编程规范及示例编写 
2.1. 编程规范 

1、写一个类(MyMapper),继承hadoop框架的Mapper类,这个类就是map任务。我们只需要重写这个类的map方法(目的就是定义怎么检查每个组的作业)

2、写一个类(MyReducer),继承hadoop框架的Reducer类,这个类就是reduce任务。我们只需要重写这个类的reduce方法(目的就是定义怎么汇总那么多map任务的输出)

3、写一个普通的类(例如Demo),在Dem类中,创建一个Job对象,这个对象用于对MyMapper类和MyReducer类的配对,还用于配置MyMapper类的输出结果类型、输入数据来源。还用于配置MyReducer类的输出结果类型,输出结果目的地等等。
 
2.2. WordCount示例编写 
需求:在一堆给定的文本文件中统计输出每一个单词出现的总次数 

public class Demo1_wordcount {
    
    private static class MyMapper extends Mapper<LongWritable, Text, Text, IntWritable>{
        IntWritable v = new IntWritable(1);
        protected void map(LongWritable key, Text value, Context context)throws IOException, InterruptedException {
            String[] arr = value.toString().split("\\s+");
            for (String item : arr) {
                value.set(item);
                context.write(value, v);
            }
        }
    }
    
    private static class MyReducer extends Reducer<Text, IntWritable, Text, IntWritable>{
        IntWritable v = new IntWritable();
        @Override
        protected void reduce(Text key, Iterable<IntWritable> value, Context context)throws IOException, InterruptedException {
            int count = 0;
            Iterator<IntWritable> it = value.iterator();
            while(it.hasNext()){
                IntWritable next = it.next();
                count += next.get();
            }
            v.set(count);
            context.write(key, v);
        }
    }
    
    public static void main(String[] args) throws Exception {
        Job job = Job.getInstance();
        job.setJarByClass(Demo1_wordcount.class);
        
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);
        
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);
        
        job.setMapperClass(MyMapper.class);
        job.setReducerClass(MyReducer.class);
        
        FileInputFormat.setInputPaths(job, new Path("/wordcount/input"));
        FileOutputFormat.setOutputPath(job, new Path("/wordcount/output"));
        boolean b = job.waitForCompletion(true);
        System.exit(b?0:1);
    }

}


3. MapReduce 程序运行模式 
3.1. 本地运行模式  
什么也不写,直接在工具上运行,默认就是本地运行模式。
前提:在windows中,解压好windows版本的hadoop,并且要配置好环境变量

3.2. 集群运行模式  
1、注意修改数据来源是hdfs集群的路径、目的地也是集群中的路径
2、对包含mr程序的项目打包,并且指定mainclass是谁(主方法所在的类就是mainclass)
3、把打好的jar包,上传到linux系统中
4、hdfs集群中要提前创建好数据来源
5、hadoop jar xxxx.jar
6、查看yarn集群界面:http://node1:8088

实际工作中,肯定是抽样拿一些数据出来,用本地模式测试正确后,才打包,用集群来运行。

二、 深入 MapReduce  
这里,直接看wordcount流程图。。。
https://blog.csdn.net/zccaogong/article/details/52314565 
  
三、 MapReduce 的序列化 
1. 概述 
序列化(Serialization)是指把结构化对象转化为字节流。
反序列化(Deserialization)是序列化的逆过程。把字节流转为结构化对象。 
当要在进程间传递对象或持久化对象的时候,就需要序列化对象成字节流,反之当要将接收到或从磁盘读取的字节流转换为对象,就要进行反序列化。 
Java的序列化(Serializable)是一个重量级序列化框架,一个对象被序列
化后,会附带很多额外的信息(各种校验信息,header,继承体系…),不便于在网络中高效传输;所以,hadoop自己开发了一套序列化机制(Writable),精
简,高效。不用像java对象类一样传输多层的父子关系,需要哪个属性就传输哪个属性值,大大的减少网络传输的开销。 
Writable是Hadoop的序列化格式,hadoop定义了这样一个Writable接口。 
一个类要支持可序列化只需实现这个接口即可。 
2. Writable 序列化接口 
private static class Flowbean implements Writable{
        private long upflow;    //上行流量
        private long downflow;    //下行流量
        
        //覆盖序列化方法
        public void write(DataOutput out) throws IOException {
            out.writeLong(upflow);        //先序列化upflow
            out.writeLong(downflow);    //再序列化downflow
        }
            
    
        //覆盖反序列化方法
        public void readFields(DataInput in) throws IOException {
            this.upflow = in.readLong();    //第一次调用readLong方法,其实是read到了upflow,因为序列化的时候,是先序列化upflow的
            this.downflow = in.readLong();
        }
        
        @Override
        public String toString() {
            return upflow + "\t" + downflow + "\t" + (upflow + downflow);
        }

    }

3.流量汇总
对应代码的Demo2_flowsum程序。
package cn.krist.mr;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Iterator;

import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

//需求:统计出每个手机号,产生的总上行流量、总下行流量、总流量是多少
public class Demo2_flowsum {

    public static void main(String[] args) throws Exception {
        Job job = Job.getInstance();

        job.setMapperClass(MyMapper.class);                //设置job的mapper类是谁
        job.setReducerClass(MyReducer.class);            //设置job的reducer类是谁

        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(Flowbean.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(Flowbean.class);

        job.setJarByClass(Demo2_flowsum.class);

        FileInputFormat.setInputPaths(job, new Path("d:/flow/flowsum/input"));
        FileOutputFormat.setOutputPath(job, new Path("d:/flow/flowsum/output"));

        boolean b = job.waitForCompletion(true);
        System.exit(b?0:5);

    }

    private static class MyMapper extends Mapper<LongWritable, Text, Text, Flowbean>{

        Text k = new Text();            //这个对象用来封装手机号
        Flowbean v = new Flowbean();


//map的输入:1363157988072     13760778710    00-FD-07-A4-7B-08:CMCC    120.196.100.82            2    2    120    120    200
//map的输出:13760778710,flowbean<上行流量,下行流量>
        protected void map(LongWritable key, Text value, Context context)throws IOException, InterruptedException {
            String[] arr = value.toString().split("\\s+");
            k.set(arr[1]);         //把手机号,设置到k对象中
            v.setUpflow(Long.parseLong(arr[arr.length-3]));        //设置上行流量到v对象中
            v.setDownflow(Long.parseLong(arr[arr.length-2]));    //设置下行流量到v对象中
            context.write(k, v);
        }
    }
    private static class MyReducer extends Reducer<Text, Flowbean, Text, Flowbean>{
        Flowbean v = new Flowbean();        //这个对象,用来封装总上行流量、总下行流量

//手机号1=[flowbean1,flowbean2]
        protected void reduce(Text key, Iterable<Flowbean> value, Context context)throws IOException, InterruptedException {
            long up = 0;
            long down = 0;
            Iterator<Flowbean> it = value.iterator();
            while(it.hasNext()){
                Flowbean next = it.next();
                up += next.getUpflow();            //把next对象的上行流量,累加到up变量中
                down += next.getDownflow();        //把next对象的下行流量,累加到down变量中
            }

            v.setUpflow(up);
            v.setDownflow(down);
            context.write(key, v);
        }
    }

    private static class Flowbean implements Writable{
        private long upflow;    //上行流量
        private long downflow;    //下行流量

        //覆盖序列化方法
        public void write(DataOutput out) throws IOException {
            out.writeLong(upflow);        //先序列化upflow
            out.writeLong(downflow);    //再序列化downflow
        }


        //覆盖反序列化方法
        public void readFields(DataInput in) throws IOException {
            this.upflow = in.readLong();    //第一次调用readLong方法,其实是read到了upflow,因为序列化的时候,是先序列化upflow的
            this.downflow = in.readLong();
        }

        @Override
        public String toString() {
            return upflow + "\t" + downflow + "\t" + (upflow + downflow);
        }


        public Flowbean(long upflow, long downflow) {
            this.upflow = upflow;
            this.downflow = downflow;
        }

        public Flowbean() {
        }


        public long getUpflow() {
            return upflow;
        }

        public void setUpflow(long upflow) {
            this.upflow = upflow;
        }

        public long getDownflow() {
            return downflow;
        }

        public void setDownflow(long downflow) {
            this.downflow = downflow;
        }

    }

}

四、 Mapreduce 的自定义排序
1. 需求 
在得出统计每一个用户(手机号)所耗费的总上行流量、下行流量,总流量结果的基础之上再加一个需求:将统计结果按照总流量倒序排序。 
2. 排序原理 
基本思路: 
MR 程序在处理数据的过程中会对数据排序,排序的依据是比较map输出的键值对中的key的大小。所以,我们如果要实现自己需要的排序规则,则可以考虑将排序因素放到 key 中,让 key 实现接口:WritableComparable,然后重写key的compareTo方法。 
3. 代码实现 
 查看代码Demo3_flowsumSort.java
 
package cn.krist.mr;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class Demo3_flowsumSort {
    
    public static void main(String[] args) throws Exception {
        Job job = Job.getInstance();
        job.setJarByClass(Demo3_flowsumSort.class);
        
        job.setMapperClass(MyMapper.class);
        job.setReducerClass(MyReducer.class);
        
        job.setMapOutputKeyClass(Flowbean.class);
        job.setMapOutputValueClass(NullWritable.class);
        
        job.setOutputKeyClass(Flowbean.class);
        job.setOutputValueClass(NullWritable.class);
        
        FileInputFormat.setInputPaths(job, new Path("D:/flow/sort/input"));
        FileOutputFormat.setOutputPath(job, new Path("d:/flow/sort/output"));
        
        boolean b = job.waitForCompletion(true);
        System.exit(b?0:12);
    }
    
    private static class MyMapper extends Mapper<LongWritable, Text, Flowbean, NullWritable>{
        Flowbean k = new Flowbean();
        NullWritable v = NullWritable.get();
        /*
         13480253104    180    180    360
         */
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            String[] arr = value.toString().split("\\s+");
            k.setPhone(arr[0]);
            k.setUpflow(Long.parseLong(arr[1]));
            k.setDownflow(Long.parseLong(arr[2]));
            k.setSumflow(Long.parseLong(arr[3]));
            context.write(k, v);                    //write(Flowbean, NullWritable)
        }
    }
    
    private static class MyReducer extends Reducer<Flowbean, NullWritable, Flowbean, NullWritable>{
        @Override
        protected void reduce(Flowbean key , Iterable<NullWritable> value, Context context)throws IOException, InterruptedException {
            context.write(key, value.iterator().next());;
        }
    }
    
    private static class Flowbean implements WritableComparable<Flowbean>{
        
        
        
        public void setUpflow(long upflow) {
            this.upflow = upflow;
        }

        public void setDownflow(long downflow) {
            this.downflow = downflow;
        }

        public void setSumflow(long sumflow) {
            this.sumflow = sumflow;
        }

        public void setPhone(String phone) {
            this.phone = phone;
        }

        @Override
        public String toString() {
            return phone + "\t" + upflow + "\t" + downflow + "\t" + sumflow;
        }
        
        private long upflow;
        private long downflow;
        private long sumflow;
        private String phone;

        @Override
        public void write(DataOutput out) throws IOException {
            out.writeLong(upflow);
            out.writeLong(downflow);
            out.writeLong(sumflow);
            out.writeUTF(phone);
            
        }

        @Override
        public void readFields(DataInput in) throws IOException {
            this.upflow = in.readLong();
            this.downflow = in.readLong();
            this.sumflow = in.readLong();
            this.phone = in.readUTF();
        }

        /**
         1、按照总流量从大到小的顺序排序
         2、总流量一样,那就按照手机号的自然顺序来排序
         13480253104    180    180    360
         13502468823    7335    110349    117684
         */
        public int compareTo(Flowbean o) {
            if(this.sumflow > o.sumflow){    //如果后添加的行的总流量,大于之前添加的行的总流量
                return -6;
            }
            
            if(this.sumflow < o.sumflow){
                return 6;
            }
            
            //代码执行到这里,意味着总流量相等
            int num = this.phone.compareTo(o.phone);
            if (num > 0) {
                return -6;
            }
            
            if (num < 0) {
                return 6;
            }
            
            return 0;
        }
        
    }

}

五、 Mapreduce 的自定义分区 
1. 需求 
将流量汇总统计结果按照手机归属地不同省份输出到不同文件中。 
2. 分区原理 
Mapreduce框架会在map方法执行之后,在数据流出map任务所在节点之前,对输出结果进行分区。默认的分区规则是HashPartitioner类定义的,如果需要自定义分区,则需要自定义一个分区类,把这个分区类,关联上任务描述类即可。
3. 代码实现 
查看课堂代码

 
      
六、 Mapreduce 的 combiner 
每一个 map 都可能会产生大量的本地输出,Combiner 的作用就是对 map 端的输出先做一次合并,以减少在 map 和 reduce 节点之间的数据传输量,以提高网络 IO 性能,是 MapReduce 的一种优化手段之一。
Combiner的父类,也是Reducer。使用时,可以公用MyReducer类,作为Combinner类 。
代码如下
查看课堂的java项目
package cn.krist.mr;

import java.io.IOException;
import java.util.Iterator;

import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class Demo5_combiner {
    
    public static void main(String[] args) throws Exception {
        //把mapper任务和reducer任务,拼接起来
        Job job = Job.getInstance();    //获取Job对象,我们会把一堆的配置,都配置到job对象中
        job.setJarByClass(cn.krist.mr.Demo5_combiner.class);
        
        job.setMapperClass(MyMapper.class);     //指定任务的mapper阶段,采用哪个类
        job.setReducerClass(MyReducer.class);     //指定任务的reducer阶段,采用哪个类
        
        job.setMapOutputKeyClass(Text.class);             //指定map阶段的输出结果的键类型
        job.setMapOutputValueClass(IntWritable.class);    //指定map阶段的输出结果的值类型
        
        job.setOutputKeyClass(Text.class);                 //指定reducer阶段的输出结果的键类型
        job.setOutputValueClass(IntWritable.class);     //指定reducer阶段的输出结果的值类型
        
        FileInputFormat.setInputPaths(job, new Path("d:/wordcount/input"));     //指定map阶段的输入数据来源于哪个文件夹
        FileOutputFormat.setOutputPath(job, new Path("d:/wordcount/output"));     //指定最终输出结果的目的地

        job.setCombinerClass(MyReducer.class);             //设置combiner组件,使用哪个类
        
        boolean b = job.waitForCompletion(true);         //把job任务,提交给yarn来运行
        System.exit(b?0:1);                                //如果运行mr程序正常,就正常退出虚拟机,否则就异常退出虚拟机
        
    }
    
    /* <0, hadoop spark>
     第1个泛型:mapper程序的输入参数(键值对类型)的键类型
     第2个泛型:mapper程序的输入参数的值的类型
     第3个泛型:mapper程序的输出结果的键的类型
     第4个泛型:mapper程序的输出结果的值的类型
     */
    private static class MyMapper extends Mapper<LongWritable, Text, Text, IntWritable>{
        Text k = new Text();
        IntWritable v = new IntWritable(1);
        /*
         读到一行内容时,就把这一行内容的起始偏移量,赋值给key
         把这一行内容,赋值给value
         context:把map的结果写出去
         */
        protected void map(LongWritable key, Text value,Context context)throws IOException, InterruptedException {
            Thread.sleep(10000);
            String[] arr = value.toString().split("\\s+"); //arr=[hadoop, spark]
            for (String item : arr) {
                k.set(item);
                context.write(k, v);  //write(Text, IntWritable)
            }
        }
    }
    
    /*
     第1个泛型:reducer程序的输入参数的键的类型
     第2个泛型:reducer程序的输入参数的值的类型
     第3个泛型:reducer程序的输出结果的键的类型
     第4个泛型:reducer程序的输出结果的值的类型
     */
    private static class MyReducer extends Reducer<Text, IntWritable, Text, IntWritable>{
        IntWritable v = new IntWritable();
        /*
            方法的输入参数:hadoop=[1,1]===》hadoop=2
            把hadoop赋值给key,把[1,1]赋值给value
         */
        protected void reduce(Text key, Iterable<IntWritable> value, Context context) throws IOException, InterruptedException {
            int count = 0;
            Iterator<IntWritable> it = value.iterator();     //拿到迭代器对象
            while(it.hasNext()){
                IntWritable next = it.next();
                count += next.get();            //把next对象封装的那个int数字,累加到count变量中
            }
            v.set(count);
            context.write(key, v);   //write(Text key, IntWritable value) 
        }
    }

}

      
七、 Apache Flume 
1. 概述 
Flume 是 Cloudera 提供的一个高可用的,高可靠的,分布式的海量日志采集、聚合和传输的软件。 
Flume软件有3个组件,分别是source、channel、sink。三个组件的作用,如下图所示:

 
      
 
      
2. Flume 采集系统结构图 
2.1. 简单结构 

2.2. 复杂结构 


八、 Flume 安装部署 
Flume的安装非常简单 
上传安装包到数据源所在节点上 
然后解压  tar -zxvf apache-flume-1.6.0-bin.tar.gz 
然后进入flume的目录,修改conf下的flume-env.sh,在里面配置JAVA_HOME 
根据数据采集需求配置采集方案,描述在配置文件中(文件名可任意自定义) 
指定采集方案配置文件,在相应的节点上启动flume agent 
 
先用一个最简单的例子来测试一下程序环境是否正常 
1、先在 flume 的 conf 目录下新建一个文件 
vi   netcat-logger.conf 
# 定义这个 agent 中各组件的名字 
a1.sources = r1 
a1.sinks = k1 
a1.channels = c1 
 
# 描述和配置 source 组件:r1 
a1.sources.r1.type = netcat 
a1.sources.r1.bind = localhost 
a1.sources.r1.port = 44444 
 
# 描述和配置 sink 组件:k1 
a1.sinks.k1.type = logger 
 
# 描述和配置 channel 组件,此处使用是内存缓存的方式 
a1.channels.c1.type = memory 
a1.channels.c1.capacity = 1000 
a1.channels.c1.transactionCapacity = 100 
 
# 描述和配置 source  channel   sink 之间的连接关系 
a1.sources.r1.channels = c1 
a1.sinks.k1.channel = c1  
2、启动 agent 去采集数据 
bin/flume-ng agent -c conf -f conf/netcat-logger.conf -n a1  -Dflume.root.logger=INFO,console 
-c conf   指定flume自身的配置文件所在目录 
-f conf/netcat-logger.con  指定我们所描述的采集方案 
-n a1  指定我们这个agent的名字 
3、测试先要往agent采集监听的端口上发送数据,让agent有数据可采。随便在一个能跟agent节点联网的机器上:
yum -y install telnet安装这个软件 
telnet anget-hostname  port   (上面配置文件的:telnet localhost 44444) 
      退出: 1.先同时按下  Ctrl + ]   2. 然后输入close

九、 Flume 简单案例 
1. 采集目录到 HDFS 
采集需求:服务器的某特定目录下,会不断产生新的文件,每当有新文件出现,就需要把文件采集到HDFS中去 
根据需求,首先定义以下3大要素 
采集源,即source——监控文件目录 :  spooldir 
下沉目标,即sink——HDFS文件系统  :  hdfs sink 
source和sink之间的传递通道——channel,可用file channel 也可以用内存channel 配置文件编写: 
# Name the components on this agent 
a1.sources = r1 
a1.sinks = k1 
a1.channels = c1 
 
# Describe/configure the source 
##注意:不能往监控目中重复丢同名文件 
a1.sources.r1.type = spooldir 
a1.sources.r1.spoolDir = /root/logs 
a1.sources.r1.fileHeader = true 
 
# Describe the sink 
a1.sinks.k1.type = hdfs 
a1.sinks.k1.hdfs.path = /flume/events/%y-%m-%d/%H%M/ 
a1.sinks.k1.hdfs.filePrefix = events- 
a1.sinks.k1.hdfs.round = true 
a1.sinks.k1.hdfs.roundValue = 10 
a1.sinks.k1.hdfs.roundUnit = minute 
a1.sinks.k1.hdfs.rollInterval = 3 
a1.sinks.k1.hdfs.rollSize = 20 
a1.sinks.k1.hdfs.rollCount = 5 
a1.sinks.k1.hdfs.batchSize = 1 
a1.sinks.k1.hdfs.useLocalTimeStamp = true 
#生成的文件类型,默认是 Sequencefile,可用 DataStream,则为普通文本 
a1.sinks.k1.hdfs.fileType = DataStream 
 
# Use a channel which buffers events in memory 
a1.channels.c1.type = memory 
a1.channels.c1.capacity = 1000 
a1.channels.c1.transactionCapacity = 100 
 
# Bind the source and sink to the channel 
a1.sources.r1.channels = c1 
a1.sinks.k1.channel = c1  

Channel参数解释: 
capacity:默认该通道中最大的可以存储的event数量 
trasactionCapacity:每次最大可以从source中拿到或者送到sink中的event
数量 
bin/flume-ng agent -c conf -f conf/spooldir-hdfs.conf -n a1  -Dflume.root.logger=INFO,console 
2. 采集文件到 HDFS 
采集需求:比如业务系统使用log4j生成的日志,日志内容不断增加,需要把追加到日志文件中的数据实时采集到hdfs 
 
根据需求,首先定义以下3大要素 
采集源,即source——监控文件内容更新 :  exec  ‘tail -F file’ 
下沉目标,即sink——HDFS文件系统  :  hdfs sink 
Source和sink之间的传递通道——channel,可用file channel 也可以用 
内存channel 
 
配置文件编写: 
# Name the components on this agent 
a1.sources = r1 
a1.sinks = k1 
a1.channels = c1 
 
# Describe/configure the source 
a1.sources.r1.type = exec 
a1.sources.r1.command = tail -F /root/logs/test.log 
a1.sources.r1.channels = c1 
 
# Describe the sink 
a1.sinks.k1.type = hdfs 
a1.sinks.k1.hdfs.path = /flume/tailout/%y-%m-%d/%H%M/ 
a1.sinks.k1.hdfs.filePrefix = events- 
a1.sinks.k1.hdfs.round = true 
a1.sinks.k1.hdfs.roundValue = 10 
a1.sinks.k1.hdfs.roundUnit = minute 
a1.sinks.k1.hdfs.rollInterval = 3 
a1.sinks.k1.hdfs.rollSize = 20 
a1.sinks.k1.hdfs.rollCount = 5 
a1.sinks.k1.hdfs.batchSize = 1 
a1.sinks.k1.hdfs.useLocalTimeStamp = true 
#生成的文件类型,默认是 Sequencefile,可用 DataStream,则为普通文本 
a1.sinks.k1.hdfs.fileType = DataStream 
 
# Use a channel which buffers events in memory 
a1.channels.c1.type = memory 
a1.channels.c1.capacity = 1000 
a1.channels.c1.transactionCapacity = 100 
 
# Bind the source and sink to the channel 
a1.sources.r1.channels = c1 
a1.sinks.k1.channel = c1  
      
参数解析: 
•rollInterval 
默认值:30 
hdfs sink间隔多长将临时文件滚动成最终目标文件,单位:秒;如果设置成0,则表示不根据时间来滚动文件; 
注:滚动(roll)指的是,hdfs sink将临时文件重命名成最终目标文件,并新打开一个临时文件来写入数据; 
•rollSize 
默认值:1024 
当临时文件达到该大小(单位:bytes)时,滚动成目标文件;如果设置成0,则表示不根据临时文件大小来滚动文件; 
•rollCount 
默认值:10 当events数据达到该数量时候,将临时文件滚动成目标文件;如果设置成0,则表示不根据events数据来滚动文件; 
•round 
默认值:false 
是否启用时间上的“舍弃”,这里的“舍弃”,类似于“四舍五入”。 
•roundValue 
默认值:1 时间上进行“舍弃”的值;  roundUnit 
默认值:seconds 时间上进行“舍弃”的单位,包含:second,minute,hour 示例: 
a1.sinks.k1.hdfs.path = /flume/events/%y-%m-%d/%H%M/%S a1.sinks.k1.hdfs.round = true a1.sinks.k1.hdfs.roundValue = 10 a1.sinks.k1.hdfs.roundUnit = minute 
当时间为2015-10-16 17:38:59时候,hdfs.path依然会被解析为: 
/flume/events/20151016/17:30/00 
因为设置的是舍弃10分钟内的时间,因此,该目录每10分钟新生成一
个。 
      
十、 Flume 的 load-balance、failover 
负载均衡是用于解决一台机器(一个进程)无法解决所有请求而产生的一种
算法。Load balancing Sink Processor能够实现load balance功能,如下图 Agent1是一个路由节点,负责将Channel暂存的Event均衡到对应的多个Sink
组件上,而每个Sink组件分别连接到一个独立的Agent上,示例配置,如下所
示: 


a1.sinkgroups = g1 
a1.sinkgroups.g1.sinks = k1 k2 k3 
a1.sinkgroups.g1.processor.type = load_balance 
a1.sinkgroups.g1.processor.backoff = true  #如果开启,则将失败的 sink 放入黑名单 
a1.sinkgroups.g1.processor.selector = round_robin  # 另外还支持 random 
a1.sinkgroups.g1.processor.selector.maxTimeOut=10000 #在黑名单放置的超时时间,超时结束时,若仍然无法接收,则超时时间呈指数增长  
      
Failover Sink Processor 能够实现 failover 功能,具体流程类似 load balance,但是内部处理机制与load balance完全不同。 
Failover Sink Processor维护一个优先级Sink组件列表,只要有一个Sink 组件可用,Event就被传递到下一个组件。故障转移机制的作用是将失败的Sink 降级到一个池,在这些池中它们被分配一个冷却时间,随着故障的连续,在重试之前冷却时间增加。一旦Sink成功发送一个事件,它将恢复到活动池。 Sink具有与之相关的优先级,数量越大,优先级越高。 
例如,具有优先级为100的sink在优先级为80的Sink之前被激活。如果在发送事件时汇聚失败,则接下来将尝试下一个具有最高优先级的Sink发送事件。如果没有指定优先级,则根据在配置中指定Sink的顺序来确定优先级。 
示例配置如下所示: 
a1.sinkgroups = g1 a1.sinkgroups.g1.sinks = k1 k2 k3 
a1.sinkgroups.g1.processor.type = failover 
a1.sinkgroups.g1.processor.priority.k1 = 5  #优先级值, 绝对值越大表示优先级越高 
a1.sinkgroups.g1.processor.priority.k2 = 7 
a1.sinkgroups.g1.processor.priority.k3 = 6 
a1.sinkgroups.g1.processor.maxpenalty = 20000  #失败的 Sink 的最大回退期(millis)       
十一、 Flume 实战案例 
1. 日志的采集和汇总 
1.1. 案例场景 
A、B 两台日志服务机器实时生产日志主要类型为 access.log、nginx.log、web.log  
现在要求: 
把A、B 机器中的access.log、nginx.log、web.log 采集汇总到C机器上
然后统一收集到hdfs中。 
但是在hdfs中要求的目录为: 
/source/logs/access/20160101/** 
/source/logs/nginx/20160101/** 
/source/logs/web/20160101/** 
1.2. 场景分析 

 
1.3. 数据流程处理分析 

1.4. 功能实现 
① 在服务器 A 和服务器 B 上 
    创建配置文件     exec_source_avro_sink.conf 
 
# Name the components on this agent 
a1.sources = r1 r2 r3 a1.sinks = k1 
a1.channels = c1 
 
# Describe/configure the source
 a1.sources.r1.type = exec
 a1.sources.r1.command = tail -F /root/data/access.log
 a1.sources.r1.interceptors = i1 
a1.sources.r1.interceptors.i1.type = static 
##  static 拦截器的功能就是往采集到的数据的 header 中插入自
##  己定义的 key-value 对 
a1.sources.r1.interceptors.i1.key = type 
a1.sources.r1.interceptors.i1.value = access 
 
a1.sources.r2.type = exec 
a1.sources.r2.command = tail -F /root/data/nginx.log 
a1.sources.r2.interceptors = i2 
a1.sources.r2.interceptors.i2.type = static 
a1.sources.r2.interceptors.i2.key = type 
a1.sources.r2.interceptors.i2.value = nginx 
 
a1.sources.r3.type = exec 
a1.sources.r3.command = tail -F /root/data/web.log 
a1.sources.r3.interceptors = i3 
a1.sources.r3.interceptors.i3.type = static 
a1.sources.r3.interceptors.i3.key = type 
a1.sources.r3.interceptors.i3.value = web 
 
# Describe the sink 
a1.sinks.k1.type = avro 
a1.sinks.k1.hostname = node3 
a1.sinks.k1.port = 41414 
 
# Use a channel which buffers events in memory 
a1.channels.c1.type = memory 
a1.channels.c1.capacity = 20000 
a1.channels.c1.transactionCapacity = 10000 
 
# Bind the source and sink to the channel 
a1.sources.r1.channels = c1 
a1.sources.r2.channels = c1 
a1.sources.r3.channels = c1 
a1.sinks.k1.channel = c1 
 
② 在服务器 C 上创建配置文件 avro_source_hdfs_sink.conf  文件内容为 
 
#定义 agent 名, source、channel、sink 的名称 
a1.sources = r1 a1.sinks = k1 
a1.channels = c1 
 
#定义 source 
a1.sources.r1.type = avro 
a1.sources.r1.bind = node3
a1.sources.r1.port =41414 
 
#添加时间拦截器 
a1.sources.r1.interceptors = i1 
a1.sources.r1.interceptors.i1.type =  org.apache.flume.interceptor.TimestampInterceptor$Builder 
 
#定义 channels 
a1.channels.c1.type = memory 
a1.channels.c1.capacity = 20000 
a1.channels.c1.transactionCapacity = 10000 
 
#定义 sink 
a1.sinks.k1.type = hdfs 
a1.sinks.k1.hdfs.path=hdfs://node1:9000/source/logs/%{type}/%Y%m%d 
a1.sinks.k1.hdfs.filePrefix =events 
a1.sinks.k1.hdfs.fileType = DataStream 
a1.sinks.k1.hdfs.writeFormat = Text 
#时间类型 
a1.sinks.k1.hdfs.useLocalTimeStamp = true 
#生成的文件不按条数生成 
a1.sinks.k1.hdfs.rollCount = 0 
#生成的文件按时间生成 
a1.sinks.k1.hdfs.rollInterval = 30 
#生成的文件按大小生成 
a1.sinks.k1.hdfs.rollSize  = 10485760 
#批量写入 hdfs 的个数 
a1.sinks.k1.hdfs.batchSize = 10000 
flume 操作 hdfs 的线程数(包括新建,写入等) 
a1.sinks.k1.hdfs.threadsPoolSize=10 
#操作 hdfs 超时时间 
a1.sinks.k1.hdfs.callTimeout=30000 
 
#组装 source、channel、sink 
a1.sources.r1.channels = c1 
a1.sinks.k1.channel = c1 
③ 配置完成之后,在服务器 A 和 B 上的/root/data 有数据文件 access.log、 nginx.log、web.log。先启动服务器 C 上的 flume,启动命令在 flume 安装目录下执行 : 
bin/flume-ng agent -c conf -f conf/avro_source_hdfs_sink.conf -name a1 Dflume.root.logger=DEBUG,console      
 
然后在启动服务器上的 A 和 B,启动命令在 flume 安装目录下执行 : 
bin/flume-ng agent -c conf -f conf/exec_source_avro_sink.conf -name a1 -
Dflume.root.logger=DEBUG,console 

1.5自定义拦截器     
      
 
      
一、 多彩缤纷数据源 
典型的数据分析系统,要分析的数据种类其实是比较丰富的。依据来源可大体分为以下几个部分: 
 
图:数据分析系统数据来源 
1. 业务系统数据 
业务系统产生的数据是不可忽视的,比如电商网站,大量的订单数据看似杂乱无章,实则蕴含潜在的商业价值,可以从中分析进而进行商业推广,产品推荐等。 
另一角度来看,业务系统数据获取成本低、方式容易,属于公司内部范畴。业务系统的数据一般保存在关系型数据库当中。获取形式有: 
接口调用:直接获取业务系统数据库的数据,但是要注意不能影响业务系统数据库的性能,比如大量获取数据增大数据库读数据压力。 
数据库dump:非高峰时段,或者在数据库从库上dump出全部数据。一般企业中会定时进行数据库的备份、导出工作,那么就可以共享使用这些数据。 
比如MySQL数据库,使用mysqldump工具就可以进行数据库的导出。 
mysqldump -uroot -pPassword [database name] [dump file] 
mysqldump命令将数据库中的数据备份成一个文本文件。表的结构和表中的数据将存储在生成的文本文件中。 
2. 爬虫数据 

 
在进行网站数据分析的时候,除了内部数据之外,还有一部分数据是我们不能够忽视的。那就是所谓的外部数据。当然这是相对公司网站来说的。拥有了外部数据可以更好的帮助我们进行数据分析。 
爬虫(Web crawler),是指一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。它们被广泛用于互联网搜索引擎或其他类似网站,可以自动采集所有其能够访问到的页面内容,以获取或更新这些网站的内容和检索方式。 
电子商务行业最初的爬虫需求来源于比价。这是某些电商网站的核心业务。大家如果买商品的时候,是一个价格敏感型用户的话,很可能会使用比价功能。毫无悬念,会使用爬虫技术来爬取所有相关电商的价格。 
当然,这并不意味着大家喜欢被爬取。于是需要通过技术手段来做反爬虫。 
      
      
二、数据仓库 

1. 数据仓库的基本概念 
数据仓库,英文名称为Data Warehouse,可简写为DW或DWH。数据仓库的目的是构建面向分析的集成化数据环境,为企业提供决策支持,它出于分析性报告和决策支持目的而创建。 
2. 数据仓库的主要特征 
数据仓库是面向主题的(Subject-Oriented )、集成的(Integrated)、非易失的(Non-Volatile)和时变的(Time-Variant )数据集合,用以支持管理决策 。 
2.1. 面向主题 
传统数据库中,最大的特点是面向应用进行数据的组织,各个业务系统可能是相互分离的。而数据仓库则是面向主题的。主题是一个抽象的概念,是较高层次上企业信息系统中的数据综合、归类并进行分析利用的抽象。在逻辑意义上,它是对应企业中某一宏观分析领域所涉及的分析对象。 
操作型处理(传统数据)对数据的划分并不适用于决策分析。而基于主题组织的数据则不同,它们被划分为各自独立的领域,每个领域有各自的逻辑内涵但互不交叉,在抽象层次上对数据进行完整、一致和准确的描述。一些主题相关的
数据通常分布在多个操作型系统中。 
      
2.2. 集成性 
通过对分散、独立、异构的数据库数据进行抽取、清理、转换和汇总便得到了数据仓库的数据,这样保证了数据仓库内的数据关于整个企业的一致性。 
数据仓库中的综合数据不能从原有的数据库系统直接得到。因此在数据进入数据仓库之前,必然要经过统一与综合,这一步是数据仓库建设中最关键、最复杂的一步,所要完成的工作有: 
(1)要统一源数据中所有矛盾之处,如字段的同名异义、异名同义、单位不统一、字长不一致,等等。 
(2)进行数据综合和计算。数据仓库中的数据综合工作可以在从原有数据库抽取数据时生成,但许多是在数据仓库内部生成的,即进入数据仓库以后进行综合生成的。 
下图说明一个保险公司综合数据的简单处理过程,其中数据仓库中与“保险”
主题有关的数据来自于多个不同的操作型系统。这些系统内部数据的命名可能不同,数据格式也可能不同。把不同来源的数据存储到数据仓库之前,需要去除这些不一致。 
 
图:数据仓库的数据集成 
2.3. 非易失性(不可更新性) 
操作型数据库主要服务于日常的业务操作,使得数据库需要不断地对数据实时更新,以便迅速获得当前最新数据,不至于影响正常的业务运作。在数据仓库中只要保存过去的业务数据,不需要每一笔业务都实时更新数据仓库,而是根据商业需要每隔一段时间把一批较新的数据导入数据仓库。 
数据仓库的数据反映的是一段相当长的时间内历史数据的内容,是不同时点的数据库快照的集合,以及基于这些快照进行统计、综合和重组的导出数据。 
数据非易失性主要是针对应用而言。数据仓库的用户对数据的操作大多是数据查询或比较复杂的挖掘,一旦数据进入数据仓库以后,一般情况下被较长时间保留。数据仓库中一般有大量的查询操作,但修改和删除操作很少。因此,数据经加工和集成进入数据仓库后是极少更新的,通常只需要定期的加载和更新。 
2.4. 时变性 
数据仓库包含各种粒度的历史数据。数据仓库中的数据可能与某个特定日期、
星期、月份、季度或者年份有关。数据仓库的目的是通过分析企业过去一段时间业务的经营状况,挖掘其中隐藏的模式。虽然数据仓库的用户不能修改数据,但并不是说数据仓库的数据是永远不变的。分析的结果只能反映过去的情况,当业务变化后,挖掘出的模式会失去时效性。因此数据仓库的数据需要更新,以适应决策的需要。从这个角度讲,数据仓库建设是一个项目,更是一个过程 。数据仓库的数据随时间的变化表现在以下几个方面。 
(1)数据仓库的数据时限一般要远远长于操作型数据的数据时限。 
(2)操作型系统存储的是当前数据,而数据仓库中的数据是历史数据。
(3)数据仓库中的数据是按照时间顺序追加的,它们都带有时间属性。 
      
3.数据仓库与数据库区别 
1、 数据库是面向事务的设计,数据仓库是面向主题设计的。 

2、 数据库一般存储业务数据,数据仓库存储的一般是历史数据。
 
 3、数据库设计是尽量避免冗余,一般针对某一业务应用进行设计,比如一
张简单的 User 表, 记录用户名、 密码等简单数据即可, 符合业务应用,
但是不符合分析。数据仓库在设计是有意引入冗余,依照分析需求,分
析维度、分析指标进行设计。 

    4、数据库是为捕获数据而设计,数据仓库是为分析数据而设计。 

数据库与数据仓库的区别实际讲的是OLTP与OLAP的区别。 
操作型处理,叫联机事务处理OLTP(On-Line Transaction Processing),也可以称面向交易的处理系统,它是针对具体业务在数据库联机的日常操作,通常对少数记录进行查询、修改。用户较为关心操作的响应时间、数据的安全性、完整性和并发支持的用户数等问题。传统的数据库系统作为数据管理的主要手段,主要用于操作型处理。 
分析型处理,叫联机分析处理OLAP(On-Line Analytical Processing)一般针对某些主题的历史数据进行分析,支持管理决策。 
首先要明白,数据仓库的出现,并不是要取代数据库。 
数据库是面向事务的设计,数据仓库是面向主题设计的。 
数据库一般存储业务数据,数据仓库存储的一般是历史数据。 
数据库设计是尽量避免冗余,一般针对某一业务应用进行设计,比如一张简单的User表,记录用户名、密码等简单数据即可,符合业务应用,但是不符合分析。数据仓库在设计是有意引入冗余,依照分析需求,分析维度、分析指标进行设计。 
数据库是为捕获数据而设计,数据仓库是为分析数据而设计。 
以银行业务为例。数据库是事务系统的数据平台,客户在银行做的每笔交易都会写入数据库,被记录下来,这里,可以简单地理解为用数据库记账。数据仓库是分析系统的数据平台,它从事务系统获取数据,并做汇总、加工,为决策者提供决策的依据。比如,某银行某分行一个月发生多少交易,该分行当前存款余额是多少。如果存款又多,消费交易又多,那么该地区就有必要设立ATM了。  
显然,银行的交易量是巨大的,通常以百万甚至千万次来计算。事务系统是实时的,这就要求时效性,客户存一笔钱需要几十秒是无法忍受的,这就要求数据库只能存储很短一段时间的数据。而分析系统是事后的,它要提供关注时间段内所有的有效数据。这些数据是海量的,汇总计算起来也要慢一些,但是,只要能够提供有效的分析数据就达到目的了。  
数据仓库,是在数据库已经大量存在的情况下,为了进一步挖掘数据资源、
为了决策需要而产生的,它决不是所谓的“大型数据库”。 
4. 数据仓库分层架构 
按照数据流入流出的过程,数据仓库架构可分为三层——源数据、数据仓库、数据应用。 
 
 数据仓库的数据来源于不同的源数据,并提供多样的数据应用,数据自下而上流入数据仓库后向上层开放应用,而数据仓库只是中间集成化数据管理的一个平台。
源数据层(ODS):此层数据无任何更改,直接沿用外围系统数据结构和数据,不对外开放;为临时存储层,是接口数据的临时存储区域,为后一步的数据处理做准备。 
数据仓库层(DW):也称为细节层,DW 层的数据应该是一致的、准确的、干净的数据,即对源系统数据进行了清洗(去除了杂质)后的数据。 
数据应用层(DA或APP):前端应用直接读取的数据源;根据报表、专题分析需求而计算生成的数据。 
数据仓库从各数据源获取数据及在数据仓库内的数据转换和流动都可以认为是ETL(抽取Extra, 转化Transfer, 装载Load)的过程,ETL是数据仓库的流水线,也可以认为是数据仓库的血液,它维系着数据仓库中数据的新陈代谢,而数据仓库日常的管理和维护工作的大部分精力就是保持ETL的正常和稳定。为什么要对数据仓库分层? 
用空间换时间,通过大量的预处理来提升应用系统的用户体验(效率),因此数据仓库会存在大量冗余的数据;不分层的话,如果源业务系统的业务规则发生变化将会影响整个数据清洗过程,工作量巨大。 
通过数据分层管理可以简化数据清洗的过程,因为把原来一步的工作分到了多个步骤去完成,相当于把一个复杂的工作拆成了多个简单的工作,把一个大的黑盒变成了一个白盒,每一层的处理逻辑都相对简单和容易理解,这样我们比较容易保证每一个步骤的正确性,当数据发生错误的时候,往往我们只需要局部调整某个步骤即可。 
5. 数据仓库元数据管理 
元数据(Meta Date),主要记录数据仓库中模型的定义、各层级间的映射关系、监控数据仓库的数据状态及ETL的任务运行状态。一般会通过元数据资料库(Metadata Repository)来统一地存储和管理元数据,其主要目的是使数据仓库的设计、部署、操作和管理能达成协同和一致。 
元数据是数据仓库管理系统的重要组成部分,元数据管理是企业级数据仓库中的关键组件,贯穿数据仓库构建的整个过程,直接影响着数据仓库的构建、使用和维护。 
构建数据仓库的主要步骤之一是 ETL。这时元数据将发挥重要的作用,它定
义了源数据系统到数据仓库的映射、数据转换的规则、数据仓库的逻辑结构、数据更新的规则、数据导入历史记录以及装载周期等相关内容。数据抽取和转换的专家以及数据仓库管理员正是通过元数据高效地构建数据仓库。 
用户在使用数据仓库时,通过元数据访问数据,明确数据项的含义以及定制报表。 
数据仓库的规模及其复杂性离不开正确的元数据管理,包括增加或移除外部数据源,改变数据清洗方法,控制出错的查询以及安排备份等。  
元数据可分为技术元数据和业务元数据。技术元数据为开发和管理数据仓库的IT 人员使用,它描述了与数据仓库开发、管理和维护相关的数据,包括数据源信息、数据转换描述、数据仓库模型、数据清洗与更新规则、数据映射和访问权限等。而业务元数据为管理层和业务分析人员服务,从业务角度描述数据,包括商务术语、数据仓库中有什么数据、数据的位置和数据的可用性等,帮助业务人员更好地理解数据仓库中哪些数据是可用的以及如何使用。
由上可见,元数据不仅定义了数据仓库中数据的模式、来源、抽取和转换规则等,而且是整个数据仓库系统运行的基础,元数据把数据仓库系统中各个松散
的组件联系起来,组成了一个有机的整体。 
      
四、 Apache Hive 
1. Hive 简介 
1.1. 什么是 Hive 
Hive是一个基于Hadoop的数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供类SQL查询功能。 
本质是将SQL转换为MapReduce程序。 
主要用途:用来做离线数据分析,比直接用MapReduce开发效率更高。 
 
1.2. 为什么使用 Hive 
直接使用Hadoop MapReduce处理数据所面临的问题:
人员学习成本太高,MapReduce实现复杂查询逻辑开发难度太大  

使用Hive : 
操作接口采用类SQL语法,提供快速开发的能力  
避免了去写MapReduce,减少开发人员的学习成本  功能扩展很方便 

2.安装部署 
1.先安装mysql在安装hive
centos默认可能会安装了mysql相关的软件,首先查找一下,如果有就卸载,没有就直接安装mysql。
查找
rpm -qa | grep mysql
卸载 rpm -e --nodeps xxxx

安装
 yum -y install mysql mysql-server mysql-devel 


完成后,用  /etc/init.d/mysqld start    启动mysql
service mysqld status 查看状态

启动mysql控制台: 
      mysql 进入客户端
mysql>USE mysql; 
mysql>UPDATE user SET Password=PASSWORD('root') WHERE user='root'; 
mysql>FLUSH PRIVILEGES; 

允许远程登录 
mysql>GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'root' WITH GRANT OPTION;  (这句话的意思:赋予全部权限在所有数据库和所有表上给root用户在任何主机上)
mysql>FLUSH PRIVILEGES; 
完成后就能远程管理mysql了。


3.Hive的安装
1.上传tar包

2.解压
    tar -zxvf hive-1.2.1.tar.gz
3.安装mysql数据库
   推荐yum 在线安装

4.配置hive
    (a)配置HIVE_HOME环境变量  
        vi conf/hive-env.sh 
        配置其中的$hadoop_home
       HADOOP_HOME=/export/server/hadoop-2.7.4
    
    (b)配置元数据库信息  
        vi  hive-site.xml (conf目录下自己创建)
        添加如下内容:
        <configuration>
        <property>
        <name>javax.jdo.option.ConnectionURL</name>
        <value>jdbc:mysql://node1:3306/hive?createDatabaseIfNotExist=true</value>
        </property>

        <property>
        <name>javax.jdo.option.ConnectionDriverName</name>
        <value>com.mysql.jdbc.Driver</value>
        </property>

        <property>
        <name>javax.jdo.option.ConnectionUserName</name>
        <value>root</value>
</property>

        <property>
        <name>javax.jdo.option.ConnectionPassword</name>
        <value>root</value>
        </property>
        </configuration>
    
5.安装hive和mysql完成后,将mysql的连接jar(MySQL-client-5.5.28-1.linux2.6.x86_64.rpm)包拷贝到$HIVE_HOME/lib目录下
    如果出现没有权限的问题,在mysql授权(在安装mysql的机器上执行)
    mysql -u root -p
    #(执行下面的语句  *.*:所有库下的所有表   %:任何IP地址或主机都可以连接)
    GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'root' WITH GRANT OPTION;
    FLUSH PRIVILEGES;

6. Jline包版本不一致的问题,需要拷贝hive的lib目录中jline.2.12.jar的jar包替换掉hadoop中的 
/home/hadoop/app/hadoop-2.6.4/share/hadoop/yarn/lib/jline-0.9.94.jar


启动hive
bin/hive
----------------------------------------------------------------------------------------------------
Hive几种使用方式:
    1.Hive交互shell      bin/hive
    
    2.Hive JDBC服务(参考java jdbc连接mysql)
    
    3.hive启动为一个服务器,来对外提供服务(两个虚拟机都要安装hive)
        bin/hiveserver2
        
        
        启动成功后,可以在别的节点上用beeline去连接
        bin/beeline -u jdbc:hive2://node1:10000 -n root
        (连接不上时,杀死runjar端口);
出现closed时先采用第一种方式查询几次表格,然后再用这种方式连接。
        或者
        bin/beeline
        ! connect jdbc:hive2://node1:10000
    
    4.Hive命令 
        hive  -e  ‘sql’
        bin/hive -e 'select * from t_test'


Hive安装前需要安装好JDK和Hadoop。配置好环境变量。 
根据元数据存储的介质不同,分为下面两个版本,其中derby属于内嵌模式。
实际生产环境中则使用mysql来进行元数据的存储。 
内置derby版:解压hive安装包 
bin/hive 启动即可使用 
缺点:不同路径启动hive,每一个hive拥有一套自己的元数据,无法共享 mysql版: 
解压、修改配置文件 
vi  conf/hive-site.xml 配置Mysql元数据库信息 
 
详细安装步骤查看《Hive 安装手册》 
      

五、 Hive 基本操作 
1. DDL 操作 

1.字段类型
A.helloworld
1、执行下面sql语句
create database krist;
use krist;
create table t_t1(id int, name string, age int) row format delimited fields terminated by ',';
Show databases;显示所有的数据库
Show tables  显示所有的表格
2、生成文件
vi 1.txt
1,zhangsan,23
2,李四,24
3,wangwu,25

3、上传1.txt到hdfs系统的相应位置 hadoop fs -put 1.txt  /user/hive/warehouse/krist.db/t_t1 
/user/hive/warehouse/krist.db/t_t1


4、查找
select * from t_t1;
select count(*) from t_t1;(mr程序查询)        ##这条记得事先启动yarn集群(这种方法运行比较慢,通常使用本地模式进行测试
set hive.enforce.bucketing = true;然后在执行select count(*) from t_t1;就会快很多)

....


B.普通字段
1、
create table t_t2(id int, name string) row format delimited fields terminated by ',';
vi 2.txt
1,tom
2,jerry
3,allen
上传2.txt


C.list字段
2、
create table t_t3(name string, work_locations(数组的名字)  array<string>)  row format delimited fields terminated by '\t' collection items terminated by ',';

vi 3.txt
zhangsan    beijing,shanghai,tianjin,hangzhou
wangwu    shanghai,chengdu,wuhan,haerbin

select work_locations[0]


D.map字段
create table t_map(id int,name string,hobby map<string,string>)
row format delimited 
fields terminated by ','
collection items terminated by '-'
map keys terminated by ':' ;

例子:create table t03(id int, name string,hobby map<string,string>)row format delimited fields terminated by ',' collection items terminated by '-' map keys terminated by ':' ;

    数据:
    1,zhangsan,唱歌:非常喜欢-跳舞:喜欢-游泳:一般般
    2,lisi,打游戏:非常喜欢-篮球:不喜欢


select hobby["跳舞"] from t03;


E.默认分隔符
建表时不指定分隔符,就是默认分隔符
vi编辑器中,ctrl+v,然后ctrl+a就可以输入默认分隔符
create table t_t4(id int, name string);
vi a.txt
1^Aallen
2^Atom


2.分区表
A.单分区表
分区建表分为2种,一种是单分区,也就是说在表文件夹目录下只有一级文件夹目录。另外一种是多分区,表文件夹下出现多文件夹嵌套模式。
    
     单分区建表语句:create table day_table (id int, content string) partitioned by (dt string);单分区表,按天分区,在表结构中存在id,content,dt三列。


     导入数据
     LOAD DATA local INPATH '/root/hivedata/dat_table.txt' INTO TABLE day_table partition(dt='2017-07-07');

     
     基于分区的查询:

     SELECT day_table.* FROM day_table WHERE day_table.dt = '2017-07-07';

     查看分区:

     show partitions day_hour_table;  

     总的说来partition就是辅助查询,缩小查询范围,加快数据的检索速度和对数据按照一定的规格和条件进行管理。
B.双分区表


     双分区建表语句:create table day_hour_table (id int, content string) partitioned by (dt string, hour string);双分区表,按天和小时分区,在表结构中新增加了dt和hour两列。

     导入数据

     
     LOAD DATA local INPATH '/root/hivedata/dat_table.txt' INTO TABLE day_hour_table PARTITION(dt='2017-07-07', hour='08');
     

C.增删分区
增加/删除分区

drop table t_partition;
create table t_partition(id int,name string)
partitioned by (dt string)
row format delimited
fields terminated by ',';

增加分区

alter table t_partition add partition (dt='2008-08-08') location 'hdfs://node-21:9000/t_parti/';
执行添加分区时   /t_parti文件夹下的数据不会被移动。并且没有分区目录dt=2008-08-08 


删除分区

alter table t_partition drop partition (dt='2008-08-08');
执行删除分区时/t_parti下的数据会被删除并且连同/t_parti文件夹也会被删除

注意区别于load data时候添加分区:会移动数据 会创建分区目录
3.分桶
A.创建分桶表
分桶表 cluster by 、sort by、distribute by

#指定开启分桶
set hive.enforce.bucketing = true;
set mapreduce.job.reduces=4;

create table stu_buck(Sno int,Sname string,Sex string,Sage int,Sdept string)
clustered by(Sno) 
sorted by(Sno DESC)
into 4 buckets
row format delimited
fields terminated by ',';

B.创建临时表

create table student(Sno int,Sname string,Sex string,Sage int,Sdept string) row format delimited fields terminated by ',';

然后上传数据:hadoop fs -put 'xxx' /user/....

95001,李勇,男,20,CS
95002,刘晨,女,19,IS
95003,王敏,女,22,MA
95004,张立,男,19,IS
95005,刘刚,男,18,MA
95006,孙庆,男,23,CS
95007,易思玲,女,19,MA
95008,李娜,女,18,CS
95009,梦圆圆,女,18,MA
95010,孔小涛,男,19,CS
95011,包小柏,男,18,MA
95012,孙花,女,20,CS
95013,冯伟,男,21,CS
95014,王小丽,女,19,CS
95015,王君,男,18,MA
95016,钱国,男,21,MA
95017,王风娟,女,18,IS
95018,王一,女,19,IS
95019,邢小丽,女,19,IS
95020,赵钱,男,21,IS
95021,周二,男,17,MA
95022,郑明,男,20,MA
C.插入数据表
select * from student cluster by(Sno) sort by(Sage);  报错,cluster 和 sort 不能共存

所以,用下面的:
insert overwrite table t07 select * from t07_tmp cluster by(sno);

D.查询

select * from stu_buck tablesample (bucket 1 out of 2 on sno);

解析:查询第1桶、第(1+2)桶的数据,其中sno是之前按照sno进行分桶。
这里的“2”的位置,必须是分桶个数的整数倍或者因子

4.内外部表
A.创建和删除内部表
1、创建和删除内部表

create table test1(id int, name string) row format delimited FIELDS TERMINATED BY ','

drop table test1;
B.创建和删除外部表

CREATE external TABLE test1(id INT, content STRING) ROW FORMAT delimited FIELDS TERMINATED BY ',' location '/stu';

drop table test1;
C.区别
删除内部表时,会删除表和相关的元数据;
删除外部表时,会删除相关的元数据,但是那个目录不会删除

5.修改表
1.2. 修改表 
增加分区: 
ALTER TABLE table_name ADD PARTITION (dt='20170101') location 
'/user/hadoop/warehouse/table_name/dt=20170101'; //一次添加一个分区 
 
ALTER TABLE table_name ADD PARTITION (dt='2008-08-08', country='us') location 
 '/path/to/us/part080808' PARTITION (dt='2008-08-09', country='us') location 
 '/path/to/us/part080809';  //一次添加多个分区 
 
删除分区 
ALTER TABLE table_name DROP IF EXISTS PARTITION (dt='2008-08-08'); 
ALTER TABLE table_name DROP IF EXISTS PARTITION (dt='2008-08-08', country='us'); 
 
 
修改分区 
ALTER TABLE table_name PARTITION (dt='2008-08-08') RENAME TO PARTITION (dt='20080808'); 
 
添加列 
ALTER TABLE table_name ADD|REPLACE COLUMNS (col_name STRING);  
注:ADD 是代表新增一个字段,新增字段位置在所有列后面(partition 列前) 
REPLACE 则是表示替换表中所有字段。 
 
修改列 
test_change (a int, b int, c int); 
 
ALTER TABLE test_change CHANGE a a1 INT; //修改 a 字段名 
 
// will change column a's name to a1, a's data type to string, and put it after column b. The new 
table's structure is: b int, a1 string, c int 
ALTER TABLE test_change CHANGE a a1 STRING AFTER b;  
 
// will change column b's name to b1, and put it as the first column. The new table's structure is: 
b1 int, a ints, c int 
ALTER TABLE test_change CHANGE b b1 INT FIRST;  
 
表重命名 
ALTER TABLE table_name RENAME TO new_table_name

6.显示命令
显示命令 
show tables; 
显示当前数据库所有表 
show databases |schemas; 
显示所有数据库 
show partitions table_name; 
显示表分区信息,不是分区表执行报错 
show functions; 
显示当前版本 hive 支持的所有方法 
desc extended table_name; 
查看表信息 
desc formatted table_name; 
查看表信息(格式化美观) 
describe database database_name; 
查看数据库相关信息 
7.like
create table t_t9 like t_t8;

1.1. 创建表 
建表语法 
CREATE [EXTERNAL] TABLE [IF NOT EXISTS] table_name  
   [(col_name data_type [COMMENT col_comment], ...)]  
   [COMMENT table_comment]  
   [PARTITIONED BY (col_name data_type [COMMENT col_comment], ...)]  
   [CLUSTERED BY (col_name, col_name, ...)  
   [SORTED BY (col_name [ASC|DESC], ...)] INTO num_buckets BUCKETS]  
   [ROW FORMAT row_format]  
   [STORED AS file_format]  
   [LOCATION hdfs_path] 
 
说明: 
1、CREATE TABLE 创建一个指定名字的表。如果相同名字的表已经存在,则抛出异常;用户可以用 IF NOT EXISTS 选项来忽略这个异常。 
2、EXTERNAL关键字可以让用户创建一个外部表,在建表的同时指定一个指向实际数据
的路径(LOCATION)。 
Hive 创建内部表时,会将数据移动到数据仓库指向的路径;若创建外部表,仅记录数据所在的路径,不对数据的位置做任何改变。在删除表的时候,内部表的元数据和数据会被一起删除,而外部表只删除元数据,不删除数据。 
3、LIKE 允许用户复制现有的表结构,但是不复制数据。 
CREATE [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_name LIKE existing_table; 
      
4、ROW FORMAT DELIMITED 
[FIELDS TERMINATED BY char] 
 [COLLECTION ITEMS TERMINATED BY char]  
 [MAP KEYS TERMINATED BY char]  
                     [LINES TERMINATED BY char] | SERDE serde_name  
                     [WITH SERDEPROPERTIES  
(property_name=property_value, property_name=property_value,...)] 
hive 建表的时候默认的分割符是'\001',若在建表的时候没有指明分隔符, load文件的时候文件的分隔符需要是'\001';若文件分隔符不是'001',程序不会报错,但表查询的结果会全部为'null'; 
用vi编辑器Ctrl+v然后Ctrl+a即可输入'\001' ----------->   ^A 
SerDe是Serialize/Deserilize的简称,目的是用于序列化和反序列化。 
Hive读取文件机制:首先调用 InputFormat(默认TextInputFormat),返回一条一条记录(默认是一行对应一条记录)。然后调用SerDe(默认LazySimpleSerDe)的Deserializer,将一条记录切分为各个字段(默认'\001')。 
Hive 写文件机制:将 Row 写入文件时,主要调用 OutputFormat、SerDe 的Seriliazer,顺序与读取相反。 可通过desc formatted 表名;进行相关信息查看。当我们的数据格式比较特殊的时候,可以自定义SerDe。 
5、 PARTITIONED BY 
在hive Select查询中一般会扫描整个表内容,会消耗很多时间做没必要的工作。有时候只需要扫描表中关心的一部分数据,因此建表时引入了partition分区概念。 
分区表指的是在创建表时指定的partition的分区空间。一个表可以拥有一个或者多个分区,每个分区以文件夹的形式单独存在表文件夹的目录下。表和列名不区分大小写。分区是以字段的形式在表结构中存在,通过describe table命令可以查看到字段存在,但是该字段不存放实际的数据内容,仅仅是分区的表示。 6、 STORED AS SEQUENCEFILE|TEXTFILE|RCFILE 
如果文件数据是纯文本,可以使用 STORED AS TEXTFILE。如果数据需要压缩,使用 STORED AS SEQUENCEFILE。 
TEXTFILE是默认的文件格式,使用DELIMITED子句来读取分隔的文件。 
6、CLUSTERED BY INTO num_buckets BUCKETS 
对于每一个表(table)或者分,Hive可以进一步组织成桶,也就是说桶是更为细粒度的数据范围划分。Hive也是针对某一列进行桶的组织。Hive采用对列值哈希,然后除以桶的个数求余的方式决定该条记录存放在哪个桶当中。  
把表(或者分区)组织成桶(Bucket)有两个理由: 
(1)获得更高的查询处理效率。桶为表加上了额外的结构,Hive 在处理有些查询时能利用这个结构。具体而言,连接两个在(包含连接列的)相同列上划分了桶的表,可以使用 Map 端连接 (Map-side join)高效的实现。比如JOIN操作。对于JOIN操作两个表有一个相同的列,如果对这两个表都进行了桶操作。那么将保存相同列值的桶进行JOIN操作就可以,可以大大较少JOIN的数据量。 
(2)使取样(sampling)更高效。在处理大规模数据集时,在开发和修改查询的阶段,如果能在数据集的一小部分数据上试运行查询,会带来很多方便。 
      
1.2. 修改表 
增加分区: 
ALTER TABLE table_name ADD PARTITION (dt='20170101') location 
'/user/hadoop/warehouse/table_name/dt=20170101'; //一次添加一个分区 
 
ALTER TABLE table_name ADD PARTITION (dt='2008-08-08', country='us') location 
 '/path/to/us/part080808' PARTITION (dt='2008-08-09', country='us') location  
'/path/to/us/part080809';  //一次添加多个分区 
 
删除分区 
ALTER TABLE table_name DROP IF EXISTS PARTITION (dt='2008-08-08'); 
ALTER TABLE table_name DROP IF EXISTS PARTITION (dt='2008-08-08', country='us'); 
      
修改分区 
ALTER TABLE table_name PARTITION (dt='2008-08-08') RENAME TO PARTITION (dt='20080808'); 
 
添加列 
ALTER TABLE table_name ADD|REPLACE COLUMNS (col_name STRING);  
注:ADD 是代表新增一个字段,新增字段位置在所有列后面(partition 列前) 
REPLACE 则是表示替换表中所有字段。 
 
修改列 
test_change (a int, b int, c int); 
 
ALTER TABLE test_change CHANGE a a1 INT; //修改 a 字段名 
 
// will change column a's name to a1, a's data type to string, and put it after column b. The new table's structure is: b int, a1 string, c int 
ALTER TABLE test_change CHANGE a a1 STRING AFTER b;  
 
// will change column b's name to b1, and put it as the first column. The new table's structure is: 
b1 int, a ints, c int 
ALTER TABLE test_change CHANGE b b1 INT FIRST;  
 
表重命名 
ALTER TABLE table_name RENAME TO new_table_name 
 
1.3. 显示命令 
show tables; 
显示当前数据库所有表 
show databases |schemas; 
显示所有数据库 
show partitions table_name; 
显示表分区信息,不是分区表执行报错 
show functions; 
显示当前版本 hive 支持的所有方法 
desc extended table_name; 
查看表信息 
desc formatted table_name; 
查看表信息(格式化美观) 
describe database database_name; 
查看数据库相关信息 
 

     
2. DML 操作 
2.1. Load 
 Load 
在将数据加载到表中时, Hive 不会进行任何转换。 加载操作是将数据文件移动到与 Hive
表对应的位置的纯复制/移动操作。 
语法结构 
LOAD DATA [LOCAL] INPATH 'filepath' [OVERWRITE] INTO  
TABLE tablename [PARTITION (partcol1=val1, partcol2=val2 ...)] 
 
说明: 
1、 filepath  
相对路径,例如:project/data1  
绝对路径,例如:/user/hive/project/data1  
完整 URI,例如:hdfs://namenode:9000/user/hive/project/data1 
filepath 可以引用一个文件(在这种情况下,Hive 将文件移动到表中) ,或
者它可以是一个目录 (在这种情况下, Hive 将把该目录中的所有文件移动到表中) 。 

2、 LOCAL 
如果指定了 LOCAL, load 命令将在本地文件系统中查找文件路径。 
load 命令会将 filepath 中的文件复制到目标文件系统中。目标文件系统由表
的位置属性决定。被复制的数据文件移动到表的数据对应的位置。 
如果没有指定 LOCAL 关键字,如果 filepath 指向的是一个完整的 URI,hive 
会直接使用这个 URI。 否则:如果没有指定 schema 或者 authority,Hive 会使
用在 hadoop 配置文件中定义的 schema 和 authority,fs.default.name 指定了 
Namenode 的 URI。  

3、 OVERWRITE  
如果使用了 OVERWRITE 关键字,则目标表(或者分区)中的内容会被删除,
然后再将 filepath 指向的文件/目录中的内容添加到表/分区中。  
如果目标表 (分区) 已经有一个文件, 并且文件名和 filepath 中的文件名冲突,
那么现有的文件会被新文件所替代
2.2. Insert 
Hive 中 insert 主要是结合 select 查询语句使用,将查询结果插入到表中,例如: 
insert overwrite table stu_buck select * from student cluster by(Sno); 
需要保证查询结果列的数目和需要插入数据表格的列数目一致. 
如果查询出来的数据类型和插入表格对应的列数据类型不一致,将会进行转换,但是不能保证转换一定成功,转换失败的数据将会为 NULL。 
可以将一个表查询出来的数据插入到原表中, 结果相当于自我复制了一份数据。 
Multi Inserts 多重插入: 
from source_table insert     overwrite     table     tablename1     [partition 
    (partcol1=val1,partclo2=val2)] select_statement1 insert     overwrite     table 
    tablename2     [partition     (partcol1=val1,partclo2=val2)] select_statement2.. 
Dynamic partition inserts 动态分区插入: 
INSERT OVERWRITE TABLE tablename PARTITION (partcol1[=val1], partcol2[=val2] ...) select_statement FROM from_statement 
动态分区是通过位置来对应分区值的。原始表 select 出来的值和输出 partition 的值的关系仅仅是通过位置来确定的,和名字并没有关系。 
 
 导出表数据语法结构 
INSERT OVERWRITE [LOCAL] DIRECTORY directory1 SELECT ... FROM ... 
multiple inserts: 
FROM from_statement 
INSERT OVERWRITE [LOCAL] DIRECTORY directory1 select_statement1 
[INSERT OVERWRITE [LOCAL] DIRECTORY directory2 select_statement2] ... 
数据写入到文件系统时进行文本序列化,且每列用^A 来区分,\n 为换行符。 
      
2.3. Select 
基本的 Select 操作语法结构 
SELECT [ALL | DISTINCT] select_expr, select_expr, ...  
FROM table_reference 
JOIN table_other ON expr 
[WHERE where_condition]  
[GROUP BY col_list [HAVING condition]]  
[CLUSTER BY col_list  
  | [DISTRIBUTE BY col_list] [SORT BY| ORDER BY col_list]  
]  
[LIMIT number] 
 
说明: 
order by 会对输入做全局排序,因此只有一个 reducer,会导致当输入规模较大时,需要较长的计算时间。 
sort by 不是全局排序,其在数据进入 reducer 前完成排序。因此,如果用 sort by 进行排序,并且设置 mapred.reduce.tasks>1,则 sort by 只保证每个 reducer 的输出有序,不保证全局有序。 
distribute by(字段)根据指定字段将数据分到不同的 reducer,分发算法是 hash 散列。 
Cluster by(字段) 除了具有 Distribute by 的功能外,还会对该字段进行排序。 
如果 distribute 和 sort 的字段是同一个时,此时,cluster by = distribute by + sort by 
     
a.分桶下载
insert overwrite local directory '/root/aaa777'
select * from student cluster by(Sno);

b.分桶下载排序

insert overwrite local directory '/root/aaa888'
select * from student DISTRIBUTE  by Sno sort by sage

c.全局排序
 
select * from student order by sage asc             
4.Hive join 
关于hive中的各种join

准备数据
1,a
2,b
3,c
4,d
7,y
8,u

2,bb
3,cc
7,yy
9,pp


+-------+---------+--+
| a.id  | a.name  |
+-------+---------+--+
| 1     | a       |
| 2     | b       |
| 3     | c       |
| 4     | d       |
| 7     | y       |
| 8     | u       |
+-------+---------+--+


+-------+---------+--+
| b.id  | b.name  |
+-------+---------+--+
| 2     | bb      |
| 3     | cc      |
| 7     | yy      |
| 9     | pp      |
+-------+---------+--+


建表:
create table a(id int,name string)
row format delimited fields terminated by ',';

create table b(id int,name string)
row format delimited fields terminated by ',';

导入数据:
load data local inpath '/root/hivedata/a.txt' into table a;
load data local inpath '/root/hivedata/b.txt' into table b;


实验:
** inner join
select * from a inner join b on a.id=b.id;
+-------+---------+-------+---------+--+
| a.id  | a.name  | b.id  | b.name  |
+-------+---------+-------+---------+--+
| 2     | b       | 2     | bb      |
| 3     | c       | 3     | cc      |
| 7     | y       | 7     | yy      |
+-------+---------+-------+---------+--+

**left join   
select * from a left join b on a.id=b.id;
+-------+---------+-------+---------+--+
| a.id  | a.name  | b.id  | b.name  |
+-------+---------+-------+---------+--+
| 1     | a       | NULL  | NULL    |
| 2     | b       | 2     | bb      |
| 3     | c       | 3     | cc      |
| 4     | d       | NULL  | NULL    |
| 7     | y       | 7     | yy      |
| 8     | u       | NULL  | NULL    |
+-------+---------+-------+---------+--+

**right join
select * from a right join b on a.id=b.id;
+-------+---------+-------+---------+--+
| a.id  | a.name  | b.id  | b.name  |
+-------+---------+-------+---------+--+
| 2     | b       | 2     | bb      |
| 3     | c       | 3     | cc      |
| 7     | y       | 7     | yy      |
| NULL  | NULL    | 9     | pp      |
+-------+---------+-------+---------+--+


**
select * from a full outer join b on a.id=b.id;
+-------+---------+-------+---------+--+
| a.id  | a.name  | b.id  | b.name  |
+-------+---------+-------+---------+--+
| 1     | a       | NULL  | NULL    |
| 2     | b       | 2     | bb      |
| 3     | c       | 3     | cc      |
| 4     | d       | NULL  | NULL    |
| 7     | y       | 7     | yy      |
| 8     | u       | NULL  | NULL    |
| NULL  | NULL    | 9     | pp      |
+-------+---------+-------+---------+--+


**hive中的特别join
select * from a left semi join b on a.id = b.id;
+-------+---------+--+
| a.id  | a.name  |
+-------+---------+--+
| 2     | b       |
| 3     | c       |
| 7     | y       |
+-------+---------+--+
相当于
select a.id,a.name from a where a.id in (select b.id from b); 在hive中效率极低

select a.id,a.name from a join b on (a.id = b.id);


cross join(##慎用)
返回两个表的笛卡尔积结果,不需要指定关联键。
select a.*,b.* from a cross join b;

Hive中除了支持和传统数据库中一样的内关联、左关联、右关联、全关联,还支持 LEFT SEMI JOIN 和 CROSS JOIN,但这两种JOIN类型也可以用前面的代替。 
Hive 支持等值连接(a.id = b.id),不支持非等值(a.id>b.id)的连接,因为非等值连接非常难转化到 map/reduce 任务。另外,Hive 支持多 2 个以上表之间的join。 
写 join 查询时,需要注意几个关键点:    
join 时,每次 map/reduce 任务的逻辑: reducer 会缓存 join 序列中除了最后一个表的所有表的记录,再通过最后一个表将结果序列化到文件系统。这一实现有助于在 reduce 端减少内存的使用量。实践中,应该把最大的那个表写在最后(否则会因为缓存浪费大量内存)。 
LEFT,RIGHT 和 FULL OUTER 关键字用于处理 join 中空记录的情况 
  SELECT a.val, b.val FROM a LEFT OUTER  JOIN b ON (a.key=b.key) 
对应所有 a 表中的记录都有一条记录输出。输出的结果应该是 a.val, b.val,当 
a.key=b.key 时,而当 b.key 中找不到等值的 a.key 记录时也会输出: 
a.val, NULL 
所以 a 表中的所有记录都被保留了; 
“a RIGHT OUTER JOIN b”会保留所有 b 表的记录。 
 
Join 发生在 WHERE 子句之前。如果你想限制 join 的输出,应该在 WHERE 子句中写过滤条件——或是在 join 子句中写。这里面一个容易混淆的问题是表分区的情况: 
  SELECT a.val, b.val FROM a 
  LEFT OUTER JOIN b ON (a.key=b.key) 
  WHERE a.ds='2009-07-07' AND b.ds='2009-07-07' 
这会 join a 表到 b 表(OUTER JOIN),列出 a.val 和 b.val 的记录。WHERE 从句
中可以使用其他列作为过滤条件。但是,如前所述,如果 b 表中找不到对应 a 表的记录,b 表的所有列都会列出 NULL,包括 ds 列。也就是说,join 会过滤 b 表中不能找到匹配 a 表 join key 的所有记录。这样的话,LEFT OUTER 就使得查询结果与 WHERE 子句无关了。解决的办法是在 OUTER JOIN 时使用以下语法: 
  SELECT a.val, b.val FROM a LEFT OUTER JOIN b 
  ON (a.key=b.key AND 
      b.ds='2009-07-07' AND 
      a.ds='2009-07-07') 
这一查询的结果是预先在 join 阶段过滤过的,所以不会存在上述问题。这一逻辑也
可以应用于 RIGHT 和 FULL 类型的 join 中。 
 
 Join 是不能交换位置的。无论是 LEFT 还是 RIGHT join,都是左连接的。 
  SELECT a.val1, a.val2, b.val, c.val 
  FROM a 
  JOIN b ON (a.key = b.key) 
  LEFT OUTER JOIN c ON (a.key = c.key) 
先 join a 表到 b 表,丢弃掉所有 join key 中不匹配的记录,然后用这一中间结果和 c 表做 join。 
5.导出数据
导出表数据 
语法结构 
INSERT OVERWRITE [LOCAL] DIRECTORY directory1 SELECT ... FROM ... 

multiple inserts: 
FROM from_statement 
INSERT OVERWRITE [LOCAL] DIRECTORY directory1 select_statement1 
[INSERT OVERWRITE [LOCAL] DIRECTORY directory2 select_statement2] ... 
数据写入到文件系统时进行文本序列化,且每列用Â 来区分,\n 为换行符

将查询结果保存到指定的文件目录(可以是本地,也可以是hdfs)
insert overwrite local directory '/home/hadoop/test'
select * from t_p;

insert overwrite directory '/aaa/test'
select * from t_p;

insert overwrite directory '/data5' select * from t88;


6.from t001 insert overwrite local directory '/root/aaa' select day  insert overwrite local directory '/root/bbb' select ip ;

六、 Hive 参数配置 
1. Hive 命令行 
输入$HIVE_HOME/bin/hive –H 或者 –help 可以显示帮助选项:说明: 
1、-i  初始化HQL文件。 
2、-e从命令行执行指定的 HQL  
3、-f 执行 HQL 脚本  
4、-v 输出执行的 HQL 语句到控制台  
5、-p <port> connect to Hive Server on port number  
6、-hiveconf x=y Use this to set hive/hadoop configuration variables. 
 
例如: 
$HIVE_HOME/bin/hive -e 'select * from  a'      
$HIVE_HOME/bin/hive -f /home/my/hive-script.sql 
$HIVE_HOME/bin/hive -f hdfs://<namenode>:<port>/hive-script.sql 
$HIVE_HOME/bin/hive -i /home/my/hive-init.sql 
$HIVE_HOME/bin/hive -e 'select a.col from  a' 
     --hiveconf hive.exec.compress.output=true  
     --hiveconf mapred.reduce.tasks=32 
2.Hive 参数配置方式 
配置文件   (全局有效) 
命令行参数   (对 hive 启动实例有效) 
参数声明   (对 hive 的连接 session 有效)
Hive 参数大全: 
https://cwiki.apache.org/confluence/display/Hive/Configuration+Properties 
 
开发 Hive 应用时,不可避免地需要设定 Hive 的参数。设定 Hive 的参数可以调优 HQL 代码的执行效率,或帮助定位问题。然而实践中经常遇到的一个问题是,为什么设定的参数没有起作用?这通常是错误的设定方式导致的。 
对于一般参数,有以下三种设定方式:配置文件   (全局有效)命令行参数   (对 hive 启动实例有效)参数声明   (对 hive 的连接 session 有效)配置文件  
用户自定义配置文件:$HIVE_CONF_DIR/hive-site.xml 默认配置文件:$HIVE_CONF_DIR/hive-default.xml  
用户自定义配置会覆盖默认配置。另外,Hive 也会读入 Hadoop 的配置,因为 Hive 是作为 Hadoop 的客户端启动的,
Hive 的配置会覆盖 Hadoop 的配置。 
配置文件的设定对本机启动的所有 Hive 进程都有效。 
 
命令行参数启动 Hive(客户端或 Server 方式)时,可以在命令行添加-hiveconf 来设定参数
         例如:bin/hive -hiveconf hive.root.logger=INFO,console 
设定对本次启动的 Session(对于 Server 方式启动,则是所有请求的 Sessions)有效。 
 
参数声明可以在 HQL 中使用 SET 关键字设定参数,这一设定的作用域也是 session 级的。比如: 
set hive.exec.reducers.bytes.per.reducer=<number>  每个 reduce task 的平均负载数据量 set hive.exec.reducers.max=<number>   设置 reduce task 数量的上限 
set mapreduce.job.reduces=<number>    指定固定的 reduce task 数量 
但是,这个参数在必要时<业务逻辑决定只能用一个 reduce task> hive 会忽略 
上述三种设定方式的优先级依次递增。即参数声明覆盖命令行参数,命令行参数覆盖配置文件设定。注意某些系统级的参数,例如 log4j 相关的设定,必须用前两种方式设定,因为那些参数的读取在 Session 建立以前已经完成了。 
      
七、 Hive 函数 
1. 内置运算符 
在 Hive 有四种类型的运算符: 
•关系运算符 
•算术运算符 
•逻辑运算符 
•复杂运算内容较多,见《Hive 官方文档》或者《hive 常用运算和函数.doc》 
2. 内置函数 
https://cwiki.apache.org/confluence/display/Hive/LanguageManual+UDF 
测试各种内置函数的快捷方法: 
创建一个dual表 
create table dual(id string); 
load一个文件(只有一行内容:内容为一个空格)到dual表 
select substr('angelababy',2,3) from dual; 
 
内容较多,见《Hive 官方文档》或者《hive 常用运算和函数.doc》 
 
3.Hive 自定义函数 
1.依赖坐标
<dependencies>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-common</artifactId>
            <version>2.7.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hive</groupId>
            <artifactId>hive-exec</artifactId>
            <version>1.2.1</version>
            <exclusions>
                <exclusion>
                    <groupId>org.pentaho</groupId>
                    <artifactId>pentaho-aggdesigner-algorithm</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
  
  <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.4</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                            <mainClass></mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>utf-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
2.代码
package cn.ali;

import org.apache.hadoop.hive.ql.exec.UDF;
import org.apache.hadoop.io.Text;

public class MyUDF extends UDF{
    public Text evaluate(Text text){
        text.set(text.toString().toUpperCase());
        return text;
    }
}

3.打包
2、打成jar包上传到服务器 

3、将jar包添加到hive的classpath 
hive>add JAR /home/hadoop/udf.jar; 
4.使用

4、创建临时函数与开发好的java class关联 
    create     temporary     function myReverse     as  'cn.krist.bigdata.udf.ToProvince';     

5、即可在hql中使用自定义的函数tolowercase ip  
    Select tolowercase(name),age from t_test; 

当 Hive 提供的内置函数无法满足你的业务处理需要时,此时就可以考虑使用用户自定
义函数(UDF:user-defined function)。 
新建JAVA maven项目  
添加 hive-exec-1.2.1.jar和hadoop-common-2.7.4.jar依赖(见参考资料) 
1、写一个java类,继承UDF,并重载evaluate方法 
package cn.krist.bigdata.udf import org.apache.hadoop.hive.ql.exec.UDF; import org.apache.hadoop.io.Text; 
 public class Lower extends UDF{      public Text evaluate(Text s){           if(s==null){return null;}           return new Text(s.toString().toLowerCase()); 
     } 

2、打成jar包上传到服务器 
3、将jar包添加到hive的classpath 
hive>add JAR /home/hadoop/udf.jar; 
4、创建临时函数与开发好的java class关联 
    create     temporary     function 
'cn.krist.bigdata.udf.ToProvince';     tolowercase     as 
5、即可在hql中使用自定义的函数tolowercase ip  
Select tolowercase(name),age from t_test; 
      
5.Hive 特殊分隔符处理(扩展) 
1.原理
hive 读取数据的机制: 
首先用InputFormat<默认是:org.apache.hadoop.mapred.TextInputFormat >的一个具体实现类读入文件数据,返回一条一条的记录(可以是行,或者是你逻辑中的“行”) 
然后利用SerDe<默认:org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe> 的一个具体实现类,对上面返回的一条一条的记录进行字段切割。
2.操作
Hive对文件中字段的分隔符默认情况下只支持单字节分隔符,如果数据文件中的分隔符是多字符的,如下所示: 
01||zhangsan 
02||lisi 
可用使用 RegexSerDe 通过正则表达式来抽取字段 
 
drop table t_bi_reg; 
create table t_bi_reg(id string,name string) row format serde 'org.apache.hadoop.hive.serde2.RegexSerDe' with serdeproperties( 
'input.regex'='(.*)\\|\\|(.*)', 
'output.format.string'='%1$s %2$s' 

stored as textfile; 
 
hive>load data local inpath '/root/hivedata/bi.dat' into table t_bi_reg; 
hive>select * from t_bi_reg; 

其中: 
input.regex:输入的正则表达式  
 表示 || 左右两边任意字符被抽取为一个字段 output.format.string:输出的正则表达式 
 %1$s  %2$s 则分别表示表中的第一个字段、第二个地段注意事项: 
a、使用RegexSerDe类时,所有的字段必须为string 
b、input.regex里面,以一个匹配组,表示一个字段 
hive 读取数据的机制: 
首先用InputFormat<默认是:org.apache.hadoop.mapred.TextInputFormat >的一个具体实现类读入文件数据,返回一条一条的记录(可以是行,或者是你逻辑中的“行”) 
然后利用SerDe<默认:org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe> 的一个具体实现类,对上面返回的一条一条的记录进行字段切割。 
 
Hive对文件中字段的分隔符默认情况下只支持单字节分隔符,如果数据文件中的分隔符是多字符的,如下所示: 
01||zhangsan 
02||lisi 
可用使用 RegexSerDe 通过正则表达式来抽取字段 
 
drop table t_bi_reg; 
create table t_bi_reg(id string,name string) row format serde 'org.apache.hadoop.hive.serde2.RegexSerDe' with serdeproperties( 
'input.regex'='(.*)\\|\\|(.*)', 
'output.format.string'='%1$s %2$s' 

stored as textfile; 
 
hive>load data local inpath '/root/hivedata/bi.dat' into table t_bi_reg; hive>select * from t_bi_reg; 
其中: 
input.regex:输入的正则表达式  
 表示 || 左右两边任意字符被抽取为一个字段 output.format.string:输出的正则表达式 
 %1$s  %2$s 则分别表示表中的第一个字段、第二个地段注意事项: 
a、使用RegexSerDe类时,所有的字段必须为string 
b、input.regex里面,以一个匹配组,表示一个字段 
八、级联求和
create table t_access_times(username string,month string,salary int) row format delimited fields terminated by ',';

load data local inpath '/home/hadoop/t_access_times.dat' into table t_access_times;

A,2015-01,5
A,2015-01,15
B,2015-01,5
A,2015-01,8
B,2015-01,25
A,2015-01,5
A,2015-02,4
A,2015-02,6
B,2015-02,10
B,2015-02,5


1、第一步,先求个用户的月总金额
select username,month,sum(salary) as salary from t_access_times group by username,month
            tmp
+-----------+----------+---------+--+
| username  |  month   | salary  |
+-----------+----------+---------+--+
| A         | 2015-01  | 33      |
| A         | 2015-02  | 10      |
| B         | 2015-01  | 30      |
| B         | 2015-02  | 15      |
+-----------+----------+---------+--+

2、第二步,将月总金额表 自己连接 自己
select A.*,B.* FROM
(select username,month,sum(salary) as salary from t_access_times group by username,month) A 
inner join 
(select username,month,sum(salary) as salary from t_access_times group by username,month) B on A.username=B.username
where B.month <= A.month

+-------------+----------+-----------+-------------+----------+-----------+--+
| a.username  | a.month  | a.salary  | b.username  | b.month  | b.salary  |
+-------------+----------+-----------+-------------+----------+-----------+--+
| A           | 2015-01  | 33        | A           | 2015-01  | 33        |
| A           | 2015-02  | 10        | A           | 2015-01  | 33        |
| A           | 2015-02  | 10        | A           | 2015-02  | 10        |
| B           | 2015-01  | 30        | B           | 2015-01  | 30        |
| B           | 2015-02  | 15        | B           | 2015-01  | 30        |
| B           | 2015-02  | 15        | B           | 2015-02  | 15        |
+-------------+----------+-----------+-------------+----------+-----------+--+


3、第三步,从上一步的结果中
进行分组查询,分组的字段是a.username a.month
求月累计值:  将b.month <= a.month的所有b.salary求和即可
select A.username,A.month,max(A.salary) as salary,sum(B.salary) as accumulate 
 from  
(select username,month,sum(salary) as salary from t_access_times group by username,month) A  
inner join  
(select username,month,sum(salary) as salary from t_access_times group by username,month) B 
 on 
A.username=B.username 
where B.month <= A.month 
group by A.username,A.month 
order by A.username,A.month;

+-------------+----------+---------+-------------+--+
| a.username  | a.month  | salary  | accumulate  |
+-------------+----------+---------+-------------+--+
| A           | 2015-01  | 33      | 33          |
| A           | 2015-02  | 10      | 43          |
| B           | 2015-01  | 30      | 30          |
| B           | 2015-02  | 15      | 45          |
+-------------+----------+---------+-------------+--+


九、制作宽表
drop table if exists ods_weblog_origin;
create table ods_weblog_origin(
valid string,                        --有效标识
remote_addr string,                    --来源IP
remote_user string,                    --用户标识
time_local string,                    --访问完整时间
request string,                        --请求的url
status string,                        --响应码
body_bytes_sent string,                --传输字节数
http_referer string,                --来源url
http_user_agent string)                --客户终端标识
partitioned by (datestr string)
row format delimited
fields terminated by '\001';


建表——明细宽表 ods_weblog_detail

drop table ods_weblog_detail;
create table ods_weblog_detail(
valid           string, --有效标识
remote_addr     string, --来源IP
remote_user     string, --用户标识
time_local      string, --访问完整时间
daystr          string, --访问日期
timestr         string, --访问时间
month           string, --访问月
day             string, --访问日
hour            string, --访问时
request         string, --请求的url
status          string, --响应码
body_bytes_sent string, --传输字节数
http_referer    string, --来源url
ref_host        string, --来源的host
ref_path        string, --来源的路径
ref_query       string, --来源参数query
ref_query_id    string, --来源参数query的值
http_user_agent string --客户终端标识
)
partitioned by(datestr string);
-------------------------------------------------------------------------------
通过查询插入数据到明细宽表  ods_weblog_detail中


分步:
--抽取refer_url到中间表  t_ods_tmp_referurl 
--也就是将来访url分离出host  path  query  query id

drop table if exists t_ods_tmp_referurl;
create table t_ods_tmp_referurl as
SELECT a.*,b.*
FROM ods_weblog_origin a 
LATERAL VIEW parse_url_tuple(regexp_replace(http_referer, "\"", ""), 'HOST', 'PATH','QUERY', 'QUERY:id') b as host, path, query, query_id; 

--抽取转换time_local字段到中间表明细表 t_ods_tmp_detail

drop table if exists t_ods_tmp_detail;
create table t_ods_tmp_detail as 
select b.*,
substring(time_local,1,10) as daystr,
substring(time_local,12) as timestr,
substring(time_local,6,2) as month,
substring(time_local,9,2) as day,
substring(time_local,12,2) as hour
From t_ods_tmp_referurl b;

以上语句可以改写成:
insert overwrite  table ods_weblog_detail partition(datestr='20130918')
select c.valid,c.remote_addr,c.remote_user,c.time_local,
substring(c.time_local,1,10) as daystr,
substring(c.time_local,12) as timestr,
substring(c.time_local,6,2) as month,
substring(c.time_local,9,2) as day,
substring(c.time_local,12,2) as hour,
c.request,c.status,c.body_bytes_sent,c.http_referer,c.ref_host,c.ref_path,c.ref_query,c.ref_query_id,c.http_user_agent
from
(SELECT 
a.valid,a.remote_addr,a.remote_user,a.time_local,
a.request,a.status,a.body_bytes_sent,a.http_referer,a.http_user_agent,b.ref_host,b.ref_path,b.ref_query,b.ref_query_id 
FROM ods_weblog_origin a LATERAL VIEW parse_url_tuple(regexp_replace(http_referer, "\"", ""), 'HOST', 'PATH','QUERY', 'QUERY:id') b as ref_host, ref_path, ref_query, ref_query_id) c;


show partitions ods_weblog_detail;

十、hive 常用运算
1.第一部分:关系运算
Hive支持的关系运算符
•常见的关系运算符
•等值比较: =
•不等值比较: <>
•小于比较: <
•小于等于比较: <=
•大于比较: >
•大于等于比较: >=
•空值判断: IS NULL
•非空判断: IS NOT NULL
•LIKE比较: LIKE
•JAVA的LIKE操作: RLIKE
•REGEXP操作: REGEXP
•等值比较: =
   语法:A=B
操作类型:所有基本类型
描述: 如果表达式A与表达式B相等,则为TRUE;否则为FALSE
举例:
hive> select 1 from dual where 1=1;
•不等值比较: <>
语法: A <> B
操作类型: 所有基本类型
描述: 如果表达式A为NULL,或者表达式B为NULL,返回NULL;如果表达式A与表达式B不相等,则为TRUE;否则为FALSE
举例:
hive> select 1 from dual where 1 <> 2;
•小于比较: <
语法: A < B
操作类型: 所有基本类型
描述: 如果表达式A为NULL,或者表达式B为NULL,返回NULL;如果表达式A小于表达式B,则为TRUE;否则为FALSE
举例:
hive> select 1 from dual where 1 < 2;
•小于等于比较: <=
语法: A <= B
操作类型: 所有基本类型
描述: 如果表达式A为NULL,或者表达式B为NULL,返回NULL;如果表达式A小于或者等于表达式B,则为TRUE;否则为FALSE
举例:
hive> select 1 from dual where 1 <= 1;
•大于等于比较: >=
语法: A >= B
操作类型: 所有基本类型
描述: 如果表达式A为NULL,或者表达式B为NULL,返回NULL;如果表达式A大于或者等于表达式B,则为TRUE;否则为FALSE
举例:
hive> select 1 from dual where 1 >= 1;
•空值判断: IS NULL
语法: A IS NULL
操作类型: 所有类型
描述: 如果表达式A的值为NULL,则为TRUE;否则为FALSE
举例:
hive> select 1 from dual where null is null;
 

•非空判断: IS NOT NULL
语法: A IS NOT NULL
操作类型: 所有类型
描述: 如果表达式A的值为NULL,则为FALSE;否则为TRUE
举例:
hive> select 1 from dual where 1 is not null;
 

•LIKE比较: LIKE
语法: A LIKE B
操作类型: strings
描述: 如果字符串A或者字符串B为NULL,则返回NULL;如果字符串A符合表达式B   的正则语法,则为TRUE;否则为FALSE。B中字符”_”表示任意单个字符,而字符”%”表示任意数量的字符。
举例:
hive> select 1 from dual where ‘key' like 'foot%';
1
hive> select 1 from dual where ‘key ' like 'foot____';
1
注意:否定比较时候用 NOT A LIKE B
hive> select 1 from dual where NOT ‘key ' like 'fff%';
•JAVA的LIKE操作: RLIKE
语法: A RLIKE B
操作类型: strings
描述: 如果字符串A或者字符串B为NULL,则返回NULL;如果字符串A符合JAVA正则表达式B的正则语法,则为TRUE;否则为FALSE。
举例:
hive> select 1 from dual where 'footbar’ rlike '^f.*r$’;
1
注意:判断一个字符串是否全为数字:
hive>select 1 from dual where '123456' rlike '^\\d+$';
1
hive> select 1 from dual where '123456aa' rlike '^\\d+$';
1
•REGEXP操作: REGEXP
语法: A REGEXP B
操作类型: strings
描述: 功能与RLIKE相同
举例:
hive> select 1 from dual where ‘key' REGEXP '^f.*r$';
1
 

2.第二部分:逻辑运算与数学运算
Hive数学运算
•加法操作: +
•减法操作: -
•乘法操作: *
•除法操作: /
•取余操作: %
•位与操作: &
•位或操作: |
•位异或操作: ^
•位取反操作: ~
•加法操作: +
语法: A + B
操作类型:所有数值类型
说明:返回A与B相加的结果。结果的数值类型等于A的类型和B的类型的最小父类型(详见数据类型的继承关系)。比如,int + int 一般结果为int类型,而int + double 一般结果为double类型
举例:
hive> select 1 + 9 from dual;
10
•减法操作: -
语法: A – B
操作类型:所有数值类型
说明:返回A与B相减的结果。结果的数值类型等于A的类型和B的类型的最小父类型(详见数据类型的继承关系)。比如,int – int 一般结果为int类型,而int – double 一般结果为double类型
举例:
hive> select 10 – 5 from dual;
5
 

• 乘法操作 : *
语法: A * B
操作类型:所有数值类型
说明:返回A与B相乘的结果。结果的数值类型等于A的类型和B的类型的最小父类型(详见数据类型的继承关系)。注意,如果A乘以B的结果超过默认结果类型的数值范围,则需要通过cast将结果转换成范围更大的数值类型
举例:
hive> select 40 * 5 from dual;
200
• 除法操作 : /
语法: A / B
操作类型:所有数值类型
说明:返回A除以B的结果。结果的数值类型为double
举例:
hive> select 40 / 5 from dual;
8.0
 
注意: hive 中最高精度的数据类型是 double, 只精确到小数点后 16 位,在做除法运算的时候要 特别注意
hive>select ceil(28.0/6.999999999999999999999) from dual limit 1;   
结果为4
hive>select ceil(28.0/6.99999999999999) from dual limit 1;          
结果为5
 
• 取余操作 : %
语法: A % B
操作类型:所有数值类型
说明:返回A除以B的余数。结果的数值类型等于A的类型和B的类型的最小父类型(详见数据类型的继承关系)。
举例:
hive> select 41 % 5 from dual;
1
hive> select 8.4 % 4 from dual;
0.40000000000000036
注意:精度在 hive 中是个很大的问题,类似这样的操作最好通过 round 指定精度
hive> select round(8.4 % 4 , 2) from dual;
0.4
 
• 位与操作 : &
语法: A & B
操作类型:所有数值类型
说明:返回A和B按位进行与操作的结果。结果的数值类型等于A的类型和B的类型的最小父类型(详见数据类型的继承关系)。
举例:
hive> select 4 & 8 from dual;
0
hive> select 6 & 4 from dual;
4
• 位或操作 : |
语法: A | B
操作类型:所有数值类型
说明:返回A和B按位进行或操作的结果。结果的数值类型等于A的类型和B的类型的最小父类型(详见数据类型的继承关系)。
举例:
hive> select 4 | 8 from dual;
12
hive> select 6 | 8 from dual;
14
• 位异或操作 : ^
语法: A ^ B
操作类型:所有数值类型
说明:返回A和B按位进行异或操作的结果。结果的数值类型等于A的类型和B的类型的最小父类型(详见数据类型的继承关系)。
举例:
hive> select 4 ^ 8 from dual;
12
hive> select 6 ^ 4 from dual;
2
• 位取反操作 : ~
语法: ~A
操作类型:所有数值类型
说明:返回A按位取反操作的结果。结果的数值类型等于A的类型。
举例:
hive> select ~6 from dual;
-7
hive> select ~4 from dual;
-5
Hive逻辑运算
•逻辑与操作: AND
•逻辑或操作: OR
•逻辑非操作: NOT
 
• 逻辑与操作 : AND
语法: A AND B
操作类型:boolean
说明:如果A和B均为TRUE,则为TRUE;否则为FALSE。如果A为NULL或B为NULL,则为NULL
举例:
hive> select 1 from dual where 1=1 and 2=2;
1
• 逻辑或操作 : OR
语法: A OR B
操作类型:boolean
说明:如果A为TRUE,或者B为TRUE,或者A和B均为TRUE,则为TRUE;否则为FALSE
举例:
hive> select 1 from dual where 1=2 or 2=2;
1
• 逻辑非操作 : NOT
语法: NOT A
操作类型:boolean
说明:如果A为FALSE,或者A为NULL,则为TRUE;否则为FALSE
举例:
hive> select 1 from dual where not 1=2;
• 逻辑非操作 : NOT
语法: NOT A
操作类型:boolean
说明:如果A为FALSE,或者A为NULL,则为TRUE;否则为FALSE
举例:
hive> select 1 from dual where  not 1=2 ;
 
3.第三部分:数值运算
•取整函数: round
•指定精度取整函数: round
•向下取整函数: floor
•向上取整函数: ceil
•向上取整函数: ceiling
•取随机数函数: rand
•自然指数函数: exp
•以10为底对数函数: log10
•以2为底对数函数: log2
• 对数函数: log
•幂运算函数: pow
•幂运算函数: power
•开平方函数: sqrt
•二进制函数: bin
•十六进制函数: hex
•反转十六进制函数: unhex
•进制转换函数: conv
•绝对值函数: abs
•正取余函数: pmod
•正弦函数: sin
•反正弦函数: asin
•余弦函数: cos
•反余弦函数: acos
•positive函数: positive
•negative函数: negative
• 取整函数 : round
语法: round(double a)
返回值: BIGINT
说明: 返回double类型的整数值部分 (遵循四舍五入)
举例:
hive> select round(3.1415926) from dual;
3
hive> select round(3.5) from dual;
4
hive> create table dual as select round(9542.158) from dual;
hive> describe dual;
_c0     bigint
 
• 指定精度取整函数 : round
语法: round(double a, int d)
返回值: DOUBLE
说明: 返回指定精度d的double类型
举例:
hive> select round(3.1415926,4) from dual;
3.1416
 
• 向下取整函数 : floor
语法: floor(double a)
返回值: BIGINT
说明: 返回等于或者小于该double变量的最大的整数
举例:
hive> select floor(3.1415926) from dual;
3
hive> select floor(25) from dual;
25
 
• 向上取整函数 : ceil
语法: ceil(double a)
返回值: BIGINT
说明: 返回等于或者大于该double变量的最小的整数
举例:
hive> select ceil(3.1415926) from dual;
4
hive> select ceil(46) from dual;
46
• 向上取整函数 : ceiling
语法: ceiling(double a)
返回值: BIGINT
说明: 与ceil功能相同
举例:
hive> select ceiling(3.1415926) from dual;
4
hive> select ceiling(46) from dual;
46
 
• 取随机数函数 : rand
语法: rand(),rand(int seed)
返回值: double
说明: 返回一个0到1范围内的随机数。如果指定种子seed,则会等到一个稳定的随机数序列
举例:
hive> select rand() from dual;
0.5577432776034763
 
• 自然指数函数 : exp
语法: exp(double a)
返回值: double
说明: 返回自然对数e的a次方
举例:
hive> select exp(2) from dual;
7.38905609893065
自然对数函数: ln
语法: ln(double a)
返回值: double
说明: 返回a的自然对数
• 以 10 为底对数函数 : log10
语法: log10(double a)
返回值: double
说明: 返回以10为底的a的对数
举例:
hive> select log10(100) from dual;
2.0
• 以 2 为底对数函数 : log2
语法: log2(double a)
返回值: double
说明: 返回以2为底的a的对数
举例:
hive> select log2(8) from dual;
3.0
• 对数函数 : log
语法: log(double base, double a)
返回值: double
说明: 返回以base为底的a的对数
举例:
hive> select log(4,256) from dual;
4.0
• 幂运算函数 : pow
语法: pow(double a, double p)
返回值: double
说明: 返回a的p次幂
举例:
hive> select pow(2,4) from dual;
16.0
• 幂运算函数 : power
语法: power(double a, double p)
返回值: double
说明: 返回a的p次幂,与pow功能相同
举例:
hive> select power(2,4) from dual;
16.0
• 开平方函数 : sqrt
语法: sqrt(double a)
返回值: double
说明: 返回a的平方根
举例:
hive> select sqrt(16) from dual;
4.0
• 二进制函数 : bin
语法: bin(BIGINT a)
返回值: string
说明: 返回a的二进制代码表示
举例:
hive> select bin(7) from dual;
111
• 十六进制函数 : hex
语法: hex(BIGINT a)
返回值: string
说明: 如果变量是int类型,那么返回a的十六进制表示;如果变量是string类型,则返回该字符串的十六进制表示
举例:
hive> select hex(17) from dual;
11
hive> select hex(‘abc’) from dual;
616263
• 反转十六进制函数 : unhex
语法: unhex(string a)
返回值: string
说明: 返回该十六进制字符串所代码的字符串
举例:
hive> select unhex(‘616263’) from dual;
abc
hive> select unhex(‘11’) from dual;
-
hive> select unhex(616263) from dual;
abc
• 进制转换函数 : conv
语法: conv(BIGINT num, int from_base, int to_base)
返回值: string
说明: 将数值num从from_base进制转化到to_base进制
举例:
hive> select conv(17,10,16) from dual;
11
hive> select conv(17,10,2) from dual;
10001
• 绝对值函数 : abs
语法: abs(double a)   abs(int a)
返回值: double        int
说明: 返回数值a的绝对值
举例:
hive> select abs(-3.9) from dual;
3.9
hive> select abs(10.9) from dual;
10.9
• 正取余函数 : pmod
语法: pmod(int a, int b),pmod(double a, double b)
返回值: int double
说明: 返回正的a除以b的余数
举例:
hive> select pmod(9,4) from dual;
1
hive> select pmod(-9,4) from dual;
3
• 正弦函数 : sin
语法: sin(double a)
返回值: double
说明: 返回a的正弦值
举例:
hive> select sin(0.8) from dual;
0.7173560908995228
• 反正弦函数 : asin
语法: asin(double a)
返回值: double
说明: 返回a的反正弦值
举例:
hive> select asin(0.7173560908995228) from dual;
0.8
• 余弦函数 : cos
语法: cos(double a)
返回值: double
说明: 返回a的余弦值
举例:
hive> select cos(0.9) from dual;
0.6216099682706644
• 反余弦函数 : acos
语法: acos(double a)
返回值: double
说明: 返回a的反余弦值
举例:
hive> select acos(0.6216099682706644) from dual;
0.9
• positive 函数 : positive
语法: positive(int a), positive(double a)
返回值: int double
说明: 返回a
举例:
hive> select positive(-10) from dual;
-10
hive> select positive(12) from dual;
12
• negative 函数 : negative
语法: negative(int a), negative(double a)
返回值: int double
说明: 返回-a
举例:
hive> select negative(-5) from dual;
5
hive> select negative(8) from dual;
-8
 
4.第四部分:日期函数
•UNIX时间戳转日期函数: from_unixtime
• 获取当前UNIX时间戳函数: unix_timestamp
•日期转UNIX时间戳函数: unix_timestamp
• 指定格式日期转UNIX时间戳函数: unix_timestamp
•日期时间转日期函数: to_date
•日期转年函数: year
• 日期转月函数: month
• 日期转天函数: day
• 日期转小时函数: hour
• 日期转分钟函数: minute
• 日期转秒函数: second
• 日期转周函数: weekofyear
• 日期比较函数: datediff
• 日期增加函数: date_add
• 日期减少函数: date_sub
• UNIX 时间戳转日期函数 : from_unixtime
语法: from_unixtime(bigint unixtime[, string format])
返回值: string
说明: 转化UNIX时间戳(从1970-01-01 00:00:00 UTC到指定时间的秒数)到当前时区的时间格式
举例:
hive> select from_unixtime(1323308943,'yyyyMMdd') from dual;
20111208
• 获取当前 UNIX 时间戳函数 : unix_timestamp
语法: unix_timestamp()
返回值: bigint
说明: 获得当前时区的UNIX时间戳
举例:
hive> select unix_timestamp() from dual;
1323309615
• 日期转 UNIX 时间戳函数 : unix_timestamp
语法: unix_timestamp(string date)
返回值: bigint
说明: 转换格式为"yyyy-MM-dd HH:mm:ss"的日期到UNIX时间戳。如果转化失败,则返回0。
举例:
hive> select unix_timestamp('2011-12-07 13:01:03') from dual;
1323234063
• 指定格式日期转 UNIX 时间戳函数 : unix_timestamp
语法: unix_timestamp(string date, string pattern)
返回值: bigint
说明: 转换pattern格式的日期到UNIX时间戳。如果转化失败,则返回0。
举例:
hive> select unix_timestamp('20111207 13:01:03','yyyyMMdd HH:mm:ss') from dual;
1323234063
• 日期时间转日期函数 : to_date
语法: to_date(string timestamp)
返回值: string
说明: 返回日期时间字段中的日期部分。
举例:
hive> select to_date('2011-12-08 10:03:01') from dual;
2011-12-08
• 日期转年函数 : year
语法: year(string date)
返回值: int
说明: 返回日期中的年。
举例:
hive> select year('2011-12-08 10:03:01') from dual;
2011
hive> select year('2012-12-08') from dual;
2012
• 日期转月函数 : month
语法: month (string date)
返回值: int
说明: 返回日期中的月份。
举例:
hive> select month('2011-12-08 10:03:01') from dual;
12
hive> select month('2011-08-08') from dual;
8
• 日期转天函数 : day
语法: day (string date)
返回值: int
说明: 返回日期中的天。
举例:
hive> select day('2011-12-08 10:03:01') from dual;
8
hive> select day('2011-12-24') from dual;
24
• 日期转小时函数 : hour
语法: hour (string date)
返回值: int
说明: 返回日期中的小时。
举例:
hive> select hour('2011-12-08 10:03:01') from dual;
10
• 日期转分钟函数 : minute
语法: minute (string date)
返回值: int
说明: 返回日期中的分钟。
举例:
hive> select minute('2011-12-08 10:03:01') from dual;
3
• 日期转秒函数 : second
语法: second (string date)
返回值: int
说明: 返回日期中的秒。
举例:
hive> select second('2011-12-08 10:03:01') from dual;
1
 
• 日期转周函数 : weekofyear
语法: weekofyear (string date)
返回值: int
说明: 返回日期在当前的周数。
举例:
hive> select weekofyear('2011-12-08 10:03:01') from dual;
49
 
• 日期比较函数 : datediff
语法: datediff(string enddate, string startdate)
返回值: int
说明: 返回结束日期减去开始日期的天数。
举例:
hive> select datediff('2012-12-08','2012-05-09') from dual;
213
• 日期增加函数 : date_add
语法: date_add(string startdate, int days)
返回值: string
说明: 返回开始日期startdate增加days天后的日期。
举例:
hive> select date_add('2012-12-08',10) from dual;
2012-12-18
 
• 日期减少函数 : date_sub
语法: date_sub (string startdate, int days)
返回值: string
说明: 返回开始日期startdate减少days天后的日期。
举例:
hive> select date_sub('2012-12-08',10) from dual;
2012-11-28
 
5.第五部分:条件函数
•If函数: if
•非空查找函数: COALESCE
•条件判断函数:CASE
• If 函数 : if
语法: if(boolean testCondition, T valueTrue, T valueFalseOrNull)
返回值: T
说明:  当条件testCondition为TRUE时,返回valueTrue;否则返回valueFalseOrNull
举例:
hive> select if(1=2,100,200) from dual;
200
hive> select if(1=1,100,200) from dual;
100
• 非空查找函数 : COALESCE
语法: COALESCE(T v1, T v2, …)
返回值: T
说明:  返回参数中的第一个非空值;如果所有值都为NULL,那么返回NULL
举例:
hive> select COALESCE(null,'100','50′) from dual;
100
条件判断函数: CASE
语法 : CASE a WHEN b THEN c [WHEN d THEN e]* [ELSE f] END
返回值 : T
说明:如果 a 等于 b ,那么返回 c ;如果 a 等于 d ,那么返回 e ;否则返回 f
举例:
hive> Select case 100 when 50 then 'tom' when 100 then 'mary' else 'tim' end from dual;
mary
 
 
6.第六部分:字符串函数
•字符串长度函数:length
•字符串反转函数:reverse
•字符串连接函数:concat
• 带分隔符字符串连接函数:concat_ws
• 字符串截取函数:substr,substring
• 字符串截取函数:substr,substring
• 字符串转大写函数:upper,ucase
• 字符串转小写函数:lower,lcase
• 去空格函数:trim
• 左边去空格函数:ltrim
• 右边去空格函数:rtrim
•正则表达式替换函数:regexp_replace
•正则表达式解析函数:regexp_extract
•URL解析函数:parse_url
•json解析函数:get_json_object
•空格字符串函数:space
•重复字符串函数:repeat
•首字符ascii函数:ascii
•左补足函数:lpad
•右补足函数:rpad
•分割字符串函数: split
•集合查找函数: find_in_set
• 字符串长度函数: length
语法: length(string A)
返回值: int
说明:返回字符串A的长度
举例:
hive> select length('abcedfg') from dual;
7
• 字符串反转函数: reverse
语法: reverse(string A)
返回值: string
说明:返回字符串A的反转结果999999举例:
hive> select reverse(abcedfg’) from dual;
gfdecba


 
一、 网站流量日志数据分析系统 
1. 点击流数据模型 
1.1. 点击流概念 
点击流(Click Stream)是指用户在网站上持续访问的轨迹。这个概念更注重用户浏览网站的整个流程。用户对网站的每次访问包含了一系列的点击动作行为,这些点击行为数据就构成了点击流数据(Click Stream Data),它代表了用户浏览网站的整个流程。 
点击流和网站日志是两个不同的概念,点击流是从用户的角度出发,注重用
户浏览网站的整个流程;而网站日志是面向整个站点,它包含了用户行为数据、服务器响应数据等众多日志信息,我们通过对网站日志的分析可以获得用户的点击流数据。 
网站是由多个网页(Page)构成,当用户在访问多个网页时,网页与网页之间是靠Referrers参数来标识上级网页来源。由此,可以确定网页被依次访问的顺序,当然也可以通过时间来标识访问的次序。其次,用户对网站的每次访问,可视作是一次会话(Session),在网站日志中将会用不同的Sessionid来唯一标识每次会话。如果把 Page 视为“点”的话,那么我们可以很容易的把 Session 描绘成一条“线”,也就是用户的点击流数据轨迹曲线。 
 
图:点击流概念模型 
1.2. 点击流模型生成 
点击流数据在具体操作上是由散点状的点击日志数据梳理所得。点击数据在数据建模时存在两张模型表Pageviews和visits,例如: 

页面点击流模型 Pageviews 表 
Session     IP 地址     时间         访问页面 URL     停留时长     第几步 
S001     101.0.0.1     2012-01-01 12:    31:12     /a/....     30     1 
S002     201.0.0.2     2012-01-01 12:    31:16     /a/....     10     1 
S002     201.0.0.2     2012-01-01 12:    31:26     /b/....     110     2 
S002     201.0.0.2     2012-01-01 12:    31:36     /e/....     30     3 
S003     201.0.0.2     2012-01-01 15:    35:06     /a/....     20     1 
 
点击流模型 Visits 表(按 session 聚集的页面访问信息)
Session     起始时间     结束时间     进 入页面     离 开页面     访问页面数     IP     referal 
S001     2012-01-01 
12:31:12     2012-01-01 
12:31:12     /a/...     /a/...     1     101.0.0.1     somesite.com 
S002     2012-01-01 
12:31:16     2012-01-01 
12:35:06     /a/...     /e/...     3     201.0.0.2     - 
S003     2012-01-01 
12:35:42     2012-01-01 
12:35:42     /c/...     /c/...     1     234.0.0.3     baidu.com 
S004    2012-01-01 
15:16:39     2012-01-01 
15:19:23     /c/...     /e/...     3     101.0.0.1     google.com 
……     ……     ……     ……     ……     ……     ……     …… 

2. 如何进行网站流量分析 
流量分析整体来说是一个内涵非常丰富的体系,整体过程是一个金字塔结构: 
 
金字塔的顶部是网站的目标:投资回报率(ROI)。 
      

2.1. 网站流量分析模型举例 
网站流量质量分析(流量分析) 
流量对于每个网站来说都是很重要,但流量并不是越多越好,应该更加看重流量的质量,换句话来说就是流量可以为我们带来多少收入。 
 
X 轴代表量,指网站获得的访问量。Y 轴代表质,指可以促进网站目标的事件次数(比如商品浏览、注册、购买等行为)。圆圈大小表示获得流量的成本。 
BD 流量是指商务拓展流量。一般指的是互联网经过运营或者竞价排名等方式,从外部拉来的流量。比如电商网站在百度上花钱来竞价排名,产生的流量就是 BD 流量的一部分。 
网站流量多维度细分(流量分析) 
细分是指通过不同维度对指标进行分割,查看同一个指标在不同维度下的表现,进而找出有问题的那部分指标,对这部分指标进行优化。 
 
网站内容及导航分析(内容分析) 
对于所有网站来说,页面都可以被划分为三个类别:导航页、功能页、内容页 
导航页的目的是引导访问者找到信息,功能页的目的是帮助访问者完成特定任务,内容页的目的是向访问者展示信息并帮助访问者进行决策。 
首页和列表页都是典型的导航页; 
站内搜索页面、注册表单页面和购物车页面都是典型的功能页,而产品详情页、新闻和文章页都是典型的内容页。 
比如从内容导航分析中,以下两类行为就是网站运营者不希望看到的行为: 
 
第一个问题:访问者从导航页(首页)还没有看到内容页面之前就从导航页离开网站,需要分析导航页造成访问者中途离开的原因。 
第二个问题:访问者从导航页进入内容页后,又返回到导航页,说明需要分
析内容页的最初设计,并考虑中内容页提供交叉的信息推荐。网站转化以及漏斗分析(转化分析) 
所谓转化,即网站业务流程中的一个封闭渠道,引导用户按照流程最终实现业务目标(比如商品成交);而漏斗模型则是指进入渠道的用户在各环节递进过程中逐渐流失的形象描述; 
对于转化渠道,主要进行两部分的分析: 
访问者的流失和迷失 
阻力的流失 
造成流失的原因很多,如:不恰当的商品或活动推荐对支付环节中专业名词的解释、帮助信息等内容不当 
 
 
迷失 
造成迷失的主要原因是转化流量设计不合理,访问者在特定阶段得不到需要的信息,并且不能根据现有的信息作出决策,比如在线购买演唱会门票,直到支付也没看到在线选座的提示,这时候就很可能会产生迷失,返回查看。 
 
总之,网站数据分析是一门内容非常丰富的学科,本课程中主要关注网站流量分析过程中的技术运用,更多关于网站数据分析的业务知识可学习文档首页推荐的资料。 
2.2. 流量分析常见分类 
指标是网站分析的基础,用来记录和衡量访问者在网站自的各种行为。比如我们经常说的流量就是一个网站指标,它是用来衡量网站获得的访问量。在进行流量分析之前,我们先来了解一些常见的指标。 
骨灰级指标 
IP:1 天之内,访问网站的不重复 IP 数。一天内相同 IP 地址多次访问网站只被计算 1 次。曾经 IP 指标可以用来表示用户访问身份,目前则更多的用来获取访问者的地理位置信息。 
PageView 浏览量: 即通常说的 PV 值,用户每打开 1 个网站页面,记录 1 个PV。用户多次打开同一页面 PV 累计多次。通俗解释就是页面被加载的总次数。 
Unique PageView: 1 天之内,访问网站的不重复用户数(以浏览器 cookie 为依据),一天内同一访客多次访问网站只被计算 1 次。 
基础级指标 
访问次数:访客从进入网站到离开网站的一系列活动记为一次访问,也称会话(session),1 次访问(会话)可能包含多个 PV。 
网站停留时间:访问者在网站上花费的时间。 
页面停留时间:访问者在某个特定页面或某组网页上所花费的时间。 
复合级指标 
人均浏览页数:平均每个独立访客产生的 PV。人均浏览页数=浏览次数/独立访客。体现网站对访客的吸引程度。 
跳出率:指某一范围内单页访问次数或访问者与总访问次数的百分比。其中跳出指单页访问或访问者的次数,即在一次访问中访问者进入网站后只访问了一个页面就离开的数量。 
退出率:指某一范围内退出的访问者与综合访问量的百分比。其中退出指访问者离开网站的次数,通常是基于某个范围的。有了上述这些指标之后,就能结合业务进行各种不同角度的分类分析,主要
是以下几大方面: 
基础分析(PV,IP,UV) 
趋势分析:根据选定的时段,提供网站流量数据,通过流量趋势变化形态,为您分析网站访客的访问规律、网站发展状况提供参考。 
对比分析:根据选定的两个对比时段,提供网站流量在时间上的纵向对比报表,帮您发现网站发展状况、发展规律、流量变化率等。 
当前在线:提供当前时刻站点上的访客量,以及最近 15 分钟流量、来源、受访、访客变化情况等,方便用户及时了解当前网站流量状况。 
访问明细:提供最近 7 日的访客访问记录,可按每个 PV 或每次访问行为(访客的每次会话)显示,并可按照来源、搜索词等条件进行筛选。 通过访问明细,用户可以详细了解网站流量的累计过程,从而为用户快速找出流量变动原因提供最原始、最准确的依据。 
 
      
来源分析
来源分类:提供不同来源形式(直接输入、搜索引擎、其他外部链接、站内来源)、不同来源项引入流量的比例情况。通过精确的量化数据,帮助用户分析什么类型的来路产生的流量多、效果好,进而合理优化推广方案。 
搜索引擎:提供各搜索引擎以及搜索引擎子产品引入流量的比例情况。 
搜索词:提供访客通过搜索引擎进入网站所使用的搜索词,以及各搜索词引入流量的特征和分布。帮助用户了解各搜索词引入流量的质量,进而了解访客的兴趣关注点、网站与访客兴趣点的匹配度,为优化 SEO(搜索引擎优化)方案及 SEM(搜索引擎营销)提词方案提供详细依据。 
最近 7 日的访客搜索记录,可按每个 PV 或每次访问行为(访客的每次会话)显示,并可按照访客类型、地区等条件进行筛选。为您搜索引擎优化提供最详细的原始数据。 
来路域名:提供具体来路域名引入流量的分布情况,并可按“社会化媒体”、“搜索引擎”、
“邮箱”等网站类型对来源域名进行分类。 帮助用户了解哪类推广渠道产生的流量多、效果好,进而合理优化网站推广方案。 
来路页面:提供具体来路页面引入流量的分布情况。 尤其对于通过流量置换、包广告位等方式从其他网站引入流量的用户,该功能可以方便、清晰地展现广告引入的流量及效果,为优化推广方案提供依据。 
来源升降榜:提供开通统计后任意两日的 TOP10000 搜索词、来路域名引入流量的对比情况,并按照变化的剧烈程度提供排行榜。 用户可通过此功能快速找到哪些来路对网站流量的影响比较大,从而及时排查相应来路问题。 
 
受访分析
受访域名:提供访客对网站中各个域名的访问情况。 一般情况下,网站不同域名提供的产品、内容各有差异,通过此功能用户可以了解不同内容的受欢迎程度以及网站运营成效。 
受访页面:提供访客对网站中各个页面的访问情况。 站内入口页面为访客进入网站时浏览的第一个页面,如果入口页面的跳出率较高则需要关注并优化;站内出口页面为访客访问网站的最后一个页面,对于离开率较高的页面需要关注并优化。 
受访升降榜:提供开通统计后任意两日的 TOP10000 受访页面的浏览情况对比,并按照变化的剧烈程度提供排行榜。 可通过此功能验证经过改版的页面是否有流量提升或哪些页面有巨大流量波动,从而及时排查相应问题。 
热点图:记录访客在页面上的鼠标点击行为,通过颜色区分不同区域的点击热度;支持将一组页面设置为"关注范围",并可按来路细分点击热度。 通过访客在页面上的点击量统计,可以了解页面设计是否合理、广告位的安排能否获取更多佣金等。 
用户视点:提供受访页面对页面上链接的其他站内页面的输出流量,并通过输出流量的高低绘制热度图,与热点图不同的是,所有记录都是实际打开了下一页面产生了浏览次数
(PV)的数据,而不仅仅是拥有鼠标点击行为。 
访问轨迹:提供观察焦点页面的上下游页面,了解访客从哪些途径进入页面,又流向了哪里。 通过上游页面列表比较出不同流量引入渠道的效果;通过下

游页面列表了解用户的浏览习惯,哪些页面元素、内容更吸引访客点击。
 
访客分析
地区运营商:提供各地区访客、各网络运营商访客的访问情况分布。 地方网站、下载站等与地域性、网络链路等结合较为紧密的网站,可以参考此功能数据,合理优化推广运营方案。 
终端详情:提供网站访客所使用的浏览终端的配置情况。 参考此数据进行网页设计、开发,可更好地提高网站兼容性,以达到良好的用户交互体验。 
新老访客:当日访客中,历史上第一次访问该网站的访客记为当日新访客;历史上已经访问过该网站的访客记为老访客。 新访客与老访客进入网站的途径和浏览行为往往存在差异。该功能可以辅助分析不同访客的行为习惯,针对不同访客优化网站,例如为制作新手导航提供数据支持等。 
忠诚度:从访客一天内回访网站的次数(日访问频度)与访客上次访问网站的时间两个角度,分析访客对网站的访问粘性、忠诚度、吸引程度。 由于提升网站内容的更新频率、增强用户体验与用户价值可以有更高的忠诚度,因此该功能在网站内容更新及用户体验方面提供了重要参考。 
活跃度:从访客单次访问浏览网站的时间与网页数两个角度,分析访客在网站上的活跃程度。 由于提升网站内容的质量与数量可以获得更高的活跃度,因此该功能是网站内容分析的关键指标之一。 

转化路径分析 
转化定义: 
访客在您的网站完成了某项您期望的活动,记为一次转化,如注册、下载、购买。 
目标示例: 
·获得用户目标:在线注册、创建账号等。 
·咨询目标:咨询、留言、电话等。 
·互动目标:视频播放、加入购物车、分享等。 
·收入目标:在线订单、付款等。 
路径分析: 
根据设置的特定路线,监测某一流程的完成转化情况,算出每步的转换率和流失率数据,
如注册流程,购买流程等。 
转化类型: 
页面 
 
 
 
事件 
 
      
二、 整体技术流程及架构 
1. 数据处理流程 
网站流量日志数据分析是一个纯粹的数据分析项目,其整体流程基本上就是
依据数据的处理流程进行。有以下几个大的步骤: 
数据采集 
数据采集概念,目前行业会有两种解释:一是数据从无到有的过程(web服务器打印的日志、自定义采集的日志等)叫做数据采集;另一方面也有把通过使用Flume等工具把数据采集到指定位置的这个过程叫做数据采集。 
关于具体含义要结合语境具体分析,明白语境中具体含义即可。 
 
数据预处理 
通过mapreduce程序对采集到的原始日志数据进行预处理,比如清洗,格式
整理,滤除脏数据等,并且梳理成点击流模型数据。 
 
数据入库 
将预处理之后的数据导入到HIVE仓库中相应的库和表中。 
 
数据分析 
项目的核心内容,即根据需求开发ETL分析语句,得出各种统计结果。 
 
数据展现 
将分析所得数据进行数据可视化,一般通过图表进行展示。 
      
2. 系统的架构 

相对于传统的BI数据处理,流程几乎差不多,但是因为是处理大数据,所以流程中各环节所使用的技术则跟传统BI完全不同:  
数据采集:定制开发采集程序,或使用开源框架Flume 
数据预处理:定制开发mapreduce程序运行于hadoop集群数据仓库技术:基于hadoop之上的Hive 
数据导出:基于hadoop的sqoop数据导入导出工具数据可视化:定制开发web程序(echarts)  
整个过程的流程调度:hadoop生态圈中的azkaban工具 

其中,需要强调的是:系统的数据分析不是一次性的,而是按照一定的时间频率反复计算,因而整个处理链条中的各个环节需要按照一定的先后依赖关系紧密衔接,即涉及到大量任务单元的管理调度,所以,项目中需要添加一个任务调度模块。 
3. 数据展现 
数据展现的目的是将分析所得的数据进行可视化,以便运营决策人员能更方便地获取数据,更快更简单地理解数据。 
市面上有许多开源的数据可视化软件、工具。比如Echarts. 

      
三、 模块开发----数据采集 
1. 需求 
在网站web流量日志分析这种场景中,对数据采集部分的可靠性、容错能力要求通常不会非常严苛,因此使用通用的 flume 日志采集框架完全可以满足需
求。 
2. Flume 日志采集系统 
2.1. Flume 采集 
Flume 采集系统的搭建相对简单: 
1、在服务器上部署 agent 节点,修改配置文件 
2、启动 agent 节点,将采集到的数据汇聚到指定的 HDFS 目录中
3、针对nginx日志生成场景,如果通过flume(1.6)收集,无论是Spooling Directory Source和Exec Source均不能满足动态实时收集的需求,在当前flume1.7稳定版本中,提供了一个非常好用的TaildirSource,使用这个source,可以监控一个目录,并且使用正则表达式匹配该目录中的文件名进行实时收集。 
核心配置如下: 
a1.sources = r1 
a1.sources.r1.type = TAILDIR 
a1.sources.r1.channels = c1 
a1.sources.r1.positionFile = /var/log/flume/taildir_position.json a1.sources.r1.filegroups = f1 f2 
a1.sources.r1.filegroups.f1 = /var/log/test1/example.log a1.sources.r1.filegroups.f2 = /var/log/test2/.*log.* 
filegroups:指定filegroups,可以有多个,以空格分隔;(TailSource可以同时监控 tail多个目录中的文件) 
positionFile:配置检查点文件的路径,检查点文件会以json格式保存已经tail文件的位置,解决了断点不能续传的缺陷。 
filegroups.<filegroupName>:配置每个filegroup的文件绝对路径,文件名可以用正则表达式匹配。
通过以上配置,就可以监控文件内容的增加和文件的增加。产生和所配置的文件名正则表达式不匹配的文件,则不会被tail。 
2.2. 数据内容样例 
 
58.215.204.118 - - [18/Sep/2013:06:51:35 +0000] "GET /wp-includes/js/jquery/jquery.js?ver=1.10.2 HTTP/1.1" 304 0 "http://blog.fens.me/nodejs-socketio-chat/" "Mozilla/5.0 (Windows NT 5.1; rv:23.0) Gecko/20100101 
Firefox/23.0" 
字段解析: 
1、访客ip地址:   58.215.204.118 
2、访客用户信息:  - - 
3、请求时间:[18/Sep/2013:06:51:35 +0000] 
4、请求方式:GET 
5、请求的url:/wp-includes/js/jquery/jquery.js?ver=1.10.2 
6、请求所用协议:HTTP/1.1 
7、响应码:304 
8、返回的数据流量:0 
9、访客的来源url:http://blog.fens.me/nodejs-socketio-chat/ 
10、访客所用浏览器:Mozilla/5.0 (Windows NT 5.1; rv:23.0) Gecko/20100101 
Firefox/23.0 
      
四、 模块开发----数据预处理 
1. 主要目的 
过滤“不合规”数据,清洗无意义的数据格式转换和规整根据后续的统计需求,过滤分离出各种不同主题(不同栏目 path)的基础数据。 

2. 实现方式 
public class WeblogPreProcess {      
static class WeblogPreProcessMapper extends Mapper<LongWritable, Text, Text, NullWritable> { 
          Text k = new Text(); 
          NullWritable v = NullWritable.get(); 
          @Override 
     protected void map(LongWritable key, Text value, Context context) throws IOException, 
InterruptedException { 
               String line = value.toString(); 
               WebLogBean webLogBean = WebLogParser.parser(line); 
//               WebLogBean productWebLog = WebLogParser.parser2(line); 
//               WebLogBean bbsWebLog = WebLogParser.parser3(line); //               WebLogBean cuxiaoBean = WebLogParser.parser4(line);                if (!webLogBean.isValid()) 
                    return; 
               k.set(webLogBean.toString());                context.write(k, v); 
//               k.set(productWebLog); 
//               context.write(k, v); 
 
 
 
 
 
 
 
 
 
 
 
 
 
      

 
 
 
 
 
 
 
 
 
 
     } 
public static void main(String[] args) throws Exception { 
 
Configuration conf = new Configuration(); Job job = Job.getInstance(conf); job.setJarByClass(WeblogPreProcess.class); job.setMapperClass(WeblogPreProcessMapper.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(NullWritable.class); 
FileInputFormat.setInputPaths(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); job.waitForCompletion(true); 
 
 }     }     
 
 运行 mr 对数据进行预处理 
hadoop jar weblog.jar  cn.krist.bigdata.hive.mr.WeblogPreProcess /weblog/input /weblog/preout 
3. 点击流模型数据梳理 
由于大量的指标统计从点击流模型中更容易得出,所以在预处理阶段,可以使用mr程序来生成点击流模型的数据。 
3.1. 点击流模型 pageviews 表 
Pageviews 表模型数据生成, 详细见:ClickStreamPageView.java  

 
此时程序的输入数据源就是上一步骤我们预处理完的数据。经过此不处理完成之后的数据格式为: 

3.2. 点击流模型 visit 信息表 
注:“一次访问”=“N 次连续请求” 
直接从原始数据中用hql 语法得出每个人的“次”访问信息比较困难,可先用mapreduce 程序分析原始数据得出“次”信息数据,然后再用hql 进行更多维度统计 
 
用 MR 程序从 pageviews 数据中,梳理出每一次 visit 的起止时间、页面信息详细代码见工程:ClickStreamVisit.java 

      
五、 工作流调度器 
1. 工作流调度系统产生背景 
一个完整的数据分析系统通常都是由大量任务单元组成: 
shell脚本程序,java程序,mapreduce程序、hive脚本等。 
各个任务单元之间存在时间先后依赖关系。 
为了很好地组织起这样的复杂执行计划,需要一个工作流调度系统来调度执行; 
2. 工作流调度实现方式 
简单的任务调度: 
直接使用linux的crontab来定义,但是缺点也是比较明显,无法设置依赖。 
复杂的任务调度: 
自主开发调度平台 使用开源调度系统,比如azkaban、ooize、Zeus等。 其中知名度比较高的是Apache Oozie,但是其配置工作流的过程是编写大量的XML配置,而且代码复杂度比较高,不易于二次开发。  
      
3. Azkaban 调度器 
3.1. Azkaban 介绍 
Azkaban是由Linkedin 公司推出的一个批量工作流任务调度器,用于在一个工作流内以一个特定的顺序运行一组工作和流程。Azkaban使用job配置文件建立任
务之间的依赖关


•提供功能清晰,简单易用的Web UI界面 
•提供job配置文件快速建立任务和任务之间的依赖关系 
•提供模块化和可插拔的插件机制,原生支持command、Java、Hive、Pig、Hadoop 
•基于Java开发,代码结构清晰,易于二次开发 
3.2. Azkaban 安装部署 
Azkaban的组成如下: 
 

•solo server mode(单机模式):该模式中webServer和executorServer运行在同一个进程中,进程名是AzkabanSingleServer。可以使用自带的H2数据库或者配置mysql数据。该模式适用于小规模的使用。 
•cluster server mode(集群模式):该模式使用MySQL数据库,webServer和 executorServer运行在不同进程中,该模式适用于大规模应用。 
其实在单机模式中,AzkabanSingleServer进程只是把AzkabanWebServer和 AzkabanExecutorServer合到一起启动而已。 
准备工作 
Azkaban Web服务器 
azkaban-web-server-2.5.0.tar.gz 
Azkaban执行服务器  
azkaban-executor-server-2.5.0.tar.gz MySQL 
本文档中默认已安装好mysql服务器。 
下载地址:http://azkaban.github.io/downloads.html 
上传安装包
将安装包上传到集群,最好上传到安装hive、sqoop的机器上,方便命令的执行。 
新建azkaban目录,用于存放azkaban运行程序。 
azkaban web 服务器安装 
解压azkaban-web-server-2.5.0.tar.gz 
命令: 
tar –zxvf azkaban-web-server-2.5.0.tar.gz 
将解压后的 azkaban-web-server-2.5.0 移动到 azkaban 目录中,并重新命
名 server 
命令: 
mv azkaban-web-server-2.5.0 ../azkaban     
cd ../azkaban         
mv azkaban-web-server-2.5.0  server 
azkaban 执行服器安装 
解压azkaban-executor-server-2.5.0.tar.gz 
命令:
tar –zxvf azkaban-executor-server-2.5.0.tar.gz 
将解压后的 azkaban-executor-server-2.5.0 移动到 azkaban 目录中,并重新命名 executor 
命令:
mv azkaban-executor-server-2.5.0  ../azkaban 
cd ../azkaban 
mv azkaban-executor-server-2.5.0  executor 
azkaban 脚本导入 
解压: azkaban-sql-script-2.5.0.tar.gz 命令:
tar –zxvf azkaban-sql-script-2.5.0.tar.gz 
将解压后的mysql 脚本,导入到mysql中: 
进入mysql 
mysql> create database azkaban; 
mysql> use azkaban; Database changed 
mysql> source /home/hadoop/azkaban-2.5.0/create-all-sql-2.5.0.sql; 
创建 SSL 配置(https) 
命令: keytool -keystore keystore -alias jetty -genkey -keyalg RSA 
运行此命令后,会提示输入当前生成 keystor 的密码及相应信息,输入的密码请劳记, 信息如下: 
输入keystore密码:  
再次输入新密码: 
您的名字与姓氏是什么? 
  [Unknown]:  
您的组织单位名称是什么? 
  [Unknown]:  
您的组织名称是什么? 
  [Unknown]:  
您所在的城市或区域名称是什么? 
  [Unknown]:  
您所在的州或省份名称是什么? 
  [Unknown]:  
该单位的两字母国家代码是什么 
  [Unknown]:  CN 
CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=CN 正确吗? 
  [否]:  y  输入<jetty>的主密码 
        (如果和 keystore 密码相同,按回车):  再次输入新密码: 
完成上述工作后,将在当前目录生成 keystore 证书文件,将 keystore 拷贝
到 azkaban web服务器根目录中.如:cp keystore azkaban/webserver 配置文件
注:先配置好服务器节点上的时区 
先生成时区配置文件Asia/Shanghai,用交互式命令 tzselect 即可 
拷贝该时区文件,覆盖系统本地时区配置 
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime   
azkaban web服务器配置进入azkaban web服务器安装目录 conf目录 
修改azkaban.properties文件 
#Azkaban Personalization Settings 
azkaban.name=Test                           #服务器 UI 名称,用于服务器上方显示的名字 azkaban.label=My Local Azkaban                               #描述 
azkaban.color=#FF3601                                                 #UI 颜色 azkaban.default.servlet.path=/index                         # 
web.resource.dir=web/                                                 #默认根 web 目录 
default.timezone.id=Asia/Shanghai                           #默认时区,已改为亚洲/上海 默认为美国 


#Azkaban UserManager class 
user.manager.class=azkaban.user.XmlUserManager   #用户权限管理默认类 user.manager.xml.file=conf/azkaban-users.xml              #用户配置,具体配置参加下文 
  
#Loader for projects executor.global.properties=conf/global.properties    # global 配置文件所在位置 azkaban.project.dir=projects                                                # 
  
database.type=mysql                                                             #数据库类型 
mysql.port=3306                                                                     #端口号
 mysql.host=hadoop03                                                      #数据库连接
 IP mysql.database=azkaban                                                       #数据库实例名 
mysql.user=root                                                                 #数据库用户名
 mysql.password=root                                                          #数据库密码 
mysql.numconnections=100                                                  #最大连接数 
  
# Velocity dev mode velocity.dev.mode=false 
# Jetty 服务器属性. 
jetty.maxThreads=25                                                               #最大线程数 
jetty.ssl.port=8443                                                                   #Jetty SSL 端口 
jetty.port=8081                                                                         #Jetty 端口 
jetty.keystore=keystore                                                          #SSL 文件名 
jetty.password=123456                                                             #SSL 文件密码 
jetty.keypassword=123456                                                      #Jetty 主密码 与 keystore 文件相同 jetty.truststore=keystore                                                                #SSL 文件名 jetty.trustpassword=123456                                                   # SSL 文件密码 
  
# 执行服务器属性 
executor.port=12321                                                               #执行服务器端口 
  
# 邮件设置 
mail.sender=xxxxxxxx@163.com                                       #发送邮箱 
mail.host=smtp.163.com                                                       #发送邮箱 smtp 地址 
mail.user=xxxxxxxx                                       #发送邮件时显示的名称 
mail.password=**********                                                 #邮箱密码 
job.failure.email=xxxxxxxx@163.com                              #任务失败时发送邮件的地址 
job.success.email=xxxxxxxx@163.com                            #任务成功时发送邮件的地址 
lockdown.create.projects=false                                           # 
cache.directory=cache                                                            #缓存目录 
 
  
 
azkaban 执行服务器配置进入执行服务器安装目录conf,修改azkaban.properties 
vi azkaban.properties 
#Azkaban 
default.timezone.id=Asia/Shanghai                                              #时区 
  
# Azkaban JobTypes 插件配置 
azkaban.jobtype.plugin.dir=plugins/jobtypes                   #jobtype 插件所在位置 
  
#Loader for projects 
executor.global.properties=conf/global.properties azkaban.project.dir=projects 
  
#数据库设置 
database.type=mysql                                                                       #数据库类型(目前只支持 mysql) 
mysql.port=3306                                                                                #数据库端口号
 mysql.host=192.168.20.200                                                           #数据库 IP 地址 
mysql.database=azkaban                                                                #数据库实例名 
mysql.user=azkaban                                                                         #数据库用户名
 mysql.password=oracle                                                                   #数据库密码
 mysql.numconnections=100                                                           #最大连接数 
  
# 执行服务器配置 
executor.maxThreads=50                                                                #最大线程数 
executor.port=12321                                                               #端口号(如修改,请与 web 服务中一致) 
executor.flow.threads=30                                                                #线程数 
 
 
 用户配置 
进入 azkaban web 服务器 conf 目录,修改 azkaban-users.xml vi azkaban-users.xml 增加 管理员用户 
<azkaban-users> 
        <user username="azkaban" password="azkaban" roles="admin" groups="azkaban" /> 
        <user username="metrics" password="metrics" roles="metrics"/> 
        <user username="admin" password="admin" roles="admin,metrics" /> 
        <role name="admin" permissions="ADMIN" /> 
        <role name="metrics" permissions="METRICS"/> 
</azkaban-users> 
 
 
启动 
web服务器在azkaban web服务器目录下执行启动命令 
bin/azkaban-web-start.sh 
注:在web服务器根目录运行执行服务器在执行服务器目录下执行启动命令 
bin/azkaban-executor-start.sh ./ 
注:只能要执行服务器根目录运行启动完成后,在浏览器(建议使用谷歌浏览器)中输入https://服务器IP地址:8443 ,即可访问azkaban服务了.在登录中输入刚才新的户用名及密码,点击login. 
      
3.3. Azkaban 实战 
Command 类型单一 job 示例 
 
创建job描述文件 
vi command.job 
#command.job 
type=command                                                    
command=echo 'hello' 
将job资源文件打包成zip文件 
zip command.job 
通过azkaban的web管理平台创建project并上传job压缩包首先创建Project 
 
上传zip包 
 
启动执行该job 

Command 类型多 job 工作流 flow 
 
创建有依赖关系的多个job描述 
第一个job:foo.job 
# foo.job 
type=command command=echo foo 
第二个job:bar.job依赖foo.job 
# bar.job 
type=command dependencies=foo command=echo bar 
 
将所有job资源文件打到一个zip包中 
 
在azkaban的web管理界面创建工程并上传zip包 
启动工作流flow 
 
HDFS 操作任务 
 
创建job描述文件 
# fs.job 
type=command 
command=/home/hadoop/apps/hadoop-2.6.1/bin/hadoop fs -mkdir /azaz 
 
将job资源文件打包成zip文件 
 
通过azkaban的web管理平台创建project并上传job压缩包 
启动执行该job 
 
 
MAPREDUCE 任务 
 
mr任务依然可以使用command的job类型来执行 
创建job描述文件,及mr程序jar包(示例中直接使用hadoop自带的example jar) 
# mrwc.job 
type=command 
command=/home/hadoop/apps/hadoop-2.6.1/bin/hadoop  jar hadoop-
mapreduceexamples-2.6.1.jar wordcount /wordcount/input /wordcount/azout 
将所有job资源文件打到一个zip包中 
 
在azkaban的web管理界面创建工程并上传zip包 
启动job 
HIVE 脚本任务 
 
创建job描述文件和hive脚本 
Hive脚本: test.sql 
use default; 
drop table aztest; 
create table aztest(id int,name string) row format delimited fields terminated by ','; 
load data inpath '/aztest/hiveinput' into table aztest; 
insert overwrite directory '/aztest/hiveoutput' select count(1) from aztest;  
Job描述文件:hivef.job 
# hivef.job 
type=command 
command=/home/hadoop/apps/hive/bin/hive -f 'test.sql' 
 将所有job资源文件打到一个zip包中创建工程并上传zip包,启动job 


一、 模块开发----数据仓库设计 
1. 维度建模基本概念 
维度建模(dimensional modeling)是专门用于分析型数据库、数据仓库、数据集市建模的方法。数据集市可以理解为是一种"小型数据仓库"。 
维度表(dimension) 
维度表示你要对数据进行分析时所用的一个量,比如你要分析产品销售情况, 
你可以选择按类别来进行分析,或按区域来分析。这样的按..分析就构成一个维度。再比如"昨天下午我在星巴克花费200元喝了一杯卡布奇诺"。那么以消费为主题进行分析,可从这段信息中提取三个维度:时间维度(昨天下午),地点维度
(星巴克), 商品维度(卡布奇诺)。通常来说维度表信息比较固定,且数据量小。 
事实表(fact table) 
表示对分析主题的度量。事实表包含了与各维度表相关联的外键,并通过 JOIN方式与维度表关联。事实表的度量通常是数值类型,且记录数会不断增加,表规模迅速增长。比如上面的消费例子,它的消费事实表结构示例如下: 
消费事实表:Prod_id(引用商品维度表), TimeKey(引用时间维度表), 
Place_id(引用地点维度表), Unit(销售量)。 
总的说来,在数据仓库中不需要严格遵守规范化设计原则。因为数据仓库的主导功能就是面向分析,以查询为主,不涉及数据更新操作。事实表的设计是以能够正确记录历史信息为准则,维度表的设计是以能够以合适的角度来聚合主题内容为准则。 
      
2.维度建模三种模式 

2.1. 星型模式 
星形模式(Star Schema)是最常用的维度建模方式。星型模式是以事实表为中心,所有的维度表直接连接在事实表上,像星星一样。 
星形模式的维度建模由一个事实表和一组维表成,且具有以下特点: 
a.维表只和事实表关联,维表之间没有关联; 
b.每个维表主键为单列,且该主键放置在事实表中,作为两边连接的外键; c. 以事实表为核心,维表围绕核心呈星形分布; 
 
 
      
2.2. 雪花模式 
雪花模式(Snowflake Schema)是对星形模式的扩展。雪花模式的维度表可以拥有其他维度表的,虽然这种模型相比星型更规范一些,但是由于这种模型不太容易理解,维护成本比较高,而且性能方面需要关联多层维表,性能也比星型模型要低。所以一般不是很常用。 
 
 
      
2.3. 星座模式 
星座模式是星型模式延伸而来,星型模式是基于一张事实表的,而星座模式是基于多张事实表的,而且共享维度信息。 
前面介绍的两种维度建模方法都是多维表对应单事实表,但在很多时候维度空间内的事实表不止一个,而一个维表也可能被多个事实表用到。在业务发展后期,绝大部分维度建模都采用的是星座模式。 
 
      
3. 本项目中数据仓库的设计 
注:采用星型模型     
3.1. 事实表设计 
原始数据表: ods_weblog_origin =>对应mr清洗完之后的数据 
valid     string     是否有效 
remote_addr     string     访客ip 
remote_user     string     访客用户信息 
time_local     string     请求时间 
request     string     请求url 
status     string     响应码 
body_bytes_sent     string     响应字节数 
http_referer     string     来源url 
http_user_agent     string     访客终端信息 
           
访问日志明细宽表:dw_weblog_detail 
valid     string     是否有效 
remote_addr     string     访客ip 
remote_user     string     访客用户信息 
time_local     string     请求完整时间 
daystr     string     访问日期 
timestr     string     访问时间 
month     string     访问月 
day     string     访问日 
hour     string     访问时 
request     string     请求url整串 
status     string     响应码 
body_bytes_sent     string     响应字节数 
http_referer     string     来源url 
ref_host     string     来源的host 
ref_path     string     来源的路径 
ref_query     string     来源参数query 
ref_query_id     string     来源参数query值 
http_user_agent     string     客户终端标识 
3.2. 维度表设计  
     时间维度 t_dim_time              访客地域维度t_dim_area     
    date_Key             area_ID     
    year             北京     
    month             上海     
    day             广州     
    hour             深圳     
                    
    终端类型维度t_dim_termination             网站栏目维度 t_dim_section     
    uc             跳蚤市场     
    firefox             房租信息     
    chrome             休闲娱乐     
    safari             建材装修     
    ios             本地服务     
    android             人才市场     
      
 
注意: 
维度表的数据一般要结合业务情况自己写脚本按照规则生成,也可以使用工具生成,方便后续的关联分析。 
事前生成时间维度表中的数据    ,跨度从业务需要的日期到当前
比如一般会
日期即可.具体根据你的分析粒度,可以生成年,季,月,周,天,时等相关信息,用于分析。 
      
二、 模块开发----ETL 
ETL工作的实质就是从各个数据源提取数据,对数据进行转换,并最终加载填充数据到数据仓库维度建模后的表中。只有当这些维度/事实表被填充好,ETL 工作才算完成。 
本项目的数据分析过程在 hadoop 集群上实现,主要应用 hive 数据仓库工具,因此,采集并经过预处理后的数据,需要加载到hive数据仓库中,以进行后续的分析过程。 
1. 创建 ODS 层数据表 
1.1. 原始日志数据表 
drop table if exists ods_weblog_origin; create table ods_weblog_origin( valid string, remote_addr string, remote_user string, time_local string, request string, status string, body_bytes_sent string, http_referer string, http_user_agent string) partitioned by (datestr string) row format delimited fields terminated by '\001'; 
1.2. 点击流模型 pageviews 表 
drop table if exists ods_click_pageviews; create table ods_click_pageviews( session string, remote_addr string, remote_user string, time_local string, 
request string, visit_step string, page_staylong string, http_referer string, http_user_agent string, body_bytes_sent string, status string) partitioned by (datestr string) row format delimited fields terminated by '\001'; 
1.3. 点击流 visit 模型表 
drop table if exist ods_click_stream_visit; create table ods_click_stream_visit( session     string, remote_addr string, inTime      string, outTime     string, inPage      string, outPage     string, referal     string, pageVisits  int) partitioned by (datestr string) row format delimited fields terminated by '\001'; 
2. 导入 ODS 层数据 
load data inpath '/weblog/preprocessed/' overwrite into table ods_weblog_origin partition(datestr='20130918');--数据导入 show partitions ods_weblog_origin;---查看分区 select count(*) from ods_weblog_origin; --统计导入的数据总数点击流模型的两张表数据导入操作同上。 
注:生产环境中应该将数据 load 命令,写在脚本中,然后配置在 azkaban 中定时运行,注意运行的时间点,应该在预处理数据完成之后。 
3. 生成 ODS 层明细宽表 
3.1. 需求实现 
整个数据分析的过程是按照数据仓库的层次分层进行的,总体来说,是从 ODS原始数据中整理出一些中间表(比如,为后续分析方便,将原始数据中的时间、url等非结构化数据作结构化抽取,将各种字段信息进行细化,形成明细表),然后再在中间表的基础之上统计出各种指标数据。 
3.2. ETL 实现 
建明细表ods_weblog_detail: 
drop table ods_weblog_detail; create table ods_weblog_detail( valid           string, --有效标识 remote_addr     string, --来源 IP remote_user     string, --用户标识 time_local      string, --访问完整时间 daystr          string, --访问日期 timestr         string, --访问时间 month           string, --访问月 day             string, --访问日 hour            string, --访问时 request         string, --请求的 url status          string, --响应码 body_bytes_sent string, --传输字节数 http_referer    string, --来源 url ref_host        string, --来源的 host ref_path        string, --来源的路径 ref_query       string, --来源参数 query ref_query_id    string, --来源参数 query 的值 http_user_agent string --客户终端标识 

partitioned by(datestr string); 
      
通过查询插入数据到明细宽表  ods_weblog_detail中 
1、 抽取refer_url到中间表 t_ods_tmp_referurl 也就是将来访url分离出host  path  query  query id 
drop table if exists t_ods_tmp_referurl; create table t_ods_tmp_referurl as 
SELECT a.*,b.* 
FROM ods_weblog_origin a  
LATERAL VIEW parse_url_tuple(regexp_replace(http_referer, "\"", ""), 
'HOST', 'PATH','QUERY', 'QUERY:id') b as host, path, query, query_id; 
注:lateral view用于和split, explode等UDTF一起使用,它能够将一列数据拆成多行数据。 
UDTF(User-Defined Table-Generating Functions) 用来解决输入一行输出多行(Onto-many maping) 的需求。Explode也是拆列函数,比如Explode (ARRAY) ,array中的每
个元素生成一行。 
2、抽取转换time_local字段到中间表明细表 t_ods_tmp_detail 
drop table if exists t_ods_tmp_detail; create table t_ods_tmp_detail as  select b.*,substring(time_local,0,10) as daystr, substring(time_local,12) as tmstr, substring(time_local,6,2) as month, substring(time_local,9,2) as day, substring(time_local,11,3) as hour from t_ods_tmp_referurl b; 
3、以上语句可以合成一个总的语句 
insert into table shizhan.ods_weblog_detail partition(datestr='2013-
09-18') select c.valid,c.remote_addr,c.remote_user,c.time_local, substring(c.time_local,0,10) as daystr, substring(c.time_local,12) as tmstr, substring(c.time_local,6,2) as month, substring(c.time_local,9,2) as day, substring(c.time_local,11,3) as hour, 
c.request,c.status,c.body_bytes_sent,c.http_referer,c.ref_host,c.ref_ path,c.ref_query,c.ref_query_id,c.http_user_agent from (SELECT  
a.valid,a.remote_addr,a.remote_user,a.time_local, 
a.request,a.status,a.body_bytes_sent,a.http_referer,a.http_user_agent
,b.ref_host,b.ref_path,b.ref_query,b.ref_query_id  FROM shizhan.ods_weblog_origin a LATERAL VIEW  parse_url_tuple(regexp_replace(http_referer, "\"", ""), 'HOST', 
'PATH','QUERY', 'QUERY:id') b as ref_host, ref_path, ref_query,  ref_query_id) c; 
      

三、 模块开发----统计分析 
数据仓库建设好以后,用户就可以编写Hive SQL语句对其进行访问并对其中数据进行分析。 
在实际生产中,究竟需要哪些统计指标通常由数据需求相关部门人员提出,
而且会不断有新的统计需求产生,以下为网站流量分析中的一些典型指标示例。 
注:每一种统计指标都可以跟各维度表进行钻取。 
1. 流量分析 
1.1. 多维度统计 PV 总量 
按时间维度 
--计算每小时pvs,注意gruop by 语法 
select count(*) as pvs,month,day,hour from ods_weblog_detail group by month,day,hour; 
 
方式一
:直接在ods_weblog_detail单表上进行查询 
--计算该处理批次(一天)中的各小时pvs 
drop table dw_pvs_everyhour_oneday; create table dw_pvs_everyhour_oneday(month string,day string,hour string,pvs bigint) partitioned by(datestr 
string); 
 
insert into table dw_pvs_everyhour_oneday partition(datestr='20130918') select a.month as month,a.day as day,a.hour as hour,count(*) as pvs from ods_weblog_detail a where  a.datestr='20130918' group by a.month,a.day,a.hour; 
 
--计算每天的pvs 
drop table dw_pvs_everyday; create table dw_pvs_everyday(pvs bigint,month string,day string); 
 
insert into table dw_pvs_everyday select count(*) as pvs,a.month as month,a.day as day from ods_weblog_detail a group by a.month,a.day; 
 
方式二:与时间维表关联查询 
--维度:日 
drop table dw_pvs_everyday; create table dw_pvs_everyday(pvs bigint,month string,day string); 
 
insert into table dw_pvs_everyday select count(*) as pvs,a.month as month,a.day as day from (select distinct month, day from t_dim_time) a join ods_weblog_detail b  on a.month=b.month and a.day=b.day group by a.month,a.day; 
 
--维度:月 
drop table dw_pvs_everymonth; create table dw_pvs_everymonth (pvs bigint,month string); 
 
insert into table dw_pvs_everymonth select count(*) as pvs,a.month from (select distinct month from t_dim_time)  a join ods_weblog_detail b on a.month=b.month group by a.month; 
 
--另外,也可以直接利用之前的计算结果。比如从之前算好的小时结果中统计每一天的 
Insert into table dw_pvs_everyday 
Select sum(pvs) as pvs,month,day from dw_pvs_everyhour_oneday group by month,day having day='18'; 
 
按终端维度 
数据中能够反映出用户终端信息的字段是http_user_agent。 
User Agent也简称UA。它是一个特殊字符串头,是一种向访问网站提供所使用的浏览器类型及版本、操作系统及版本、浏览器内核、等信息的标识。例如: 
User-Agent,Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) 
Chrome/58.0.3029.276 Safari/537.36 
上述UA信息就可以提取出以下的信息: 
chrome 58.0、浏览器 chrome、浏览器版本 58.0、系统平台 windows 浏览器内核 webkit 
      
这里不再拓展相关知识,感兴趣的可以查看参考资料如何解析UA。 
可以用下面的语句进行试探性统计,当然这样的准确度不是很高。 
select distinct(http_user_agent) from ods_weblog_detail where http_user_agent like '%Chrome%' limit 200; 
按栏目维度 
网站栏目可以理解为网站中内容相关的主题集中。体现在域名上来看就是不同的栏目会有不同的二级目录。比如某网站网址为www.xxxx.cn,旗下栏目可以通过如下方式访问:栏目维度:../job 栏目维度:../news 栏目维度:../sports 
栏目维度:../technology 
那么根据用户请求url就可以解析出访问栏目,然后按照栏目进行统计分析。 
按 referer 维度 
--统计每小时各来访url 产生的pv 量 
drop table dw_pvs_referer_everyhour; create table dw_pvs_referer_everyhour(referer_url string,referer_host string,month string,day string,hour 
string,pv_referer_cnt bigint) partitioned by(datestr string); 
 
insert into table dw_pvs_referer_everyhour partition(datestr='20130918') select http_referer,ref_host,month,day,hour,count(1) as pv_referer_cnt from ods_weblog_detail  group by http_referer,ref_host,month,day,hour  having ref_host is not null order by hour asc,day asc,month asc,pv_referer_cnt desc; 
 
--统计每小时各来访host 的产生的pv 数并排序 
drop table dw_pvs_refererhost_everyhour; create table dw_pvs_refererhost_everyhour(ref_host string,month string,day string,hour string,ref_host_cnts 
bigint) partitioned by(datestr string); 
 
insert into table dw_pvs_refererhost_everyhour partition(datestr='20130918') select ref_host,month,day,hour,count(1) as ref_host_cnts from ods_weblog_detail  
group by ref_host,month,day,hour  
having ref_host is not null order by hour asc,day asc,month asc,ref_host_cnts desc; 

1.2. 人均浏览量 
需求描述:统计今日所有来访者平均请求的页面数。人均浏览量也称作人均浏览页数,该指标可以说明网站对用户的粘性。人均页面浏览量表示用户某一时段平均浏览页面的次数。 
计算方式:总页面请求数/去重总人数 
remote_addr表示不同的用户。可以先统计出不同remote_addr的pv量,然后累加(sum)所有pv作为总的页面请求数,再count所有remote_addr作为总的去重总人数。 
 
--总页面请求数/去重总人数 
drop table dw_avgpv_user_everyday; create table dw_avgpv_user_everyday( 
day string, avgpv string); 
 
insert into table dw_avgpv_user_everyday select '20130918',sum(b.pvs)/count(b.remote_addr) from 
(select remote_addr,count(1) as pvs from ods_weblog_detail where datestr='20130918' group by remote_addr) b; 
      
1.3. 统计 pv 总量最大的来源 TOPN (分组 TOP) 
需求描述:统计每小时各来访 host 的产生的 pvs 数最多的前 N 个(topN)。
 row_number()函数 
语法:row_number() over (partition by xxx order by xxx) rank,rank为分
组的别名,相当于新增一个字段为rank。 
partition by用于分组,比方说依照sex字段分组 
order by用于分组内排序,比方说依照sex分组,组内按照age排序 
排好序之后,为每个分组内每一条分组记录从1开始返回一个数字 
取组内某个数据,可以使用where 表名.rank>x之类的语法去取以下语句对每个小时内的来访host次数倒序排序标号: 
select ref_host,ref_host_cnts,concat(month,day,hour), row_number() over (partition by concat(month,day,hour) order by ref_host_cnts desc) as od from dw_pvs_refererhost_everyhour; 
效果如下: 
 
      
根据上述row_number的功能,可编写hql取各小时的ref_host访问次数topn 
 
drop table dw_pvs_refhost_topn_everyhour; create table dw_pvs_refhost_topn_everyhour( 
hour string, toporder string, ref_host string, ref_host_cnts string 
)partitioned by(datestr string); 
 
insert into table dw_pvs_refhost_topn_everyhour partition(datestr='20130918') select t.hour,t.od,t.ref_host,t.ref_host_cnts from 
 (select ref_host,ref_host_cnts,concat(month,day,hour) as hour, row_number() over (partition by concat(month,day,hour) order by ref_host_cnts desc) as od  from dw_pvs_refererhost_everyhour) t where od<=3; 
 
结果如下: 
 
      
2. 受访分析(从页面的角度分析) 
2.1. 各页面访问统计 
主要是针对数据中的request进行统计分析,比如各页面PV ,各页面UV 等。 
以上指标无非就是根据页面的字段group by。例如: 
 
--统计各页面pv 
select request as request,count(request) as request_counts from ods_weblog_detail group by request having request is not null order by request_counts desc limit 20; 
2.2. 热门页面统计 
 
--统计每日最热门的页面top10 
drop table dw_hotpages_everyday; create table dw_hotpages_everyday(day string,url string,pvs string); 
 
insert into table dw_hotpages_everyday select '20130918',a.request,a.request_counts from 
(select request as request,count(request) as request_counts from ods_weblog_detail where datestr='20130918' group by request having request is not null) a order by a.request_counts desc limit 10; 
 
      
3. 访客分析 
3.1. 独立访客 
需求描述:按照时间维度比如小时来统计独立访客及其产生的pv。 
对于独立访客的识别,如果在原始日志中有用户标识,则根据用户标识即很好实现;此处,由于原始日志中并没有用户标识,以访客IP来模拟,技术上是一样的,只是精确度相对较低。 
 
--时间维度:时 
drop table dw_user_dstc_ip_h; create table dw_user_dstc_ip_h( remote_addr string, pvs      bigint, hour     string); 
 
insert into table dw_user_dstc_ip_h  select remote_addr,count(1) as pvs,concat(month,day,hour) as hour  from ods_weblog_detail Where datestr='20130918' group by concat(month,day,hour),remote_addr; 
 
在此结果表之上,可以进一步统计,如每小时独立访客总数: 
select count(1) as dstc_ip_cnts,hour from dw_user_dstc_ip_h group by hour; 
 
--时间维度:日 
select remote_addr,count(1) as counts,concat(month,day) as day from ods_weblog_detail Where datestr='20130918' group by concat(month,day),remote_addr; 
 
--时间维度:月 
select remote_addr,count(1) as counts,month  from ods_weblog_detail group by month,remote_addr; 
3.2. 每日新访客 
需求:将每天的新访客统计出来。 
实现思路:创建一个去重访客累积表,然后将每日访客对比累积表。 

--历日去重访客累积表 
drop table dw_user_dsct_history; create table dw_user_dsct_history( day string, ip string )  partitioned by(datestr string); 
 
--每日新访客表 
drop table dw_user_new_d; create table dw_user_new_d ( 
day string, ip string )  partitioned by(datestr string); 
 
--每日新用户插入新访客表 
insert into table dw_user_new_d partition(datestr='20130918') select tmp.day as day,tmp.today_addr as new_ip from 

select today.day as day,today.remote_addr as today_addr,old.ip as old_addr  from  
(select distinct remote_addr datestr="20130918") today 
left outer join  
dw_user_dsct_history old on today.remote_addr=old.ip 
) tmp where tmp.old_addr is null; 
 
--每日新用户追加到累计表 
insert into table dw_user_dsct_history partition(datestr='20130918') select day,ip from dw_user_new_d where datestr='20130918';    as     remote_addr,"20130918" 
     as 
     day     from     ods_weblog_detail     where 
 
验证查看: 
select count(distinct remote_addr) from ods_weblog_detail; 
 
select count(1) from dw_user_dsct_history where datestr='20130918'; 
 
select count(1) from dw_user_new_d where datestr='20130918'; 
 
注:还可以按来源地域维度、访客终端维度等计算 
      
4. 访客 Visit 分析(点击流模型) 
4.1. 回头/单次访客统计 
需求:查询今日所有回头访客及其访问次数。 

实现思路:上表中出现次数>1的访客,即回头访客;反之,则为单次访客。 
drop table dw_user_returning; create table dw_user_returning( day string, remote_addr string, acc_cnt string) partitioned by (datestr string); 
 
insert overwrite table dw_user_returning partition(datestr='20130918') select tmp.day,tmp.remote_addr,tmp.acc_cnt from 
(select '20130918' as day,remote_addr,count(session) as acc_cnt from ods_click_stream_visit group by remote_addr) tmp where tmp.acc_cnt>1; 
4.2. 人均访问频次 
需求:统计出每天所有用户访问网站的平均次数(visit)总visit数/去重总用户数 
select sum(pagevisits)/count(distinct remote_addr) from ods_click_stream_visit where datestr='20130918'; 
      
5. 关键路径转化率分析(漏斗模型) 
5.1. 需求分析 
转化:在一条指定的业务流程中,各个步骤的完成人数及相对上一个步骤的百分比。 
 
5.2. 模型设计 
定义好业务流程中的页面标识,下例中的步骤为: 
Step1、  /item 
Step2、  /category 
Step3、  /index 
Step4、  /order 

5.3. 开发实现 
查询每一个步骤的总访问人数 
 
--查询每一步人数存入dw_oute_numbs 
create table dw_oute_numbs as  select 'step1' as step,count(distinct remote_addr)  as numbs from ods_click_pageviews where datestr='20130920' and request like '/item%' union select 'step2' as step,count(distinct remote_addr)  as numbs from ods_click_pageviews where datestr='20130920' and request like '/category%' union select 'step3' as step,count(distinct remote_addr)  as numbs from ods_click_pageviews where datestr='20130920' and request like '/order%' union select 'step4' as step,count(distinct remote_addr)  as numbs from ods_click_pageviews where datestr='20130920' and request like '/index%'; 
注:UNION将多个SELECT语句的结果集合并为一个独立的结果集。 
查询每一步骤相对于路径起点人数的比例思路:级联查询,利用自 join 
 
--dw_oute_numbs 跟自己join 
select rn.step as rnstep,rn.numbs as rnnumbs,rr.step as rrstep,rr.numbs as rrnumbs  from dw_oute_numbs rn inner join  
dw_oute_numbs rr; 
 
--每一步的人数/第一步的人数==每一步相对起点人数比例 
select tmp.rnstep,tmp.rnnumbs/tmp.rrnumbs as ratio from 

select rn.step as rnstep,rn.numbs as rnnumbs,rr.step as rrstep,rr.numbs as rrnumbs  from dw_oute_numbs rn inner join  
dw_oute_numbs rr) tmp where tmp.rrstep='step1'; 
 
查询每一步骤相对于上一步骤的漏出率 
 
--自join 表过滤出每一步跟上一步的记录 
select rn.step as rnstep,rn.numbs as rnnumbs,rr.step as rrstep,rr.numbs as rrnumbs  from dw_oute_numbs rn inner join  
dw_oute_numbs rr 
where cast(substr(rn.step,5,1) as int)=cast(substr(rr.step,5,1) as int)-1; 
 
select tmp.rrstep as step,tmp.rrnumbs/tmp.rnnumbs as leakage_rate from 

select rn.step as rnstep,rn.numbs as rnnumbs,rr.step as rrstep,rr.numbs as rrnumbs  from dw_oute_numbs rn inner join  
dw_oute_numbs rr) tmp 
where cast(substr(tmp.rnstep,5,1) as int)=cast(substr(tmp.rrstep,5,1) as int)-1; 
 
汇总以上两种指标 
select abs.step,abs.numbs,abs.rate as abs_ratio,rel.rate as leakage_rate from  

select tmp.rnstep as step,tmp.rnnumbs as numbs,tmp.rnnumbs/tmp.rrnumbs as rate 
from 

select rn.step as rnstep,rn.numbs as rnnumbs,rr.step as rrstep,rr.numbs as rrnumbs  from dw_oute_numbs rn inner join  
dw_oute_numbs rr) tmp where tmp.rrstep='step1' 
) abs left outer join 

select tmp.rrstep as step,tmp.rrnumbs/tmp.rnnumbs as rate from 

select rn.step as rnstep,rn.numbs as rnnumbs,rr.step as rrstep,rr.numbs as rrnumbs  from dw_oute_numbs rn inner join  
dw_oute_numbs rr) tmp 
where cast(substr(tmp.rnstep,5,1) as int)=cast(substr(tmp.rrstep,5,1) as int)-1 
) rel 
on abs.step=rel.step; 
      
四、 模块开发----结果导出 
1. Apache Sqoop  
Sqoop是Hadoop和关系数据库服务器之间传送数据的一种工具。它是用来从关系数据库如:MySQL,Oracle到Hadoop的HDFS,并从Hadoop的文件系统导出数
据到关系数据库。由Apache软件基金会提供。 
Sqoop:“SQL 到 Hadoop 和 Hadoop 到SQL”。 
 
Sqoop工作机制是将导入或导出命令翻译成mapreduce程序来实现。 
在翻译出的mapreduce中主要是对inputformat和outputformat进行定制。 
sqoop安装 
安装sqoop的前提是已经具备java和hadoop的环境。 
最新稳定版: 1.4.6 
配置文件修改:
 

cd SQOOP/conf
 mv sqoop-env-template.sh sqoop-env.sh 
vi sqoop-env.sh 
export HADOOP_COMMON_HOME=/export/server/hadoop-2.7.4 (hadoop安装目录)
 export HADOOP_MAPRED_HOME=/export/server/hadoop-2.7.4 
export HIVE_HOME=/root/apps/hive 
加入mysql的jdbc驱动包 
cp  /hive/lib/mysql-connector-java-5.1.28.jar   $SQOOP_HOME/lib/ 
验证启动 
bin/sqoop list-databases --connect jdbc:mysql://localhost:3306/ -username root --password 123
本命令会列出所有mysql的数据库。到这里,整个Sqoop安装工作完成。 
2. Sqoop 导入 
“导入工具”导入单个表从RDBMS到HDFS。表中的每一行被视为HDFS的记录。所有记录都存储为文本文件的文本数据(或者Avro、sequence文件等二进制数据)。下面的语法用于将数据导入HDFS。 
$ sqoop import (generic-args) (import-args) 
Sqoop测试表数据在mysql中创建数据库userdb,然后执行参考资料中的sql脚本: 
创建三张表: emp  emp_add emp_conn。   
2.1. 导入 mysql 表数据到 HDFS 
下面的命令用于从 MySQL 数据库服务器中的 emp 表导入 HDFS。 bin/sqoop import \ 
--connect jdbc:mysql://node-21:3306/sqoopdb \ 
--username root \ 
--password hadoop \ 
--target-dir /sqoopresult \ 
--table emp --m 1 
其中--target-dir可以用来指定导出数据存放至HDFS的目录; 
mysql jdbc url 请使用 ip 地址。 
为了验证在HDFS导入的数据,请使用以下命令查看导入的数据: 
hdfs dfs -cat /sqoopresult/part-m-00000 
可以看出它会用逗号,分隔emp表的数据和字段。 
1201,gopal,manager,50000,TP 
1202,manisha,Proof reader,50000,TP 
1203,khalil,php dev,30000,AC 
1204,prasanth,php dev,30000,AC 
1205,kranthi,admin,20000,TP 
2.2. 导入 mysql 表数据到 HIVE 
将关系型数据的表结构复制到 hive 中 bin/sqoop create-hive-table \ 
--connect jdbc:mysql://node-21:3306/sqoopdb \ 
--table emp_add \ 
--username root \ 
--password hadoop \ 
--hive-table test.emp_add_sp 
其中: 
 --table emp_add为mysql中的数据库sqoopdb中的表。    
 --hive-table emp_add_sp 为hive中新建的表名称。 
从关系数据库导入文件到hive中 
bin/sqoop import \ 
--connect jdbc:mysql://node-21:3306/sqoopdb \ 
--username root \ 
--password hadoop \ 
--table emp_add \ 
--hive-table test.emp_add_sp \ 
--hive-import \ 
--m 1 
2.3. 导入表数据子集 
--where 可以指定从关系数据库导入数据时的查询条件。它执行在各自的数据库服务器相应的SQL查询,并将结果存储在HDFS的目标目录。 
bin/sqoop import \ 
--connect jdbc:mysql://node-21:3306/sqoopdb \ 
--username root \ 
--password hadoop \ 
--where "city ='sec-bad'" \ 
--target-dir /wherequery \ 
--table emp_add --m 1 
复杂查询条件: 
bin/sqoop import \ 
--connect jdbc:mysql://node-21:3306/sqoopdb \ 
--username root \ 
--password hadoop \ 
--target-dir /wherequery12 \ 
--query 'select id,name,deg from emp WHERE  id>1203 and $CONDITIONS' \ 
--split-by id \ 
--fields-terminated-by '\t' \ 
--m 1 
2.4. 增量导入 
增量导入是仅导入新添加的表中的行的技术。 
--check-column (col)     用来作为判断的列名,如id 
--incremental (mode)     append:追加,比如对大于last-value指定的值之后的记录进行追加导入。lastmodified:最后的修改时间,追加last-value 指定的日期之后的记录 
--last-value (value)     指定自从上次导入后列的最大值(大于该指定的值),也可以自
己设定某一值 
假设新添加的数据转换成emp表如下: 
1206, satish p, grp des, 20000, GR 
下面的命令用于在EMP表执行增量导入: 
bin/sqoop import \ 
--connect jdbc:mysql://node-21:3306/sqoopdb \ 
--username root \ 
--password hadoop \ 
--table emp --m 1 \ 
--incremental append \ 
--check-column id \ 
--last-value 1205 
3. Sqoop 导出 
将数据从HDFS导出到RDBMS数据库导出前,目标表必须存在于目标数据库中。 
默认操作是从将文件中的数据使用INSERT语句插入到表中,更新模式下,是生成UPDATE 语句更新表数据。 
以下是export命令语法: 
$ sqoop export (generic-args) (export-args) 
3.1. 导出 HDFS 数据到 mysql 
数据是在HDFS 中“emp/”目录的emp_data文件中: 
1201,gopal,manager,50000,TP 
1202,manisha,preader,50000,TP 
1203,kalil,php dev,30000,AC 
1204,prasanth,php dev,30000,AC 
1205,kranthi,admin,20000,TP 
1206,satishp,grpdes,20000,GR 
首先需要手动创建mysql中的目标表: mysql> USE sqoopdb; mysql> CREATE TABLE employee (     id INT NOT NULL PRIMARY KEY,     name VARCHAR(20),     deg VARCHAR(20),    salary INT,    dept VARCHAR(10)); 然后执行导出命令: 
bin/sqoop export \ 
--connect jdbc:mysql://node-21:3306/sqoopdb \ 
--username root \ 
--password hadoop \ 
--table employee \ 
--export-dir /emp/emp_data 
还可以用下面命令指定输入文件的分隔符 
--input-fields-terminated-by '\t' 
 
如果运行报错如下: 

则需要把localhost更改为ip或者域名。示例如下,将点击流模型表导出到 mysql   
 
sqoop export \ 
--connect jdbc:mysql://hdp-node-01:3306/webdb --username root --password root  \ 
--table click_stream_visit  \ 
--export-dir /user/hive/warehouse/dw_click.db/click_stream_visit/datestr=2013-09-18 \ 
--input-fields-terminated-by '\001' 
 
<更多表的导出,可参照修改> 
      

五、 模块开发----工作流调度 
整个项目的数据按照处理过程,从数据采集到数据分析,再到结果数据的导出,一系列的任务可以分割成若干个azkaban的job单元,然后由工作流调度器调度执行。 
调度脚本的编写难点在于shell脚本。但是一般都是有固定编写模式。大家可以参考资料中的脚本进行编写。大体框架如下: 
#!/bin/bash 
 
#set java env 
 
#set hadoop env 
 
#设置一些主类、目录等常量 
 
#获取时间信息 
 
#shell主程序、结合流程控制(if....else)去分别执行shell命令。 
更多工作流及hql脚本定义见参考资料。 
      
六、 模块开发----数据可视化 
1. Echarts 介绍 
直观,生动,可交互,可个性化定制的数据可视化图表
ECharts是一款由百度前端技术部开发的,基于 Javascript的数据可视化图表库,提供。 
提供大量常用的数据可视化图表,底层基于ZRender(一个全新的轻量级canvas类库),创建了坐标系,图例,提示,工具箱等基础组件,并在此上构建出折线图(区域图)、柱状图(条状图)、散点图(气泡图)、饼图(环形图)、K线图、地图、力导向布局图以及和弦图,同时支持任意维度的堆积和多图表混合展现。 
 

      
2. Web 程序工程结构 
本项目是个纯粹的JavaEE项目,基于ssm的框架整合构建。使用maven的tomcat插件启动项目。 
 
      
3. 感受 Echarts—简单入门 
3.1. 下载 Echarts 
从官网下载界面选择你需要的版本下载,根据开发者功能和体积上的需求,提供了不同打包的下载,如果在体积上没有要求,可以直接下载完整版本。开发环境建议下载源代码版本,包含了常见的错误提示和警告。 
3.2. 页面引入 Echarts 
ECharts 3 开始只需要像普通的 JavaScript 库一样用 script 标签引入即可。 
 
<!DOCTYPE html> 
<html> 
<head> 
    <meta charset="utf-8"> 
    <!-- 引入 ECharts 文件 --> 
    <script src="echarts.min.js"></script> 
</head> </html>

  • 0
    点赞
  • 0
    评论
  • 2
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值