Spring Boot 中使用 kafka

分類 database, hadoop

Kafka 是一种高吞吐的分布式发布订阅消息系统,能够替代传统的消息队列用于解耦合数据处理,缓存未处理消息等,同时具有更高的吞吐率,支持分区、多副本、冗余,因此被广泛用于大规模消息数据处理应用。Kafka 支持Java 及多种其它语言客户端,可与Hadoop、Storm、Spark等其它大数据工具结合使用。

环境安装

搭建高吞吐量 Kafka 分布式发布订阅消息 集群

测试用例

Github 代码

代码我已放到 Github ,导入spring-boot-kafka 项目

github https://github.com/souyunku/spring-boot-examples/tree/master/spring-boot-kafka

添加依赖

在项目中添加 kafka-clients 依赖

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>0.10.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>

启用 kafka

1
2
3
4
5
@Configuration
@EnableKafka
public class KafkaConfiguration {

}

消息生产者

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
@Component
public class MsgProducer {

private static final Logger log = LoggerFactory.getLogger(MsgProducer.class);

@Autowired
private KafkaTemplate<String, String> kafkaTemplate;

public void sendMessage(String topicName, String jsonData) {
log.info("向kafka推送数据:[{}]", jsonData);
try {
kafkaTemplate.send(topicName, jsonData);
} catch (Exception e) {
log.error("发送数据出错!!!{}{}", topicName, jsonData);
log.error("发送数据出错=====>", e);
}

//消息发送的监听器,用于回调返回信息
kafkaTemplate.setProducerListener(new ProducerListener<String, String>() {
@Override
public void onSuccess(String topic, Integer partition, String key, String value, RecordMetadata recordMetadata) {
}

@Override
public void onError(String topic, Integer partition, String key, String value, Exception exception) {
}

@Override
public boolean isInterestedInSuccess() {
log.info("数据发送完毕");
return false;
}
});
}

}

消息消费者

1
2
3
4
5
6
7
8
9
10
@Component
public class MsgConsumer {

@KafkaListener(topics = {"topic-1","topic-2"})
public void processMessage(String content) {

System.out.println("消息被消费"+content);
}

}

参数配置

application.properties

1
2
3
4
5
6
7
8
9
10
11
#kafka
# 指定kafka 代理地址,可以多个
spring.kafka.bootstrap-servers=YZ-PTEST-APP-HADOOP-02:9092,YZ-PTEST-APP-HADOOP-04:9092
# 指定listener 容器中的线程数,用于提高并发量
spring.kafka.listener.concurrency=3
# 每次批量发送消息的数量
spring.kafka.producer.batch-size=1000
# 指定默认消费者group id
spring.kafka.consumer.group-id=myGroup
# 指定默认topic id
spring.kafka.template.default-topic=topic-1

启动服务

1
2
3
4
5
6
7
8
@SpringBootApplication
@ComponentScan(value = {"io.ymq.kafka"})
public class Startup {

public static void main(String[] args) {
SpringApplication.run(Startup.class, args);
}
}

单元测试

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
import io.ymq.kafka.MsgProducer;
import io.ymq.kafka.run.Startup;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* 描述: 测试 kafka
*
* @author yanpenglei
* @create 2017-10-16 18:45
**/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Startup.class)
public class BaseTest {

@Autowired
private MsgProducer msgProducer;

@Test
public void test() throws Exception {

msgProducer.sendMessage("topic-1", "topic--------1");
msgProducer.sendMessage("topic-2", "topic--------2");
}
}

消息生产者,响应

1
2
3
4
2017-10-17 15:54:44.814  INFO 2960 --- [           main] io.ymq.kafka.MsgProducer                 : 向kafka推送数据:[topic--------1]
2017-10-17 15:54:44.860 INFO 2960 --- [ main] io.ymq.kafka.MsgProducer : 向kafka推送数据:[topic--------2]
2017-10-17 15:54:44.878 INFO 2960 --- [ad | producer-1] io.ymq.kafka.MsgProducer : 数据发送完毕
2017-10-17 15:54:44.878 INFO 2960 --- [ad | producer-1] io.ymq.kafka.MsgProducer : 数据发送完毕

消息消费者,响应

1
2
消息被消费topic--------1
消息被消费topic--------2

代码我已放到 Github ,导入spring-boot-kafka 项目

github https://github.com/souyunku/spring-boot-examples/tree/master/spring-boot-kafka

遇到一些坑

1
[2017-10-16 19:20:08.340] - 14884 严重 [main] --- org.springframework.kafka.support.LoggingProducerListener: Exception thrown when sending a message with key='null' and payload='topic--------2' to topic topic-2:

经调试发现 kafka 连接是用的主机名,所以修改 hosts

1
2
3
4
C:\Windows\System32\drivers\etc\hosts

10.32.32.149 YZ-PTEST-APP-HADOOP-02
10.32.32.154 YZ-PTEST-APP-HADOOP-04

留言與分享

搭建高吞吐量 Kafka 分布式发布订阅消息 集群

简介

Kafka 是一种高吞吐的分布式发布订阅消息系统,能够替代传统的消息队列用于解耦合数据处理,缓存未处理消息等,同时具有更高的吞吐率,支持分区、多副本、冗余,因此被广泛用于大规模消息数据处理应用。Kafka 支持Java 及多种其它语言客户端,可与Hadoop、Storm、Spark等其它大数据工具结合使用。

环境

Zookeeper集群: 192.168.252.121:2181,192.168.252.122:2181,192.168.252.123:2181

kafka 集群: 192.168.252.124 , 192.168.252.125 , 192.168.252.126

kafka-manager: 192.168.252.127

主机名修改

CentOs7.3 修改主机名

ssh 免密登录

CentOs7.3 ssh 免密登录

安装 JDK1.8

CentOs7.3 安装 JDK1.8

搭建 Zookeeper 集群

CentOs7.3 搭建 ZooKeeper-3.4.9 Cluster 集群服务

Zookeeper集群: 192.168.252.121:2181,192.168.252.122:2181,192.168.252.123:2181

主机名依次被我修改成: node1,node2,node3

搭建 kafka 集群

kafka 集群: 192.168.252.124 , 192.168.252.125 , 192.168.252.126

主机名依次被我修改成: node4,node5,node6

1.下载代码

kafka 官网下载 http://kafka.apache.org/downloads

下载最新版本的kafka ,

清华镜像:https://mirrors.tuna.tsinghua.edu.cn/apache/kafka/

阿里镜像:https://mirrors.aliyun.com/apache/kafka/

1
2
3
4
$ cd /opt
$ wget https://mirrors.tuna.tsinghua.edu.cn/apache/kafka/0.11.0.0/kafka_2.12-0.11.0.0.tgz
$ tar -zxvf kafka_2.12-0.11.0.0.tgz
$ cd kafka_2.12-0.11.0.0

2.修改配置

在 node4 操作

1
$ vi /opt/kafka_2.12-0.11.0.0/config/server.properties
1
2
3
4
broker.id=0  每台服务器不能重复

#设置zookeeper的集群地址
zookeeper.connect=192.168.252.121:2181,192.168.252.122:2181,192.168.252.123:2181

把配置复制到 node5,node6 集群

1
$ for a in {5..6} ; do scp -r /opt/kafka_2.12-0.11.0.0/ node$a:/opt/kafka_2.12-0.11.0.0 ; done

修改 node5,node6 集群的broker.id

1
$ vi /opt/kafka_2.12-0.11.0.0/config/server.properties

3.启动kafka

在 node1,启动 Kafka使用的 ZooKeeper,所以先启动ZooKeeper服务器

1
$ for a in {1..3} ; do ssh node$a "source /etc/profile;  /opt/zookeeper-3.4.9/bin/zkServer.sh start" ; done

现在 node4 启动Kafka服务器

1
$ for a in {4..6} ; do ssh node$a "source /etc/profile;  /opt/kafka_2.12-0.11.0.0/bin/kafka-server-start.sh /opt/kafka_2.12-0.11.0.0/config/server.properties" ; done

或者后台启动运行,日志查看去Kafka解压目录有个log 文件夹查看

1
$ for a in {4..6} ; do ssh node$a "source /etc/profile; nohup  /opt/kafka_2.12-0.11.0.0/bin/kafka-server-start.sh /opt/kafka_2.12-0.11.0.0/config/server.properties > /dev/null 2>&1 &" ; done

查看进程,Kafka 是否启动成功

1
2
3
$ jps
3825 Kafka
6360 Jps

如果报错删除

1
2
3
kafka.common.KafkaException: Failed to acquire lock on file .lock in /tmp/kafka-logs. A Kafka instance in another process or thread is using this directory.

$ rm -rf /tmp/kafka-logs

4.创建主题

1
$ /opt/kafka_2.12-0.11.0.0/bin/kafka-topics.sh --create --zookeeper 192.168.252.121:2181,192.168.252.122:2181,192.168.252.123:2181 --replication-factor 2 --partitions 1 --topic ymq

--replication-factor 2 #复制两份
--partitions 1 #创建1个分区
--topic #主题为ymq

运行list topic命令,可以看到该主题:

1
$ /opt/kafka_2.12-0.11.0.0/bin/kafka-topics.sh --list --zookeeper 192.168.252.121:2181,192.168.252.122:2181,192.168.252.123:2181

5.生产消息

Kafka附带一个命令行客户端,它将从文件或标准输入中输入,并将其作为消息发送到Kafka群集。默认情况下,每行将作为单独的消息发送。

node5 运行生产者,然后在控制台中输入一些消息以发送到服务器。

1
2
$ /opt/kafka_2.12-0.11.0.0/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic ymq
>www.ymq.io

6.消费消息

node6 运行消费者,将把消息转储到标准输出。

1
2
3
$ /opt/kafka_2.12-0.11.0.0/bin/kafka-console-consumer.sh --zookeeper 192.168.252.121:2181,192.168.252.122:2181,192.168.252.123:2181  --topic ymq --from-beginning
Using the ConsoleConsumer with old consumer is deprecated and will be removed in a future major release. Consider using the new consumer by passing [bootstrap-server] instead of [zookeeper].
www.ymq.io

7.topic详情

用describe 查看集群中topic每个节点情况

1
2
3
$ /opt/kafka_2.12-0.11.0.0/bin/kafka-topics.sh --describe --zookeeper 192.168.252.121:2181,192.168.252.122:2181,192.168.252.123:2181 --topic ymq
Topic:ymq PartitionCount:1 ReplicationFactor:2 Configs:
Topic: ymq Partition: 0 Leader: 1 Replicas: 1,3 Isr: 3,1

以下是输出的说明。第一行给出了所有分区的摘要,每个附加行提供有关一个分区的信息。由于我们这个主题只有一个分区,只有一行。

leader负责给定分区的读取和写入分配节点编号,每个分区的部分数据会随机指定不同的节点
replicas是复制此分区的日志的节点列表
isr一组正在同步的副本列表

8.删除topic

1
2
3
$ /opt/kafka_2.12-0.11.0.0/bin/kafka-topics.sh --delete --zookeeper 192.168.252.121:2181,192.168.252.122:2181,192.168.252.123:2181 --topic ymq
Topic ymq is marked for deletion.
Note: This will have no impact if delete.topic.enable is not set to true.

9.停止kafka

1
$ for a in {4..6} ; do ssh node$a "source /etc/profile;  /opt/kafka_2.12-0.11.0.0/bin/kafka-server-stop.sh /opt/kafka_2.12-0.11.0.0/config/server.properties " ; done

部署 Kafka Manager

Yahoo开源Kafka集群管理器Kafka Manager

作为一个分布式的消息发布-订阅系统,Apache Kafka在Yahoo内部已经被很多团队所使用,例如媒体分析团队就将其应用到了实时分析流水线中,同时,Yahoo整个Kafka集群处理的峰值带宽超过了20Gbps(压缩数据)。为了让开发者和服务工程师能够更加简单地维护Kafka集群,Yahoo构建了一个基于Web的管理工具,称为Kafka Manager,日前该项目已经在GitHub上开源。

通过Kafka Manager用户能够更容易地发现集群中哪些主题或者分区分布不均匀,同时能够管理多个集群,能够更容易地检查集群的状态,能够创建主题,执行首选的副本选择,能够基于集群当前的状态生成分区分配,并基于生成的分配执行分区的重分配,此外,Kafka Manager还是一个非常好的可以快速查看集群状态的工具。

Kafka Manager使用Scala语言编写,其Web控制台基于Play Framework实现,除此之外,Yahoo还迁移了一些Apache Kafka的帮助程序以便能够与Apache Curator框架一起工作。

一、它支持以下内容:

  • 管理多个群集
  • 容易检查集群状态(主题,消费者,偏移量,经纪人,副本分发,分区分配)
  • 运行首选副本选举
  • 使用选项生成分区分配,以选择要使用的代理
  • 运行分区的重新分配(基于生成的分配)
  • 创建可选主题配置的主题(0.8.1.1具有不同于0.8.2+的配置)
  • 删除主题(仅支持0.8.2+,并记住在代理配​​置中设置delete.topic.enable = true)
  • 主题列表现在表示标记为删除的主题(仅支持0.8.2+)
  • 批量生成多个主题的分区分配,并选择要使用的代理
  • 批量运行多个主题的分区重新分配
  • 将分区添加到现有主题
  • 更新现有主题的配置
  • 可选地,启用JMX轮询代理级和主题级度量。
  • 可选地筛选出在zookeeper中没有ids / owner /&offset /目录的消费者。

