0%

[TOC]

Storm 简介

Storm 是个实时的、分布式以及具备高容错(协调 )的计算系统

  • Storm 进程常驻内存
  • Storm 数据不经过磁盘,在内存中处理

官网对 Storm 的解释:

Apache Storm is a free and open source distributed realtime computation system. Apache Storm makes it easy to reliably process unbounded streams of data, doing for realtime processing what Hadoop did for batch processing. Apache Storm is simple, can be used with any programming language, and is a lot of fun to use!

Apache Storm has many use cases: realtime analytics, online machine learning, continuous computation, distributed RPC, ETL, and more. Apache Storm is fast: a benchmark clocked it at over a million tuples processed per second per node. It is scalable, fault-tolerant, guarantees your data will be processed, and is easy to set up and operate.

Apache Storm integrates with the queueing and database technologies you already use. An Apache Storm topology consumes streams of data and processes those streams in arbitrarily complex ways, repartitioning the streams between each stage of the computation however needed. Read more in the tutorial.

官网地址: http://storm.apache.org/

Storm 与 MapReduce 的区别

计算框架 Storm MapReduce
概念 分布式实时流式计算 批处理离线计算
处理方式 realtime processing batch processing
处理数据量的级别 MB 、KB TB级 PB级
单位时间处理数据量
速度
模型 DAG 、Spout、Bolt 模型 Map+Reduce 模式
生命周期 常驻运行 反复启停

分布式承载数据的方式: 1、切片 2、径向全量 -> ???

Storm 计算模型

  • 整个图是 DAG(Directed Acyclic Graph)有向无环图

  • 水龙头: 表示数据源 spout

  • 小水滴: 表示一次数据量的大小 tuple

  • 大水滴: 表示一个计算单元 bolt

  • 闪电: 表示计算速度快,就像闪电一样

Storm 应用场景

  • 阿里巴巴的 JStorm 项目- 双 11 大屏幕
  • 腾讯的 QQ 同时在线人数图

Storm 特点

  • 架构
    Nimbus
    Supervisor
    Worker

  • 编程模型
    DAG (Topology)
    Spout
    Bolt

  • 数据传输
    ZMQ(twitter早期产品)-ZeroMQ 开源的消息传递框架,并不是一个MessageQueue

    Netty-Netty 是基于 NIO 的网络框架,更加高效。

之所以Storm 0.9版本之后使用Netty,是因为ZMQ的license和Storm的license不兼容。

  • 高可靠性
    异常处理
    消息可靠性保障机制(ACK)

  • 可维护性
    StormUI 图形化监控接口

Storm 计算模型详述

spout 将一个个 tuple 通过流的方式, 发送给一个或多个 blot 处理

  • Topology – DAG 有向无环图的实现
    对于Storm实时计算逻辑的封装
    即,由一系列通过数据流相互关联的 Spout、Bolt 所组成的拓扑结构
    生命周期:此拓扑只要启动就会一直在集群中运行,直到手动将其kill,否则不会终止
    (区别于MapReduce当中的 Job,MR当中的Job在计算执行完成就会终止)
  • Tuple – 元组
    Stream 中最小数据组成单元
  • Stream – 数据流
    从Spout中源源不断传递数据给Bolt、以及上一个Bolt传递数据给下一个Bolt,所形成的这些数据通道即叫做Stream
    Stream声明时需给其指定一个Id(默认为Default)
    实际开发场景中,多使用单一数据流,此时不需要单独指定StreamId

  • Spout – 数据源
    拓扑中数据流的来源。一般会从指定外部的数据源读取元组(Tuple)发送到拓扑(Topology)中
    一个 Spout 可以发送多个数据流(Stream)
    可先通过 OutputFieldsDeclarer 中的 declare 方法声明定义的不同数据流,发送数据时通过SpoutOutputCollector 中的 emit 方法指定数据流Id(streamId)参数将数据发送出去
    Spout中最核心的方法是 nextTuple,该方法会被Storm线程不断调用、主动从数据源拉取数据,再通过emit方法将数据生成元组(Tuple)发送给之后的Bolt计算
  • Bolt – 数据流处理组件
    • 拓扑中数据处理均有 Bolt 完成。对于简单的任务或者数据流转换,单个 Bolt 可以简单实现;更加复杂场景往往需要多个 Bolt 分多个步骤完成
    • 一个 Bolt 可以发送多个数据流(Stream)
      可先通过 OutputFieldsDeclarer 中的 declare 方法声明定义的不同数据流,发送数据时通过SpoutOutputCollector 中的 emit 方法指定数据流 Id(streamId)参数将数据发送出去
    • Bolt 中最核心的方法是 execute 方法,该方法负责接收到一个元组(Tuple)数据、真正实现核心的业务逻辑
  • Stream Grouping – 数据流分组(即数据分发策略)

Storm 案例

Storm 数据累加

  • WsSpout.java: 将消息发送给 Blot

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    public class WsSpout extends BaseRichSpout {

    Map conf;
    TopologyContext context;
    SpoutOutputCollector collector;
    int i = 0;

    /**
    * 初始化
    */
    @Override
    public void open(Map conf, TopologyContext context
    , SpoutOutputCollector collector) {
    this.conf = conf;
    this.context = context;
    this.collector = collector;
    }

    /**
    * 获取和发送消息
    */
    @Override
    public void nextTuple() {
    i++;
    List val = new Values(i);
    this.collector.emit(val);
    System.err.println("spout----------:" + i);
    Utils.sleep(1000); // 休眠一秒
    }

    /**
    * 声明字段名称
    */
    @Override
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
    declarer.declare(new Fields("nums"));
    }

    }
  • WsBlot.java: 接收消息并处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public class WsBlot extends BaseRichBolt {

    Map stormConf;
    TopologyContext context;
    OutputCollector collector;
    int sum = 0;

    @Override
    public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
    this.stormConf = stormConf;
    this.context = context;
    this.collector = collector;
    }

    @Override
    public void execute(Tuple input) {
    Integer num = input.getIntegerByField("nums");
    sum += num;
    System.err.println("nums--------:"+sum);
    try {
    Thread.sleep(2000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }

    @Override
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
    declarer.declare(new Fields("nums"));
    }

    }
  • MainTest.java: 测试类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class MainTest {
    /**
    * 测试
    *
    * @param args 命令行参数
    */
    public static void main(String[] args) {
    // 创建拓扑构建者对象
    TopologyBuilder builder = new TopologyBuilder();
    // 设置 spout
    builder.setSpout("wsspout", new WsSpout());
    // 设置 blot
    builder.setBolt("wsbolt", new WsBlot()).shuffleGrouping("wsspout");
    // 创建本地集群
    LocalCluster locCluster = new LocalCluster();
    // 提交拓扑
    locCluster.submitTopology("ws", new Config(), builder.createTopology());

    }
    }

Storm Word Count

  • WcSpout.java: 发送消息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public class WcSpout extends BaseRichSpout {

    SpoutOutputCollector collector;

    String[] artile = { "WelCome to Bejing", "WelCome to AnHui", "WelCome to Lujiang" };

    Random random = new Random();

    @Override
    public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
    this.collector = collector;
    }

    @Override
    public void nextTuple() {
    List val = new Values(artile[this.random.nextInt(artile.length)]);
    this.collector.emit(val);
    System.err.println("line--------:"+val.get(0));
    Utils.sleep(2000);
    }

    @Override
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
    declarer.declare(new Fields("line"));
    }

    }
  • WcBolt1.java: 对一行数据进行切分,分别发送给下一个 bolt

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public class WcBolt1 extends BaseRichBolt {

    OutputCollector collector;

    @Override
    public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
    this.collector = collector;
    }

    @Override
    public void execute(Tuple input) {
    String line = input.getString(0);
    String[] words = line.split(" ");
    for (String wd : words) {
    List val = new Values(wd);
    this.collector.emit(val);
    //System.err.println("wcbolt1.......:"+wd);
    }
    Utils.sleep(200);
    }

    @Override
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
    declarer.declare(new Fields("wd"));
    }

    }
  • WcBolt2.java:统计单词的次数,并放入 Map 集合中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public class WcBolt2 extends BaseRichBolt {

    OutputCollector collector;

    Map<String, Integer> wordCount =new HashMap<>();

    @Override
    public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
    this.collector = collector;
    }

    @Override
    public void execute(Tuple input) {
    // Welcome
    String wd = input.getStringByField("wd");
    if (this.wordCount.containsKey(wd)) {
    int count = this.wordCount.get(wd);
    this.wordCount.put(wd, ++count);

    } else {
    this.wordCount.put(wd, 1);
    }
    System.err.println(this.wordCount);
    Utils.sleep(500);
    }

    @Override
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
    declarer.declare(new Fields("w"));
    }

    }
  • WordCountMainTest.java: 测试类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class WordCountMainTest {
    public static void main(String[] args) {
    TopologyBuilder builder = new TopologyBuilder();
    builder.setSpout("wcspout", new WcSpout());
    builder.setBolt("wcbolt1", new WcBolt1()).shuffleGrouping("wcspout");
    // fieldGrouping: 数据分发策略
    builder.setBolt("wcbolt2", new WcBolt2(),3).fieldsGrouping("wcbolt1", new Fields("wd"));
    // builder.setBolt("wcbolt2", new WcBolt2()).shuffleGrouping("wcbolt1");
    LocalCluster cluster = new LocalCluster();
    cluster.submitTopology("wordcount", new Config(), builder.createTopology());
    }
    }

Storm 数据分发策略*

  1. Shuffle Grouping*
    随机分组,随机派发stream里面的tuple,保证每个bolt task接收到的tuple数目大致相同。
    轮询,平均分配

  2. Fields Grouping*
    按字段分组,比如,按”user-id”这个字段来分组,那么具有同样”user-id”的 tuple 会被分到相同的Bolt里的一个task, 而不同的”user-id”则可能会被分配到不同的task。

  3. All Grouping*
    广播发送,对于每一个tuple,所有的bolts都会收到

  4. Global Grouping
    全局分组,把tuple分配给task id最低的task 。

  5. None Grouping
    不分组,这个分组的意思是说stream不关心到底怎样分组。目前这种分组和Shuffle grouping是一样的效果。 有一点不同的是storm会把使用none grouping的这个bolt放到这个bolt的订阅者同一个线程里面去执行(未来Storm如果可能的话会这样设计)。

  6. Direct Grouping
    指向型分组, 这是一种比较特别的分组方法,用这种分组意味着消息(tuple)的发送者指定由消息接收者的哪个task处理这个消息。只有被声明为 Direct Stream 的消息流可以声明这种分组方法。而且这种消息tuple必须使用 emitDirect 方法来发射。消息处理者可以通过 TopologyContext 来获取处理它的消息的task的id (OutputCollector.emit方法也会返回task的id)

  7. Local or shuffle grouping
    本地或随机分组。如果目标bolt有一个或者多个task与源bolt的task在同一个工作进程中,tuple将会被随机发送给这些同进程中的tasks。否则,和普通的Shuffle Grouping行为一致

  8. customGrouping
    自定义,相当于mapreduce那里自己去实现一个partition一样。

Storm 架构设计*

角色作用

  • Nimbus
    • 资源调度
    • 任务分配
    • 接收 jar 包
  • Supervisor
    • 接收nimbus分配的任务
    • 启动、停止自己管理的 worker 进程(当前 supervisor 上 worker 数量由配置文件设定)
  • Worker
    • 运行具体处理运算组件的进程(每个Worker对应执行一个Topology的子集)
    • worker 任务类型,即 spout 任务、bolt 任务两种
    • 启动 executor
      (executor即worker JVM进程中的一个java线程,一般默认每个executor负责执行一个task任务)
  • Zookeeper
    • 负责角色的健康检查

Storm 架构与 Hadoop 架构比较

Hadoop Storm
主节点 ResourceManager Nimbus
从节点 NodeManager Supervisor
应用程序 Job Topology
工作进程 Child Worker
计算模型 Map/Reduce(split,map,shuffle,reduce) Spout/Bolt

Storm 任务提交流程

Storm 并发机制*

Worker Executor Task 之间的联系

  • Work Process 进程
    一个 Topology 拓扑会包含一个或多个 Worker(每个Worker进程只能从属于一个特定的Topology)

这些Worker进程会并行跑在集群中不同的服务器上,即一个Topology拓扑其实是由并行运行在Storm集群中

多台服务器上的进程所组成

  • Executor (Threads) 线程

    Executor 是由 Worker 进程中生成的一个线程

    每个 Worker 进程中会运行拓扑当中的一个或多个 Executor 线程

    一个 Executor 线程中可以执行一个或多个 Task 任务(默认每个Executor只执行一个Task任务),但是这些

    Task 任务都是对应着同一个组件(Spout、Bolt)。

  • Task 任务

    实际执行数据处理的最小单元,每个 task 即为一个 Spout 或者一个 Bolt

    Task 数量在整个 Topology 生命周期中保持不变,Executor 数量可以变化或手动调整

    默认情况下,Task 数量和 Executor 是相同的,即每个 Executor 线程中默认运行一个 Task 任务

