梦入琼楼寒有月,行过石树冻无烟

📖 earlier posts 📖

Vagrant

Vagrant 最早通过 Mitchell Hashimoto 因业余兴趣而发布使用 Ruby lang 所实现的一种基于构建及配置虚拟开发环境工具,主要依赖 VirtualBox、 VMware、Libvirt 等虚拟化系统来实现快速部署开发环境的构建。

通过由 Oracle 所开源的 VirtualBox 虚拟化系统,受其开源所带来的好处可配合 Vagrant 来快速构建虚拟环境,这让 Vagrant + VirtualBox 这种组合更加的流行且成为了生产力工具之一。

install

安装 Vagrant 本身不怎么麻烦,但是对于没有 Ruby 环境的读者以及虚拟依赖可能非常困惑,特别是 VirtualBox 依赖的问题。

如果通过 dpkg 进行安装很可能安装不到 VirtualBox 的依赖,因此我们需要手动进行安装依赖(VirtualBox >= 6.0):

1
2
3
4
sudo apt-get install build-essentials
sudo apt-get install virtualbox-ose
sudo /sbin/vboxconfig
sudo apt-get install dkms build-essential linux-headers-`uname -r`

Box

vagrant box 主要的作用最多的就是管理当前的 box,包括安装、移除等。等 vagrant 安装完成之后就开始到了我们的使用环节,通常我们最常用的就是添加一个 Vagrant box:

1
vagrant init [name[,url]

当执行完 init 后,则当前目录已经初始化为 Vagrant 环境,就比如我们可以使用:

1
vagrant init kalilinux/rolling

如果要删除框,首先需要查询框,通过 vagrant box list 来查询目前所拥有的框,我们可以通过使用下述命令删除:

1
2
$ vagrant box remove kalilinux/rolling
Removing box 'kalilinux/rolling' (v2021.2.0) with provider 'virtualbox'...

kalilinux/rolling 则是通过使用 vagrant box list 所得出的信息。

redmine

Redmine 是一个基于Web项目的管理和权限管理和跟踪工具,与其他主流的项目管理平台不同的是 Redmine 是一个开源的。它用日历和甘特图辅助项目及进度可视化显示。同时它又支持多项目管理。Redmine 是一个自由开放 源码软件解决方案,它提供集成的项目管理功能,问题跟踪,并为多个版本控制选项的支持。需要注意的是 Redmine 是建立在 Ruby and Rails 的框架之上,因此他支持跨平台和多种数据库,但是缺点是安装的过程会非常折腾。

安装 redmine

下载

最新版的 redmine 可以通过https://www.redmine.org/projects/redmine/wiki/RedmineInstall#Step-9-Test-the-installation 内进行下载,本文使用 4.0.9 (2021-04-26) 作为演示。

需要注意的是下面的所有步骤均在 redmine 文件下进行(除了你 ruby 编译安装)

mysql

首先按照 redmine 官方文档的意思是,我们需要创建一个 redmine 的用户,并创建一个 redmine 库,为了让操作变得简单我们忽略新建用户的这个步骤,需们需要创建一个库:

1
create database redmine

当然你如果非常讲究我们也可以根据官方文档中一样,创建一个 redmine 用户:

1
2
3
CREATE DATABASE redmine CHARACTER SET utf8mb4;
CREATE USER 'redmine'@'localhost' IDENTIFIED BY 'my_password';
GRANT ALL PRIVILEGES ON redmine.* TO 'redmine'@'localhost';
redmine

之后进入到 redmine-4.0.9/config目录,之后将 database.yml.example 文件下的 production 复制到 database.yml文件下(需要创建一个 database.yml)

1
2
3
4
5
6
7
production:
adapter: mysql2
database: redmine
host: localhost
username: username
password: "password"
encoding: utf8

此时也是一个必然的过程,你还需要修改 production内的username、password等信息,来配置你的数据库帐号密码

ruby

值得注意的是 redmine 所依赖的 ruby&gem and bundle 如果你选择使用 MySQL数据库作为数据存储方式的话还需要有一个 MySQL 的环境。通常在 debian 的系统中已经内置了ruby&gem 的语言环境,如果没有您还需要执行 apt-get install ruby来进行安装,如果您已经存在了ruby&gem则不需要重新安装。

自动安装

本文使用的 ruby版本为2.5.5p157,gem 的版本为 3.2.16,通常 gem 版本可通过使用gem update --system直接进行升级,但需要注意的是

编译安装

本文基于 debian bunsen labs 发行版进行演示,通常除了Arch Linux系的系统基本上都可以进行安装,需要注意的是由于 Arch Linux 滚动更新的缘故,因此他的软件包都非常的新。

现在是 2021年05月11日,此时 debian 系官方提供的 ruby 版本是 2019-03-15 所发布的2.5.5p157版本,而此时的 Arch Linux 可能已经用上了前年,即2020-12-25所发布的 ruby 3.0了,而值得庆幸的是 redmine 也支持 ruby 3.0:

redmine 版本 支持的 ruby 版本 使用的 rails 版本
trunk (>= r20913) Ruby 2.5, 2.6, 2.7, 3.0 Rails 6.1
4.2 Ruby 2.4, 2.5, 2.6, 2.7 Rails 5.2
4.1 Ruby 2.3, 2.4, 2.5, 2.6 Rails 5.2
4.0 Ruby 2.2, 2.3, 2.4, 2.5, 2.6 Rails 5.2
  1. Redmine 4.2不支持Ruby 2.7.0和2.7.1。使用Ruby 2.7.2或更高版本
  2. 24.0.6 之前的 Redmine 支持 Ruby >= 2.2.2。Redmine 4.0.6及更高版本不支持Ruby 2.2。
下载&安装

下载 ruby 2.4.2 版的压缩文件,更多全新版本你可以在 https://cache.ruby-lang.org/pub/ruby/ 进行下载。

1
wget https://cache.ruby-lang.org/pub/ruby/ruby-2.4.2.tar.gz

之后解压并进入文件

1
2
tar -zxvf ruby-2.4.2.tgz
cd ruby-2.4.2

进入后配置并编译源代码

1
2
3
./configure
make
sudo make install

最后通过使用 ruby -v来验证是否安装成功,如果你本机中已经存在了 ruby或其他版本,您需要注意下安装的位置不要与其他版本冲突,否则你无论使用 ruby&gem都会报错:

如果遇到这个问题我会选择将所有 ruby 文件删除,只安装一个 ruby 版本。这个时候你 google 就会有人让你执行 gem install bundler,但是你换个思路想:“我执行 gem update 都报错了,我还能执行 gem install bundler 来安装 bundler 吗?”,很明显这个方法并不适用。如果你有其他的方法欢迎联系我 kl@sif.one

gem

RubyGem(gem),是一个Ruby 的一个包管理器,他提供了一个分发 ruby 程序和库的标准格式,还提供了一个管理程序包的安装工具,类似于 npm、pip。

配置源

RubyGems 一直以来在国内都非常难访问到,在本地你或许可以翻墙,当你要发布上线的时候,你就很难搞了。RubyGems 是由一个非常高端也非常有优越感的一个社区,他们为此提供了一个 Gem 的 CDN,来确保几乎无延迟的同步。

在本文中我们除了使用 RubyChina 所提供的 CDN,以及由 Ruby 官方社区所提供的 GemCDN 源,https://rubygems.org/ 国内是可以访问的,因此我们不需要移出,只需要添加一个 https://gems.ruby-china.com/ 源即可:

1
gem sources -a https://gems.ruby-china.com/

之后通过gem sources来查看源列表存在 rubychina社区所提供的源即可:

1
2
3
4
5
gem source
*** CURRENT SOURCES ***

https://gems.ruby-china.com/
https://rubygems.org/

bundler

安装&配置

bundler 是一个能够跟踪并安装所需特定版本的 gem,以此来为 ruby 项目提供一致的运行环境,安装 bundler 安装的方式可以通过 gem 也可通过外部文件安装:

1
gem install bundler

当然我们也可以制定安装 bundler 版本2.2.17,只需要在安装时指定版本即可:

1
gem install bundler:2.2.17

安装完成后我们需要新的 bundle,来以防下面的操作报错。

1
bundle install

bundler install时你可能会出现两个错误

Gem::Ext::BuildError: ERROR: Failed to build gem native extension error
gem install jaro_winkler -v ‘1.5.2’ –source ‘https://cernerrepos.net/api/gems/rubygems/'

Gem::Installer::ExtensionBuildError: ERROR: Failed to build gem native extension.
如果出现了这种错误我们需要通过 apt-get 安装 install build-essential patch ruby-dev zlib1g-dev liblzma-dev以及nokogiri等依赖:
sudo apt-get install build-essential patch ruby-dev zlib1g-dev liblzma-dev
gem install nokogiri

然后安装 redmine 所需的所有 gem 依赖:

1
bundle install --without development test

接着我们来生成一个随机密钥,rails 使用他来编码存储会话数据的 cookies,从而防止被篡改(重新启动后生成的令牌将会使得现有会话无效)

1
bundle exec rake generate_secret_token
依赖项

本文所使用的 redmine 4.2 版本,因此我们需要使用下面步骤来跳过 rmagick gem 的安装

rmagick 对于 redmine 4.1.0 之前的版本主要用于将甘特图导出至 png 或 pdf 格式文件

1
bundle install --without development test rmagick

rails

在上述配置 redmine 库的时候你可能非常疑问为什么又有新建表,因此在下面的步骤中会给你一个非常好的答案。我们在 redmine 项目下运行下述命令来创建数据结构:

1
RAILS_ENV=production bundle exec rake db:migrate

之后通过下属命令,在数据库中插入默认的配置数据:

1
RAILS_ENV=production bundle exec rake redmine:load_default_data

run


当上述步骤完成之后,我们可以试着通过下述命令运行 redmine:

1
bundle exec rails server webrick -e production

在此时你可能还会遇到一个错误

RubyGems warning/error: Gem::Specification# default_executable = is deprecated with no replacement. It will be removed

这是一个 RubyGem 团队的最新公告声明,这是在 RubyGem 1.8 之后才有的,因此我们需要通过使用下述命令进行更新。
gem pristine –all –no-extensions

当该命令运行成功后,我们可以通过使用localhost:3000进入到 redmine 页面,如果可以看到程序 view 则表示已经部署完成。可以通过使用 admin\admin来登入你的管理员帐号。

redmine-run

读者在运行时可能非常迷惑 redmine 的启动命令太长而记不住,导致每次需要时都需要找到资料粘贴上去。因此为了解决这个问题国内开发者为此开发了 redmine-run 脚本来解决 redmien 启动的问题。

需要注意的是,此项目基于 Linux 4.19.0-16-amd64 #1 SMP Debian 4.19.181-1 (2021-03-19) x86_64 GNU/Linux 环境进行开发,理论上来讲系统只要存在ln以及mv和支持 .sh 脚本基本上就可以使用本项目。

通过使用 redmine run ,可以直接通过redmine来启动 redmine 项目管理平台,避免输入长且毫无规律的 bundle exec rails server webrick -e production,还需要进入到 redmine 目录中运行。

下述操作需要全部使用 root 权限运行

首先我们需要拉取 redmine run:

1
git clone https://gitee.com/analysis-of-river-snow/radmine-run.git

将里面的 install.sh以及redmine.sh拖到 redmine 目录中

redmine 目录即你可以执行bundle exec rails server webrick -e production

让 install.sh 和 redmine.sh 成为可执行文件:

1
2
chmod 777 install.sh
chmod 777 redmine.sh

之后我们先修改 install.sh 文件内容,并将/opt/redmine/redmine-4.0.9/修改为你的 redmine 所在目录即可:

1
2
3
4
5
6
#!/bin/bash

cd /opt/redmine/redmine-4.0.9/
redmine_run=`bundle exec rails server webrick -e production`
echo "[echo] 正在进入目录" $path_use
echo "[echo] 执行 redmine run 命令 => bundle exec rails server webrick -e production" $redmine_run

然后执行./install.sh即可,之后通过使用redmine 来运行项目。

Redis 数据类型

Redis 中的数据类型主要分为 字符串、哈希、列表、集合、有序集合、HyperLogLog、地理信息、Stream 等类型,字符串类型在上一章我们就已经介绍过,他通过 SET 就可以直接进行创建,而其他的数据类型则是需要通过其他的命令以及其下属配合的命令来完成操作。

哈希(Hash)

哈希是一个 string 类型的 字段(field) 以及值(value)映射表,可适用与存储对象,单个哈希键值可存储多大40亿字段值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
127.0.0.1:6379> HMSET hash_value name "kun" info "hello,world" url "http://jiangxue.org.cn" and "one" or "two" age "17" redis "redislabs.com"
OK
127.0.0.1:6379> HGETALL hash_value
1) "name"
2) "kun"
3) "info"
4) "hello,world"
5) "url"
6) "http://jiangxue.org.cn"
7) "and"
8) "one"
9) "or"
10) "two"
11) "age"
12) "17"
13) "redis"
14) "redislabs.com"

需要注意的是 Hashes 类型的字段依然是通过键值来进行存储的,如 name=kun、info=hello,world

可以通过使用 HMSET 来创建 hash 类型的键值对,也可以使用 HGETALL 来获取所有的字段值,当然也可以指定键名的方式来进行获取 HGETALL hash_value "name"

Id Name Info Command
1 HMSET 创建 hash 类型的键值段 HMSET hash_value name "kun" info "hello,world" url "http://jiangxue.org.cn"
2 HMGET 获取指定字段的值 HMGET hash_value name
2 HGET 获取存储在哈希表中指定的字段值 HGET hash_value "name"
3 HSET 自 redis 4.0 起可以一次性设置多个字段对,HMSET 区别是可以覆盖字段值 HSET hash_value age "10"
4 HSETNX 为哈希表中不存在的字段赋值(如存在返回 0) HSETNX hash_value about "jiangxue"
5 HGETALL 获取全部或单个的 hash 类型键值段 HGETALL hash_value or HGET hash_value "name"
6 HEXISTS 查看哈希表中的 key 是否存在(存在返回 1,否则返回0) HEXISTS hash_value "name"
7 HKEYS 获取当前所有哈希表中的字段 HKEYS hash_value
8 HVALS 获取当前所有哈希表中的字段值 HVALS hash_value
9 HLEN 获取哈希表中字段的数量(从1开始) HLEN hash_value
10 HDEL 删除一个多多个哈希表中的键值对 HDEL hash_value "and" "or"
11 HINCRBY 为指定的哈希表中的 key 增加或减少数值(整数) HINCRBY hash_value age 1 (原本 17将会被增加1,得出 18)
12 HINCRBYFLOAT HINCRBY 的区别是支持非整数计算以及科学计数法 HINCRBYFLOAT hash_value age -1.5 (18-1.5 得出 16.5)
13 HSCAN 模糊搜索哈希表中的字段,并返回键值段 HSCAN hash_value 0 match "na*" (返回 name 字段即值)
14 HSTRLEN 返回字段值的长度 HSTRLEN hash_value "name"

列表(List)