源码,并编译打包

在 kafka-manager: 192.168.252.127 node7 部署

编译超级慢

1
2
3
4
5
$ yum install git
$ cd /opt/
$ git clone https://github.com/yahoo/kafka-manager
$ cd kafka-manager/
$ ./sbt clean dist

下载编译好的包

1
2
3
$ yum install unzip
$ unzip kafka-manager-1.3.2.1.zip
$ vi /opt/kafka-manager-1.3.2.1/conf/application.conf

修改这个 zk 地址

1
kafka-manager.zkhosts="192.168.252.121:2181,192.168.252.122:2181,192.168.252.123:2181"

启动 kafka-manager

默认端口 NettyServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000

1
$ /opt/kafka-manager-1.3.2.1/bin/kafka-manager -Dconfig.file=conf/application.conf

或者后台运行 并且配置端口

1
$ nohup bin/kafka-manager  -Dconfig.file=/home/hadoop/app/kafka-manager-1.3.2.1/conf/application.conf -Dhttp.port=9000 &

访问: http://ip:9000

图片描述

留言與分享

CentOs7.3 搭建 ZooKeeper-3.4.9 Cluster 集群服务

Zookeeper 概述

zookeeper实际上是yahoo开发的,用于分布式中一致性处理的框架。最初其作为研发Hadoop时的副产品。由于分布式系统中一致性处理较为困难,其他的分布式系统没有必要 费劲重复造轮子,故随后的分布式系统中大量应用了zookeeper,以至于zookeeper成为了各种分布式系统的基础组件,其地位之重要,可想而知。著名的hadoop,kafka,dubbo 都是基于zookeeper而构建

要想理解zookeeper到底是做啥的,那首先得理解清楚,什么是一致性?

所谓的一致性,实际上就是围绕着“看见”来的。谁能看见?能否看见?什么时候看见?举个例子:淘宝后台卖家,在后台上架一件大促的商品,通过服务器A提交到主数据库,假设刚提交后立马就有用户去通过应用服务器B去从数据库查询该商品,就会出现一个现象,卖家已经更新成功了,然而买家却看不到;而经过一段时间后,主数据库的数据同步到了从数据库,买家就能查到了。

假设卖家更新成功之后买家立马就能看到卖家的更新,则称为强一致性

如果卖家更新成功后买家不能看到卖家更新的内容,则称为弱一致性

而卖家更新成功后,买家经过一段时间最终能看到卖家的更新,则称为最终一致性

《一致性协议》

《ZooKeeper应用场景》

《分布式架构》

《分布式 ZooKeeper 系列》

环境

VMware版本号:12.0.0

CentOS版本:CentOS 7.3.1611

ZooKeeper版本:ZooKeeper-3.4.9.tar.gz

虚拟机IP:192.168.252.101,192.168.252.102,192.168.252.103

集群主机名称:node1,node2,node3

集群主机用户:都是用root用户

集群JDK环境:jdk-8u144-linux-x64.tar.gz JDK 1.8 安装 具体参考《CentOs7.3 安装 JDK1.8》

集群主机之间设置免密登陆:

注意事项

关闭防火墙

centos 6.x 关闭 iptables

1
$ service iptables stop # 关闭命令:

centos 7.x 关闭firewall

1
$ systemctl stop firewalld.service # 停止firewall

ZooKeeper 安装

1.下载ZooKeeper

下载最新版本的ZooKeeper ,我在北京我就选择,清华镜像比较快

清华镜像:https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/

阿里镜像:https://mirrors.aliyun.com/apache/zookeeper/

1
2
$ cd /opt/
$ wget https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.4.9/zookeeper-3.4.9.tar.gz

或者在浏览器下载上传至opt 目录

2.提取tar文件

1
2
3
$ cd /opt/
$ tar -zxf zookeeper-3.4.9.tar.gz
$ cd zookeeper-3.4.9

创建data文件夹 用于存储数据文件

1
$ mkdir /opt/zookeeper-3.4.9/data

创建logs文件夹 用于存储日志

1
$ mkdir /opt/zookeeper-3.4.9/logs  

3.创建配置文件

zoo.cfg

zookeeper的主要配置文件,因为Zookeeper是一个集群服务,集群的每个节点都需要这个配置文件。为了避免出差错,zoo.cfg这个配置文件里没有跟特定节点相关的配置,所以每个节点上的这个zoo.cfg都是一模一样的配置。这样就非常便于管理了,比如我们可以把这个文件提交到版本控制里管理起来。其实这给我们设计集群系统的时候也是个提示:集群系统一般有很多配置,应该尽量将通用的配置和特定每个服务的配置(比如服务标识)分离,这样通用的配置在不同服务之间copy就ok了

1
$ vi /opt/zookeeper-3.4.9/conf/zoo.cfg

编辑内容如下

1
2
3
4
5
6
7
8
9
10
11
tickTime = 2000
dataDir = /opt/zookeeper-3.4.9/data
dataLogDir = /opt/zookeeper-3.4.9/logs
tickTime = 2000
clientPort = 2181
initLimit = 5
syncLimit = 2

server.1=node1:2888:3888
server.2=node2:2888:3888
server.3=node3:2888:3888

配置文件描述

tickTime

  • tickTime则是上述两个超时配置的基本单位,例如对于initLimit,其配置值为5,说明其超时时间为 2000ms * 5 = 10秒。

dataDir

  • 其配置的含义跟单机模式下的含义类似,不同的是集群模式下还有一个myid文件。myid文件的内容只有一行,且内容只能为1 - 255之间的数字,这个数字亦即上面介绍server.id中的id,表示zk进程的id。

dataLogDir

  • 如果没提供的话使用的则是dataDir。zookeeper的持久化都存储在这两个目录里。dataLogDir里是放到的顺序日志(WAL)。而dataDir里放的是内存数据结构的snapshot,便于快速恢复。为了达到性能最大化,一般建议把dataDir和dataLogDir分到不同的磁盘上,这样就可以充分利用磁盘顺序写的特性。

initLimit

  • ZooKeeper集群模式下包含多个zk进程,其中一个进程为leader,余下的进程为follower。

当follower最初与leader建立连接时,它们之间会传输相当多的数据,尤其是follower的数据落后leader很多。initLimit配置follower与leader之间建立连接后进行同步的最长时间。

syncLimit

  • 配置follower和leader之间发送消息,请求和应答的最大时间长度。

server.id=host:port1:port2

server.id 其中id为一个数字,表示zk进程的id,这个id也是data目录下myid文件的内容

host 是该zk进程所在的IP地址

port1 表示follower和leader交换消息所使用的端口

port2 表示选举leader所使用的端口

4.创建myid 文件

在data里会放置一个myid文件,里面就一个数字,用来唯一标识这个服务。这个id是很重要的,一定要保证整个集群中唯一

ZooKeeper会根据这个id来取出server.x上的配置。比如当前id为1,则对应着zoo.cfg里的server.1的配置

1
$ echo "1" > /opt/zookeeper-3.4.9/data/myid

这样一台node1机器就配置完了

5.复制集群配置

在集群node1 上执行,复制配置好的zookeeper到其他两台主机上

1
$ for a in {2..3} ; do scp -r /opt/zookeeper-3.4.9/ node$a:/opt/zookeeper-3.4.9 ; done

在集群node1 上执行 ,批量修改myid 文件

1
$ for a in {1..3} ; do ssh node$a "source /etc/profile; echo $a > /opt/zookeeper-3.4.9/data/myid" ; done

集群操作

在集群任意一台机器上执行

启动集群

1
$ for a in {1..3} ; do ssh node$a "source /etc/profile; /opt/zookeeper-3.4.9/bin/zkServer.sh start" ; done

响应

1
2
3
4
5
6
7
8
9
ZooKeeper JMX enabled by default
Using config: /opt/zookeeper-3.4.9/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
ZooKeeper JMX enabled by default
Using config: /opt/zookeeper-3.4.9/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
ZooKeeper JMX enabled by default
Using config: /opt/zookeeper-3.4.9/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED

连接集群

1
$ /opt/zookeeper-3.4.9/bin/zkCli.sh -server node1:2181,node2:2181,node3:2181