调整 Worker Executor Task 的数量

  • 设置 Worker 进程数

    1
    Config.setNumWorkers(int workers)
  • 设置 Executor 线程数

    1
    2
    3
    4
    TopologyBuilder.setSpout(String id, IRichSpout spout, Number parallelism_hint)
    TopologyBuilder.setBolt(String id, IRichBolt bolt, Number parallelism_hint)

    //:其中, parallelism_hint即为executor线程数
  • 设置 Task 数量

    1
    ComponentConfigurationDeclarer.setNumTasks(Number val)

    例:

    1
    2
    3
    4
    5
    6
    7
    8
    Config conf = new Config() ;
    conf.setNumWorkers(2);

    TopologyBuilder topologyBuilder = new TopologyBuilder();
    topologyBuilder.setSpout("spout", new MySpout(), 1);
    topologyBuilder.setBolt("green-bolt", new GreenBolt(), 2)
    .setNumTasks(4)
    .shuffleGrouping("blue-spout);

rebalance 再平衡

动态调整 Topology 拓扑的 Worker 进程数量、以及 Executor 线程数量

支持两种调整方式:
1、通过 Storm UI
2、通过 Storm CLI

通过 Storm CLI 动态调整:

例:storm rebalance mytopology -n 5 -e blue-spout=3 -e yellow-bolt=10

将mytopology拓扑worker进程数量调整为5个

“ blue-spout ” 所使用的线程数量调整为3个

“ yellow-bolt ”所使用的线程数量调整为10个

Storm 通信机制

Worker 进程间的数据通信

  • ZMQ
    ZeroMQ 开源的消息传递框架,并不是一个MessageQueue
  • Netty
    Netty是基于 NIO 的网络框架,更加高效。(之所以Storm 0.9版本之后使用 Netty,是因为ZMQ的license和Storm的license不兼容。)

Worker内部的数据通信*

Disruptor 实现了“队列”的功能。可以理解为一种事件监听或者消息处理机制,即在队列当中一边由生产者放入消息数据,另一边消费者并行取出消息数据处理。

Storm 安装

伪分布式

  • 系统环境

    • Java 环境
  • 搭建

    伪分布式搭建在节点 node01(192.168.170.101)上

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    #上传 storm 软件压缩包到 Linux 服务器上
    tar xf apache-storm-0.10.0.tar.gz -C /opt/sxt

    # 配置 storm 环境变量
    export STORM_HOME=/opt/sxt/apache-storm-0.10.0
    export PATH=$PATH:$STORM_HOME/bin
    # cd $STROM_HOME 根目录

    # 首先启动 dev-zookeeper
    storm dev-zookeeper >> ./logs/dev-zookeeper.out 2>&1 &

    # 再启动 nimbus
    storm nimbus >> ./logs/nimbus.out 2>&1 &

    # 再启动 spervisor
    storm supervisor >> ./logs/supervisor.out 2>&1 &

    # 最后启动 ui,注意:使用 jps 命令查看 ui 进程的名称为 core
    storm ui >> ./logs/ui.out 2>&1 &

    # 使用 jps 命令
    [root@node01 apache-storm-0.10.0]# jps
    4661 nimbus
    3932 DFSZKFailoverController
    4998 Jps
    4895 core
    4563 dev_zookeeper
    4805 supervisor
    3790 JournalNode

    # 打开 node01:8080 端口
    http://node01:8080

    小技巧: 通过 storm 或者 storm help 查看命令的如何使用。 storm help 参数查看具体命令的使用

  • 部署伪分布式任务

    在部署之前,确保 storm 中的必要的角色(zk、nimbus、supervisor)都已经启动

    1
    2
    3
    4
    # 将 IDE 中的项目打包成 jar 文件,并上传到 Linux 服务器上
    # 运行 jar 包
    storm jar jar的位置 类的全限定路径名 [参数]
    若未给定参数,则在本地运行。若给定参数,则将提交给集群运行

完全分布式

  • 节点分布

    host/role 192.168.170.102/node02 192.168.170.103/node03 192.168.170.104/node04
    nimbus *
    supervisor * *
    worker * * *
  • 系统环境

    • 安装 Java 环境 ,并保证 JDK 版本在 1.6 以上
    • 安装 Python 环境,并保存 Python 在 2.6.6 以上即可
  • Storm 完全分布式搭建

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    # 上传 storm软件压缩包 到 node02 节点 上
    # 解压
    tar zxvf apache-storm-0.10.0.tar.gz -C /opt/sxt
    # 配置 storm 环境变量

    # 进入 $STROM_HOME 根目录下
    # cd conf
    # 编辑 storm.yaml 文件,内容如下:
    storm.zookeeper.servers:
    - "node02"
    - "node03"
    - "node04"

    nimbus.host: "node02"

    storm.local.dir: "/tmp/storm"

    supervisor.slots.ports:
    - 6700
    - 6701
    - 6702
    - 6703

    # 分发到 node03、node04 节点上
    scp -r apache-storm-0.10.0/ root@node03:`pwd`
    scp -r apache-storm-0.10.0/ root@node04:`pwd
    #-------------------------------------------------------
    # 以下操作都是在 $STROM_HOME 根目录下执行的
    # 在 node02、node03、node04 节点上创建 logs 目录
    mkdir logs
    # 在 node02 启动
    storm nimbus >> ./logs/nimbus.out 2>&1 &
    storm ui >> ./logs/ui.out 2>&1 &

    # node03、node04 启动
    storm supervisor >> supervisor.out 2>&1 &

    # 访问 http://node02:8080

    # 上传任务 jar
    storm jar jar的路径 主类的全限定路径名 任务名

Storm 容错保护机制

集群节点宕机

  • Nimbus服务器
    单点故障?

    当 Nimbus 出现了宕机,并不会直接影响 Storm 的运行,因为 Nimbus 并不是直接与 Supervisor 进行交互,而是与 Zookeeper 协调者之间进行交互,即 Zookeeper 将 Nimbus 与 Supervisor 的直接关系,转为间接关系。但有一个情况下,Supervisor 中某个节点宕机了,Zookeeper 会该 Supervisor 消息传递给 Nimbus,这时 Nimbus 必须宕机后恢复重启,继续工作。

  • 非 Nimbus 服务器
    故障时,该节点上所有 Task 任务都会超时,Nimbus 会将这些 Task 任务重新分配到其他服务器上运行

进程挂掉

  • Worker
    挂掉时,Supervisor会重新启动这个进程。如果启动过程中仍然一直失败,并且无法向Nimbus发送心跳,Nimbus会将该Worker重新分配到其他服务器上
  • Supervisor
    无状态(所有的状态信息都存放在Zookeeper中来管理)
    快速失败(每当遇到任何异常情况,都会自动毁灭)
  • Nimbus
    无状态(所有的状态信息都存放在Zookeeper中来管理)
    快速失败(每当遇到任何异常情况,都会自动毁灭)

消息的完整性

从Spout中发出的Tuple,以及基于他所产生Tuple(例如上个例子当中Spout发出的句子,以及句子当中单词的tuple等)由这些消息就构成了一棵tuple树。当这棵tuple树发送完成,并且树当中每一条消息都被正确处理,就表明spout发送消息被“完整处理”,即消息的完整性

  • ack 机制 –消息完整性的实现机制

    1. Storm的拓扑当中特殊的一些任务
    2. 负责跟踪每个Spout发出的Tuple的DAG(有向无环图)

    注意:ack 无法保证数据不被重复计算,但是可以保证数据至少被正确处理一次。

    在实际的使用中,由于处理的整体数据量大,出现个别消息不完整,是可以容忍的。

  • 事务

    无,后续

DRPC -同步实时分析

Distributed Remote Procedure Call

DRPC 是通过一个 DRPC 服务端(DRPC server)来实现分布式 RPC 功能的。
DRPC Server 负责接收 RPC 请求,并将该请求发送到 Storm中运行的 Topology,等待接收 Topology 发送的处理结果,并将该结果返回给发送请求的客户端。
(其实,从客户端的角度来说,DPRC 与普通的 RPC 调用并没有什么区别。)

DRPC设计目的:

为了充分利用 Storm 的计算能力实现==高密度的并行实时计算==,Storm 接收若干个数据流输入,数据在 Topology 当中运行完成,然后通过 DRPC 将结果进行输出。

DRPC架构图

客户端通过向 DRPC 服务器发送待执行函数的名称以及该函数的参数来获取处理结果。实现该函数的拓扑使用一个 DRPCSpout 从 DRPC 服务器中接收一个函数调用流。DRPC 服务器会为每个函数调用都标记了一个唯一的 id。随后拓扑会执行函数来计算结果,并在拓扑的最后使用一个名为 ReturnResults 的 bolt 连接到 DRPC 服务器,根据函数调用的 id 来将函数调用的结果返回。

DRPC 实现

  1. 通过 LinearDRPCTopologyBuilder (该方法也过期,不建议使用)
    该方法会自动为我们设定 Spout、将结果返回给 DRPC Server 等,我们只需要将 Topology 实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* This topology is a basic example of doing distributed RPC on top of Storm. It
* implements a function that appends a "!" to any string you send the DRPC
* function.
* <p/>
* See https://github.com/nathanmarz/storm/wiki/Distributed-RPC for more
* information on doing distributed RPC on top of Storm.
*/
public class BasicDRPCTopology {
public static class ExclaimBolt extends BaseBasicBolt {
@Override
public void execute(Tuple tuple, BasicOutputCollector collector) {
String input = tuple.getString(1);
collector.emit(new Values(tuple.getValue(0), input + "!"));
}

@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("id", "result"));
}

}

public static void main(String[] args) throws Exception {
LinearDRPCTopologyBuilder builder = new LinearDRPCTopologyBuilder("exclamation");
builder.addBolt(new ExclaimBolt(), 3);

Config conf = new Config();

if (args == null || args.length == 0) {
LocalDRPC drpc = new LocalDRPC();
LocalCluster cluster = new LocalCluster();

cluster.submitTopology("drpc-demo", conf, builder.createLocalTopology(drpc));

for (String word : new String[] { "hello", "goodbye" }) {
System.err.println("Result for \"" + word + "\": " + drpc.execute("exclamation", word));
}

cluster.shutdown();
drpc.shutdown();
} else {
conf.setNumWorkers(3);
StormSubmitter.submitTopologyWithProgressBar(args[0], conf, builder.createRemoteTopology());
}
}
}
  1. 直接通过普通的拓扑构造方法 TopologyBuilder 来创建 DRPC 拓扑,需要手动设定好开始的 DRPCSpout 以及结束的 ReturnResults
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class ManualDRPC {
public static class ExclamationBolt extends BaseBasicBolt {

@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("result", "return-info"));
}

@Override
public void execute(Tuple tuple, BasicOutputCollector collector) {
String arg = tuple.getString(0);
Object retInfo = tuple.getValue(1);
collector.emit(new Values(arg + "!!!", retInfo));
}

}

public static void main(String[] args) {
TopologyBuilder builder = new TopologyBuilder();
LocalDRPC drpc = new LocalDRPC();

DRPCSpout spout = new DRPCSpout("exclamation", drpc);
builder.setSpout("drpc", spout);
builder.setBolt("exclaim", new ExclamationBolt(), 3).shuffleGrouping("drpc");
builder.setBolt("return", new ReturnResults(), 3).shuffleGrouping("exclaim");

LocalCluster cluster = new LocalCluster();
Config conf = new Config();
cluster.submitTopology("exclaim", conf, builder.createTopology());

System.err.println(drpc.execute("exclamation", "aaa"));
System.err.println(drpc.execute("exclamation", "bbb"));

}
}

DRPC 远程模式

  • 节点分布

    host/role node02 node03 node04
    nimbus *
    supervisor * *
    drpc *
  • 在 storm 完全分布式的基础上,修改 storm.yarm 文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    cd $STORM_HOME/conf/

    修改配置文件conf/storm.yaml
    drpc.servers:
    - "node1“

    启动DRPC Server
    bin/storm drpc &

    通过StormSubmitter.submitTopology提交拓扑
    StormSubmitter
    .submitTopologyWithProgressBar(args[0], conf, builder.createRemoteTopology());
  • DRPC 客户端代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class MyDRPCclient {

    /**
    * @param args
    */
    public static void main(String[] args) {


    DRPCClient client = new DRPCClient("node02", 3772);

    try {
    String result = client.execute("exclamation", "11,22");

    System.out.println(result);
    } catch (TException e) {
    e.printStackTrace();
    } catch (DRPCExecutionException e) {
    e.printStackTrace();
    }
    }
    }

kafka*

Kafka 是一个分布式的消息队列系统 (Message Queue)。

kafka 集群有多个 Broker 服务器组成,每个类型的消息被定义为 topic。

同一 topic 内部的消息按照一定的 key 和算法被分区 (partition) 存储在不同的 Broker上。

消息生产者 producer 和消费者 consumer 可以在多个 Broker上生产/消费 topic

Topics and Logs:

Topic 即为每条发布到 Kafka 集群的消息都有一个类别,topic 在 Kafka 中可以由多个消费者订阅、消费。

每个 topic 包含一个或多个partition(分区),partition 数量可以在创建 topic 时指定,每个分区日志中记录了该分区的数据以及索引信息。如下图:

Kafka 只保证一个分区内的消息有序,不能保证一个主题的不同分区之间的消息有序。

如果你想要保证所有的消息都绝对有序可以只为一个主题分配一个分区。

分区会给每个消息记录分配一个顺序 ID 号(偏移量), 能够唯一地标识该分区中的每个记录。

Kafka 集群保留所有发布的记录,不管这个记录有没有被消费过,Kafka 提供相应策略通过配置从而对旧数据处理。

实际上,每个消费者唯一保存的元数据信息就是消费者当前消费日志的位移位置。位移位置是由消费者控制,即、消费者可以通过修改偏移量读取任何位置的数据。

角色

  • Distribution – 分布式

  • Producers – 生产者

    指定 topic 来发送消息到 Kafka Broker

  • Consumers – 消费者

    根据 topic 消费相应的消息

Kafka集群部署

  • 集群规划

    node02/192.168.170.102 node03/192.168.170.103 node04 /192.168.170.104
    zookeeper * * *
    kafka * * *
  • 安装与配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # 在 node02 节点上
    # 上传 kakfa 软件安装包到 Linux 服务器上
    # 解压到指定目录下 /opt/sxt
    tar xf kafka_2.10-0.9.0.1.tgz -C /opt/sxt
    # 配置环境变量
    export KAFKA_HOME=/opt/sxt/kafka_2.10-0.9.0.1
    # 进入 $KAFKA_HOME/config 下
    # 修改 vi server.properties 文件
    # root directory for all kafka znodes.
    zookeeper.connect=node02:2181,node03:2181,node04:2181
    # 分发,进入 /opt/sxt 目录下
    scp -r kafka_2.10-0.9.0.1/ node03:`pwd`
    scp -r kafka_2.10-0.9.0.1/ node04:`pwd`
    # 在 node03、node04 节点上修改 server.proerties
    # The id of the broker. This must be set to a unique integer for each broker.
    broker.id=1
  • 启动

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    # 进入 $KAFKA_HOME
    bin/kafka-server-start.sh config/server.properties # 阻塞当前窗口

    # 新打开一个 shell 窗口,node02
    # 创建 topic
    kafka-topics.sh --zookeeper node02:2181,node03:2181,node04:2181 --create --replication-factor 2 --partition 3 --topic test

    # 查看创建好的 topic
    kafka-topics.sh --zookeeper node02:2181,node03:2181,node04:2181 --list
    (参数说明:
    --replication-factor:指定每个分区的复制因子个数,默认1个
    --partitions:指定当前创建的kafka分区数量,默认为1个
    --topic:指定新建topic的名称)

    # 查看 “test” topic 的描述
    kafka-topics.sh --zookeeper node02:2181,node03:2181,node04:2181 --describe test
    Topic:test PartitionCount:3 ReplicationFactor:2 Configs:
    Topic: test Partition: 0 Leader: 0 Replicas: 0,2 Isr: 0,2
    Topic: test Partition: 1 Leader: 1 Replicas: 1,0 Isr: 1,0
    Topic: test Partition: 2 Leader: 2 Replicas: 2,1 Isr: 2,1

    # 创建生产者
    kafka-console-producer.sh --broker-list node02:9092,node03:9092,node04:9092 --topic test

    # 创建消费者
    kafka-console-consumer.sh --zookeeper node02:2181,node03:2181,node04:2181 --from-beginning --topic test

kafka 与 flume 整合

  • 安装 flume

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    解压jar包
    mv conf/flume-env.sh.template flume-env.sh
    vi flume-env.sh java环境变量
    ./bin flume-ng version
    /conf/下 创建配置文件fk.conf内容如下:
    a1.sources = r1
    a1.sinks = k1
    a1.channels = c1

    # Describe/configure the source
    a1.sources.r1.type = avro
    a1.sources.r1.bind = node01
    a1.sources.r1.port = 41414

    # Describe the sink
    a1.sinks.k1.type = org.apache.flume.sink.kafka.KafkaSink
    a1.sinks.k1.topic = testflume
    a1.sinks.k1.brokerList = node02:9092,node03:9092,node04:9092
    a1.sinks.k1.requiredAcks = 1
    a1.sinks.k1.batchSize = 20

    # Use a channel which buffers events in memory
    a1.channels.c1.type = memory
    a1.channels.c1.capacity = 1000000
    a1.channels.c1.transactionCapacity = 10000

    # Bind the source and sink to the channel
    a1.sources.r1.channels = c1
    a1.sinks.k1.channel = c1
  • 启动

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 启动zk集群
    A、启动Kafka集群。
    kafka-server-start.sh config/server.properties

    B、配置Flume集群,并启动Flume集群。 # 注意在存在 kafka.conf 的目录中启动
    flume-ng agent -n a1 -c conf -f kafka.conf -Dflume.root.logger=DEBUG,console

    # 创建 topic testflume 消费者
    kafka-console-consumer.sh --zookeeper node02:2181,node03:2181,node04:2181 --from-beginning --topic testflume

    # 创建 topic LogError 消费者
    kafka-console-consumer.sh --zookeeper node02:2181,node03:2181,node04:2181 --from-beginnin --topic LogError
  • Java 发送 RPC 到 Flume

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    /**
    * Flume官网案例
    * http://flume.apache.org/FlumeDeveloperGuide.html
    * @author root
    */
    public class RpcClientDemo {

    public static void main(String[] args) {
    MyRpcClientFacade client = new MyRpcClientFacade();
    // Initialize client with the remote Flume agent's host and port
    client.init("node01", 41414);

    // Send 10 events to the remote Flume agent. That agent should be
    // configured to listen with an AvroSource.
    for (int i = 100; i < 110; i++) {
    String sampleData = "Hello Flume! ERROR" + i;
    client.sendDataToFlume(sampleData);
    System.out.println("发送数据:" + sampleData);
    }

    client.cleanUp();
    }
    }

    class MyRpcClientFacade {
    private RpcClient client;
    private String hostname;
    private int port;

    public void init(String hostname, int port) {
    // Setup the RPC connection
    this.hostname = hostname;
    this.port = port;
    this.client = RpcClientFactory.getDefaultInstance(hostname, port);
    // Use the following method to create a thrift client (instead of the
    // above line):
    // this.client = RpcClientFactory.getThriftInstance(hostname, port);
    }

    public void sendDataToFlume(String data) {
    // Create a Flume Event object that encapsulates the sample data
    Event event = EventBuilder.withBody(data, Charset.forName("UTF-8"));

    // Send the event
    try {
    client.append(event);
    } catch (EventDeliveryException e) {
    // clean up and recreate the client
    client.close();
    client = null;
    client = RpcClientFactory.getDefaultInstance(hostname, port);
    // Use the following method to create a thrift client (instead of
    // the above line):
    // this.client = RpcClientFactory.getThriftInstance(hostname, port);
    }
    }

    public void cleanUp() {
    // Close the RPC connection
    client.close();
    }
    }

flume 与 Storm 整合

http://storm.apache.org/about/integrates.html

Flume 、Storm 、Kafka 整合架构

项目案例

模拟电信项目

事务

  • 事务性拓扑(Transactional Topologies)

  • 保证消息(tuple)被且仅被处理一次

Design1

强顺序流(强有序)

引入事务(transaction)的概念,每个transaction(即每个tuple)关联一个transaction id。
Transaction id 从 1开始,每个 tuple 会按照顺序+1。
在处理 tuple 时,将处理成功的 tuple 结果以及 transaction id 同时写入数据库中进行存储。

两种情况:
1、当前transaction id与数据库中的transaction id不一致
2、两个transaction id相同

缺点:
一次只能处理一个tuple,无法实现分布式计算

Design2

强顺序的Batch流

事务(transaction)以 batch 为单位,即把一批 tuple 称为一个 batch,每次处理一个 batch。
每个 batch(一批 tuple)关联一个 transaction id
每个 batch 内部可以并行计算

缺点:空余占用资源

Design3

  • Storm’s design

    将 Topology 拆分为两个阶段:

    1. Processing phase
      允许并行处理多个 batch
    2. Commit phase
      保证batch的强有序,一次只能处理一个batch
  • Design details

    • Manages state - 状态管理
      Storm 通过 Zookeeper 存储所有 transaction 相关信息(包含了:当前transaction id 以及batch的元数据信息)

    • Coordinates the transactions - 协调事务
      Storm 会管理决定 transaction 应该处理什么阶段(processing、committing)

    • Fault detection - 故障检测
      Storm 内部通过 Acker 机制保障消息被正常处理(用户不需要手动去维护)

    • First class batch processing API
      Storm 提供 batch bolt 接口

三种事务

1、普通事务

2、Partitioned Transaction - 分区事务

3、Opaque Transaction - 不透明分区事务

[TOC]

CDH 简介

为什么 在 Hadoop 2.x 中 HDFS 中有 ZKFC 进程,而 yarn 却没有?

在 Hadoop 1.x 升级到 Hadoop 2.x 的过程中,考虑到向下兼容的问题,NameNode 进程没有嵌入 ZKFC 中的代码,而另外开辟一个进程 ZKFC 。再者由于 Hadoop 1.x 中没有 yarn 组件,Hadoop 2.x 中才出现的 yarn 组件,所以 yarn 不用考虑向下兼容的问题,即 ResourceManager 进程就直接嵌入 ZKFC 中的代码,只运行一个进程。

Apache Hadoop 不足之处

  • 版本管理混乱
  • 部署过程繁琐、升级过程复杂
  • 兼容性差
  • 安全性低

Hadoop 发行版

  • Apache Hadoop
  • Cloudera’s Distribution Including Apache Hadoop(CDH)
  • Hortonworks Data Platform (HDP)
  • MapR
  • EMR

Cloudera’s Distribution, including Apache Hadoop

是Hadoop众多分支中的一种,由Cloudera维护,基于稳定版本的Apache Hadoop构建

提供了Hadoop的核心

  • 可扩展存储
  • 分布式计算

基于Web的用户界面

CDH 框架

CDH 框架图

Impala 是基于内存计算的,执行 SQL ,速度比 Hive 要快

CDH 的优点

  • 版本划分清晰

  • 版本更新速度快

  • 支持Kerberos安全认证

  • 文档清晰

  • 支持多种安装方式(Cloudera Manager方式)

CDH 安装方式

  • Cloudera Manager
  • Yum
  • Rpm
  • Tarball

CDH5.4
http://archive.cloudera.com/cdh5/

Cloudera Manager5.4.3:
http://www.cloudera.com/downloads/manager/5-4-3.html

CDH 安装

Cloudera Manager 简介

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Server
管理控制台服务器和应用程序逻辑
负责软件安装、配置
启动和停止服务
管理服务运行的群集
Agent
安装在每台主机上
负责启动和停止进程,配置,监控主机
Management Service
由一组角色组成的服务,执行各种监视、报警和报告功能
Database
Cloudera Repository
Clients
Admin Console
API

Cloudera Manager 部署

  1. 系统环境准备

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    # 使用 xshell 登录时
    # 不能 open 方式登录,可能会影响后面 ssh 免密
    # 而是采用 ssh 方式登录

    ssh 免密钥
    ssh localhost 方式创建本地目录

    1、网络配置
    vi /etc/sysconfig/network
    vi /etc/hosts

    2、SSH免密钥登录
    ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa
    ssh-copy-id

    3、防火墙关闭
    service iptables stop
    chkconfig iptables off

    4、SELINUX关闭
    setenforce 0
    vi /etc/selinux/config (SELINUX=disabled)

    5、安装JDK配置环境变量
    export JAVA_HOME=/usr/java/jdkXXX
    export PATH=$JAVA_HOME/bin:$PATH
    export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar

    6、安装NTP
    # 获取阿里云
    curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo
    # 更新 yum 缓存
    yum makecache
    yum install -y ntp
    # 设置开机启动
    chkconfig ntpd on
    # 启动 ntpd 服务
    service ntpd start
    # 设置时间同步
    ntpdate ntp1.aliyun.com

    7、安装配置mysql
    yum install -y mysql-server
    GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123' WITH GRANT OPTION;
    flush privileges

    8、下载第三方依赖包
    yum install -y chkconfig python bind-utils psmisc libxslt zlib sqlite cyrus-sasl-plain cyrus-sasl-gssapi fuse fuse-libs redhat-lsb
  1. cloudera Manager 安装

    集群分发时,一定要先分发,后启动

    HOST node06 node07 node08
    IP 192.168.170.106 192.168.170.107 192.168.170.108
    Server *
    Agent * * *
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    1、安装Cloudera Manager Server、Agent
    mkdir /opt/cloudera-manager # (node06,node07,node08)
    tar xvzf cloudera-manager*.tar.gz -C /opt/cloudera-manager

    2、创建用户cloudera-scm # (node06,node07,node08)
    useradd --system --no-create-home --shell=/bin/false --comment "Cloudera SCM User" cloudera-scm

    3、配置CM Agent # (node06)
    修改文件/opt/cloudera-manager/cm-5.4.3/etc/cloudera-scm-agent/config.ini中server_host

    4、配置CM Server数据库 # (node06)
    拷贝mysql jar文件到目录 /usr/share/java/
    注意jar包名称要修改为mysql-connector-java.jar

    # node06
    grant all on *.* to 'temp'@'%' identified by 'temp' with grant option;
    cd /opt/cloudera-manager/cm-5.4.3/share/cmf/schema/
    ./scm_prepare_database.sh mysql temp -h node06 -utemp -ptemp --scm-host node06 scm scm scm
    格式:数据库类型、数据库、数据库服务器、用户名、密码、cm server服务器

    Server节点 #(node06)
    mkdir -p /opt/cloudera/parcel-repo
    chown cloudera-scm:cloudera-scm /opt/cloudera/parcel-repo
    Agent节点 # (node06,node07,node08)
    mkdir -p /opt/cloudera/parcels
    chown cloudera-scm:cloudera-scm /opt/cloudera/parcels

    6、制作CDH本地源 #(node06)
    下载好文件CDH-5.4.0-1.cdh5.4.0.p0.27-el6.parcel以及manifest.json,将这两个文件放到server节点的/opt/cloudera/parcel-repo下。
    打开manifest.json文件,里面是json格式的配置,找到与下载版本相对应的hash码,新建文件,文件名与你的parel包名一致,并加上.sha后缀,将hash码复制到文件中保存。

    7、分发
    cd /opt/cloudera-manager/
    scp -r ./* root@node07:`pwd`
    scp -r ./* root@node08:`pwd`

    8、启动CM Server、Agent
    cd /opt/cloudera-manager/cm-5.4.3/etc/init.d/
    ./cloudera-scm-server start
    Sever首次启动会自动创建表以及数据,不要立即关闭或重启,否则需要删除所有表及数据重新安装
    ./cloudera-scm-agent start

小技巧: 1. 若运行某个指令执行任务阻塞当前 shell 窗口,且想中断运行该任务,若 Ctrl + C不能中断停止,可使用 Ctrl + Z 将当前任务放到后台进行,从而不阻塞当前 shell 窗口,然后输入 jobs -l,显示当前任务作业的状态及进程号,由 kill -9 进程号,强制终止任务作业 2. netstat -natp |grep 进程号,查看某个进程使用的端口号

  1. 访问 Clouder-manager 的 web 界面

    1
    2
    3
    访问:http://ManagerHost:7180,
    用户名、密码:admin
    若可以访问,则CM安装成功。
  2. 为什么集群个数更倾向于奇数个,而不是偶数个?

    1
    2
    3
    4
    5
    6
    7
    8
    以 3 台集群和 4 台集群举例:
    3 台集群,若其中有一台宕机了,3 / 2 = 1.5 < 2,达到了过半的条件,集群可以运行。
    4 台集群,若其中有一台宕机了,4 / 2 = 2 < 3,达到了过半的条件,集群也可以运行。
    但是4 台主机集群和 3台主机集群却承担相同的风险,且成本 4 台主机集群的成本比 3 台主机集群的成本高

    举例,若 4 台主机集群中,宕机了 2 台,剩余 2 台,不满足集群主机数量过分的条件,就不保证了集群的数据一致性,进而集群的可用性。同样 3 台主机集群中,宕机了 2台,剩余 1台,也满足集群主机数量过半的条件,即 4 台主机集群和 3台主机集群却承担相同的风险。

    说明: 集群中主机数量过半才能正常运行,因为集群中的网络条件等其他因素,可能会出现某台主机在一定时间内不能接受到或者发送消息,所以以集群中主机数量过半作为条件,是较为合理的。

Hue

Hue是一个开源的Apache Hadoop UI系统。

通过使用Hue我们可以在浏览器端的Web控制台上与Hadoop集群进行交互来分析处理数据。
例如操作HDFS上的数据、运行Hive脚本、管理Oozie任务等等。

是基于Python Web框架Django实现的。

支持任何版本Hadoop

Hue 的特点:
基于文件浏览器(File Browser)访问HDFS
基于web编辑器来开发和运行Hive查询
支持基于Solr进行搜索的应用,并提供可视化的数据视图,报表生成
通过web调试和开发impala交互式查询
spark调试和开发
Pig开发和调试
oozie任务的开发,监控,和工作流协调调度
Hbase数据查询和修改,数据展示
Hive的元数据(metastore)查询
MapReduce任务进度查看,日志追踪
创建和提交MapReduce,Streaming,Java job任务
Sqoop2的开发和调试
Zookeeper的浏览和编辑
数据库(MySQL,PostGres,SQlite,Oracle)的查询和展示

ClouderaManager 使用 impala_oozie

ClouderManager 功能使用

主机 - host
机架 - rack
集群 - Cluster
服务 - service
服务实例 - service instance
角色 - role
角色实例 - role instance
角色组 - role group
主机模板 - host template
parcel
静态服务池 - static service pool
动态资源池 - dynamic resource pool

1、集群管理
添加、删除集群
启动、停止、重启集群
重命名集群
全体集群配置
移动主机

2、主机管理
查看主机详细
主机检查
集群添加主机
分配机架
主机模板
维护模式
删除主机

3、服务管理
添加服务
对比不同集群上的服务配置
启动、停止、重启服务
滚动重启
终止客户端正在执行的命令
删除服务
重命名服务
配置最大进程数
rlimit_fds

4、角色管理
角色实例
添加角色实例
启动、停止、重启角色实例
解除授权
重新授权
删除角色实例
角色组
创建角色组
管理角色组

5、资源管理
动态资源池
静态服务池

6、用户管理

7、安全管理

安装 Hive

图形化操作

中间要 Hive 在关系型数据库建立表,并授权

1
2
create database hive     DEFAULT CHARACTER SET utf8;
grant all on hive.* TO 'hive'@'%' IDENTIFIED BY 'hive';

安装 OOZIE

同样图形化操作

中间要为 OOZIE 在关系型数量库建立库,并授权

1
2
create database oozie     DEFAULT CHARACTER SET utf8;
grant all on oozie.* TO 'oozie'@'%' IDENTIFIED BY 'oozie';

安装 Hub

图形化操作,点点点…

小插曲: 什么叫解耦底层技术的平台产品 ? 直接屏蔽底层的实现

  1. Hue 介绍

    代理 HDFS、Hive 、OOIZE 等模块,调用他们的 API ,执行相应的操作,自己只提供了一个 Web 界面,本身并不做什么事情。

  2. Hue 的用户模块

    在开始使用 Hue 时,首先要进行登录,登录的用户名和密码,由自己设置。使用 Hue 登录成功后,Hue 会将登录的用户名,告诉给 HDFS ,并在 HDFS 中创建用户家目录。

    注意: 使用 HDFS 只需要用户名,并不需要密码,需要登录的是 Hue

  3. Hue 支持文件修改-仅针对小文件

Implal

Cloudera 公司推出,提供对 HDFS 、HBase 数据的高性能、低延迟的交互式 SQL 查询功能

基于 Hive 使用内存计算,兼顾数据仓库、具有实时、批处理、多并发等特点

是 CDH 平台首选的 PB 级大数据实时查询分析引擎

Shuffle

  • MapReduce Shuffle :

    ​ 首先数据会进行序列化,然后放入环形字节数组缓冲池,当缓冲池达到阈值(默认为 80 M)后,会触发 spill 溢写操作,将缓冲池中的数据写入磁盘文件中,在过程中,会先进行二次排序、分区等操作。若相同的 key 的文件数量达到三个以上,触发 combiner 操作(归并排序),合并文件。注意: 若相同的 key 文件,spill 溢写二次产生二个文件,但不会执行 combiner 操作。从中得出:MapReduce 不能将相同的 key 文件归并到一个文件中,进而得出,MapReduce 写的时候必须采用二次排序的机制来分区有序,且分区里 key 有序(邻接排列在一起), 才能够保证MapReduce 的原语(相同的 key 为一组,方法内迭代这一组数据)。

    MapReduce Shuffile 消耗的计算资源较多,二次排序不可避免

  • Spark Shuffle:

    rpartition : 重新分区 ….

Implal 架构图

  • Implal 启动后会加载 Hive 的 MeataStore 元数据

  • Implal 运行速度比 Hive 快的多

  • Implal 创建元数据会持久化到 Hive 中

    ​ Hive 为 Implal 做元数据持久化的操作,而 Hive 的元数据存放在关系型数据库中(MySQL、Oracle 中)

安装 Implal

Catalog Server 安装在 node06 机器上

StateStore 安装在 node06 机器上

Daemon 安装在 node07、node08 机器上

Impala 使用

Impala 的使用 SQL 与 Hive 的使用类似,但是不支持 Hive 一些特殊操作,如: UDF等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
使用  impala-shell 打开,进入 impala 交互界面
show tables;
refresh <tablename> 增量刷新元数据库
invalidate metadata 全量刷新元数据库,将 Hive 的元数据同步刷新到 impala
explain <sql> 显示查询执行计划,步骤
shell <shell> 不退出 impala—shell ,执行 Linux 命令
profile (查询完成后执行)查询最近一次查询的底层信息,[事后诸葛亮]


impala-shell -p 显示 sql 计划
impala-shell -i host 登录指定 impala
impala-shell -q sql(语句) 不进入交互界面
-B 去掉格式化的样式
-f sql 文件的位置
-o 输出文件
-c 查询失败后继续执行

压缩数据文件的好处:

  1. 节省空间开销
  2. 由于数据文件压缩后,体积变小,进入内存速度变快,因为 IO 小了。

Impala 与 HBase 整合

与 Hive 和 HBase 整合类似

OOZIE

分布式调度

Oozie 是用于 Hadoop 平台的开源的工作流调度引擎

用来管理 Hadoop 作业

属于 web 应用程序,由 Oozie Client 和 Oozie Server 两个组件构成。

Oozie Server 运行于 Java Servlet 容器(tomcat) 中的 web 程序

官网: https://oozie.apache.org

作用: 一组任务使用一个 DAG(有向无环图) 来表示

Oozie 使用

Oozie 启动成功后,打开 Web 的 UI 界面

解决方法:

1
unzip  ext-2.2.zip -d  /var/lib/oozie
  • 修改 配置 Configuration,保存,重新启动 Oozie

job.properties

1
2
3
4
5
6
nameNode=hdfs://node06:8020
jobTracker=node01:8032
queueName=default
exampleRoot=examples

oozie.wf.application.path=${nameNode}/user/

workflow.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<workflow-app xmlns="uri:oozie:workflow:0.3" name="shell-wf">
<start to="shell-node"/>
<action name="shell-node">
<shell xmlns="uri:oozie:shell-action:0.1">
<job-tracker>${jobTracker}</job-tracker>
<name-node><${nameNode}</name-node>
<configuration>
<property>
<name>mapred.job.queue.name</name>
<value>${queueName}</value>
</property>
</configuration>
<exec>echo</exec>
<argument>hi shell in oozie</argument>
</shell>
<ok to="end"/>
<error to="fail"/>
</action>
<kill name="fail">
<message>
Map/Reduce failed, error message [${wf:errorMessgae(wf:lastErrorNode())}]
</message>
</kill>
<end name="end" />
</workflow-app>

[TOC]

Lucene

数据分类

  • 结构化(关系型数据库),全文检索
    表:字段数量,字段类型

  • 非结构化:

    文本文档,图片,视频,音乐…

  • 半结构化:

    json,html,xml

Lucene 简介

Lucene 是 Apache 软件基金会的一个项目,是一个开发源码的全文检索引擎工具包,是一个全文检索引擎的一个架构。提供了完成的查询引擎和检索引擎,部分文本分析引擎。

倒排索引

通俗解释,我们通常都是通过查找文件位置及文件名,再查找文件的内容。倒排索引可以理解为通过文件内容来查找文件位置及文件名的。

倒排索引是一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。它是文档检索系统中最常用的数据结构。通过倒排索引,可以根据单词快速获取包含这个单词的文档列表。

倒排索引也是 lucence 的索引核心。

举例:有两个文档 doc,id 分别为 1,2 。doc-id-1中的 content 是“我是中国人”,doc-id-2 中的 content 是 “中国是全球人口最多的国家,中国人也最多”。紧接着为这两个文档建立倒排索引。先读取 doc 文档,使用分词器对 content 中的内容 content 进行分词,产生的结果放入索引 index 表中。以“中国”这个词为例,中国(1:1){2},(2:2){0,15}。(1:1){2}表示的意思是中国这个词出现在 id 为 1 的 doc 文档中,且出现的次数为 1,偏移量为 2 。(2:2){0,15} 表示的意思是中国这个词出现在 id 为 2 的doc 文档猴子那个,且出现的次数为 2,偏移量有0,15 。

每个域可以设置三个类型:是否保存,是否索引,是否分词

从 Hbase 中按行取出数据,建立 doc 文档,经过分析(分词,过滤)后,建立倒排索引。

参考: https://www.cnblogs.com/one--way/p/5708456.html

Lucene 架构

  • 横向扩展: 加机器

    ​ Lucene 集群之间的单机的计算速度,彼此应该相差不大 。

  • 纵向扩展:主从模式

    ​ 主节点-负责计算

    ​ 从节点-1. 备机 2. 分担主节点读请求的压力

下面采用 3 个机器,作为 Lucene 的计算集群。

​ 根据 doc 的 id 做 hash 取模,分配到 Lucene 计算集群中,doc 处理完毕后,会在本地生成 index 索引文件,各个机器上 index 索引文件最终汇聚到 master 节点。

ElasticSearch 分布式安装

零配置,开箱即用
没有繁琐的安装配置
Java 版本要求:最低 1.7
下载地址:https://www.elastic.co/downloads/

启动

1
2
3
cd /usr/local/elasticsearch-2.2.0
./bin/elasticsearch
bin/elasticsearch -d(后台运行)

基础环境

安装 JDK ,并配置环境变量

安装工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
共享模式下:
useradd sxt
echo sxt | passwd --stdin sxt

su sxt
root 用户创建 /opt/sxt/es(普通用户无法创建)
mkdir -p /opt/sxt/es (注意:此时的目录权限属于root)
在父级目录 sxt 下执行: chown sxt:sxt es


单节点模式下root用户:
安装解压程序
ftp拷贝至根目录下,或者software目录下(备用,可选)

切换回sxt用户(共享模式,可以看到software内容)

单节点模式:

使用sxt用户解压es2.2.1zip包至es目录,保证es文件属于用户sxt:

unzip elasticsearch-2.2.1.zip -d /opt/sxt/es

进入es/conf, 修改elastic配置文件:
修改:
---------------cluster-------------------------

cluster.name: bjsxt-es
----------------node-------------------------------
node.name: node06 (分发后各节点修改)

----------------network--------------------------------
network.host: 192.168.133.6 (分发后修改)

http.port:9200 (放开)

末尾增加防脑裂:
discovery.zen.ping.multicast.enabled: false
discovery.zen.ping.unicast.hosts: ["192.168.133.6","192.168.133.7", "192.168.133.8"]
discovery.zen.ping_timeout: 120s
client.transport.ping_timeout: 60s


增加图形可视化插件:

[root@node06 software]# cp -abr plugins/ /opt/sxt/es/elasticsearch-2.2.1/

分发其他节点:
scp -r ./elasticsearch-2.2.1/ sxt@node07:`pwd`
scp -r ./elasticsearch-2.2.1/ sxt@node08:`pwd`

修改配置文件


进入bin目录,启动脚本:
[root@node06 bin]# ./elasticsearch


--- 访问
http://node04:9200/_plugin/head/

注意

ElasticSearch 是不可以以 root 身份启动的,若以 root 身份启动后,启动会报错。然后删除 elasticSearch 安装目录下的 logs 文件夹及其子文件,切换到 elasticSearch 用户,重新启动。

1
2
3
4
5
6
7
[root@node04 bin]# ./elasticsearch
Exception in thread "main" java.lang.RuntimeException: don't run elasticsearch as root.
at org.elasticsearch.bootstrap.Bootstrap.initializeNatives(Bootstrap.java:93)
at org.elasticsearch.bootstrap.Bootstrap.setup(Bootstrap.java:144)
at org.elasticsearch.bootstrap.Bootstrap.init(Bootstrap.java:285)
at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:35)
Refer to the log for complete error details.

中文分词器

1
2
3
4
5
6
7
# 下载 elasticsearch 对应版本的中文分词器

# 上传到 Linux 服务器上,并修改 plugin-descriptor.properties 文件
# 将 es 的版本修改为当前安装 es 版本
elasticsearch.version=2.2.1

# 重新启动 es

REST

REST 英文解释为: Representational State Transfer, 中文解释为: 表面层状态转移
一种软件架构风格,而不是标准,只是提供了一组设计原则和约束条件。

  • 它主要用于客户端和服务器交互类的软件。

  • 基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

REST 简介

REST 操作

REST的操作分为以下几种:

  • GET:获取对象的当前状态;
  • PUT:改变对象的状态;
  • POST:创建对象;
  • DELETE:删除对象;
  • HEAD:获取头信息。

ES 内置的 REST 接口

curl 命令

简单认为是可以在命令行下访问url的一个工具
curl是利用URL语法在命令行方式下工作的开源文件传输工具,使用curl可以简单实现常见的get/post请求。

curl 使用

1
2
3
curl  
-X 指定http请求的方法 HEAD GET POST PUT DELETE
-d 指定要传输的数据

ES 操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254

file
segment(段,多个document组成)
document(一条记录,一个对象实例)
field(对象的属性)
term(项,分词之后的词条)



# yes
curl -XPUT http://192.168.133.6:9200/bjsxt/
# yes
curl -XDELETE http://192.168.133.6:9200/test2/
curl -XDELETE http://192.168.133.6:9200/test3/

#document:yes
curl -XPOST http://192.168.133.6:9200/bjsxt/employee -d '
{
"first_name" : "bin",
"age" : 33,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}'

curl -XPOST http://node02:9200/szxy/employee -d '
{
"first_name" : "bin",
"age" : 33,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}'

curl -XPOST http://node02:9200/szxy/employee -d '
{
"first_name" : "gob bin",
"age" : 43,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}'

curl -XPOST http://192.168.133.6:9200/bjsxt/employee/2 -d '
{
"first_name" : "bin",
"age" : 45,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}'


#add field yes

curl -XPOST http://192.168.133.6:9200/bjsxt/employee -d '
{
"first_name" : "pablo2",
"age" : 33,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ],
"sex": "man"
}'


curl -XPOST http://node02:9200/szxy/employee/1 -d '
{
"first_name" : "pablo2",
"age" : 35,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ],
"sex": "man"
}'


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


#put:yes


curl -XPUT http://node02:9200/szxy/employee/1 -d '
{
"first_name" : "pablo2",
"last_name" : "pang",
"age" : 42,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}'

curl -XPUT http://node02:9200/szxy/employee -d '
{
"first_name" : "god bin",
"last_name" : "bin",
"age" : 45,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}'


curl -XPUT http://node02:9200/szxy/employee/2 -d '
{
"first_name" : "god bin",
"last_name" : "bin",
"age" : 45,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}'

curl -XPUT http://192.168.133.6:9200/bjsxt/employee/1 -d '
{
"first_name" : "god bin",
"last_name" : "pang",
"age" : 40,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}'



#根据document的id来获取数据:(without pretty)
curl -XGET http://node02:9200/bjsxt/employee/1?pretty

#根据field来查询数据:
curl -XGET http://node02:9200/szxy/employee/_search?q=first_name="bin"

#根据field来查询数据:match
curl -XGET http://node02:9200/szxy/employee/_search?pretty -d '
{
"query":
{"match":
{"first_name":"bin"}
}
}'



#对多个field发起查询:multi_match
curl -XGET http://node02:9200/szxy/employee/_search?pretty -d '
{
"query":
{"multi_match":
{
"query":"bin",
"fields":["last_name","first_name"],
"operator":"and"
}
}
}'


#多个term对多个field发起查询:bool(boolean)
# 组合查询,must,must_not,should
# must + must : 交集
# must +must_not :差集
# should+should : 并集

curl -XGET http://node02:9200/szxy/employee/_search?pretty -d '
{
"query":
{"bool" :
{
"must" :
{"match":
{"first_name":"bin"}
},
"must" :
{"match":
{"age":33}
}
}
}
}'

curl -XGET http://192.168.133.6:9200/bjsxt/employee/_search?pretty -d '
{
"query":
{"bool" :
{
"must" :
{"match":
{"first_name":"bin"}
},
"must_not" :
{"match":
{"age":33}
}
}
}
}'





curl -XGET http://192.168.133.6:9200/bjsxt/employee/_search?pretty -d '
{
"query":
{"bool" :
{
"must_not" :
{"match":
{"first_name":"bin"}
},
"must_not" :
{"match":
{"age":33}
}
}
}
}'

##查询first_name=bin的,或者年龄在20岁到33岁之间的

curl -XGET http://192.168.133.6:9200/bjsxt/employee/_search -d '
{
"query":
{"bool" :
{
"must" :
{"term" :
{ "first_name" : "bin" }
}
,
"must_not" :
{"range":
{"age" : { "from" : 20, "to" : 33 }
}
}
}
}
}'


#修改配置
curl -XPUT 'http://192.168.133.6:9200/test2/' -d'{"settings":{"number_of_replicas":2}}'

curl -XPUT 'http://192.168.133.6:9200/test3/' -d'{"settings":{"number_of_shards":3,"number_of_replicas":3}}'

curl -XPUT 'http://192.168.133.6:9200/test4/' -d'{"settings":{"number_of_shards":6,"number_of_replicas":4}}'


curl -XPOST http://192.168.9.11:9200/bjsxt/person/_mapping -d'
{
"person": {
"properties": {
"content": {
"type": "string",
"store": "no",
"term_vector": "with_positions_offsets",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word",
"include_in_all": "true",
"boost": 8
}
}
}
}'

ES API操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class TestES {

Client client;

@Before
public void conn() throws Exception{

Settings settings = Settings.settingsBuilder()
.put("cluster.name", "es-cluster").build();
client = TransportClient.builder().settings(settings).build()
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("node02"), 9300))
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("node03"), 9300))
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("node04"), 9300));
}


@After
public void close(){
client.close();
}

@Test
public void test01(){

IndicesExistsResponse resp = client.admin().indices().prepareExists("javatest").execute().actionGet();
if(resp.isExists()){
System.err.println("index is exist...");
client.admin().indices().prepareDelete("javatest");
}
Map<String, Object> sets = new HashMap<String,Object>();
sets.put("number_of_replicas", 2);
client.admin().indices().prepareCreate("javatest").setSettings(sets).execute().actionGet();
}


@Test
public void test02(){

Map<String, Object> data = new HashMap<String,Object>();
data.put("name", "bjsxt");
data.put("content", "hadoop");
data.put("size", 10086);

IndexResponse resp = client.prepareIndex("javatest","testfiled").setSource(data).execute().actionGet();
System.out.println(resp.getId());

}

@Test
public void test03(){

QueryBuilder q = new MatchQueryBuilder("content", "hadoop");
SearchResponse resp = client.prepareSearch("javatest")
.setTypes("testfiled")
.addHighlightedField("content")
.setHighlighterPreTags("<font color=red>")
.setHighlighterPostTags("</font>")
.setQuery(q)
.setFrom(1)
.setSize(2)
.execute()
.actionGet();


SearchHits hits = resp.getHits();
System.out.println(hits.getTotalHits());
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
System.out.println(hit.getSource().get("name"));
System.out.println(hit.getHighlightFields().get("content").getFragments()[0]);

}

}

}

[TOC]

paxos 小岛的故事

组成:

  • 议员: 管理小岛
  • 议员记事本: 记录处理的草案的编号,初始当前编号为 0。
  • 草案 (提议): 由单个议员发起草案,草案的编号必须大于议员记事本上记录的编号,否则不予处理
  • 法令: 由单个议员发起,需要有半数以上议员们同意,才可生效

过程:

​ 比方说,该小岛有三个议员,开始的时候,三个议员a、b、c手中记事本记录草案的编号都是 0 。议员 a 提出草案工业用电 2 元/度 ,提出的草案编号为 1,议员 a 自己的记事本的当前编号设置为 1 ,将该草案告诉其他议员 b、c。议员 b、c 接收到该提议后,首先查看自己记事本的当前编号为 0 ,可以处理该草案,处理(赞成或反对)结束后将自己记事本的当前编号设置为 1 ,最后若赞成草案的票数大于半数,则该草案通过,否则不通过。

对应关系:

  • 小岛(Island)——ZK Server Cluster
  • 议员(Senator)——ZK Serverabout:reader?url=https://www.douban.com/note/208430424/
  • 提议(Proposal)——ZNode Change(Create/Delete/SetData…)
  • 提议编号(PID)——Zxid(ZooKeeper Transaction Id)
  • 正式法令——所有ZNode及其数据

Zookeeper 简介

Zookeeper 是Google的Chubby一个开源的实现,是Hadoop的分布式协调服务包含一个简单的原语集,分布式应用程序可以基于它实现

特点:

Zookeeper 集群

zookeeper 集群的数量是单数的

数据模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
目录结构
层次的,目录型结构,便于管理逻辑关系
节点znode而非文件file
znode信息
包含最大1MB的数据信息
记录了zxid等元数据信息
节点类型
znode有两种类型,瞬时的(ephemeral)和持久的(persistent)
znode支持序列SEQUENTIAL:leader
短暂znode的客户端会话结束时,zookeeper会将该短暂znode删除,短暂znode不可以有子节点
持久znode不依赖于客户端会话,只有当客户端明确要删除该持久znode时才会被删除
znode的类型在创建时确定并且之后不能再修改
有序znode节点被分配唯一单调递增的整数。
比如:客户端创建有序znode,路径为/task/task-,则zookeeper为其分配序号1,并追加到znode节点:
/task/task-1。有序znode节点唯一,同时也可根据该序号查看znode创建顺序。
znode有四种形式的目录节点
PERSISTENT
EPHEMERAL
PERSISTENT_SEQUENTIAL
EPHEMERAL_SEQUENTIAL

事件监听

  • 基于客户端轮询机制

    缺陷:客户端轮询指定节点下的数据,通过网络轮询,代价很大

  • 基于事件监听机制

    客户端向 ZooKeeper 注册需要接收通知的 znode,通过对 znode 设置监视点(watch)来接收通知。

    监视点是一个单次触发的操作,意即监视点会触发一个通知。

    为了接收多个通知,客户端必须在每次通知后设置一个新的监视点。

  • 事件监听 Watcher
    Watcher 在 ZooKeeper 是一个核心功能,Watcher 可以监控目录节点的数据变化以及子目录的变化,一旦这些状态发生变化,服务器就会通知所有设置在这个目录节点上的Watcher,从而每个客户端都很快知道它所关注的目录节点的状态发生变化,而做出相应的反应

可以设置观察的操作:exists,getChildren,getData
可以触发观察的操作:create,delete,setData

回调client方法业务核心代码在哪里? `client`

节点模式-原子广播

Zookeeper的核心是原子广播,这个机制保证了各个server之间的同步。实现这个机制的协议叫做Zab协议。

1
2
3
4
5
6
Zab协议有两种模式:
恢复模式
广播模式
当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,
当领导者被选举出来,且大多数server的完成了和leader的状态同步以后,恢复模式就结束了。
状态同步保证了leader和server具有相同的系统状态
恢复模式 广播模式
无主,无服务 主从模式
选举leader leader维护事物的唯一和有序性
队列机制

下面详细介绍广播模式和恢复模式

  • 广播模式

    广播模式需要保证 proposal 被按顺序处理,因此zk采用了递增的事务id号(zxid)来保证。

    所有的提议 (proposal) 都在被提出的时候加上了 zxid。

    实现中 zxid 是一个 64 为的数字,它高32位是 epoch 用来标识 leader 关系是否改变,每次一个leader被选出来,它都会有一个新的epoch,低32位是个递增计数。

    两阶段提交:

  • 恢复模式-选举

    1,判定(zxid,myid);2,投票传递

    1
    2
    3
    4
    zxid   <从paxos 到 zookeeper>
    myid

    首先选举zxid最大的,如果zxid相同,则选举myid最大的

Leader 选举

选举过程耗时在 200ms 之内,一般情况下zookeeper恢复服务时间间隔不超过 200ms

server Si (myid,zxid)

总结

Zookeeper 安装与配置

  • 安装

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    1.3节点 java 安装 

    2.所有集群节点创建目录: mkdir opt/sxt

    3.zk压缩包解压在其他路径下::
    # tar xf zookeeper-3.4.6.tar.gz -C /opt/sxt/

    4.进入conf目录,拷贝zoo_sample.cfg zoo.cfg 并配置
    dataDir,集群节点。
    5.单节点配置环境变量、并分发 ZOOKEEPER_PREFIX,共享模式读取profile
    6. 共享创建 /var/sxt/zk目录,进入各自目录 分别输出1,2,3 至文件 myid
    echo 1 > /var/sxt/zk/myid
    ...
    7. 共享启动zkServer.sh start 集群
    8.启动客户端 help命令查看
  • 配置

    在conf目录下创建一个配置文件 zoo.cfg,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    tickTime=2000  
    dataDir=/Users/zdandljb/zookeeper/data
    dataLogDir=/Users/zdandljb/zookeeper/dataLog
    clientPort=2181
    initLimit=5
    syncLimit=2
    server.1=server1:2888:3888
    server.2=server2:2888:3888
    server.3=server3:2888:3888 observer(表示对应节点不参与投票)

    创建myid文件

    tickTime:发送心跳的间隔时间,单位:毫秒
    dataDir:zookeeper保存数据的目录。
    clientPort:客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。
    initLimit: 这个配置项是用来配置 Zookeeper 接受客户端(这里所说的客户端不是用户连接Zookeeper服务器的客户端,而是 Zookeeper 服务器集群中连接到 Leader的Follower 服务器)初始化连接时最长能忍受多少个心跳时间间隔数。当已经超过 5 个心跳的时间(也就是 tickTime)长度后 Zookeeper 服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。总的时间长度就是 52000=10秒
    syncLimit:这个配置项标识 Leader 与 Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个tickTime 的时间长度,总的时间长度就是 2
    2000=4 秒
    server.A=B:C:D:其 中 A 是一个数字,表示这个是第几号服务器;B 是这个服务器的ip地址;C 表示的是这个服务器与集群中的Leader服务器交换信息的端口;D表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的Leader,而这个端口就是用来执行选举时服务器相互通信的端口。如果是伪集群的配置方式,由于B都是一样,所以不同的Zookeeper实例通信端口号不能一样,所以要给它们分配不同的端口号。

Zookeeper 操作

常用命令

  • ls 命令

    功能 ls 命令获取路径下的节点信息(所有子节点),注意路径为绝对路径
    作用 ls /zookeeper
  • create 命令

    功能 创建 zookeeper 下的节点
    create /path null 创建 zookeeper 下的空节点
    create -s /path data 创建 zookeeper 下的持续节点
    craete -e /path data 创建 zookeeper 下的瞬时节点后
    即与 zookeeper 断开连接后,创建的瞬时节点会自动移除。
  • get 命令

    功能 获取 zookeeper 下节点中的信息
    get /path 获取指定的节点信息
  • delete 命令

    功能 删除 Zookeeper 中节点
    delete /path 删除 /path 节点,注意 /path 下面必须没有子节点,不然不能删除

Zookeeper API 使用

使用 RMI (Remote Method Innvocation),远程调用方法。

使用 zookeeper 作为注册中心

RMI 案例

服务提供端

  • ServiceProvider.java:连接 zk,发布服务到 zk 上

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    public class ServiceProvider {

    private static final Logger LOGGER = LoggerFactory.getLogger(ServiceProvider.class);

    // 用于等待 SyncConnected 事件触发后继续执行当前线程
    private CountDownLatch latch = new CountDownLatch(1);

    // 发布 RMI 服务并注册 RMI 地址到 ZooKeeper 中
    public void publish(Remote remote, String host, int port) {
    String url = publishService(remote, host, port); // 发布 RMI 服务并返回 RMI 地址
    if (url != null) {
    ZooKeeper zk = connectServer(); // 连接 ZooKeeper 服务器并获取 ZooKeeper 对象
    if (zk != null) {
    createNode(zk, url); // 创建 ZNode 并将 RMI 地址放入 ZNode 上
    }
    }
    }

    // 发布 RMI 服务
    private String publishService(Remote remote, String host, int port) {
    String url = null;
    try {
    // rmi://localhost:1099/demo.zookeeper.remoting.server.HelloServiceImpl
    url = String.format("rmi://%s:%d/%s", host, port, remote.getClass().getName());
    LocateRegistry.createRegistry(port);
    Naming.rebind(url, remote);
    LOGGER.debug("publish rmi service (url: {})", url);
    } catch (RemoteException | MalformedURLException e) {
    LOGGER.error("", e);
    }
    return url;
    }

    // 连接 ZooKeeper 服务器
    private ZooKeeper connectServer() {
    ZooKeeper zk = null;
    try {
    zk = new ZooKeeper(Constant.ZK_CONNECTION_STRING, Constant.ZK_SESSION_TIMEOUT, new Watcher() {
    @Override
    public void process(WatchedEvent event) {
    if (event.getState() == Event.KeeperState.SyncConnected) {
    latch.countDown(); // 唤醒当前正在执行的线程
    }
    }
    });
    latch.await(); // 使当前线程处于等待状态
    } catch (IOException | InterruptedException e) {
    LOGGER.error("", e);
    }
    return zk;
    }

    // 创建 ZNode
    private void createNode(ZooKeeper zk, String url) {
    try {
    byte[] data = url.getBytes();
    // /registry/provider
    // /registry/provider000000003
    String path = zk.create(Constant.ZK_PROVIDER_PATH, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); // 创建一个临时性且有序的 ZNode
    LOGGER.debug("create zookeeper node ({} => {})", path, url);
    } catch (KeeperException | InterruptedException e) {
    LOGGER.error("", e);
    }
    }


    }
  • HelloServiceImpl.java:服务具体实现类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class HelloServiceImpl extends UnicastRemoteObject implements HelloService {

    protected HelloServiceImpl() throws RemoteException {
    }

    @Override
    public String sayHello(String name) throws RemoteException {
    return String.format("Hello %s", name);
    }
    }
  • Server.java :服务提供者

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class Server {

    public static void main(String[] args) throws Exception {
    String host = "localhost";
    int port = Integer.parseInt("11117");
    ServiceProvider provider = new ServiceProvider();

    HelloService helloService = new HelloServiceImpl();
    provider.publish(helloService, host, port);

    Thread.sleep(Long.MAX_VALUE);
    }
    }

服务消费端

  • ServiceConsumer.java:连接 zk ,获取服务列表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    public class ServiceConsumer {

    private static final Logger LOGGER = LoggerFactory.getLogger(ServiceConsumer.class);

    // 用于等待 SyncConnected 事件触发后继续执行当前线程
    private CountDownLatch latch = new CountDownLatch(1);

    // 定义一个 volatile 成员变量,用于保存最新的 RMI 地址(考虑到该变量或许会被其它线程所修改,一旦修改后,该变量的值会影响到所有线程)
    private volatile List<String> urlList = new ArrayList<>();

    // 构造器
    public ServiceConsumer() {
    ZooKeeper zk = connectServer(); // 连接 ZooKeeper 服务器并获取 ZooKeeper 对象
    if (zk != null) {
    watchNode(zk); // 观察 /registry 节点的所有子节点并更新 urlList 成员变量
    }
    }

    // 查找 RMI 服务
    public <T extends Remote> T lookup() {
    T service = null;
    int size = urlList.size();
    if (size > 0) {
    String url;
    if (size == 1) {
    url = urlList.get(0); // 若 urlList 中只有一个元素,则直接获取该元素
    LOGGER.debug("using only url: {}", url);
    } else {
    url = urlList.get(ThreadLocalRandom.current().nextInt(size)); // 若 urlList 中存在多个元素,则随机获取一个元素
    LOGGER.debug("using random url: {}", url);
    }
    System.out.println(url);
    service = lookupService(url); // 从 JNDI 中查找 RMI 服务
    }
    return service;
    }

    // 连接 ZooKeeper 服务器
    private ZooKeeper connectServer() {
    ZooKeeper zk = null;
    try {
    zk = new ZooKeeper(Constant.ZK_CONNECTION_STRING, Constant.ZK_SESSION_TIMEOUT, new Watcher() {
    @Override
    public void process(WatchedEvent event) {
    if (event.getState() == Event.KeeperState.SyncConnected) {
    latch.countDown(); // 唤醒当前正在执行的线程
    }
    }
    });
    latch.await(); // 使当前线程处于等待状态
    } catch (IOException | InterruptedException e) {
    LOGGER.error("", e);
    }
    return zk;
    }

    // 观察 /registry 节点下所有子节点是否有变化
    private void watchNode(final ZooKeeper zk) {
    try {
    List<String> nodeList = zk.getChildren(Constant.ZK_REGISTRY_PATH, new Watcher() {
    @Override
    public void process(WatchedEvent event) {
    if (event.getType() == Event.EventType.NodeChildrenChanged) {
    watchNode(zk); // 若子节点有变化,则重新调用该方法(为了获取最新子节点中的数据)
    }
    }
    });
    List<String> dataList = new ArrayList<>(); // 用于存放 /registry 所有子节点中的数据
    for (String node : nodeList) { // /registry/provider00000000003
    byte[] data = zk.getData(Constant.ZK_REGISTRY_PATH + "/" + node, false, null); // 获取 /registry 的子节点中的数据
    dataList.add(new String(data));
    }
    LOGGER.debug("node data: {}", dataList);
    urlList = dataList; // 更新最新的 RMI 地址
    } catch (KeeperException | InterruptedException e) {
    LOGGER.error("", e);
    }
    }

    // 在 JNDI 中查找 RMI 远程服务对象
    @SuppressWarnings("unchecked")
    private <T> T lookupService(String url) {
    T remote = null;
    try {
    remote = (T) Naming.lookup(url);
    } catch (NotBoundException | MalformedURLException | RemoteException e) {
    if (e instanceof ConnectException) {
    // 若连接中断,则使用 urlList 中第一个 RMI 地址来查找(这是一种简单的重试方式,确保不会抛出异常)
    LOGGER.error("ConnectException -> url: {}", url);
    if (urlList.size() != 0) {
    url = urlList.get(0);
    return lookupService(url);
    }
    }
    LOGGER.error("", e);
    }
    return remote;
    }
    }
  • RmiClient.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class Client {

    public static void main(String[] args) throws Exception {
    ServiceConsumer consumer = new ServiceConsumer();
    // zookeeper测试
    while (true) {
    HelloService helloService = consumer.lookup();
    String result = helloService.sayHello("Jack");
    System.out.println(result);
    Thread.sleep(2000);
    }
    }
    }

共有

  • HelloService.java

    1
    2
    3
    4
    public interface HelloService extends Remote {

    String sayHello(String name) throws RemoteException;
    }
  • Constant.java

    1
    2
    3
    4
    5
    6
    7
    public interface Constant {

    String ZK_CONNECTION_STRING = "node02:2181,node03:2181,node04:2181";
    int ZK_SESSION_TIMEOUT = 5000;
    String ZK_REGISTRY_PATH = "/registry";
    String ZK_PROVIDER_PATH = ZK_REGISTRY_PATH + "/provider";
    }

Socket 案例

Socket 服务端向 zk 注册服务,Socket 客户端从 zk 获取服务列表,从而连接服务器

  • Server.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    public class Server {

    private ZooKeeper zk = null;
    private String zkHost = "node02:2181,node03:2181,node04:2181";
    private int SESSION_OUT = 3000;

    private CountDownLatch latch = new CountDownLatch(1);

    public static void main(String[] args) throws Exception {
    for(int i = 0; i < 3; i++){
    Thread t = new Thread(new Runnable() {
    @Override
    public void run() {
    try {
    String host = "localhost";
    /*Random random = new Random();
    String num = String.format("2%4d",random.nextInt(9999));*/
    int port = (int)((Math.random()*9+1)*10000);
    String address = host + ":" + String.valueOf(port);
    // 创建对象
    Server s = new Server();
    // 连接 zk 集群
    s.connectZk();
    // 开启服务端
    ServerSocket server = new ServerSocket(port);
    // 注册服务
    s.registryService(address);
    while (true) {
    Socket client = server.accept();
    handleReq(client);
    }
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    });
    t.start();
    }
    }

    /**
    * 连接 zk 集群
    *
    * @throws Exception
    */
    private void connectZk() throws Exception {
    zk = new ZooKeeper(zkHost, SESSION_OUT, new Watcher() {
    @Override
    public void process(WatchedEvent event) {
    if (event.getState() == Event.KeeperState.SyncConnected) {
    latch.countDown();
    }
    }
    });
    latch.await();
    }

    /**
    * 注册服务
    *
    * @param address
    */
    private void registryService(String address) throws Exception {
    String path = "/myRegistry/service";
    zk.create(path,address.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
    }

    /**
    * 处理客户端请求
    *
    * @param client
    * @throws Exception
    */
    private static void handleReq(Socket client) throws Exception {
    OutputStream os = client.getOutputStream();
    OutputStreamWriter osw = new OutputStreamWriter(os, "utf-8");
    BufferedWriter writer = new BufferedWriter(osw);
    writer.write("Hello Client");
    writer.newLine();
    writer.flush();
    client.close();
    }

    }
  • Client.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    public class Client {

    private ZooKeeper zk = null;
    private String zkHost = "node02:2181,node03:2181,node04:2181";
    private int SESSION_OUT = 3000;

    private CountDownLatch latch = new CountDownLatch(1);

    private List<String> urlList = null;

    private Socket client = null;

    public static void main(String[] args) throws Exception {
    Client c = new Client();
    // 连接 zk 集群
    c.connectZk();
    // 获取注册服务列表
    c.getRegistryList();

    while (true) {
    // 连接服务器
    c.connectServer();
    }
    }

    /**
    * 连接 zk 集群
    */
    private void connectZk() {
    try {
    zk = new ZooKeeper(zkHost, SESSION_OUT, new Watcher() {
    @Override
    public void process(WatchedEvent event) {
    if (event.getState() == Event.KeeperState.SyncConnected) {
    latch.countDown();
    }
    }
    });
    latch.await();
    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    /**
    * 获取服务列表
    */
    private void getRegistryList() {
    List<String> nodeList = null;
    try {
    nodeList = zk.getChildren("/myRegistry", new Watcher() {
    @Override
    public void process(WatchedEvent event) {
    if (event.getType() == Event.EventType.NodeChildrenChanged) {
    System.out.println("节点发生变化。。。");
    getRegistryList(); // 调用自身方法
    }
    }
    });
    urlList = new ArrayList<>();
    for (String node : nodeList) {
    byte[] data = zk.getData("/myRegistry/" + node, null, null);
    String url = new String(data);
    urlList.add(url);
    }
    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    /**
    * 连接服务器
    *
    * @throws Exception
    */
    private void connectServer() throws Exception {
    String url = null;
    String host = null;
    if (this.urlList.size() == 0) {
    System.out.println("没有可用的服务器....");
    Thread.sleep(2000);
    return;
    } else if (this.urlList.size() == 1) {
    url = this.urlList.get(0);
    } else {
    Random random = new Random();
    url = this.urlList.get(random.nextInt(urlList.size()));
    }

    host = url.substring(0, url.indexOf(":"));
    int port = Integer.valueOf(url.substring(url.indexOf(":") + 1));

    client = new Socket(host, port);
    InputStream is = client.getInputStream();
    InputStreamReader isr = new InputStreamReader(is, "utf-8");
    BufferedReader reader = new BufferedReader(isr);
    String line = reader.readLine();
    System.out.println(host + ":" + port + " 服务器响应消息: " + line);
    Thread.sleep(2000);

    client.close();
    }


    }

HBase 简介

Hadoop Database,是一个高可靠性、高性能、面向列、可伸缩、实时读写的分布式数据库

作用:主要用来存储非结构化和半结构化的松散数据(列存 NoSQL 数据库)

利用 Hadoop HDFS 作为其文件存储系统,

利用 Hadoop MapReduce 来处理 HBase 中的海量数据,

利用 Zookeeper 作为其分布式协同服务

  • 非关系型数据库知识面扩展

    • Cassandra hbase mongodb
    • Couchdb,文件存储数据库
    • Neo4j非关系型图数据库
  • HBase 官网地址:http://hbase.apache.org/

Hadoop 生态系统

MapReduce 计算的数据来源:

  1. HDFS 或者其他文件系统 2.数据库(既可以是关系型数据也可以非关系型)

Hive 和 HBase 的区别:

Hive 是数据仓库,HBase 是数据库。Hive 是用 SQL 方式处理数据,底层使用 MapReduce 计算框架

使用 MapReduce 注意:

  1. map 生成小文件 2. 数据倾斜

HBase 数据模型

  • ROW KEY(ROW KEY 设计很重要)

    决定一行数据,按照字典顺序排序的。Row key只能存储 64k 的字节数据

  • Column Family 列族 & qualifier列

    1. HBase 表中的每个列都归属于某个列族,列族必须作为表模式(schema)定义的一部分预先给出。

      如 create ‘test’, ‘course’;

    2. 列名以列族作为前缀,每个“列族”都可以有多个列成员(column);

      如course:math, course:english, 新的列族成员(列)可以随后按需、动态加入;

    3. 权限控制、存储以及调优都是在列族层面进行的;

    4. HBase 把同一列族里面的数据存储在同一目录下,由几个文件保存。

  • Cell 单元格

    由行和列的坐标交叉决定;

    单元格是有版本的;

    单元格的内容是未解析的字节数组

    {row key, column( =<family> +<qualifier>), version} 唯一确定的单元。

    cell中的数据是没有类型的,全部是字节码形式存贮

  • Timestamp 时间戳

    在 HBase 每个 cell 存储单元对同一份数据有多个版本,根据唯一的时间戳来区分每个版本之间的差异,不同版本的数据按照时间倒序排序,最新的数据版本排在最前面。

    时间戳的类型是 64位整型。

    时间戳可以由 HBase (在数据写入时自动)赋值,此时时间戳是精确到毫秒的当前系统时间。

    时间戳也可以由客户显式赋值,如果应用程序要避免数据版本冲突,就必须自己生成具有唯一性的时间戳。

  • HLog(WAL log)

    HLog 文件就是一个普通的 Hadoop Sequence File,Sequence File 的 Key 是 HLogKey 对象,HLogKey 中记录了写入数据的归属信息,除了 table 和 region 名字外,同时还包括 sequence number 和 timestamp,timestamp 是” 写入时间”,sequence number 的起始值为0,或者是最近一次存入文件系统中 sequence number。
    HLog SequeceFile 的 Value 是 HBase 的 KeyValue 对象,即对应 HFile 中的 KeyValue。

注:region:范围、地区

HBase 架构

HBase 架构图

HBase 架构中各角色作用

  • Client
    包含访问 HBase 的接口并维护 cache 来加快对 HBase 的访问

  • Zookeeper(不仅可以做集群的高可用)
    保证任何时候,集群中只有一个 master
    存贮所有 Region 的寻址入口。
    实时监控 Region server 的上线和下线信息。并实时通知 Master
    存储 HBase 的 schema 和 table 元数据

  • Master
    为 Region server 分配 region
    负责 Region server 的负载均衡
    发现失效的 Region server 并重新分配其上的 region
    管理用户对 table 的增删改操作

  • RegionServer
    Region server 维护 region,处理对这些 region 的 IO 请求
    Region server 负责切分在运行过程中变得过大的 region

  • Region
    HBase 自动把表水平划分成多个区域(region),每个 region 会保存一个表里面某段连续的数据(按字典序)

    每个表一开始只有一个 region,随着数据不断插入表,region 不断增大,当增大到一个阀值的时候,region就会等分会两个新的 region(裂变)

    当 table 中的行不断增多,就会有越来越多的 region。这样一张完整的表被保存在多个 Regionserver 上。

  • Memstore & storefile
    一个 region 由多个 store 组成,一个 store 对应一个CF(列族)

    store 包括位于内存中的 memstore 和位于磁盘的 storefile 。

    写操作先写入memstore,当 memstore 中的数据达到某个阈值,hregionserver 会启动 flashcache 进程写

    入 storefile,每次写入形成单独的一个 storefile。

    当 storefile 文件的数量增长到一定阈值后,系统会进行合并(minor、major compaction),在合并过程

    中会进行版本合并和删除工作(majar),形成更大的 storefile。

    当一个 region 所有 storefile的大小和数量超过一定阈值后,会把当前的 region 分割为两个,并由 hmaster

    分配到相应的 regionserver 服务器,实现负载均衡。

    客户端检索数据,先在 memstore 找,找不到再找 storefile(先找 cache 后找磁盘)

  • Region补充:

    HRegion 是 HBase 中分布式存储和负载均衡的最小单元。

    最小单元就表示不同的 HRegion 可以分布在不同的 HRegion server上。

    HRegion 由一个或者多个 Store 组成,每个 store 保存一个 columns family。

    每个 Strore 又由一个 memStore 和 0 至多个 StoreFile 组成。

    如图:StoreFile 以 HFile 格式保存在 HDFS上。

HBase 环境搭建

HBase 官方文档地址:http://hbase.apache.org/book.html#quickstart

HBase 伪分布式搭建

  • 系统环境

    JDK 1.7,并配置 JAVA_HOME 环境变量

  • HBase 安装步骤

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    # 上传 HBase 压缩包文件,并解压到指定目录, 通过 tar 命令中 -C 指定解压到的目录
    tar xf hbase-0.98.12.1-hadoop2-bin.tar.gz -C /opt/sxt

    # 配置 HBase 环境变量
    vi /etc/profile
    # 增加的配置内容
    export HBASE_HOME=/opt/sxt/hbase-0.98.12.1-hadoop2
    export PATH=$PATH:$JAVA_HOME/bin:$HBASE_HOME/bin
    # 环境变量生效
    . /etc/profile

    # 进入 $HBSE_HOME/conf 下
    # 修改 hbase-env.sh
    export JAVA_HOME=/usr/java/jdk1.7.0_67

    # 修改 hbase-site.xml
    <property>
    <name>hbase.rootdir</name>
    <value>file:///home/testuser/hbase</value>
    </property>
    <property>
    <name>hbase.zookeeper.property.dataDir</name>
    <value>/home/testuser/zookeeper</value>
    </property>
    <property>
    <name>hbase.unsafe.stream.capability.enforce</name>
    <value>false</value>
    </property>

    # 启动
    start-hbase.sh
    # 访问 HBase ,端口号为 60010
    http://192.168.170.105:60010
    #
    hbase shell
    #help 查看 hbase 使用
    #list 列出所欲当前数据表
    #scan 列出当前数据表中记录数
    #create 创建表
    #put 增加记录
    #delete 删除记录
  • HBase 操作

补充: list 显示当前 HBase 中所有表

​ flush 将 inmemstore 中数据写入 storefile

HBase 完全分布式搭建

  • 节点分布

    NN DN ZK HMaster Backup-HMaster RegionServer
    node01 * *
    node02 * * *
    node03 * * *
    node04 * * *
    node05 *
  • 系统设置

    1. 集群间网络通信

    2. 时间同步

      1
      2
      3
      4
      # 安装 ntp 服务
      yum install -y ntp
      # 时间服务器
      ntpdate ntp1.aliyun.com
    3. JDK 环境

    4. 免秘钥登录

      1
      2
      3
      ssh-keygen
      ssh-copy-id -i /root/.ssh/id_rsa.pub host(需要免密钥登录的服务器地址)
      eg: ssh-copy-id -i .ssh/id_rsa.pub node02
    5. hadoop 集群启动

      1
      start-dfs.sh
  • HBase 搭建

    1. 上传解压

    2. 修改配置文件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      # node 节点上------------------------------
      # 进入 $HBASE_HOME/conf 目录下
      # 编辑 vi hbase-env.sh ,配置JDk 、关闭 zk
      export JAVA_HOME=/usr/java/jdk1.7.0_67
      export HBASE_MANAGES_ZK=false

      # 编辑 vi hbase-site.xml ,增加内容为
      <property>
      <name>hbase.cluster.distributed</name>
      <value>true</value>
      </property>
      <property>
      <name>hbase.rootdir</name>
      <value>hdfs://mycluster:8020/hbase</value>
      </property>
      <property>
      <name>hbase.zookeeper.quorum</name>
      <value>node02,node03,node04</value>
      </property>

      # 编辑 vi regionservers,增加内容为
      node02
      node03
      node04
      # 编辑 vi backup-masters,增加内容为
      node05
      # 拷贝 $HADOOP_HOME/etc/hadoop/hdfs-size.xml 到 $HBASE_HOME/conf 目录下(重要)

      # 分发到 node02、node03、node04、node05

      # 配置 node02、node03、node04、node05 的 hbase 环境变量
    3. 启动

      1
      start-hbase.sh

HBase-API

重点 : rowkey 设计

Demo

  1. 创建 hbase-demo 普通 java 项目,并导入 hadoop 和 hbase 相关依赖 jar 包,并发布到类路径上

  2. 创建 HBaseDemo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class HbaseDemo {

HBaseAdmin admin;
HTable htable;
Configuration conf;
String TN = "test";

@Before
public void init() throws Exception {
conf = new Configuration();
conf.set("hbase.zookeeper.quorum", "node02,node03,node04");
admin = new HBaseAdmin(conf);
htable = new HTable(conf, TN.getBytes());
}

@After
public void close() throws Exception {
if (admin != null) {
admin.close();
}
}
}
  • 创建表
1
2
3
4
5
6
7
8
9
10
11
@Test
public void createTable() throws Exception {
HTableDescriptor table = new HTableDescriptor(TableName.valueOf(TN.getBytes()));
HColumnDescriptor column = new HColumnDescriptor("cf");
table.addFamily(column);
if (admin.tableExists(TN.getBytes())) {
admin.disableTable(TN.getBytes());
admin.deleteTable(TN.getBytes());
}
admin.createTable(table);
}
  • 存放数据
1
2
3
4
5
6
7
8
public void put() throws Exception {
String rowkey = "r124";
Put put = new Put(rowkey.getBytes());
put.add("cf".getBytes(), "name".getBytes(), "xiaoli".getBytes());
put.add("cf".getBytes(), "age".getBytes(), "32".getBytes());
put.add("cf".getBytes(), "sex".getBytes(), "female".getBytes());
htable.put(put);
}
  • 获取数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void showTable() throws Exception {
Get get = new Get("r123".getBytes());
// 必须加
get.addColumn("cf".getBytes(), "name".getBytes());
get.addColumn("cf".getBytes(), "age".getBytes());
Result result = htable.get(get);
Cell name = result.getColumnLatestCell("cf".getBytes(), "name".getBytes());
Cell age = result.getColumnLatestCell("cf".getBytes(), "age".getBytes());
// System.out.println(new String(name.getValue()));
// System.out.println(new String(age.getValue()));
System.out.print(new String(CellUtil.cloneValue(name)));
System.out.print(new String(CellUtil.cloneValue(age)));
}
  • 条件查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
	
/**
* 生成电话号码
* @param prefix
* @return
*/
Random r = new Random();
private String generatePhoneNumber(String prefix) {
String pNum = prefix + String.format("%08d", r.nextInt(99999999));
return pNum;
}

/**
* 获取随机时间
*/
DateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
public String getDate(String year) {
// 月 天 小时 分钟 秒
Object[] args = { r.nextInt(12)+1, r.nextInt(31)+1, r.nextInt(24), r.nextInt(60), r.nextInt(60) };
String dateStr = year + String.format("%02d%02d%02d%02d%02d", args);
return dateStr;
}

/**
* 生成 10个人通话记录
* // phoneNum_(max-timestamp)
*/
@Test
public void addPhoneRecord() throws Exception{
List<Put> puts = new ArrayList<>();
for(int i = 0;i < 10;i++){
String phoneNum = generatePhoneNumber("189");
for(int j = 0;j < 100;j++){
String dnum = generatePhoneNumber("138");
String length = r.nextInt(99)+""; // 通话时长
String type = r.nextInt(2)+""; // 1 是主叫,2 是被叫
String dataStr = getDate("2019"); // 通话开始时间
String rowkey = phoneNum+"_"+(Long.MAX_VALUE-sdf.parse(dataStr).getTime());
Put put = new Put(rowkey.getBytes());
put.add("cf".getBytes(),"dnum".getBytes(),dnum.getBytes());
put.add("cf".getBytes(),"length".getBytes(),length.getBytes());
put.add("cf".getBytes(),"type".getBytes(),type.getBytes());
put.add("cf".getBytes(),"dataStr".getBytes(),dataStr.getBytes());
puts.add(put);
}
}
htable.put(puts);
}

/**
* 查找通话记录 ,指定电话的在 1月份 4 月份之间的通话记录
*/
@Test
public void scan() throws Exception {
String phoneNum = "18985484208"; //20190022203207
String startRow = phoneNum+"_"+(Long.MAX_VALUE-sdf.parse("20190401000000").getTime());
String stopRow = phoneNum+"_"+(Long.MAX_VALUE-sdf.parse("20190101000000").getTime());
Scan scan = new Scan();
scan.setStartRow(startRow.getBytes());
scan.setStopRow(stopRow.getBytes());
ResultScanner scanner = htable.getScanner(scan);
int count = 0;
for (Result rs : scanner) {
System.out.print(new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "dnum".getBytes()))));
System.out.print("\t"+new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "length".getBytes()))));
System.out.print("\t"+new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "type".getBytes()))));
System.out.println("\t"+new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "dataStr".getBytes()))));
count ++;
}
System.out.println("count:"+count);
}

/**
* 查找指定电话主叫的通话记录
* @throws Exception
*/
@Test
public void scan2() throws Exception{
FilterList list = new FilterList(FilterList.Operator.MUST_PASS_ALL);
PrefixFilter filter1 = new PrefixFilter("18985484208".getBytes());
SingleColumnValueFilter filter2 = new SingleColumnValueFilter("cf".getBytes(), "type".getBytes(),
CompareOp.EQUAL, "1".getBytes());
list.addFilter(filter1);
list.addFilter(filter2);
Scan scan = new Scan();
scan.setFilter(list);
ResultScanner scanner = htable.getScanner(scan);
int count = 0;
for (Result rs : scanner) {
System.out.print(new String(rs.getRow()));
System.out.print("\t"+new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "dnum".getBytes()))));
System.out.print("\t"+new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "length".getBytes()))));
System.out.print("\t"+new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "type".getBytes()))));
System.out.println("\t"+new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "dataStr".getBytes()))));
count ++;
}
System.out.println("count:"+count);
}

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
public class HBaseDAOImp {

HConnection hTablePool = null;
static Configuration conf = null;

public HBaseDAOImp() {
conf = new Configuration();
String zk_list = "node01,node02,node03";
conf.set("hbase.zookeeper.quorum", zk_list);
try {
hTablePool = HConnectionManager.createConnection(conf);
} catch (IOException e) {
e.printStackTrace();
}
}

public void save(Put put, String tableName) {
HTableInterface table = null;
try {
table = hTablePool.getTable(tableName);
table.put(put);

} catch (Exception e) {
e.printStackTrace();
} finally {
try {
table.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

/**
* 插入一个cell
*
* @param tableName
* @param rowKey
* @param family
* @param quailifer
* @param value
*/
public void insert(String tableName, String rowKey, String family, String quailifer, String value) {
// TODO Auto-generated method stub
HTableInterface table = null;
try {
table = hTablePool.getTable(tableName);
Put put = new Put(rowKey.getBytes());
put.add(family.getBytes(), quailifer.getBytes(), value.getBytes());
table.put(put);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
table.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

/**
* 在一个列族下插入多个单元格
*
* @param tableName
* @param rowKey
* @param family
* @param quailifer
* @param value
*/
public void insert(String tableName, String rowKey, String family, String quailifer[], String value[]) {
HTableInterface table = null;
try {
table = hTablePool.getTable(tableName);
Put put = new Put(rowKey.getBytes());
// 批量添加
for (int i = 0; i < quailifer.length; i++) {
String col = quailifer[i];
String val = value[i];
put.add(family.getBytes(), col.getBytes(), val.getBytes());
}
table.put(put);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
table.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

public void save(List<Put> Put, String tableName) {
HTableInterface table = null;
try {
table = hTablePool.getTable(tableName);
table.put(Put);
} catch (Exception e) {
} finally {
try {
table.close();
} catch (IOException e) {
e.printStackTrace();
}
}

}

public Result getOneRow(String tableName, String rowKey) {
HTableInterface table = null;
Result rsResult = null;
try {
table = hTablePool.getTable(tableName);
Get get = new Get(rowKey.getBytes());
rsResult = table.get(get);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
table.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return rsResult;
}

/**
* 最常用的方法,优化查询 查询一行数据,
*
* @param tableName
* @param rowKey
* @param cols
* @return
*/
public Result getOneRowAndMultiColumn(String tableName, String rowKey, String[] cols) {
HTableInterface table = null;
Result rsResult = null;
try {
table = hTablePool.getTable(tableName);
Get get = new Get(rowKey.getBytes());
for (int i = 0; i < cols.length; i++) {
get.addColumn("cf".getBytes(), cols[i].getBytes());
}
rsResult = table.get(get);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
table.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return rsResult;
}

public List<Result> getRows(String tableName, String rowKeyLike) {
HTableInterface table = null;
List<Result> list = null;
try {
FilterList fl = new FilterList(FilterList.Operator.MUST_PASS_ALL);
table = hTablePool.getTable(tableName);
PrefixFilter filter = new PrefixFilter(rowKeyLike.getBytes());
SingleColumnValueFilter filter1 = new SingleColumnValueFilter("order".getBytes(), "order_type".getBytes(),
CompareOp.EQUAL, Bytes.toBytes("1"));
fl.addFilter(filter);
fl.addFilter(filter1);
Scan scan = new Scan();
scan.setFilter(fl);
ResultScanner scanner = table.getScanner(scan);
list = new ArrayList<Result>();
for (Result rs : scanner) {
list.add(rs);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
table.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return list;
}

public List<Result> getRows(String tableName, String rowKeyLike, String cols[]) {
// TODO Auto-generated method stub
HTableInterface table = null;
List<Result> list = null;
try {
table = hTablePool.getTable(tableName);
PrefixFilter filter = new PrefixFilter(rowKeyLike.getBytes());

Scan scan = new Scan();
for (int i = 0; i < cols.length; i++) {
scan.addColumn("cf".getBytes(), cols[i].getBytes());
}
scan.setFilter(filter);
ResultScanner scanner = table.getScanner(scan);
list = new ArrayList<Result>();
for (Result rs : scanner) {
list.add(rs);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
table.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return list;
}

public List<Result> getRowsByOneKey(String tableName, String rowKeyLike, String cols[]) {
// TODO Auto-generated method stub
HTableInterface table = null;
List<Result> list = null;
try {
table = hTablePool.getTable(tableName);
PrefixFilter filter = new PrefixFilter(rowKeyLike.getBytes());

Scan scan = new Scan();
for (int i = 0; i < cols.length; i++) {
scan.addColumn("cf".getBytes(), cols[i].getBytes());
}
scan.setFilter(filter);
ResultScanner scanner = table.getScanner(scan);
list = new ArrayList<Result>();
for (Result rs : scanner) {
list.add(rs);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
table.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return list;
}

/**
* 范围查询
*
* @param tableName
* @param startRow
* @param stopRow
* @return
*/
public List<Result> getRows(String tableName, String startRow, String stopRow) {
HTableInterface table = null;
List<Result> list = null;
try {
table = hTablePool.getTable(tableName);
Scan scan = new Scan();
scan.setStartRow(startRow.getBytes());
scan.setStopRow(stopRow.getBytes());
ResultScanner scanner = table.getScanner(scan);
list = new ArrayList<Result>();
for (Result rsResult : scanner) {
list.add(rsResult);
}

} catch (Exception e) {
e.printStackTrace();
} finally {
try {
table.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return list;
}

public void deleteRecords(String tableName, String rowKeyLike) {
HTableInterface table = null;
try {
table = hTablePool.getTable(tableName);
PrefixFilter filter = new PrefixFilter(rowKeyLike.getBytes());
Scan scan = new Scan();
scan.setFilter(filter);
ResultScanner scanner = table.getScanner(scan);
List<Delete> list = new ArrayList<Delete>();
for (Result rs : scanner) {
Delete del = new Delete(rs.getRow());
list.add(del);
}
table.delete(list);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
table.close();
} catch (IOException e) {
e.printStackTrace();
}
}

}

public void deleteCell(String tableName, String rowkey, String cf, String column) {
HTableInterface table = null;
try {
table = hTablePool.getTable(tableName);
Delete del = new Delete(rowkey.getBytes());
del.deleteColumn(cf.getBytes(), column.getBytes());
table.delete(del);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
table.close();
} catch (IOException e) {
e.printStackTrace();
}
}

}

public void createTable(String tableName, String[] columnFamilys) {
try {
// admin 对象
HBaseAdmin admin = new HBaseAdmin(conf);
if (admin.tableExists(tableName)) {
System.err.println("此表,已存在!");
} else {
HTableDescriptor tableDesc = new HTableDescriptor(TableName.valueOf(tableName));

for (String columnFamily : columnFamilys) {
tableDesc.addFamily(new HColumnDescriptor(columnFamily));
}

admin.createTable(tableDesc);
System.err.println("建表成功!");

}
admin.close();// 关闭释放资源
} catch (MasterNotRunningException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ZooKeeperConnectionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

/**
* 删除一个表
*
* @param tableName
* 删除的表名
*/
public void deleteTable(String tableName) {
try {
HBaseAdmin admin = new HBaseAdmin(conf);
if (admin.tableExists(tableName)) {
admin.disableTable(tableName);// 禁用表
admin.deleteTable(tableName);// 删除表
System.err.println("删除表成功!");
} else {
System.err.println("删除的表不存在!");
}
admin.close();
} catch (MasterNotRunningException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ZooKeeperConnectionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

/**
* 查询表中所有行
*
* @param tablename
*/
public void scaner(String tablename) {
try {
HTable table = new HTable(conf, tablename);
Scan s = new Scan();
// s.addColumn(family, qualifier)
// s.addColumn(family, qualifier)
ResultScanner rs = table.getScanner(s);
for (Result r : rs) {

for (Cell cell : r.rawCells()) {
System.out.println("RowName:" + new String(CellUtil.cloneRow(cell)) + " ");
System.out.println("Timetamp:" + cell.getTimestamp() + " ");
System.out.println("column Family:" + new String(CellUtil.cloneFamily(cell)) + " ");
System.out.println("row Name:" + new String(CellUtil.cloneQualifier(cell)) + " ");
System.out.println("value:" + new String(CellUtil.cloneValue(cell)) + " ");
}
}
} catch (IOException e) {
e.printStackTrace();
}
}

public void scanerByColumn(String tablename) {

try {
HTable table = new HTable(conf, tablename);
Scan s = new Scan();
s.addColumn("cf".getBytes(), "201504052237".getBytes());
s.addColumn("cf".getBytes(), "201504052237".getBytes());
ResultScanner rs = table.getScanner(s);
for (Result r : rs) {

for (Cell cell : r.rawCells()) {
System.out.println("RowName:" + new String(CellUtil.cloneRow(cell)) + " ");
System.out.println("Timetamp:" + cell.getTimestamp() + " ");
System.out.println("column Family:" + new String(CellUtil.cloneFamily(cell)) + " ");
System.out.println("row Name:" + new String(CellUtil.cloneQualifier(cell)) + " ");
System.out.println("value:" + new String(CellUtil.cloneValue(cell)) + " ");
}
}
} catch (IOException e) {
e.printStackTrace();
}
}

public static void main(String[] args) {

// 创建表
// String tableName="test";
// String cfs[] = {"cf"};
// dao.createTable(tableName,cfs);

// 存入一条数据
// Put put = new Put("bjsxt".getBytes());
// put.add("cf".getBytes(), "name".getBytes(), "cai10".getBytes()) ;
// dao.save(put, "test") ;

// 插入多列数据
// Put put = new Put("bjsxt".getBytes());
// List<Put> list = new ArrayList<Put>();
// put.add("cf".getBytes(), "addr".getBytes(), "shanghai1".getBytes()) ;
// put.add("cf".getBytes(), "age".getBytes(), "30".getBytes()) ;
// put.add("cf".getBytes(), "tel".getBytes(), "13889891818".getBytes())
// ;
// list.add(put) ;
// dao.save(list, "test");

// 插入单行数据
// dao.insert("test", "testrow", "cf", "age", "35") ;
// dao.insert("test", "testrow", "cf", "cardid", "12312312335") ;
// dao.insert("test", "testrow", "cf", "tel", "13512312345") ;

// List<Result> list = dao.getRows("test", "testrow",new
// String[]{"age"}) ;
// for(Result rs : list)
// {
// for(Cell cell:rs.rawCells()){
// System.out.println("RowName:"+new String(CellUtil.cloneRow(cell))+"
// ");
// System.out.println("Timetamp:"+cell.getTimestamp()+" ");
// System.out.println("column Family:"+new
// String(CellUtil.cloneFamily(cell))+" ");
// System.out.println("row Name:"+new
// String(CellUtil.cloneQualifier(cell))+" ");
// System.out.println("value:"+new String(CellUtil.cloneValue(cell))+"
// ");
// }
// }

// Result rs = dao.getOneRow("test", "testrow");
// System.out.println(new String(rs.getValue("cf".getBytes(),
// "age".getBytes())));

// Result rs = dao.getOneRowAndMultiColumn("cell_monitor_table",
// "29448-513332015-04-05", new
// String[]{"201504052236","201504052237"});
// for(Cell cell:rs.rawCells()){
// System.out.println("RowName:"+new String(CellUtil.cloneRow(cell))+"
// ");
// System.out.println("Timetamp:"+cell.getTimestamp()+" ");
// System.out.println("column Family:"+new
// String(CellUtil.cloneFamily(cell))+" ");
// System.out.println("row Name:"+new
// String(CellUtil.cloneQualifier(cell))+" ");
// System.out.println("value:"+new String(CellUtil.cloneValue(cell))+"
// ");
// }

// dao.deleteTable("cell_monitor_table");
// 创建表
String tableName = "cell_monitor_table";
String cfs[] = { "cf" };
// dao.createTable(tableName,cfs);
}

public static void testRowFilter(String tableName) {
try {
HTable table = new HTable(conf, tableName);
Scan scan = new Scan();
scan.addColumn(Bytes.toBytes("column1"), Bytes.toBytes("qqqq"));
Filter filter1 = new RowFilter(CompareOp.LESS_OR_EQUAL, new BinaryComparator(Bytes.toBytes("laoxia157")));
scan.setFilter(filter1);
ResultScanner scanner1 = table.getScanner(scan);
for (Result res : scanner1) {
System.out.println(res);
}
scanner1.close();

//
// Filter filter2 = new RowFilter(CompareFilter.CompareOp.EQUAL,new
// RegexStringComparator("laoxia4\\d{2}"));
// scan.setFilter(filter2);
// ResultScanner scanner2 = table.getScanner(scan);
// for (Result res : scanner2) {
// System.out.println(res);
// }
// scanner2.close();

Filter filter3 = new RowFilter(CompareOp.EQUAL, new SubstringComparator("laoxia407"));
scan.setFilter(filter3);
ResultScanner scanner3 = table.getScanner(scan);
for (Result res : scanner3) {
System.out.println(res);
}
scanner3.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

@Test
public void testTrasaction() {
try {
HTableInterface table = null;
table = hTablePool.getTable("t_test".getBytes());
// Put put1 =new Put("002".getBytes());
// put1.add("cf1".getBytes(), "name".getBytes(), "王五".getBytes());
// table.put(put1);
Put newput = new Put("001".getBytes());
newput.add("cf1".getBytes(), "like".getBytes(), "看书".getBytes());

boolean f = table.checkAndPut("001".getBytes(), "cf1".getBytes(), "age".getBytes(), "24".getBytes(),
newput);
System.out.println(f);

} catch (Exception e) {
e.printStackTrace();
}

}
}

HBase 微博案例

目的:怎么设计 HBase 表以及内部 RowKey 的设计

需求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1. 添加、查看关注
2. 粉丝列表
3. 写微博
4. 查看首页, 所有关注过的好友发布的最新微博
5. 查看某个用户发布的所有微博

eg: 关注列表 粉丝列表
张三001 李四 王五
李四002 张三,王五
王五003 张三 张三

follow_fan
rowkey cf1(关注列表) cf2(粉丝列表)
001 002=李四; 003=王五;
002 001=张三;003=王五
003 001=张三; 001=张三

tb_write_blog
rowkey cf1
uid_(Max-timestamp) cf1:title;cf1:content;

writed_blog_history_version

Protobuf

Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化

Protobuf 简介

Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准。

目前已经正在使用的有超过 48,162 种报文格式定义和超过 12,183 个 .proto 文件。

他们用于 RPC 系统和持续数据存储系统。

Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化

它很适合做数据存储或 RPC 数据交换格式。

可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。

目前提供了 C++、Java、Python 三种语言的 API。

安装 Google Protocol Buffer

1
2
3
4
5
6
7
8
9
10
11
yum groupinstall Development tools -y

tar -xzf protobuf-2.1.0.tar.gz
# 配置
.cofigure --prefix=/usr/local/protobuf

#编译并安装
make && make install

#配置环境变量
export PROTOBUF=/usr/local/protobuf

protobuf 的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# 编辑 vi  Phone.proto 文件
package com.szxy.hbase;
message PhoneDetail{
required string dnum=1; // 被叫电话
required string type=2; // 通话类型
required string length=3; // 通话时长
required string datastr=4; // 通话时间
}

# 执行
protoc ./Phone --java_out=./

# 将生成的 java 文件拷贝到本地 Eclipse 上

@Test
public void addPhoneRecord2() throws Exception{
List<Put> puts = new ArrayList<>();
for(int i = 0;i < 10;i++){
String phoneNum = generatePhoneNumber("189");
for(int j = 0;j < 100;j++){
String dnum = generatePhoneNumber("138");
String length = r.nextInt(99)+""; // 通话时长
String type = r.nextInt(2)+""; // 1 是主叫,2 是被叫
String dataStr = getDate("2019"); // 通话开始时间
String rowkey =
phoneNum+"_"+(Long.MAX_VALUE-sdf.parse(dataStr).getTime());
Builder builder
= Phone.PhoneDetail.getDefaultInstance().newBuilderForType();
builder.setDnum(dnum);
builder.setLength(length);
builder.setType(type);
builder.setDatastr(dataStr);
Put put
= new Put(rowkey.getBytes());
put.add("cf".getBytes(),"phoneDetail".getBytes(),builder.build().toByteArray());
puts.add(put);
}
}
htable.put(puts);
}

# 编辑 vi phoneDetail.proto 文件
package com.szxy.hbase;

message PhoneDetail
{
required string dnum=1;
required string type=2;
required string length=3;
required string datastr=4;
}
message dayPhoneDetail
{
repeated PhoneDetail phoneDetail = 1;
}

/**
* 生成 10 个人一天的通话记录
* 每行中 列中存储 100 条通话记录
*
*/
@Test
public void addPhoneRecord3() throws Exception{
List<Put> puts = new ArrayList<>();
for(int i = 0;i < 10;i++){
String phoneNum = generatePhoneNumber("189");
String rowkey = phoneNum+"_"+(Long.MAX_VALUE-sdf.parse(getDate2("20190801")).getTime());
Phone.dayPhoneDetail.Builder dayPhoneDetail = Phone.dayPhoneDetail.newBuilder();
for(int j = 0;j < 100;j++){
String dnum = generatePhoneNumber("138");
String length = r.nextInt(99)+""; // 通话时长
String type = r.nextInt(2)+""; // 1 是主叫,2 是被叫
String dataStr = getDate("2019"); // 通话开始时间
PhoneDetail.Builder phoneDetail = Phone.PhoneDetail.newBuilder();
phoneDetail.setDnum(dnum);
phoneDetail.setLength(length);
phoneDetail.setType(type);
phoneDetail.setDatastr(dataStr);
dayPhoneDetail.addPhoneDetail(phoneDetail);
}
Put put = new Put(rowkey.getBytes());
put.add("cf".getBytes(),"day".getBytes(),dayPhoneDetail.build().toByteArray());
puts.add(put);
}
htable.put(puts);
}

/**
* 查看数据
* @throws IOException
*/
@Test
public void getData() throws IOException{
Get get = new Get("18962786962_9223370472247815807".getBytes());
Result result = htable.get(get);
Cell cell = result.getColumnLatestCell("cf".getBytes(), "day".getBytes());
dayPhoneDetail dayPhoneDetail = Phone.dayPhoneDetail.parseFrom(CellUtil.cloneValue(cell));
List<PhoneDetail> phoneDetailList = dayPhoneDetail.getPhoneDetailList();
int count = 0;
for (PhoneDetail pd : phoneDetailList) {
System.out.println(pd.getDnum()+"\t"+pd.getType()+"\t"+pd.getLength()+"\t"+pd.getDatastr());
count ++;
}
System.out.println("count:"+count);
}

HBase 优化

表设计

  • Region 切分

    在表设计时,预先估计表中的数据,并切分多个 region,防止数据过多,导致单个服务器过载。

    Region 是按照 Rowkey 来切分的

  • Rowkey 设计

    Rowkey 按照字典序排列(即 ASCII )。Rowkey 的长度不要太长,符合业务需求即可。

  • ColumnFamily 设计

    列族控制在 1~2 之间,不要超过 3 个列族

  • InMemory

    HBase 中缓存分为写缓存和读缓存。通过 HColumnDescriptor.setInMemory(true) 将表放到RegionServer的缓存中,保证在读取的时候被cache命中。

  • compact & split

    在 HBase 中,数据在更新时首先写入 WAL 日志(HLog)和内存(MemStore)中,MemStore 中的数据是排序的,当 MemStore 累计到一定阈值时,就会创建一个新的 MemStore,并且将老的 MemStore 添加到 flush队列,由单独的线程 flush 到磁盘上,成为一个 StoreFile 。于此同时, 系统会在 zookeeper 中记录一个 redo point,表示这个时刻之前的变更已经持久化了(minor compact)。
    StoreFile 是只读的,一旦创建后就不可以再修改。因此 Hbase 的更新其实是不断追加的操作。当一个 Store中的 StoreFile 达到一定的阈值后,就会进行一次合并(major compact),将对同一个 key 的修改合并到一起,形成一个大的 StoreFile,当 StoreFile 的大小达到一定阈值后,又会对 StoreFile进行分割(split),等分为两个StoreFile。
    由于对表的更新是不断追加的,处理读请求时,需要访问Store中全部的StoreFile和MemStore,将它们按照row key进行合并,由于StoreFile和MemStore都是经过排序的,并且StoreFile带有内存中索引,通常合并过程还是比较快的。
    实际应用中,可以考虑必要时手动进行major compact,将同一个row key的修改进行合并形成一个大的StoreFile。同时,可以将StoreFile设置大些,减少split的发生。

    hbase为了防止小文件(被刷到磁盘的menstore)过多,以保证保证查询效率,hbase需要在必要的时候将这些小的store file合并成相对较大的store file,这个过程就称之为compaction。在hbase中,主要存在两种类型的compaction:minor compaction和major compaction。
    minor compaction:的是较小、很少文件的合并。
    major compaction 的功能是将所有的store file合并成一个,触发major compaction的可能条件有:major_compact 命令、majorCompact() API、region server自动运行(相关参数:hbase.hregion.majoucompaction 默认为24 小时、hbase.hregion.majorcompaction.jetter 默认值为0.2 防止region server 在同一时间进行major compaction)。
    hbase.hregion.majorcompaction.jetter参数的作用是:对参数hbase.hregion.majoucompaction 规定的值起到浮动的作用,假如两个参数都为默认值24和0,2,那么major compact最终使用的数值为:19.2~28.8 这个范围。

    1、关闭自动major compaction
    2、手动编程major compaction
    Timer类,contab
    minor compaction的运行机制要复杂一些,它由一下几个参数共同决定:
    hbase.hstore.compaction.min :默认值为 3,表示至少需要三个满足条件的store file时,minor compaction才会启动
    hbase.hstore.compaction.max 默认值为10,表示一次minor compaction中最多选取10个store file
    hbase.hstore.compaction.min.size 表示文件大小小于该值的store file 一定会加入到minor compaction的store file中
    hbase.hstore.compaction.max.size 表示文件大小大于该值的store file 一定会被minor compaction排除
    hbase.hstore.compaction.ratio 将store file 按照文件年龄排序(older to younger),minor compaction总是从older store file开始选择

写表操作

  1. 多 Table 并发写

    创建多个HTable客户端用于写操作,提高写数据的吞吐量,一个例子:

    1
    2
    3
    4
    5
    6
    7
    8
    static final Configuration conf = HBaseConfiguration.create();
    static final String table_log_name = “user_log”;
    wTableLog = new HTable[tableN];
    for (int i = 0; i < tableN; i++) {
    wTableLog[i] = new HTable(conf, table_log_name);
    wTableLog[i].setWriteBufferSize(5 * 1024 * 1024); //5MB
    wTableLog[i].setAutoFlush(false);
    }
  2. HTable 参数设置:

    • Auto Flush

      通过调用HTable.setAutoFlush(false)方法可以将HTable写客户端的自动flush关闭,这样可以批量写入数据到HBase,而不是有一条put就执行一次更新,只有当put填满客户端写缓存时,才实际向HBase服务端发起写请求。默认情况下auto flush是开启的。

    • Write Buffer

      通过调用HTable.setWriteBufferSize(writeBufferSize)方法可以设置HTable客户端的写buffer大小,如果新设置的buffer小于当前写buffer中的数据时,buffer将会被flush到服务端。其中,writeBufferSize的单位是byte字节数,可以根据实际写入数据量的多少来设置该值。

    • WAL Flag

      在HBae中,客户端向集群中的RegionServer提交数据时(Put/Delete操作),首先会先写WAL(Write Ahead Log)日志(即HLog,一个RegionServer上的所有Region共享一个HLog),只有当WAL日志写成功后,再接着写MemStore,然后客户端被通知提交数据成功;如果写WAL日志失败,客户端则被通知提交失败。这样做的好处是可以做到RegionServer宕机后的数据恢复。

      因此,对于相对不太重要的数据,可以在Put/Delete操作时,通过调用Put.setWriteToWAL(false)或Delete.setWriteToWAL(false)函数,放弃写WAL日志,从而提高数据写入的性能。

      值得注意的是:谨慎选择关闭WAL日志,因为这样的话,一旦RegionServer宕机,Put/Delete的数据将会无法根据WAL日志进行恢复。

  3. 批量写

    通过调用HTable.put(Put)方法可以将一个指定的row key记录写入HBase,同样HBase提供了另一个方法:通过调用HTable.put(List)方法可以将指定的row key列表,批量写入多行记录,这样做的好处是批量执行,只需要一次网络I/O开销,这对于对数据实时性要求高,网络传输RTT高的情景下可能带来明显的性能提升。

  4. 多线程并发写

    在客户端开启多个HTable写线程,每个写线程负责一个HTable对象的flush操作,这样结合定时flush和写buffer(writeBufferSize),可以既保证在数据量小的时候,数据可以在较短时间内被flush(如1秒内),同时又保证在数据量大的时候,写buffer一满就及时进行flush。下面给个具体的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    for (int i = 0; i < threadN; i++) {
    Thread th = new Thread() {
    public void run() {
    while (true) {
    try {
    sleep(1000); //1 second
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    synchronized (wTableLog[i]) {
    try {
    wTableLog[i].flushCommits();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }
    }
    };
    th.setDaemon(true);
    th.start();
    }

读表操作

  1. 多 HTable 并发读

    创建多个HTable客户端用于读操作,提高读数据的吞吐量,一个例子:

    1
    2
    3
    4
    5
    6
    7
    static final Configuration conf = HBaseConfiguration.create();
    static final String table_log_name = “user_log”;
    rTableLog = new HTable[tableN];
    for (int i = 0; i < tableN; i++) {
    rTableLog[i] = new HTable(conf, table_log_name);
    rTableLog[i].setScannerCaching(50);
    }
  2. HTable 参数设置

    • Scanner Caching
      hbase.client.scanner.caching配置项可以设置HBase scanner一次从服务端抓取的数据条数,默认情况下一次一条。通过将其设置成一个合理的值,可以减少scan过程中next()的时间开销,代价是scanner需要通过客户端的内存来维持这些被cache的行记录。
      有三个地方可以进行配置:1)在HBase的conf配置文件中进行配置;2)通过调用HTable.setScannerCaching(int scannerCaching)进行配置;3)通过调用Scan.setCaching(int caching)进行配置。三者的优先级越来越高。
    • Scan Attribute Selection
      scan时指定需要的Column Family,可以减少网络传输数据量,否则默认scan操作会返回整行所有Column Family的数据。
    • Close ResultScanner
      通过scan取完数据后,记得要关闭ResultScanner,否则RegionServer可能会出现问题(对应的Server资源无法释放)。
  3. 批量读

    通过调用HTable.get(Get)方法可以根据一个指定的row key获取一行记录,同样HBase提供了另一个方法:通过调用HTable.get(List)方法可以根据一个指定的row key列表,批量获取多行记录,这样做的好处是批量执行,只需要一次网络I/O开销,这对于对数据实时性要求高而且网络传输RTT高的情景下可能带来明显的性能提升。

  4. 多线程并发读

    在客户端开启多个HTable读线程,每个读线程负责通过HTable对象进行get操作。下面是一个多线程并发读取HBase,获取店铺一天内各分钟PV值的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    public class DataReaderServer {
    //获取店铺一天内各分钟PV值的入口函数
    public static ConcurrentHashMap<String, String> getUnitMinutePV(long uid, long startStamp, long endStamp){
    long min = startStamp;
    int count = (int)((endStamp - startStamp) / (60*1000));
    List<String> lst = new ArrayList<String>();
    for (int i = 0; i <= count; i++) {
    min = startStamp + i * 60 * 1000;
    lst.add(uid + "_" + min);
    }
    return parallelBatchMinutePV(lst);
    }
    //多线程并发查询,获取分钟PV值
    private static ConcurrentHashMap<String, String> parallelBatchMinutePV(List<String> lstKeys){
    ConcurrentHashMap<String, String> hashRet = new ConcurrentHashMap<String, String>();
    int parallel = 3;
    List<List<String>> lstBatchKeys = null;
    if (lstKeys.size() < parallel ){
    lstBatchKeys = new ArrayList<List<String>>(1);
    lstBatchKeys.add(lstKeys);
    }
    else{
    lstBatchKeys = new ArrayList<List<String>>(parallel);
    for(int i = 0; i < parallel; i++ ){
    List<String> lst = new ArrayList<String>();
    lstBatchKeys.add(lst);
    }

    for(int i = 0 ; i < lstKeys.size() ; i ++ ){
    lstBatchKeys.get(i%parallel).add(lstKeys.get(i));
    }
    }

    List<Future< ConcurrentHashMap<String, String> >> futures = new ArrayList<Future< ConcurrentHashMap<String, String> >>(5);

    ThreadFactoryBuilder builder = new ThreadFactoryBuilder();
    builder.setNameFormat("ParallelBatchQuery");
    ThreadFactory factory = builder.build();
    ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(lstBatchKeys.size(), factory);

    for(List<String> keys : lstBatchKeys){
    Callable< ConcurrentHashMap<String, String> > callable = new BatchMinutePVCallable(keys);
    FutureTask< ConcurrentHashMap<String, String> > future = (FutureTask< ConcurrentHashMap<String, String> >) executor.submit(callable);
    futures.add(future);
    }
    executor.shutdown();

    // Wait for all the tasks to finish
    try {
    boolean stillRunning = !executor.awaitTermination(
    5000000, TimeUnit.MILLISECONDS);
    if (stillRunning) {
    try {
    executor.shutdownNow();
    } catch (Exception e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    }
    } catch (InterruptedException e) {
    try {
    Thread.currentThread().interrupt();
    } catch (Exception e1) {
    // TODO Auto-generated catch block
    e1.printStackTrace();
    }
    }

    // Look for any exception
    for (Future f : futures) {
    try {
    if(f.get() != null)
    {
    hashRet.putAll((ConcurrentHashMap<String, String>)f.get());
    }
    } catch (InterruptedException e) {
    try {
    Thread.currentThread().interrupt();
    } catch (Exception e1) {
    // TODO Auto-generated catch block
    e1.printStackTrace();
    }
    } catch (ExecutionException e) {
    e.printStackTrace();
    }
    }

    return hashRet;
    }
    //一个线程批量查询,获取分钟PV值
    protected static ConcurrentHashMap<String, String> getBatchMinutePV(List<String> lstKeys){
    ConcurrentHashMap<String, String> hashRet = null;
    List<Get> lstGet = new ArrayList<Get>();
    String[] splitValue = null;
    for (String s : lstKeys) {
    splitValue = s.split("_");
    long uid = Long.parseLong(splitValue[0]);
    long min = Long.parseLong(splitValue[1]);
    byte[] key = new byte[16];
    Bytes.putLong(key, 0, uid);
    Bytes.putLong(key, 8, min);
    Get g = new Get(key);
    g.addFamily(fp);
    lstGet.add(g);
    }
    Result[] res = null;
    try {
    res = tableMinutePV[rand.nextInt(tableN)].get(lstGet);
    } catch (IOException e1) {
    logger.error("tableMinutePV exception, e=" + e1.getStackTrace());
    }

    if (res != null && res.length > 0) {
    hashRet = new ConcurrentHashMap<String, String>(res.length);
    for (Result re : res) {
    if (re != null && !re.isEmpty()) {
    try {
    byte[] key = re.getRow();
    byte[] value = re.getValue(fp, cp);
    if (key != null && value != null) {
    hashRet.put(String.valueOf(Bytes.toLong(key,
    Bytes.SIZEOF_LONG)), String.valueOf(Bytes
    .toLong(value)));
    }
    } catch (Exception e2) {
    logger.error(e2.getStackTrace());
    }
    }
    }
    }

    return hashRet;
    }
    }
    //调用接口类,实现Callable接口
    class BatchMinutePVCallable implements Callable<ConcurrentHashMap<String, String>>{
    private List<String> keys;

    public BatchMinutePVCallable(List<String> lstKeys ) {
    this.keys = lstKeys;
    }

    public ConcurrentHashMap<String, String> call() throws Exception {
    return DataReadServer.getBatchMinutePV(keys);
    }
    }
  5. 缓存查询

    对于频繁查询HBase的应用场景,可以考虑在应用程序中做缓存,当有新的查询请求时,首先在缓存中查找,如果存在则直接返回,不再查询HBase;否则对HBase发起读请求查询,然后在应用程序中将查询结果缓存起来。至于缓存的替换策略,可以考虑LRU等常用的策略。

  6. BlockCache

    HBase上Regionserver 的内存分为两个部分,一部分作为 Memstore,主要用来写;另外一部分作为BlockCache,主要用于读。

    写请求会先写入Memstore,Regionserver会给每个region提供一个Memstore,当Memstore满64MB以后,会启动 flush刷新到磁盘。当Memstore的总大小超过限制时(heapsize * hbase.regionserver.global.memstore.upperLimit * 0.9),会强行启动flush进程,从最大的Memstore开始flush直到低于限制。

    读请求先到Memstore中查数据,查不到就到BlockCache中查,再查不到就会到磁盘上读,并把读的结果放入BlockCache。由于BlockCache采用的是LRU策略,因此BlockCache达到上限(heapsize * hfile.block.cache.size * 0.85)后,会启动淘汰机制,淘汰掉最老的一批数据。

    一个Regionserver上有一个BlockCache和N个Memstore,它们的大小之和不能大于等于heapsize * 0.8,否则HBase不能启动。默认BlockCache为0.2,而Memstore为0.4。对于注重读响应时间的系统,可以将 BlockCache设大些,比如设置BlockCache=0.4,Memstore=0.39,以加大缓存的命中率。

HTable 和 HTablePool使用注意事项

HTable和HTablePool都是HBase客户端API的一部分,可以使用它们对HBase表进行CRUD操作。下面结合在项目中的应用情况,对二者使用过程中的注意事项做一下概括总结。

1
2
3
4
5
6
7
8
9
10
11
Configuration conf = HBaseConfiguration.create();

try (Connection connection = ConnectionFactory.createConnection(conf)) {

try (Table table = connection.getTable(TableName.valueOf(tablename)) {

// use table as needed, the table returned is lightweight

}

}

HTable

HTable是HBase客户端与HBase服务端通讯的Java API对象,客户端可以通过HTable对象与服务端进行CRUD操作(增删改查)。它的创建很简单:

1
2
3
4
5
Configuration conf = HBaseConfiguration.create();

HTable table = new HTable(conf, "tablename");

//TODO CRUD Operation……

HTable使用时的一些注意事项:

  1. 规避 HTable 对象的创建开销
    因为客户端创建 HTable 对象后,需要进行一系列的操作:检查.META.表确认指定名称的HBase表是否存在,表是否有效等等,整个时间开销比较重,可能会耗时几秒钟之长,因此最好在程序启动时一次性创建完成需要的HTable对象,如果使用 Java API,一般来说是在构造函数中进行创建,程序启动后直接重用。

  2. HTable 对象不是线程安全的
    HTable 对象对于客户端读写数据来说不是线程安全的,因此多线程时,要为每个线程单独创建复用一个HTable 对象,不同对象间不要共享HTable对象使用,特别是在客户端auto flash被置为false时,由于存在本地write buffer,可能导致数据不一致。

  3. HTable对象之间共享Configuration
    HTable对象共享Configuration对象,这样的好处在于:

  • 共享ZooKeeper的连接:每个客户端需要与ZooKeeper建立连接,查询用户的table regions位置,这些信息可以在连接建立后缓存起来共享使用;
  • 共享公共的资源:客户端需要通过ZooKeeper查找-ROOT-和.META.表,这个需要网络传输开销,客户端缓存这些公共资源后能够减少后续的网络传输开销,加快查找过程速度。

因此,与以下这种方式相比:

1
2
3
HTable table1 = new HTable("table1");

HTable table2 = new HTable("table2");

下面的方式更有效些:

1
2
3
4
5
Configuration conf = HBaseConfiguration.create();

HTable table1 = new HTable(conf, "table1");

HTable table2 = new HTable(conf, "table2");

备注:即使是高负载的多线程程序,也并没有发现因为共享Configuration而导致的性能问题;如果你的实际情况中不是如此,那么可以尝试不共享Configuration。

HTablePool

HTablePool可以解决HTable存在的线程不安全问题,同时通过维护固定数量的HTable对象,能够在程序运行期间复用这些HTable资源对象。

1
2
3
Configuration conf = HBaseConfiguration.create();

HTablePool pool = new HTablePool(conf, 10);
  1. HTablePool可以自动创建HTable对象,而且对客户端来说使用上是完全透明的,可以避免多线程间数据并发修改问题。

  2. HTablePool中的HTable对象之间是公用Configuration连接的,能够可以减少网络开销。

HTablePool的使用很简单:每次进行操作前,通过HTablePool的getTable方法取得一个HTable对象,然后进行put/get/scan/delete等操作,最后通过 HTablePool 的 putTable 方法将HTable对象放回到HTablePool中。

下面是个使用HTablePool的简单例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public void createUser(String username, String firstName, String lastName, String email, String password, String roles) throws IOException {

  HTable table = rm.getTable(UserTable.NAME);

  Put put = new Put(Bytes.toBytes(username));

  put.add(UserTable.DATA_FAMILY, UserTable.FIRSTNAME,

  Bytes.toBytes(firstName));

  put.add(UserTable.DATA_FAMILY, UserTable.LASTNAME,

    Bytes.toBytes(lastName));

  put.add(UserTable.DATA_FAMILY, UserTable.EMAIL, Bytes.toBytes(email));

  put.add(UserTable.DATA_FAMILY, UserTable.CREDENTIALS,

    Bytes.toBytes(password));

  put.add(UserTable.DATA_FAMILY, UserTable.ROLES, Bytes.toBytes(roles));

  table.put(put);

  table.flushCommits();

  rm.putTable(table);

}

Hbase和DBMS比较:

查询数据不灵活:

  1. 不能使用column之间过滤查询

  2. 不支持全文索引。使用solr和hbase整合完成全文搜索。

    a) 使用MR批量读取hbase中的数据,在solr里面建立索引(no store)之保存rowkey的值。

    b) 根据关键词从索引中搜索到rowkey(分页)

    c) 根据rowkey从hbase查询所有数据

HBase-MapReduce 整合

1
2


[TOC]

Hive 简介

  • Hive 数据仓库
  • Hive 解释器,编译器,优化器
  • Hive 运行时,元数据存储在关系型数据库里面

Hive 架构

CLI: command line interface 命令行接口

JDBC/ODBC: Java 连接数据库(MySQL、Oracle)

Web GUI: Hive web 用户界面

metastore:表、字段的约束

Driver: Driver 服务,负责 Hadoop 和 Hive 之间的联系()

(1)用户接口主要有三个:CLI,Client 和 WUI。其中最常用的是CLI,Cli启动的时候,会同时启动一个Hive副本。Client是Hive的客户端,用户连接至Hive Server。在启动 Client模式的时候,需要指出Hive Server所在节点,并且在该节点启动Hive Server。 WUI是通过浏览器访问Hive。
(2)Hive将元数据存储在数据库中,如mysql、derby。Hive中的元数据包括表的名字,表的列和分区及其属性,表的属性(是否为外部表等),表的数据所在目录等。
(3)解释器、编译器、优化器完成HQL查询语句从词法分析、语法分析、编译、优化以及查询计划的生成。生成的查询计划存储在HDFS中,并在随后有MapReduce调用执行。
(4)Hive的数据存储在HDFS中,大部分的查询、计算由MapReduce完成(包含*的查询,比如select * from tbl不会生成MapRedcue任务)。

Hive 的架构

编译器将一个Hive SQL转换操作符
操作符是Hive的最小的处理单元
每个操作符代表HDFS的一个操作或者一道MapReduce作业

Operator

Operator都是hive定义的一个处理过程
Operator都定义有:
protected List <Operator<? extends Serializable >> childOperators;
protected List <Operator<? extends Serializable >> parentOperators;
protected boolean done; // 初始化值为false

  • ANTER 词法语法分析工具解析 SQL

Hive 搭建模式

单机模式

通过网络连接到一个数据库中

节点部署:

HOST/Soft MySQL Hive
node01/192.168.170.101 *
hode02/192.168.170.102 *

搭建过程步骤:

  • 安装 MySQL

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # 安装 mysql 服务
    yum install mysql-server -y

    # 输入 msyql 会出现这样的错误信息, 原因是 mysqld 服务未启动
    # ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (2)

    # 启动 mysqld 服务
    service mysqld start

    # 配置 mysql 远程连接以及用户名和密码
    grant all privileges on *.* to root@'%' identified by '123' with grant option;

    # 刷新权限
    flush privileges;

    # 删除 mysql.user 表中的除了远程连接用户外其他用户的记录
    delete from user where mysql.host != '%'

    # 查看 mysql 用户表
    select host,user,password from mysql.user
  • 安装 Hive

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
      # 安装 hive
    # 上传 hive 压缩包并解压
    tar -zxvf hive.x.y.z.tar.gz
    # 移动到 /opt/sxt 目录下
    mv hive.x.y.z /opt/sxt
    # 配置 hive 环境变量,编辑 vi /etc/profile 文件
    export HIVE_HOME=/opt/sxt/hive.x.y.z
    export PATH=$PATH:$HIVE_HOME/bin
    # 使 /etc/profile 生效
    . /etc/profile
    # 输入 hive 命令,查看 hive 是否安装成功

    # 修改配置文件
    cp hive-.xml hive-site.xml
    vi hive-site.xml
    <property>
    <name>hive.metastore.warehouse.dir</name>
    <value>/user/hive_remote/warehouse</value>
    </property>
    <property>
    <name>javax.jdo.option.ConnectionURL</name>
    <value>jdbc:mysql://node01/hive_remote?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>123</value>
    </property>

    # 更新 jar 资源
    # 将 jline.jar 调整为高版本,同时将 hadoop 的低版本删除
    cd $HODOOP_HOME/share/hadoop/yarn/lib/
    rm -fr jline-0.9.94.jar
    cp $HIVE_HOME/lib/jline-2.12.jar ./

    # 启动
    hive

分布式模式

用于非Java客户端访问元数据库,在服务器端启动 MetaStoreServer,客户端利用 Thrift 协议通过MetaStoreServer访问元数据库

搭建环境准备:

mysql hive-server hive-client
node01 *
node03 *
node04 *

搭建步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 搭建分布式 Hive 是建立在单机模式之上
# 从之前的 node02 节点上拷贝 $HIVE_HOME 目录 到 node03、node04 上
# 其中 node03 作为 Hive 服务端, node04 作为 Hive 客户端
scp -r apache-hive-1.2.1-bin/ root@node03:`pwd`/
scp -r apache-hive-1.2.1-bin/ root@node04:`pwd`/

# 配置 node03 node04 的 HIVE 环境变量

# 更新 jar 资源
# 将 jline.jar 调整为高版本,同时将 hadoop 的低版本删除
cd $HODOOP_HOME/share/hadoop/yarn/lib/
rm -fr jline-0.9.94.jar
cp $HIVE_HOME/lib/jline-2.12.jar ./

# 在 node03 上 启动 hive metastore 服务
hive --service metastore
# 在 node04 上启用 hive 客户端
hive

配置环境变量的目的:

  1. 找可执行性文件
  2. 方便其他框架或者服务使用。 eg: HIVE 通过 HADOOP 的环境变量连接到 Hadoop 上

Hive 之 DDL

官方文档地址:

https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL-Overview

DDL 语法

  • 创建表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_name    -- (Note: TEMPORARY available in Hive 0.14.0 and later)
[(col_name data_type [column_constraint_specification] [COMMENT col_comment], ... [constraint_specification])]
[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]
[SKEWED BY (col_name, col_name, ...) -- (Note: Available in Hive 0.10.0 and later)]
ON ((col_value, col_value, ...), (col_value, col_value, ...), ...)
[STORED AS DIRECTORIES]
[
[ROW FORMAT row_format]
[STORED AS file_format]
| STORED BY 'storage.handler.class.name' [WITH SERDEPROPERTIES (...)] -- (Note: Available in Hive 0.6.0 and later)
]
[LOCATION hdfs_path]
[TBLPROPERTIES (property_name=property_value, ...)] -- (Note: Available in Hive 0.6.0 and later)
[AS select_statement]; -- (Note: Available in Hive 0.5.0 and later; not supported for external tables)

CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_name
LIKE existing_table_or_view_name
[LOCATION hdfs_path];

data_type
: primitive_type
| array_type
| map_type
| struct_type
| union_type -- (Note: Available in Hive 0.7.0 and later)

primitive_type
: TINYINT
| SMALLINT
| INT
| BIGINT
| BOOLEAN
| FLOAT
| DOUBLE
| DOUBLE PRECISION -- (Note: Available in Hive 2.2.0 and later)
| STRING
| BINARY -- (Note: Available in Hive 0.8.0 and later)
| TIMESTAMP -- (Note: Available in Hive 0.8.0 and later)
| DECIMAL -- (Note: Available in Hive 0.11.0 and later)
| DECIMAL(precision, scale) -- (Note: Available in Hive 0.13.0 and later)
| DATE -- (Note: Available in Hive 0.12.0 and later)
| VARCHAR -- (Note: Available in Hive 0.12.0 and later)
| CHAR -- (Note: Available in Hive 0.13.0 and later)

array_type
: ARRAY < data_type >

map_type
: MAP < primitive_type, data_type >

struct_type
: STRUCT < col_name : data_type [COMMENT col_comment], ...>

union_type
: UNIONTYPE < data_type, data_type, ... > -- (Note: Available in Hive 0.7.0 and later)

row_format
: DELIMITED [FIELDS TERMINATED BY char [ESCAPED BY char]] [COLLECTION ITEMS TERMINATED BY char]
[MAP KEYS TERMINATED BY char] [LINES TERMINATED BY char]
[NULL DEFINED AS char] -- (Note: Available in Hive 0.13 and later)
| SERDE serde_name [WITH SERDEPROPERTIES (property_name=property_value, property_name=property_value, ...)]

file_format:
: SEQUENCEFILE
| TEXTFILE -- (Default, depending on hive.default.fileformat configuration)
| RCFILE -- (Note: Available in Hive 0.6.0 and later)
| ORC -- (Note: Available in Hive 0.11.0 and later)
| PARQUET -- (Note: Available in Hive 0.13.0 and later)
| AVRO -- (Note: Available in Hive 0.14.0 and later)
| JSONFILE -- (Note: Available in Hive 4.0.0 and later)
| INPUTFORMAT input_format_classname OUTPUTFORMAT output_format_classname

column_constraint_specification:
: [ PRIMARY KEY|UNIQUE|NOT NULL|DEFAULT [default_value]|CHECK [check_expression] ENABLE|DISABLE NOVALIDATE RELY/NORELY ]

default_value:
: [ LITERAL|CURRENT_USER()|CURRENT_DATE()|CURRENT_TIMESTAMP()|NULL ]

constraint_specification:
: [, PRIMARY KEY (col_name, ...) DISABLE NOVALIDATE RELY/NORELY ]
[, PRIMARY KEY (col_name, ...) DISABLE NOVALIDATE RELY/NORELY ]
[, CONSTRAINT constraint_name FOREIGN KEY (col_name, ...) REFERENCES table_name(col_name, ...) DISABLE NOVALIDATE
[, CONSTRAINT constraint_name UNIQUE (col_name, ...) DISABLE NOVALIDATE RELY/NORELY ]
[, CONSTRAINT constraint_name CHECK [check_expression] ENABLE|DISABLE NOVALIDATE RELY/NORELY ]

练习:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
create table psn
(
id int,
name string,
likes array<string>,
address map<string,string>
)
row format delimited
fields terminated by ','
collection items terminated by '-'
map keys terminated by ':';


load data local inpath '/root/data/data' into table psn;

-- 查看表结构
desc formatted 表名

Hive 表

内部表

1
2
3
4
5
6
7
8
9
10
11
create table psn3
(
id int,
name string,
likes array<string>,
address map<string,string>
)
row format delimited
fields terminated by '\001'
collection items terminated by '\002'
map keys terminated by '\003';

外部表

1
2
3
4
5
6
7
8
9
10
11
12
create external table psn4
(
id int,
name string,
likes array<string>,
address map<string,string>
)
row format delimited
fields terminated by ','
collection items terminated by '-'
map keys terminated by ':'
location '/data/hive/input/';

区别*

内部表 MANAGED 外部表 EXTERNAL
创建表时 直接存储在默认的hdfs路径 需要自己指定路径
删除表时 将数据和元数据全部删除 只删除元数据,数据不删除

先有表,后有数据,使用内部表。先有数据,后有表,使用外部表。

注意:

  1. 删除外部表中不会删除 HDFS 中的数据
  2. Hive 读时检查(解耦,便于数据读取); 关系数据库 写时检查

Hive 分区

分区表的意义在于优化查询。查询时尽量利用分区字段。如果不使用分区字段,就会全部扫描。

注意:分区属于元数据,不能通过外部表直接从 HDFS 加载 Hive 中,必须在表定义时指定对应的partition字段

分区建表

a. 单分区建表语句:

1
create table day_table (id int, content string) partitioned by (dt string);

单分区表,按天分区,在表结构中存在 id,content,dt 三列。
以 dt 为文件夹区分

b. 双分区建表语句:

1
create table day_hour_table (id int, content string) partitioned by (dt string, hour string);

双分区表,按天和小时分区,在表结构中新增加了 dt 和 hour 两列。
先以 dt 为文件夹,再以 hour 子文件夹区分

添加分区表语法

(表已创建,在此基础上添加分区):
ALTER TABLE table_name ADD [IF NOT EXISTS] PARTITION partition_spec [LOCATION ‘location1’] partition_spec [LOCATION ‘location2’] …;

partition_spec:
(partition_column = partition_col_value, partition_column = partition_col_value, …)
例:
1
ALTER TABLE day_table ADD PARTITION (dt='2008-08-08', hour='08')

删除分区

LTER TABLE table_name DROP partition_spec, partition_spec,…
partition_spec:
(partition_column = partition_col_value, partition_column = partition_col_value, …)

用户可以用 ALTER TABLE DROP PARTITION 来删除分区。
内部表中、对应分区的元数据和数据将被一并删除。

例:

1
ALTER TABLE day_hour_table DROP PARTITION (dt='2008-08-08', hour='09');

向指定分区添加数据语法

1
2
LOAD DATA [LOCAL] INPATH 'filepath' [OVERWRITE] 
INTO TABLE tablename [PARTITION (partcol1=val1, partcol2=val2 ...)]

例:

1
2
3
4
5
6
7
-- 从 HDFS 中加载数据
LOAD DATA INPATH '/user/pv.txt'
INTO TABLE day_hour_table PARTITION(dt='2008-08- 08', hour='08');

-- 从本地文件系统中加载数据
LOAD DATA local INPATH '/user/hua/*'
INTO TABLE day_hour partition(dt='2010-07- 07');

当数据被加载至表中时,不会对数据进行任何转换。

Load 操作只是将数据复制至 Hive 表对应的位置。数据加载时在表下自动创建一个目录

查询执行分区语法

1
SELECT day_table.* FROM day_table WHERE day_table.dt>= '2008-08-08';

分区表的意义在于优化查询。查询时尽量利用分区字段。如果不使用分区字段,就会全部扫描。

Hive查询表的分区信息语法

1
SHOW PARTITIONS day_hour_table;

预先导入分区数据,但是无法识别怎么办?

1
Msck repair table tablename

直接添加分区

动态分区

  • 开启支持动态分区

    1
    set hive.exec.dynamic.partition=true;

    默认:true

    1
    set hive.exec.dynamic.partition.mode=nostrict;

    默认:strict(至少有一个分区列是静态分区)
    相关参数

    1
    set hive.exec.max.dynamic.partitions.pernode;

    每一个执行mr节点上,允许创建的动态分区的最大数量(100)

    1
    set hive.exec.max.dynamic.partitions;

    所有执行mr节点上,允许创建的所有动态分区的最大数量(1000)

    1
    set hive.exec.max.created.files;

    所有的mr job允许创建的文件的最大数量(100000)

  • 加载数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    create external table psn21(
    id int,
    name string,
    sex string,
    age int,
    likes array<string>,
    address map<string,string>
    )
    row format delimited
    fields terminated by ','
    collection items terminated by '-'
    map keys terminated by ':'
    location '/data/bucket/input';


    create table psn22(
    id int,
    name string,
    likes array<string>,
    address map<string,string>
    )
    partitioned by (age int,sex string)
    row format delimited
    fields terminated by ','
    collection items terminated by '-'
    map keys terminated by ':';

    # hive 命令行中设置动态分区为非严格模式
    set hive.exec.dynamic.partition.mode=nonstrict

    # 注意: 参数的位置要对应
    from psn21
    insert overwrite table psn22 partition(age, sex)
    select id, name, likes, address, age,sex distribute by age, sex;

Hive 之 DML*

官方文档地址:

https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DML#LanguageManualDML-HiveDataManipulationLanguage

加载数据的方式

  • Loading files into tables 从文件中加载数据

    Hive does not do any transformation while loading data into tables. Load operations are currently pure copy/move operations that move datafiles into locations corresponding to Hive tables.

    语法:

    1
    2
    3
    4
    5
    6
    7
    8
    LOAD DATA [LOCAL] INPATH 'filepath' [OVERWRITE] 
    INTO TABLE tablename
    [PARTITION (partcol1=val1, partcol2=val2 ...)]

    LOAD DATA [LOCAL] INPATH 'filepath' [OVERWRITE]
    INTO TABLE tablename
    [PARTITION (partcol1=val1, partcol2=val2 ...)]
    [INPUTFORMAT 'inputformat' SERDE 'serde'] (3.0 or later)

注意: 从 HDFS 中加载数据,数据发生移动,而从本地加载数据,数据发生拷贝。

  • Inserting data into Hive Tables from queries 从查询结果集中加载数据

    Query Results can be inserted into tables by using the insert clause.

    语法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    Standard syntax:
    INSERT OVERWRITE TABLE tablename1 [PARTITION (partcol1=val1, partcol2=val2 ...) [IF NOT EXISTS]] select_statement1 FROM from_statement;
    INSERT INTO TABLE tablename1 [PARTITION (partcol1=val1, partcol2=val2 ...)] select_statement1 FROM from_statement;

    Hive extension (multiple inserts):
    FROM from_statement
    INSERT OVERWRITE TABLE tablename1 [PARTITION (partcol1=val1, partcol2=val2 ...) [IF NOT EXISTS]] select_statement1
    [INSERT OVERWRITE TABLE tablename2 [PARTITION ... [IF NOT EXISTS]] select_statement2]
    [INSERT INTO TABLE tablename2 [PARTITION ...] select_statement2] ...;
    FROM from_statement
    INSERT INTO TABLE tablename1 [PARTITION (partcol1=val1, partcol2=val2 ...)] select_statement1
    [INSERT INTO TABLE tablename2 [PARTITION ...] select_statement2]
    [INSERT OVERWRITE TABLE tablename2 [PARTITION ... [IF NOT EXISTS]] select_statement2] ...;

    Hive extension (dynamic partition inserts):
    INSERT OVERWRITE TABLE tablename PARTITION (partcol1[=val1], partcol2[=val2] ...) select_statement FROM from_statement;
    INSERT INTO TABLE tablename PARTITION (partcol1[=val1], partcol2[=val2] ...) select_statement FROM from_statement;

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    FROM psn
    INSERT OVERWRITE TABLE psn10
    SELECT id,name
    insert into psn11
    select id,likes

    insert overwrite local directory '/root/result'
    select * from psn;

更新操作

  • ACID 事务的特性
  • 三大范式

Hive SerDe

SerDe 用于做序列化和反序列化。

构建在数据存储和执行引擎之间,对两者实现解耦。
Hive通过 ROW FORMAT DELIMITED 以及 SERDE 进行内容的读写。

1
2
3
4
5
6
7
row_format
: DELIMITED
[FIELDS TERMINATED BY char [ESCAPED 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 正则匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE TABLE logtbl (
host STRING,
identity STRING,
t_user STRING,
time STRING,
request STRING,
referer STRING,
agent STRING)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.RegexSerDe'
WITH SERDEPROPERTIES (
"input.regex" = "([^ ]*) ([^ ]*) ([^ ]*) \\[(.*)\\] \"(.*)\" (-|[0-9]*) (-|[0-9]*)"
)
STORED AS TEXTFILE;

Hive Beeline

提供了 JDBC 的访问方式

beenline 不能用于 DML 操作,只能执行一些查询操作

1
2
3
4
5
6
-- 第一种方式
beeline
!connect jdbc:hive2://node04:10000/default root 123

-- 第二种方式
beeline -u connect jdbc:hive2://node04:10000/default -n root

Hive JDBC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class HiveDemo {

// private final static String driver = "org.apache.hive.jdbc.HiveDriver";
// private final static String url = "jdbc:hive2://node04:10000/default";
// private final static String username = "root";
// private final static String password = "123";

public static void main(String[] args) {
try {
Properties prop = new Properties();
prop.load(new FileInputStream(new File("jdbc.properties")));
String driver = prop.getProperty("driver");
String url = prop.getProperty("url");
String username = prop.getProperty("username");
String password = prop.getProperty("password");

Class.forName(driver);
Connection conn = DriverManager.getConnection(url,username,password);
Statement st = conn.createStatement();
String sql = "select * from psn";
ResultSet rs = st.executeQuery(sql);
while(rs.next()){
int id = rs.getInt("id");
String name = rs.getString("name");
System.out.println(id+"\t"+name);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

Hive 函数

官方文档地址:

https://cwiki.apache.org/confluence/display/Hive/LanguageManual+UDF

自定义 UDF

官方文档地址:

https://cwiki.apache.org/confluence/display/Hive/HivePlugins

  • java 代码
1
2
3
4
5
6
7
8
9
10
public class TuoMing extends UDF{

public Text evaluate(final Text s) {
if (s == null) {
return null;
}
String str = s.toString().substring(0, 3) + "***";
return new Text(str);
}
}
  • 将 java 代码文件打包成 jar ,上传 Linux 上的 HDFS 中

  • 创建临时函数

    1
    2
    3
    4
    5
    6
    7
    -- 本地文件系统加载
    add jar /root/tm/tm.jar;
    create temporary function tm as 'com.szxy.hive.TuoMing';

    -- 从 HDFS 中加载
    create temporary function tms as 'com.szxy.hive.TuoMing'
    using jar 'hdfs://node01:8020/data/jar/tm/tm.jar';
  • 使用临时函数

    1
    select tms(name) from psn;
  • 结果

Hive 案例

struct 结构体

  • 测试数据

    1
    2
    3
    4
    1001,zhangsan:24
    1002,lisi:25
    1003,wangwu:26
    1004,zhaoliu:27
  • 创建表

    1
    2
    3
    4
    5
    6
    7
    8
    create table student(
    id int,
    info struct<name:string,age:int>)
    row format delimited
    fields terminated by ","
    collection items terminated by ":";

    load data inpath '/data/struct/input' into table student;

WordCount

1
2
3
4
5
6
7
8
9
10
11
12
13
create external table hello(
line string
)
location '/data/wc/input'

create table hello_wc(
word string ,
num int
);

from (select explode(split(line,' ')) word from hello ) t
insert into hello_wc
select word,count(word) group by word;

基站掉话率统计

  • 需求:

    找出掉线率最高的前10基站

  • sql 语句

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    create table tb_cell_result(
    imei string,
    drop_num int,
    duration int,
    drop_rate double
    );

    create external table tb_cell(
    record_time string,
    imei string,
    cell string,
    ph_num int,
    call_num string,
    drop_num int,
    duration int,
    drop_rate int,
    net_type string,
    erl int
    )
    row format delimited fields terminated by ','
    location '/data/cell/input';

    from tb_cell
    insert into tb_cell_result
    select imei,sum(drop_num) sdrop,sum(duration) sdura, sum(drop_num)/sum(duration) srate group by imei sorted by srate desc;

    select * from tb_cell_result limit 10;

Hive 参数

  • hive 参数、变量

    hive当中的参数、变量,都是以命名空间开头

    命名空间 读写权限 含义
    hiveconf 可读写 hive-site.xml 中配置各种变量
    例:hive --hiveconf hive.cli.print.header=true
    System 可读写 系统变量,包括 JVM 运行参数等
    例:system:user.name=root
    env 只读 环境变量“
    例:env:JAVA_HOME
    hivevar 可读写 例:hive -d val=key

    通过 ${} 方式进行引用,其中 system、env 下的变量必须以前缀开头

  • hive 参数设置方式

    1. 修改配置文件 ${HIVE_HOME}/conf/hive-site.xml

      1
      2
      3
      4
      <property>
      <name>hive.cli.print.header</name>
      <value>true</value>
      </property>
    2. 启动 hive cli 时,通过 --hiveconf key=value的方式进行设置

      ​ 例:hive --hiveconf hive.cli.print.header=true

    3. 进入cli之后,通过使用set命令设置

      1
      set hive.cli.print.header=true;

Hive 分桶

分桶概念

  • 分桶表是对列值取哈希值的方式,将不同数据放到不同文件中存储。

  • 对于hive中每一个表、分区都可以进一步进行分桶。

  • 由列的哈希值除以桶的个数来决定每条数据划分在哪个桶中。

适用场景

数据抽样( sampling )

分桶操作

  • 开启支持分桶

    1
    set hive.enforce.bucketing=true;

    默认:false;设置为 true之后,mr运行时会根据 bucket 的个数自动分配 reduce task 个数。

    (用户也可以通过mapred.reduce.tasks自己设置reduce任务个数,但分桶时不推荐使用)
    注意:一次作业产生的桶(文件数量)和reduce task个数一致。

  • 往分桶表中加载数据

    1
    2
    insert into table bucket_table select columns from tbl;
    insert overwrite table bucket_table select columns from tbl;
  • 桶表 抽样查询

    1
    select * from bucket_table tablesample(bucket 1 out of 4 on columns);
  • TABLESAMPLE 语法

    1
    TABLESAMPLE(BUCKET x OUT OF y)

    x:表示从哪个bucket开始抽取数据
    y:必须为该表总bucket数的倍数或因子

  • 栗子
    当表总 bucket 数为32时

    TABLESAMPLE(BUCKET 3 OUT OF 8),抽取哪些数据?

    答:共抽取2(32/16)个bucket的数据,抽取第2、第18(16+2)个bucket的数据

    TABLESAMPLE(BUCKET 3 OUT OF 256),抽取哪些数据?

分桶案例

  • 测试数据

    1
    2
    3
    4
    5
    6
    7
    8
    1,tom,11
    2,cat,22
    3,dog,33
    4,hive,44
    5,hbase,55
    6,mr,66
    7,alice,77
    8,scala,88
  • 创建 hive 表

    1
    2
    3
    4
    5
    6
    7
    8
    create external table tb_bucket(
    id int,
    name string,
    score int
    )
    row format delimited
    fields terminated by ','
    location '/data/bucket/input';
  • 创建分桶表

    1
    2
    3
    4
    5
    6
    7
    8
    create table psn_bucket(
    id int,
    name string,
    score int
    )
    clustered by(score) into 4 buckets
    row format delimited
    fields terminated by ',';
  • 向分桶表中添加数据

    1
    insert into psn_bucket select id,name,score from tb_bucket;

    注意:Hive 分桶默认是关闭的,通过 set hive.enforce.bucketing=true;开启分桶

  • 抽样

    1
    select id,name,score from psn_bucket tablesample(bucket 2 out of 4);

Hive Laternal View

在 UDTF 函数中使用

Lateral View用于和UDTF函数(explode、split)结合来使用。
首先通过UDTF函数拆分成多行,再将多行结果组合成一个支持别名的虚拟表。
主要解决在select使用UDTF做查询过程中,查询只能包含单个UDTF,不能包含其他字段、以及多个UDTF的问题

语法:

1
LATERAL VIEW udtf(expression) tableAlias AS columnAlias (',' columnAlias)

注意: 列别名有多个,并且可以重复

栗子

统计人员表中共有多少种爱好、多少个城市?

1
2
3
4
5
select count(distinct(myCol1)), count(distinct(myCol2)) from psn
LATERAL VIEW explode(likes) myTable1 AS myCol1
LATERAL VIEW explode(address) myTable2 AS myCol2, myCol3;

select count(distinct(mycol)) from psn lateral view explode(likes) myTable as mycol;

Hive 视图

视图本质上就是一个虚拟表 Virtual Table,和关系型数据库中的普通视图一样,hive也支持视图

特点:

  • 不支持物化视图

  • 只能查询,不能做加载数据操作

  • 视图的创建,只是保存一份元数据,查询视图时才执行对应的子查询

  • view定义中若包含了ORDER BY/LIMIT语句,当查询视图时也进行ORDER BY/LIMIT语句操作,view当中定义的优先级更高

  • view支持迭代视图

view语法

  • 创建视图:

    1
    2
    3
    4
    5
    CREATE VIEW [IF NOT EXISTS] [db_name.]view_name 
    [(column_name [COMMENT column_comment], ...) ]
    [COMMENT view_comment]
    [TBLPROPERTIES (property_name = property_value, ...)]
    AS SELECT ... ;
  • 查询视图:

    1
    select colums from view;
  • 删除视图:

    1
    DROP VIEW [IF EXISTS] [db_name.]view_name

Hive 索引

  • 目的

    优化查询以及检索性能

  • 创建索引:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    create index t1_index on table psn(name) 
    as 'org.apache.hadoop.hive.ql.index.compact.CompactIndexHandler'
    with deferred rebuild
    in table t1_index_table;
    -- as:指定索引器;
    -- in table:指定索引表,若不指定默认生成在default__psn2_t1_index__表中

    create index t1_index on table psn2(name)
    as 'org.apache.hadoop.hive.ql.index.compact.CompactIndexHandler'
    with deferred rebuild;
  • 查询索引

    1
    show index on psn2;
  • 重建索引(建立索引之后必须重建索引才能生效)

    1
    ALTER INDEX t1_index ON psn REBUILD;
  • 删除索引

    1
    DROP INDEX IF EXISTS t1_index ON psn;

Hive 运行方式

  1. 命令行方式cli:控制台模式

    • 与hdfs交互
      执行执行dfs命令
      例:dfs –ls /
    • 与Linux交互
      !开头
      例:!pwd
  2. 脚本运行方式(实际生产环境中用最多)

    1
    2
    3
    4
    5
    6
    hive -e ""
    hive -e "">aaa
    hive -S -e "">aaa
    hive -f file
    hive -i /home/my/hive-init.sql
    hive> source file (在hive cli中运行)
  3. JDBC方式:hiveserver2

  4. web GUI接口 (hwi、hue等)

Hive 权限管理

官方文档地址:

https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Authorization

三种授权模型:

  1. Storage Based Authorization in the Metastore Server

    基于存储的授权 - 可以对Metastore中的元数据进行保护,但是没有提供更加细粒度的访问控制(例如:列级别、行级别)。

  2. SQL Standards Based Authorization in HiveServer2

    基于SQL标准的Hive授权 - 完全兼容SQL的授权模型,推荐使用该模式。
    https://cwiki.apache.org/confluence/display/Hive/SQL+Standard+Based+Hive+Authorization

  3. Default Hive Authorization (Legacy Mode)

    hive默认授权 - 设计目的仅仅只是为了防止用户产生误操作,而不是防止恶意用户访问未经授权的数据。

SQL Standards Based Authorization in HiveServer2

  • 完全兼容SQL的授权模型

  • 除支持对于用户的授权认证,还支持角色 role 的授权认证

    role可理解为是一组权限的集合,通过role为用户授权

    一个用户可以具有一个或多个角色

    默认包含另种角色:public、admin

  • 限制:
    1、启用当前认证方式之后,dfs, add, delete, compile, and reset等命令被禁用。

    2、通过set命令设置hive configuration的方式被限制某些用户使用。

    (可通过修改配置文件hive-site.xml中hive.security.authorization.sqlstd.confwhitelist进行配置)

    3、添加、删除函数以及宏的操作,仅为具有admin的用户开放。

    4、用户自定义函数(开放支持永久的自定义函数),可通过具有admin角色的用户创建,其他用户都可以使用。

    5、Transform功能被禁用。

  • 在hive服务端修改配置文件hive-site.xml添加以下配置内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <property>
    <name>hive.security.authorization.enabled</name>
    <value>true</value>
    </property>
    <property>
    <name>hive.server2.enable.doAs</name>
    <value>false</value>
    </property>
    <property>
    <name>hive.users.in.admin.role</name>
    <value>root</value>
    </property>
    <property>
    <name>hive.security.authorization.manager</name>
    <value>org.apache.hadoop.hive.ql.security.authorization.plugin.sqlstd.SQLStdHiveAuthorizerFactory</value>
    </property>
    <property>
    <name>hive.security.authenticator.manager</name>
    <value>org.apache.hadoop.hive.ql.security.SessionStateUserAuthenticator</value>
    </property>

    服务端启动hiveserver2;客户端通过beeline进行连接

Hive 优化*

核心思想:把 Hive SQL 当做Mapreduce程序去优化

以下 SQL 不会转为Mapreduce来执行:

  1. select 仅查询本表字段

  2. where 仅对本表字段做条件过滤

Explain 显示执行计划

1
EXPLAIN [EXTENDED] query

Hive抓取策略

Hive中对某些情况的查询不需要使用 MapReduce 计算

抓取策略

1
Set hive.fetch.task.conversion=none/more;

Hive运行方式

  • 本地模式

  • 集群模式

开启本地模式

1
set hive.exec.mode.local.auto=true;

注意:
hive.exec.mode.local.auto.inputbytes.max 默认值为128M
表示加载文件的最大值,若大于该配置仍会以集群方式来运行

并行计算

通过设置以下参数开启并行模式:

1
set hive.exec.parallel=true;

注意:hive.exec.parallel.thread.number
(一次SQL计算中允许并行执行的job个数的最大值)

严格模式

  • 通过设置以下参数开启严格模式:
1
set hive.mapred.mode=strict;

(默认为:nonstrict非严格模式)

  • 查询限制

    1、对于分区表,必须添加where对于分区字段的条件过滤;

    2、order by语句必须包含limit输出限制;

    3、限制执行笛卡尔积的查询。

Hive排序

  • Order By - 对于查询结果做全排序,只允许有一个reduce处理
    (当数据量较大时,应慎用。严格模式下,必须结合limit来使用)
  • Sort By - 对于单个reduce的数据进行排序
  • Distribute By - 分区排序,经常和Sort By结合使用
  • Cluster By - 相当于 Sort By + Distribute By
    (Cluster By不能通过asc、desc的方式指定排序规则;
    可通过 distribute by column sort by column asc|desc 的方式)

Hive Join

Join计算时,将小表(驱动表)放在join的左边

Map Join:在Map端完成Join

两种实现方式:

  1. SQL方式,在SQL语句中添加 MapJoin 标记(mapjoin hint)
    语法:
1
2
SELECT  /*+ MAPJOIN(smallTable) */  smallTable.key,  bigTable.value 
FROM smallTable JOIN bigTable ON smallTable.key = bigTable.key;
  1. 开启自动的MapJoin

    自动的mapjoin
    通过修改以下配置启用自动的 mapjoin:

    1
    set hive.auto.convert.join = true;

    (该参数为true时,Hive 自动对左边的表统计量,如果是小表就加入内存,即对小表使用 Map join)

    相关配置参数:

    1
    hive.mapjoin.smalltable.filesize;

    (大表小表判断的阈值,如果表的大小小于该值则会被加载到内存中运行)

    1
    hive.ignore.mapjoin.hint;

    (默认值:true;是否忽略mapjoin hint 即mapjoin标记)

    1
    hive.auto.convert.join.noconditionaltask;

    (默认值:true;将普通的join转化为普通的mapjoin时,是否将多个mapjoin转化为一个mapjoin)

    1
    hive.auto.convert.join.noconditionaltask.size;

    (将多个mapjoin转化为一个mapjoin时,其表的最大值)

    注意:hive.exec.parallel.thread.number(一次SQL计算中允许并行执行的job个数的最大值)

    • 尽可能使用相同的连接键(会转化为一个MapReduce作业)
  • 大表join大表

    空key过滤:有时join超时是因为某些key对应的数据太多,而相同key对应的数据都会发送到相同的reducer上,从而导致内存不够。此时我们应该仔细分析这些异常的key,很多情况下,这些key对应的数据是异常数据,我们需要在SQL语句中进行过滤。
    空key转换:有时虽然某个key为空对应的数据很多,但是相应的数据不是异常数据,必须要包含在join的结果中,此时我们可以表a中key为空的字段赋一个随机的值,使得数据随机均匀地分不到不同的reducer上

Map-Side聚合

通过设置以下参数开启在Map端的聚合:

1
set hive.map.aggr=true;

相关配置参数:

1
hive.groupby.mapaggr.checkinterval:

map 端 group by 执行聚合时处理的多少行数据(默认:100000)

1
hive.map.aggr.hash.min.reduction:

进行聚合的最小比例(预先对100000条数据做聚合,若聚合之后的数据量/100000的值大于该配置0.5,则不会聚合)

1
hive.map.aggr.hash.percentmemory:

map端聚合使用的内存的最大值

1
hive.map.aggr.hash.force.flush.memory.threshold:

map端做聚合操作是hash表的最大可用内容,大于该值则会触发flush

1
hive.groupby.skewindata

是否对GroupBy产生的数据倾斜做优化,默认为false

相关配置参数:

1
hive.groupby.mapaggr.checkinterval:

map端group by执行聚合时处理的多少行数据(默认:100000)

1
hive.map.aggr.hash.min.reduction:

进行聚合的最小比例(预先对100000条数据做聚合,若聚合之后的数据量/100000的值大于该配置0.5,则不会聚合)

1
hive.map.aggr.hash.percentmemory:

map端聚合使用的内存的最大值

1
hive.map.aggr.hash.force.flush.memory.threshold:

map端做聚合操作是hash表的最大可用内容,大于该值则会触发flush

1
hive.groupby.skewindata

是否对GroupBy产生的数据倾斜做优化,默认为false

合并小文件

文件数目小,容易在文件存储端造成压力,给hdfs造成压力,影响效率

  • 设置合并属性

    是否合并map输出文件:hive.merge.mapfiles=true
    是否合并reduce输出文件:hive.merge.mapredfiles=true;
    合并文件的大小:hive.merge.size.per.task=25610001000

  • 去重统计

    数据量小的时候无所谓,数据量大的情况下,由于 COUNT DISTINCT 操作需要用一个 Reduce Task来完成,这一个Reduce需要处理的数据量太大,就会导致整个Job很难完成,一般COUNT DISTINCT使用先GROUP BY再COUNT的方式替换

控制Hive中Map以及Reduce的数量

  • Map 数量相关的参数

    参数设置 解释
    mapred.max.split.size 一个split的最大值,即每个map处理文件的最大值
    mapred.min.split.size.per.node 一个节点上split的最小值
    mapred.min.split.size.per.rack 一个机架上split的最小值
  • Reduce 数量相关的参数
    | 参数| 解释|
    | —- | —- | —- |
    | mapred.reduce.tasks | 强制指定reduce任务的数量|
    | hive.exec.reducers.bytes.per.reducer | 每个reduce任务处理的数据量 |
    | hive.exec.reducers.max| 每个任务最大的reduce数|

Hive- JVM重用

适用场景:
1、小文件个数过多
2、task个数过多

通过 set mapred.job.reuse.jvm.num.tasks=n;来设置
(n为task插槽个数)

缺点:

设置开启之后,task插槽会一直占用资源,不论是否有task运行,

直到所有的 task 即整个 job 全部执行完成时,才会释放所有的 task 插槽资源!

MapReduce

MapTask & ReduceTask

一个切片对应一个 Map,也就是说切片的数量决定了 Map 的数量

split 切片指逻辑上概念,用于指定 Map 处理数据的大小

切片用于将 HDFS中的块与 Map 之间解耦

Reduce 的数量由人来决定,根据前面的组的推导

MR 原语

输入(格式化k,v)数据集 -> map映射成一个中间数据集(k,v) -> reduce
“相同”的 key 为一组,调用一次 reduce 方法,方法内迭代这一组数据进行计算

关系/对应比例 block > split split > map map > reduce group(key)>partition partition > outputfile
1:1 * * * *
1:N * * 违背了原语
N:1 * * *
N:M * *

Shuffler<洗牌>

框架内部实现机制
分布式计算节点数据流转:连接 MapTask 与 ReduceTask

计算框架

作用
Map 读懂数据
映射为KV模型
并行分布式
计算向数据移动
Reduce 数据全量/分量加工
Reduce中可以包含不同的key
相同的Key汇聚到一个Reduce中
相同的Key调用一次reduce方法
排序实现key的汇聚
K,V使用自定义数据类型
作为参数传发成本,提高程序自由度
- Writable 序列化
- Comparable 比较器
实现具体排序(字典序,数值序等)

MapReduce 1.x

计算向数据移动

计算框架 Mapper

计算框架 Reducer

MRv1角色: 作用
JobTracker 核心,,单点
调度所有的作业
监控整个集群的资源负载
TaskTracker ,自身节点资源管理
和 JobTracker 心跳,汇报资源,获取Task
Client 作业为单位
规划作业计算分布
提交作业资源到HDFS
最终提交作业到 JobTracker
弊端: JobTracker:负载过重,单点故障
资源管理与计算调度强耦合,其他计算框架需要重复实现资源管理
不同框架对资源不能全局管理

MRV2 之 YARN

YARN:解耦资源与计算

作用
ResourceManager 主,核心
集群节点资源管理
NodeManager 与RM汇报资源
管理Container生命周期
计算框架中的角色都以Container表示
Container 【节点NM,CPU,MEM,I/O大小,启动命令】
默认NodeManager启动线程监控Container大小,超出申请资源额度,kill
支持Linux内核的Cgroup
MR - MR-ApplicationMaster-Container
x作业为单位,避免单点故障,负载到不同的节点
创建Task需要和RM申请资源(Container)
- Task-Container
Client RM-Client:请求资源创建AM
AM-Client:与AM交互

搭建 yarn

准备

NN-1 NN-2 DN ZK ZKFC JNN RM NM
node01 * * *
node02 * * * * * *
node03 * * * * *
node04 * * * *

说明:

1. HA 高可用 HDFS 
 2. RM 资源管理器采用主从架构,使用 Zookeeper 做分布式协调
 3. NM 的数量与 DN 的数量相同

修改配置文件

  • mapred-site.xml
1
2
3
4
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
  • yarn-site.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<property>
<name>yarn.resourcemanager.ha.enabled</name>
<value>true</value>
</property>
<property>
<name>yarn.resourcemanager.cluster-id</name>
<value>cluster1</value>
</property>
<property>
<name>yarn.resourcemanager.ha.rm-ids</name>
<value>rm1,rm2</value>
</property>
<property>
<name>yarn.resourcemanager.hostname.rm1</name>
<value>node03</value>
</property>
<property>
<name>yarn.resourcemanager.hostname.rm2</name>
<value>node04</value>
</property>
<property>
<name>yarn.resourcemanager.zk-address</name>
<value>node02:2181,node03:2181,node04:2181</value>
</property>
  • 分发
1
2
3
4
# 将 node01 修改的配置文件分发给 node02、node03、node04
scp mapred-site.xml yarn-site.xml root@node02:`pwd`
scp mapred-site.xml yarn-site.xml root@node03:`pwd`
scp mapred-site.xml yarn-site.xml root@node04:`pwd`

部署 yarn

1
2
3
4
5
6
# 在 node01。 
# node01 可以免秘钥直接访问其他三个节点 node02、node03、node04
# 这样 node01 上 hadoop 管理脚本可以直接操纵其他其他机器上的 hadoop
start-yarn.sh
# 在 node03、node04
yarn-daemon.sh start resourcemanager

访问 yarn web 界面

1
http://node03:8088

直接访问 http://node04:8088 ,会自动重定向到 http://node03:8088

测试-运行 wordCount 程序

1
2
# 进入 hadoop-2.6.5/share/hadoop/mapreduce 目录下
hadoop jar hadoop-mapreduce-examples-2.6.5.jar wordcount /user/root/test.txt /data/wc/output

手写 wordcount 程序

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class MainClient {

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

// Create a new Job
// Job job = Job.getInstance();
job.setJarByClass(MainClient.class);

// Specify various job-specific parameters
job.setJobName("myjob");

// job.setInputPath(new Path("in"));
// job.setOutputPath(new Path("out"));
// import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
Path path = new Path("/user/root/test.txt");
FileInputFormat.addInputPath(job, path);

Path output = new Path("/data/wc/output");
if(output.getFileSystem(conf).exists(output)){
output.getFileSystem(conf).delete(output, true);
}
FileOutputFormat.setOutputPath(job, output );

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


job.setReducerClass(MyReducer.class);

// Submit the job, then poll for progress until the job is complete
job.waitForCompletion(true);

}

}

Mapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 自定义 Mapper
*
* @author zwer Hadoop 对基本数据类型进行了包装
*
*
*/
public class MyMapper extends Mapper<Object, Text, Text, IntWritable> {

private final static IntWritable one = new IntWritable(1);
private Text word = new Text();

public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
// StringTokenizer 对单词数字进行分割
StringTokenizer itr = new StringTokenizer(value.toString());
while (itr.hasMoreTokens()) {
word.set(itr.nextToken());
context.write(word, one);
}
}

}

Reducer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyReducer
extends Reducer<Text, IntWritable, Text, IntWritable> {

private IntWritable result = new IntWritable();
//相同的 key 为一组,调用一次 reduce 方法,方法内迭代这一组数据进行计算 (sum count max min)
public void reduce(Text key, Iterable<IntWritable> values, Context context)
throws IOException, InterruptedException {
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
result.set(sum);
context.write(key, result);
}

}

注意: 导出 jar 的 JDK 版本与 Linux 上 JDK 版本(大版本号)一致

需求

数据流图

JS-SDK

Java-SDK

BlockingQueue 使用 :

https://app.yinxiang.com/shard/s66/nl/15607671/d69a1aac-5cd6-46fd-ad1f-b28a4d7850b4

Nginx 搭建

注册 Nginx 服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
安装之前准备
1、依赖 gcc openssl-devel pcre-devel zlib-devel
安装:yum install gcc openssl-devel pcre-devel zlib-devel -y

安装Nginx
./configure

make && make install

默认安装目录:
/usr/local/nginx


配置Nginx为系统服务,以方便管理
1、在/etc/rc.d/init.d/目录中建立文本文件nginx
2、在文件中粘贴下面的内容:
#!/bin/sh
#
# nginx - this script starts and stops the nginx daemon
#
# chkconfig: - 85 15
# description: Nginx is an HTTP(S) server, HTTP(S) reverse \
# proxy and IMAP/POP3 proxy server
# processname: nginx
# config: /etc/nginx/nginx.conf
# config: /etc/sysconfig/nginx
# pidfile: /var/run/nginx.pid

# Source function library.
. /etc/rc.d/init.d/functions

# Source networking configuration.
. /etc/sysconfig/network

# Check that networking is up.
[ "$NETWORKING" = "no" ] && exit 0

nginx="/usr/local/nginx/sbin/nginx"
prog=$(basename $nginx)

NGINX_CONF_FILE="/usr/local/nginx/conf/nginx.conf"

[ -f /etc/sysconfig/nginx ] && . /etc/sysconfig/nginx

lockfile=/var/lock/subsys/nginx

make_dirs() {
# make required directories
user=`nginx -V 2>&1 | grep "configure arguments:" | sed 's/[^*]*--user=\([^ ]*\).*/\1/g' -`
options=`$nginx -V 2>&1 | grep 'configure arguments:'`
for opt in $options; do
if [ `echo $opt | grep '.*-temp-path'` ]; then
value=`echo $opt | cut -d "=" -f 2`
if [ ! -d "$value" ]; then
# echo "creating" $value
mkdir -p $value && chown -R $user $value
fi
fi
done
}

start() {
[ -x $nginx ] || exit 5
[ -f $NGINX_CONF_FILE ] || exit 6
make_dirs
echo -n $"Starting $prog: "
daemon $nginx -c $NGINX_CONF_FILE
retval=$?
echo
[ $retval -eq 0 ] && touch $lockfile
return $retval
}

stop() {
echo -n $"Stopping $prog: "
killproc $prog -QUIT
retval=$?
echo
[ $retval -eq 0 ] && rm -f $lockfile
return $retval
}

restart() {
configtest || return $?
stop
sleep 1
start
}

reload() {
configtest || return $?
echo -n $"Reloading $prog: "
killproc $nginx -HUP
RETVAL=$?
echo
}

force_reload() {
restart
}

configtest() {
$nginx -t -c $NGINX_CONF_FILE
}

rh_status() {
status $prog
}

rh_status_q() {
rh_status >/dev/null 2>&1
}

case "$1" in
start)
rh_status_q && exit 0
$1
;;
stop)
rh_status_q || exit 0
$1
;;
restart|configtest)
$1
;;
reload)
rh_status_q || exit 7
$1
;;
force-reload)
force_reload
;;
status)
rh_status
;;
condrestart|try-restart)
rh_status_q || exit 0
;;
*)
echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload|configtest}"
exit 2
esac

3、修改nginx文件的执行权限
chmod +x nginx
4、添加该文件到系统服务中去
chkconfig --add nginx
查看是否添加成功
chkconfig --list nginx

启动,停止,重新装载
service nginx start|stop

Nginx.conf 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#user  nobody;
worker_processes 1;

events {
worker_connections 1024;
}

http {
include mime.types;
default_type application/octet-stream;

log_format my_format '$remote_addr^A$msec^A$http_host^A$request_uri';

sendfile on;
#tcp_nopush on;

#keepalive_timeout 0;
keepalive_timeout 65;

server {
listen 80;
server_name localhost;

location / {
root html;
index index.html index.htm;
}
location = /log.gif {
default_type image/gif;
access_log /opt/data/access.log my_format;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}

}
}

Flume

Flume 简介

Flume is a distributed, reliable, and available service for efficiently collecting, aggregating, and moving large amounts of log data.

It has a simple and flexible architecture based on streaming data flows. It is robust and fault tolerant with tunable reliability mechanisms and many failover and recovery mechanisms. It uses a simple extensible data model that allows for online analytic application.

Flume 官网:http://flume.apache.org/

Flume 安装配置及案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
安装
1、上传
2、解压
3、修改conf/flume-env.sh 文件中的JDK目录
注意:JAVA_OPTS 配置 如果我们传输文件过大 报内存溢出时 需要修改这个配置项
4、验证安装是否成功 ./flume-ng version
5、配置环境变量
export FLUME_HOME=/home/apache-flume-1.6.0-bin


Source、Channel、Sink有哪些类型
Flume Source
Source类型 | 说明
Avro Source | 支持Avro协议(实际上是Avro RPC),内置支持
Thrift Source | 支持Thrift协议,内置支持
Exec Source | 基于Unix的command在标准输出上生产数据
JMS Source | 从JMS系统(消息、主题)中读取数据
Spooling Directory Source | 监控指定目录内数据变更
Twitter 1% firehose Source| 通过API持续下载Twitter数据,试验性质
Netcat Source | 监控某个端口,将流经端口的每一个文本行数据作为Event输入
Sequence Generator Source | 序列生成器数据源,生产序列数据
Syslog Sources | 读取syslog数据,产生Event,支持UDP和TCP两种协议
HTTP Source | 基于HTTP POST或GET方式的数据源,支持JSON、BLOB表示形式
Legacy Sources | 兼容老的Flume OG中Source(0.9.x版本)

Flume Channel
Channel类型 说明
Memory Channel | Event数据存储在内存中
JDBC Channel | Event数据存储在持久化存储中,当前Flume Channel内置支持Derby
File Channel | Event数据存储在磁盘文件中
Spillable Memory Channel | Event数据存储在内存中和磁盘上,当内存队列满了,会持久化到磁盘文件
Pseudo Transaction Channel | 测试用途
Custom Channel | 自定义Channel实现

Flume Sink
Sink类型 说明
HDFS Sink | 数据写入HDFS
Logger Sink | 数据写入日志文件
Avro Sink | 数据被转换成Avro Event,然后发送到配置的RPC端口上
Thrift Sink | 数据被转换成Thrift Event,然后发送到配置的RPC端口上
IRC Sink | 数据在IRC上进行回放
File Roll Sink | 存储数据到本地文件系统
Null Sink | 丢弃到所有数据
HBase Sink | 数据写入HBase数据库
Morphline Solr Sink | 数据发送到Solr搜索服务器(集群)
ElasticSearch Sink | 数据发送到Elastic Search搜索服务器(集群)
Kite Dataset Sink | 写数据到Kite Dataset,试验性质的
Custom Sink | 自定义Sink实现



案例1、 A simple example
http://flume.apache.org/FlumeUserGuide.html#a-simple-example

配置文件
############################################################
# Name the components on this agent
a1.sources = r1
a1.sinks = k1
a1.channels = c1

# Describe/configure the source
a1.sources.r1.type = netcat
a1.sources.r1.bind = localhost // 改成主机名
a1.sources.r1.port = 44444

# Describe the sink
a1.sinks.k1.type = logger

# 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
############################################################

启动flume
flume-ng agent -n a1 -c conf -f simple.conf -Dflume.root.logger=INFO,console

安装telnet
yum install telnet
退出 ctrl+] quit

Memory Chanel 配置
capacity:默认该通道中最大的可以存储的event数量是100,
trasactionCapacity:每次最大可以source中拿到或者送到sink中的event数量也是100
keep-alive:event添加到通道中或者移出的允许时间
byte**:即event的字节量的限制,只包括eventbody



案例2、两个flume做集群

node01服务器中,配置文件
############################################################
# Name the components on this agent
a1.sources = r1
a1.sinks = k1
a1.channels = c1

# Describe/configure the source
a1.sources.r1.type = netcat
a1.sources.r1.bind = node1
a1.sources.r1.port = 44444

# Describe the sink
# a1.sinks.k1.type = logger
a1.sinks.k1.type = avro
a1.sinks.k1.hostname = node2
a1.sinks.k1.port = 60000

# 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
############################################################

node02服务器中,安装Flume(步骤略)
配置文件
############################################################
# Name the components on this agent
a1.sources = r1
a1.sinks = k1
a1.channels = c1

# Describe/configure the source
a1.sources.r1.type = avro
a1.sources.r1.bind = node2
a1.sources.r1.port = 60000

# Describe the sink
a1.sinks.k1.type = logger

# 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
############################################################

先启动node02的Flume
flume-ng agent -n a1 -c conf -f avro.conf -Dflume.root.logger=INFO,console

再启动node01的Flume
flume-ng agent -n a1 -c conf -f simple.conf2 -Dflume.root.logger=INFO,console

打开telnet 测试 node02控制台输出结果


案例3、Exec Source
http://flume.apache.org/FlumeUserGuide.html#exec-source

配置文件
############################################################
a1.sources = r1
a1.sinks = k1
a1.channels = c1

# Describe/configure the source
a1.sources.r1.type = exec
a1.sources.r1.command = tail -F /home/flume.exec.log

# Describe the sink
a1.sinks.k1.type = logger

# 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
############################################################

启动Flume
flume-ng agent -n a1 -c conf -f exec.conf -Dflume.root.logger=INFO,console

创建空文件演示 touch flume.exec.log
循环添加数据
for i in {1..50}; do echo "$i hi flume" >> flume.exec.log ; sleep 0.1; done

案例4、Spooling Directory Source
http://flume.apache.org/FlumeUserGuide.html#spooling-directory-source
配置文件
############################################################
a1.sources = r1
a1.sinks = k1
a1.channels = c1

# Describe/configure the source
a1.sources.r1.type = spooldir
a1.sources.r1.spoolDir = /home/logs
a1.sources.r1.fileHeader = true

# Describe the sink
a1.sinks.k1.type = logger

# 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
############################################################

启动Flume
flume-ng agent -n a1 -c conf -f spool.conf -Dflume.root.logger=INFO,console

拷贝文件演示
mkdir logs
cp flume.exec.log logs/


案例5、hdfs sink
http://flume.apache.org/FlumeUserGuide.html#hdfs-sink

配置文件
############################################################
a1.sources = r1
a1.sinks = k1
a1.channels = c1

# Describe/configure the source
a1.sources.r1.type = spooldir
a1.sources.r1.spoolDir = /home/logs
a1.sources.r1.fileHeader = true

# Describe the sink
***只修改上一个spool sink的配置代码块 a1.sinks.k1.type = logger
a1.sinks.k1.type=hdfs
a1.sinks.k1.hdfs.path=hdfs://bjsxt/flume/%Y-%m-%d/%H%M

##每隔60s或者文件大小超过10M的时候产生新文件
# hdfs有多少条消息时新建文件,0不基于消息个数
a1.sinks.k1.hdfs.rollCount=0
# hdfs创建多长时间新建文件,0不基于时间,时间单位 s
a1.sinks.k1.hdfs.rollInterval=60
# hdfs多大时新建文件,0不基于文件大小
a1.sinks.k1.hdfs.rollSize=10240
# 当目前被打开的临时文件在该参数指定的时间(秒)内,没有任何数据写入,则将该临时文件关闭并重命名成目标文件
a1.sinks.k1.hdfs.idleTimeout=3

a1.sinks.k1.hdfs.fileType=DataStream
a1.sinks.k1.hdfs.useLocalTimeStamp=true

## 每五分钟生成一个目录:
# 是否启用时间上的”舍弃”,这里的”舍弃”,类似于”四舍五入”,后面再介绍。如果启用,则会影响除了%t的其他所有时间表达式
a1.sinks.k1.hdfs.round=true
# 时间上进行“舍弃”的值;
a1.sinks.k1.hdfs.roundValue=5
# 时间上进行”舍弃”的单位,包含:second,minute,hour
a1.sinks.k1.hdfs.roundUnit=minute

# 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
############################################################
创建HDFS目录
hadoop fs -mkdir /flume

启动Flume
flume-ng agent -n a1 -c conf -f hdfs.conf -Dflume.root.logger=INFO,console

查看hdfs文件
hadoop fs -ls /flume/...
hadoop fs -get /flume/...

作业:
1、flume如何收集java请求数据
2、项目当中如何来做? 日志存放/log/目录下 以yyyyMMdd为子目录 分别存放每天的数据

项目模块设计*

Map(k:按照所需的维度划分 v:唯一标志(便于后面去重))

Reduce(将相同的 k 汇聚到起来,对 value 的值进行去重累加)

ETL -MR

抽取 extract

转换 transform

加载 load

Hive 与 Hbase 整合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
hive和hbase同步
https://cwiki.apache.org/confluence/display/Hive/HBaseIntegration

1、在hive的配置文件增加属性:
<property>
<name>hbase.zookeeper.quorum</name>
<value>node2,node3,node4</value>
</property>

2、在hive中创建临时表

CREATE EXTERNAL TABLE tmp_order
(key string, id string, user_id string)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES ("hbase.columns.mapping" = ":key,order:order_id,order:user_id") TBLPROPERTIES ("hbase.table.name" = "t_order");

-- 内部表
--
CREATE TABLE hbasetbl(key int, value string)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES ("hbase.columns.mapping" = ":key,cf1:val")
TBLPROPERTIES ("hbase.table.name" = "xyz", "hbase.mapred.output.outputtable" = "xyz");

Apache Sqoop

Sqoop 中导入和导出是相对于 HDFS的。

Sqoop 简介

Sqoop:将关系数据库(oracle、mysql、postgresql等)数据与 hadoop 数据进行转换的工具

官网:http://sqoop.apache.org/

版本:(两个版本完全不兼容,sqoop1使用最多)

sqoop1:1.4.x

sqoop2:1.99.x

同类产品

DataX:阿里顶级数据交换工具

Sqoop 架构

  • sqoop 架构非常简单,是 hadoop 生态系统的架构最简单的框架。
  • sqoop1 由 client 端直接接入 hadoop ,任务通过解析生成对应的 maprecue 执行

Sqoop 导入

将数据从 DataBase table 经由 MR 程序导入到 HDFS 中

Sqoop 导出

将数据从 HDFS 经由 MR 作业导出到DataBase table 中

Sqoop 安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
安装步骤:
1、解压
2、配置环境变量
export SQOOP_HOME=/XX/sqoop.xx
source /etc/profile
3、添加数据库驱动包
cp mysql-connector-java-5.1.10.jar /sqoop-install-path/lib
4、重命名配置文件
mv sqoop-env-template.sh sqoop-env.sh
5、修改配置configure-sqoop
去掉未安装服务相关内容;例如(HBase、HCatalog、Accumulo):
#if [ ! -d "${HBASE_HOME}" ]; then
# echo "Error: $HBASE_HOME does not exist!"
# echo 'Please set $HBASE_HOME to the root of your HBase installation.'
# exit 1
6、测试
sqoop version
sqoop list-databases -connect jdbc:mysql://node3:3306/ -username root -password 123

Sqoop 工具*

  • 连接
选项 含义说明
--connect<jdbc-uri> 指定JDBC 连接字符串
--connection-manager<class-name> 指定要使用的连接管理器类
--driver<class-name> 指定要使用的 JDBC 驱动类
--hadoop-mapred-home<dir> 指定 $HADOOP_MAPRED_HOME路径
--help 万能帮助
--password-file 设置用于存放认证的密码的信息文件的路径
-P 从控制台读取输入的密码
--password<password> 设置认证密码
--username<username> 设置认证用户名
--verbose 打印详细的运行信息
--connection-param-file<filename> 可选,指定存储数据库连接参数的属性文件
  • 导入工具

  • 导出工具

  • 将 MySQL 中数据导入到 HDFS 中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    # vi 编辑文件 option
    # 连接 MySQL 数据库,将表中数据导入 HDFS 中
    import
    --connect
    jdbc:mysql://node01:3306/result_db
    --username
    root
    --password
    123
    --as-textfile
    --columns
    browser_name,browser_version
    --table
    dimension_browser
    --delete-target-dir
    --target-dir
    /sqoop/data
    -m
    1

    # 执行
    sqoop --options-file option

    # vi 编辑文件 option2
    import
    --connect
    jdbc:mysql://node01:3306/result_db
    --username
    root
    --password
    123
    --as-textfile
    --delete-target-dir
    --target-dir
    /sqoop/data
    -m
    1
    -e
    select * from dimension_browser where $CONDITIONS

    # sqoop --options-file option2
  • 将 MySQL 数据导入到 Hive 中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import
    --connect
    jdbc:mysql://node01/result_db
    --username
    root
    --password
    123
    --as-textfile
    --query
    'select * from dimension_browser where $CONDITIONS'
    --delete-target-dir
    --target-dir
    /sqoop/tmp
    -m
    1
    --hive-home
    /user/hive/warehouse
    --hive-import
    --create-hive-table
    --hive-table
    t_browser
  • 将 Hive 中导出到 MySQL 表中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
        export
    --connect
    jdbc:mysql://node01/test
    --username
    root
    --password
    123
    -m
    1
    --columns
    id,browser_name,browser_version
    --export-dir
    /sqoop/data
    --table
    h_browser
注意:MySQL中 test.h_browser   需要提前创建

优化

调优的目的

1
2
充分的利用机器的性能,更快的完成 mr 程序的计算任务。
甚至是在有限的机器条件下,能够支持运行足够多的mr程序。

调优的总体概述

1
2
3
从mr程序的内部运行机制,我们可以了解到一个mr程序由mapper和reducer两个阶段组成,其中mapper阶段包括数据的读取、map处理以及写出操作(排序和合并/sort&merge),而reducer阶段包含mapper输出数据的获取、数据合并(sort&merge)、reduce处理以及写出操作。那么在这七个子阶段中,能够进行较大力度的进行调优的就是map输出、reducer数据合并以及reducer个数这三个方面的调优操作。
也就是说虽然性能调优包括cpu、内存、磁盘io以及网络这四个大方面,但是从mr程序的执行流程中,我们可以知道主要有调优的是内存、磁盘io以及网络。
在mr程序中调优,主要考虑的就是减少网络传输和减少磁盘IO操作,故本次课程的mr调优主要包括服务器调优、代码调优、mapper调优、reducer调优以及runner调优这五个方面。

服务器调优

1
服务器调优主要包括服务器参数调优和jvm调优。在本次项目中,由于我们使用hbase作为我们分析数据的原始数据存储表,所以对于hbase我们也需要进行一些调优操作。除了参数调优之外,和其他一般的java程序一样,还需要进行一些jvm调优。

推荐书籍: 《深入理解 Java 虚拟机 -周志明》

hdfs 调优

1. dfs.datanode.failed.volumes.tolerated: 允许发生磁盘错误的磁盘数量,默认为0,表示不允许datanode发生磁盘异常。当挂载多个磁盘的时候,可以修改该值。
2. dfs.replication: 复制因子,默认3
3. dfs.namenode.handler.count: namenode节点并发线程量,默认10
4. dfs.datanode.handler.count:datanode之间的并发线程量,默认10。
5. dfs.datanode.max.transfer.threads:datanode提供的数据流操作的并发线程量,默认4096。
    一般将其设置为linux系统的文件句柄数的85%~90%之间,查看文件句柄数语句ulimit -a,修改vim /etc/security/limits.conf, 不能设置太大
    文件末尾,添加
        * soft nofile 65535
        * hard nofile 65535
        注意:句柄数不能够太大,可以设置为1000000以下的所有数值,一般不设置为-1。
        异常处理:当设置句柄数较大的时候,重新登录可能出现unable load session的提示信息,这个时候采用单用户模式进行修改操作即可。
            单用户模式:
                启动的时候按'a'键,进入选择界面,然后按'e'键进入kernel修改界面,然后选择第二行'kernel...',按'e'键进行修改,在最后添加空格+single即可,按回车键回到修改界面,最后按'b'键进行单用户模式启动,当启动成功后,还原文件后保存,最后退出(exit)重启系统即可。
6. io.file.buffer.size: 读取/写出数据的buffer大小,默认4096,一般不用设置,推荐设置为4096的整数倍(物理页面的整数倍大小)。

hbase调优

1. 设置regionserver的内存大小,默认为1g,推荐设置为4g。
    修改conf/hbase-env.sh中的HBASE_HEAPSIZE=4g
2. hbase.regionserver.handler.count: 修改客户端并发线程数,默认为10。设置规则为,当put和scans操作比较的多的时候,将其设置为比较小的值;当get和delete操作比较多的时候,将其设置为比较大的值。原因是防止频繁GC操作导致内存异常。
3. 自定义hbase的分割和紧缩操作,默认情况下hbase的分割机制是当region大小达到hbase.hregion.max.filesize(10g)的时候进行自动分割,推荐每个regionserver的region个数在20~500个为最佳。hbase的紧缩机制是hbase的一个非常重要的管理机制,hbase的紧缩操作是非常消耗内存和cpu的,所以一般机器压力比较大的话,推荐将其关闭,改为手动控制。
4. hbase.balancer.period: 设置hbase的负载均衡时间,默认为300000(5分钟),在负载比较高的集群上,将其值可以适当的改大。
5. hfile.block.cache.size:修改hflie文件块在内存的占比,默认0.4。在读应用比较多的系统中,可以适当的增大该值,在写应用比较多的系统中,可以适当的减少该值,不过不推荐修改为0。
6. hbase.regionserver.global.memstore.upperLimit:修改memstore的内存占用比率上限,默认0.4,当达到该值的时候,会进行flush操作将内容写的磁盘中。
7. hbase.regionserver.global.memstore.lowerLimit: 修改memstore的内存占用比率下限,默认0.38,进行flush操作后,memstore占用的内存比率必须不大于该值。
8. hbase.hregion.memstore.flush.size: 当memstore的值大于该值的时候,进行flush操作。默认134217728(128M)。
9. hbase.hregion.memstore.block.multiplier: 修改memstore阻塞块大小比率值,默认为4。也就是说在memstore的大小超过4*hbase.hregion.memstore.flush.size的时候就会触发写阻塞操作。最终可能会导致出现oom异常。

mapreduce调优

1. mapreduce.task.io.sort.factor: mr程序进行合并排序的时候,打开的文件数量,默认为10个.
2. mapreduce.task.io.sort.mb: mr程序进行合并排序操作的时候或者mapper写数据的时候,内存大小,默认100M
3. mapreduce.map.sort.spill.percent: mr程序进行flush操作的阀值,默认0.80。
4. mapreduce.reduce.shuffle.parallelcopies:mr程序reducer copy数据的线程数,默认5。
5. mapreduce.reduce.shuffle.input.buffer.percent: reduce复制map数据的时候指定的内存堆大小百分比,默认为0.70,适当的增加该值可以减少map数据的磁盘溢出,能够提高系统性能。
6. mapreduce.reduce.shuffle.merge.percent:reduce进行shuffle的时候,用于启动合并输出和磁盘溢写的过程的阀值,默认为0.66。如果允许,适当增大其比例能够减少磁盘溢写次数,提高系统性能。同mapreduce.reduce.shuffle.input.buffer.percent一起使用。
7. mapreduce.task.timeout:mr程序的task执行情况汇报过期时间,默认600000(10分钟),设置为0表示不进行该值的判断。

代码调优

​ 代码调优,主要是mapper和reducer中,针对多次创建的对象,进行代码提出操作。这个和一般的java程序的代码调优一样。

  • mapper 调优
1
2
3
4
5
6
7
mapper调优主要就是就一个目标:减少输出量。我们可以通过增加combine阶段以及对输出进行压缩设置进行mapper调优。
combine介绍:
实现自定义combine要求继承reducer类,特点:
以map的输出key/value键值对作为输入输出键值对,作用是减少网络输出,在map节点上就合并一部分数据。
比较适合,map的输出是数值型的,方便进行统计。
压缩设置:
在提交job的时候分别设置启动压缩和指定压缩方式。
  • reducer 调优

    1
    2
    3
    4
    5
    6
    7
    reducer调优主要是通过参数调优和设置reducer的个数来完成。
    reducer个数调优:
    要求:一个reducer和多个reducer的执行结果一致,不能因为多个reducer导致执行结果异常。
    规则:一般要求在hadoop集群中的执行mr程序,map执行完成100%后,尽量早的看到reducer执行到33%,可以通过命令hadoop job -status job_id或者web页面来查看。
    原因: map的执行process数是通过inputformat返回recordread来定义的;而reducer是有三部分构成的,分别为读取mapper输出数据、合并所有输出数据以及reduce处理,其中第一步要依赖map的执行,所以在数据量比较大的情况下,一个reducer无法满足性能要求的情况下,我们可以通过调高reducer的个数来解决该问题。
    优点:充分利用集群的优势。
    缺点:有些mr程序没法利用多reducer的优点,比如获取top n的mr程序。
  • runner 调优

    runner 调优其实就是在提交job的时候设置 job 参数,一般都可以通过代码和xml文件两种方式进行设置。
      1~8详见ActiveUserRunner(before和configure方法),9详解TransformerBaseRunner(initScans方法)
    1. mapred.child.java.opts: 修改childyard进程执行的jvm参数,针对map和reducer均有效,默认:-Xmx200m
    2. mapreduce.map.java.opts: 需改map阶段的childyard进程执行jvm参数,默认为空,当为空的时候,使用mapred.child.java.opts。
    3. mapreduce.reduce.java.opts:修改reducer阶段的childyard进程执行jvm参数,默认为空,当为空的时候,使用mapred.child.java.opts。
    4. mapreduce.job.reduces: 修改reducer的个数,默认为1。可以通过job.setNumReduceTasks方法来进行更改。
    5. mapreduce.map.speculative:是否启动map阶段的推测执行,默认为true。其实一般情况设置为false比较好。可通过方法job.setMapSpeculativeExecution来设置。
    6. mapreduce.reduce.speculative:是否需要启动reduce阶段的推测执行,默认为true,其实一般情况设置为fase比较好。可通过方法job.setReduceSpeculativeExecution来设置。
    7. mapreduce.map.output.compress:设置是否启动map输出的压缩机制,默认为false。在需要减少网络传输的时候,可以设置为true。
    8. mapreduce.map.output.compress.codec:设置map输出压缩机制,默认为org.apache.hadoop.io.compress.DefaultCodec,推荐使用SnappyCodec(在之前版本中需要进行安装操作,现在版本不太清楚,安装参数:http://www.cnblogs.com/chengxin1982/p/3862309.html)
    9. hbase参数设置
由于hbase默认是一条一条数据拿取的,在mapper节点上执行的时候是每处理一条数据后就从hbase中获取下一条数据,通过设置cache值可以一次获取多条数据,减少网络数据传输。

  • 我只想拉住流年
  • 好好地说声再见
  • 遗憾感谢 都回不去昨天
  • 我只想铭记这瞬间
  • 我们一去走过的流年

是什么

为什么

如何去使用

Hadoop

Hadoop简介:http://hadoop.apache.org

The Apache™ Hadoop® project develops open-source software for reliable, scalable, distributed computing.The Apache Hadoop software library is a framework that allows for the distributed processing of large data sets across clusters of computers using simple programming models. It is designed to scale up from single servers to thousands of machines, each offering local computation and storage. Rather than rely on hardware to deliver high-availability, the library itself is designed to detect and handle failures at the application layer, so delivering a highly-available service on top of a cluster of computers, each of which may be prone to failures.

模块 特点
分布式存储系统HDFS (Hadoop Distributed File System )POSIX 提供了 高可靠性、高扩展性和高吞吐率的数据存储服务
分布式计算框架MapReduce分布式计算框架(计算向数据移动) 具有 易于编程、高容错性和高扩展性等优点。
分布式资源管理框架YARN(Yet Another Resource Management) 负责集群资源的管理和调度

HDFS

存储模型:字节*

文件线性切割成块(Block):偏移量 offset (byte)

  • Block 分散存储在集群节点中单一文件Block大小一致,文件与文件可以不一致
  • Block可以设置副本数,副本分散在不同节点中副本数不要超过节点数量
  • 文件上传可以设置Block大小和副本数
  • 已上传的文件Block副本数可以调整
  • 大小不变只支持一次写入多次读取,同一时刻只有一个写入者可以 append 追加数据

在一个文件中 Block 的大小保持一致,在不同文件中 Block 的大小可以不一致。

注意:同一个机器不能存储两个相同的副本,副本应该存在不同的机器上的。

架构模型

文件元数据 MetaData,文件数据
元数据:数据本身
NameNode(主)节点保存文件元数据:单节点 posix
DataNode(从)节点保存文件Block数据:多节点
DataNode与 NameNode 保持心跳,提交Block列表
HdfsClient 与 NameNode 交互元数据信息
HdfsClient 与 DataNode 交互文件Block数据

NameNode(NN)

定义 主要功能
基于内存存储 :不会和磁盘发生交换
- 只存在内存中
- 持久化(fsimage、edits)
接受客户端的读写服务
收集 DataNode 汇报的 Block 列表信息
NameNode 保存 metadata 信息包括
文件 owership 和 permissions
文件大小,时间
(Block列表:Block偏移量),位置信息
Block每副本位置(由DataNode上报)

注意: NameNode 记录快照时是不包括 DataNode 的位置信息的。

NameNode 持久化

  • NameNode 的 metadate 信息在启动后会加载到内存
  • metadata 存储到磁盘文件名为”fsimage”
  • Block 的位置信息不会保存到 fsimage
  • edits记录对 metadata 的操作日志

若 NameNode 发生故障,首先会从磁盘 fsimage 文件中恢复到上一次记录快照的状态,然后在根据 edits 记录执行从上一次记录快照的时刻到 NameNode 宕机前的操作。

DataNode(DN)

本地磁盘目录存储数据(Block),文件形式

同时存储Block的元数据信息文件

启动 DN 时会向 NN 汇报 block 信息

通过向 NN 发送心跳保持与其联系(3秒一次),

如果 NN 10分钟没有收到 DN 的心跳,则认为其已经 lost,并 copy 其上的 block 到其它 DN

注意:若一台 DN 宕机了,NN 10 分钟才会认为该 DN 已经丢失。这时 NN 会根据之前该 DN 上传的 block 信息,去其他 DN 上寻找这些 block 的副本。

HDFS 优缺点

优点 缺点
1. 高容错性
数据自动保存多个副本 副本丢失后,自动恢复
2. 适合批处理
移动计算而非数据 数据位置暴露给计算框架(Block偏移量)
3. 适合大数据处理
GB 、TB 、甚至PB 级数据 百万规模以上的文件数量 10K+ 节点
4. 可构建在廉价机器上
通过多副本提高可靠性 提供了容错和恢复 机制
低延迟数据访问
比如毫秒级
低延迟与高吞吐率
小文件存取
占用NameNode 大量内存
寻道时间超过读取时间
并发写入、文件随机修改
一个文件只能有一个写者
仅支持append

SecondaryNameNode(SNN)

它不是NN的备份(但可以做备份),它的主要工作是帮助NN合并edits log,减少NN启动时间。
SNN执行合并时机
根据配置文件设置的时间间隔 fs.checkpoint.period 默认3600秒
根据配置文件设置edits log大小 fs.checkpoint.size 规定edits文件的最大值默认是64MB

SNN合并流程

  1. 在 PN 合并之前,会将 edits 和 fsimage 文件发送给 SN,然后 PN 创建一个新的 edits.new 文件继续记录 PN 的操作。
  2. PN 将之前的 edits 和 fsimage 发送给 SN 后,SN 会将 fsimage 加载到内存,edits 也加载到内存
  3. 根据 edits 中操作记录执行相应的指令,当 edits 的所有操作记录对应的指令执行完毕,会生成一个新的 fsimage.ckpt 快照。
  4. 将新生成的 fsimage.ckpt 再发送给 PN ,这时 PN 就拥有 edits.new 创建之前的快照记录
  5. 若 PN 发生了宕机,可以根据 fsimage 和 edits.new 恢复到宕机前的状态

副本放置策略

第一个副本:放置在上传文件的 DN;如果是集群外提交,则随机挑选一台磁盘不太满,CPU不太忙的节点。

第二个副本:放置在于第一个副本不同的 机架的节点上。

第三个副本:与第二个副本相同机架的节点。

更多副本:随机节点

注意:第一个副本与第二个副本不在同一台机架上

服务器的种类: 1.塔式(就像家里的台式机主机箱那样)2. 机箱(像 DVD机那样)3. 刀片(像厨房放刀的刀架)

HDFS 写流程*

Client:

  • 切分文件 Block

  • 按 Block 线性和 NN 获取 DN 列表(副本数)

  • 验证DN列表后以更小的单位流式传输数据

  • 各节点,两两通信确定可用

  • Block传输结束后:

    • DN 向 NN汇报Block信息

    • DN 向 Client汇报完成

    • Clien t向 NN 汇报完成

  • 获取下一个 Block 存放的 DN 列表
    循环往复之前的操作 …

  • 最终Client汇报完成

  • NN会在写流程更新文件状态

注意:

  1. client 向 DataNode 写数据,分为许多小数据包,一次次的传递,直到所有数据包都发送完毕

HDFS 读流程*

Client:

  • 和 NN 获取一部分 Block 副本位置列表
  • 线性和 DN 获取 Block,最终合并为一个文件
  • 在Block副本列表中按距离择优选取

  1. HDFS 发送请求到 DistributedFileSystem,DistributedFileSystem 会向 NameNode 索要指定文件所有块信息,并返回所有块副本信息(按副本顺序按距离排序)
  2. 通过 HDFS,可以读取文件任意块的位置

记: 分布式文件系统很好的支持计算层的本地化读取

HDFS文件权限 POSIX

与Linux文件权限类似

r: read; w:write; x:execute

权限x对于文件忽略,对于文件夹表示是否允许访问其内容

如果Linux系统用户zhangsan使用hadoop命令创建一个文件,那么这个文件在HDFS中owner就是zhangsan。

HDFS的权限目的:阻止好人错错事,而不是阻止坏人做坏事。HDFS相信,你告诉我你是谁,我就认为你是谁。

安全模式

namenode启动的时候,首先将映像文件(fsimage)载入内存,并执行编辑日志(edits)中的各项操作。

一旦在内存中成功建立文件系统元数据的映射,则创建一个新的fsimage文件(这个操作不需要

SecondaryNameNode)和一个空的编辑日志。

此刻namenode运行在安全模式。即namenode的文件系统对于客户端来说是只读的。(显示目录,显示文件内容

等。写、删除、重命名都会失败)。
在此阶段Namenode收集各个datanode的报告,当数据块达到最小副本数以上时,会被认为是“安全”的, 在一定

比例(可设置)的数据块被确定为“安全”后,再过若干时间,安全模式结束

当检测到副本数不足的数据块时,该块会被复制直到达到最小副本数,系统中数据块的位置并不是由namenode

维护的,而是以块列表形式存储在datanode中。

集群

角色(进程) 功能
NameNode 数据元数据
内存存储,不会有磁盘交换
持久化(fsimage,eidts log)
不会持久化block的位置信息
block 偏移量,因为block不可以调整大小,hdfs,不支持修改文件
偏移量不会改变
datanode
block块
磁盘 面向文件,大小一样,不能调整
副本数,调整,(备份,高可用,容错/可以调整很多个,为了计算向数据移动)
SN
NN & DN 心跳机制
DN 向 NN 汇报block信息
安全模式
client

搭建 Hadoop 伪分布式

准备

时间同步

1
2
3
4
5
type ntpdate
# 安装 ntpdate
yum install -y ntpdate
# 同步网络时
ntpdate -u ntp.api.bz

操作系统环境设置

  • 确认主机名一致

  • ssh 免密钥
1
2
3
4
5
# 在另外一台机器上远程执行 192.168.170.101 机器上的命令(验证)
ssh root@192.168.170.101 'ls ~'

$ ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa # 生成密钥
$ cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys # 对自己免密钥
  • 安装 Java 并配置环境变量
1
2
3
4
rpm -ivh jdk-7u67-linux-x64.rpm
# 修改 /etc/profile
export JAVA_HOME=/usr/java/jdk1.7.0_67
export PATH=$PATH:$JAVA_HOME

Hadoop 配置

  • 安装 Hadoop 并配置环境变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
tar xf hadoop-2.6.5.tar.gz 
mkdir /opt/sxt
mv hadoop-2.6.5 /opt/sxt/

# 配置 hadoop 环境变量
export HADOOP_HOME=/opt/sxt/hadoop-2.6.5
export PATH=$PATH:$JAVA_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin

# cd /opt/sxt/hadoop-2.6.5/
# 进入 etc 目录中
# 修改 Java 环境路径,export JAVA_HOME=/usr/java/jdk1.7.0_67
# 目的是 hadoop 管理脚本操作机器不能读取 /etc/profile 文件,所以需要改 Java 的绝对路径
vi hadoop-env.sh
vi mapred-env.sh
vi yarn-env.sh
  • 修改 slaves
1
2
将 localhsot 改为 node01
node01
  • 修改 core-site.xml
1
2
3
4
5
6
7
8
9
10
<configuration>
<property>
<name>fs.defaultFS</name>
<value>hdfs://node01:9000</value>
</property>
<property>
<name>hadoop.tmp.dir</name>
<value>/var/sxt/hadoop/local</value>
</property>
</configuration>
  • 修改 hdfs-site.xml
1
2
3
4
5
6
7
8
9
10
<configuration>
<property>
<name>dfs.replication</name>
<value>1</value>
</property>
<property>
<name>dfs.namenode.secondary.http-address</name>
<value>node01:50090</value>
</property>
</configuration>
配置文件 作用
etc/hadoop/core-site.xml 决定 NameNode 的启动
etc/hadoop/slaves 决定 DataNode 的启动
etc/hadoop/hdfs-site.xml 决定 SecondaryNode 的启动

启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 格式化
$hdfs namenode -format

$ls /var/sxt/hodoop/local/dfs/name/current/
fsimage_0000000000000000000 fsimage_0000000000000000000.md5 seen_txid VERSION

# 启动 NN、DN、SN
$start-dfs.sh
# jps 查看 Java 进程,是否有 NN、DN、SN
$jps
3385 DataNode
3543 SecondaryNameNode
3674 Jps
3306 NameNode

#访问 http://192.168.170.101:50070

使用

1
2
3
4
5
6
hdfs dfs -mkdir /user
hdfs dfs -ls /user
hdfs dfs -mkdir /user/root
hdfs dfs -D dfs.blocksize=1048576 -put hadoop-2.6.5.tar.gz
# 展示当前目录,并显示文件大小
ll -h

搭建 Hadoop 全分布式

准备

NN SNN DN
node01 *
node02 * *
node03 *
node04 *

搭建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 关闭 Hadoop 伪分布式
stop-dfs.sh

# 配置节点 node02、node03、node04 的 java 环境 和 ssh
cat ~/node01.pub >> ~/.ssh/authorized_keys
cat ~/.ssh/id_dsa.pub >> ~/.ssh/authorized_keys

# 以下操作针对 node01 ---------------------------------------------
# 修改 Hadoop 配置文件
# 编辑 etc/core-site.xml 文件
<configuration>
<property>
<name>fs.defaultFS</name>
<value>hdfs://node01:9000</value>
</property>
<property>
<name>hadoop.tmp.dir</name>
<value>/var/sxt/hadoop/full</value>
</property>
</configuration>
# 编辑 etc/hdfs-site.xml 文件
<configuration>
<property>
<name>dfs.replication</name>
<value>2</value>
</property>
<property>
<name>dfs.namenode.secondary.http-address</name>
<value>node02:50090</value>
</property>
</configuration>
# 编辑 etc/slaves 文件
node02
node03
node04
# ------------------------------------------------------------------
# 在 node02、node03、node04 下创建文件夹
mkdir /opt/sxt
# 在 node01 节点的 /opt/sxt 目录下执行
scp -r ./hadoop-2.6.5/ node02:`pwd`
scp -r ./hadoop-2.6.5/ node03:`pwd`
scp -r ./hadoop-2.6.5/ node04:`pwd`

# 在 node01 节点上执行 ---------------------------
hdfs namenode -format
start-dfd.sh

在 node01 节点上执行 格式化

启动 hdfsnamenode -format 截图

在 node01 节点启动 hadoop

Hadoop 2.x

  • 主备 NameNode
    解决单点故障(属性,位置)
    主NameNode对外提供服务,备NameNode同步主NameNode元数据,以待切换
    所有DataNode同时向两个NameNode汇报数据块信息(位置)
  • JNN:集群(属性)
  • standby:备,完成了 edits.log 文件的合并产生新的image,推送回 ANN
    两种切换选择
    手动切换:通过命令实现主备之间的切换,可以用HDFS升级等场合
    自动切换:基于Zookeeper实现
    基于Zookeeper自动切换方案
  • ZooKeeper Failover Controller:监控NameNode健康状态,并向Zookeeper注册NameNode
    NameNode挂掉后,ZKFC为NameNode竞争锁,获得ZKFC 锁的NameNode变为active

Hadoop 2.x 联邦

联邦:不同应用间是相互隔离

  1. 通过多个namenode/namespace把元数据的存储和管理分散到多个节点中,使到namenode/namespace可以通过增加机器来进行水平扩展。
  2. 能把单个 namenode 的负载分散到多个节点中,在 HDFS 数据规模较大的时候不会也降低 HDFS 的性能。可以通过多个 namespace 来隔离不同类型的应用,把不同类型应用的HDFS元数据的存储和管理分派到不同的namenode中。

HA 高可用

准备

NN-1 NN-2 DN ZK ZKFC JNN
node01 * * *
node02 * * * * *
node03 * * *
node04 * *

搭建 Zookeeper 集群

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 解压 Zookeeper 压缩包,并移动到 /opt/sxt 目录下
# 配置 Zookeeper 环境变量
# 到 zookeeper 目录下,进入 conf 目录下
# 复制 zoo_simple.cfg 到 zoo.cfg
# 修改 zoo.cfg 配置文件
dataDir=/var/sxt/hadoop/zk
clientPort=2181
# 配置 zk 集群,第一次启动按 serverid 区分 leader 和 fellower
server.1=192.168.170.102:2888:3888
server.2=192.168.170.103:2888:3888
server.3=192.168.170.104:2888:3888

#创建 /var/sxt/hadoop/zk 目录,将当前机器 zk 的 id 写入 myid 文件中
mkdir /var/sxt/hadoop/zk -p
echo "1">/var/sxt/hadoop/zk/myid

# 分发 zookeeper 目录到其他机器上(node03、node04)
scp -r ./zookeeper-3.4.6/ root@node03:`pwd`
scp -r ./zookeeper-3.4.6/ root@node04:`pwd`

# 在 node03、node04 机器上
# 1. 配置 zookeeper 的环境变量
# 2. 增加 myid 文件,将当前机器 zk 的 serverid 写入

HA 搭建

  1. 逻辑到物理的映射
  2. JNode 位置信息相关配置
  3. 出现故障的处理方式及 SSH 免密钥配置

hdfs-site.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<property>
<name>dfs.nameservices</name>
<value>mycluster</value>
</property>
<property>
<name>dfs.ha.namenodes.mycluster</name>
<value>nn1,nn2</value>
</property>
<property>
<name>dfs.namenode.rpc-address.mycluster.nn1</name>
<value>node01:8020</value>
</property>
<property>
<name>dfs.namenode.rpc-address.mycluster.nn2</name>
<value>node02:8020</value>
</property>
<property>
<name>dfs.namenode.http-address.mycluster.nn1</name>
<value>node01:50070</value>
</property>
<property>
<name>dfs.namenode.http-address.mycluster.nn2</name>
<value>node02:50070</value>
</property>
<property>
<name>dfs.namenode.shared.edits.dir</name>
<value>qjournal://node01:8485;node02:8485;node03:8485/mycluster</value>
</property>

<property>
<name>dfs.journalnode.edits.dir</name>
<value>/var/sxt/hadoop/ha/jn</value>
</property>

<property>
<name>dfs.client.failover.proxy.provider.mycluster</name>
<value>
org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider
</value>
</property>
<property>
<name>dfs.ha.fencing.methods</name>
<value>sshfence</value>
</property>
<property>
<name>dfs.ha.fencing.ssh.private-key-files</name>
<value>/root/.ssh/id_dsa</value>
</property>

<property>
<name>dfs.ha.automatic-failover.enabled</name>
<value>true</value>
</property>

core-site.xml

注意:hadoop.tmp.dir的配置要变更:/var/sxt/hadoop-2.6/ha ###

1
2
3
4
5
6
7
8
<property>
<name>fs.defaultFS</name>
<value>hdfs://mycluster</value>
</property>
<property>
<name>ha.zookeeper.quorum</name>
<value>node02:2181,node03:2181,node04:2181</value>
</property>

HA 部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 第一次部署
# 1.启动 node01、node02、node03 的 journalnode
hadoop-daemon.sh start journalnode
# 2.格式化 node01 dfs
hdfs namenode –format
# 3.启动 node01 的 NN ,启动 node02 的 standyNN
hadoop-daemon.sh start namenode # 在 node01
hdfs namenode -bootstrapStandby # 在 node02
# 4.格式化 zk
hdfs zkfc -formatZK
# 5.启动 hdfs
start-dfs.sh
#测试
kill -9 进程号 强制清除

# 第二次启动
1,启动zk
2,start-dfs.sh

SSH 免密钥的应用场景

1
2
3
4
5
1. 管理脚本管理服务的开启与关闭
2. 搭建 HA 时,ZKFC 需要控制自己及对方

ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa # 生成密钥
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys # 对自己免密钥

Windows 下开发大数据

添加 Hadoop 环境变量和指定用户

  • 新建系统环境变量

  • 修改系统环境变量 path

Eclipse 中自定义用户库

  1. 打开 Eclipse 的首选项:点击菜单栏下 window > Perferences

  2. 搜索 user ,即可找到 User Libraries 的位置并点击

  3. 点击 New 按钮,新建一个用户库,自定义库的名称

  4. 选择刚创建自定义用户库,点击 Add Exteral JARs …点击,选择需要添加 jar 包,最后确定即可

HDFS-api

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public class  TestHDFS{

Configuration conf = null;
FileSystem fs = null;

@Before
public void before() throws Exception{
conf = new Configuration(true);
fs = FileSystem.get(conf);
}

@After
public void after() throws Exception{
fs.close(); //关闭资源
}

// 创建目录
@Test
public void mkdir() throws Exception{
// 给定一个目录,若该目录存在,则直接删除再创建
// 若该目录不存在,直接创建
Path f = new Path("/hello");
if(fs.exists(f)){
fs.delete(f, true);
}
fs.mkdirs(f);
}

// 上传文件
@Test
public void upload() throws Exception{

InputStream is =
new BufferedInputStream(
new FileInputStream(new File("D:\\hello.txt")));

Path p = new Path("/user/root/test-h.txt");
FSDataOutputStream out = fs.create(p);

IOUtils.copyBytes(is, out, conf, true);

}

// 下载文件
@Test
public void download() throws Exception{
Path path = new Path("/user/root/test.txt");
InputStream in = fs.open(path);
// 输入流
FSDataInputStream fsin = new FSDataInputStream(in);
// 输出流
OutputStream ops =
new BufferedOutputStream(
new FileOutputStream(new File("D:\\hadoop.txt")));
// 流对接
IOUtils.copyBytes(fsin, ops, conf);

}


// 块
@Test
public void testBlockLocation() throws Exception{
Path path = new Path("/user/root/test.txt");
FileStatus fStatus = fs.getFileStatus(path);
BlockLocation[] bkls =
fs.getFileBlockLocations(fStatus , 0, fStatus.getLen());
for (BlockLocation bkl : bkls) {
System.out.println(bkl);
}

FSDataInputStream in = fs.open(path);

in.seek(1048576);
System.out.println((char)in.readByte());
System.out.println((char)in.readByte());
}

}

大数据

高并发 -> 日志* -> 分析行为 ->画像 ->推荐 -> 服务*

TCP/IP 网络模型

三次握手:确认客户端和服务端的收发正常(IO 正常),可以进行连接

  • C -> S : 发送 sync 包给服务端,表示客户端想与服务端建立连接

  • S -> C:服务端接收到客户端的 sync 包,并将 sync +ack 包,发送给客户端

    表示服务端接收到客户端的请求,并回复客户端可以与服务端建立连接

  • C -> S: 客户端接收到服务端的 ack 包,将 ack 包发送给服务端,表示客户端马上与服务端建立连接,让服务端做好准备

四次离手:TCP 是稳定的连接,对于连接断开需要经过 Client 与 Server 双方都确认后,才可关闭

  • C - >S: 发送 fin 包给服务端,表示客户端想与服务端断开连接
  • S -> C: 发送 ack包给客户端,表示接收到服务端的断开请求
  • S -> C: 发送 fin 包给客户端,表示服务端想与客户端断开连接
  • C -> S: 发送 ack 包给服务端,服务端接收到后,表示与客户端断开连接。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
TCP/IP协议  OSI 7L参考模型
GET / www.baidu.com/
7:应用层www.baidu.com IP:80 1212
http,smtp,ssh
4:传输层控制:【三次握手>>(传输数据)>>四次分手】
tcp,udp
SOCKET:IP:PORT-IP:PORT
netstat -natp
3:网络层: 192.168.9.11
ip,icmp
ROUTE:下一跳
route -n
2:链路层
以太网:Ethernet:MAC
ARP:全F,两点通信,交换机学习
arp -a

注意: 三次握手和四次离手中间不可拆分,一定针对于两台特定 IP:Port - IP:Port

1
2
3
4
5
6
netstat antp

route -n # -n 查看 IP 地址

arp -a
0.0.0.0 默认网关

功能分层

层与层依赖
1,能够申请到端口号
2,路由表有下一跳条目
3,ARP能请求到下一跳MAC
4,三次握手
5,传输数据
6,四次分手

下一跳

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
整个互联网建立在下一跳的模式下
IP是逻辑上的两个端点
MAC是物理上连接的两个节点
端点间TCP传输过程中
确认机制
状态机制
不可分割
解析数据包需要成本
交换机:二层,只关心MAC地址
学习机制:
路由器:三层,只关心IP和路由表
LVS服务器:四层,只关心PORT,状态
nginx:七层,关心socket对应关系

## 负载均衡 ##

LVS

LVS技术要达到的目标是:通过LVS提供的负载均衡技术和Linux操作系统实现一个高性能、高可用的服务器群集,它具有良好可靠性、可扩展性和可操作性。从而以低廉的成本实现最优的服务性能。

LVS自从1998年开始,发展到现在已经是一个比较成熟的技术项目了。可以利用LVS技术实现高可伸缩的、高可用的网络服务,例如WWW服务、Cache服务、DNS服务、FTP服务、MAIL服务、视频/音频点播服务等等,有许多比较著名网站和组织都在使用LVS架设的集群系统,例如:Linux的门户网站(www.linux.com)、向RealPlayer提供音频视频服务而闻名的Real公司(www.real.com)、全球最大的开源网站(sourceforge.net)等

LVS 参考 https://superproxy.github.io/docs/lvs/index.html

硬件

1
2
3
4
昂贵,性能优越
F5 BIG-IP
Citrix NetScaler
A10

软件

便宜,灵活度(开源)

四层 七层
tcp 之上的第四层协议 LVS 只能操作IP,端口 ,在操作系统内核中。
nginx
haproxy
httpd “apache”webserver

LVS -DR

Director 接收用户的请求,然后根据负载均衡算法选取一台 realserver,将包转发过去,最后由realserver直接回复给用户。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
DR:Director
客户端发送对VIP的请求
lvs负载到后端某一台server
后端server处理后,直接封包回送客户端
源IP地址一定是lvs上面陪的那个公网服务地址
也就后端server要配置这个ip
后端server收到的数据包是lvs没有变动过的(IP:vip)
目标ip一定是自己持有的
so:多个server,接入互联网的server持有相同的IP,是不对的
必须将后端server中的vip隐藏起来(对外隐藏)

VIP: 虚拟服务器地址
DIP: 转发的网络地址
1,和RIP通信:ARP协议,获取Real Server的RIP:MAC地址
2,转发Client的数据包到RIP上(隐藏的VIP)
RIP: 后端真实主机(后端服务器)

CIP: 客户端IP地址

LVS 配置实验

环境

主机名 node01 node02 node03
IP 类型 DIP RIP RIP
IP 192.168.170.101 192.168.170.102 192.168.170.103

实验图

步骤

  1. 配置 LVS 服务器的虚拟IP 和开启 IPv4 转发

    1
    2
    [root@node01 ~]# ifconfig eth0:0 192.168.170.100/24  # 临时生效
    echo “1” > /proc/sys/net/ipv4/ip_forward
  2. 修改 RP 服务器的文件

    1
    2
    3
    4
    echo 1 > /proc/sys/net/ipv4/conf/eth0/arp_ignore 
    echo 2 > /proc/sys/net/ipv4/conf/eth0/arp_announce
    echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
    echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce
  3. 配置 RP 服务器的本地环回地址

    1
    ifconfig lo:0 192.168.170.100 netmask 255.255.255.255
  4. 在 RP 服务器上安装 httpd 服务

    1
    yum install -y httpd
  5. 设置 主页

    1
    2
    3
    cd /var/www/html
    vi index.html
    内容: from 192.168.170.102
  6. 启动 httpd 服务

    1
    service httpd start
  7. LVS 配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
     yum install ipvsadm -y
    ipvsadm -A -t 192.168.170.100:80 -s rr
    ipvsadm -a -t 192.168.170.100:80 -r 192.168.170.102 -g
    ipvsadm -a -t 192.168.170.100:80 -r 192.168.170.103 -g

    ipvsadm -ln
    浏览器刷新: 访问vip
    ipvsadm –lnc
    netstat -natp

keepalived

集群管理中保证集群高可用的服务软件

用于做健康检查和主备切换,这里用在 LVS 服务器,使用 LVS 服务器达到高可用 HA(High Availability)

高可用 High Available

1
2
3
4
1、需要心跳机制探测后端RS是否提供服务。
a) 探测down,需要从lvs中删除该RS
b) 探测发送从down到up,需要从lvs中再次添加RS。
2、Lvs DR,需要主备(HA)

实验

环境

主机名 node01 node02 node03 node04
类型 LVS(MASTER) RIP1 RIP2 LVS(BACKUP)
IP 192.168.170.101 192.168.170.102 192.168.170.103 192.168.170.104
实验图

步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#  node01 上LVS 服务器上关闭之前的 ipvsadm 设置
ipvsadm -C # Clear the virtual server table.
ifconfig eth0:0 down

# 在 node01、node04上安装 keepalived
yum install -y keepalived

# 安装完成后, 进入 cd /etc/keepalived 目录下,备份 keepalived.conf 文件
! Configuration File for keepalived

global_defs {
notification_email {
acassen@firewall.loc
failover@firewall.loc
sysadmin@firewall.loc
}
notification_email_from Alexandre.Cassen@firewall.loc
smtp_server 192.168.200.1
smtp_connect_timeout 30
router_id LVS_DEVEL
}

vrrp_instance VI_1 {
state MASTER # 主节点
interface eth0 # 操作的网卡
virtual_router_id 51
priority 100 # 优先级
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress { # VIP 虚拟IP配置
192.168.170.100/24 dev eth0 label eth0:3
}
}

virtual_server 192.168.170.100 80 {
delay_loop 6
lb_algo rr
lb_kind DR # 指定 LVS DR模式
nat_mask 255.255.255.0
persistence_timeout 0 # 同一个IP地址在50秒内lvs转发给同一个后端服务器
protocol TCP

real_server 192.168.170.102 80 { #设置真实服务器的心跳机制 RID PORT
weight 1
HTTP_GET { #心跳检测的方式
url {
path / # 心跳检查的地址
status_code 200 #心跳检查返回的状态
}
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
real_server 192.168.170.103 80 {
weight 1
HTTP_GET {
url {
path /
status_code 200
}
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
}

# 配置完成 keepalived.conf ,将文件拷贝到 node04 上,并修改
scp ./keepalived.conf root@192.168.170.104:/`pwd`

# keepalived.conf 修改 node04 部分内容
vrrp_instance VI_1 {
state BACKUP # 从节点
interface eth0 # 操作的网卡
virtual_router_id 51
priority 50 # 优先级,从节点的优先级应低于主节点的优先级
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress { # VIP 虚拟IP配置
192.168.170.100/24 dev eth0 label eth0:3
}
}

# -------------------------------------------------------------------
# 在 node01、node04 上启动 keepalived 服务
service keepalived start

# 在 ifconfig 命令查看
ifocnifg

# 查看路由
ipvsadm -ln

Nginx

Nginx (“engine x”) 是一个高性能的 HTTP 和 反向代理 服务器,也是一个 IMAP/POP3/SMTP 代理服务器。

Apache 与 Nginx 的比较

Apache Nginx
一个客户端连接开辟一个进程处理 仅有一个进程
同步阻塞 异步非阻塞

nginx.conf

events

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#工作模式与连接数上限
events
{
#参考事件模型,use [ kqueue | rtsig | epoll | /dev/poll | select | poll ];
epoll模型是Linux 2.6以上版本内核中的高性能网络I/O模型,如果跑在FreeBSD上面,就用kqueue模型。
use epoll;
#单个进程最大连接数(最大连接数=连接数*进程数)
worker_connections 65535;
}

event下的一些配置及其意义
#单个后台worker process进程的最大并发链接数
worker_connections 1024;
# 并发总数是 worker_processes 和 worker_connections 的乘积
# 即 max_clients = worker_processes * worker_connections
# 在设置了反向代理的情况下,max_clients = worker_processes * worker_connections / 4 为什么
# 为什么上面反向代理要除以4,应该说是一个经验值
# 根据以上条件,正常情况下的Nginx Server可以应付的最大连接数为:4 * 8000 = 32000
# worker_connections 值的设置跟物理内存大小有关
# 因为并发受IO约束,max_clients的值须小于系统可以打开的最大文件数

event下的一些配置及其意义
# 而系统可以打开的最大文件数和内存大小成正比,一般1GB内存的机器上可以打开的文件数大约是10万左右
# 我们来看看360M内存的VPS可以打开的文件句柄数是多少:
# $ cat /proc/sys/fs/file-max
# 输出 34336
# 32000 < 34336,即并发连接总数小于系统可以打开的文件句柄总数,这样就在操作系统可以承受的范围之内
# 所以,worker_connections 的值需根据 worker_processes 进程数目和系统可以打开的最大文件总数进行适当地进行设置
# 使得并发总数小于操作系统可以打开的最大文件数目
# 其实质也就是根据主机的物理CPU和内存进行配置
# 当然,理论上的并发总数可能会和实际有所偏差,因为主机还有其他的工作进程需要消耗系统资源。
# ulimit -SHn 65535

#定义Nginx运行的用户和用户组
user www www;

#nginx进程数,建议设置为等于CPU总核心数。
worker_processes 8;

#全局错误日志定义类型,[ debug | info | notice | warn | error | crit ]
error_log /var/log/nginx/error.log info;

#进程文件
pid /var/run/nginx.pid;

#一个nginx进程打开的最多文件描述符数目,理论值应该是最多打开文件数(系统的值ulimit -n)与nginx进程数相除,但是nginx分配请求并不均匀,所以建议与ulimit -n的值保持一致。
worker_rlimit_nofile 65535;

httpd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#设定http服务器
http
{
include mime.types; #文件扩展名与文件类型映射表
default_type application/octet-stream; #默认文件类型
#charset utf-8; #默认编码
server_names_hash_bucket_size 128; #服务器名字的hash表大小
client_header_buffer_size 32k; #上传文件大小限制
large_client_header_buffers 4 64k; #设定请求缓
client_max_body_size 8m; #设定请求缓
sendfile on; #开启高效文件传输模式,sendfile指令指定nginx是否调用sendfile函数来输出文件,对于普通应用设为 on,如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络I/O处理速度,降低系统的负载。注意:如果图片显示不正常把这个改成off。
autoindex on; #开启目录列表访问,合适下载服务器,默认关闭。
tcp_nopush on; #防止网络阻塞
tcp_nodelay on; #防止网络阻塞
keepalive_timeout 120; #长连接超时时间,单位是秒

gzip

1
2
3
4
5
6
7
8
9
10
11
12
13
gzip的一些配置及其意义
#gzip模块设置gzip on;
#开启gzip压缩输出 gzip_min_length 1k;
#最小压缩文件大小 gzip_buffers 4 16k;
#压缩缓冲区 gzip_http_version 1.0;
#压缩版本(默认1.1,前端如果是squid2.5请使用1.0)
gzip_comp_level 2;
#压缩等级
gzip_types text/plain application/x-javascript text/css application/xml;
#压缩类型,默认就已经包含text/html,所以下面就不用再写了,写上去也不会有问题,
但是会有一个warn。
gzip_vary on;
#limit_zone crawler $binary_remote_addr 10m; #开启限制IP连接数的时候需要使用

虚拟主机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
虚拟主机一些配置及其意义
#虚拟主机的配置
server{
#监听端口 listen 80;
#域名可以有多个,用空格隔开
server_name www.ha97.com ha97.com;
index index.html index.htm index.jsp;
root /data/www/ha97;
location ~ .*\.(php|php5)?${
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.jsp;
include fastcgi.conf;
}
通过nginx可以实现虚拟主机的配置,nginx支持三种类型的虚拟主机配置,
1、基于ip的虚拟主机, (一块主机绑定多个ip地址)
2、基于域名的虚拟主机(servername)
3、基于端口的虚拟主机(listen如果不写ip端口模式)
示例基于虚拟机ip的配置,这里需要配置多个ip
server
{
    listen 192.168.20.20:80;
    server_name www.linuxidc.com;
    root /data/www;
}
server
{
    listen 192.168.20.21:80;
    server_name www.linuxidc.com;
    root /data/www;
}

location

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
location 映射(ngx_http_core_module)
location [ = | ~ | ~* | ^~ ] uri { ... }
location URI {}:
对当前路径及子路径下的所有对象都生效;
location = URI {}: 注意URL最好为具体路径。
精确匹配指定的路径,不包括子路径,因此,只对当前资源生效;
location ~ URI {}:
location ~* URI {}:
模式匹配URI,此处的URI可使用正则表达式,~区分字符大小写,~*不区分字符大小写;
location ^~ URI {}:
不使用正则表达式
优先级:= > ^~ > ~|~* > /|/dir/

/loghaha.html
/logheihei.html
^/log.*html$

## location 配置规则
=前缀的指令严格匹配这个查询。如果找到,停止搜索。
所有剩下的常规字符串,最长的匹配。如果这个匹配使用^〜前缀,搜索停止。
正则表达式,在配置文件中定义的顺序。
如果第3条规则产生匹配的话,结果被使用。否则,如同从第2条规则被使用

## location配置规则
location 的执行逻辑跟 location 的编辑顺序无关。
矫正:这句话不全对,“普通 location ”的匹配规则是“最大前缀”,
因此“普通 location ”的确与 location 编辑顺序无关;

但是“正则 location ”的匹配规则是“顺序匹配,且只要匹配到第一个就停止后面的匹配”;
“普通location ”与“正则 location ”之间的匹配顺序是?
先匹配普通 location ,再“考虑”匹配正则 location 。
注意这里的“考虑”是“可能”的意思,也就是说匹配完“普通 location ”后,有的时候需要继续匹配“正则 location ”,有的时候则不需要继续匹配“正则 location ”。两种情况下,不需要继续匹配正则 location :
( 1 )当普通 location 前面指定了“ ^~ ”,特别告诉 Nginx 本条普通 location 一旦匹配上,则不需要继续正则匹配;
( 2 )当普通location 恰好严格匹配上,不是最大前缀匹配,则不再继续匹配正则

loghaha.html
l: logha
l: ^~ loghah
l: loghaha.html
l: =loghaha.html
l: ^logh.*html$
l: ^logha.*html$

# 总结
nginx 收到请求头:判定ip,port,hosts决定server
nginx location匹配:用客户端的uri匹配location的uri
先普通
顺序无关
最大前缀
匹配规则简单
打断:
^~
完全匹配
再正则
不完全匹配
正则特殊性:一条URI可以和多条location匹配上
有顺序的
先匹配,先应用,即时退出匹配

请求头

1
2
3
4
请求头
host:决策server负责处理
uri:决策location
反向代理:proxy_pass ip:port[uri];

IP 访问控制

1
2
3
4
5
6
7
IP访问控制
location {
deny IP /IP段
deny 192.168.1.109;
allow 192.168.1.0/24;192.168.0.0/16;192.0.0.0/8
}
规则:按照顺序依次检测,直到匹配到第一条规则

Nginx 的 session 一致性问题

​ http协议是无状态的,即你连续访问某个网页100次和访问1次对服务器来说是没有区别对待的,因为它记不住你。那么,在一些场合,确实需要服务器记住当前用户怎么办?比如用户登录邮箱后,接下来要收邮件、写邮件,总不能每次操作都让用户输入用户名和密码吧,为了解决这个问题,session的方案就被提了出来,事实上它并不是什么新技术,而且也不能脱离http协议以及任何现有的web技术

​ session的常见实现形式是会话cookie(session cookie),即未设置过期时间的cookie,这个cookie的默认生命周期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了。实现机制是当用户发起一个请求的时候,服务器会检查该请求中是否包含sessionid,如果未包含,则系统会创造一个名为JSESSIONID的输出 cookie返回给浏览器(只放入内存,并不存在硬盘中),并将其以HashTable的形式写到服务器的内存里面;当已经包含sessionid是,服务端会检查找到与该session相匹配的信息,如果存在则直接使用该sessionid,若不存在则重新生成新的 session。这里需要注意的是session始终是有服务端创建的,并非浏览器自己生成的。 但是浏览器的cookie被禁止后session就需要用get方法的URL重写的机制或使用POST方法提交隐藏表单的形式来实现

Session共享

​ 首先我们应该明白,为什么要实现共享,如果你的网站是存放在一个机器上,那么是不存在这个问题的,因为会话数据就在这台机器,但是如果你使用了负载均衡把请求分发到不同的机器呢?这个时候会话id在客户端是没有问题的,但是如果用户的两次请求到了两台不同的机器,而它的session数据可能存在其中一台机器,这个时候就会出现取不到session数据的情况,于是session的共享就成了一个问题

Session一致性解决方案

​ 1、session复制
​ tomcat 本身带有复制session的功能。(不讲)
​ 2、共享session
​ 需要专门管理session的软件,
​ memcached 缓存服务,可以和tomcat整合,帮助tomcat共享管理session。

  • 安装 memecache 缓存服务
1
2
3
4
5
6
7
8
9
10
11
12
13
安装memcached
1、安装libevent
2、安装memcached
3、启动memcached
memcached -d -m 128m -p 11211 -l 192.168.170.101 -u root -P /tmp/
-d:后台启动服务
-m:缓存大小
-p:端口
-l:IP
-P:服务器启动后的系统进程ID,存储的文件
-u:服务器启动是以哪个用户名作为管理用户
如果源配置了也可以用
yum –y install memcached
  • tomcat 配置 session共享
1
2
3
4
5
6
7
8
9
10
11
配置session共享如下:
3、拷贝jar到tomcat的lib下,jar包见附件
4、配置tomcat,每个tomcat里面的context.xml中加入
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
memcachedNodes="n1:192.168.170.101:11211"
sticky="false"
lockingMode="auto"
sessionBackupAsync="false"
requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
sessionBackupTimeout="1000" transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
/>

注意: tomcat 集群一定要保持时间一致性

Linux 基础

Linux 开始

虚拟机 Linux 准备工作

Linux 安装步骤

  • 选择稍后安装

  • 网络类型选择 NAT

  • 修改最大磁盘大小(这里最大磁盘大小指给虚拟机分配的,但不真正使用不是这么多)

注意: Linux 版本根据自己的Linux 版本选择。列如:我这用的是 CentOS-6.5-x86_64-minimal.iso,

所以选择 Linux 版本时选择时 Centos 64位

  • 添加 ISO 镜像文件,之后开启虚拟机即可
    1. 选择编辑虚拟机设置

​ 2. 挂载 iso 镜像文件

  • 虚拟机安装配置(若特殊说明,则选择默认即可)

    • 跳过磁盘检查,否则磁盘检查时间会很长

      • 语言、键盘都选择英文即可。

      • 选择第一个

      • 选择 yes,discard any data

      • 自定义 Linux 虚拟机名称

      • 选择时区

      • 密码设置要简单、透明(大家都知道的,不属于个人重要密码)

      • 自定义磁盘分区

注意事项:

  1. VM 安装选择自定义安装,便于之后的自定义硬件配置(如:磁盘大小等)
  2. 语言、键盘都选择英文
  3. 时区选择亚洲/上海
  4. 密码设置要简单透明
  5. 磁盘需要手动分区 boot(200MB) swap(2G) /(剩余所有)

Linux 静态 IP 设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 设置静态 IP 
# 编辑 vi /etc/sysconfig/network-scripts/ifcfg-eth0
[root@node01 network-scripts]# cd /etc/sysconfig/network-scripts/
[root@node01 network-scripts]# vi ifcfg-eth0

# ifcfg-eth0 网卡配置,使用打开后编辑,
# ONBOOT=on 开机自启
# BOOTPROTO=static 表示 IP 使用静态IP
DEVICE=eth0
TYPE=Ethernet
ONBOOT=yes
NM_CONTROLLED=yes
BOOTPROTO=static
IPADDR=192.168.170.101
NETMASK=255.255.255.0
GATEWAY=192.168.170.2
DNS1=144.144.144.144
DNS2=8.8.8.8



# 删除 rm -f 70-persistent-net.rules
# 70-persistent-net.rules 该文件的作用是将 IP 地址与 MAC 地址绑定
[root@node01 etc]# cd /etc/udev/rules.d/
[root@node01 rules.d]# ll
total 16
-rw-r--r--. 1 root root 316 Nov 22 2013 60-raw.rules
-rw-r--r--. 1 root root 789 Sep 21 19:33 70-persistent-cd.rules
-rw-r--r--. 1 root root 420 Sep 21 19:28 70-persistent-net.rules
-rw-r--r--. 1 root root 54 Dec 8 2011 99-fuse.rules
[root@node01 rules.d]# rm -f 70-persistent-net.rules

# 关机
poweroff

# 修改主机名
[root@node01 ~]# vi /etc/sysconfig/network
# 编辑 network 文件
NETWORKING=yes
HOSTNAME=node01
Linux 网络配置文件位置
文件位置 文件作用说明
/etc/sysconfig/network-scripts/ifcfg-eth0 配置静态 IP 地址
/etc/udev/rules.d/70-persistent-net.rules 将 IP 地址与 MAC 地址绑定
/etc/sysconfig/network 配置主机名

使用 VM 软件 记录快照

Linux 关机和重启命令

关机 重启
poweroff reboot
init 0 init 6

Linux 常用命令

命令 作用
ls 显示目录内容 ll –i
pwd 显示当前目录的绝对路径
cd 切换目录
mkdir 文件夹 -p {}
cp 复制 -r
mv 移动
touch 创建文件夹、文件
type 命令名 查看指定命令的文件的存放位置 外部命令 & 内部命令
cat 查看文件内容
file 查看文件详细信息 (ELF 表示二进制文件)
echo 回声,打印标椎输出
echo $PATH 查看环境变量路径
echo $$ 当前shell的PID
help 查看内部命令
man 查看外部命令命令
whereis 定位命令位置
hash 查看 hash 表
ps -ef 查看当前进程信息
df -h 显示文件系统使用情况[甩锅]
du -h 查看某种文件使用状况[甩锅]
mount/unmout 挂载/卸载磁盘文件
#### ls 命令 ####

文件类型:
-:普通文件 (f)
d: 目录文件

​ b: 块设备文件 (block)
​ c: 字符设备文件 (character)

​ l: 符号链接文件(symbolic link file)

​ p: 命令管道文件(pipe)
​ s: 套接字文件(socket)
文件权限:9位,每3位一组,3组 权限(U,G,O)每一组:rwx(读,写,执行), r–
文件硬链接的次数
文件的属主(owner)
文件的属组(group)
文件大小(size),单位是字节
时间戳(timestamp):最近一次被修改的时间
​ 访问:access
​ 修改:modify,文件内容发生了改变
​ 改变:change,metadata,元数据

使用 ls 同时展示多个目录

1
2
3
4
5
6
[root@node01 god]# ls ~  /usr/local
/root:
anaconda-ks.cfg install.log install.log.syslog

/usr/local:
bin etc games include lib lib64 libexec sbin share src

使用 mkdir 命令同时创建多个文件夹

1
2
3
4
5
6
7
8
9
10
[root@node01 c]# mkdir Hello/{a,b,c}A -p
[root@node01 c]# ll
total 4
drwxr-xr-x. 5 root root 4096 Sep 22 00:45 Hello
[root@node01 c]# cd Hello
[root@node01 Hello]# ll
total 12
drwxr-xr-x. 2 root root 4096 Sep 22 00:45 aA
drwxr-xr-x. 2 root root 4096 Sep 22 00:45 bA
drwxr-xr-x. 2 root root 4096 Sep 22 00:45 cA

查看文件系统和文件使用情况

1
2
3
4
5
6
7
8
9
10
[root@node01 /]# du -h /etc/udev
4.0K /etc/udev/makedev.d
20K /etc/udev/rules.d
32K /etc/udev
[root@node01 /]# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda3 97G 842M 91G 1% /
tmpfs 491M 0 491M 0% /dev/shm
/dev/sda1 194M 28M 157M 15% /boot
/dev/sr0 398M 398M 0 100% /mnt

使用硬链接和软链接

  • 硬链接类似于拷贝,即使原文件被删除了,硬链接生成文件仍可使用
1
ln --l  原文件名 硬链接文件名
  • 软链接,原文件删除了,软链接生成文件不可使用
1
ln -s 原文件名 软链接文件名

安装 man 命令

1
yum install man man-pages

Linux命令执行流程

​ 用户通过客户端命令行输入命令以及命令参数, bash 解释器会根据空白符切割,得到的第一个元素,作为命令 command。通过判断该 command 命令是外部命令还是内部命令,若内部命令,则由 bash 执行器直接执行。若是外部命令,则 根据 $PATH 中给定的目录中从左向右寻找该命令文件的可执行程序文件,然后由 bash 执行器执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@node01 ~]# type ls
ls is aliased to `ls --color=auto'
[root@node01 ~]# type -a ls
ls is aliased to `ls --color=auto'
ls is /bin/ls
[root@node01 ~]# type ifconfig
ifconfig is /sbin/ifconfig
[root@node01 ~]# file /sbin/ifconfig
/sbin/ifconfig: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped
[root@node01 ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin
[root@node01 ~]# type yum
yum is hashed (/usr/bin/yum)
[root@node01 ~]# file /usr/bin/yum
/usr/bin/yum: a /usr/bin/python script text executable

小技巧:使用 xshell 登录 Linux ssh root:123456@192.168.170.101

1
ssh 用户名:密码@IP地址

Linux 文件系统

boot 目录的挂载与卸载

1
2
3
4
5
6
7
8
9
10
11
[root@node01 /]# umount /boot
[root@node01 /]# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda3 97G 842M 91G 1% /
tmpfs 491M 0 491M 0% /dev/shm
[root@node01 /]# mount /dev/sda1 /boot
[root@node01 /]# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda3 97G 842M 91G 1% /
tmpfs 491M 0 491M 0% /dev/shm
/dev/sda1 194M 28M 157M 15% /boot

File System Hierarchy Standard

文件系统层次化标准

目录
/boot 系统启动相关的文件,如内核、initrd,以及grub(bootloader)
/dev 设备文件
/etc 配置文件
/home 用户的家目录,每一个用户的家目录通常默认为/home/USERNAME
/root 管理员的家目录
/lib 库文件
/media 挂载点目录,移动设备
/mnt 挂载点目录,额外的临时文件系统
/opt 可选目录,第三方程序的安装目录
/proc 伪文件系统,跟硬件设备相关的属性映射文件
/sys 临时文件, /var/tmp
/tmp 可变化的文件
/bin 可执行文件, 用户命令
/sbin 管理命令

系统启动必须:

  • /boot:存放的启动Linux 时使用的内核文件,包括连接文件以及镜像文件。

  • /etc:存放所有的系统需要的配置文件子目录列表,更改目录下的文件可能会导致系统不能启动。

  • /lib:存放基本代码库(比如c++库),其作用类似于Windows里的DLL文件。几乎所有的应用程序都需要用到这些共享库。

  • /sys: 这是linux2.6内核的一个很大的变化。该目录下安装了2.6内核中新出现的一个文件系统 sysfs 。sysfs文件系统集成了下面3种文件系统的信息:针对进程信息的proc文件系统、针对设备的devfs文件系统以及针对伪终端的devpts文件系统。该文件系统是内核设备树的一个直观反映。当一个内核对象被创建的时候,对应的文件和目录也在内核对象子系统中

指令集合:

  • /bin:存放着最常用的程序和指令

  • /sbin:只有系统管理员能使用的程序和指令。

外部文件管理:

  • /dev :Device(设备)的缩写, 存放的是Linux的外部设备。注意:在Linux中访问设备和访问文件的方式是相同的。

  • /media:类windows的其他设备,例如U盘、光驱等等,识别后linux会把设备放到这个目录下。

  • /mnt:临时挂载别的文件系统的,我们可以将光驱挂载在/mnt/上,然后进入该目录就可以查看光驱里的内容了。

临时文件:

  • /run:是一个临时文件系统,存储系统启动以来的信息。当系统重启时,这个目录下的文件应该被删掉或清除。如果你的系统上有 /var/run 目录,应该让它指向 run。

  • /lost+found:一般情况下为空的,系统非法关机后,这里就存放一些文件。

  • /tmp:这个目录是用来存放一些临时文件的。

账户:

  • /root:系统管理员的用户主目录。

  • /home:用户的主目录,以用户的账号命名的。

  • /usr:用户的很多应用程序和文件都放在这个目录下,类似于windows下的program files目录。

  • /usr/bin:系统用户使用的应用程序与指令。

  • /usr/sbin:超级用户使用的比较高级的管理程序和系统守护程序。

  • /usr/src:内核源代码默认的放置目录。

运行过程中要用:

  • /var:存放经常修改的数据,比如程序运行的日志文件(/var/log 目录下)。

  • /proc:管理内存空间!虚拟的目录,是系统内存的映射,我们可以直接访问这个目录来,获取系统信息。这个目录的内容不在硬盘上而是在内存里,我们也可以直接修改里面的某些文件来做修改。

扩展用的:

  • /opt:默认是空的,我们安装额外软件可以放在这个里面。

  • /srv:存放服务启动后需要提取的数据(不用服务器就是空)

挂载 cdrom 到 /mnt 目录下

1
mount /dev/cdrom  /mnt

文本操作命令

command 作用
cat 显示全部文本文件内容
more 逐页显示文本文件内容
less 一次性读取全部文件内容,相比more,可以往回看
head 显示前 n 行的内容
tail 显示后 n 行的内容
| 管道
xargs

使用 head 和 tail 命令遍历每行

1
2
[root@node01 god]# cat profile | head -3 | tail -1
# System wide environment and startup programs, for login setup

xargs 用法

1
2
3
4
5
6
[root@node01 god]# echo "/"  |xargs ls -l
total 90
dr-xr-xr-x. 2 root root 4096 Sep 21 19:29 bin
dr-xr-xr-x. 5 root root 1024 Sep 21 19:29 boot
drwxr-xr-x. 18 root root 3680 Sep 21 22:21 dev
# ....

vi 全屏文本编辑器

打开方式

1
2
3
4
vim /path/to/somefile
vim +# :打开文件,并定位于第#行
vim +:打开文件,定位至最后一行
vim +/PATTERN : 打开文件,定位至第一次被PATTERN匹配到的行的行首

关闭文件

1
2
3
4
5
6
7
8
9
末行模式:
:q 退出 没有动过文件
:wq 保存并退出 动过了,不后悔
:q! 不保存并退出 动过了,后悔了
:w 保存
:w! 强行保存
:wq --> :x

ZZ: 保存并退出 不需要冒号,编辑模式 # 重点*

三种模式

按键具有编辑文本功能:默认打开进入编辑模式
输入模式:按键本身意义
末行模式:接受用户命令输入

  • 编辑模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #编辑-->输入:
    i: 在当前光标所在字符的前面,转为输入模式;*
    a: 在当前光标所在字符的后面,转为输入模式;*

    o: 在当前光标所在行的下方,新建一行,并转为输入模式;
    O:在当前光标所在行的上方,新建一行,并转为输入模式;
    I:在当前光标所在行的行首,转换为输入模式
    A:在当前光标所在行的行尾,转换为输入模式
    输入-->编辑:
    ESC
    #编辑-->末行:

    #末行-->编辑:
    ESC, ESC
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    #移动光标
    字符
    h: 左;j: 下;k: 上;l: 右
    # 单词
    w: 移至下一个单词的词首*
    e: 跳至当前或下一个单词的词尾
    b: 跳至当前或前一个单词的词首
    # 行内
    0: 绝对行首
    ^: 行首的第一个非空白字符*
    $: 绝对行尾*
    # 行间
    G:文章末尾
    3G:第3行
    gg:文章开头
    # 翻屏
    ctrl:f,b

    # 删除&替换单个字符
    x:删除光标位置字符
    3x:删除光标开始3个字符
    r:替换光标位置字符
    # 删除命令 : d
    dw,dd
    # 复制粘贴&剪切
    yw(复制一个词),yy(复制一行)
    p(向下粘贴)
    P(向上粘贴)
    # 撤销&重做
    u 撤销*
    ctrl+r 重做 撤销的操作*

    . 重复上一步的操作
  • 末行模式 (进入末行模式 shirt+: )

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    set:设置
    set nu number
    set nonu nonumber
    set readonly


    /:查找
    /after
    n(向下查找),N(向上查找)
    ?向上查找
    !:执行命令
    :!ls -l /

    s查找并替换
    s/str1/str2/gi
    /:临近s命令的第一个字符为边界字符:/,@,#
    g:一行内全部替换(若没有选择,则替换当前行的第一个替换)
    i:忽略大小写
    范围
    n:行号
    .:当前光标行
    +n:偏移n行
    $:末尾行,$-3
    %:全文

    # 替换查找从当前光标处,到文件末尾处
    eg: .,$s/str1/str2/gi

末行模式小技巧

1
2
3
4
5
编辑模式下 :
:Gd 清空内容
:.,$d 删除从当前光标到末尾所有行
:n,md 删除从第 n 行到 m 行的所有内容
:n,my 复制从第 n 行到 m 行的所有内容

正则表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
grep:显示匹配行
- v:反显示
- e:使用扩展正则表达式

匹配操作符
\ 转义字符
. 匹配任意单个字符
[1249a],[^12],[a-k] 字符序列单字符占位
^ 行首
$ 行尾
\<,\>:\<abc 单词首尾边界*
| 连接操作符
(,) 选择操作符
\n 反向引用

重复操作符:
? 匹配0到1次。
* 匹配0到多次。
+ 匹配1到多次。
{n} 匹配n次。
{n,} 匹配n到多次。
{n,m} 匹配n到m次。
与扩展正则表达式的区别:grep basic
\?, \+, \{, \|, \(, and \)
匹配任意字符
.*

注意:在未扩展正则表达式时,若匹配规则中含有 ?,+,{,},|,(,) ,都需要通过 \ 转义,否则匹配不到

查询文章中包括4位整数的行

1
grep -E  "([^0-9][0-9]|^[0-9])([0-9]{2})([0-9][^0-9]|[0-9]$)"  grep.txt

查看指定单词

1
grep “\<单词名\>”  file

反向引用

1
2
3
4
[root@node01 god]# grep  -E ".*(god).*(good).*\2.*\1" test
asgodssgoodsssagoodssgod
[root@node01 god]# grep -E ".*(god).*(good).*\1.*\2" test
asgodssgoodsssagodssgood

文本处理

cut 命令

1
2
3
4
cut:显示切割的行数据
f:选择显示的列
s:不显示没有分隔符的行
d:自定义分隔符

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@node01 god]# cut -d' ' -s  -f1-3 grep.txt 
ooxx 12121212
oox 12121212
1212 ooxx 1212
[root@node01 god]# cut -d' ' -f1,2,3 grep.txt
ooxx12121212ooxx
ooxx 12121212
oox 12121212
1212 ooxx 1212
oo3xx
oo4xx
ooWxx
oomxx
$ooxx
oo1234xx
ooxyzxx

sort命令

1
2
3
4
5
6
7
sort:排序文件的行
n:按数值排序
r:倒序
t:自定义分隔符
k:选择排序列
u:合并相同行
f:忽略大小写
1
2
3
4
5
6
[root@node01 god]# sort  -k2 -n -r  fruits.txt 
orange--27
banana--15
apple--20
apple--20
[root@node01 god]# sort -k2 -n -r fruits.txt

wc 命令

1
wc 打印每个文件的换行数、单词数和字节数
1
2
3
4
5
6
[root@node01 god]# wc fruits.txt 
4 4 42 fruits.txt
[root@node01 god]# cat fruits.txt | wc
4 4 42
[root@node01 god]# cat fruits.txt | wc -l
4

统计当前目录中文件的个数

1
ls ./ | wc

行编辑器

sed 命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
sed [options] 'AddressCommand' file ...
-n: 静默模式,不再默认显示模式空间中的内容
-i: 直接修改原文件*
-e SCRIPT -e SCRIPT:可以同时执行多个脚本
-f /PATH/TO/SED_SCRIPT
-r: 表示使用扩展正则表达式*

d: 删除符合条件的行;
p: 显示符合条件的行;
a \string: 在指定的行后面追加新行,内容为string*
\n:可以用于换行
i \string: 在指定的行前面添加新行,内容为string*
r FILE: 将指定的文件的内容添加至符合条件的行处
w FILE: 将地址指定的范围内的行另存至指定的文件中;
s/pattern/string/修饰符: 查找并替换,默认只替换每行中第一次被模式匹配到的字符串*
g: 行内全局替换
i: 忽略字符大小写
s///: s###, s@@@
\(\), \1, \2 *

sed:行编辑器Address
可以没有
给定范围
查找指定行/str/

模糊匹配指定行,并替换

1
2
3
# id:9:initdefault:

sed "s/\(id:\)[0-9]\(:initdefault:\)/\19\2/" inittab

模糊匹配 IP 地址,并替换最后的主机号

1
2
3
4
sed "s/\(IPADDR=\(\([0-9]\|[1-9][0-9]\|1[0-9][0-9]\|2[0-4][0-9]\|25[0-5]\)\.\)\{3\}\).*/\1144/" ifcfg-eth0

[root@node01 god]# num=100
[root@node01 god]# sed "s/\(IPADDR=\(\([0-9]\|[1-9][0-9]\|1[0-9][0-9]\|2[0-4][0-4]\|25[0-5]\)\.\)\{3\}\).*/\1$num/" -i ifcfg-eth0

注意: \. 表示真实的点,不作为正则匹配中任意字符

awk 命令

awk是一个强大的文本分析工具。
相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大。
简单来说awk就是把文件逐行的读入,(空格,制表符)为默认分隔符将每行切片,切开的部分再进行各种分析处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
awk -F '{pattern + action}' {filenames}
支持自定义分隔符
支持正则表达式匹配
支持自定义变量,数组 a[1] a[tom] map(key)
支持内置变量
ARGC 命令行参数个数
ARGV 命令行参数排列
ENVIRON 支持队列中系统环境变量的使用
FILENAME awk浏览的文件名
FNR 浏览文件的记录数
FS 设置输入域分隔符,等价于命令行 -F选项
NF 浏览记录的域的个数*
NR 已读的记录数*
OFS 输出域分隔符
ORS 输出记录分隔符
RS 控制记录分隔符
支持函数
print、split、substr、sub、gsub
支持流程控制语句,类C语言
if、while、do/while、for、break、continue
#--------------------------------------------------------------------------------#
只是显示/etc/passwd的账户:CUT
awk -F':' '{print $1}' passwd
只是显示/etc/passwd的账户和账户对应的shell,而账户与shell之间以逗号分割,而且在所有行开始前添加列名name,shell,在最后一行添加"blue,/bin/nosh"(cut,sed)
awk -F':' 'BEGIN{print "name,shell"} {print $1 "," $7} END{print "blue,/bin/nosh"}' passwd
搜索/etc/passwd有root关键字的所有行
awk '/root/ { print $0}' passwd

注意:

  1. $0 表示一整行内容

  2. 代码块{} 外加单引号,不能是双引号,即 '{}'

  3. awk 默认以空白、TAB作为分隔符 delimiter,即若以空白、TAB为分隔号,不需要指定 -F参数

统计/etc/passwd文件中,每行的行号,每行的列数,对应的完整行内容

1
2
3
4
5
6
7
awk -F":" 'BEGIN{ print "NR\tNF\tContent" } {print NR "\t" NF "\t" $0 }' passwd 
# 结果
NR NF Content
1 7 root:x:0:0:root:/root:/bin/bash
2 7 bin:x:1:1:bin:/bin:/sbin/nologin
3 7 daemon:x:2:2:daemon:/sbin:/sbin/nologin
# ....

打印报表功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# awk.txt
Tom 0 2012-12-11 car 3000
John 1 2013-01-13 bike 1000
vivi 1 2013-01-18 car 2800
Tom 0 2013-01-20 car 2500
John 1 2013-01-28 bike 3500


# awk.sh
{
split($3,date,"-")
if(date[2] == "01"){
name[$1] += $5 #把名字作为数组下标,对应的value为工资
}
if($2 == "0"){
role[$1] = "manager"
}else{
role[$1] = "worker"
}
}
END{
for (i in name){
print i "\t" role[i] "\t" name[i]
}
}

# 使用
[root@node01 god]# awk -f awk.sh awk.txt
vivi worker 2800
Tom manager 2500
John worker 4500

Linux 用户管理

命令 说明
id 打印输出有效用户的自身的ID 和 所在组的 ID
useradd 添加用户
userdel 删除用户
groupadd 添加组
groupdel 删除组
passwd 设置或修改密码
sudo * 提升权限
su 切换用户

删除用户

若要删除用户,再创建同名用户,需要在创建前删除原 /home 目录和 /var/mail 目录下的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@node01 /]# userdel god
[root@node01 /]# useradd god
useradd: warning: the home directory already exists.
Not copying any file from skel directory into it.
Creating mailbox file: File exists
[root@node01 /]# rm -rf /home/god/
[root@node01 /]# useradd god
useradd: user 'god' already exists
[root@node01 /]# rm -fr /var/
cache/ empty/ lib/ lock/ mail/ opt/ run/ tmp/
db/ games/ local/ log/ nis/ preserve/ spool/ yp/
[root@node01 /]# rm -fr /var/mail/god
[root@node01 /]# useradd god

权限

命令 说明
r 读权限 r=4
w 写权限 w=2
x 可执行权限 x=1
usermod 修改用户权限
chown 修改文件的属主和属组
chmod 修改文件操作权限 (RWX) augo

注意:

  1. 一个文件只能有一个属主和一个属组

  2. 文件夹的执行权限 x ,表示用户是否可以进入到该文件夹下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
useradd sxt01
passwd sxt01
useradd sxt02
passwd sxt02

mkdir /var/swapdata
1,权限修正:
chmod 770 swapdata | chmod o-rwx g+rwx swapdata
2,修正属组
groupadd sxtswap
usermod -a -G sxtswap sxt01
usermod -a -G sxtswap sxt02
chown root:sxtswap swapdata
chown :sxtswap ooxx.file
chmod 770 ooxx.file
id username

Linux 编译安装

执行顺 说明
./configure 生成编译依赖关系
make 编译
make install 新建文件夹、拷贝文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1,下载源码包
2,解压缩:tar xf filename
3,cd:vi README
4,./configure
检查操作系统
检查编译环境
yum install gcc
pcre 依赖库
yum search pcre
yum install pcre-devel
openssl
yum install opssl-devel
./configure --help
./configure --prefix=/opt/sxt/nginx #配置安装路径
5,Makefile
6,make
7,make install
8,cd /opt/sxt/nginx/sbin ./nginx
9,浏览器访问测试
10,祝君好运~!

Linux RPM 安装

Redhat提供了rpm管理体系
已经编译的软件包:针对不同的平台系统编译目标软件包
操作系统维护安装信息
软件包包含依赖检查,但还需人为解决

安装 rpm 软件

1
rpm -ivh  xxxx.rpm

rpm 命令使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
rpm安装:
-ivh filename
--prefix
rpm升级:
-Uvh
-Fvh
rpm卸载:
-e PACKAGE_NAME

# rpm查询
rpm -qa : 查询已经安装的所有包
rpm -q PACKAGE_NAME: 查询指定的包是否已经安装
rpm -qi PACKAGE_NAME: 查询指定包的说明信息
rpm -ql PACKAGE_NAME: 查询指定包安装后生成的文件列表
rpm -qc PACEAGE_NEME:查询指定包安装的配置文件
rpm -qd PACKAGE_NAME: 查询指定包安装的帮助文件
rpm -q --scripts PACKAGE_NAME: 查询指定包中包含的脚本
rpm -qf /path/to/somefile: 查询文件是由哪个rpm包安装生成的
如果某rpm包尚未安装,需查询其说明信息、安装以后会生成的文件
rpm -qpi /PATH/TO/PACKAGE_FILE
rpm -qpl

Linux YUM 安装

yum 命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
yum命令:
yum repolist
yum clean all
yum makecache
yum update
查询:
yum list
yum search
yum info
安装&卸载:
yum install
remove|erase

yum命令:分组
yum grouplist
yum groupinfo
yum groupinstall
yum groupremove
yum groupupdate

更换安装为阿里源

1
2
3
4
5
6
7
8
# 备份
mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup

# 下载阿里 yum 源
curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo

# 更新缓存
yum makecache

本地 yum 源

  1. 为当前虚拟机挂载镜像文件

  1. 操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 挂载 cdrom 
mount /dev/cdrom /mnt

repo:
/etc/yum.repos.d/
[repoID]
baseurl=
http://
file://
ftp://
#gpgchkeck= 有1和0两个选择,分别代表是否是否进行gpg校验,如果没有这一项,默认是检查的。
gpgcheck=1/0
#当某个软件仓库被配置成 enabled=0 时,yum 在安装或升级软件包时不会将该仓库做为软件包提供源。使用这个选项,可以启用或禁用软件仓库。
enable=0/1

#[base]
name=local
failovermethod=priority
baseurl=file:///mnt
gpgcheck=1
enable=1

注意: 若包名中间包括空格,一定使用双引号括起来

中文显示,查看中文文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# yum 的 repo 变成aliyun  || 本地DVD
yum grouplist
yum groupinstall "Chinese Support"
echo $LANG
en_US.UTF-8
LANG=zh_CN.UTF-8

#增加epel的repo:
http://mirrors.aliyun.com
epel>>>>>help
wget centos6.......

yum clean all
yum makecache
yum search man-pages
yum install man man-pages man-pages-zh-CN
【【【【 man bash 】】】】

文本流

变量
引用&命令替换
退出状态&逻辑判断
表达式
流程控制

1
2
3
4
5
6
7
8
9
10
重定向:不是命令
程序自身都有I/O
0:标准输入
1:标准输出
2:错误输出
控制程序I/O位置
一切皆文件
/proc/$$/fd
程序是否处理I/O?
绑定顺序:从左到右

输出重定向

输出重定向 说明
1>xxx 标准输出覆盖重定向
1>>xxx 标准输出追加重定向
2>xxx 错误覆盖重定向
2>>xxx 错误追加重定向
>&或者&> xxx 向 xxx 文件添加错误和标准输出信息

注意: 重定向符号左边都不能包括空格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[root@node01 god]# ls /yy 2>error
[root@node01 god]# ll
total 8
-rw-r--r--. 1 root root 140 Sep 22 22:39 a
-rw-r--r--. 1 root root 49 Sep 22 22:40 error
[root@node01 god]# cat error
ls: cannot access /yy: No such file or directory
[root@node01 god]# ls /123 2>>error
[root@node01 god]# cat error
ls: cannot access /yy: No such file or directory
ls: cannot access /123: No such file or directory


[root@node01 god]# ls / /suibian 1>ls02.out 2>&1
[root@node01 god]# cat ls02.out
ls: cannot access /suibian: No such file or directory
/:
abc
bin
# ... 省略
[root@node01 god]# ls / /suibian >& ls01.out
[root@node01 god]# cat ls01.out
ls: cannot access /suibian: No such file or directory
/:
abc
bin
boot
# ... 省略

注意: 若一条命令中既包括错误输出,也包括正确输出,则先输出错误输出

输入重定向

输入重定向 说明
<<< 从字符串中读取输入
<<E 从键盘中读取输入,E表示结束符
< 从文件中读取输入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@node01 god]# read aaa <<<"HelloWord"
[root@node01 god]# echo $aaa
HelloWord
[root@node01 god]# read aaa<<GG
> ljasf
> ajlsdfj;la
> ;ajdfkl
> GG #跟 GG 一样,表示输入退出
[root@node01 god]# echo $aaa
ljasf

# input.sh 脚本
cat <<AABB
print error info
AABB
echo "you know me .."
# 执行
source input.sh

[root@node01 god]# cat 0< /etc/in
init/ init.d/ inittab inputrc
[root@node01 god]# cat 0< /etc/inittab
# inittab is only used by upstart for the default runlevel.
#
# ADDING OTHER CONFIGURATION HERE WILL HAVE NO EFFECT ON YOUR SYSTEM.

全重定向

1
2
3
exec 8<> /dev/tcp/www.baidu.com/80
echo -e "GET / HTTP/1.0\n" >& 8
cat <& 8

Shell 编程

bash

1
2
3
4
5
6
7
8
9
10
11
shell  bash
解释器,启动器
解释器:
用户交互输入
文本文件输入*
脚本本质:
#! /bin/bash
#! /usr/bin/python
读取方式:
当前shell:source/.
新建子shell:/bin/bash file / ./file.sh 《chmod +x file.sh》
执行方式 说明
source xxx.sh 在当前 bash 中执行脚本
./xxx.sh 开启一个子 bash 执行脚本

注意:在不同 bash 中执行的结果有可能不相同,即要理清父子 bash 之间的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[root@node01 sh]# echo $$
1132
[root@node01 sh]# source sh01.sh
abc boot etc lib lost+found mnt proc sbin share sys usr
bin dev home lib64 media opt root selinux srv tmp var
Hello World
1132
init─┬─auditd───{auditd}
├─crond
├─dbus-daemon───{dbus-daemon}
├─master─┬─pickup
│ └─qmgr
├─6*[mingetty]
├─rsyslogd───3*[{rsyslogd}]
├─sshd───sshd───bash───pstree
└─udevd───2*[udevd]
[root@node01 sh]# ./sh01.sh
abc boot etc lib lost+found mnt proc sbin share sys usr
bin dev home lib64 media opt root selinux srv tmp var
Hello World
1296
init─┬─auditd───{auditd}
├─crond
├─dbus-daemon───{dbus-daemon}
├─master─┬─pickup
│ └─qmgr
├─6*[mingetty]
├─rsyslogd───3*[{rsyslogd}]
├─sshd───sshd───bash───bash───pstree
└─udevd───2*[udevd]

变量

类型 作用
本地变量 作用于当前 bash 中
局部变量 local,只作用于当前代码块
位置变量 $1,$2,,从脚本文件后,读取参数
特殊变量 $#: 位置参数个数
$*: 参数列表,双引号引用为一个字符串
$@: 参数列表,双引号引用为单独的字符串
$$:当前 shell 的 PID:接收者
$BASHPID:真实
管道 *
$?:上一个命令退出状态
- 0 成功
-other: 失败
环境变量 - export 定义变量
- 导出到子 shell
- fork() Copy On Write 时间复杂度 O(1)
适用用于函数
unset: 取消变量
set: 查看shell 的变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
[root@node01 sh]# num=1
[root@node01 sh]# myfunc(){
> echo $num
> echo HelloWorld
> }
[root@node01 sh]# myfunc
1
HelloWorld


# 本地变量 local
[root@node01 sh]# myfunc(){
> local fun=1
> echo $fun
> echo ok
> }
[root@node01 sh]# myfunc
1
ok
[root@node01 sh]# echo $fun

# -------------------------------
# 位置参数
[root@node01 sh]# cat sh02.sh
echo $1
echo $2
[root@node01 sh]# vi sh02.sh
[root@node01 sh]# source sh02.sh 1 2
1
2


# 特殊参数
[root@node01 sh]# cat sh02.sh
echo $1
echo $2
echo ${11}
echo "---------"
echo {'position parameters:'}$#
echo $@
[root@node01 sh]# source sh02.sh a b c d e f j h k m n o
a
b
n
---------
{position parameters:}12
a b c d e f j h k m n o

#查看上一个命令的执行状态
[root@node01 sh]# ls /
abc bin boot dev etc home lib lib64 lost+found media mnt opt proc root sbin selinux share srv sys tmp usr var
[root@node01 sh]# echo $?
0
[root@node01 sh]# ls /asf
ls: cannot access /asf: No such file or directory
[root@node01 sh]# echo $?
2

# 数组使用
[root@node01 sh]# arr=(1 2 3)
[root@node01 sh]# echo $arr[1]
1[1]
[root@node01 sh]# echo ${arr[1]}
2
[root@node01 sh]# echo ${arr[0]}
1
[root@node01 sh]# echo ${arr[2]}
3

# 变量与其他字符串的拼接,需要使用花括号 {},标识为变量
[root@node01 sh]# num=1
[root@node01 sh]# echo $num1231

[root@node01 sh]# echo ${num}111
1111

# 管道--管道两边分为在两个子 bash 中, 子 bash 可以使用父 bash 中的引用
[root@node01 sh]# dd=1
[root@node01 sh]# dd=10 | echo $dd
1

# export 导入不共享
[root@node01 sh]# cat sh04.sh
echo $k
echo "-------------"
sleep 10
echo $k
[root@node01 sh]# export k=5
[root@node01 sh]# ./sh04.sh & # $ 表示在后台运行
[1] 1729
[root@node01 sh]# 5
-------------
^C
[root@node01 sh]# k=8
[root@node01 sh]# 5
^C
[1]+ Done ./sh04.sh

注意:

  1. 数组元素之间通过空格分隔,使用逗号分号,bash 会把数组整体当做字符串使用
  2. export 导出非共享

    引用

    双引号:弱引用,参数扩展
    单引号:强引用,不可嵌套
    花括号扩展不能被引用
    命令执行前删除引用

    1
    2
    3
    4
    5
    6
    7
    8
    [root@node01 sh]# kk=12
    [root@node01 sh]# echo "$kk"
    12
    [root@node01 sh]# echo '$kk'
    $kk
    # 花括号扩展不能被引用
    [root@node01 b]# cp "/etc/{passwd,inittab}" ./
    cp: cannot stat `/etc/{passwd,inittab}': No such file or directory

命令替换

1
2
3
反引号:`ls -l /`
$(ls -l /)
可以嵌套
1
2
3
4
5
6
7
8
9
10
[root@node01 sh]# echo "`echo 123`"
123

[root@node01 sh]# abc=$(echo $(echo "sxt"))
[root@node01 sh]# echo $abc
sxt

[root@node01 sh]# abc=$(ls -l /)
[root@node01 sh]# echo $abc
total 98 drwxrw-r-x. 3 root root 4096 Sep 22 13:53 abc dr-xr-xr-x. 2 root root 4096 Sep 22 21:34 bin dr-xr-xr-x. 5 root root 1024 Sep 21 19:29 boot drwxr-xr-x. 18 root root 3680 Sep 22 21:45 dev drwxr-xr-x. 76 root root 4096 Sep 22 21:45 etc drwxr-xr-x. 4 root root 4096 Sep 22 13:04 home dr-xr-xr-x. 8 root root 4096 Sep 22 14:16 lib dr-xr-xr-x. 10 root root 12288 Sep 22 21:34 lib64 drwx------. 2 root root 16384 Sep 21 19:28 lost+found drwxr-xr-x. 2 root root 4096 Sep 23 2011 media drwxr-xr-x. 2 root root 4096 Sep 23 2011 mnt drwxr-xr-x. 2 root root 4096 Sep 23 2011 opt dr-xr-xr-x. 87 root root 0 Sep 22 21:45 proc dr-xr-x---. 5 root root 4096 Sep 23 03:26 root dr-xr-xr-x. 2 root root 12288 Sep 22 17:58 sbin drwxr-xr-x. 7 root root 0 Sep 22 21:45 selinux drwxrwx---. 2 root share 4096 Sep 22 13:51 share drwxr-xr-x. 2 root root 4096 Sep 23 2011 srv drwxr-xr-x. 13 root root 0 Sep 22 21:45 sys drwxrwxrwt. 3 root root 4096 Sep 23 02:02 tmp drwxr-xr-x. 14 root root 4096 Sep 22 17:50 usr drwxr-xr-x. 17 root root 4096 Sep 21 19:28 var

逻辑判断*

1
2
3
4
5
退出状态
echo $?
逻辑判断
command1 && command2
command1 || command2

表达式

算术表达式

1
2
3
4
5
6
7
8
9
10
let  算术运算表达式
let C=$A+$B
$[算术表达式]
C =$[$A+$B]
$((算术表达式))
C=$(($A+$B))
expr 算术表达式
注意:表达式中各操作数及运算符之间要有空格。而且要使用命令引用
C=`expr $A + $B`
help let
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@node01 sh]# a=1
[root@node01 sh]# b=2
[root@node01 sh]# let c=$a+$b
[root@node01 sh]# echo $c
3
[root@node01 sh]# c=3
[root@node01 sh]# d=4
[root@node01 sh]# e=$(($c+$d))
[root@node01 sh]# echo $e
7
[root@node01 sh]# j=1
[root@node01 sh]# k=2
[root@node01 sh]# c=$((j+k))
[root@node01 sh]# echo $c
3