列表在 Redis 中表示可以将数据按顺序排序插入的字符串列表,通常可以包含超过40亿个元素,这启动主要通过 LPUSH 来插入一个或多个到列表头部,以及通过 LRANGE 来获取列表范围

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
127.0.0.1:6379> LPUSH list_key redis
(integer) 1
127.0.0.1:6379> LPUSH list_key mysql
(integer) 2
127.0.0.1:6379> LPUSH list_key sql
(integer) 3
127.0.0.1:6379> LPUSH list_key mongoDB
(integer) 4
127.0.0.1:6379> LPUSH list_key one two
(integer) 6
127.0.0.1:6379> LRANGE list_key 0 10
1) "two"
2) "one"
3) "mongoDB"
4) "sql"
5) "mysql"
6) "redis"
Id Name Info Command
1 LPUSH 将一个或多个值插入到列表头部 (如果 key 不存在则会创建一个) LPUSH list_key mongoDB
2 RPUSH 将一个或多个值插入到表尾部(如果 key 不存在则会创建一个) RPUSH list_key "nice!"
3 LPUSHX key 不存在时不进行任何操作并返回 0,存在的话则将值插入到列表头部 LPUSHX list_key "hello"
4 RPUSHX key 不存在时不进行任何操作并返回 0,存在的话则将值插入到列表尾部 RPUSHX list_keys "world"
5 LINSERT 在指定元素前插入列表数据 LINSERT list_key BEFORE "redis" "redislab.com" (在 redis 前插入 redislab.com)
6 LSET 通过索引替换列表元素(以0开始) LSET list_key 0 "start"(将 索引为0的元素替换为 “start”)
7 LRANGE 在一定范围内获取列表内元素 LRANGE list_key 0 100
8 LINDEX 返回指定索引对应的表内元素(超出范围返回 nill) LINDEX list_key 0
9 BRPOPLPUSH 将指定列表内最后一个元素插入到另外一个列表的头部(如果列表没有元素会阻塞到超时为止,或者发现可以移出的元素为止,单位是秒,需要注意的是如果超时参数是 0 ,那么将会表示阻塞的时间可以无限期延长) BRPOPLPUSH list_key key_name 500
10 LLEN 获取列表的元素数量 LLEN list_key
11 LTRIM 用于修建已经存在列表内的元素(要么从前删除要么从后删除,不可跨越索引) LTRIM list_key -1 -1 (从倒数地一个开始,然后直到倒数第一个,也就是说之保留倒数地一个)
12 BLPOP 移出第一个列表元素,并返回所移出的列表名称和元素 BLPOP list_key 0
13 BRPOP 移出列表最后一个元素,同样返回岁移出的列表名和元素 LRANGE list_key 0 -1
14 LPOP 移出列表中地一个元素(与 BLPOP 的区别是不用设置阻塞时间) LPOP list_key 1
15 LREM 指定一个或多个列表中相同元素进行删除 LREM list_key 1 "sql"

LTRIM


在 Redis 中 LTRIM 主要用于修剪(删除)已经存在的列表内的元素,其中分为 start 以及 stop 以 0 为开始,-1 可以表示列表里面的最后一个元素,-2 表倒数第二个 ……(这适用绝大多数依赖索引所完成的命令,如 RANGE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
127.0.0.1:6379> LRANGE list_key 0 1000
1) "start"
2) "two"
3) "one"
4) "mongoDB"
5) "sql"
6) "mysql"
7) "redislab.com"
8) "redis"
9) "world"
127.0.0.1:6379> clear

127.0.0.1:6379> LTRIM list_key -1 -1
OK
127.0.0.1:6379> LRANGE list_key 0 100
1) "world"

要么从前删除要么从后删除,不可跨越索引

我们所构造的 -1~-1 翻译到人可以理解的那就是,从倒数地一个开始,然后直到倒数第一个,也就是说之保留倒数地一个,当然你也可以通过 LTRIM list_key -6 -1 从倒数第六到最后一个来进行保留,删除其余外的元素:

1
2
3
4
5
6
7
8
9
127.0.0.1:6379> LTRIM list_key -6 -1
OK
127.0.0.1:6379> LRANGE list_key 0 -1
1) "mongoDB"
2) "sql"
3) "mysql"
4) "redislab.com"
5) "redis"
6) "world"

BLPOP

主要的作用就是移出指定列表中的第一个元素,并返回所移出的元素名称,如果列表没有元素或没有该列表,则会等待超时时间过后即可关闭阻塞,或者得到发现可以移出的元素为止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
127.0.0.1:6379> LRANGE list_key 0 -1
1) "world"
2) "redis"
3) "redislab.com"
4) "mysql"
5) "sql"
6) "mongoDB"
7) "one"
8) "two"
9) "start"
127.0.0.1:6379> BLPOP list_key 0
1) "list_key"
2) "world"
127.0.0.1:6379> LRANGE list_key 0 -1
1) "redis"
2) "redislab.com"
3) "mysql"
4) "sql"
5) "mongoDB"
6) "one"
7) "two"
8) "start"

LTRIM


LTRIM 与列表中所有的移出命令不同的是,他可以指定一个或多个在列表中相同的元素进行删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
127.0.0.1:6379> LPUSH list_key "sql" sql sql
(integer) 6
127.0.0.1:6379> LRANGE list_key 0 -1
1) "sql"
2) "sql"
3) "sql"
4) "redislab.com"
5) "mysql"
6) "mongoDB"
127.0.0.1:6379> LREM list_key 1 "sql"
(integer) 1
127.0.0.1:6379> LRANGE list_key 0 -1
1) "sql"
2) "sql"
3) "redislab.com"
4) "mysql"
5) "mongoDB"

集合(Set)

集合(set)是一个字符串类型的无序集合,集合成员是唯一的,因此这意味着在集合中不可以有重复的成员数据,每个集合中最大可以存储40多亿个成员数据。通常可以通过 SADD 创建一个集合并向其添加成员(一个或多个),之后 SMEMBERS 来返回集合中的所有成员。

1
2
3
4
5
6
7
127.0.0.1:6379> SADD set_key sql mysql redis mongoDB 
(integer) 4
127.0.0.1:6379> SMEMBERS set_key
1) "mongoDB"
2) "redis"
3) "mysql"
4) "sql"
Id Name Info Command
1 SADD 创建集合并向其中添加一个或多个成员 SADD set_key sql mysql redis mongoDB
2 SINTERSTORE 将两个集合中相同的成员保存在另一个集合中 SINTERSTORE set_key set_one set_two
3 SDIFFSTORE 将两个集合中不相同的成员数据保存到另一个集合中 SDIFFSTORE set_key set_one set_two (将 set_one 和 set_two 两个集合之间不相同的成员保存到 set_key 集合中)
4 SUNIONSTORE 将两个或多个集合之间的成员进行合并到另一个集合中 SUNIONSTORE key_set set_one set_two
5 SMOVE 将一个集合中的成员移动到另一个集合中 SMOVE set_one set_two "sql" (将 set_one 集合中的成员“sql” 移动到 set_two 中)
6 SDIFF 返回指定集合中的不同成员 SDIFF set_two set_one
7 SINTER 返回指定集合中相同的成员 SINTER set_one set_two
8 SUNION 返回将指定集合合并后的并集 SUNION set_one set_two
9 SMEMBERS 返回指定集合中所有的成员 SMEMBERS set_key
10 SISMEMBER 判断集合中成员是否存在(存在返回1,不存在返回0) SISMEMBER set_one "mysql"
11 SSCAN 遍历指定集合中所存在的键元素 SSCAN set_two 0 MATCH s* (匹配该集合中所有以 “s” 开头的成员 )
12 SRANDMEMBER 随机返回指定集合中的成员数(如果数大于成员数那么将返回所有成员) SRANDMEMBER set_two 10
13 SPOP 根据指定数值随机删除集合成员,并返回所删除的成员数据 SPOP set_two 3
14 SREM 删除一个或多个集合中的成员 SREM set_two "mysql"

SINTERSTORE


SINTERSTORE 命令在 Redis 中的主要作用就是将两个集合中相同的成员数据添加到另一个集合中。

1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6379> SMEMBERS set_one
1) "redis"
2) "mysql"
3) "sql"
127.0.0.1:6379> SMEMBERS set_two
1) "mongoDB"
2) "mysql"
3) "redislab.com"
127.0.0.1:6379> SINTERSTORE set_key set_one set_two
(integer) 1
127.0.0.1:6379> SMEMBERS set_key
1) "mysql"

SDIFF


返回两个集合中不相同的成员数据,相同的数据将不会被输出,按照官方的意思说这个命令主要返回地一个集合与其他集合之间的差异(说人话就是将其他集合不相同的数据进行输出)

1
2
3
4
5
6
7
127.0.0.1:6379> SDIFF set_two set_one
1) "mongoDB"
2) "redislab.com"

127.0.0.1:6379> SDIFF set_one set_two
1) "redis"
2) "sql"

在集合中有两个非常重要的名词,分别是交集和差集,交集就是相同的意思,而差集就不相同的意思。

SDIFFSTORE

SDIFFSTORE 与 SDIFF 类似,但他主要的作用就是将两个集合之间的不相同数据(也就是差值)保存到另一个集合之中

1
2
3
4
5
127.0.0.1:6379> SDIFFSTORE set_key set_one set_two
(integer) 2
127.0.0.1:6379> SMEMBERS set_key
1) "redis"
2) "sql"

SINTER


SINTER 与 SDIFF 不同之处在于,SINTER 用于返回所有集合中的交集,也就是相同的成员集合。

1
2
127.0.0.1:6379> SINTER set_one set_two
1) "mysql"

SINTERSTORE

Redis 是一个考虑很全面的 key-value 数据库,因此他的命令也非常的具有针对性,该命令与 SDIFF 中的 SDIFFSTORE 非常相似,但该命令是将两个集合中的交集存储到另一个集合中:

1
2
3
4
127.0.0.1:6379> SINTERSTORE set_key set_one set_two
(integer) 1
127.0.0.1:6379> SMEMBERS set_key
1) "mysql"

SUNION


SUNION 简单来说就是将两个集合并集,也就是将两个集合合并,将相同的数据所剔除,与 SDIFF 和 SINTER 一样,分为输出和存储两个非常具有针对性的命令。

1
2
3
4
5
6
127.0.0.1:6379> SUNION set_one set_two
1) "sql"
2) "redis"
3) "mysql"
4) "mongoDB"
5) "redislab.com"

所谓“并集” 也就是将两个或多个集合成员进行合并,如果不同集合之间的成员数据相同将会被剔除。

SUNIONSTORE

SUNIONSTORE 同样集成了 SUNION 命令逻辑,只不过是将输出改为了存储,将两个或多个集合之间的成员进行并集

1
2
3
4
5
6
7
8
127.0.0.1:6379> SUNIONSTORE key_set set_one set_two
(integer) 5
127.0.0.1:6379> SMEMBERS key_set
1) "sql"
2) "redis"
3) "mysql"
4) "mongoDB"
5) "redislab.com"

有序集合(Zset)

有序集合(zset)和集合同样都是一个字符串类型的集合元素,他们都不允许同样重复的成员。不同的是他拥有一个双精度浮点数来达到从达到小排序的效果,与集合一样可以存储越 40亿个成员。

作为 Redis 几个最为主要的数据类型,他同样拥有针对性的命令,分别为 ZADD 即添加有序集合,他可以添加多个或单个(取决与你的习惯),当然也有 ZRANGE 来进行输出。

1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> ZADD key_zset 1 sql 2 mysql 3 mongoDB 4 redis
(integer) 1
127.0.0.1:6379> ZRANGE key_zset 0 -1 WITHSCORES
1) "sql"
2) "1"
3) "mysql"
4) "2"
5) "mongoDB"
6) "3"
7) "redis"
8) "4"

ZRANGE(从小到大)


ZRANGE 命令会按照数值从小到大来进行排序(具有相同数值的成员将会被按照字典序列来进行排序)

字典序列也就是根据 A、B、C、D 这个序列来进行从小到大来进行划分,也就是根据首字母开头进行排序。

在通过 ZRANGE 命令进行查询的时候,如果不加上 WITHSCORES 返回的是一个元素列表,加上的话返回的则是一个数组列表。

1
2
3
4
5
127.0.0.1:6379> ZRANGE key_zset 0 -1
1) "sql"
2) "mysql"
3) "mongoDB"
4) "redis"

ZRANGEBYLEX

当集合中的元素都以相同的方式进行插入时,那么将会强制按照字典顺序来进行排列(在 Redis 中默认为英文字典 ABC以此类推进行排序)。

在这个命令中他支持有效的 start 以及 stop 修饰符,分别为 [ 以及 - \ + 等。其中 -/+ 分别表示最小和最大字符串,也就是开始和结尾,而 [/( 的区别是输出时是否包含元素 [ 是包含 ( 即不包含。

1
2
3
4
5
6
7
8
9
10
127.0.0.1:6379> ZRANGEBYLEX key_zset - +
1) "a"
2) "b"
3) "c"
4) "d"
127.0.0.1:6379> ZRANGEBYLEX key_zset - (b
1) "a"
127.0.0.1:6379> ZRANGEBYLEX key_zset - [b
1) "a"
2) "b"

ZRANGEBYSCORE

ZRANGEBYSCORE 与 ZRANGEBYLEX 相似,但以我个人感觉更喜欢后者,前者的 -inf+inf 与后者的 -/+ 功能基本一样,区别是多了个 ”inf“,而之后的 [( 功能也很一样,但将需要通过元素来作为参数改为了数值(在ZRANGEBYSCORE 中 [ 并不支持)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
127.0.0.1:6379> ZRANGEBYSCORE key_zset 1 2
1) "one"
2) "two"
127.0.0.1:6379> ZRANGEBYSCORE key_zset -inf 2
1) "day"
2) "one"
3) "two"
127.0.0.1:6379> ZRANGEBYSCORE key_zset -inf (2
1) "day"
2) "one"
127.0.0.1:6379> ZRANGEBYSCORE key_zset -inf +inf
1) "day"
2) "one"
3) "two"
4) "three"

ZSCORE

ZSCORE 命令是上述几个查询命令中最为朴素的命令之一,他主要根据成员名称来返回在其集合中的索引号

1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> ZRANGE zset_one 0 -1 WITHSCORES
1) "day"
2) "0"
3) "one"
4) "1"
5) "two"
6) "2"
7) "three"
8) "3"
127.0.0.1:6379> ZSCORE zset_one "day"
"0"

ZCOUNT

ZCOUNT 同样支持 ( 以及 -inf\+inf这类的表达式,他主要的作用就是计算出集合中的成员数。

1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6379> ZRANGE zset_two 0 -1
1) "day"
2) "one"
3) "two"
4) "three"
5) "four"
127.0.0.1:6379> ZCOUNT zset_two -inf +inf
(integer) 5
127.0.0.1:6379> ZCOUNT zset_two -inf 3
(integer) 4
127.0.0.1:6379> ZCOUNT zset_two -inf (3
(integer) 3

ZREVRANK(从大到小)

ZREVRANK 同样是从大到小根据 0 为起始点进行排序,与 ZREVEANGE 类似,但该命令主要通过成员来返回在其集合中的索引。

1
2
3
4
5
6
7
127.0.0.1:6379> ZRANGE zset_one 0 -1
1) "day"
2) "one"
3) "two"
4) "three"
127.0.0.1:6379> ZREVRANK zset_one "day"
(integer) 3

ZREVRANGEBYSCORE

ZREVRANGEBYSCORE 可以理解为是 ZREVRANK 的延续之作,他除了索引之外还支持 ZRANGEBYLEX 其中的表达式,除此之外还整合了 ZRANGEBYSCORE 来打通信息屏障,通过差异化和颗粒度达到引爆点来聚焦用户感知赛道。

1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6379> ZREVRANGEBYSCORE zset_one 4 1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> ZREVRANGEBYSCORE zset_one 4 (1
1) "three"
2) "two"
127.0.0.1:6379> ZREVRANGEBYSCORE zset_one +inf -inf
1) "three"
2) "two"
3) "one"
4) "day"

ZREVEANGE

ZREVRANGE 是一个更加纯粹的返回字典序列的一个命令,他比 ZRANGE 这群命令显得更加的纯粹和干练,同样是返回集合的作用,该命令只需要指定索引即可,不支持任何华丽胡少的表达式(但支持 redis 自带的)

1
2
3
4
5
127.0.0.1:6379> ZREVRANGE zset_one 0 -1
1) "three"
2) "two"
3) "one"
4) "day"

ZINCRBY

ZINCRBY 简单来讲就是对集合元素中的数组进行增加,假设集合中元素的数组为 0,那么即可通过 ZINCRBY 来进行增加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
127.0.0.1:6379> ZINCRBY key_zset 10 "three"
"13"