响应

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
Connecting to node1:2181,node2:2181,node3:2181
2017-08-23 11:08:10,323 [myid:] - INFO [main:Environment@100] - Client environment:zookeeper.version=3.4.9-1757313, built on 08/23/2016 06:50 GMT
2017-08-23 11:08:10,328 [myid:] - INFO [main:Environment@100] - Client environment:host.name=node1
2017-08-23 11:08:10,329 [myid:] - INFO [main:Environment@100] - Client environment:java.version=1.8.0_144
2017-08-23 11:08:10,331 [myid:] - INFO [main:Environment@100] - Client environment:java.vendor=Oracle Corporation
2017-08-23 11:08:10,331 [myid:] - INFO [main:Environment@100] - Client environment:java.home=/usr/lib/jvm/jre
2017-08-23 11:08:10,331 [myid:] - INFO [main:Environment@100] - Client environment:java.class.path=/opt/zookeeper-3.4.9/bin/../build/classes:/opt/zookeeper-3.4.9/bin/../build/lib/*.jar:/opt/zookeeper-3.4.9/bin/../lib/slf4j-log4j12-1.6.1.jar:/opt/zookeeper-3.4.9/bin/../lib/slf4j-api-1.6.1.jar:/opt/zookeeper-3.4.9/bin/../lib/netty-3.10.5.Final.jar:/opt/zookeeper-3.4.9/bin/../lib/log4j-1.2.16.jar:/opt/zookeeper-3.4.9/bin/../lib/jline-0.9.94.jar:/opt/zookeeper-3.4.9/bin/../zookeeper-3.4.9.jar:/opt/zookeeper-3.4.9/bin/../src/java/lib/*.jar:/opt/zookeeper-3.4.9/bin/../conf:.:/lib/jvm/lib:/lib/jvm/jre/lib
2017-08-23 11:08:10,331 [myid:] - INFO [main:Environment@100] - Client environment:java.library.path=/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
2017-08-23 11:08:10,331 [myid:] - INFO [main:Environment@100] - Client environment:java.io.tmpdir=/tmp
2017-08-23 11:08:10,331 [myid:] - INFO [main:Environment@100] - Client environment:java.compiler=<NA>
2017-08-23 11:08:10,331 [myid:] - INFO [main:Environment@100] - Client environment:os.name=Linux
2017-08-23 11:08:10,331 [myid:] - INFO [main:Environment@100] - Client environment:os.arch=amd64
2017-08-23 11:08:10,331 [myid:] - INFO [main:Environment@100] - Client environment:os.version=3.10.0-514.26.2.el7.x86_64
2017-08-23 11:08:10,331 [myid:] - INFO [main:Environment@100] - Client environment:user.name=root
2017-08-23 11:08:10,332 [myid:] - INFO [main:Environment@100] - Client environment:user.home=/root
2017-08-23 11:08:10,332 [myid:] - INFO [main:Environment@100] - Client environment:user.dir=/root
2017-08-23 11:08:10,333 [myid:] - INFO [main:ZooKeeper@438] - Initiating client connection, connectString=node1:2181,node2:2181,node3:2181 sessionTimeout=30000 watcher=org.apache.zookeeper.ZooKeeperMain$MyWatcher@506c589e
2017-08-23 11:08:10,361 [myid:] - INFO [main-SendThread(node3:2181):ClientCnxn$SendThread@1032] - Opening socket connection to server node3/192.168.252.123:2181. Will not attempt to authenticate using SASL (unknown error)
Welcome to ZooKeeper!
JLine support is enabled
2017-08-23 11:08:10,474 [myid:] - INFO [main-SendThread(node3:2181):ClientCnxn$SendThread@876] - Socket connection established to node3/192.168.252.123:2181, initiating session
[zk: node1:2181,node2:2181,node3:2181(CONNECTING) 0] 2017-08-23 11:08:10,535 [myid:] - INFO [main-SendThread(node3:2181):ClientCnxn$SendThread@1299] - Session establishment complete on server node3/192.168.252.123:2181, sessionid = 0x35e0d0716340000, negotiated timeout = 30000

WATCHER::

WatchedEvent state:SyncConnected type:None path:null

[zk: node1:2181,node2:2181,node3:2181(CONNECTED) 0]

从日志可以看出客户端成功连接的是node3 连接上哪台机器的zk进程是随机的

1
2
3
4

2017-08-23 11:08:10,361 [myid:] - INFO [main-SendThread(node3:2181):ClientCnxn$SendThread@1032] - Opening socket connection to server node3/192.168.252.123:2181. Will not attempt to authenticate using SASL (unknown error)
Welcome to ZooKeeper!
JLine support is enabled

集群状态

1
$ for a in {1..3} ; do ssh node$a "source /etc/profile; /opt/zookeeper-3.4.9/bin/zkServer.sh status" ; done

响应

1
2
3
4
5
6
7
8
9
ZooKeeper JMX enabled by default
Using config: /opt/zookeeper-3.4.9/bin/../conf/zoo.cfg
Mode: follower
ZooKeeper JMX enabled by default
Using config: /opt/zookeeper-3.4.9/bin/../conf/zoo.cfg
Mode: leader
ZooKeeper JMX enabled by default
Using config: /opt/zookeeper-3.4.9/bin/../conf/zoo.cfg
Mode: follower

通过日志我可以看到 node2 leader (ps 是老大),其他 node1 ,node2 follower (ps 都是小弟)

Leader 怎么选举的可以参考《Zookeeper的Leader选举》

停止集群

1
$ for a in {1..3} ; do ssh node$a "source /etc/profile; /opt/zookeeper-3.4.9/bin/zkServer.sh stop" ; done

响应

1
2
3
4
5
6
7
8
9
ZooKeeper JMX enabled by default
Using config: /opt/zookeeper-3.4.9/bin/../conf/zoo.cfg
Stopping zookeeper ... STOPPED
ZooKeeper JMX enabled by default
Using config: /opt/zookeeper-3.4.9/bin/../conf/zoo.cfg
Stopping zookeeper ... STOPPED
ZooKeeper JMX enabled by default
Using config: /opt/zookeeper-3.4.9/bin/../conf/zoo.cfg
Stopping zookeeper ... STOPPED

留言與分享

Hive-2.3.0 快速搭建与使用

分類 database, hadoop, Hive

Hive 简介

Hive 是一个基于 hadoop 的开源数据仓库工具,用于存储和处理海量结构化数据。它把海量数据存储于 hadoop 文件系统,而不是数据库,但提供了一套类数据库的数据存储和处理机制,并采用 HQL (类 SQL )语言对这些数据进行自动化管理和处理。我们可以把 Hive 中海量结构化数据看成一个个的表,而实际上这些数据是分布式存储在 HDFS 中的。 Hive 经过对语句进行解析和转换,最终生成一系列基于 hadoop 的 map/reduce 任务,通过执行这些任务完成数据处理。

Hive 诞生于 facebook 的日志分析需求,面对海量的结构化数据, Hive 以较低的成本完成了以往需要大规模数据库才能完成的任务,并且学习门槛相对较低,应用开发灵活而高效。

Hive 自 2009.4.29 发布第一个官方稳定版 0.3.0 至今,不过一年的时间,正在慢慢完善,网上能找到的相关资料相当少,尤其中文资料更少,本文结合业务对 Hive 的应用做了一些探索,并把这些经验做一个总结,所谓前车之鉴,希望读者能少走一些弯路。

准备工作

环境

1
2
3
4
5
6
7
8
9
JDK:1.8  
Hadoop Release:2.7.4
centos:7.3

node1(master) 主机: 192.168.252.121
node2(slave1) 从机: 192.168.252.122
node3(slave2) 从机: 192.168.252.123

node4(mysql) 从机: 192.168.252.124

依赖环境

安装**Apache Hive**前提是要先安装hadoop集群,并且hive只需要在hadoop的namenode节点集群里安装即可(需要在有的namenode上安装),可以不在datanode节点的机器上安装。还需要说明的是,虽然修改配置文件并不需要把hadoop运行起来,但是本文中用到了hadoop的hdfs命令,在执行这些命令时你必须确保hadoop是正在运行着的,而且启动hive的前提也需要hadoop在正常运行着,所以建议先把hadoop集群启动起来。

安装**MySQL** 用于存储 Hive 的元数据(也可以用 Hive 自带的嵌入式数据库 Derby,但是 Hive 的生产环境一般不用 Derby),这里只需要安装 MySQL 单机版即可,如果想保证高可用的化,也可以部署 MySQL 主从模式;

Hadoop

Hadoop-2.7.4 集群快速搭建

MySQL 随意任选其一

CentOs7.3 安装 MySQL 5.7.19 二进制版本

搭建 MySQL 5.7.19 主从复制,以及复制实现细节分析

安装

下载解压

1
2
3
4
5
su hadoop
cd /home/hadoop/
wget https://mirrors.tuna.tsinghua.edu.cn/apache/hive/hive-2.3.0/apache-hive-2.3.0-bin.tar.gz
tar -zxvf apache-hive-2.3.0-bin.tar.gz
mv apache-hive-2.3.0-bin hive-2.3.0

环境变量

如果是对所有的用户都生效就修改vi /etc/profile 文件
如果只针对当前用户生效就修改 vi ~/.bahsrc 文件

1
sudo vi /etc/profile
1
2
3
#hive
export PATH=${HIVE_HOME}/bin:$PATH
export HIVE_HOME=/home/hadoop/hive-2.3.0/

使环境变量生效,运行 source /etc/profile使/etc/profile文件生效

Hive 配置 Hadoop HDFS

复制 hive-site.xml

1
2
cd /home/hadoop/hive-2.3.0/conf
cp hive-default.xml.template hive-site.xml

新建 hdfs 目录

使用 hadoop 新建 hdfs 目录,因为在 hive-site.xml 中有默认如下配置:

1
2
3
4
5
6
<property>
<name>hive.metastore.warehouse.dir</name>
<value>/user/hive/warehouse</value>
<description>location of default database for the warehouse</description>
</property>
<property>

进入 hadoop 安装目录 执行hadoop命令新建/user/hive/warehouse目录,并授权,用于存储文件

1
2
3
4
5
6
7
8
cd /home/hadoop/hadoop-2.7.4

bin/hadoop fs -mkdir -p /user/hive/warehouse
bin/hadoop fs -mkdir -p /user/hive/tmp
bin/hadoop fs -mkdir -p /user/hive/log
bin/hadoop fs -chmod -R 777 /user/hive/warehouse
bin/hadoop fs -chmod -R 777 /user/hive/tmp
bin/hadoop fs -chmod -R 777 /user/hive/log

用以下命令检查目录是否创建成功

1
bin/hadoop fs -ls /user/hive

修改 hive-site.xml

搜索hive.exec.scratchdir,将该name对应的value修改为/user/hive/tmp

1
2
3
4
<property>  
<name>hive.exec.scratchdir</name>
<value>/user/hive/tmp</value>
</property>

搜索hive.querylog.location,将该name对应的value修改为/user/hive/log/hadoop

1
2
3
4
5
<property>
<name>hive.querylog.location</name>
<value>/user/hive/log/hadoop</value>
<description>Location of Hive run time structured log file</description>
</property>

搜索javax.jdo.option.connectionURL,将该name对应的value修改为MySQL的地址

1
2
3
4
5
6
7
8
9
<property>
<name>javax.jdo.option.ConnectionURL</name>
<value>jdbc:mysql://192.168.252.124:3306/hive?createDatabaseIfNotExist=true</value>
<description>
JDBC connect string for a JDBC metastore.
To use SSL to encrypt/authenticate the connection, provide database-specific SSL flag in the connection URL.
For example, jdbc:postgresql://myhost/db?ssl=true for postgres database.
</description>
</property>

搜索javax.jdo.option.ConnectionDriverName,将该name对应的value修改为MySQL驱动类路径

1
2
3
4
5
<property>
<name>javax.jdo.option.ConnectionDriverName</name>
<value>com.mysql.jdbc.Driver</value>
<description>Driver class name for a JDBC metastore</description>
</property>

搜索javax.jdo.option.ConnectionUserName,将对应的value修改为MySQL数据库登录名

1
2
3
4
5
<property>
<name>javax.jdo.option.ConnectionUserName</name>
<value>root</value>
<description>Username to use against metastore database</description>
</property>

搜索javax.jdo.option.ConnectionPassword,将对应的value修改为MySQL数据库的登录密码

1
2
3
4
5
<property>
<name>javax.jdo.option.ConnectionPassword</name>
<value>mima</value>
<description>password to use against metastore database</description>
</property>

创建 tmp 文件

1
mkdir /home/hadoop/hive-2.3.0/tmp

并在 hive-site.xml 中修改

{system:java.io.tmpdir} 改成 /home/hadoop/hive-2.3.0/tmp

{system:user.name} 改成 {user.name}

新建 hive-env.sh

1
2
3
4
5
6
7
cp hive-env.sh.template hive-env.sh

vi hive-env.sh

HADOOP_HOME=/home/hadoop/hadoop-2.7.4/
export HIVE_CONF_DIR=/home/hadoop/hive-2.3.0/conf
export HIVE_AUX_JARS_PATH=/home/hadoop/hive-2.3.0/lib

下载 mysql 驱动包

1
2
3
cd /home/hadoop/hive-2.3.0/lib

wget http://central.maven.org/maven2/mysql/mysql-connector-java/5.1.38/mysql-connector-java-5.1.38.jar

初始化 mysql

MySQL数据库进行初始化

首先确保 mysql 中已经创建 hive

1
2
cd /home/hadoop/hive-2.3.0/bin
./schematool -initSchema -dbType mysql

如果看到如下,表示初始化成功

1
2
3
4
Starting metastore schema initialization to 2.3.0
Initialization script hive-schema-2.3.0.mysql.sql
Initialization script completed
schemaTool completed

查看 mysql 数据库

1
/usr/local/mysql/bin/mysql -uroot -p
1
2
3
4
5
6
7
8
9
10
11
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| hive |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.00 sec)
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
mysql> use hive;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+---------------------------+
| Tables_in_hive |
+---------------------------+
| AUX_TABLE |
| BUCKETING_COLS |
| CDS |
| COLUMNS_V2 |
| COMPACTION_QUEUE |
| COMPLETED_COMPACTIONS |
| COMPLETED_TXN_COMPONENTS |
| DATABASE_PARAMS |
| DBS |
| DB_PRIVS |
| DELEGATION_TOKENS |
| FUNCS |
| FUNC_RU |
| GLOBAL_PRIVS |
| HIVE_LOCKS |
| IDXS |
| INDEX_PARAMS |
| KEY_CONSTRAINTS |
| MASTER_KEYS |
| NEXT_COMPACTION_QUEUE_ID |
| NEXT_LOCK_ID |
| NEXT_TXN_ID |
| NOTIFICATION_LOG |
| NOTIFICATION_SEQUENCE |
| NUCLEUS_TABLES |
| PARTITIONS |
| PARTITION_EVENTS |
| PARTITION_KEYS |
| PARTITION_KEY_VALS |
| PARTITION_PARAMS |
| PART_COL_PRIVS |
| PART_COL_STATS |
| PART_PRIVS |
| ROLES |
| ROLE_MAP |
| SDS |
| SD_PARAMS |
| SEQUENCE_TABLE |
| SERDES |
| SERDE_PARAMS |
| SKEWED_COL_NAMES |
| SKEWED_COL_VALUE_LOC_MAP |
| SKEWED_STRING_LIST |
| SKEWED_STRING_LIST_VALUES |
| SKEWED_VALUES |
| SORT_COLS |
| TABLE_PARAMS |
| TAB_COL_STATS |
| TBLS |
| TBL_COL_PRIVS |
| TBL_PRIVS |
| TXNS |
| TXN_COMPONENTS |
| TYPES |
| TYPE_FIELDS |
| VERSION |
| WRITE_SET |
+---------------------------+
57 rows in set (0.00 sec)

启动 Hive

简单测试

启动Hive

1
2
3
cd /home/hadoop/hive-2.3.0/bin

./hive

创建 hive 库

1
2
3
hive>  create database ymq;
OK
Time taken: 0.742 seconds

选择库

1
2
3
hive> use ymq;
OK
Time taken: 0.036 seconds

创建表

1
2
3
hive> create table test (mykey string,myval string);
OK
Time taken: 0.569 seconds

插入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
hive> insert into test values("1","www.ymq.io");

WARNING: Hive-on-MR is deprecated in Hive 2 and may not be available in the future versions. Consider using a different execution engine (i.e. spark, tez) or using Hive 1.X releases.
Query ID = hadoop_20170922011126_abadfa44-8ebe-4ffc-9615-4241707b3c03
Total jobs = 3
Launching Job 1 out of 3
Number of reduce tasks is set to 0 since there's no reduce operator
Starting Job = job_1506006892375_0001, Tracking URL = http://node1:8088/proxy/application_1506006892375_0001/
Kill Command = /home/hadoop/hadoop-2.7.4//bin/hadoop job -kill job_1506006892375_0001
Hadoop job information for Stage-1: number of mappers: 1; number of reducers: 0
2017-09-22 01:12:12,763 Stage-1 map = 0%, reduce = 0%
2017-09-22 01:12:20,751 Stage-1 map = 100%, reduce = 0%, Cumulative CPU 1.24 sec
MapReduce Total cumulative CPU time: 1 seconds 240 msec
Ended Job = job_1506006892375_0001
Stage-4 is selected by condition resolver.
Stage-3 is filtered out by condition resolver.
Stage-5 is filtered out by condition resolver.
Moving data to directory hdfs://node1:9000/user/hive/warehouse/ymq.db/test/.hive-staging_hive_2017-09-22_01-11-26_242_8022847052615616955-1/-ext-10000
Loading data to table ymq.test
MapReduce Jobs Launched:
Stage-Stage-1: Map: 1 Cumulative CPU: 1.24 sec HDFS Read: 4056 HDFS Write: 77 SUCCESS
Total MapReduce CPU Time Spent: 1 seconds 240 msec
OK
Time taken: 56.642 seconds

查询数据

1
2
3
4
hive> select * from test;
OK
1 www.ymq.io
Time taken: 0.253 seconds, Fetched: 1 row(s)

页面数据

在界面上查看刚刚写入的hdfs数据

图片描述

图片描述

留言與分享

CentOs7.3 hadoop ssh 免密登录

分類 database, hadoop

环境

三台虚拟机(IP):

  • 192.168.252.121
  • 192.168.252.122
  • 192.168.252.123

1.修改主机名

修改三台主机名,以此类推,node1,node3,node3

命令格式

1
hostnamectl set-hostname <hostname>
1
sudo hostnamectl set-hostname node1

剩下的虚拟机依次修改hostnamectl set-hostname[1-3]

重启操作系统

1
$ reboot

2.修改映射关系

1.在 node1 的 /etc/hosts 文件下添加如下内容

1
2
su hadoop
vi /etc/hosts

2.查看修改后的/etc/hosts 文件内容

1
2
3
4
5
6
7
$ cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# 以下是添加的
192.168.252.121 node1
192.168.252.122 node2
192.168.252.123 node3

2.将集群node1 上的文件hosts文件 通过 scp 命令复制发送到集群的每一个节点

1
for a in {1..3} ; do sudo scp /etc/hosts hadoop@node$a:/etc/hosts ; done

3.检查是否集群每一个节点的 hosts 文件都已经修改过来了

1
for a in {1..3} ; do sudo ssh hadoop@node$a cat /etc/hosts ; done

3.启动 ssh 无密登录

1.在集群node1的 /etc/ssh/sshd_config 文件去掉以下选项的注释

1
sudo vi /etc/ssh/sshd_config 
1
2
RSAAuthentication yes      #开启私钥验证
PubkeyAuthentication yes #开启公钥验证

2.将集群node1 修改后的 /etc/ssh/sshd_config 通过 scp 命令复制发送到集群的每一个节点

1
for a in {1..3} ; do  sudo scp /etc/ssh/sshd_config hadoop@node$a:/etc/ssh/sshd_config ; done

4.生成公钥、私钥

1.在集群的每一个节点节点输入命令 ssh-keygen -t rsa -P '',生成 key,一律回车

1
ssh-keygen -t rsa -P ''
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Generating public/private rsa key pair.
Enter file in which to save the key (/home/hadoop/.ssh/id_rsa):
Created directory '/home/hadoop/.ssh'.
Your identification has been saved in /home/hadoop/.ssh/id_rsa.
Your public key has been saved in /home/hadoop/.ssh/id_rsa.pub.
The key fingerprint is:
aa:be:0e:46:9a:e8:d5:dc:79:ea:5a:b8:9b:08:e2:dd hadoop@node2
The key's randomart image is:
+--[ RSA 2048]----+
| |
| |
| |
| |
| . S |
|.+ o o.. |
|=.o. +.+ . |
|+.+.o.+ o |
| o ==E+o |
+-----------------+

2.在集群的node1 节点输入命令

将集群每一个节点的公钥id_rsa.pub放入到自己的认证文件中authorized_keys;

1
for a in {1..3}; do sudo ssh hadoop@node$a cat /home/hadoop/.ssh/id_rsa.pub >> /home/hadoop/.ssh/authorized_keys; done

3.在集群的node1 节点输入命令

将自己的认证文件 authorized_keys 通过 scp 命令复制发送到每一个节点上去: /home/hadoop/.ssh/authorized_keys`

1
for a in {1..3}; do sudo scp /home/hadoop/.ssh/authorized_keys hadoop@node$a:/home/hadoop/.ssh/authorized_keys ; done

4.非ROOT 用户需赋权限

1
2
3
chmod 700 /home/hadoop/.ssh/
chmod 700 /home/hadoop/
chmod 600 /home/hadoop/.ssh/authorized_keys

5.在集群的每一个节点节点输入命令

接重启ssh服务

1
sudo systemctl restart sshd.service

6.验证 ssh 无密登录

开一个其他窗口测试下能否免密登陆

例如:在node3

1
ssh hadoop@node2

exit 退出

1
2
3
[hadoop@node1 ~]# exit
logout
Connection to node1 closed.

注意:开新的其他窗口测试下能否免密登陆,把当前窗口都关了

留言與分享

HBase 深入浅出

分類 database, hadoop, HBase

HBase 深入浅出

HBase 在大数据生态圈中的位置

提到大数据的存储,大多数人首先联想到的是 Hadoop 和 Hadoop 中的 HDFS 模块。大家熟知的 Spark、以及 Hadoop 的 MapReduce,可以理解为一种计算框架。而 HDFS,我们可以认为是为计算框架服务的存储层。因此不管是 Spark 还是 MapReduce,都需要使用 HDFS 作为默认的持久化存储层。那么 HBase 又是什么,可以用在哪里,解决什么样的问题?简单地,我们可以认为 HBase 是一种类似于数据库的存储层,也就是说 HBase 适用于结构化的存储。并且 HBase 是一种列式的分布式数据库,是由当年的 Google 公布的 BigTable 的论文而生。不过这里也要注意 HBase 底层依旧依赖 HDFS 来作为其物理存储,这点类似于 Hive。

可能有的读者会好奇 HBase 于 Hive 的区别,我们简单的梳理一下 Hive 和 HBase 的应用场景:

Hive 适合用来对一段时间内的数据进行分析查询,例如,用来计算趋势或者网站的日志。Hive 不应该用来进行实时的查询(Hive 的设计目的,也不是支持实时的查询)。因为它需要很长时间才可以返回结果;HBase 则非常适合用来进行大数据的实时查询,例如 Facebook 用 HBase 进行消息和实时的分析。对于 Hive 和 HBase 的部署来说,也有一些区别,Hive 一般只要有 Hadoop 便可以工作。而 HBase 则还需要 Zookeeper 的帮助(Zookeeper,是一个用来进行分布式协调的服务,这些服务包括配置服务,维护元信息和命名空间服务)。再而,HBase 本身只提供了 Java 的 API 接口,并不直接支持 SQL 的语句查询,而 Hive 则可以直接使用 HQL(一种类 SQL 语言)。如果想要在 HBase 上使用 SQL,则需要联合使用 Apache Phonenix,或者联合使用 Hive 和 HBase。但是和上面提到的一样,如果集成使用 Hive 查询 HBase 的数据,则无法绕过 MapReduce,那么实时性还是有一定的损失。Phoenix 加 HBase 的组合则不经过 MapReduce 的框架,因此当使用 Phoneix 加 HBase 的组成,实时性上会优于 Hive 加 HBase 的组合,我们后续也会示例性介绍如何使用两者。最后我们再提下 Hive 和 HBase 所使用的存储层,默认情况下 Hive 和 HBase 的存储层都是 HDFS。但是 HBase 在一些特殊的情况下也可以直接使用本机的文件系统。例如 Ambari 中的 AMS 服务直接在本地文件系统上运行 HBase。

HBase 与传统关系数据库的区别

首先让我们了解下什么是 ACID。ACID 是指数据库事务正确执行的四个基本要素的缩写,其包含:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)以及持久性(Durability)。对于一个支持事务(Transaction)的数据库系统,必需要具有这四种特性,否则在事务过程(Transaction Processing)当中无法保证数据的正确性,交易过程极可能达不到交易方的要求。下面,我们就简单的介绍下这 4 个特性的含义。

  • 原子性(Atomicity)是指一个事务要么全部执行,要么全部不执行。换句话说,一个事务不可能只执行了一半就停止了。比如一个事情分为两步完成才可以完成,那么这两步必须同时完成,要么一步也不执行,绝不会停留在某一个中间状态。如果事物执行过程中,发生错误,系统会将事物的状态回滚到最开始的状态。
  • 一致性(Consistency)是指事务的运行并不改变数据库中数据的一致性。也就是说,无论并发事务有多少个,但是必须保证数据从一个一致性的状态转换到另一个一致性的状态。例如有 a、b 两个账户,分别都是 10。当 a 增加 5 时,b 也会随着改变,总值 20 是不会改变的。
  • 隔离性(Isolation)是指两个以上的事务不会出现交错执行的状态。因为这样可能会导致数据不一致。如果有多个事务,运行在相同的时间内,执行相同的功能,事务的隔离性将确保每一事务在系统中认为只有该事务在使用系统。这种属性有时称为串行化,为了防止事务操作间的混淆,必须串行化或序列化请求,使得在同一时间仅有一个请求用于同一数据。
  • 持久性(Durability)指事务执行成功以后,该事务对数据库所作的更改便是持久的保存在数据库之中,不会无缘无故的回滚。

在具体的介绍 HBase 之前,我们先简单对比下 HBase 与传统关系数据库的(RDBMS,全称为 Relational Database Management System)区别。如表 1 所示。

表 1. HBase 与 RDBMS 的区别

HBase RDBMS
硬件架构 类似于 Hadoop 的分布式集群,硬件成本低廉 传统的多核系统,硬件成本昂贵
容错性 由软件架构实现,由于由多个节点组成,所以不担心一点或几点宕机 一般需要额外硬件设备实现 HA 机制
数据库大小 PB GB、TB
数据排布方式 稀疏的、分布的多维的 Map 以行和列组织
数据类型 Bytes 丰富的数据类型
事物支持 ACID 只支持单个 Row 级别 全面的 ACID 支持,对 Row 和表
查询语言 只支持 Java API (除非与其他框架一起使用,如 Phoenix、Hive) SQL
索引 只支持 Row-key,除非与其他技术一起应用,如 Phoenix、Hive 支持
吞吐量 百万查询/每秒 数千查询/每秒

理解了上面的表格之后,我们在看看数据是如何在 HBase 以及 RDBMS 中排布的。首先,数据在 RDBMS 的排布大致如表 2。

表 2. 数据在 RDBMS 中的排布示例

ID 密码 时间戳
1 111 20160719
2 222 20160720

那么数据在 HBase 中的排布会是什么样子呢?如表 3 所示(这只是逻辑上的排布)。

表 3. 数据在 HBase 中的排布(逻辑上)

Row-Key Value(CF、Qualifier、Version)
1 info{‘姓’: ‘张’,‘名’:‘三’}
pwd{‘密码’: ‘111’}
2 Info{‘姓’: ‘李’,‘名’:‘四’}
pwd{‘密码’: ‘222’}

从上面示例表中,我们可以看出,在 HBase 中首先会有 Column Family 的概念,简称为 CF。CF 一般用于将相关的列(Column)组合起来。在物理上 HBase 其实是按 CF 存储的,只是按照 Row-key 将相关 CF 中的列关联起来。物理上的数据排布大致可以如表 4 所示。

表 4. 数据在 HBase 中的排布

Row-Key CF:Column-Key 时间戳 Cell Value
1 info:fn 123456789
1 info:ln 123456789
2 info:fn 123456789
2 info:ln 123456789

我们已经提到 HBase 是按照 CF 来存储数据的。在表 3 中,我们看到了两个 CF,分别是 info 和 pwd。info 存储着姓名相关列的数据,而 pwd 则是密码相关的数据。上表便是 info 这个 CF 存储在 Hbase 中的数据排布。Pwd 的数据排布是类似的。上表中的 fn 和 ln 称之为 Column-key 或者 Qulifimer。在 Hbase 中,Row-key 加上 CF 加上 Qulifier 再加上一个时间戳才可以定位到一个单元格数据(Hbase 中每个单元格默认有 3 个时间戳的版本数据)。初学者,在一开始接触这些概念是很容易混淆。其实不管是 CF 还是 Qulifier 都是客户定义出来的。也就是说在 HBase 中创建表格时,就需要指定表格的 CF、Row-key 以及 Qulifier。我们会在后续的介绍中,尝试指定这些相关的概念,以便加深理解。这里我们先通过下图理解下 HBase 中,逻辑上的数据排布与物理上的数据排布之间的关系。

图 1. Hbase 中逻辑上数据的排布与物理上排布的关联

图片描述

从上图我们看到 Row1 到 Row5 的数据分布在两个 CF 中,并且每个 CF 对应一个 HFile。并且逻辑上每一行中的一个单元格数据,对应于 HFile 中的一行,然后当用户按照 Row-key 查询数据的时候,HBase 会遍历两个 HFile,通过相同的 Row-Key 标识,将相关的单元格组织成行返回,这样便有了逻辑上的行数据。讲解到这,我们就大致了解 HBase 中的数据排布格式,以及与 RDBMS 的一些区别。

对于 RDBMS 来说,一般都是以 SQL 作为为主要的访问方式。而 HBase 是一种"NoSQL"数据库。"NoSQL"是一个通用词表示该数据库并

是 RDBMS 。现在的市面上有许多种 NoSQL 数据库,如 BerkeleyDB 是本地 NoSQL 数据库的例子, HBase 则为大型分布式 NoSql 数据库。从技术上来说,Hbase 更像是"数据存储"而非"数据库"(HBase 和 HDFS 都属于大数据的存储层)。因此,HBase 缺少很多 RDBMS 特性,如列类型,二级索引,触发器和高级查询语言等。然而, HBase 也具有许多其他特征同时支持线性化和模块化扩充。最明显的方式,我们可以通过增加 Region Server 的数量扩展 HBase。并且 HBase 可以放在普通的服务器中,例如将集群从 5 个扩充到 10 个 Region Server 时,存储空间和处理容量都可以同时翻倍。当然 RDBMS 也能很好的扩充,但仅对一个点,尤其是对一个单独数据库服务器而言,为了更好的性能,往往需要特殊的硬件和存储设备(往往价格也非常昂贵)。

HBase 相关模块以及 HBase 表格特性

在这里,让我们了解下 HBase 都有哪些模块,以及大致的工作流程。前面我们提到过 HBase 也是构建于 HDFS 之上,这是正确的,但也不是完全正确。HBase 其实也支持直接在本地文件系统之上运行,不过这样的 HBase 只能运行在一台机器上,那么对于分布式大数据的环境是没有意义的(这也是所谓的 HBase 的单机模式)。一般只用于测试或者验证某一个 HBase 的功能,后面我们在详细的介绍 HBase 的几种运行模式。这里我们只要记得在分布式的生产环境中,HBase 需要运行在 HDFS 之上,以 HDFS 作为其基础的存储设施。HBase 上层提供了访问的数据的 Java API 层,供应用访问存储在 HBase 的数据。在 HBase 的集群中主要由 Master 和 Region Server 组成,以及 Zookeeper,具体模块如下图所示。

图 2. HBase 的相关模块

图片描述

接下来,我们简单的一一介绍下 HBase 中相关模块的作用。

  • Master

    HBase Master 用于协调多个 Region Server,侦测各个 Region Server 之间的状态,并平衡 Region Server 之间的负载。HBase Master 还有一个职责就是负责分配 Region 给 Region Server。HBase 允许多个 Master 节点共存,但是这需要 Zookeeper 的帮助。不过当多个 Master 节点共存时,只有一个 Master 是提供服务的,其他的 Master 节点处于待命的状态。当正在工作的 Master 节点宕机时,其他的 Master 则会接管 HBase 的集群。

  • Region Server

    对于一个 Region Server 而言,其包括了多个 Region。Region Server 的作用只是管理表格,以及实现读写操作。Client 直接连接 Region Server,并通信获取 HBase 中的数据。对于 Region 而言,则是真实存放 HBase 数据的地方,也就说 Region 是 HBase 可用性和分布式的基本单位。如果当一个表格很大,并由多个 CF 组成时,那么表的数据将存放在多个 Region 之间,并且在每个 Region 中会关联多个存储的单元(Store)。

  • Zookeeper

    对于 HBase 而言,Zookeeper 的作用是至关重要的。首先 Zookeeper 是作为 HBase Master 的 HA 解决方案。也就是说,是 Zookeeper 保证了至少有一个 HBase Master 处于运行状态。并且 Zookeeper 负责 Region 和 Region Server 的注册。其实 Zookeeper 发展到目前为止,已经成为了分布式大数据框架中容错性的标准框架。不光是 HBase,几乎所有的分布式大数据相关的开源框架,都依赖于 Zookeeper 实现 HA。

一个完整分布式的 HBase 的工作原理示意图如下:

图 3. HBase 的工作原理

图片描述

在上面的图中,我们需要注意几个我们之前没有提到的概念:Store、MemStore、StoreFile 以及 HFile。带着这几个新的概念,我们完整的梳理下整个 HBase 的工作流程。

首先我们需要知道 HBase 的集群是通过 Zookeeper 来进行机器之前的协调,也就是说 HBase Master 与 Region Server 之间的关系是依赖 Zookeeper 来维护。当一个 Client 需要访问 HBase 集群时,Client 需要先和 Zookeeper 来通信,然后才会找到对应的 Region Server。每一个 Region Server 管理着很多个 Region。对于 HBase 来说,Region 是 HBase 并行化的基本单元。因此,数据也都存储在 Region 中。这里我们需要特别注意,每一个 Region 都只存储一个 Column Family 的数据,并且是该 CF 中的一段(按 Row 的区间分成多个 Region)。Region 所能存储的数据大小是有上限的,当达到该上限时(Threshold),Region 会进行分裂,数据也会分裂到多个 Region 中,这样便可以提高数据的并行化,以及提高数据的容量。每个 Region 包含着多个 Store 对象。每个 Store 包含一个 MemStore,和一个或多个 HFile。MemStore 便是数据在内存中的实体,并且一般都是有序的。当数据向 Region 写入的时候,会先写入 MemStore。当 MemStore 中的数据需要向底层文件系统倾倒(Dump)时(例如 MemStore 中的数据体积到达 MemStore 配置的最大值),Store 便会创建 StoreFile,而 StoreFile 就是对 HFile 一层封装。所以 MemStore 中的数据会最终写入到 HFile 中,也就是磁盘 IO。由于 HBase 底层依靠 HDFS,因此 HFile 都存储在 HDFS 之中。这便是整个 HBase 工作的原理简述。

我们了解了 HBase 大致的工作原理,那么在 HBase 的工作过程中,如何保证数据的可靠性呢?带着这个问题,我们理解下 HLog 的作用。HBase 中的 HLog 机制是 WAL 的一种实现,而 WAL(一般翻译为预写日志)是事务机制中常见的一致性的实现方式。每个 Region Server 中都会有一个 HLog 的实例,Region Server 会将更新操作(如 Put,Delete)先记录到 WAL(也就是 HLog)中,然后将其写入到 Store 的 MemStore,最终 MemStore 会将数据写入到持久化的 HFile 中(MemStore 到达配置的内存阀值)。这样就保证了 HBase 的写的可靠性。如果没有 WAL,当 Region Server 宕掉的时候,MemStore 还没有写入到 HFile,或者 StoreFile 还没有保存,数据就会丢失。或许有的读者会担心 HFile 本身会不会丢失,这是由 HDFS 来保证的。在 HDFS 中的数据默认会有 3 份。因此这里并不考虑 HFile 本身的可靠性。

前面,我们很多次提到了 HFile,也就是 HBase 持久化的存储文件。也许有的读者还不能完全理解 HFile,这里我们便详细的看看 HFile 的结构,如下图。

图 4. HFile 的结构

图片描述

从图中我们可以看到 HFile 由很多个数据块(Block)组成,并且有一个固定的结尾块。其中的数据块是由一个 Header 和多个 Key-Value 的键值对组成。在结尾的数据块中包含了数据相关的索引信息,系统也是通过结尾的索引信息找到 HFile 中的数据。HFile 中的数据块大小默认为 64KB。如果访问 HBase 数据库的场景多为有序的访问,那么建议将该值设置的大一些。如果场景多为随机访问,那么建议将该值设置的小一些。一般情况下,通过调整该值可以提高 HBase 的性能。

如果要用很短的一句话总结 HBase,我们可以认为 HBase 就是一个有序的多维 Map,其中每一个 Row-key 映射了许多数据,这些数据存储在 CF 中的 Column。我们可以用下图来表示这句话。

图 5. HBase 的数据映射关系

图片描述

HBase 的使用建议

之前我介绍了很多 HBase 与 RDBMS 的区别,以及一些优势的地方。那么什么时候最需要 HBase,或者说 HBase 是否可以替代原有的 RDBMS?对于这个问题,我们必须时刻谨记——HBase 并不适合所有问题,其设计目标并不是替代 RDBMS,而是对 RDBMS 的一个重要补充,尤其是对大数据的场景。当需要考量 HBase 作为一个备选项时,我们需要进行如下的调研工作。

首先,要确信有足够多数据,如果有上亿或上千亿行数据,HBase 才会是一个很好的备选。其次,需要确信业务上可以不依赖 RDBMS 的额外特性,例如,列数据类型, 二级索引,SQL 查询语言等。再而,需要确保有足够硬件。且不说 HBase,一般情况下当 HDFS 的集群小于 5 个数据节点时,也干不好什么事情 (HDFS 默认会将每一个 Block 数据备份 3 分),还要加上一个 NameNode。

以下我给了一些使用 HBase 时候对表格设计的一些建议,读者也可以理解背后的含义。不过我并不希望这些建议成为使用 HBase 的教条,毕竟也有不尽合理的地方。首先,一个 HBase 数据库是否高效,很大程度会和 Row-Key 的设计有关。因此,如何设计 Row-key 是使用 HBase 时,一个非常重要的话题。随着数据访问方式的不同,Row-Key 的设计也会有所不同。不过概括起来的宗旨只有一个,那就是尽可能选择一个 Row-Key,可以使你的数据均匀的分布在集群中。这也很容易理解,因为 HBase 是一个分布式环境,Client 会访问不同 Region Server 获取数据。如果数据排布均匀在不同的多个节点,那么在批量的 Client 便可以从不同的 Region Server 上获取数据,而不是瓶颈在某一个节点,性能自然会有所提升。对于具体的建议我们一般有几条:

  1. 当客户端需要频繁的写一张表,随机的 RowKey 会获得更好的性能。
  2. 当客户端需要频繁的读一张表,有序的 RowKey 则会获得更好的性能。
  3. 对于时间连续的数据(例如 log),有序的 RowKey 会很方便查询一段时间的数据(Scan 操作)。

上面我们谈及了对 Row-Key 的设计,接着我们需要想想是否 Column Family 也会在不同的场景需要不同的设计方案呢。答案是肯定的,不过 CF 跟 Row-key 比较的话,确实也简单一些,但这并不意味着 CF 的设计就是一个琐碎的话题。在 RDBMS(传统关系数据库)系统中,我们知道如果当用户的信息分散在不同的表中,便需要根据一个 Key 进行 Join 操作。而在 HBase 中,我们需要设计 CF 来聚合用户所有相关信息。简单来说,就是需要将数据按类别(或者一个特性)聚合在一个或多个 CF 中。这样,便可以根据 CF 获取这类信息。上面,我们讲解过一个 Region 对应于一个 CF。那么设想,如果在一个表中定义了多个 CF 时,就必然会有多个 Region。当 Client 查询数据时,就不得不查询多个 Region。这样性能自然会有所下降,尤其当 Region 夸机器的时候。因此在大多数的情况下,一个表格不会超过 2 到 3 个 CF,而且很多情况下都是 1 个 CF 就足够了。

Phoenix 的使用

当一个新业务需要使用 HBase 时,是完全可以使用 Java API 开发 HBase 的应用,从而实现具体的业务逻辑。但是如果对于习惯使用 RDBMS 的 SQL,或者想要将原来使用 JDBC 的应用直接迁移到 HBase,这就是不可能的。由于这种缅怀过去的情怀,便催生了 Phoenix 的诞生。那么 Phoenix 都能提供哪些功能呢?简单来说 Phoenix 在 HBase 之上提供了 OLTP 相关的功能,例如完全的 ACID 支持、SQL、二级索引等,此外 Phoenix 还提供了标准的 JDBC 的 API。在 Phoenix 的帮助下,RDBMS 的用户可以很容易的使用 HBase,并且迁移原有的业务到 HBase 之中。下来就让我们简单了解一下,如何在 HBase 之上使用 Phoenix。

首先我们需要在 Phoenix 的网站下载与 HBase 版本对应的 Phoenix 安装包。我环境的 HBase 是通过 Ambari HDP2.4 部署的,其中的 HBase 是 1.1 的版本,因此我下载的是下图中的 phoenix-4.7.0-HBase-1.1。

图 6. Phoenix 的下载页面

图片描述

下载之后需要解压 Phoenix 的 tar 包,并将所有的 jar 文件拷贝到每台 Region Server 机器的 $HBASE_HOME/lib 下面,并重启所有的 Region Server。对于 Ambari 部署的 HBase,其 HBASE_HOME 目录便是/usr/hdp/2.4.0.0-169/hbase/lib/,添加 Jar 包到该目录之后,可以直接在 Ambari 的 WEB 中,重启整个 HBase。重启之后,我们便尽可以进入到刚才解压的 Phoenix 目录,进入其子目录 bin。在这个目录中 Phoenix 提供了 sqlline.py 脚本。我们可以通过该脚本连接 HBase,并测试相关的 SQL 语句。我们可以在 bin 目录中看到文件 hbase-site.xml,如果需要对 Phoenix 设置相关参数,就需要更改该文件,并将该文件同步给 HBase 中。Sqlline.py 最简单的使用方法,就是直接以 Zookeeper 机器名为参数即可,如下图:

图 7. Sqlline.py 使用示意图

图片描述

上图中,我们还使用了 sqlline.py 支持的 table 命令,该命令可以列出 HBase 中所有的表。这里需要注意 Phoenix 不支持直接显示 HBase Shell(HBase 自带一个 CLI 访问工具,后续文章在介绍)中创建的表格。原因很简单,当在 Phoenix 创建一张表时,Phoenix 是将表进行了重组装。而对 HBase Shell 创建的表 Phoenix 并未进行加工,所以无法直接显示。如果需要将 HBase Shell 中创建的表格关联到 Phoenix 中查看,就需要在 Phoenix 中创建一个视图(View)做关联。例如,我们现在 HBase Shell 中创建了一张表"table1",并插入了几行数据,如下。

然后我们在 Sqlline.py 的终端中执行"!table"命令,我们发现并没有 table1 这张表。接下来我们执行如下的命令:

然后再使用!table 命令,这时候结果如下:

图 8. Phoenix 执行表查询结果

图片描述

我们可以看到结果中多了一个 table1 的视图,这样 Phoenix 就将 table1 表的内容关联到了 Phoenix 的视图当中。我们可以使用 select 等语句访问其中的内容,如下:

图 9. Phoenix 执行查询结果

图片描述

最后我们再回头解释下刚才创建视图的命令。在创建关联的视图时,我们需要确保视图和列的名称与原表的名称完全一致。Phoenix 默认使用大写字母,因此,当 HBase Shell 中使用的是小写,我们便需要使用双引号引用相关的名称。如果原名称是大写,就可以省去双引号。Pk 是我们定义的一个主键名(可以随便定义),这是由于在 HBase Shell 中并没有主键的概念,所以 Row-key 是没有一个名称的。cf1 和 name 加起来用于指向 HBase 中的一个单元格(Cell),示例的命令中我关联了两个单元格(如果你愿意,可以只关联一个)。在安装了 Phoenix 之后,我们应尽量避免直接使用 HBase Shell 来创建表,取而代之的便是直接使用 Phoenix。例如下图中,我使用 Phoenix 创建了一张表 t1,包含了 name 和 age 两个列,并插入了两行数据。具体的命令如下图:

图 10. 如何在 Phoenix 中创建表
图片描述

看到这些命令之后,熟悉 SQL 的读者肯定不会觉得陌生。这便是 Phoenix 提供的最重要的功能之一——SQL 的支持。我们可以看到在 Phoenix 中,我们使用了丰富的数据类型,如 INTEGER 和 VARCHAR。这些都是无法直接在 HBase 中使用的。有兴趣的读者可以在 sqlline.py 中尝试更多的 SQL 语句。当需要从 sqlline.py 退出时,可以执行!quit 命令(可以通过使用!help 查看更多的命令)。退出 sqlline.py 之后,让我们在 HBase Shell 中看看 Phoenix 创建的表会是什么样子。如下:

我们可以明显的看到,Phoenix 将如上的数据进行了重组,才形成了图 10 中所展示的样子。到这里,我们就简单的介绍了 Phoenix 简单的用法。需要强调的是,本章所展示的只是 Phoenix 所提供特性的很小一部分。Phoenix 作为一个标准 JDBC API 的支持者,原有建立在 JDBC 之上应用程序可以直接通过 Phoenix 访问 HBase,例如 Squirrel 这样图形化的 SQL 客户端。当然也可以直接在 Java 代码中通过 JDBC 访问 HBase 数据库,而不用使用 HBase 的 Java API 重新开发。想要了解更多 Phoenix 特性的读者,可以从 Apache Phoenix 官方的文档中查看,例如二级索引等。

总结

对于 HBase 还有很多内容需要介绍,例如使用 Java API 开发应用,快速部署使用(涉及 Ambari 以及 HBase 部署模式)、HBase Shell 以及如何集成 Hive 和 HBase 等。目前 HBase 的应用场景很多,尤其是互联网公司的后台信息存储中,例如淘宝等都在使用 HBase。我相信了解 HBase 之后,可以更好的架构使用大数据解决方案,这里篇幅有限,后续再介绍更多内容。

参考资源

Apache Hadoop

Apache Phoneix

Apache HBase

留言與分享

Apache Spark 简介

Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎。Spark是UC Berkeley AMP lab (加州大学伯克利分校的AMP实验室)所开源的类Hadoop MapReduce的通用并行框架,Spark,拥有Hadoop MapReduce所具有的优点;但不同于MapReduce的是Job中间输出结果可以保存在内存中,从而不再需要读写HDFS,因此Spark能更好地适用于数据挖掘与机器学习等需要迭代的MapReduce的算法。

Spark 是一种与 Hadoop 相似的开源集群计算环境,但是两者之间还存在一些不同之处,这些有用的不同之处使 Spark 在某些工作负载方面表现得更加优越,换句话说,Spark 启用了内存分布数据集,除了能够提供交互式查询外,它还可以优化迭代工作负载。

Spark 是在 Scala 语言中实现的,它将 Scala 用作其应用程序框架。与 Hadoop 不同,Spark 和 Scala 能够紧密集成,其中的 Scala 可以像操作本地集合对象一样轻松地操作分布式数据集。

尽管创建 Spark 是为了支持分布式数据集上的迭代作业,但是实际上它是对 Hadoop 的补充,可以在 Hadoop 文件系统中并行运行。通过名为 Mesos 的第三方集群框架可以支持此行为。Spark 由加州大学伯克利分校 AMP 实验室 (Algorithms, Machines, and People Lab) 开发,可用来构建大型的、低延迟的数据分析应用程序。

准备工作

环境

1
2
3
4
JDK:1.8  
Spark-2.2.0
Hadoop Release:2.7.4
centos:7.3
主机名 ip地址 安装服务
spark-master 192.168.252.121 jdk、hadoop、spark、scala
spark-slave01 192.168.252.122 jdk、hadoop、spark
spark-slave02 192.168.252.123 jdk、hadoop、spark

依赖环境

Spark 是在 Scala 语言中实现的,它将 Scala 用作其应用程序框架。与 Hadoop 不同,Spark 和 Scala 能够紧密集成,其中的 Scala 可以像操作本地集合对象一样轻松地操作分布式数据集。所有我们安装 Scala

Scala

Scala-2.13.0 安装及配置

Hadoop

Hadoop-2.7.4 集群快速搭建

安装

下载解压

1
2
3
4
5
su hadoop
cd /home/hadoop/
wget https://mirrors.tuna.tsinghua.edu.cn/apache/spark/spark-2.2.0/spark-2.2.0-bin-hadoop2.7.tgz
tar -zxvf spark-2.2.0-bin-hadoop2.7.tgz
mv spark-2.2.0-bin-hadoop2.7 spark-2.2.0

环境变量

如果是对所有的用户都生效就修改vi /etc/profile 文件
如果只针对当前用户生效就修改 vi ~/.bahsrc 文件

1
sudo vi /etc/profile
1
2
3
#spark
export PATH=${SPARK_HOME}/bin:$PATH
export SPARK_HOME=/home/hadoop/spark-2.2.0/

使环境变量生效,运行 source /etc/profile使/etc/profile文件生效

修改配置

修改 spark-env.sh

1
cd /home/hadoop/spark-2.2.0/conf
1
2
mv spark-env.sh.template spark-env.sh
vi spark-env.sh
1
2
3
4
5
6
7
8
#java
export JAVA_HOME=/lib/jvm

#Spark主节点的IP
export SPARK_MASTER_IP=192.168.252.121

#Spark主节点的端口号
export SPARK_MASTER_PORT=7077

简单介绍几个变量

  • JAVA_HOME:Java安装目录
  • SCALA_HOME:Scala安装目录
  • HADOOP_HOME:hadoop安装目录
  • HADOOP_CONF_DIR:hadoop集群的配置文件的目录
  • SPARK_MASTER_IP:spark集群的Master节点的ip地址
  • SPARK_WORKER_MEMORY:每个worker节点能够最大分配给exectors的内存大小
  • SPARK_WORKER_CORES:每个worker节点所占有的CPU核数目
  • SPARK_WORKER_INSTANCES:每台机器上开启的worker节点的数目

修改 slaves

1
cd /home/hadoop/spark-2.2.0/conf
1
2
mv slaves.template slaves
vi slaves
1
2
3
node1
node2
node3

配置集群

复制节点

进去 spark 安装目录 ,打包,并发送,到其他节点

1
2
3
4
5
6
cd cd /home/hadoop/

tar zcvf spark.tar.gz spark-2.2.0

scp spark.tar.gz hadoop@node2:/home/hadoop/
scp spark.tar.gz hadoop@node3:/home/hadoop/

进去 node1,node2 节点 解压

1
2
3
cd /home/hadoop/

tar -zxvf spark.tar.gz

环境变量

到这里一步 确保你的每一个节点 环境变量够数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#jdk
export JAVA_HOME=/lib/jvm
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${SPARK_HOME}/bin:${SCALA_HOME}/bin:${HADOOP_HOME}/bin:${JAVA_HOME}/bin:$PATH

#hadoop
export HADOOP_HOME=/home/hadoop/hadoop-2.7.4/

#scala
export SCALA_HOME=/lib/scala

#spark
export SPARK_HOME=/home/hadoop/spark-2.2.0/

启动集群

关闭防火墙

1
systemctl stop firewalld.service

启动 Hadoop

1
2
3
cd /home/hadoop/hadoop-2.7.4/sbin

./start-all.sh

启动 Spark

1
2
3
cd /home/hadoop/spark-2.2.0/sbin

./start-all.sh

启动 Spark Shell

1
2
3
cd /home/hadoop/spark-2.2.0/bin

./spark-shell

spark 访问:192.168.252.121:8080

spark-shell 访问:192.168.252.121:4040

图片描述

图片描述

留言與分享

搭建 MySQL 5.7.19 主从复制,以及复制实现细节分析

概念

主从复制可以使MySQL数据库主服务器的主数据库,复制到一个或多个MySQL从服务器从数据库,默认情况下,复制异步; 根据配置,可以复制数据库中的所有数据库,选定的数据库或甚至选定的表。

MySQL中主从复制的优点

横向扩展解决方案

在多个从库之间扩展负载以提高性能。在这种环境中,所有写入和更新在主库上进行。但是,读取可能发生在一个或多个从库上。该模型可以提高写入的性能(由于主库专用于更新),同时在多个从库上读取,可以大大提高读取速度。

数据安全性

由于主库数据被复制到从库,从库可以暂停复制过程,可以在从库上运行备份服务,而不会破坏对应的主库数据。

分析

可以在主库上创建实时数据,而信息分析可以在从库上进行,而不会影响主服务器的性能。

长距离数据分发

可以使用复制创建远程站点使用的数据的本地副本,而无需永久访问主库。

1.准备工作

参考 MySQL官网 - 第16章主从复制

Mysql版本:MySQL 5.7.19
Master-Server : 192.168.252.123
Slave-Server : 192.168.252.124

关闭防火墙

1
$ systemctl stop firewalld.service

安装 MySQL

参考 - CentOs7.3 安装 MySQL 5.7.19 二进制版本

首先在两台机器上装上,保证正常启动,可以使用

2. Master-Server 配置

修改 my.cnf

配置 Master 以使用基于二进制日志文件位置的复制,必须启用二进制日志记录并建立唯一的服务器ID,否则则无法进行主从复制。

停止MySQL服务。

1
$ service mysql.server stop

开启binlog ,每台设置不同的 server-id

1
2
3
4
$ cat /etc/my.cnf
[mysqld]
log-bin=mysql-bin
server-id=1

启动MySQL服务

1
$ service mysql.server start

登录MySQL

1
$ /usr/local/mysql/bin/mysql -uroot -p

创建用户

每个从库使用MySQL用户名和密码连接到主库,因此主库上必须有用户帐户,从库可以连接。任何帐户都可以用于此操作,只要它已被授予 REPLICATION SLAVE权限。可以选择为每个从库创建不同的帐户,或者每个从库使用相同帐户连接到主库

虽然不必专门为复制创建帐户,但应注意,复制用到的用户名和密码会以纯文本格式存储在主信息存储库文件或表中 。因此,需要创建一个单独的帐户,该帐户只具有复制过程的权限,以尽可能减少对其他帐户的危害。

登录MySQL

1
$ /usr/local/mysql/bin/mysql -uroot -p
1
2
mysql> CREATE USER 'replication'@'192.168.252.124' IDENTIFIED BY 'mima';
mysql> GRANT REPLICATION SLAVE ON *.* TO 'replication'@'192.168.252.124';

3.Slave-Server 配置

修改 my.cnf

停止MySQL服务。

1
$ service mysql.server stop
1
2
3
$ cat /etc/my.cnf
[mysqld]
server-id=2

如果要设置多个从库,则每个从库的server-id与主库和其他从库设置不同的唯一值。

启动MySQL服务

1
$ service mysql.server start

登录MySQL

1
$ /usr/local/mysql/bin/mysql -uroot -p

配置主库通信

查看 Master-Server , binlog File 文件名称和 Position值位置 并且记下来

1
2
3
4
5
6
7
mysql>  show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 629 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)

要设置从库与主库进行通信,进行复制,使用必要的连接信息配置从库在从库上执行以下语句
将选项值替换为与系统相关的实际值

参数格式,请勿执行

1
2
3
4
5
6
mysql> CHANGE MASTER TO
-> MASTER_HOST='master_host_name',
-> MASTER_USER='replication_user_name',
-> MASTER_PASSWORD='replication_password',
-> MASTER_LOG_FILE='recorded_log_file_name',
-> MASTER_LOG_POS=recorded_log_position;
1
2
3
4
5
6
7
mysql> CHANGE MASTER TO
-> MASTER_HOST='192.168.252.123',
-> MASTER_USER='replication',
-> MASTER_PASSWORD='mima',
-> MASTER_LOG_FILE='mysql-bin.000001',
-> MASTER_LOG_POS=629;
Query OK, 0 rows affected, 2 warnings (0.02 sec)

MASTER_LOG_POS=0 写成0 也是可以的

放在一行执行方便

1
CHANGE MASTER TO MASTER_HOST='192.168.252.123', MASTER_USER='replication', MASTER_PASSWORD='mima', MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=629;

启动从服务器复制线程

1
2
mysql> START SLAVE;
Query OK, 0 rows affected (0.00 sec)

查看复制状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql>  show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.252.123
Master_User: replication
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000001
Read_Master_Log_Pos: 629
Relay_Log_File: master2-relay-bin.000003
Relay_Log_Pos: 320
Relay_Master_Log_File: mysql-bin.000001
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
......

检查主从复制通信状态

Slave_IO_State #从站的当前状态
Slave_IO_Running: Yes #读取主程序二进制日志的I/O线程是否正在运行
Slave_SQL_Running: Yes #执行读取主服务器中二进制日志事件的SQL线程是否正在运行。与I/O线程一样
Seconds_Behind_Master #是否为0,0就是已经同步了

必须都是 Yes

如果不是原因主要有以下 4 个方面:

1、网络不通
2、密码不对
3、MASTER_LOG_POS 不对 ps
4、mysql 的 auto.cnf server-uuid 一样(可能你是复制的mysql)

1
2
3
4
$ find / -name 'auto.cnf'
$ cat /var/lib/mysql/auto.cnf
[auto]
server-uuid=6b831bf3-8ae7-11e7-a178-000c29cb5cbc # 按照这个16进制格式,修改server-uuid,重启mysql即可

检查复制状态

4.测试主从复制

启动MySQL服务

1
$ service mysql.server start

登录MySQL

1
$ /usr/local/mysql/bin/mysql -uroot -p

在 Master-Server 创建测试库

1
2
3
mysql> CREATE DATABASE `replication_wwww.ymq.io`;
mysql> use `replication_wwww.ymq.io`;
mysql> CREATE TABLE `sync_test` (`id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

在 Slave-Server 查看是否同步过来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
mysql> show databases;
+-------------------------+
| Database |
+-------------------------+
| information_schema |
| mysql |
| performance_schema |
| replication_wwww.ymq.io |
| sys |
+-------------------------+

mysql> use replication_wwww.ymq.io
mysql> show tables;

+-----------------------------------+
| Tables_in_replication_wwww.ymq.io |
+-----------------------------------+
| sync_test |
+-----------------------------------+
1 row in set (0.00 sec)

一些命令

查看主服务器的运行状态

1
2
3
4
5
6
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 1190 | | | |
+------------------+----------+--------------+------------------+-------------------+

查看从服务器主机列表

1
2
3
4
5
6
mysql> show slave hosts;
+-----------+------+------+-----------+--------------------------------------+
| Server_id | Host | Port | Master_id | Slave_UUID |
+-----------+------+------+-----------+--------------------------------------+
| 2 | | 3306 | 1 | 6b831bf2-8ae7-11e7-a178-000c29cb5cbc |
+-----------+------+------+-----------+--------------------------------------+

获取binlog文件列表

1
2
3
4
5
6
mysql> show binary logs;
+------------------+-----------+
| Log_name | File_size |
+------------------+-----------+
| mysql-bin.000001 | 1190 |
+------------------+-----------+

只查看第一个binlog文件的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
mysql> mysql> show binlog events;
+------------------+-----+----------------+-----------+-------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+------------------+-----+----------------+-----------+-------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| mysql-bin.000001 | 4 | Format_desc | 1 | 123 | Server ver: 5.7.19-log, Binlog ver: 4 |
| mysql-bin.000001 | 123 | Previous_gtids | 1 | 154 | |
| mysql-bin.000001 | 420 | Anonymous_Gtid | 1 | 485 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| mysql-bin.000001 | 485 | Query | 1 | 629 | GRANT REPLICATION SLAVE ON *.* TO 'replication'@'192.168.252.124' |
| mysql-bin.000001 | 629 | Anonymous_Gtid | 1 | 694 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| mysql-bin.000001 | 694 | Query | 1 | 847 | CREATE DATABASE `replication_wwww.ymq.io` |
| mysql-bin.000001 | 847 | Anonymous_Gtid | 1 | 912 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| mysql-bin.000001 | 912 | Query | 1 | 1190 | use `replication_wwww.ymq.io`; CREATE TABLE `sync_test` (`id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 |
+------------------+-----+----------------+-----------+-------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

查看指定binlog文件的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
mysql> mysql> show binlog events in 'mysql-bin.000001';
+------------------+-----+----------------+-----------+-------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+------------------+-----+----------------+-----------+-------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| mysql-bin.000001 | 4 | Format_desc | 1 | 123 | Server ver: 5.7.19-log, Binlog ver: 4 |
| mysql-bin.000001 | 123 | Previous_gtids | 1 | 154 | |
| mysql-bin.000001 | 420 | Anonymous_Gtid | 1 | 485 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| mysql-bin.000001 | 485 | Query | 1 | 629 | GRANT REPLICATION SLAVE ON *.* TO 'replication'@'192.168.252.124' |
| mysql-bin.000001 | 629 | Anonymous_Gtid | 1 | 694 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| mysql-bin.000001 | 694 | Query | 1 | 847 | CREATE DATABASE `replication_wwww.ymq.io` |
| mysql-bin.000001 | 847 | Anonymous_Gtid | 1 | 912 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| mysql-bin.000001 | 912 | Query | 1 | 1190 | use `replication_wwww.ymq.io`; CREATE TABLE `sync_test` (`id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 |
+------------------+-----+----------------+-----------+-------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

启动从库复制线程

1
2
mysql> START SLAVE;
Query OK, 0 rows affected, 1 warning (0.00 sec)

停止从库复制线程

1
2
mysql> STOP SLAVE;
Query OK, 0 rows affected (0.00 sec)

5.复制实现细节分析

MySQL主从复制功能使用三个线程实现一个在主服务器上两个在从服务器上

1.Binlog转储线程。

当从服务器与主服务器连接时,主服务器会创建一个线程将二进制日志内容发送到从服务器。
该线程可以使用 语句 SHOW PROCESSLIST(下面有示例介绍) 在服务器 sql 控制台输出中标识为Binlog Dump线程。

二进制日志转储线程获取服务器上二进制日志上的锁,用于读取要发送到从服务器的每个事件。一旦事件被读取,即使在将事件发送到从服务器之前,锁会被释放。

2.从服务器I/O线程。

当在从服务器sql 控制台发出 START SLAVE语句时,从服务器将创建一个I/O线程,该线程连接到主服务器,并要求它发送记录在主服务器上的二进制更新日志。

从机I/O线程读取主服务器Binlog Dump线程发送的更新 (参考上面 Binlog转储线程 介绍),并将它们复制到自己的本地文件二进制日志中。

该线程的状态显示详情 Slave_IO_running 在输出端 使用 命令SHOW SLAVE STATUS

使用\G语句终结符,而不是分号,是为了,易读的垂直布局

这个命令在上面 查看从服务器状态 用到过

1
mysql> SHOW SLAVE STATUS\G

3.从服务器SQL线程。

从服务器创建一条SQL线程来读取由主服务器I/O线程写入的二级制日志,并执行其中包含的事件。

在前面的描述中,每个主/从连接有三个线程。主服务器为每个当前连接的从服务器创建一个二进制日志转储线程,每个从服务器都有自己的I/O和SQL线程。
从服务器使用两个线程将读取更新与主服务器更新事件,并将其执行为独立任务。因此,如果语句执行缓慢,则读取语句的任务不会减慢。

例如,如果从服务器开始几分钟没有运行,或者即使SQL线程远远落后,它的I/O线程也可以从主服务器建立连接时,快速获取所有二进制日志内容。

如果从服务器在SQL线程执行所有获取的语句之前停止,则I/O线程至少获取已经读取到的内容,以便将语句的安全副本存储在自己的二级制日志文件中,准备下次执行主从服务器建立连接,继续同步。

使用命令 SHOW PROCESSLIST\G 可以查看有关复制的信息

命令 SHOW FULL PROCESSLISTG

在 Master 主服务器 执行的数据示例

1
2
3
4
5
6
7
8
9
10
mysql>  SHOW FULL PROCESSLIST\G
*************************** 1. row ***************************
Id: 22
User: repl
Host: node2:39114
db: NULL
Command: Binlog Dump
Time: 4435
State: Master has sent all binlog to slave; waiting for more updates
Info: NULL

Id: 22是Binlog Dump服务连接的从站的复制线程
Host: node2:39114 是从服务,主机名 级及端口
State: 信息表示所有更新都已同步发送到从服务器,并且主服务器正在等待更多更新发生。
如果Binlog Dump在主服务器上看不到 线程,意味着主从复制没有配置成功; 也就是说,没有从服务器连接主服务器。

命令 SHOW PROCESSLISTG

在 Slave 从服务器 ,查看两个线程的更新状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mysql> SHOW PROCESSLIST\G
*************************** 1. row ***************************
Id: 6
User: system user
Host:
db: NULL
Command: Connect
Time: 6810
State: Waiting for master to send event
Info: NULL
*************************** 2. row ***************************
Id: 7
User: system user
Host:
db: NULL
Command: Connect
Time: 3069
State: Slave has read all relay log; waiting for more updates
Info: NULL

Id: 6是与主服务器通信的I/O线程
Id: 7是正在处理存储在中继日志中的更新的SQL线程

在 运行 SHOW PROCESSLIST 命令时,两个线程都空闲,等待进一步更新

如果在主服务器上在设置的超时,时间内 Binlog Dump线程没有活动,则主服务器会和从服务器断开连接。超时取决于的 服务器系统变量 值 net_write_timeout(在中止写入之前等待块写入连接的秒数,默认10秒)和 net_retry_count;(如果通信端口上的读取或写入中断,请在重试次数,默认10次) 设置 服务器系统变量

该SHOW SLAVE STATUS语句提供了有关从服务器上复制处理的附加信息。请参见 第16.1.7.1节“检查复制状态”。

6.更多常见主从复制问题:

常见主从复制问题

留言與分享

git rebase

分類 devops, git

变基

在 Git 中整合来自不同分支的修改主要有两种方法:merge 以及 rebase。 在本节中我们将学习什么是“变基”,怎样使用“变基”,并将展示该操作的惊艳之处,以及指出在何种情况下你应避免使用它。

变基的基本操作

请回顾之前在 分支的合并 中的一个例子,你会看到开发任务分叉到两个不同分支,又各自提交了更新。

分叉的提交历史。

Figure 35. 分叉的提交历史

之前介绍过,整合分支最容易的方法是 merge 命令。 它会把两个分支的最新快照(C3C4)以及二者最近的共同祖先(C2)进行三方合并,合并的结果是生成一个新的快照(并提交)。

通过合并操作来整合分叉了的历史。

Figure 36. 通过合并操作来整合分叉的历史

其实,还有一种方法:你可以提取在 C4 中引入的补丁和修改,然后在 C3 的基础上应用一次。 在 Git 中,这种操作就叫做 变基(rebase)。 你可以使用 rebase 命令将提交到某一分支上的所有修改都移至另一分支上,就好像“重新播放”一样。

在这个例子中,你可以检出 experiment 分支,然后将它变基到 master 分支上:

1
2
3
4
$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command

它的原理是首先找到这两个分支(即当前分支 experiment、变基操作的目标基底分支 master) 的最近共同祖先 C2,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件, 然后将当前分支指向目标基底 C3, 最后以此将之前另存为临时文件的修改依序应用。 (译注:写明了 commit id,以便理解,下同)

将  中的修改变基到  上。

Figure 37. 将 C4 中的修改变基到 C3

现在回到 master 分支,进行一次快进合并。

1
2
$ git checkout master
$ git merge experiment

 分支的快进合并。

Figure 38. master 分支的快进合并

此时,C4' 指向的快照就和 the merge exampleC5 指向的快照一模一样了。 这两种整合方法的最终结果没有任何区别,但是变基使得提交历史更加整洁。 你在查看一个经过变基的分支的历史记录时会发现,尽管实际的开发工作是并行的, 但它们看上去就像是串行的一样,提交历史是一条直线没有分叉。

一般我们这样做的目的是为了确保在向远程分支推送时能保持提交历史的整洁——例如向某个其他人维护的项目贡献代码时。 在这种情况下,你首先在自己的分支里进行开发,当开发完成时你需要先将你的代码变基到 origin/master 上,然后再向主项目提交修改。 这样的话,该项目的维护者就不再需要进行整合工作,只需要快进合并便可。

请注意,无论是通过变基,还是通过三方合并,整合的最终结果所指向的快照始终是一样的,只不过提交历史不同罢了。 变基是将一系列提交按照原有次序依次应用到另一分支上,而合并是把最终结果合在一起。

更有趣的变基例子

在对两个分支进行变基时,所生成的“重放”并不一定要在目标分支上应用,你也可以指定另外的一个分支进行应用。 就像 从一个主题分支里再分出一个主题分支的提交历史 中的例子那样。 你创建了一个主题分支 server,为服务端添加了一些功能,提交了 C3C4。 然后从 C3 上创建了主题分支 client,为客户端添加了一些功能,提交了 C8C9。 最后,你回到 server 分支,又提交了 C10

从一个主题分支里再分出一个主题分支的提交历史。

Figure 39. 从一个主题分支里再分出一个主题分支的提交历史

假设你希望将 client 中的修改合并到主分支并发布,但暂时并不想合并 server 中的修改, 因为它们还需要经过更全面的测试。这时,你就可以使用 git rebase 命令的 --onto 选项, 选中在 client 分支里但不在 server 分支里的修改(即 C8C9),将它们在 master 分支上重放:

1
$ git rebase --onto master server client

以上命令的意思是:“取出 client 分支,找出它从 server 分支分歧之后的补丁, 然后把这些补丁在 master 分支上重放一遍,让 client 看起来像直接基于 master 修改一样”。这理解起来有一点复杂,不过效果非常酷。

截取主题分支上的另一个主题分支,然后变基到其他分支。

Figure 40. 截取主题分支上的另一个主题分支,然后变基到其他分支

1
2
$ git checkout master
$ git merge client

快进合并  分支,使之包含来自  分支的修改。

Figure 41. 快进合并 master 分支,使之包含来自 client 分支的修改

接下来你决定将 server 分支中的修改也整合进来。 使用 git rebase <basebranch> <topicbranch> 命令可以直接将主题分支 (即本例中的 server)变基到目标分支(即 master)上。 这样做能省去你先切换到 server 分支,再对其执行变基命令的多个步骤。

1
$ git rebase master server

将  中的修改变基到  上。

Figure 42. 将 server 中的修改变基到 master

然后就可以快进合并主分支 master 了:

1
2
$ git checkout master
$ git merge server

至此,clientserver 分支中的修改都已经整合到主分支里了, 你可以删除这两个分支,最终提交历史会变成图 最终的提交历史 中的样子:

1
2
$ git branch -d client
$ git branch -d server

最终的提交历史。

Figure 43. 最终的提交历史

变基的风险

呃,奇妙的变基也并非完美无缺,要用它得遵守一条准则:

如果提交存在于你的仓库之外,而别人可能基于这些提交进行开发,那么不要执行变基。

如果你遵循这条金科玉律,就不会出差错。 否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你。

变基操作的实质是丢弃一些现有的提交,然后相应地新建一些内容一样但实际上不同的提交。 如果你已经将提交推送至某个仓库,而其他人也已经从该仓库拉取提交并进行了后续工作,此时,如果你用 git rebase 命令重新整理了提交并再次推送,你的同伴因此将不得不再次将他们手头的工作与你的提交进行整合,如果接下来你还要拉取并整合他们修改过的提交,事情就会变得一团糟。

让我们来看一个在公开的仓库上执行变基操作所带来的问题。 假设你从一个中央服务器克隆然后在它的基础上进行了一些开发。 你的提交历史如图所示:

克隆一个仓库,然后在它的基础上进行了一些开发。

Figure 44. 克隆一个仓库,然后在它的基础上进行了一些开发

然后,某人又向中央服务器提交了一些修改,其中还包括一次合并。 你抓取了这些在远程分支上的修改,并将其合并到你本地的开发分支,然后你的提交历史就会变成这样:

抓取别人的提交,合并到自己的开发分支。

Figure 45. 抓取别人的提交,合并到自己的开发分支

接下来,这个人又决定把合并操作回滚,改用变基;继而又用 git push --force 命令覆盖了服务器上的提交历史。 之后你从服务器抓取更新,会发现多出来一些新的提交。

有人推送了经过变基的提交,并丢弃了你的本地开发所基于的一些提交。

Figure 46. 有人推送了经过变基的提交,并丢弃了你的本地开发所基于的一些提交

结果就是你们两人的处境都十分尴尬。 如果你执行 git pull 命令,你将合并来自两条提交历史的内容,生成一个新的合并提交,最终仓库会如图所示:

你将相同的内容又合并了一次,生成了一个新的提交。

Figure 47. 你将相同的内容又合并了一次,生成了一个新的提交

此时如果你执行 git log 命令,你会发现有两个提交的作者、日期、日志居然是一样的,这会令人感到混乱。 此外,如果你将这一堆又推送到服务器上,你实际上是将那些已经被变基抛弃的提交又找了回来,这会令人感到更加混乱。 很明显对方并不想在提交历史中看到 C4C6,因为之前就是他把这两个提交通过变基丢弃的。

用变基解决变基

如果你 真的 遭遇了类似的处境,Git 还有一些高级魔法可以帮到你。 如果团队中的某人强制推送并覆盖了一些你所基于的提交,你需要做的就是检查你做了哪些修改,以及他们覆盖了哪些修改。

实际上,Git 除了对整个提交计算 SHA-1 校验和以外,也对本次提交所引入的修改计算了校验和——即 “patch-id”。

如果你拉取被覆盖过的更新并将你手头的工作基于此进行变基的话,一般情况下 Git 都能成功分辨出哪些是你的修改,并把它们应用到新分支上。

  • 检查哪些提交是我们的分支上独有的(C2,C3,C4,C6,C7)

  • 检查其中哪些提交不是合并操作的结果(C2,C3,C4)

  • 检查哪些提交在对方覆盖更新时并没有被纳入目标分支(只有 C2 和 C3,因为 C4 其实就是 C4’)

  • 把查到的这些提交应用在 teamone/master 上面

在一个被变基然后强制推送的分支上再次执行变基。

Figure 48. 在一个被变基然后强制推送的分支上再次执行变基

要想上述方案有效,还需要对方在变基时确保 C4'C4 是几乎一样的。 否则变基操作将无法识别,并新建另一个类似 C4 的补丁(而这个补丁很可能无法整洁的整合入历史,因为补丁中的修改已经存在于某个地方了)。

在本例中另一种简单的方法是使用 git pull --rebase 命令而不是直接 git pull。 又或者你可以自己手动完成这个过程,先 git fetch,再 git rebase teamone/master

如果你习惯使用 git pull ,同时又希望默认使用选项 --rebase,你可以执行这条语句 git config --global pull.rebase true 来更改 pull.rebase 的默认配置。

如果你只对不会离开你电脑的提交执行变基,那就不会有事。 如果你对已经推送过的提交执行变基,但别人没有基于它的提交,那么也不会有事。 如果你对已经推送至共用仓库的提交上执行变基命令,并因此丢失了一些别人的开发所基于的提交, 那你就有大麻烦了,你的同事也会因此鄙视你。

如果你或你的同事在某些情形下决意要这么做,请一定要通知每个人执行 git pull --rebase 命令,这样尽管不能避免伤痛,但能有所缓解。

变基 vs. 合并

至此,你已在实战中学习了变基和合并的用法,你一定会想问,到底哪种方式更好。 在回答这个问题之前,让我们退后一步,想讨论一下提交历史到底意味着什么。

有一种观点认为,仓库的提交历史即是 记录实际发生过什么。 它是针对历史的文档,本身就有价值,不能乱改。 从这个角度看来,改变提交历史是一种亵渎,你使用 谎言 掩盖了实际发生过的事情。 如果由合并产生的提交历史是一团糟怎么办? 既然事实就是如此,那么这些痕迹就应该被保留下来,让后人能够查阅。

另一种观点则正好相反,他们认为提交历史是 项目过程中发生的事。 没人会出版一本书的第一版草稿,软件维护手册也是需要反复修订才能方便使用。 持这一观点的人会使用 rebasefilter-branch 等工具来编写故事,怎么方便后来的读者就怎么写。

现在,让我们回到之前的问题上来,到底合并还是变基好?希望你能明白,这并没有一个简单的答案。 Git 是一个非常强大的工具,它允许你对提交历史做许多事情,但每个团队、每个项目对此的需求并不相同。 既然你已经分别学习了两者的用法,相信你能够根据实际情况作出明智的选择。

总的原则是,只对尚未推送或分享给别人的本地修改执行变基操作清理历史, 从不对已推送至别处的提交执行变基操作,这样,你才能享受到两种方式带来的便利。

留言與分享

git flow

分類 devops, git

Git Flow

Git Flow 是一种基于 Git 的分支模型,旨在帮助团队更好地管理和发布软件。

Git Flow 由 Vincent Driessen 在 2010 年提出,并通过一套标准的分支命名和工作流程,使开发、测试和发布过程更加有序和高效。

Git Flow 主要由以下几类分支组成:masterdevelopfeaturereleasehotfix

Git Flow 安装

Linux

Debian/Ubuntu:

1
sudo apt-get install git-flow

Fedora:

1
2
sudo dnf install gitflow
sudo apt-get install git-flow

macOS

在 macOS 上,你可以使用 Homebrew 来安装 Git Flow:

1
brew install git-flow

源码安装

如果你的发行版的包管理器中没有 Git Flow,你也可以从源代码进行安装:

1
2
3
git clone https://github.com/nvie/gitflow.git
cd gitflow
sudo make install

安装完成后,你可以通过以下命令验证 Git Flow 是否成功安装:

1
git flow version

Windows

在 Windows 上,你可以通过以下方式安装 Git Flow:

  • 使用 Git for Windows: Git for Windows 包含了 Git Flow。你可以从 Git for Windows 安装 Git,然后使用 Git Bash 来使用 Git Flow。

  • 使用 Scoop: 如果你使用 Scoop 包管理工具,可以通过以下命令安装 Git Flow:

    1
    scoop install git-flow
  • 使用 Chocolatey: 如果你使用 Chocolatey 包管理工具,可以通过以下命令安装 Git Flow:

    1
    choco install gitflow

Git Flow 分支模型

master 分支

  • 永远保持稳定和可发布的状态。
  • 每次发布一个新的版本时,都会从 develop 分支合并到 master 分支。

develop 分支

  • 用于集成所有的开发分支。
  • 代表了最新的开发进度。
  • 功能分支、发布分支和修复分支都从这里分支出去,最终合并回这里。

feature 分支

  • 用于开发新功能。
  • develop 分支创建,开发完成后合并回 develop 分支。
  • 命名规范:feature/feature-name

release 分支

  • 用于准备新版本的发布。
  • develop 分支创建,进行最后的测试和修复,然后合并回 developmaster 分支,并打上版本标签。
  • 命名规范:release/release-name

hotfix 分支

  • 用于修复紧急问题。
  • master 分支创建,修复完成后合并回 masterdevelop 分支,并打上版本标签。
  • 命名规范:hotfix/hotfix-name

分支操作原理

  • Master 分支上的每个 Commit 应打上 Tag,Develop 分支基于 Master 创建。
  • Feature 分支完成后合并回 Develop 分支,并通常删除该分支。
  • Release 分支基于 Develop 创建,用于测试和修复 Bug,发布后合并回 Master 和 Develop,并打 Tag 标记版本号。
  • Hotfix 分支基于 Master 创建,完成后合并回 Master 和 Develop,并打 Tag 1。

Git Flow 命令示例

  • 开始 Feature 分支:git flow feature start MYFEATURE
  • 完成 Feature 分支:git flow feature finish MYFEATURE
  • 开始 Release 分支:git flow release start RELEASE [BASE]
  • 完成 Release 分支:合并到 Master 和 Develop,打 Tag,删除 Release 分支。
  • 开始 Hotfix 分支:git flow hotfix start HOTFIX [BASE]
  • 完成 Hotfix 分支:合并到 Master 和 Develop,打 Tag,删除 Hotfix 分支。

Git Flow 工作流程

1. 初始化 Git Flow

首先,在项目中初始化 Git Flow。可以使用 Git Flow 插件(例如 git-flow)来简化操作。

1
git flow init

初始化时,你需要设置分支命名规则和默认分支。

2. 创建功能分支

当开始开发一个新功能时,从 develop 分支创建一个功能分支。

1
git flow feature start feature-name

完成开发后,将功能分支合并回 develop 分支,并删除功能分支。

1
git flow feature finish feature-name

3. 创建发布分支

当准备发布一个新版本时,从 develop 分支创建一个发布分支。

1
git flow release start release-name

在发布分支上进行最后的测试和修复,准备好发布后,将发布分支合并回 developmaster 分支,并打上版本标签。

1
git flow release finish release-name

4. 创建修复分支

当发现需要紧急修复的问题时,从 master 分支创建一个修复分支。

1
git flow hotfix start hotfix-name

修复完成后,将修复分支合并回 masterdevelop 分支,并打上版本标签。

1
git flow hotfix finish hotfix-name

实例操作

以下是一个实际使用 Git Flow 的综合实例。

初始化 Git Flow

1
git flow init

创建和完成功能分支

1
2
git flow feature start new-feature # 开发新功能
git flow feature finish new-feature

创建和完成发布分支

1
2
git flow release start v1.0.0 # 测试和修复
git flow release finish v1.0.0

创建和完成修复分支

1
2
git flow hotfix start hotfix-1.0.1. # 修复紧急问题
git flow hotfix finish hotfix-1.0.1

优点和缺点

优点

  • 明确的分支模型:清晰的分支命名和使用规则,使得开发过程井然有序。
  • 隔离开发和发布:开发和发布过程分离,减少了开发中的不确定性对发布的影响。
  • 版本管理:每次发布和修复都会打上版本标签,方便回溯和管理。

缺点

  • 复杂性:对于小型团队或简单项目,Git Flow 的分支模型可能显得过于复杂。
  • 频繁的合并:在大型团队中,频繁的分支合并可能导致合并冲突增加。

Git Flow 是一种结构化的分支管理模型,通过定义明确的分支和工作流程,帮助团队更好地管理软件开发和发布过程。虽然它增加了一定的复杂性,但对于大型项目和团队协作,Git Flow 提供了强大的支持和管理能力。

留言與分享

作者的圖片

Kein Chan

這是獨立全棧工程師Kein Chan的技術博客
分享一些技術教程,命令備忘(cheat-sheet)等


全棧工程師
資深技術顧問
數據科學家
Hit廣島觀光大使


Tokyo/Macau