[root@node01 sh]# echo $a
2
[root@node01 sh]# ((a++))
[root@node01 sh]# echo $a
3

条件表达式

1
2
3
4
[  expression  ]
test expression
[[ expression ]]
help test
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@node01 sh]# test -a /etc/adf
[root@node01 sh]# echo $?
1
[root@node01 sh]# test -a /etc/password
[root@node01 sh]# echo $?
1

[root@node01 sh]# [ $a -gt $b ]
[root@node01 sh]# echo $?
0

[root@node01 sh]# [3 -gt 2]
-bash: [3: command not found
[root@node01 sh]# [ 3 -gt 2 ]
[root@node01 sh]# echo $?
0

注意: 靠近 [] ,需要有空格间隔

添加用户脚本

1
2
3
4
5
6
7
8
9
10
11
12
#添加用户
#用户密码同用户名
#静默运行脚本
#避免捕获用户接口
#程序自定义输出


#! /bin/bash
[ ! $# -eq 1 ] && echo "args error ! " && exit 3
id $1 >&/dev/null && echo "user exits" && exit 4
useradd $1 >& /dev/null && echo $1 | passwd --stdin $1 >& /dev/null && echo "useradd user success!" && exit 0
echo "don't know, user add error!"

流程控制

if

1
2
3
4
5
6
7
8
[test06@node01 sh]$ if [ 3 -gt 1 ]; then echo ok; fi
ok
[test06@node01 sh]$ a=1
[test06@node01 sh]$ b=2
[test06@node01 sh]$ if [ $b -lt $a ];then echo ok;elif [ $b -gt $a ];then echo ojbk;fi
ojbk
[test06@node01 sh]$ if [ $b -lt $a ];then echo lt;else echo gt;fi
gt

while

1
2
3
4
5
6
7
8
9
10
11
[test06@node01 sh]$ while ls /;do echo ok;break;done
abc boot etc lib lost+found mnt proc sbin share sys usr
bin dev home lib64 media opt root selinux srv tmp var
ok
[test06@node01 sh]$ a=1
[test06@node01 sh]$ while [ $a -le 5 ];do echo $a;((a++));done
1
2
3
4
5

for

1
2
3
4
5
6
7
8
9
10
11
[test06@node01 sh]$ for ((ab=1;ab<5;ab++));do echo ab=$ab;done
ab=1
ab=2
ab=3
ab=4
[test06@node01 sh]$ for i in 1 2 3 4 5;do echo $i;done
1
2
3
4
5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 用户给定路径
# 输出文件大小最大的文件
# 递归子目录
#! /bin/bash
oldIFS=$IFS
IFS=$'\n'
for i in `du -a $1 | sort -nr`;do
filename=`echo $i | awk '{print $2}'`
if [ -f $filename ];then
echo $filename
break;
fi
done
IFS=$oldIFS

注意: 变量的赋值等于号前面两边不能有空格,bash 对空格敏感

循环遍历文件输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#! /bin/bash
oldIFS=$IFS
IFS=$'\n'
num=0
for i in `cat file.txt`;do
echo $i
((num++));
done
#! /bin/bash
oldIFS=$IFS
IFS=$'\n'
num=0
for i in `cat file.txt`;do
echo $i
((num++));
done
echo "num:$num"
IFS=$oldIFS
echo "---------------"
lines=`cat file.txt | wc -l`
for ((i=1;i<=lines;i++));do
echo `head -$i file.txt | tail -1`
((line++))
done
echo "line:$line"
echo "---------------"
num=0
while read line ;do
echo $line
((num++ ))
done < file.txt
echo "num:$num"
echo "-----------------"
num=0
cat file.txt | while read line;do
echo $line
((num++))
done
echo "num:$num"

人工智能真来了吗?

来了,真滴来了。

Example:

  • 谷歌、百度的无人汽车
  • 高铁站出入检票站
  • 杭州主要干道由阿里巴巴的人工智能(ET)管理

人工智能三要素

  • 硬件(计算机硬件的计算速度提升和计算成本下降)
  • 算法(机械学习算法、深度学习算法)
  • 数据(特指大数据)

人工智能的发展阶段

人工智能的概念

李开复:大数据+深度学习=人工智能

机器学习