127.0.0.1:6379> ZRANGE key_zset 0 -1 WITHSCORES
1) "day"
2) "0"
3) "one"
4) "1"
5) "two"
6) "2"
7) "three"
8) "13"
127.0.0.1:6379> ZINCRBY key_zset -10 "three"
"3"

并集/合集

ZINTERSTORE(合集)

将两个集合之间的交集存储到另一个集合中,需要指定两个集合中相同数的数值(也就是最终相同数所存储到另一个集合中的数量),默认情况下,结果集中的元素数值是这些集合中元素数值之和。

1
2
3
4
5
6
7
8
9
127.0.0.1:6379> ZINTERSTORE zset_key 2 zset_one zset_two
(integer) 3
127.0.0.1:6379> ZRANGE zset_key 0 -1 WITHSCORES
1) "day"
2) "0"
3) "one"
4) "2"
5) "two"
6) "4"

ZUNIONSTORE (并集)

ZUNIONSTORE 与 ZINTERSTORE 的区别就是一个是合集另一个是并集的一字之差,说简单一点就是将两个集合进行合并,相同的元素剔除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
127.0.0.1:6379> ZRANGE zset_two 0 -1
1) "day"
2) "one"
3) "two"
4) "three"
5) "four"
127.0.0.1:6379> ZRANGE zset_one 0 -1
1) "day"
2) "one"
3) "two"
4) "three"
127.0.0.1:6379> ZUNIONSTORE zset_key 2 zset_one zset_two
(integer) 5
127.0.0.1:6379> ZRANGE zset_key 0 -1 WITHSCORES
1) "day"
2) "0"
3) "one"
4) "2"
5) "four"
6) "4"
7) "two"
8) "4"
9) "three"
10) "6"

但这样会造成集合中索引的相同,为了避免这类事情的发生我们可以自定义权重或和来解决 ZUNIONSTORE zset_key 2 zset_one zest_two WEIGHTS 2 3 [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX],这同样在 ZINTERSTORE 命令中适用。

ZRANK

ZRANK 简单来说就单纯的是一个可以根据其集合成员来返回其索引的作用,如果其成员不存在则会返回 nil

1
2
3
4
5
6
7
8
9
10
11
12
13
127.0.0.1:6379> ZRANK zset_one "day"
(integer) 0
127.0.0.1:6379> ZRANK zset_one "one"
(integer) 1
127.0.0.1:6379> ZRANGE zset_one 0 -1 WITHSCORES
1) "day"
2) "0"
3) "one"
4) "1"
5) "two"
6) "2"
7) "three"
8) "3"

ZREM

ZREM 没有像后续几条命令一样那么否有诗书气自华,他主要根据成员来针对单个集合进行删除,在 Redis => 2.4 版本中支持删除多个成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
127.0.0.1:6379> ZADD key_zset 0 day 1 one 2 two 3 three 3 four
(integer) 5
127.0.0.1:6379> ZRANGE key_zset 0 -1
1) "day"
2) "one"
3) "two"
4) "four"
5) "three"
127.0.0.1:6379> ZREM key_zset "four"
(integer) 1
127.0.0.1:6379> ZRANGE key_zset 0 -1
1) "day"
2) "one"
3) "two"
4) "three"
127.0.0.1:6379>

ZREMRANGEBYLEX

该命令与 ZREM 相比起来就非常的丰富,他支持了一些较为简单的 Redis 表达式([ 包括自己),适合多个成员一起删除,当然你也可以选择删除单个(弊端就是你删除不了第一个和倒数第二个成员)。

但是 ZREMRANGEBYLEX 的弊端就是需要该集合索引都是一样的才可以进行删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
127.0.0.1:6379> ZRANGE key_zset 0 -1 WITHSCORES
1) "day"
2) "0"
3) "four"
4) "0"
5) "one"
6) "0"
7) "three"
8) "0"
9) "two"
10) "0"
127.0.0.1:6379> ZREMRANGEBYLEX key_zset [day [one
(integer) 3
127.0.0.1:6379> ZRANGE key_zset 0 -1 WITHSCORES
1) "three"
2) "0"
3) "two"
4) "0"

ZREMRANGEBYRANK

该命令同样非产简约优雅,通过 start\stop 的操作可以快速删除多个集合中的成员,但劣势也一样展现出来,他并不能删除单个成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
127.0.0.1:6379> ZRANGE key_zset 0 -1 WITHSCORES
1) "day"
2) "0"
3) "one"
4) "1"
5) "two"
6) "2"
7) "three"
8) "3"
9) "four"
10) "5"
127.0.0.1:6379> ZREMRANGEBYRANK key_zset 3 5
(integer) 2
127.0.0.1:6379> ZRANGE key_zset 0 -1 WITHSCORES
1) "day"
2) "0"
3) "one"
4) "1"
5) "two"
6) "2"

ZREMRANGEBYSCORE

ZREMRANGEBYSCORE Reids 的有序列表删除命令就发展的非常特别且简单优雅,他同样适合于多个成员的删除操作,与前者不同的是到他这开始支持了 -inf (不包含该成员) 这类的表达式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
127.0.0.1:6379> ZRANGE key_zset 0 -1 WITHSCORES
1) "day"
2) "0"
3) "one"
4) "1"
5) "two"
6) "2"
127.0.0.1:6379> ZREMRANGEBYSCORE key_zset -inf (1
(integer) 1
127.0.0.1:6379> ZRANGE key_zset 0 -1 WITHSCORES
1) "one"
2) "1"
3) "two"
4) "2"

Redis value

Redis 除了对键的操作,当然也有不少对值的操作,通常,就比如 GETRANGE 即截取一部分变量/值信息,在 python 等语言中可以通过 [key:n] 来进行访问。

1
2
127.0.0.1:6379> GETRANGE key_name 1 4
"1234"

就如上述 code 中,1 是一个起始数,而 4 是一个结尾数,该键的值为 0123456,因此该命令可以实现出截取键值的效果。

Id Name Info Command
1 GETRANGE 截取键值并返回 GETRANGE key_name start end
2 GETSET 重新设置键值的时候返回旧的键值数据 GETSET key_name "Hello,world" (上一个 key_name 的值是 0123456,因此返回的也是他)
3 GETBIT 获取 key 所存储字符串的偏移量上的位(bit) SETBIT value offset
4 SETRANGE 通过偏移量来修改 key 中的值 SETRANGE key_name 5 " World!"
5 SETBIT 修改 key 所存储的字符串上的偏移量 (bit) SETBIT key_name 2 1
6 MGET 获取一个或多个 key MGET key_name1 key_name2
7 MSET 设置多个 key(依然会通过新值替换旧值) MSET key_one "one" key_two "two"
8 MSETNX 用于设置多个 key,遵循原子性操作(0失败,1成功) MSETNX key_one "one" key_two "two"
9 SETNX 如果 key 存在则什么都不做,不存在则插入 SETNX key_name "1" (如果 key_name 存在则返回 0,否则返回 1)
10 STRLEN 返回 key 值的字符串长度(如果存储的不是字符串类型则报错) STRLEN key_name
11 APPEND 该命令的好处就是直接在原有的键值中直接拼合 APPEND key_one "Hello" and APPEND key_one " World!" show Hello World!

获取或存储偏移量上的位

偏移量(Offset)简单概括就是存储地址和实际地址之间所存在的空段之间的距离,这通常被称之为有效地址或偏移量。

GETSET


我们以 GETSET 命令为例,首先 H 的二进制数为:01001000 ,通过 GETSET 分别输出一组后可以看到结果正是二进制数,因此本命令也就是获取 value 内的偏移量位数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
127.0.0.1:6379> GETBIT key_name 0
(integer) 0
127.0.0.1:6379> GETBIT key_name 1
(integer) 1
127.0.0.1:6379> GETBIT key_name 2
(integer) 0
127.0.0.1:6379> GETBIT key_name 3
(integer) 0
127.0.0.1:6379> GETBIT key_name 4
(integer) 1
127.0.0.1:6379> GETBIT key_name 5
(integer) 0
127.0.0.1:6379> GETBIT key_name 6
(integer) 0
127.0.0.1:6379> GETBIT key_name 7
(integer) 0

SETBIT

.SETBIT 在 redis 中,主要用于更改 key 的存储偏移量,如 SETBIT key_name 2 1 则表示将从0,左到右开始计数,替换原有的位数从而替换为 1,让其从 H 改为 h

.

1
2
3
4
127.0.0.1:6379> SETBIT key_name 2 1
(integer) 0
127.0.0.1:6379> GET key_name
"hello,world"

SETRANGE

1
2
3
4
5
6
7
127.0.0.1:6379> SET key_name "Hello"
OK
127.0.0.1:6379> SETRANGE key_name 5 " World!"
(integer) 12
127.0.0.1:6379> GET key_name
"Hello World!"
127.0.0.1:6379>

自增与自减

Id Name Info Command
1 INCR 将数值自曾(key 不存在时为 0,假设运行100次 INCR 则 数值也会被自曾到 100,每运行一次数值 +1) INCR key_one
2 INCRBY 在原有数值的基础上指定增加数值 INCRBY key_one 10(假设 key 内值为2,则通过该命令增加 10,即12)
3 DECR 在原有数值基础上来进行值减 DECR key_one (假设key内值为100,运行100次则为0)
4 DECRBY 在原有基础上自定义减值数 DECRBY key_one 9 (假设 key 值内为 10,则直接减为 9)
5 INCRBYFLOAT 为键的存储值中增加,可以执行像 1.03e+08 这样的科学计数法 INCRBYFLOAT key_name -0.01 \ INCRBYFLOAT key_name 1.03e08

INCRBYFLOAT

INCRBYFLOAT 的主要作用就是可以浮点数的增量以及支持诸如 1.03E+08 这样的科学计数法,此外无论加法计算所得到的精度实际长度有多长,该命令最终的结果只保留小数点后十七位数。

1
2
3
4
5
6
7
127.0.0.1:6379> SET key_name 1.04
OK
127.0.0.1:6379> INCRBYFLOAT key_name -0.01
"1.03"
127.0.0.1:6379> INCRBYFLOAT key_name 1.03e08
"103000001.02999999999883585"
127.0.0.1:6379>

e 是一个科学计数法符号,通常以 “E” 表示,在科学计数法中为了公式的简便,可以将 1.03 x10的次方简写为 1.03E+08 的形式

1.03 x 10的8次方,也就是 8个10相乘,之后在乘于 1.03,只保留十七位小数则应为 103000001.02999999999883585

Redis Keys

键(keys)通常背后都会代表着一个数据结构,就已字符串的形式来称,他会表现为 key_name=value_name 在 Redis 中的命令与常见的数据传输码类似,如 SET 是创建、GET 是请求。

1
2
3
4
5
6
7
8
9
127.0.0.1:6379> SET key_name hello,world!
OK
127.0.0.1:6379> GET key_name
"hello,world!"
127.0.0.1:6379> DEL key_name
(integer) 1
127.0.0.1:6379> GET key_name
(nil)
127.0.0.1:6379>

在上面的命令中,我们通过 SET 创建了一个 key_name,他的值是 **hello,world!**,并通过 GET 进行请求,毫无疑问返回的是对应的值。

键的操作

在这个之间还使用到了 DEL 他是删除键的意思,而对于键的操作,并不只有这一条命令:

Id Name Info Command
1 DEL 删除 Key DEL value_name
2 DUMP 序列化 key,返回所序列化的值 DUMP key_name
3 EXISTS 检查 key 是否存在 EXISTS key_name (存在返回1,不存在返回0,虽然你通过 GET 也可以媲美 EXISTS 的效果)
4 RANDOMKEY 随机从数据库中抽取一个 key RANDOMKEY
5 RENAME 修改 key 的名称 RENAME key_name new_name
6 RENAMENX 为了避免错误,虽然与 RENAME 同样是修改名称,但 RENAME 会直接覆盖(无论是否存在),而如果用此命令如果是相同的键名则会返回错误(0)创建成功会返回 1 RENAMENX key key_name
7 TYPE 返回 key 所存储的数据类型 TYPE key_name

易失 key

Id Name Info Command
1 EXPIRE 设置 key 的过期时间 EXPIRE key_name 10(```EXPIRE 的单位是秒,即10秒后过期)
2 PEXPIRE 设置 key 的过期时间(毫秒) PEXPIRE key_name 10
3 PSETEX 设置 key 的过期时间(毫秒) PTTL key_one
4 EXPIREAT 通过 UNIX 时间戳的方式为 key 设置过期时间 EXPIREAT key_name 1626521040 (2021-07-17 19:24 过期)
5 PEXPIREAT 通过 Unix 时间戳的形式来设置 key 过期时间,与 EXPIREAT 的区别是他需要精确到毫秒 PEXPIREAT key_name 1626521962 (2021-07-17 19:39:22 过期)
6 PERSIST 移除 key 的过期时间,让其成为持久 key PERSIST key_name
7 PTTL 以毫秒为单位返回 key 剩余的过期时间 PTTL key_name
8 TTL 以秒为单位返回 key 剩余的过期时间 TTL key_name
9 SETEX 同时设置过期时间为秒和键值 SETEX key_name 10 "hello"

所谓的 易失 KEY 主要就是被 Redis 命令所设置了过期时间的(如EXPIRE\EXPIRET\PEXPIRE\PEXPIREAT)所进行设置的。

这些易失的KEY只能通过使用 DEL、SET、GETSET、PERSIST 等 STORE(存储) 命令进行清楚或覆盖成为 持久 key(persistent)

只能通过覆盖或清除的方式来让其成为持久key,像诸如更改 key 名称、修改 key 值、自增的做法并不适用,过期时间依然会移植到新的 key 上。

对于通过 EXPIREAT 命令来使用 Unix 时间戳让 key 成为易失 KEY 的方式,可以通过:https://tool.chinaz.com/tools/unixtime.aspx 转换工具来完成。

于此同时需要注意的是 EXPIRESETEX 相比之下 SETEX 区别就是原子性的操作

原子性就是要么同时发生,要么就什么都不发生

关于过期时间的准确性

由于我们所使用的是 redis 2.6 因此过期时间的精度往往比 redis 2.4 提高到 0~1 毫秒之多。需要注意的是如果使用绝对 Unix 时间戳的方式进行存储,那么无论 Redis 是否运行都会流逝,但如果服务器时间并不精准,那也会导致易失 key 可靠性会降低。

过期时间的淘汰流程

Redis key 的过期时间被分为两种淘汰过程,分别为被动方式和主动,而被动方式最好理解的就是当你访问了该 key ,而这时候恰好该 key 是过期的,那么这个 key 就会被发现之后淘汰了。

当然这种方式存在缺点,假设你这个 key 永远不会被访问,那么他岂不是会存在直到你访问的那天,于是 主动过期就出来了。

主动过期的淘汰方式也就是周期性的主动随机检查一部分被设置生存时间的 key,当扫到过期时间到了的 key 将会在 key 空间中删除,Redis 每 10 秒都会执行这个操作。

Redis 安装与简介

Redis 是一个遵循 BSD 协议,高性能且灵活的 key-value 数据结构存储,通常可以用于作为数据库、缓存和消息队列等应用,由于遵循的是 key-value 数据结构存储,他对比其他产品的特点在于:

  1. 支持数据的持久话,可以将数据保存内存或者磁盘中,重启时可以可以加载到缓存或者内存中使用
  2. 支持简单的 key-value 类型数据的数据(同时提供了 list、set、zset、hash 结构存储)
  3. 高性能: redis 的一大特点,一个入门级的 Linux 服务器中可以每秒写入(SET)11w次,读取(GET)8.1w次,同时还支持 Pipeling 命令。
  4. 持久化:也就是说数据都存在内存中的时候,可以根据上次保存的到目前的时间来更新次数,以此他通过异步保存到磁盘上。
  5. 数据结构:Redis 所支持的数据结构有很多,除了常见的字符串、散列、集合、列表等还带有了数据集、位图、超级日志和担忧半径查询的地理空间索引等……
  6. 主/从复制:Redis 分为 client/server,主/从复制只需要一行配置文件即可达成
  7. 生态支持:Redis 支持多个语言,如 Java、JavaScript(含 node.js)、Lua、Objective-C、PHP、Perl、R、Ruby、Scale、Go、C、C++、Python 等主流语言……

安装 Redis

本文即主要介绍 Linux redis 的安装与启动,redis 的安装非常简单,需要通过下载在解压之后重新编译即可:

1
2
3
4
wget http://download.redis.io/redis-stable.tar.gz
tar -zxvf redis-6.0.8.tar.gz
cd redis-6.0.8
make

之后编译完成即可通过通过 cd src 命令进入目录,来启动 redis-server/client 客户端

进入后先启动服务端在启动客户端

./redis-server 以及 ./redis-client,当然这种启动的方式是使用默认配置文件的,如果你对服务也有调整即可使用制定的配置文件启动 ./redis-server ../redis.conf

当然既然有了服务端,那么你也可以将服务端在远程服务器启动,之后通过下述命令启动 redis-client:

1
redis-cli -h host -p port - a "password"

当上述的命令执行完后,在客户端中输入 PING 来查看是否正常启动:

1
2
127.0.0.1:6379> PING
PONG

当返回的是 PONG 的时候即代表服务正常,如果所返回的是 Could not connect to Redis at 127.0.0.1:6379: Connection refused 将会代表服务端出现了问题或断开,这时请仔细检查服务端运行情况。

配置 Redis

在默认的情况下,Redis 是没有密码的,如果需要检测目前是否设置密码,需要通过 CONFIG GET requirepass 进行检测,如果属性为空,则可以通过下述命令来设置密码:

1
2
3
4
5
127.0.0.1:6379> CONFIG SET requirepass "toor"
OK
127.0.0.1:6379> CONFIG GET requirepass
1) "requirepass"
2) "toor"

这时候我们重新启动 redis-cli 后创建一个键,会返回 (error) NOAUTH Authentication required.,这将表示我们之前所整的操作已经被应用,只需要进行身份验证即可:

1
2
127.0.0.1:6379> AUTH "toor"
OK

HBase API 使用

通常 HBase API 的使用是通过 Apache HBase Client 他主要提供了操作 HBase API 的一系列接口和类,新建 Maven 项目后引入依赖即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- https://mvnrepository.com/artifact/org.apache.hbase/hbase-client -->
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>3.0.0-alpha-2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.hbase/hbase -->
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase</artifactId>
<version>3.0.0-alpha-2</version>
<type>pom</type>
</dependency>
Name Info
Admin HBase 的管理 API。
Attributes
BufferedMutator 用于与类似于 Table 的单个 HBase 表进行通信,但用于批量异步放置。
BufferedMutator.ExceptionListener 侦听 BufferedMutator 上的异步异常。
Connection 一个集群连接,封装了与实际服务器的较低级别的单个连接以及与 zookeeper 的连接。
HConnection
HTableInterfaceFactory
RegionLocator 用于查看单个 HBase 表的区域位置信息。
ResultScanner 客户端扫描接口。
Row 一行
Table 一个表

如果需要通过 API 操作 HBase 中的 CURD,那么就需要通过 HBase client 依赖中的 Admin 借口,创建后通过 Table 实例进行访问,以此向表中添加一行内容,Put 对象用于插入指定值、目标列以及时间戳等。

使用 put 提交更新后如果需要获取,那么可以通过 Get 实列进行查询,在特定的行中获取内容,亦或者使用 scan 设置一个扫描仪来返回结果,通常你也可以使用 Delete 来删除单元格或整个 row。

但在此之前你需要通过告诉配置对象你的客户端在那个地方连接,同工厂你可以将 hbase-site.xml 以及 core-site.xml 放在 Maven 项目文件中的 src/main/resources 文件夹内,此时 HbaseConfiguration 会自动帮你寻找。

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
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.util.Bytes;

import java.io.IOException;


public class helloHbase {
public static void main (String[] args) throws IOException {

// 自动检测 hbase-site.xml 和 core-site.xml 配置
Configuration config = HBaseConfiguration.create();
config.addResource("core-site.xml");
config.addResource("hbase-site.xml");
config.addResource("hdfs-site.xml");
config.set("zookeeper.znode.parent","/resources");
config.set("hbase.zookeeper.property.clientPort","2181");
config.set("hbase.zookeeper.quorum","192.168.115.10,192.168.115.11,192.168.115.12,192.168.115.13,192.168.115.14");

// 创建一个链接,用于连接集群,之后关闭
Connection connection = ConnectionFactory.createConnection(config);
try {

// 实列化 Table 对象
Table table = connection.getTable(TableName.valueOf("test"));
try {
// 通过 Put 构造函数添加一行
Put p = new Put(Bytes.toBytes("row2"));

// 向该行插入一条信息
p.addColumn(Bytes.toBytes("myLittleFamily"), Bytes.toBytes("someQualifier"),
Bytes.toBytes("Some Value"));
table.put(p);

// 检索数据
Get g = new Get(Bytes.toBytes("row2"));
Result r = table.get(g);
byte [] value = r.getValue(Bytes.toBytes("myLittleFamily"),
Bytes.toBytes("someQualifier"));

// 返回 "Some Value"
String valueStr = Bytes.toString(value);
System.out.println("Get:" + valueStr);

// 使用 Scan 扫描
Scan s = new Scan();
s.addColumn(Bytes.toBytes("myLittleFamily"),Bytes.toBytes("someQualifier"));
ResultScanner scanner = table.getScanner(s);

try {
// Scan 返回 Result 实列
// 对于迭代使用 while 循环打印
for (Result rr = scanner.next(); rr != null; rr = scanner.next()) {
System.out.println("Found row: " + rr);
}
} finally {
// 完成后关闭 Scan 进程
scanner.close();
}
} finally {
// 关闭表和集群的连接
if (table != null) table.close();
}
} finally {
connection.close();
}
}
}
ERROR
https://stackoverflow.com/questions/71028364/java-api-operate-hbase-api-error-connecting-to-cluster-exception-making-rpc-to

HBase 基础语法

CURD

Create

Create and Table of Coumn familu or Column


Hbase 的表都是由列和族(Column Family)所组成的,列是列族的子项,而每个列或多个列会形成一行(row)。在 HBase 中可以使用 create 创建一个表。

在创建的途中可能会遇到 Hadoop 集群的报错: “util.NativeCodeLoader: Unable to load native-hadoop library for your platform… using builtin-java classes where applicable” 来导致最终 create 语法无法成功插入的报错,此问题的解决就是清除 Hadoop 除去 myid 和 version 相关的集群数据目录,然后重启并格式化即可,在重新启动下 Hbase 集群即可。

1
2
3
4
create 'test', 'cf'
Created table test
Took 11.8491 seconds
=> Hbase::Table - test

通过 create 所创建的,一个是表(table),另一个则是列族(column family),而列(Column)定义是在插入第一条数据的时候才会创建,也就是单元格(call)。所创建的所有数据都定义在列族上,而表的本身则是存放列族和列。

Create Table of Column family

通过 describe 'table' 可以看之前所创建的 cf 列族,而实际上 describe 大部分都是列族的属性,这体现在 alter 'text', 'cf2' 中:

1
2
3
4
5
6
7
8
9
10
11
describe 'test'
Table test is ENABLED
test
COLUMN FAMILIES DESCRIPTION
{NAME => 'cf', BLOOMFILTER => 'ROW', IN_MEMORY => 'false', VERSIONS => '1', KEEP_DELETED_CELLS => 'FALSE', DATA_BLOCK_ENCODING => 'NONE', COMPRESSION => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', BLOCKCACHE => 'true', BLOCKSIZE => '65536', REPLICATION_SCOPE => '0'}

{NAME => 'cf2', BLOOMFILTER => 'ROW', IN_MEMORY => 'false', VERSIONS => '1', KEEP_DELETED_CELLS => 'FALSE', DATA_BLOCK_ENCODING => 'NONE', COMPRESSION => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', BLOCKCACHE => 'true', BLOCKSIZE => '65536', REPLICATION_SCOPE => '0'}

2 row(s)
Quota is disabled
Took 0.0608 seconds

可以看出 describe 所列出的表属性大多数都是根据列族而进行输出的,涉及到表的属性很少。

Put

Hbase 的插入数据也体现在出了高离散的特点,其格式也是 表, 行, 列族:列名, 数据值,也就是必须指定数据值要插入到那一行,那一列当中,对应的命令是: put 'test', 'row1', 'cf:name', 'hello,world!',即使用 put 向 test 表中的第一行,列族为 cf 插入了列明为 name 的 hello,world 数据值。之后通过 scan 'test' 可扫描出:

1
2
3
4
5
> scan 'test'
ROW COLUMN+CELL
row1 column=cf:name, timestamp=2021-12-07T20:14:24.144, value=hello,world!
1 row(s)
Took 0.2802 seconds

通过 scan 可以扫描出该表的所有数据,可以看出对照关系是:

ROW COLUMN+CELL
rowkey column=列族:列名, timestamp=时间戳, value=数据值

值得注意的是单元格可以通过 timestamp 来多个版本的值,通俗来点的说他也起到了版本号(version)的作用,而 HBase 会取版本号最大的数据版本进行输出,但同时在默认情况下 timestamp 是由系统自动生成的,当然我们也可以自行定义时间戳。

在自定义时间戳之前我们需要通过 alter 'test',{NAME=>'cf',VERSIONS=>5} 来修改版本数。

这样一来就算在一个单元格无论修改多少次,HBase 也会保存最后一个版本,如:

1
2
put 'test','row2,'cf:name','date_time',1638909613
put 'test', 'row2', 'cf:name','date_update'

那么通过 scan 的方式肯定会现实时间戳最高的一位进行输出,也就是 date_update,这是因为 date_time 的时间戳转换后是 1970-01-20T08:00:45.613 而,date_update 的时间戳是 2021-12-07T20:40:52.334,所以没有输出 date_time

1
2
3
4
5
6
scan 'test'
ROW COLUMN+CELL
row1 column=cf:name, timestamp=2021-12-07T20:14:24.144, value=hello,world!
row2 column=cf:name, timestamp=2021-12-07T20:40:52.334, value=date_update
2 row(s)
Took 0.0128 seconds

不过我们如果真的需要查询 date_time 的话,可以通过 get 的表达式来获取单元格的数据,也就是 get 'test','row2',{COLUMN=>'cf:name', VERSIONS=>3}

1
2
3
4
5
get 'test','row2',{COLUMN=>'cf:name', VERSIONS=>3}
COLUMN CELL
cf:name timestamp=2021-12-07T20:40:52.334, value=date_update
cf:name timestamp=1970-01-20T08:00:45.613, value=data_timestamp
1 row(s)

对于上述这种表达式,同样的 scan 以及大部分的 hbase 语法都支持,比如我们可以通过 scan 起始行(STARTROW)和结束行(ENDROW)的表达式来进行筛选:

1
2
3
4
5
6
scan 'test',{STARTROW=>'row1',ENDROW=>'row2'}
ROW COLUMN+CELL
row1 column=cf:name, timestamp=2021-12-07T20:14:24.144, value=hello,world!
row2 column=cf:name, timestamp=2021-12-07T20:40:52.334, value=date_update
2 row(s)
Took 0.0793 seconds

同样的基于这种表达式,也赋予了和 Put 一教高下的能力,比如同样的可以使用表达式来查询出多个单元块的版本数据:

1
2
3
4
5
6
7
scan 'test',{VERSIONS=>5}
ROW COLUMN+CELL
row1 column=cf:name, timestamp=2021-12-07T20:14:24.144, value=hello,world!
row2 column=cf:name, timestamp=2021-12-07T20:40:52.334, value=date_update
row2 column=cf:name, timestamp=1970-01-20T08:00:45.613, value=data_timestamp
2 row(s)
Took 0.0773 seconds

Read

list

通过 listlist <table_name> 的方式来查询当前库中有多少个表以及查询单个表,列出刚刚所创建的表是否存在

1
2
3
4
5
6
list 'test'
TABLE
test
1 row(s)
Took 0.3364 seconds
=> ["test"]

describe

查看表和列族的详细信息,实际上有很大一部分的信息输出都是针对列族的,主要的原因是 HBase 中的表上只有几个属性,大部分的属性都在列族上而已。

1
2
3
4
5
6
7
8
9
10
describe 'test'
Table test is ENABLED
test
COLUMN FAMILIES DESCRIPTION
{NAME => 'cf', BLOOMFILTER => 'ROW', IN_MEMORY => 'false', VERSIONS => '1', KEEP_DELETED_CELLS => 'FALSE', DATA_BLOCK_ENCODING => 'NONE', COMPRESSION => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', BLOCKCACHE => 'true', BLOCKSIZE => '65536', REPLICAT
ION_SCOPE => '0'}

1 row(s)
Quota is disabled
Took 2.1830 seconds
Name Name_cn Info
NAME Name family 名 Name_family 的名
BLOOMFILTER 布隆过滤 在 1970 年由 Burton Howard Bloom 所提出,用于测试一个元素是否在特定集中。
ROW 提高随机读取性能
IN_MEMORY 是否在内存中 HBase 所提供的缓存选择,默认为 “false”
VERSIONS 版本数 记录单元格所插入的数据,只保留最后一个版本并记录所有版本的数据
KEEP_DELETED_CELLS 删除标记 是否保留已经删除的单元格,默认为 “false”
DATA_BLOCK_ENCODING 数据块编码 数据块编码,可自行进行选择,默认 “none”
COMPRESSION 压缩算法 决定是否启用压缩算法,默认 “none”
TTL 生存时间 制定后可以达到过期事件后自动删除行,默认为 “FOREVER”
MIN_VERSIONS 最小版本 存储列中的最小版本号,默认为 “0”,也意味着该功能处于禁用状态
BLOCKCACHE 块缓存 Hbase 提供了 LruBlockCache 和 BucketCache 两种缓存机制,通常是 off-heap
BLOCKSIZE 块大小 HDFS 块,单元格越带,块大小就越大,默认值为 64k
REPLICATION_SCOPE 复制范围 允许使用源集群预写日志来传播更改,使一个集群的状态与零一个集群的状态保持同步

scan

通过 scan 可以扫描出该表的所有数据,可以看出对照关系是:

ROW COLUMN+CELL
rowkey column=列族:列名, timestamp=时间戳, value=数据值

值得注意的是单元格可以通过 timestamp 来多个版本的值,通俗来点的说他也起到了版本号(version)的作用,而 HBase 会取版本号最大的数据版本进行输出,但同时在默认情况下 timestamp 是由系统自动生成的,当然我们也可以自行定义时间戳。

在自定义时间戳之前我们需要通过 alter 'test',{NAME=>'cf',VERSIONS=>5} 来修改版本数。

这样一来就算在一个单元格无论修改多少次,HBase 也会保存最后一个版本,如:

1
2
put 'test','row2,'cf:name','date_time',1638909613
put 'test', 'row2', 'cf:name','date_update'

那么通过 scan 的方式肯定会现实时间戳最高的一位进行输出,也就是 date_update,这是因为 date_time 的时间戳转换后是 1970-01-20T08:00:45.613 而,date_update 的时间戳是 2021-12-07T20:40:52.334,所以没有输出 date_time

1
2
3
4
5
6
scan 'test'
ROW COLUMN+CELL
row1 column=cf:name, timestamp=2021-12-07T20:14:24.144, value=hello,world!
row2 column=cf:name, timestamp=2021-12-07T20:40:52.334, value=date_update
2 row(s)
Took 0.0128 seconds

基于表达式的应用,也赋予了和 Put 一教高下的能力,比如同样的可以使用表达式来查询出多个单元块的版本数据:

1
2
3
4
5
6
7
scan 'test',{VERSIONS=>5}
ROW COLUMN+CELL
row1 column=cf:name, timestamp=2021-12-07T20:14:24.144, value=hello,world!
row2 column=cf:name, timestamp=2021-12-07T20:40:52.334, value=date_update
row2 column=cf:name, timestamp=1970-01-20T08:00:45.613, value=data_timestamp
2 row(s)
Took 0.0773 seconds

Delete

Delete of data and tombstone marker

Delete 其如其名就是删除,同样的根据高离散的格式来定义表达式,如 delete 'test','row1','cf:name'

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
> scan 'test',{VERSIONS=>5}
ROW COLUMN+CELL
row1 column=cf:name, timestamp=2021-12-07T20:14:24.144, value=hello,world!
row2 column=cf:name, timestamp=2021-12-07T20:40:52.334, value=date_update
row2 column=cf:name, timestamp=1970-01-20T08:00:45.613, value=data_timestamp
2 row(s)
Took 0.0181 seconds

> delete 'test','row1','cf:name'
Took 0.0246 seconds

> scan 'test',{VERSIONS=>5}
ROW COLUMN+CELL
row2 column=cf:name, timestamp=2021-12-07T20:40:52.334, value=date_update
row2 column=cf:name, timestamp=1970-01-20T08:00:45.613, value=data_timestamp
1 row(s)
Took 0.0080 seconds

而这个时候就会牵扯到版本记录,如我们将 row2 的时间戳 1641645000 删除,则可以构造表达式 delete 'test','row2','cf:name',1670445613

很多时候 Hbase 的时间戳转换的格式为 yyyy-MM-dd'T'HH:mm:ss.SSS,我们可以直接在 hbase shell 中来进行转换:

  1. import java.text.SimpleDateFormat
  2. import java.text.ParsePosition
  3. SimpleDateFormat.new(“yyyy-MM-dd’T’HH:mm:ss.SSS”).parse(“1970-01-20T08:00:45.613”,ParsePosition.new(0
    )).getTime()
1
2
3
4
5
 scan 'test',{VERSIONS=>5}
ROW COLUMN+CELL
row2 column=cf:name, timestamp=2021-12-07T20:40:52.334, value=date_update
1 row(s)
Took 0.0449 seconds

上述提到的墓碑标记(tombstone marker) 即被删除的数据,他并不是被真正的删除而是被打上了一个墓碑标记,打上后会连同之前的版本也会被标记为不可见。

同时为了性能着想他并不会马上清除,而是定期的去清理这些已经被删除的记录,而所谓的定期则是在 HBase 做自动合并的时候将墓碑合并到一起进行删除,以此来将 HBase 的消耗降到最低,我们可以通过 scan 'test',{RAW=>true,VERSIONS=>5} 来进行查看被打赏墓碑标记的数据 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
> scan 'test',{RAW=>true,VERSIONS=>5}
ROW COLUMN+CELL
row1 column=cf:name, timestamp=2021-12-07T20:14:24.144, type=Delete
row1 column=cf:name, timestamp=2021-12-07T20:14:24.144, value=hello,world!
row2 column=cf:name, timestamp=2021-12-07T20:40:52.334, value=date_update
row2 column=cf:name, timestamp=1970-01-20T08:00:45.613, type=Delete
row2 column=cf:name, timestamp=1970-01-20T08:00:45.613, value=data_timestamp
row2 column=cf:name, timestamp=1970-01-20T00:00:45, type=Delete
row2 column=cf:name, timestamp=1970-01-19T23:15:09.613, type=Delete
row2 column=cf:name, timestamp=1970-01-19T23:14:40.852, type=Delete
row2 column=cf:name, timestamp=1970-01-01T00:27:21.645, type=Delete
row2 column=cf:name, timestamp=1970-01-01T00:27:18.911, type=Delete
2 row(s)
Took 0.0579 seconds

deleteall

deteall 与单纯的 detell 的区别是他是删除整行目录的,也就是说他无需指定列族,指定到 rowkey 即可进行删除该 rowkey 下的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
> scan 'test'
ROW COLUMN+CELL
row2 column=cf:name, timestamp=2021-12-07T20:40:52.334, value=date_update
1 row(s)
Took 0.1310 seconds

> deleteall 'test','row2'
Took 0.0148 seconds

hbase(main):062:0> scan 'test'
ROW COLUMN+CELL
0 row(s)
Took 0.0095 seconds

disable

disable 的命令作用是停用表,也就是更加真实应对分布式数据库高并发高和高性能下所提供的一个命令,在删除 HBase 表的时候强烈建议西安 disable 在 drop 删除表。

如果 disable 目标表下线的时候很快则表明这个表的依赖度不高,但如果假设依赖度很高的话则会很慢,因为他会通知所有 RegisionServer 来下线该表,以保证完全不参与任何工作了在进行下线。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
hbase(main):063:0> disable 'test'
Took 15.3142 seconds
hbase(main):064:0> scan 'test'
ROW COLUMN+CELL
org.apache.hadoop.hbase.TableNotEnabledException: test is disabled.
at org.apache.hadoop.hbase.client.ConnectionImplementation.relocateRegion(ConnectionImplementation.java:770)
at org.apache.hadoop.hbase.client.RpcRetryingCallerWithReadReplicas.getRegionLocations(RpcRetryingCallerWithReadReplicas.java:330)
at org.apache.hadoop.hbase.client.ScannerCallable.prepare(ScannerCallable.java:139)
at org.apache.hadoop.hbase.client.ScannerCallableWithReplicas$RetryingRPC.prepare(ScannerCallableWithReplicas.java:408)
at org.apache.hadoop.hbase.client.RpcRetryingCallerImpl.callWithRetries(RpcRetryingCallerImpl.java:105)
at org.apache.hadoop.hbase.client.ResultBoundedCompletionService$QueueingFuture.run(ResultBoundedCompletionService.java:80)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)

ERROR: Table test is disabled!

For usage try 'help "scan"'

当该表下线后则无法使用 scan 命令进行查询,并会输出错误信息,来表示当前表已经被关闭,无法 scan。

drop

当使用 disable 停用表后,则开始基于 drop 进行删除表,在此之前我们使用 list 还是可以发现 test 表存在的,但如果使用 drop 'test' 后,则真的无法使用 list 查看到 test 表的任何痕迹了。

HBase 与分布式数据库概述

分布式数据库系统(Distributed Database System, DDBS),研究开始于 20实际70年代中期。随着数据库应用需求的扩展和信息技术特别是计算机网络与数字通信技术的飞速发展,集中式的单体数据库系统已经无法适应这样的环境。

DDBS 起源

集中式数据库痛点

  1. 对于集中式数据库,主要的不足体现在数据按照需要在网络上分布存储,假设采用集中式处理,将会造成附加成本和通信的开销。

  2. 应用集中在一台计算机或服务器中运行,一旦发生故障将会导致整个系统的运行,可靠性受到威胁。

  3. 集中式的处理导致系统规模和配置不够灵活,可扩展性较差,这种情况下数据库应用普遍构建于网络上。

在 2004 年左右或之前,英国国家计算机中心(United Kingodom National Computing Centre, UK-NCC) 专门对分布式数据库进行了分析和预测,并表明:“在未来 10 年后计算机科学发展的主要方向之一”。

在 2021 年的今天很明显证实了这一点

信息孤岛 (Information Island)

例如大型互联网公司、政府机关等单位中,这些组织内都有一个自己所维护的数据库,比如开发部门(bj-dev),行政部门(sz-adm),这些数据库通常是已经分布的了。

在这种情况下,企业的整个信息资源就被分割成了信息孤岛,而分布式数据库的主要作用就是将信息孤岛连接起来。

做个比较明显的比喻就是分布式系统可以明显的体现出组织的系统架构,本地是数据本地保存或维护,同时也可以在需要时存取异地数据,而这种操作被称之为分布式数据库。

世界上第一个分布式数据库是 SDD-1(System for Distributed Database),由 CCA(Computer Corporation of Amerca)

DDBS 发展

初期

分布式数据库系统在 20实际 70 年代末时期诞生,在 80 年代进入了成当阶段,因为计算机功能的增强以及成本的下降,使得计算机广遍普及。也随着计算机网络的发展,降低了数据网络的传输费用,以及个人计算机的出现在计算机局域网的广泛发展。

这些都是分布式数据库系统的研制和实现所提供了必要的基础条件,无论是在民用还是军用领域中,各国都在分布式数据库系统的研究投入了大量的人力、物理和财力。

德国与美国

例如德国的斯图加特大学所研制的 POREL 历经 11 年,投资 450w 德元,美国的 IBM 公司 20 世纪 70 年代由 San Jose 实验室所研制的 System R。

San Jose 实验室后来更名为 IBM Almaden 研究中心。

西欧

美国加利副尼亚大学伯克利分校(Universiry of Californaia, Berkeley)研制的分布式 1n-gres 和荷兰阿姆斯特丹大学所研制的 Ingres,在 Unix/PDP 上实现。

法国 IN-RIA 所研制的 SIRIUS-DELTA 系统和 IMAG 研究中心所研制的 MICROBE 系统。

国内

中国对分布数据库系统在 20 世纪 80 年代初期,国内一些科研单位和高等院校先后建立和实现了几个各具特色的分布式数据库,在这其中有中国科学院数学与系统科学研究院设计,和上海大学以及华东师范大学所合作实现的 C-POREL

以及上海大学和华东师范大学所合作实现的 C-POREL,以及武汉大学所研制的 WDDBS 和 WOODDBS ,东北大学研制的 DMU/FO 系统等。

标准化

在 1987 年,关系数据库最早的设计者之一 C.J.Date,在 “Distributed Database: A Closer Look” 中提出完全的、真正的分布式数据库管理系统所应该遵循的 12 条规则。

  1. 本地自治性 (Local Autonomy)
    本地站点的独立性 (Local site independence),每个本地站点都可以作为一个独立、资质的集中的分布式数据库系统,并负责安全、并发控制、备份和恢复。

  2. 中央站点独立性 (Central site independence)
    不依赖于中心站点(No Reliance On Central Site),网络中没有站带你依赖于中心站点或其他任何站点,所有的站点都具备相同的功能。

  3. 可连续操作性 (Continuous Operation)
    失败的独立(Failure independence)即故障的对系统无影响,即使在节点故障的网络情况下,系统也可以连续运行。

  4. 数据位置透明性和独立性 (Location Transparency and Location Independ-ence)
    位置的独立性 (Location Transparency),用户不需要知道数据的位置就可以检索这些数据。

  5. 数据分片的独立性 (Fragmmentation Independence)
    分裂的透明度(Fragmentation transparency)数据碎片对用户来说是独立的,用户只能看到一个逻辑数据库,用户不知道数据库片段的名称可以检索他们。

  6. 数据复制的独立性 (Fragrnentation Independence)
    复制的透明度(Replication transparency)用户只能看到一个逻辑数据库,分布式数据库系统独立的选择访问数据库片段,对于用户来说分布式数据库系统可以独立管理所有片段。

  7. 分布式查询处理 (Distributed Query Processing)
    分布式查询可能在多个不同的数据库站点执行,查询优化是分布式数据库独立执行的。

  8. 分布式事务管理 (Distributed Transanction Management)
    一个事务可以在不同的站点上进行数据的更新,并且该事物是透明执行的。

  9. 硬件独立性 (Hardware Independence)
    是指系统必须在任何硬件平台上运行。

  10. 操作系统独立性 (Operatnig System Independence)
    该系统必须在任何操作系统中运行。

  11. 网络系统的独立性 (Operating System Independence)
    必须在任何网络平台上运行。

  12. 数据库管理系统独立性 (DBMS Independenece)
    系统必须支持任何厂商的数据库产品

上述的 12 条是真正的分布式数据管理系统所应该遵循的 12 条规则,这也被行业而广泛的接受,有助于规划一个特定的分布式数据库系统的功能,从而也有助与区分一个真正普遍意义上的分布式数据库系统。

商业化

20世纪90年代开始,分布式数据库开始进入商业化应用阶段,数据库厂商不断推出和改进自己的分布式数据库产品,来适应客户的需求和扩大市场份额。

对于分布式数据库系统,还需要网络技术的相互渗透和有机融合后得出的结果,他不仅带来了新的技术,还有了自己特色的理论基础和固有的技术难度,这就导致了分布式数据库系统的应用被延迟。

而对于现在,一些商业化的数据库产品如 ORACLE、IBM DB2、SYBASE、Microsoft SQL Server 以及 MySQL 等大都提供了对分布式数据库的支持(虽然支持的程度不同)

本世纪的发展

随着 21 世纪下 Web 2.0 的兴起使得互联网上的站点数据大量增长从而促进了搜索引擎技术的萌芽,之后的 Google 或 百度等的数据也有海量的趋势增长。

BigTable 与 GFS

对于这些流量的日益增长,传统的分布式数据库已经不在适用,相应的也出现了新的分布式海量数据的组织和管理方式。在这其中 Google 所设计的 BigTable 用于管理结构化数据。

Big Table 通过利用 Google 分布式文件系统 (Google File System) 来实现数据的分布式存储和管理。

Big Table 使用与 PB (Petabyte, 1PB=10E15B),则表示拥有 1500000000000000 千万亿级数据处理和上千台的数据分布,因此他具有高可测量、高可用和高效以及高可靠的特点。

多级映射结构

BigTable 不是关系型数据库而是多级映射的数据库结构,这种结果用于面向大规模的数据处理且容错性较强的自我管理系统,拥有 TB(Terabyte,万亿字节) 级的内存和 PB(Petabyte,千万亿字节 ) 级的存储能力,每秒可以处理百万次的读写操作。

虽然 BigTable 不支持关系型数据查询,但是却是建立在 GFS/MapReduce 基础上的分布式存储大规模结构化数据的优秀的解决方案。

HBase

不同于 BigTable,HBase 利用了 Hadoop 分布式文件系统来管理和存储数据,同样不同于一般的关系数据库,他还是一个适合于非结构化数据存储的数据库,HBase 主要用于需要随机访问、实时读写大量数据的场景中。

Not Only SQL

关系型数据库的痛点

对于关系型数据库,如 MySQL 或 Oracle 这样主流的关系性数据库而言,一个站点的表最为核心的就是用户的数据表,而当用户表达到 KB(Kilobyte,千字节)的情况下。

对单条数据的检索就会花上数秒或分钟级别的查询时间,而实际情况下会变得更加复杂,假设在查询的过程中又有几十个或几百个数据插入、编辑或删除等,一此时数据的查询的延迟将会轻易达到分钟级别。

在或者,需要查询的数据可能还需要设计到联合查询之类的操作,或者更为复杂的关联查询,则会造成数据库的性能大幅度的下降。

CAP

在 CAP 理论提出之前,很多的研究人员尝试将关系型数据库做成分布式架构的数据库,将数据的压力平坦分流到多个数据服务器上,而这期间很难保证原子性和 ACID

原子性(Atomicity)、一致性(Consistency)、独立性(Isolation)、持久性(Durability)

原子性指一个操作不可中断,要么全部成功,要么全部失败

如何平衡原子性和高性能数据库的方式,截至到在 20 世纪 90 年代初期的 Berkerly 大学 Eric Brewer 教授所提出的 CAP (Consistency Availability and Partition tolerance)理论所提出之前,该问题一直是关系型数据库的痛点。

布鲁尔定理(Brewer’s theorem)也被称之为CAP定理(CAP theorem),主要指出在分布式系统中的:

  1. 一致性(Consistency), 数据一致更新,所有的数据变动都是同步的
  2. 可用性(Availability),每次请求都能获取到正确的响应,拥有良好的响应性能
  3. 分区容错性\可靠性(Partition tolerance),保证服务宕机时其他服务依然可以正常提供服务(系统中任意信息丢失或失败不会影响系统的继续运作)。

对此 Brewer 教授给出的定理是:“任何分布式系统只可能满足两点,无法兼备三者”,并给出了忠告:“架构师不要将精力浪费在如何设计出兼备三者的完美的分布式系统,而是如何进行取舍”。

对此通过 CAP 定理,我们得知无法同时满足一致性、可用性、分区容错性这三个特性,因此假设:

  1. CP
    即 一致性、分区容错性,牺牲掉了可用性保证了一致性,可能会有几个节点不可用,通常适用与银行系统。

  2. AP
    是可用性和分区容错性,舍弃掉了一致性,保障服务可用但可能会造成数据的冲突,可适用流量访问大对系统正常提供服务要求较高的系统;

  3. CA
    一旦集群中一台服务无法正常提供服务则会造成当前集群完全崩溃,适用与挑战者。

NoSQL

对 CAP 特性的放弃打来了一种更加新颖的非关系型数据库,和关系型数据库相反,他对事务性的要求并不严格。

对于非关系型数据库而言,我们假设数据库保证了最终一致性,即信息不会立即同步,而是经过了一定的时间才达到一致性,而不是像传统的关系型数据库,有很大可能会造成性能的下降。

事务的持久性

事务的提交可以被分为完全持久或延迟持久(通常被称之为迟缓提交),完全持久事务提交是同步的,假设在日志吸入磁盘后立马就会提交成功并进行发布。

而对于延迟持久事物的提交是异步的,他只要最终将事务持久性最终写入磁盘即可。完全和延迟事务的类型各有所长,一个服务能够同时包含完全和延迟持久事物,应该根据其业务需求进行设计。

HBase 概述

2006 年 Google 技术人员 Fay Chang 发表了 Bigtable: A Distributed Storage System for Structure Data,向人们介绍了分户是数据库,并可以在几台服务器崩溃的情况下继续提供高性能的服务。

进跟着 2007 年 Powerset inc. 内的技术人员根据该文章研发了 BigTable 的 Java 开源版本,即就是 HBase,虽然刚开始他只是 Hadoop 的一部分。

不久之后的2008年,HBase 成为了 Apache 的顶级项目,HBase 几乎实现了 BigTable 的所有特性,他也被称为开源的分布式非关系型数据库。

在随后的 2010 年 HBase 的开发速度打破了一直以来紧跟着 Hadoop 版本一致的管理,这是因为 HBase 的版本发布速度已经超越了 Hadoop。

Hadoop 与 HBase

HBase 的存储基于 Hadoop ,他的崛起是因为高性能、高稳定、可管理的大数据应用平台,因此 Hadoop 实现了一个分布式文件系统 HDFS(Hadoop File System),有着高容错的特点,通常用来部署在价格低廉的硬件中,这也意味着基于 Hadoop 的 HBase 拥有着与与生俱来的扩展性吞吐量。

优缺点

HBase 所采用的是 Key/Value 的存储方式,这也意味着即使处理海量数据,也不会导致数据的下降,反观 HBase 又是一个列式数据库,可以将子字段放在不同的集群中的机器上来分散负载压力。

而这样的存储结构和分布式的存储方式所带来的问题就是哪怕是存储少量的数据,HBase 并不会很快,但是对于海量数据的时候,他慢的没有关系型数据库慢。

因此只有在单表数据量超过千万,且高并发环境,对数据分析的需求较弱或者不需要经常使用,那么则需要使用 HBash。

部署环境


Hbase 从大到小划分为 Master 服务器和 RegionServer 服务器。其中 Master 为注册中心,而 RegionServer 即服务器端。

RegionServer 所保存的数据是直接存储在 Hadoop 的分布式文件系统中(HDFS):

而在一个完整的 HBash 环境中,RegionServer 非常依赖 ZooKeeper 服务,如果没有 ZooKeeper 则 HBase 的发展也会收到影响,因为在 HBase 的生态中, ZooKeeper 主要扮演一个类似管家的一个角色,管理了 HBase 所有的 RegionServer 信息(服务治理),其中包括具体的数据段存放在那个 RegionServer 中。

ZooKeeper 提供了配置维护、域名服务、分布式服务、组服务、服务治理等功能

客户端每次于 HBase 连接,就是与 ZooKeeper 通信,查询出需要链接的 RegionServer 需要连接,然后在链接 RegionServer。

RegionServer

RegionServer 是存放 Region 的容器,直观上了解就是服务器上的服务,简单来说一个服务只能安装一个 RegionServer(在未开虚拟化的情况下),当客户端从 ZooKeeper 获取到 RegionServer 的地址后,可以从 RegionServer 获取数据。

客户端从 ZooKeeper 获取到 RegionServer 地之后,也可以从 RegionServer 中获取数据,以及插入、删除等所有操作都是直接操作 REgionServer,之后的 Master 只负责协调各种工作

当 HBash 负载均衡的时候,也有可能从一台 RegionServer 上把 RegionServer 到另一台 RegionServer 上。

Region

Region 是一段数据的集合,HBase 表中一般哟更有一个到多个 Region,他不能跨服务器,数据量小的时候一个 Region 足以存储所有数据,但是在数据量打的时候,HBase 会拆分 Region。

Region 是基于 HDFS 的,他的所有数据存取操作都是调用了 HDFS 的客户端接口来进行实现。

Master

Master 在整个 HBase 架构中负责协调各种工作,如建表、删表、移动、合并 Region 等操作,他们的通电就是需要跨越 RegionServer,这个操作由他本身来执行就显得很臃肿和不合适,因此就将这个操作放到了 Master 之上了。

这类结构的好处在于降低了 Master 的依赖,假设 Master 就诶点一般只有一个到两个,一旦宕机则会对集群造成单点故障,因此在 HBase 中,即使 Master 宕机了,集群依然可以正常工作。

存储架构

列(Column)

在 HBash 中最基本的存储单位就是列一个列或多个列形成一行 (row),传统数据库是严格的行列对齐,比如这一行有两列,每列都有多个版本,多个版本的值都存储在单元格 (cell) 中,若干个列又可以被归类为一个列族。

单元格(Call)

列族和列并不是唯一能够确定一个值的方式,通过单元格,一个列可以存储多个版本的值,多个版本的值都存储在一个单元格之中,为此通过 version 来进行区分。

对此唯一可以确定一条结果的表达式应该为:列族:列:版本号 (rowkey:column family:column:version)

列族(Column family)

在 HBase 中,若干个列可以组成列族(Column Family),在建表中不需要创建列。这是由于他可变且灵活,在这个过程中唯一需要确定的就是列族。

如过期时间,数据块缓存以及是否压缩都是定义在列组上,而不是定义在表上或列上。

同一个表中不同的列组可以有完全不同的属性配置,同一个列组内的列组都会有相同的属性。

原因在于都是在同一个列族之中,而属性是定义在列族上的。

列必须依赖列组的存在,在 HBase 中,一个列的签名会带着他的所属列族,列名称的规范在于 列族:列名,如:brother:age、brother:name、parent:age、parent:name

他的主要的作用就是 HBase 会将列族的列尽量放到同一台服务器中,如果要想将列合并在一起,就需要定义相同的列族。

想对于生产环境中,版本号是可以被忽略的,如果不填写版本号将会默认获取最后一个版本的数据返回。

每个列或单元格的值都被赋予了一个时间戳,这是由系统默认进行并制定的,也可以自定义显示。

行键(Row Key)

每一个行 (row) 都有唯一的行键(row key),来标定这个行的唯一性,而 row key 和 MySQL、Oracle 中的主键比起来就简单许多,行键是由用户指定的一串不重复的字符串,rowkey 会决定该 row 的存储位置,HBase 无法根据某个 列来进行排序,系统永远根据 rowkey 来进行排序,因此 rowkey 就决定 row 存储顺序的唯一凭证,例如:

  1. row-1
  2. row-2
  3. row-11

那么经过 HBase 字典排序后则顺序为:

  1. row-1
  2. row-11
  3. row-2

假设将数据插入 HBase ,用了之前存在的 rowkey,那么之前存在的 value 将会被更新,而之前所存在的 value 将会被存放在 column 的 call (即版本单元格)

Region 和行的关系

一个 Region 就是多个行的集合,Region 中的行按照键 (rowkey) 的字典来进行排序。

高离散


相对与关系性数据库,HBase 内的行都是离散的,所以一个行里面的列无论被划分到了不同的服务中,行的概念都被进行一定的削弱。

因为离散存储的关系,在 HBase 中,每个存储或编辑的语句都需要写出数据要被存储到那个单元格,即:表,行,列族:列名 进行定义,也就是说要精准的写出要将数据存储到那位置中。

而对比关系型数据库,只需要通过 insert 语句来一次性将数据写入在数据库中。

Hadoop+Zookeeper+HBase


Hbase 是一个依赖于 Hadoop+ZooKeeper 的开源的非关系型数据库,而 Hadoop 是一个 Google File System 论文的实现,用于支持数据密集型分布式应用程序。在此之前 HBase 只是 Hadoop 的子项目,而 Hadoop 是在 Google File System 在 2003 年之后的 2004 年 Doug Cutting 基于 MapReduce、Nutch 搜索引擎实现了自己的分布式文件存储系统被命名为 *NDFS(Nutch Distributed File System)*,而 Google 自己的分布式文件存储系统被称之为 GFS(Google File System)

同年 Google 又发布了一篇技术学术论文,展示了 MapReduce 编程模型,用于大规模数据集的并行分析运算,而后的 1 年时间里,也就是 2005 年,Docug Cutting 基于 MapReduce 在 Nutch 搜索引擎中实现了这一功能并命名为 MapReduce,之后 Diug Cutting 被 Yahoo 收购后改名 Hadoop,

2006 年Bigtable: A Distributed Storage System for Structured Data 发布而后 Diug Cutting 在自己的 Hadoop 进行集成了 Bigtable 后加入到 Hadoop 中命名为 HBase,这也意味着 HBase 具有高性能、高可扩展性基于 HDFS 的文件存储系统,用于存储大规模结构化数据,适用于云计算。

HBase 核心

Hadoop

HDFS

HBase 核心就是 HDFS(Hadoop File System)和 MapReduce,由于 HBase 是基于 HDFS 上运行,每个存储的文件也被称之为 HDFS 文件,具有高度的容错能力,可在部署在低成本的硬件中,并提供高吞吐量的访问,适用于具有大型数据集的应用程序。

大概设计上是通过 The Google File System 进行实现,阅读 HDFS 的 官方文档 基本上等同于阅读 The Google File System

id Name Info
1 硬件故障 HDFS 通常可能由几百台或几千台计算机组成,每台计算机都村粗文件系统数据的一部分,实际上大量的组件可能会出现故障概率,因此 HDFS 可以检测故障并从中快速且自动回复故障是架构的核心目标
2 流数据访问 流式数据访问的特点就是像水一样,不是一次写入而是一点一点写入,如果是全部收到数据后在处理,那么延迟会加大,会消耗大量的内存。HDFS 更多的是为了批处理而进行设计,重点的数据访问的高吞吐量而不是数据访问的低延迟。
3 大型数据集 在 HDFS 上运行的程序具有大型数据集,HDFS 中典型的文件大小为 GB~TB ,因此 HBase 也支持大文件,应提供高举和数据贷款并扩展到单个集群中的数百个节点
4 简单的一致性模型 HDFS 应用程序对文件采用一次写入多次读取的访问模型,文件一旦创建后写入和关闭除追加和截断意外无需更改。支持i将内容追加到文件末尾但不能在任意节点中更新,这简化了一致性问题并实现了高吞吐量数据访问
5 移动计算比移动数据更便宜 应用程序请求的计算在他所操作的数据附近所执行的,那么他的效率会搞得很多,当数据集规模较大的时候依然如此。
6 跨异构硬件和软件平台的可移植性 HDFS 被设计易于从移植到另一个平台,可移植性有益于 HDFS 作为大量应用程序的首选平台

上述六个是 HBase 的假设和目标,来让 HBase 成为一个非关系型数据库,在 Eric Brewer 的 CAP 理论中,HBase 属于 CP 类系统,保证了一致性但对可用性或缺,可能会有几个节点不可用,通常适用与银行系统。

Namenode and Datanode


HDFS 是一个主/从架构,集群由 NameNode 组成,作为主服务器,用于管理文件系统命名空间并调节客户端对文件的访问。而 Datanode 管理附加到运行他们的节点存储,HDFS 文件系统命名空间,并允许用户数据存储在文件夹中,在内部被拆分为一个或多个块,这些块都存储在 DataNode 中。

NameNode 执行文件系统命名空间操作,如打开、关闭和重命名目录,并确定块到 DataNode 的映射,DataNode 负责为来自文件系统客户端的读取和请求提供服务,DataNode 还根据 NameNode 的执行来执行块的创建和删除以及复制。

Hadoop HA

Hadoop 分为 HA(High Avaliable,高可用性)模式和非 HA 模式,在生产环境中 Hadoop 都应为 HA 模式,以保障整个集群中 Namenode 节点没有单点故障的问题,HA 模式主要用于解决单点故障。

如果一个集群太依赖一个点,而那个点宕掉了就算其他集群是好的,而整个集群也相当于整体瘫痪,他通常会出现在元数据存储节点中,而这种节点通常出现在 namenode 上,Hadoop HA 的做法就是启动两个或多个 Namenode,一个处于 active(活跃) 状态,其他的机器则处于 standby(后备机) 状态,他只是单纯同步 active 数据,当活跃机宕掉后可直接自动切换过去,这也被称之为 HA 模式。

Zookeeper

Zookeeper 是一个轻量级的分布式架构集群,同时也是 Hadoop 和 HBase 的重要组件,提供了配置维护、域名服务、分布式同步、组服务、服务治理等功能,Zookeeper 的集成使得我们可以直接依赖 Zookeeper 来维护节点,而不是自己在编写节点的注册、取消、维持和存活检测、节点失效等代码。

Hadoop

Vagrant

在正式安装 Hadoop 之前我们需要依赖于 vagrant 以及 Oracle VM VirtualBox 来搭建 Centos 系统环境,共为 5 台机器。

前三台分别被称之为 nn01、nn02,作为 namenode,后三台作为 datanode,分别为 dn01、dn02、dn3。

对于第一次使用 vagrant 作为主要的虚拟机管理工具的读者,我们首先需要通过 vagrant init centos/7 然后直接 vagrant up 即可。

但由于一系列不可抗拒的因素国内可能通过这样下载很慢,我们也可以借助工具来下载他的 .box 文件,下载后可以通过下述几个命令来进行启动:

1
2
3
vagrant init 'D:\Vagrant Dabatables\hbase\CentOS-7-x86_64-Vagrant-2004_01.VirtualBox.box'
vagrant add box CentOS-7-x86_64-Vagrant-2004_01.VirtualBox.box --name centos/7
vagrant up

当看到 vagrant up 成功启动 Centos 之后,接下来输入 vagrant ssh 以连接虚拟机的 Shell 进行操作,除此我们还可以使用 supend(挂起)、reload(重启)、halt(关机)、status(查看状态) 等参数提升效率。

在上述的操作过程中(泛指 vagrant init) 之后会产生一个 Vagrantfile 文件,我们需要修改并设置与物理机共享的文件夹,以及固定的 IP 等配置:

尽管 config.vm.box 可能会报错,改为 .box 所在地址即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Create a private network, which allows host-only access to the machine
# using a specific IP.
config.vm.network "private_network", ip: "192.168.115.10"


# Create a public network, which generally matched to bridged network.
# Bridged networks make the machine appear as another physical device on
# your network.
config.vm.network "public_network"

# Share an additional folder to the guest VM. The first argument is
# the path on the host to the actual folder. The second argument is
# the path on the guest to mount the folder. And the optional third
# argument is a set of non-required options.
config.vm.synced_folder "src/", "/srv/website"

Host Configuration

Host name

在了解到 vagrant 基本的使用方法后我们可以搭建 HBase 所需要的环境,当然这是通用的,我们只演示 nn01 的配置方法,之后 nn02、dn01、dn02、dn03 跟着随后即可,可以说名称和IP不一样。

通过 vi /etc/hosts 文件将所有机器的 IP 和机器名都配置到 hosts 文件中,以方便我们使用 ssh 根据对应的节点名称来进行连接(对于网段可以根据自身环境来进行配置)

1
2
3
4
5
192.168.115.10 nn01
192.168.115.11 nn02
192.168.115.12 dn01
192.168.115.13 dn02
192.168.115.14 dn03

为防止之后通过 ssh nn1 的时候可能会提示 Permission denied (publickey,gssapi-keyex,gssapi-with-mic). 的错误消息,我们可以通过 vi /etc/ssh/sshd_config 来进行更改配置:

1
2
3
4
5
6
7
8
# To disable tunneled clear text passwords, change to no here!
PasswordAuthentication yes
#PermitEmptyPasswords no
#PasswordAuthentication no

# Change to no to disable s/key passwords
#ChallengeResponseAuthentication yes
ChallengeResponseAuthentication no

最后通过 sudo systemctl restart sshd 来重启下 sshd 服务,在重新进行测试即可,为区分节点之间的名称建议通过 hostnamectl set-hostname <name> 来修改当前主机名称以方便之后在 ssh 还是在控制台内的查看集群信息。

同样的还有 /etc/sysconfig/network 文件下配置当前的 hostname 之后通过 service network restart 重新穷下。

Shell No password

useradd hadoop 新建用户 hadoop 并为其设置 passwd hadoop 密码后通过 sudo chmod u+w /etc/sudoers 将 hadoop 添加到 sudoers 列表,向 /etc/sudoers 文件中添加

1
2
3
## Allow root to run any commands anywhere
root ALL=(ALL) ALL
hadoop ALL=NOPASSWD:ALL

使用 ssh-keygen -t rsa 生成 id_rsa.pub 后以 scp 将自身的 rsa 密钥分发给 nn01,scp .ssh/id_rsa.pub hadoop@nn01:/home/hadoop/.ssh/nn02_keys,当然也包括自己的,这将使得可以自己 shell 免密连接自己,并设置目录权限:

1
2
3
4
5
cd .ssh/
cat dn01_keys dn02_keys dn03_keys nn01_keys nn02_keys >> authorized_keys
sudo chmod 700 /home/hadoop/
sudo chown hadoop:hadoop ./.ssh/
sudo chmod 700 .ssh/authorized_keys

最后使用 scp authorized_keys hadoop@nn02:/home/hadoop/.ssh/authorized_keys 文件分发给 nn02、dn01、dn02、dn03 机器中。

Java 1.8 install

在安装和配置 Hadoop 之前,我们需要安装 Java 环境且至少实在 1.8 版本以上,我们直接从 https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html 下载系统对应的文件并通过 sudo vi /etc/profile 添加环境变量后刷新即可 source /etc/profile

1
2
3
export JAVA_HOME=/usr/jdk1.8.0_202
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export PATH=$PATH:$JAVA_HOME/bin

为了防止普通用户无法使用 Java 还需要设置普通用户的环境变量 sudo vi ~/.bashrc 同样的向此文件添加 JDK 所在位置并使用 source ~/.bashrc 刷新即可:

1
2
3
export JAVA_HOME=/usr/jdk1.8.0_202
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export PATH=$PATH:$JAVA_HOME/bin

正常环境下直接通过 /etc/profile 配置全局的环境变量即可,可根据实际环境自行选择

单机模式与集群模式


当上述配置完成之后,开始安装 Hadoop,这也是 HBase 的运行基础,在此之前我们先看下当前系统的磁盘最大分区,以决定是否要修改日志存储目录:

1
2
3
4
5
6
7
8
df -h
Filesystem Size Used Avail Use% Mounted on
devtmpfs 237M 0 237M 0% /dev
tmpfs 244M 0 244M 0% /dev/shm
tmpfs 244M 4.5M 240M 2% /run
tmpfs 244M 0 244M 0% /sys/fs/cgroup
/dev/sda1 40G 3.4G 37G 9% /
tmpfs 49M 0 49M 0% /run/user/1000

我的系统分区最大是 / 也就是说无需更改他的日志存储路径,如果你的磁盘大小和我的不一样那么建议之后在 Hadoop 的配置文件中更改日志存储位置即可,可通过清华大学镜像站直接进行下载 Hadoop 并解压:https://mirrors.tuna.tsinghua.edu.cn/apache/hadoop/common/hadoop-3.3.1/

通过 tar zxvf hadoop-3.3.1.tar.gz 解压文件并重命名为 hadoop(mv hadoop-3.3.1 hadoop)并移动到 usr/local 文件夹中,之后在 ~/.bashrc 添加环境变量:

1
2
3
4
# Hadoop_Home
export HADOOP_HOME=/usr/local/hadoop
export PATH=$PATH:$HADOOP_HOME/bin
export PATH=$PATH:$HADOOP_HOME/sbin

保证可以输入 hadoop 出现参数提示即可,但不要忘记使用 source ~/.bashrc 刷新配置,以及在 /usr/local 目录那为 hadoop 设置用户组为 hadoop(sudo chown hadoop:hadoop /usr/local/hadoop/*

进入到 /usr/local/hadoop/etc/hadoop 目录下配置 core-site.xml 文件并写入节点名称

1
2
3
4
5
6
<configuration>
<property>
<name>fs.defaultFS</name>
<value>hdfs://nn01:8020</value>
</property>
</configuration>

之后编辑 hdfs-site.xml 文件被指 HDFS 相关属性并增加数据备份(dfs.replication)、namenode\datanode 存储文件位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<configuration>
<property>
<name>dfs.replication</name>
<value>1</value>
</property>
<property>
<name>dfs.namenode.name.dir</name>
<value>file:///data/hadoop/hdfs/nn</value>
</property>
<property>
<name>dfs.datanode.data.dir</name>
<value>file:///data/hadoop/hdfs/dn</value>
</property>
</configuration>

并建立 datanode/namenode 存储文件以及为其分配权限:

1
2
3
4
cd /
sudo mkdir -p /data/hadoop/hdfs/nn
sudo mkdir -p /data/hadoop/hdfs/dn
sudo chown hadoop:hadoop data/*

切换到 hadoop 用户使用 hdfs namenode -format 来格式化 namenode 后启动单机模式 bash /usr/local/hadoop/sbin/start-dfs.sh,结束后访问 http://<ip>:9870/ 即可看到 Hadoop 单机模式的控制台页面来验证上述配置的正确,之后将上述配置全部在 nn02、dn01、dn02、dn03 中在配置一遍。

可使用 bash /usr/local/hadoop/sbin/stop-dfs.sh 来停止单机模式

并在所有机器上执行 hdfs namenode -format 用于格式化来调试,并启动 bash /usr/local/hadoop/sbin/start-dfs.sh 进行单机启动来保证所有机器配置都是正确的,之后在所有节点中删除数据:

1
2
rm -rf /data/hadoop/hdfs/nn/*
rm -rf /data/hadoop/hdfs/dn/*

之后在 nn01 节点中使用 hdfs namenode 来启动主节点,之后在其他节点中启动 start-dfs.sh 即可将此连接为一个集群。

在没有配置 ZooKeeper 之前,datanode 和 namenode 是通过 core-site.xml 中的 fs.defaultFS 属性去连接 namenode 的 8020 端口,并建立连接,在没有 ZooKeeper 之前,是不允许有两个 Namenode 节点存在的,也就是非HA模式,他只会有一个 Namenode 节点,对于其他的都会被 Hadoop 视为 Datanode 节点,在后续步骤中我们要完成 Hadoop HA 的配置。

Hadoop HA and ZooKeeper

ZooKeeper install / run


在开始 ZooKeeper 之前我们已经了解到为什么 Hadoop HA 需要 ZooKeeper 了,ZooKeeper 同样是 Apache 基金会下的开源项目,他用于分布式应用程序协调服务,是 Google Chubby 的开源实现,为分布式应用提供一致性服务,包括了配置维护、域名服务、配置同步以及组服务等。

ZooKeeper 集群最好是奇数,这样会有利于仲裁,感兴趣的读者可以去了解下 Raft(Reliable,Replicated,Redundant,And Fault-Tolerant)这和 Hadoop HA 有异曲同工之妙。

useradd zookeeper 新建用户 zooke 并为其设置 passwd zookeeper 密码后通过 sudo chmod u+w /etc/sudoers 将 hadoop 添加到 sudoers 列表,向 /etc/sudoers 文件中添加

1
2
3
4
## Allow root to run any commands anywhere
root ALL=(ALL) ALL
hadoop ALL=NOPASSWD:ALL
zookeeper ALL=NOPASSWD:ALL

之后通过清华大学开源镜像站获取 https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.6.3/apache-zookeeper-3.6.3-bin.tar.gz 并解压到 /usr/local/ 中。

1
2
3
tar zxvf apache-zookeeper-3.6.3.tar.gz
sudo mv apache-zookeeper-3.6.3/ /usr/local/zookeeper
sudo chown -R zookeeper:zookeeper /usr/local/zookeeper

通过在 /etc/profile 增加全局变量后以 source /etc/profile 命令让配置生效

1
2
export ZOOKEEPER=/usr/local/zookeeper
export PATH=$PATH:$ZOOKEEPER/bin

进入到 /usr/local/zookeeper/conf 目录下将 zoo_sample.cfg 文件复制一份并改名 zoo.cfgcp -r zoo_sample.cfg zoo.cfg ,并编辑 daraDir 这一行的文件夹位置为 /data/zookeeper

/usr/local/zookeeper/bin/ 文件内的 zkEnv.sh 文件中增加 ZOO_LOG_DIR=/data/logs/zookeeper 日志存放目录并新建目录和权限的设置:

1
2
3
4
5
sudo mkdir /data/zookeeper
sudo chown zookeeper:zookeeper /data/zookeeper/
sudo mkdir /data/logs
sudo mkdir /data/logs/zookeeper
sudo chown -R zookeeper:zookeeper /data/logs/zookeeper

然后在 /data/zookeeper 新建一个文件命名为 myid,并写入这台服务器的 Zookeeper id,取值范围为 1~255,配置如下:

id host
1 nn01
2 nn02
3 dn01
4 dn02
5 dn03

通过 zkServer.sh start 启动 Zookeeper,之后使用 zkCli.sh 测试是否可以连接上,如无报错执行下列命令查看 ZooKeeper 根目录。

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

之后 zkServer.sh stop 来停止 Zookeeper 的运行并清空数据文件夹和日志文件夹,并在 /usr/local/zookeeper/conf/zoo.cfg 添加节点信息:

1
2
3
4
5
server.1=nn01:2888:3888
server.2=nn02:2888:3888
server.3=dn01:2888:3888
server.4=dn02:2888:3888
server.5=dn03:2888:3888

此时在执行过 zkServer.sh start 后 Zookeeper 会将配置中所有的节点连接成一个集群,可以通过 zkServer.sh status 来进行查看:

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
[zookeeper@nn01 local]$ zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost. Client SSL: false.
Mode: leader

ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost. Client SSL: false.
Mode: follower

[zookeeper@dn01 zookeeper]$ zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost. Client SSL: false.
Mode: follower

[zookeeper@dn02 zookeeper]$ zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost. Client SSL: false.
Mode: follower

[zookeeper@dn03 zookeeper]$ zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost. Client SSL: false.
Mode: follower

自启动脚本

自启动脚本在 etc/init.d 下,通过 root 账号来进行操作,我们通过建立 zookeeper 文件后写入下述内容,即可通过 service zookeeper status | start | stop 来进行对 zookeeper 的操作。

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
#! /bin/bash
#chkconfig:2345 80 10
#description: zookeeper service
export JAVA_HOME="/usr/jdk1.8.0_202"
RETVAL=0
start() {
su zookeeper -c "bash /usr/local/zookeeper/bin/zkServer.sh start"
}
stop() {
su zookeeper -c "bash /usr/local/zookeeper/bin/zkServer.sh stop"
}
status() {
su zookeeper -c "bash /usr/local/zookeeper/bin/zkServer.sh status"
}
case $1 in
start)
start
;;
status)
status
;;
stop)
stop
;;
esac
exit $RETVAL

为脚本赋予可执行权限 sudo chmod +x /etc/init.d/zookeeper 添加 Zookeeper 在自启动,重启机器后通过 jps 命令再次查看服务是否正常启动。

Hadoop HA

Haddoop HA (High Avaliable,高可用) 的主要作用就是 保证当一个 namenode 宕机的时候,另一台 namenode 可以立即切换来替代宕掉的 namenode,这样就大概上解决了单点故障的问题。这样的原理基本就是同时启动两台 namenode,一台是活跃状态(active),另一台则是处于 支持(standby) 状态,其主要的作用就是将处于 active 状态的机器上所做过的事情进行同步,以方便在 active 挂掉的时候 standy 可以无缝切换。

在 Hadoop HA 模式下,主要通过 ZooKeeper 来进行对节点的维护,因此 Hadoop HA 的搭建与 ZooKeeper 密不可分,而 ZooKeeper 在整个流程当中都处于服务治理、故障检测、节点维护的身份进行工作。

JournalNode

日志节点(JournalNode) 主要的作用就是在 standby 同步 active 这个流程中充当与数据同步的方式,standby 就是通过 journalnode 集群来同步 active 节点的操作。

JournalNodde 与 dataname 和 namenode 一样,都是 Hadoop 集群中的角色,只不过他主要用于同步 namenode 之间的操作,以防止脑裂现象。

假设测试环境的配置不是很好,那么可以通过在 hdfs-site.xml 文件内添加 dfs.qjournal.start-segment.timeout.ms 的配置,来增加和 JournalNode 集群之间的超时时间

1
2
3
4
<property>
<name>dfs.qjournal.start-segment.timeout.ms</name>
<value>60000</value>
</property>
手动 Failover

failover 的配置分为手动和自动,本文我们选择手动进行操作,failover 即故障切换或故障转移。当一个 Haddop 中有一台 namenode 宕掉了,那么就可以将另外台处于 standby 状态的机器切换成 active 的 namenode 。

这样就可以大幅程度上减少了单点故障所给服务集群带来的影响和损失,在 failover 没有运用之前,相对于单点应用只有一个台 namenode,假设这台刚好宕掉了,那么集群就约等于无法正常提供服务了。当 failover 配置成功后,当这台 namenode 宕掉了还会有另一台 namenode 进入 active 状态从而继续提供服务。

修改所有 namenode 机器上的 /usr/local/hadoop/etc/hadoop/hdfs-site.xml 文件并写入以下配置,分别作为服务id,以及服务 id 内所含有的 namenode:

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
<property>
<name>dfs.nameservices</name>
<value>mycluster</value>
</property>
<property>
<name>dfs.ha.namenodes.mycluster</name>
<value>nn01,nn02</value>
</property>
<property>
<name>dfs.namenode.rpc-address.mycluster.nn01</name>
<value>nn01:8020</value>
</property>
<property>
<name>dfs.namenode.rpc-address.mycluster.nn02</name>
<value>nn02:8020</value>
</property>
<property>
<name>dfs.namenode.http-address.mycluster.nn01</name>
<value>nn01:9870</value>
</property>
<property>
<name>dfs.namenode.http-address.mycluster.nn02</name>
<value>nn02:9870</value>
</property>
<property>
<name>dfs.namenode.shared.edits.dir</name>
<value>qjournal://nn01:8485;nn02:8485;dn01:8485;dn02:8485;dn03:8485/mycluster</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.ssh.private-key-files</name>
<value>/home/hadoop/.ssh/id_rsa</value>
</property>
<property>
<name>dfs.journalnode.edits.dir</name>
<value>/data/hadoop/hdfs/jn</value>
</property>
<property>
<name>dfs.ha.fencing.methods</name>
<value>
sshfence
shell(/bin/true)
</value>
</property>
Id Name Info
1 dfs.nameservices 服务 id
2 dfs.ha.namenodes.mycluster 服务 id 内所含有的 namenode
3 dfs.namenode.rpc-address.mycluster.nn01 设置两个 Namenode 的 RPC 访问地址
dfs.namenode.rpc-address.mycluster.nn02
4 dfs.namenode.http-address.mycluster.nn01 设置两个 Namenode 的 HTTP 访问地址
dfs.namenode.http-address.mycluster.nn02
5 dfs.namenode.shared.edits.dir 配置 journalnode 集群的访问地址
6 dfs.client.failover.proxy.provider.mycluster 配置 dfs 客户端的类名,以判断哪个 namenode 是活跃的
7 dfs.ha.fencing.methods 故障迁移方法,通过 ssh 直接过去将被判断为故障的 namenode 直接杀掉,以防止脑裂现象
dfs.ha.fencing.ssh.private-key-files ssh 免密登录的 id_rsa 位置,以实现上面的故障迁移
8 dfs.journalnode.edits.dir journalnode 的数据文件夹位置
9 dfs.ha.fencing.methods 隔离机制方法 (多个机制用换行分割,即每个机制暂用一行)

之后修改 core-site.xml 文件,将之前的 fd.defaultFS value 值改为 hdfs://mycluster 。此前的 fs.defaultFS 内是需要单独配置 8020 端口的,但是这个端口被移植到了 hdfs-site.xml 中,因此在 core-site.xml 中不需要专门填写一个端口,他们已经组成了一个集群,只需要向外暴漏 nameserver ID 在 ZooKeeper 集群中找出 active 的 namenode 所对应的 ip:port 来进行连接。

并为 journalnode 建立需要的文件夹和赋予权限:

1
2
mkdir /data/hadoop/hdfs/jn
sudo chown -R hadoop:hadoop /data/hadoop/

在此之前我强烈的建议你清口所有 hadoop 生成过数据的目录,否则你可能会在启动 journalnode 之后出现一系列的问题,比如 hdfs namenode -format 格式化失败等:

1
2
3
4
sudo rm -rf /data/hadoop/hdfs/dn/*
sudo rm -rf /data/hadoop/hdfs/nn/*
sudo rm -rf /data/hadoop/hdfs/jn/*
sudo rm -rf /tmp/*

然后在所有节点中启动 journalnode,数量必须为奇数即可,分别执行 hdfs --daemon start journalnode 之后通过 jps 来查看是否 journalnode 节点启动成功:

1
2
3
$ jps
2900 JournalNode
2938 Jps

可通过 hdfs --daemon stop journalnode 停止 journalnode 节点

然后在 nn01 上使用 hdfs namenode -format 来进行格式化,然后在在启动 Namenode :hdfs namenode

切换到 nn02 中同步 namenode 让他完全做好成为备份机(standby) 的准备 hdfs namenode -bootstrapStandby,然后在 dn01~dn03 机器中启动 HDFS: start-dfs.sh 使得启动 Datanode 节点后使用 jps 进行查看节点的启动状态:

1
2
3
2615 JournalNode
2895 DataNode
3231 Jps

最后访问 <namenode ip>:9870 可以直接查看到 Hadoop 控制台状态,显示两个 namenode 节点都处于 standby 状态中,并执行手动 failover :hdfs haadmin -failover nn02 nn01,此时会提示:

1
Failover from nn02 to nn01 successful

但查看 namenode 1 节点的控制台中可以发现已经将 nn01 切换为 active 状态了,而 namenode 2 则仍然处于 standby 状态

自动 Failover


在此前我们提到过 Failover 即故障切换和 Hadoop HA 主要是为了应对脑裂现象,而脑裂现象主要由 Namenode 所产生。在这里就需要引入 zkfc 的概述,zkfc 主要是检测 Hadoop HA 集群中处于 active 状态的 namenode 是否宕机,如果宕机了则会迅速的将 standby 状态的 namenode 切换为 active ,并将当前已经宕机的 namenode kill,以防止脑裂现象的出现,这也是 Failover 核心所解决的问题。

本地的测试环境中,通常机器的性能不是很高,那么就会遇到 zkfc 启动不起来的问题,也就是超过 5s 连接不上 Zookeeper 就自动推出,那么可以在 core-site.xml 中加入下述配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
<property>
<name>ha.zookeeper.session-timeout.ms</name>
<value>30000</value>
</property>
<property>
<name>dfs.ha.automatic-fallover.enabled</name>
<value>true</value>
</property>
<property>
<name>ha.zookeeper.quorum</name>
<value>nn01:2181,nn02:2181,dn01:2181,dn02:2181,dn03:2181</value>
</property>

Id Name Info
1 ha.zookeeper.session-timeout.ms 设置 zkfc 连接 Zookeeper 的延迟时间
2 dfs.ha.automatic-fallover.enabled 启动自动 failover
3 ha.zookeeper.quorum ZooKeeper 集群访问地址

在所有节点中启动 journalnode: hdfs --daemon start journalnode ,以及所有 namenode 节点中启动 zkfc: hdfs --daemon start zkfc

在此之前可能需要使用 hdfs zkfc -formatZK 进行格式化 / hdfs namenode -format

和 Namenode 主节点中启动:hdfs namenode / hdfs --daemon start namenode,然后切换到 nn02 中同步 namenode 让他完全做好成为备份机(standby) 的准备 hdfs namenode -bootstrapStandby,最后在 dn01~dn03 机器中启动 HDFS: start-dfs.sh 那么再次查看 Hadoop 控制台则会在两个 namenode 之间任选一个节点为 active

自启动脚本

为方便之后环境的便捷启动,我们需要为 journalnode(nn01dn03) 、zkfs(nn01nn02) 设置自启动,在 /etc/init.d/ 目录下新建 hadoop-journalnode 并写入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#! /bin/bash
#chkconfig:2345 81 09
#description: hadoop-namenode service
RETVAL=0
. /home/hadoop/.bashrc
start() {
su hadoop -c "hdfs --daemon start journalnode"
}
stop() {
su hadoop -c "hdfs --daemon start journalnode"
}
case $1 in
start)
start
;;
stop)
stop
;;
esac
exit $RETVAL

之后在为 zkfs 设置自启动服务,这主要用于 nn01~nn02 机器中,之后同样的赋予他们 +x 权限以及通过 chkconfig 将服务加入到启动项中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#! /bin/bash
#chkconfig:2345 98 07
#description: hadoop-zkfc service
RETVAL=0
. /home/hadoop/.bashrc
start() {
su hadoop -c "hdfs --daemon start zkfc"
}
stop() {
su hadoop -c "hdfs --daemon stop zkfc"
}
case $1 in
start)
start
;;
stop)
stop
;;
esac
exit $RETVAL

没有将 namenode 和 datanode 设置为自启动服务的主要原因是因为 namenode 需要 active 和 standby 主/备机器,所以建议手动启动

HBase


安装 HBase 的前提是拥有 Zookeeper 虽然 HBase 自带了一个 Zookeeper 但是无法满足 Hadoop 的使用,因此我们通过 https://downloads.apache.org/hbase/stable/ 下载 HBase 稳定版本。

在 HBase 中主要通过 HMaster 来管理元数据,也就是 Hadoop 中的 namenode,之后 RegionServer(区域服务器) 负责存储数据,,也就类似于 Hadoop 中的 datanode。同样的,Zookeeper 在此的作用依然是维护节点(需要注意的是 HDFS 是基于完全部署模式的,也就是通过 HDFS 存储数据,在单机模式下直接使用的是普通文件系统来存储数据。)

这里需要引入一个点是,在 HBase 官方文档中:http://hbase.apache.org/book.html#standalone_dist,将 HBase 的启动分为了三大类,分别为:独立 HBase、伪分布式和完全分布式集群启动,通常独立 HBase 也被称之为快速上手并理解 HBase 的不二之选。

hbase user create and Shell nopasswd

useradd hbase 新建用户 hbase 并为其设置 passwd hbase 密码后通过 sudo chmod u+w /etc/sudoers 将 hbase 添加到 sudoers 列表,向 /etc/sudoers 文件中增加

1
2
3
4
## Allow root to run any commands anywhere
root ALL=(ALL) ALL
hadoop ALL=NOPASSWD:ALL
hbase ALL=NOPASSWD:ALL

之后切换到 hbase 用户下执行 sudo whoami 来查看 etc/sudoers 是否配置成功(一般是 sudo su root 无需输入密码并返回 root 即完成此配置)

同样的为了应对之后的 HBase 伪分布式模式,我们需要为其配置免密登录,使用 ssh-keygen -t rsa 生成 id_rsa.pub 后以 scp 将自身的 rsa 密钥分发给 nn01,scp .ssh/id_rsa.pub hbase@nn01:/home/hbase/.ssh/nn01_keys,当然也包括自己的,这将使得可以自己 shell 免密连接自己,并设置目录权限:

1
2
3
4
5
sudo chmod 700 /home/hbase/
sudo chown hbase:hbase ./.ssh/
cd .ssh/
cat dn01_keys dn02_keys dn03_keys nn01_keys nn02_keys >> authorized_keys
sudo chmod 700 authorized_keys

之后在通过 scp 将 authorized_keys 分发到 nn01~dn03 的 /.ssh 文件夹中,切记也为其赋予 700 权限

1
2
3
4
sudo chmod 700 /home/hbase/
sudo chown hbase:hbase ./.ssh/
cd .ssh/
sudo chmod 700 authorized_keys

Java bashrc and HBase install

https://downloads.apache.org/hbase/stable/ 内下载一个稳定版本的 HBase 并在节点机器中使用 wgethttps://downloads.apache.org/hbase/2.3.7/ 进行下载并直接使用 sudo mv hbase-2.3.7 /usr/local/hbase 移动到 /usr/local 目录下。

最后我们还需要通过sudo vi ~/.bashrc 向用户变量内添加 JDK 所在位置和 HBase 的环境变量,然后使用 source ~/.bashrc 刷新即可:

1
2
3
4
5
6
7
export JAVA_HOME=/usr/jdk1.8.0_202
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export PATH=$PATH:$JAVA_HOME/bin

# hbase
export HBASE_HOME=/usr/local/hbase
export PATH=$PATH:$HBASE_HOME/bin

单机模式

进入到 /usr/local/hbase 目录下追加或修改 hbase.rootdirhbase.zookeeer.property.dataDir 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  <property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<property>
<name>hbase.tmp.dir</name>
<value>./tmp</value>
</property>
<property>
<name>hbase.unsafe.stream.capability.enforce</name>
<value>false</value>
</property>
<property>
<name>hbase.rootdir</name>
<value>file:///home/hbase/hbase</value>
</property>
</configuration>

单机模式下我们启动了 HBase 自带的 Zookeeper

这个操作只需要在单个节点中进行测试,因为只是单机模式,会临时使用到 /home/hbase/hbase/home/hbase/zookeeper 目录

接下来通过 start-hbase.sh 来启动 hbae,jps 查看进程正常启动后由 hbase shell 来连接到 hbase,并进行建表(create)、插入(put)、查询(scan) 等三个测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
hbase(main):001:0> create 'testTable','testFamily'
Created table testTable
Took 3.0652 seconds
=> Hbase::Table - testTable

hbase(main):002:0> put 'testTable','title','testFamily:heyinfo','hello,hbase'
Took 0.8718 seconds

hbase(main):003:0> scan 'testTable'
ROW COLUMN+CELL
title column=testFamily:heyinfo, timestamp=2021-11-24T15:31:45.381, value=hello,hbase
1 row(s)
Took 0.2130 seconds

当一切完成后我们可通过 stop-hbase.sh 来停止单机模式的 HBase 并 rm -rf /home/hbase/hbase /home/hbase/zookeeper/ 删除掉 HBase 在启动之前自动建立的文件夹。

在这里我们需要引入一个 HBase 对于伪分布式的概念,伪分布式很类似完全分布式,但是他是介于 HDFS 为非 HA 状态的,音译仅此将此模式用于原型设计和测试目的,无法用于开发环境和性能评估,我们直接进入完全分布式模式。

完全分布式


默认情况下 HBase 在独立模式(单机模式)下运行,以提供最小规模测试(同样包含了非分布式),对于生产环境加以使用分布式模式,在分布式模式下 HBase 守护程序将会在多个实例集群中服务器运行,但需要注意的完全分布式需要 HDFS 为 HA 模式即可,之后记得在 hbase-env.sh 文件中关闭 HBase 自带的 Zookeeper:

1
2
# Tell HBase whether it should manage it's own instance of ZooKeeper or not.
export HBASE_MANAGES_ZK=false

之后通过sudo groupadd supergroup 建立 supergroup 用户组,然后将 hbase 用户加入到该组中 sudo groupmems -g supergroup -a hbase ,这也是 hdfs 所默认的超级用户组,也就是超级用户,可以无限制权限访问的特殊用户,之后为 Hbase 建立日志文件存储目录:

1
2
sudo mkdir /data/logs/hbase
sudo chown hbase:hbase /data/logs/hbase

HBase 会根据 HDFS 的客户端配置来做册罗调整,而让 HBase 读取到 HDFS 最为直接的方法就是把 HADOOP__CONF_DIR 的配置文件目录地址添加到 HBASE_CLASSPATH 中,以及 hbase 的日志文件夹配置

1
2
3
4
5
# Extra Java CLASSPATH elements.  Optional.
export HBASE_CLASSPATH=/usr/local/hadoop/etc/hadoop

# Where log files are stored. $HBASE_HOME/logs by default.
export HBASE_LOG_DIR=/data/logs/hbase

hbase-site.xml 中增加并修改 hbase.cluster.distributed 为 true 以开启 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
<configuration>
<!--
The following properties are set for running HBase as a single process on a
developer workstation. With this configuration, HBase is running in
"stand-alone" mode and without a distributed file system. In this mode, and
without further configuration, HBase and ZooKeeper data are stored on the
local filesystem, in a path under the value configured for `hbase.tmp.dir`.
This value is overridden from its default value of `/tmp` because many
systems clean `/tmp` on a regular basis. Instead, it points to a path within
this HBase installation directory.

Running against the `LocalFileSystem`, as opposed to a distributed
filesystem, runs the risk of data integrity issues and data loss. Normally
HBase will refuse to run in such an environment. Setting
`hbase.unsafe.stream.capability.enforce` to `false` overrides this behavior,
permitting operation. This configuration is for the developer workstation
only and __should not be used in production!__

See also https://hbase.apache.org/book.html#standalone_dist
-->
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<property>
<name>hbase.tmp.dir</name>
<value>./tmp</value>
</property>
<property>
<name>hbase.unsafe.stream.capability.enforce</name>
<value>false</value>
</property>
<property>
<name>hbase.rootdir</name>
<value>hdfs://mycluster/hbase</value>
</property>
<property>
<name>hbase.zookeeper.quorum</name>
<value>nn01,nn02,dn01,dn02,dn03</value>
</property>
</configuration>
Id Name info
1 hbase.cluster.distributed 告诉 hbase 开启了分布式模式启动
2 hbase.rootdir HBase 的根存储目录,其中 mycluster 为集群的 namenode,如果是伪分布式模式可以替换为机器的 namenode
3 hbase.zookeeper.quorum Zookeeper 集群地址

然后将上述配置推送/复制到所有节点后,在 nn01 节点(任意节点都可在作为 Master )作为 Master 执行 hbase-daemon.sh start master,之后查看 jps HMaster 是否启动,在其余的集群中(nn01~dn02) 启动 RegionServer:hbase-daemon.sh start regionserver,然后浏览器访问 <master>:16010 即可,hbase 模式下的服务为:

1
2
3
4
$ jps
9841 HRegionServer
10437 Jps
9548 HMaster
📖 more posts 📖