1;NoSQL数据库简介
NoSQL(NoSQL = Not Only SQL );意即“不仅仅是SQL”;泛指非关系型的数据库。
NoSQL 不拘泥于关系型数据库的设计范式;放弃了通用的技术标准;为某一领域特定场景而设计;从而使性能、容量或者扩展性都打到了一定程度的突破。
不遵循SQL标准。不支持ACID。远超于SQL的性能。NoSQL适用场景;
性能快容量大扩展性高NoSQL不适用场景
需要事务支持基于sql的结构化查询存储;处理复杂的关系;需要即席查询。;用不着sql的和用了sql也不行的情况;请考虑用NoSql;2;NoSQL家族
1. Memcache ;内存数据库;
很早出现的NoSql数据库数据都在内存中;一般不持久化支持简单的key-value模式;支持类型单一一般是作为缓存数据库辅助持久化的数据库。
2. Redis;内存数据库;
几乎覆盖了Memcached的绝大部分功能。
数据都在内存中;支持持久化;主要用作备份恢复。
除了支持简单的key-value模式;还支持多种数据结构的存储;比如 list、set、hash、zset等。
一般是作为缓存数据库辅助持久化的数据库。
3. Mongodb ;文档数据库;
高性能、开源、模式自由(schema free)的文档型数据库数据都在内存中; 如果内存不足;把不常用的数据保存到硬盘虽然是key-value模式;但是对value;尤其是json;提供了丰富的查询功能支持二进制数据及大型对象可以根据数据的特点替代RDBMS ;成为独立的数据库。或者配合RDBMS;存储特定的数据。
4. Hbase ;列式数据库;
HBase是Hadoop项目中的数据库。它用于需要对大量的数据进行随机、实时的读写操作的场景中。
HBase的目标就是处理数据量非常庞大的表;可以用普通的计算机处理超过10亿行数据;还可处理有数百万列元素的数据表。
5. Cassandra ;列式数据库;
Apache Cassandra是一款免费的开源NoSQL数据库;其设计目的在于管理由大量商用服务器构建起来的庞大集群上的海量数据集(数据量通常达到PB级别)。在众多显著特性当中;Cassandra最为卓越的长处是对写入及读取操作进行规模调整;而且其不强调主集群的设计思路能够以相对直观的方式简化各集群的创建与扩展流程。
3;DB-Engines 数据库排名
DB-Engines Ranking - popularity ranking of database management systems
Redis应用场景;
1;配合关系型数据库做高速缓存
高频次;热门访问的数据;降低数据库IO经典的缓存Cache Aside Pattern ;旁路缓存模式;2;大数据场景;缓存数据
3;大数据场景;临时数据
4;大数据场景;计算结果
5;利用redis的特殊数据结构解决一些特点问题
Redis官方网站
Redis中文官方网站
Redis
http://www.Redis.net.cn
安装版本;
6.0.8 for Linux;redis-6.0.8.tar.gz;不用考虑在windows环境下对Redis的支持安装步骤;
准备工作;下载安装最新版的gcc编译器。
安装C 语言的编译环境;
sudo yum install centos-release-scl scl-utils-build
sudo yum install -y devtoolset-8-toolchain
sudo scl enable devtoolset-8 bash
测试 gcc版本;
sudo gcc --version
下载redis-6.0.8.tar.gz放/opt目录。
解压命令;
tar -zxvf redis-6.0.8.tar.gz
解压完成后进入目录;
cd redis-6.0.8
在redis-6.0.8目录下再次执行make命令;只是编译好;
如果make 报错—Jemalloc/jemalloc.h;没有那个文件
解决方案;运行make distclean
在redis-6.0.8目录下再次执行make命令;只是编译好; 。
跳过make test 继续执行:
make install
安装目录;
默认安装目录; /usr/local/bin
redis-benchmark:性能测试工具;可以在自己本子运行;看看自己本子性能如何
redis-check-aof;修复有问题的AOF文件;rdb和aof后面讲
redis-check-dump;修复有问题的dump.rdb文件
redis-sentinel;Redis集群使用
redis-server;Redis服务器启动命令
redis-cli;客户端;操作入口
1;前台启动;不推荐;
前台启动;命令行窗口不能关闭;否则服务器停止。
2;后台启动;推荐;
1. 备份redis.conf
拷贝一份redis.conf到其他目录;
cp /opt/redis-6.0.8/redis.conf /myredis
2. 后台启动设置daemonize no 改成 yes
修改redis.conf(128行)文件将里面的daemonize no 改成 yes;让服务在后台启动。
3. Redis启动
redis-server /myredis/redis.conf
4. 用客户端访问;redis-cli
多个端口可以;
redis-cli -p 6379
测试验证;
ping
3;Redis关闭
单实例关闭;
redis-cli shutdown
也可以进入终端后再关闭;
多实例关闭;指定端口关闭;
redis-cli -p 6379 shutdown
1;Redis端口
默认16个数据库;类似数组下标从0开始;初始默认使用0号库
使用命令 select <dbid> 来切换数据库。
如:
select 8
统一密码管理;所有库同样密码。
2;Redis的单线程;多路IO复用技术
多路io复用 ;指的是同一个进程用一个线程处理多个IO数据流。
原理;多路Io复用是利用select、poll、epoll;不同的监控策略;可以同时监察多个流的IO事件的能力;在空闲的时候会把当前线程阻塞;当有一个或多个流由IO事件发生时;就从阻塞态中唤醒;处理就绪的流。
优势;当处理的消耗对比IO几乎可以忽略不计时;可以处理大量的并发IO;而不用消耗太多CPU/内存。
串行 vs 多线程;锁;memcached; vs 单线程;多路IO复用(Redis)
;与Memcache三点不同: 支持多数据类型;支持持久化;单线程;多路IO复用;
新版 Redis 6.x
虽然io多路复用已经不错了;但是面临很多大键值的访问时;其中IO操作还是容易出现高延迟的问题;为了进一步优化;Redis 6.x把IO的部分做成允许多线程的模式。
注意这个IO部分只是处理网络数据的读写和协议解析;执行API命令仍然使用单线程。所以这个多线程并不会让redis存在并发的情况。
另外;多线程IO默认也是不开启的;需要再配置文件中配置;
io-threads-do-reads yes
io-threads 4
redis常见数据类型操作命令;请参考官网;Redis 命令参考 — Redis 命令参考
1;Redis键(key)
keys * 查看当前库所有key (匹配;keys *1)
exists key 判断某个key是否存在
type key 查看你的key是什么类型
del key 删除指定的key数据
expire key 10 10秒钟;为给定的key设置过期时间
ttl key 查看还有多少秒过期;-1表示永不过期;-2表示已过期
select 命令切换数据库
dbsize 查看当前数据库的key的数量
flushdb 清空当前库
flushall 通杀全部库
2;Redis字符串(String)
String是Redis最基本的类型;你可以理解成与Memcached一模一样的类型;一个key对应一个value。
String类型是Redis最基本的数据类型;一个Redis中字符串value最多可以是512M。
set <key> <value> 添加键值对
get <key> 查询对应键值
append <key> <value> 将给定的<value> 追加到原值的末尾
setnx <key> <value> 只有在 key 不存在时 设置 key 的值
incr <key>
将 key 中储存的数字值增1
只能对数字值操作;如果为空;新增值为1
decr <key>
将 key 中储存的数字值减1
只能对数字值操作;如果为空;新增值为-1
incrby / decrby <key> <步长> 将 key 中储存的数字值增减。自定义步长。
原子性;
所谓原子操作是指不会被线程调度机制打断的操作;
这种操作一旦开始;就一直运行到结束;中间不会有任何 context switch ;切换到另一个线程;。
;1;在单线程中; 能够在单条指令中完成的操作都可以认为是; 原子操作;;因为中断只能发生于指令之间。
;2;在多线程中;不能被其它进程;线程;打断的操作就叫原子操作。
Redis单命令的原子性主要得益于Redis的单线程。
案例;
java中的i;;是否是原子操作?不是
i=0;两个线程分别对i进行;;100次,值是多少? 2
mset <key1> <value1> <key2> <value2> .....
同时设置一个或多个 key-value对
mget <key1> <key2> <key3> .....
同时获取一个或多个 value
msetnx <key1> <value1> <key2> <value2> .....
同时设置一个或多个 key-value 对;当且仅当所有给定 key 都不存在。
原子性;有一个失败则都失败
setex <key> <过期时间> <value>
设置键值的同时;设置过期时间;单位秒。
3;Redis列表(List)
单键多值
Redis 列表是简单的字符串列表;按照插入顺序排序。你可以添加一个元素到列表的头部;左边;或者尾部;右边;。
它的底层实际是个双向链表;对两端的操作性能很高;通过索引下标的操作中间的节点性能会较差。
lpush/rpush <key> <value1> <value2> <value3> .... 从左边/右边插入一个或多个值。
lpop/rpop <key> 从左边/右边吐出一个值。值在键在;值光键亡。
rpoplpush <key1> <key2> 从<key1>列表右边吐出一个值;插到<key2>列表左边。
lrange <key> <start> <stop>
按照索引下标获得元素(从左到右)
lrange mylist 0 -1 从0开始;-1表示获取所有
lindex <key> <index> 按照索引下标获得元素(从左到右)
llen <key> 获得列表长度
linsert <key> before <value> <newvalue> 在<value>的后面插入<newvalue> 插入值
lrem <key> <n> <value> 从左边删除n个value(从左到右)
4;Redis集合(Set)
Redis set对外提供的功能与list类似是一个列表的功能;特殊之处在于set是可以自动排重的;当你需要存储一个列表数据;又不希望出现重复数据时;set是一个很好的选择;并且set提供了判断某个成员是否在一个set集合内的重要接口;这个也是list所不能提供的。
Redis的Set是string类型的无序集合。它底层其实是一个value为null的hash表;所以添加;删除;查找的复杂度都是O(1)。
一个算法;随着数据的增加;执行时间的长短;如果是O(1);数据增加;查找数据的时间不变
sadd <key> <value1> <value2> .....
将一个或多个 member 元素加入到集合 key 中;已经存在的 member 元素将被忽略
smembers <key> 取出该集合的所有值。
sismember <key> <value> 判断集合<key>是否为含有该<value>值;有1;没有0
scard <key> 返回该集合的元素个数。
srem <key> <value1> <value2> .... 删除集合中的某个元素。
sinter <key1> <key2> 返回两个集合的交集元素。
sunion <key1> <key2> 返回两个集合的并集元素。
sdiff <key1> <key2> 返回两个集合的差集元素(key1中的;不包含key2中的)
场景; 去重 判存 集合间运算。
5;Redis哈希(Hash)
Redis hash 是一个键值对集合。
Redis hash是一个string类型的field和value的映射表;hash特别适合用于存储对象。
类似Java里面的Map<String,Object>
用户ID为查找的key;存储的value用户对象包含姓名;年龄;生日等信息;如果用普通的key/value结构来存储
主要有以下2种存储方式;
每次修改用户的某个属性需要;先反序列化改好后再序列化回去。开销较大。
用户ID数据冗余
通过 key(用户ID) ; field(属性标签) 就可以操作对应属性数据了;既不需要重复存储数据;也不会带来序列化和并发修改控制的问题。
hset <key> <field> <value> 给<key>集合中的 <field>键赋值<value>
hget <key1> <field> 从<key1>集合<field> 取出 value
hmset <key1> <field1> <value1> <field2> <value2>... 批量设置hash的值
hexists <key1> <field> 查看哈希表 key 中;给定域 field 是否存在。
hgetall <key> 列出该hash集合的所有field和value
hincrby <key> <field> <increment> 为哈希表 key 中的域 field 的值加上增量 1 -1
6;Redis有序集合Zset(sorted set)
Redis有序集合zset与普通集合set非常相似;是一个没有重复元素的字符串集合。
不同之处是有序集合的每个成员都关联了一个评分;score;,这个评分;score;被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的;但是评分可以是重复了 。
因为元素是有序的, 所以你也可以很快的根据评分;score;或者次序;position;来获取一个范围的元素。
访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。
zadd <key> <score1> <value1> <score2> <value2>…
将一个或多个 member 元素及其 score 值加入到有序集 key 当中。
zrange <key> <start> <stop> [WITHSCORES]
返回有序集 key 中;下标在<start> <stop>之间的元素
带WITHSCORES;可以让分数一起和值返回到结果集。
zrevrange <key> <start> <stop> [WITHSCORES]
逆序返回下标范围的数据
zrangebyscore key min max [withscores] [limit offset count]
返回有序集 key 中;所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。
zrevrangebyscore key max min [withscores] [limit offset count]
同上;改为从大到小排列。
zincrby <key> <increment> <value> 为元素的score加上增量
zrem <key> <value> 删除该集合下;指定值的元素
案例;如何利用zset实现一个文章访问量的排行榜?
每次文章访问用;
zincrby topn 1 v1
自定义目录;/myredis/redis.conf
1;Units单位
配置大小单位,开头定义了一些基本的度量单位;只支持bytes;不支持bit。
大小写不敏感;
2;INCLUDES包含
类似jsp中的include;多实例的情况可以把公用的配置文件提取出来。
3;网络相关配置
1. bind
默认情况bind=127.0.0.1只能接受本机的访问请求。
不写的情况下;无限制接受任何ip地址的访问。
生产环境肯定要写你应用服务器的地址;服务器是需要远程访问的;所以需要将其注释掉。
如果开启了protected-mode;那么在没有设定bind ip且没有设密码的情况下;Redis只允许接受本机的响应。
保存配置;停止服务;重启启动查看进程;不再是本机访问了。
2. protected-mode
将本机访问保护模式设置no;
3. Port
端口号;默认 6379;
4. timeout
一个空闲的客户端维持多少秒会关闭;0表示关闭该功能。即永不关闭。
4;GENERAL通用
1. daemonize
是否为后台进程;设置为yes。
守护进程;后台启动;
2. pidfile
存放pid文件的位置;每个实例会产生一个不同的pid文件;
3. loglevel
指定日志记录级别;Redis总共支持四个级别;debug、verbose、notice、warning;默认为notice
四个级别根据使用阶段来选择;生产环境选择notice 或者warning
4. logfile
日志文件名称;
5. databases 16
设定库的数量 默认16;默认数据库为0;可以使用SELECT <dbid>命令在连接上指定数据库id;
5;SECURITY安全
1. 设置密码
访问密码的查看、设置和取消。
在命令中设置密码;只是临时的。重启redis服务器;密码就还原了。
永久设置;需要再配置文件中进行设置。
6; LIMITS限制
1. maxclients
设置redis同时可以与多少个客户端进行连接。默认情况下为10000个客户端。如果达到了此限制;redis则会拒绝新的连接请求;并且向这些连接请求方发出“max number of clients reached”以作回应。2. maxmemory
建议必须设置;否则;将内存占满;造成服务器宕机设置redis可以使用的内存量。一旦到达内存使用上限;redis将会试图移除内部数据;移除规则可以通过maxmemory-policy来指定。如果redis无法根据移除规则来移除内存中的数据;或者设置了“不允许移除”;那么redis则会针对那些需要申请内存的指令返回错误信息;比如SET、LPUSH等。但是对于无内存申请的指令;仍然会正常响应;比如GET等。如果你的redis是主redis;说明你的redis有从redis;;那么在设置内存使用上限时;需要在系统中留出一些内存空间给同步队列缓存;只有在你设置的是“不移除”的情况下;才不用考虑这个因素。
3. maxmemory-policy
volatile-lru;使用LRU算法移除key;只对设置了过期时间的键;;最近最少使用;allkeys-lru;在所有集合key中;使用LRU算法移除keyvolatile-lfu;使用LFU算法移除key;只对设置了过期时间的键;;最近最少使用;allkeys-lfu;在所有集合key中;使用LFU算法移除keyvolatile-random;在过期集合中移除随机的key;只对设置了过期时间的键allkeys-random;在所有集合key中;移除随机的keyvolatile-ttl;移除那些TTL值最小的key;即那些最近要过期的keynoeviction;不进行移除。针对写操作;只是返回错误信息
LFU 最低访问频率; 这个算法主要是考察key被访问的次数;淘汰访问次数最少的。
LRU 最近最少被使用;最久未被使用;;这个算法主要是考察key最后一次访问距今的时间。淘汰最久的。
4. maxmemory-samples
设置样本数量;LRULFU算法和最小TTL算法都并非是精确的算法;而是估算值;所以你可以设置样本的大小;redis默认会检查这么多个key并选择其中LRU的那个。一般设置3到7的数字;数值越小样本越不准确;但性能消耗越小。
1. Jedis所需要的jar包
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
2. 连接Redis注意事项
禁用Linux的防火墙;Linux(CentOS7)里执行命令
systemctl stop/disable firewalld.service
redis.conf中注释掉bind 127.0.0.1 ,然后 protected-mode no
3. Jedis常用操作
import redis.clients.jedis.Jedis;
public class Demo01 {
public static void main(String[] args) {
Jedis jedis = new Jedis(;192.168.137.3;,6379);
String pong = jedis.ping();
System.out.println(;连接成功;;;pong);
jedis.close();
}
}
4. 测试相关数据类型
1;Jedis-API: Key
jedis.set(;k1;, ;v1;);
jedis.set(;k2;, ;v2;);
jedis.set(;k3;, ;v3;);
Set<String> keys = jedis.keys(;*;);
System.out.println(keys.size());
for (String key : keys) {
System.out.println(key);
}
System.out.println(jedis.exists(;k1;));
System.out.println(jedis.ttl(;k1;));
System.out.println(jedis.get(;k1;));
2;Jedis-API: String
jedis.mset(;str1;,;v1;,;str2;,;v2;,;str3;,;v3;);
System.out.println(jedis.mget(;str1;,;str2;,;str3;));
3;Jedis-API: List
List<String> list = jedis.lrange(;mylist;,0,-1);
for (String element : list) {
System.out.println(element);
}
4;Jedis-API: set
jedis.sadd(;orders;, ;order01;);
jedis.sadd(;orders;, ;order02;);
jedis.sadd(;orders;, ;order03;);
jedis.sadd(;orders;, ;order04;);
Set<String> smembers = jedis.smembers(;orders;);
for (String order : smembers) {
System.out.println(order);
}
jedis.srem(;orders;, ;order02;);
5;Jedis-API: hash
jedis.hset(;hash1;,;userName;,;lisi;);
System.out.println(jedis.hget(;hash1;,;userName;));
Map<String,String> map = new HashMap<String,String>();
map.put(;telphone;,;13810169999;);
map.put(;address;,;yyds;);
map.put(;email;,;abc;163.com;);
jedis.hmset(;hash2;,map);
List<String> result = jedis.hmget(;hash2;, ;telphone;,;email;);
for (String element : result) {
System.out.println(element);
}
6;Jedis-API: zset
jedis.zadd(;zset01;, 100d, ;z3;);
jedis.zadd(;zset01;, 90d, ;l4;);
jedis.zadd(;zset01;, 80d, ;w5;);
jedis.zadd(;zset01;, 70d, ;z6;);
Set<String> zrange = jedis.zrange(;zset01;, 0, -1);
for (String e : zrange) {
System.out.println(e);
}
7;连接池
节省每次连接redis服务带来的消耗;把连接好的实例反复利用。
通过参数管理连接的行为。
代码见项目中;
private static JedisPool jedisPool=null;
public static Jedis getJedisFromPool(){
if(jedisPool==null){
JedisPoolConfig jedisPoolConfig =new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(10); //最大可用连接数
jedisPoolConfig.setMaxIdle(5); //最大闲置连接数
jedisPoolConfig.setMinIdle(5); //最小闲置连接数
jedisPoolConfig.setBlockWhenExhausted(true); //连接耗尽是否等待
jedisPoolConfig.setMaxWaitMillis(2000); //等待时间
jedisPoolConfig.setTestOnBorrow(true); //取连接的时候进行一下测试 ping pong
jedisPool=new JedisPool(jedisPoolConfig,;192.168.11.103;, 6379 );
return jedisPool.getResource();
}else{
return jedisPool.getResource();
}
}
链接池参数;
MaxTotal;控制一个pool可分配多少个jedis实例;通过pool.getResource()来获取;如果赋值为-1;则表示不限制;如果pool已经分配了MaxTotal个jedis实例;则此时pool的状态为exhausted。maxIdle;控制一个pool最多有多少个状态为idle(空闲)的jedis实例;minIdle;控制一个pool最少有多少个状态为idle(空闲)的jedis实例;BlockWhenExhausted;连接耗尽是否等待MaxWaitMillis;表示当borrow一个jedis实例时;最大的等待毫秒数;如果超过等待时间;则直接抛JedisConnectionException;
testOnBorrow;获得一个jedis实例的时候是否检查连接可用性;ping();;如果为true;则得到的jedis实例均是可用的。
1. 总体介绍
官网介绍;Redis
Redis 提供了2个不同形式的持久化方式。
RDB;Redis DataBase;AOF;Append Only File;2. RDB;Redis DataBase;
官网介绍;
RDB在指定的时间间隔内将内存中的数据集快照写入磁盘; 也就是行话讲的Snapshot快照;它恢复时是将快照文件直接读到内存里。
3. 备份是如何执行的
Redis会单独创建;fork;一个子进程来进行持久化;会先将数据写入到 一个临时文件中;待持久化过程都结束了;再用这个临时文件替换上次持久化好的文件。 整个过程中;主进程是不进行任何IO操作的;这就确保了极高的性能 如果需要进行大规模数据的恢复;且对于数据恢复的完整性不是非常敏感;那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
4. Fork
Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据;变量、环境变量、程序计数器等; 数值都和原进程一致;但是是一个全新的进程;并作为原进程的子进程在Linux程序中;fork()会产生一个和父进程完全相同的子进程;但子进程在此后多会exec系统调用;出于效率考虑;Linux中引入了“写时复制技术”一般情况父进程和子进程会共用同一段物理内存;只有进程空间的各段的内容要发生变化时;才会将父进程的内容复制一份给子进程。5. dump.rdb文件
在redis.conf中配置文件名称;默认为dump.rdb;
6. 配置位置
rdb文件的保存路径;也可以修改。默认为Redis启动时命令行所在的目录下;
dir ;/myredis/;
7. 如何触发RDB快照;保存策略
配置文件中默认的快照配置;
1;命令save VS bgsave
save ;save时只管保存;其它不管;全部阻塞。手动保存。不建议。
bgsave;Redis会在后台异步进行快照操作; 快照同时还可以响应客户端请求。
可以通过lastsave 命令获取最后一次成功执行快照的时间
2;flushall命令
执行flushall命令;也会产生dump.rdb文件;但里面是空的;无意义。
3;SNAPSHOTTING快照
4;Save
格式;save 秒钟 写操作次数。
RDB是整个内存的压缩过的Snapshot;RDB的数据结构;可以配置复合的快照触发条件;
默认是1分钟内改了1万次;或5分钟内改了10次;或15分钟内改了1次。
禁用;不设置save指令;或者给save传入空字符串。
5;stop-writes-on-bgsave-error
当Redis无法写入磁盘的话;直接关掉Redis的写操作;推荐yes。
6;rdbcompression 压缩文件
对于存储到磁盘中的快照;可以设置是否进行压缩存储。如果是的话;redis会采用LZF算法进行缩。
如果你不想消耗CPU来进行压缩的话;可以设置为关闭此功能。推荐yes.
7;rdbchecksum 检查完整性
在存储快照后;还可以让redis使用CRC64算法来进行数据校验;但是这样做会增加大约10%的性能消耗;如果希望获取到最大的性能提升;可以关闭此功能。
推荐yes。
8;rdb的备份
先通过config get dir 查询rdb文件的目录
将*.rdb的文件拷贝到别的地方
rdb的恢复;
1. 关闭Redis
2. 先把备份的文件拷贝到工作目录下 cp dump2.rdb dump.rdb
3. 启动Redis, 备份数据会直接加载
8. 优势
节省磁盘空间恢复速度快9. 劣势
虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。在备份周期在一定间隔时间做一次备份;所以如果Redis意外down掉的话;就会丢失最后一次快照后的所有修改。10. 如何停止
动态停止RDB;redis-cli config set save ;; #save后给空值;表示禁用保存策略
11. 总结
1. AOF;Append Only File;
1;是什么
以日志的形式来记录每个写操作;增量保存;;将Redis执行过的所有写指令记录下来(读操作不记录); 只许追加文件但不可以改写文件;redis启动之初会读取该文件重新构建数据;换言之;redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
2;AOF默认不开启
可以在redis.conf中配置文件名称;默认为 appendonly.aof
AOF文件的保存路径;同RDB的路径一致。
3;AOF和RDB同时开启;redis听谁的?
AOF和RDB同时开启;系统默认取AOF的数据;数据不会存在丢失;
4;AOF启动/修复/恢复
AOF的备份机制和性能虽然和RDB不同, 但是备份和恢复的操作同RDB一样;都是拷贝备份文件;需要恢复时再拷贝到Redis工作目录下;启动系统即加载。正常恢复1、修改默认的appendonly no;改为yes
2、将有数据的aof文件复制一份保存到对应目录(查看目录;config get dir)
3、恢复;重启redis然后重新加载
1、修改默认的appendonly no;改为yes
2、如遇到AOF文件损坏;通过/usr/local/bin/redis-check-aof --fix appendonly.aof进行恢复
3、备份被写坏的AOF文件
4、恢复;重启redis;然后重新加载
5;AOF同步频率设置
appendfsync always;
始终同步;每次Redis的写入都会立刻记入日志;性能较差但数据完整性比较好
appendfsync everysec;
每秒同步;每秒记入日志一次;如果宕机;本秒的数据可能丢失。
appendfsync no;
redis不主动进行同步;把同步时机交给操作系统。
6;Rewrite压缩
是什么;
AOF采用文件追加方式;文件会越来越大为避免出现此种情况;新增了重写机制, 当AOF文件的大小超过所设定的阈值时;Redis就会启动AOF文件的内容压缩; 只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof
重写原理;如何实现重写
AOF文件持续增长而过大时;会fork出一条新进程来将文件重写(也是先写临时文件最后再rename);redis4.0版本后的重写;是指上就是把rdb 的快照;以二级制的形式附在新的aof头部;作为已有的历史数据;替换掉原来的流水账操作。
no-appendfsync-on-rewrite;
如果 no-appendfsync-on-rewrite=yes ,不写入aof文件只写入缓存;用户请求不会阻塞;但是在这段时间如果宕机会丢失这段时间的缓存数据。;降低数据安全性;提高性能;
如果 no-appendfsync-on-rewrite=no, 还是会把数据往磁盘里刷;但是遇到重写操作;可能会发生阻塞。;数据安全;但是性能降低;
触发机制;何时重写
Redis会记录上次重写时的AOF大小;默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发
重写虽然可以节约大量磁盘空间;减少恢复时间。但是每次重写还是有一定的负担的;因此设定Redis要满足一定条件才会进行重写。
auto-aof-rewrite-percentage;设置重写的基准值;文件达到100%时开始重写;文件是原来重写后文件的2倍时触发;
auto-aof-rewrite-min-size;设置重写的基准值;最小文件64MB。达到这个值开始重写。
例如;文件达到70MB开始重写;降到50MB;下次什么时候开始重写?100MB
系统载入时或者上次重写完毕时;Redis会记录此时AOF大小;设为base_size,
如果Redis的AOF当前大小>= base_size ;base_size*100% (默认)且当前大小>=64mb(默认)的情况下;Redis会对AOF进行重写。
7;优势
备份机制更稳健;丢失数据概率更低。可读的日志文本;通过操作AOF文件;可以处理误操作。8;劣势
比起RDB占用更多的磁盘空间。恢复备份速度要慢。每次读写都同步的话;有一定的性能压力。存在个别Bug;造成恢复不能。9;AOF总结
官方推荐两个都启用。
如果对数据不敏感;可以选单独用RDB。
不建议单独用 AOF;因为可能会出现Bug。
如果只是做纯内存缓存;可以都不用。;谨慎;冗余出现雪崩;
1;主从复制简介
主机数据更新后根据配置和策略; 自动同步到备机的master/slaver机制;Master以写为主;Slave以读为主。
容灾快速恢复;
2;主从复制步骤
新建redis6379.conf;填写以下内容;
新建redis6380.conf;填写以下内容;
新建redis6381.conf;填写以下内容;
slave-priority 10
设置从机的优先级;值越小;优先级越高;用于选举主机时使用;默认100 。
启动三台redis服务器;
查看系统进程;看看三台服务器是否启动;
查看三台主机运行情况;
配从(库)不配主(库);
拷贝多个redis.conf文件include(写绝对路径)
开启daemonize yes
Pid文件名字pidfile
指定端口port
Log文件名字
dump.rdb名字dbfilename
Appendonly 关掉或者换名字
info replication
打印主从复制的相关信息
slaveof <ip> <port>
成为某个实例的从服务器
在6380和6381上执行: slaveof 127.0.0.1 6379
在主机上写;在从机上可以读取数据。
在从机上写数据报错;
主机挂掉;重启就行;一切如初。
从机重启需重设;slaveof 127.0.0.1 6379
可以将配置增加到文件中;永久生效。
3;一主二仆
切入点问题?slave1、slave2是从头开始复制还是从切入点开始复制?比如从k4进来;那之前的k1,k2,k3是否也可以复制?
从机是否可以写?set可否?
主机shutdown后情况如何?从机是上位还是原地待命?
主机又回来了后;主机新增记录;从机还能否顺利复制?
其中一台从机down后情况如何?;?
复制原理
每次从机联通后;都会给主机发送sync(同步)指令。
主机立刻进行存盘操作;发送RDB文件给从机。
从机收到RDB文件后覆盖自己的RDB文件;进行全盘加载。
之后每次主机的写操作;都会立刻发送给从机;从机执行相同的命令。
上一个Slave可以是下一个slave的Master;Slave同样可以接收其他 slaves的连接和同步请求;那么该slave作为了链条中下一个的master, 可以有效减轻master的写压力,去中心化降低风险。
用 slaveof <ip> <port>
中途变更转向:会清除之前的数据;重新建立拷贝最新的
风险是一旦某个slave宕机;后面的slave都没法备份
主机挂了;从机还是从机;无法写数据了
4;反客为主
当一个master宕机后;后面的slave可以立刻升为master;其后面的slave不用做任何修改。
用 slaveof no one 将从机变为主机。
复制原理
Slave启动成功连接到master后会发送一个sync命令Master接到命令启动后台的存盘进程;同时收集所有接收到的用于修改数据集命令; 在后台进程执行完毕之后;master将传送整个数据文件到slave,以完成一次完全同步全量复制;而slave服务在接收到数据库文件数据后;将其存盘并加载到内存中。增量复制;Master继续将新的所有收集到的修改命令依次传给slave,完成同步但是只要是重新连接master,一次完全同步;全量复制)将被自动执行5; 哨兵模式(sentinel)
反客为主的自动版;能够后台监控主机是否故障;如果故障了根据投票数自动将从库转换为主库。
调整为一主二仆模式;6379带着6380、6381。
自定义的/myredis目录下新建sentinel.conf文件;名字绝不能错。
配置哨兵;填写内容;
sentinel monitor mymaster 127.0.0.1 6379 1
其中mymaster为监控对象起的服务器名称; 1 为至少有多少个哨兵同意迁移的数量。
启动哨兵;
/usr/local/bin
redis做压测可以用自带的redis-benchmark工具。
执行;
redis-sentinel /myredis/sentinel.conf
当主机挂掉;从机选举中产生新的主机。
(大概10秒左右可以看到哨兵窗口日志;切换了新的主机)
哪个从机会被选举为主机呢?根据优先级别;
slave-priority
原主机重启后会变为从机。
复制延时
由于所有的写操作都是先在Master上操作;然后同步更新到Slave上;所以从Master同步到Slave机器有一定的延迟;当系统很繁忙的时候;延迟问题会更加严重;Slave机器数量的增加也会使这个问题更加严重。
故障恢复
优先级在redis.conf中默认;slave-priority 100;值越小优先级越高。
偏移量是指获得原主机数据最全的。
每个redis实例启动后都会随机生成一个40位的runid。
主从复制代码;
private static JedisSentinelPool jedisSentinelPool=null;
public static Jedis getJedisFromSentinel(){
if(jedisSentinelPool==null){
Set<String> sentinelSet=new HashSet<>();
sentinelSet.add(;192.168.11.103:26379;);
JedisPoolConfig jedisPoolConfig =new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(10); //最大可用连接数
jedisPoolConfig.setMaxIdle(5); //最大闲置连接数
jedisPoolConfig.setMinIdle(5); //最小闲置连接数
jedisPoolConfig.setBlockWhenExhausted(true); //连接耗尽是否等待
jedisPoolConfig.setMaxWaitMillis(2000); //等待时间
jedisPoolConfig.setTestOnBorrow(true); //取连接的时候进行一下测试 ping pong
jedisSentinelPool=new JedisSentinelPool(;mymaster;,sentinelSet,jedisPoolConfig);
return jedisSentinelPool.getResource();
}else{
return jedisSentinelPool.getResource();
}
}
容量不够;redis如何进行扩容?
高并发操作; redis如何分摊QPS?
什么是集群;
Redis 集群实现了对Redis的水平扩容;即启动N个redis节点;将整个数据库分布存储在这N个节点中;每个节点存储总数据的1/N。
Redis 集群通过分区;partition;来提供一定程度的可用性;availability;; 即使集群中有一部分节点失效或者无法进行通讯; 集群也可以继续处理命令请求。
删除持久化数据;
将rdb,aof文件都删除掉。
制作6个实例;6379,6380,6381,6389,6390,6391
1;配置基本信息
开启daemonize yes
Pid文件名字
指定端口
Log文件名字
Dump.rdb名字
Appendonly 关掉或者换名字
2;redis cluster配置修改
cluster-enabled yes 打开集群模式
cluster-config-file nodes-6379.conf 设定节点配置文件名
cluster-node-timeout 15000 设定节点失联时间;超过该时间;毫秒;;集群自动进行主从切换。
include /home/bigdata/redis.conf
port 6379
pidfile ;/var/run/redis_6379.pid;
dbfilename ;dump6379.rdb;
dir ;/home/bigdata/redis_cluster;
logfile ;/home/bigdata/redis_cluster/redis_err_6379.log;
cluster-enabled yes
cluster-config-file nodes-6379.conf
cluster-node-timeout 15000
3;修改好redis6379.conf文件;拷贝多个redis.conf文件
4;使用查找替换修改另外5个文件
例如;:%s/6379/6380
5;启动6个redis服务
6;将六个节点合成一个集群
组合之前;请确保所有redis实例启动后;nodes-xxxx.conf文件都生成正常。
合体;
cd /opt/redis-6.0.8/src
redis-cli --cluster create --cluster-replicas 1 192.168.11.101:6379 192.168.11.101:6380 192.168.11.101:6381 192.168.11.101:6389 192.168.11.101:6390 192.168.11.101:6391
此处不要用127.0.0.1; 请用真实IP地址
--replicas 1 采用最简单的方式配置集群;一台主机;一台从机;正好三组。
普通方式登录;
可能直接进入读主机;存储数据时;会出现MOVED重定向操作。所以;应该以集群方式登录。
-c 采用集群策略连接;设置数据会自动切换到相应的写主机;
7;通过 cluster nodes 命令查看集群信息
8;redis cluster 如何分配这六个节点?
一个集群至少要有三个主节点。
选项 --cluster-replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。
分配原则尽量保证每个主数据库运行在不同的IP地址;每个从库和主库不在一个IP地址上。
9;什么是slots
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
一个 Redis 集群包含 16384 个插槽;hash slot;; 数据库中的每个键都属于这 16384 个插槽的其中一个; 集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽; 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。
集群中的每个节点负责处理一部分插槽。 举个例子; 如果一个集群可以有主节点; 其中;
节点 A 负责处理 0 号至 5460 号插槽。
节点 B 负责处理 5461 号至 10922 号插槽。
节点 C 负责处理 10923 号至 16383 号插槽。
10;在集群中录入值
在redis-cli每次录入、查询键值;redis都会计算出该key应该送往的插槽;如果不是该客户端对应服务器的插槽;redis会报错;并告知应前往的redis实例地址和端口。
redis-cli客户端提供了 –c 参数实现自动重定向。
如 redis-cli -c –p 6379 登入后;再录入、查询键值对可以自动重定向。
不在一个slot下的键值;是不能使用mget,mset等多键操作。
可以通过{}来定义组的概念;从而使key中{}内相同内容的键值对放到一个slot中去。
11;查询集群中的值
CLUSTER GETKEYSINSLOT <slot> <count> 返回 count 个 slot 槽中的键。
12;故障恢复
如果主节点下线?从节点能否自动升为主节点?注意;15秒超时
主节点恢复后;主从关系会如何?主节点回来变成从机。
如果所有某一段插槽的主从节点都宕掉;redis服务是否还能继续?
如果某一段插槽的主从都挂掉;而cluster-require-full-coverage 为yes ;那么整个集群都挂掉
如果某一段插槽的主从都挂掉;而cluster-require-full-coverage 为no ;那么该插槽数据全都不能使用;也无法存储。
redis.conf中的参数;
cluster-require-full-coverage
13;集群的Jedis开发
即使连接的不是主机;集群会自动切换主机存储。主机写;从机读。
无中心化主从集群。无论从哪台主机写的数据;其他主机上都能读到数据。
private static JedisCluster jedisCluster=null;
public static JedisCluster getJedisCluster(){
if(jedisCluster==null){
Set<HostAndPort> hostAndPortSet =new HashSet<>();
hostAndPortSet.add(new HostAndPort(;hdp3;,6390));
hostAndPortSet.add(new HostAndPort(;hdp3;,6391));
JedisPoolConfig jedisPoolConfig =new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(10); //最大可用连接数
jedisPoolConfig.setMaxIdle(5); //最大闲置连接数
jedisPoolConfig.setMinIdle(5); //最小闲置连接数
jedisPoolConfig.setBlockWhenExhausted(true); //连接耗尽是否等待
jedisPoolConfig.setMaxWaitMillis(2000); //等待时间
jedisPoolConfig.setTestOnBorrow(true); //取连接的时候进行一下测试 ping pong
jedisCluster=new JedisCluster(hostAndPortSet,jedisPoolConfig);
return jedisCluster;
}else{
return jedisCluster;
}
}
public static void main(String[] args) {
JedisCluster jedisCluster = RedisUtil.getJedisCluster();
jedisCluster.set(;k100;,;v100;);
}
14;Redis 集群提供了以下好处
实现扩容
分摊压力
无中心配置相对简单
15;Redis 集群的不足
多键操作是不被支持的
多键的Redis事务是不被支持的。lua脚本不被支持
由于集群方案出现较晚;很多公司已经采用了其他的集群方案;而代理或者客户端分片的方案想要迁移至redis cluster;需要整体迁移而不是逐步过渡;复杂度较大。
1;先更新数据库还是先更新缓存?
在更新缓存时;对于更新完数据库;是更新缓存呢;还是删除缓存。又或者是先删除缓存;再更新数据库;其实都会存在一定的问题。
Cache Aside Pattern;旁路缓存模式;
这是最常用的缓存使用方式了。其具体逻辑如下
失效;应用程序先从cache取数据;没有得到;则从数据库中取数据;成功后;放到缓存中。命中;应用程序从cache中取数据;取到后返回。更新;先把数据存到数据库中;成功后;再让缓存失效。我们更新时是先更新数据库;数据库更新成功后再让缓存失效。那么这种方式真的没有问题么?
我们可以考虑一下以下的并发场景;
缓存key1刚好失效请求A发起读请求没有命中缓存去数据库查询;此时查询出来的结果是老的数据请求B发起更新请求;先更新数据库;请求B让缓存失效此时请求A将步骤2中读取出来的老数据写入缓存以上的并发场景理论上确实会发生导致脏数据的产生;但是在实际的生产环境中出现的概率非常低;因为这个条件需要发生在读缓存时缓存失效;而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多;而且还要锁表;而读操作必需在写操作前进入数据库操作;而又要晚于写操作更新缓存;所有的这些条件都具备的概率基本并不大。
先删除缓存;再更新数据库
该方案会导致数据不一致的原因如下;
请求A进行写操作;删除缓存;并更新数据库;此时还没更新;请求B查询发现缓存不存在请求B去数据库查询得到旧的值请求B将旧的值写入缓存此时请求A将新值更新进数据库这种情况下如果缓存不被更新或者被过期策略淘汰;那么这个数据将永远是脏数据。
先更新数据库;再更新缓存
我们可以看下以下的并发场景;
线程A和线程B同时去更新数据key1线程A更新了数据库数据key1;此时value1线程A更新了数据库数据key1;此时value2(最新的数据)线程B更新的缓存key1的值为value2线程A更新的缓存key1的值为value1由于线程A的更新数据库操作早于线程B;线程B更新的结果value2才是最新的结果;最终应该把value2放入缓存才符合实际的需求。但是因为网络等原因;B却比A更早更新了缓存。这就导致了脏数据;因此这种方案存在线程安全问题。
先更新缓存;再更新数据库
该方案不做考虑;若先更新缓存;缓存更新成功;但是更新数据库时发生异常导致回滚;那么缓存中的数据无法回滚;导致数据不一致。
2;redis作为高速缓存和数据库的数据一致性的问题;如果数据更新的话是先更新数据库还是先更新缓存?若果先更新数据库再更新缓存会涉及什么问题?
首先;缓存由于其高并发和高性能的特性;已经在项目中被广泛使用。在读取缓存方面;大家没啥疑问;都是按照下图的流程来进行业务操作。
但是在更新缓存方面;对于更新完数据库;是更新缓存呢;还是删除缓存。又或者是先删除缓存;再更新数据库;其实大家存在很大的争议。
先做一个说明;从理论上来说;给缓存设置过期时间;是保证最终一致性的解决方案。这种方案下;我们可以对存入缓存的数据设置过期时间;所有的写操作以数据库为准;对缓存操作只是尽最大努力即可。也就是说如果数据库写成功;缓存更新失败;那么只要到达过期时间;则后面的读请求自然会从数据库中读取新值然后回填缓存。因此;接下来讨论的思路不依赖于给缓存设置过期时间这个方案。
在这里;我们讨论三种更新策略;
先更新数据库;再更新缓存
先删除缓存;再更新数据库
先更新数据库;再删除缓存
应该没人问我;为什么没有先更新缓存;再更新数据库这种策略。
(1)先更新数据库;再更新缓存
这套方案;大家是普遍反对的。为什么呢?有如下两点原因。
- 原因一;线程安全角度;
同时有请求A和请求B进行更新操作;那么会出现
;1;线程A更新了数据库
;2;线程B更新了数据库
;3;线程B更新了缓存
;4;线程A更新了缓存
这就出现请求A更新缓存应该比请求B更新缓存早才对;但是因为网络等原因;B却比A更早更新了缓存。这就导致了脏数据;因此不考虑。
- 原因二;业务场景角度;
有如下两点;
;1;如果你是一个写数据库场景比较多;而读数据场景比较少的业务需求;采用这种方案就会导致;数据压根还没读到;缓存就被频繁的更新;浪费性能。
;2;如果你写入数据库的值;并不是直接写入缓存的;而是要经过一系列复杂的计算再写入缓存。那么;每次写入数据库后;都再次计算写入缓存的值;无疑是浪费性能的。显然;删除缓存更为适合。
接下来讨论的就是争议最大的;先删缓存;再更新数据库。还是先更新数据库;再删缓存的问题。
(2)先删缓存;再更新数据库
该方案会导致不一致的原因是。同时有一个请求A进行更新操作;另一个请求B进行查询操作。那么会出现如下情形:
;1;请求A进行写操作;删除缓存
;2;请求B查询发现缓存不存在
;3;请求B去数据库查询得到旧值
;4;请求B将旧值写入缓存
;5;请求A将新值写入数据库
上述情况就会导致不一致的情形出现。而且;如果不采用给缓存设置过期时间策略;该数据永远都是脏数据。
那么;如何解决呢?采用延时双删策略
伪代码如下;
public void write(String key,Object data){
redis.delKey(key);
db.updateData(data);
Thread.sleep(1000);
redis.delKey(key);
}
转化为中文描述就是
;1;先淘汰缓存
;2;再写数据库;这两步和原来一样;
;3;休眠1秒;再次淘汰缓存
这么做;可以将1秒内所造成的缓存脏数据;再次删除。
那么;这个1秒怎么确定的;具体该休眠多久呢?
针对上面的情形;读者应该自行评估自己的项目的读数据业务逻辑的耗时。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上;加几百ms即可。这么做的目的;就是确保读请求结束;写请求可以删除读请求造成的缓存脏数据。
如果你用了mysql的读写分离架构怎么办?
ok;在这种情况下;造成数据不一致的原因如下;还是两个请求;一个请求A进行更新操作;另一个请求B进行查询操作。
;1;请求A进行写操作;删除缓存
;2;请求A将数据写入数据库了;
;3;请求B查询缓存发现;缓存没有值
;4;请求B去从库查询;这时;还没有完成主从同步;因此查询到的是旧值
;5;请求B将旧值写入缓存
;6;数据库完成主从同步;从库变为新值
上述情形;就是数据不一致的原因。还是使用双删延时策略。只是;睡眠时间修改为在主从同步的延时时间基础上;加几百ms。
采用这种同步淘汰策略;吞吐量降低怎么办?
ok;那就将第二次删除作为异步的。自己起一个线程;异步删除。这样;写的请求就不用沉睡一段时间后了;再返回。这么做;加大吞吐量。
第二次删除,如果删除失败怎么办?
这是个非常好的问题;因为第二次删除失败;就会出现如下情形。还是有两个请求;一个请求A进行更新操作;另一个请求B进行查询操作;为了方便;假设是单库;
;1;请求A进行写操作;删除缓存
;2;请求B查询发现缓存不存在
;3;请求B去数据库查询得到旧值
;4;请求B将旧值写入缓存
;5;请求A将新值写入数据库
;6;请求A试图去删除请求B写入对缓存值;结果失败了。
ok,这也就是说。如果第二次删除缓存失败;会再次出现缓存和数据库不一致的问题。
如何解决呢?
具体解决方案;且看博主对第(3)种更新策略的解析。
(3)先更新数据库;再删缓存
首先;先说一下。老外提出了一个缓存更新套路;名为《Cache-Aside pattern》。其中就指出
失效;应用程序先从cache取数据;没有得到;则从数据库中取数据;成功后;放到缓存中。
命中;应用程序从cache中取数据;取到后返回。
更新;先把数据存到数据库中;成功后;再让缓存失效。
另外;知名社交网站facebook也在论文《Scaling Memcache at Facebook》中提出;他们用的也是先更新数据库;再删缓存的策略。
这种情况不存在并发问题么?
不是的。假设这会有两个请求;一个请求A做查询操作;一个请求B做更新操作;那么会有如下情形产生
;1;缓存刚好失效
;2;请求A查询数据库;得一个旧值
;3;请求B将新值写入数据库
;4;请求B删除缓存
;5;请求A将查到的旧值写入缓存
ok;如果发生上述情况;确实是会发生脏数据。
然而;发生这种情况的概率又有多少呢?
发生上述情况有一个先天性条件;就是步骤;3;的写数据库操作比步骤;2;的读数据库操作耗时更短;才有可能使得步骤;4;先于步骤;5;。可是;大家想想;数据库的读操作的速度远快于写操作的;不然做读写分离干嘛;做读写分离的意义就是因为读操作比较快;耗资源少;;因此步骤;3;耗时比步骤;2;更短;这一情形很难出现。
假设;有人非要抬杠;有强迫症;一定要解决怎么办?
如何解决上述并发问题?
首先;给缓存设有效时间是一种方案。其次;采用策略;2;里给出的异步延时删除策略;保证读请求完成以后;再进行删除操作。
还有其他造成不一致的原因么?
有的;这也是缓存更新策略;2;和缓存更新策略;3;都存在的一个问题;如果删缓存失败了怎么办;那不是会有不一致的情况出现么。比如一个写数据请求;然后写入数据库了;删缓存失败了;这会就出现不一致的情况了。这也是缓存更新策略;2;里留下的最后一个疑问。
如何解决?
提供一个保障的重试机制即可;这里给出两套方案。
方案一;
如下图所示;
流程如下所示;
;1;更新数据库数据;
;2;缓存因为种种问题删除失败
;3;将需要删除的key发送至消息队列
;4;自己消费消息;获得需要删除的key
;5;继续重试删除操作;直到成功
然而;该方案有一个缺点;对业务线代码造成大量的侵入。于是有了方案二;在方案二中;启动一个订阅程序去订阅数据库的binlog;获得需要操作的数据。在应用程序中;另起一段程序;获得这个订阅程序传来的信息;进行删除缓存操作。
方案二;
流程如下图所示;
;1;更新数据库数据
;2;数据库会将操作信息写入binlog日志当中
;3;订阅程序提取出所需要的数据以及key
;4;另起一段非业务代码;获得该信息
;5;尝试删除缓存操作;发现删除失败
;6;将这些信息发送至消息队列
;7;重新从消息队列中获得该数据;重试操作。
备注说明;上述的订阅binlog程序在mysql中有现成的中间件叫canal;可以完成订阅binlog日志的功能。至于Oracle中;博主目前不知道有没有现成中间件可以使用。另外;重试机制;博主是采用的是消息队列的方式。如果对一致性要求不是很高;直接在程序中另起一个线程;每隔一段时间去重试即可;这些大家可以灵活自由发挥;只是提供一个思路。
存粒度控制
选用全量属性;通用性会更好;也便于维护;像user表这种;用全量属性还可以;
但我们选用缓存就需要考虑性能和空间的问题;只保存我们需要的属性就好了;但后期表结构改了;维护性很差;
3;缓存穿透;;直接对存储层操作;失去了缓存层的意义;
查询一个数据库中不存在的数据;比如商品详情;查询一个不存在的ID;每次都会访问DB;如果有人恶意破坏;很可能直接对DB造成过大地压力。
解决方案;
1.当通过某一个key去查询数据的时候;如果对应在数据库中的数据都不存在;我们将此key对应的value设置为一个默认的值;比如“NULL”;并设置一个缓存的失效时间;这时在缓存失效之前;所有通过此key的访问都被缓存挡住了。后面如果此key对应的数据在DB中存在时;缓存失效之后;通过此key再去访问数据;就能拿到新的value了。
2.常见的则是采用布隆过滤器;可以用很小的内存保留很多的数据;;将所有可能存在的数据哈希到一个足够大的bitmap中;一个一定不存在的数据会被 这个bitmap拦截掉;从而避免了对底层存储系统的查询压力。;布隆过滤器;实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法;缺点是有一定的误识别率和删除困难。;
关于布隆过滤器;
4;缓存雪崩;;缓存失效;
缓存同一时间大面积的失效;所以;后面的请求都会落到数据库上;造成数据库短时间内承受大量请求而崩掉。
解决方案;
1.将系统中key的缓存失效时间均匀地错开;防止统一时间点有大量的key对应的缓存失效;
2.重新设计缓存的使用方式;当我们通过key去查询数据时;首先查询缓存;如果此时缓存中查询不到;就通过分布式锁进行加锁;取得锁的进程查DB并设置缓存;然后解锁;其他进程如果发现有锁就等待;然后等解锁后返回缓存数据或者再次查询DB。
3.尽量保证整个 redis 集群的高可用性;发现机器宕机尽快补上
4.本地ehcache缓存 ; hystrix限流&降级;避免MySQL崩掉
假如已经崩溃了;也可以利用redis的持久化机制将保存的数据尽快恢复到缓存里。
5;缓存无底洞
为了满足业务大量加节点;但是性能没提升反而下降。
当客户端增加一个缓存的时候;只需要 mget 一次;但是如果增加到三台缓存;这个时候则需要 mget 三次了;网络通信的时间增加了;;每增加一台;客户端都需要做一次新的 mget;给服务器造成性能上的压力。
同时;mget 需要等待最慢的一台机器操作完成才能算是完成了 mget 操作。这还是并行的设计;如果是串行的设计就更加慢了。
通过上面这个实例可以总结出;更多的机器;=更高的性能
但是并不是没办法;一般在优化 IO 的时候可以采用以下几个方法。
1.命令的优化。例如慢查下 keys、hgetall bigkey。
2.我们需要减少网络通讯的次数。这个优化在实际应用中使用次数是最多的;我们尽量减少通讯次数。
3.降低接入成本。比如使用客户端长连接或者连接池、NIO 等等。
6;hashMap底层?为什么jdk1.8要用红黑树实现?什么时候会出现线程不安全?怎么解决线程不安全?默认初始容量是16;如果我改成7;容量会变成7么??为什么?
在JDK1.6;JDK1.7中;HashMap采用位桶;链表实现;即使用链表处理冲突;同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多;即hash值相等的元素较多时;通过key值依次查找的效率较低。而JDK1.8中;HashMap采用位桶;链表;红黑树实现;当链表长度超过阈值;8;时;将链表转换为红黑树;这样大大减少了查找时间。
不安全原因;
(1) 在put的时候;因为该方法不是同步的;假如有两个线程A,B它们的put的key的hash值相同;不论是从头插入还是从尾插入;假如A获取了插入位置为x;但是还未插入;此时B也计算出待插入位置为x;则不论AB插入的先后顺序肯定有一个会丢失
以下是put方法;
(2) 在扩容的时候;jdk1.8之前是采用头插法;当两个线程同时检测到hashmap需要扩容;在进行同时扩容的时候有可能会造成链表的循环;主要原因就是;采用头插法;新链表与旧链表的顺序是反的;在1.8后采用尾插法就不会出现这种问题;同时1.8的链表长度如果大于8就会转变成红黑树。
默认容量为16的原因;
关于这个默认容量的选择;JDK并没有给出官方解释;笔者也没有在网上找到关于这个任何有价值的资料。;如果哪位有相关的权威资料或者想法;可以留言交流;
根据作者的推断;这应该就是个经验值;Experience Value;;既然一定要设置一个默认的2^n 作为初始值;那么就需要在效率和内存使用上做一个权衡。这个值既不能太小;也不能太大。
太小了就有可能频繁发生扩容;影响效率。太大了又浪费空间;不划算。
所以;16就作为一个经验值被采用了。
7;常见的线程池有哪些?线程池中一个线程死了;就没有线程了么?如果在线程池中new了一个线程;这个线程是存在还是不存在?线程池中的一些参数有哪些?newCachedPool最大可开启的线程数是多少?
常见的线程池;
1、newCachedThreadPool
创建一个可缓存线程池;如果线程池长度超过处理需要;可灵活回收空闲线程;若无可回收;则新建线程。
这种类型的线程池特点是;
工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。如果长时间没有往线程池中提交任务;即如果工作线程空闲了指定的时间(默认为1分钟);则该工作线程将自动终止。终止后;如果你又提交了新的任务;则线程池重新创建一个工作线程。在使用CachedThreadPool时;一定要注意控制任务的数量;否则;由于大量线程同时运行;很有会造成系统瘫痪。
2、newFixedThreadPool
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程;如果工作线程数量达到线程池初始的最大数;则将提交的任务存入到池队列中。FixedThreadPool是一个典型且优秀的线程池;它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是;在线程池空闲时;即线程池中没有可运行任务时;它不会释放工作线程;还会占用一定的系统资源。
3、newSingleThreadExecutor
创建一个单线程化的Executor;即只创建唯一的工作者线程来执行任务;它只会用唯一的工作线程来执行任务;保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束;会有另一个取代它;保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务;并且在任意给定的时间不会有多个线程是活动的。
4、newScheduleThreadPool
创建一个定长的线程池;而且支持定时的以及周期性的任务执行;支持定时及周期性任务执行。
手撕代码;
给你一个整数数组 nums 和一个正整数 k;请你判断是否可以把这个数组划分成一些由 k 个连续数字组成的集合。
如果可以;请返回 True;否则;返回 False。
示例 1;
输入;nums = [1,2,3,3,4,4,5,6], k = 4
输出;true
解释;数组可以分成 [1,2,3,4] 和 [3,4,5,6]。
示例 2;
输入;nums = [3,2,1,2,3,4,3,4,5,9,10,11], k = 3
输出;true
解释;数组可以分成 [1,2,3] , [2,3,4] , [3,4,5] 和 [9,10,11]。
示例 3;
输入;nums = [3,3,2,2,1,1], k = 3
输出;true
示例 4;
输入;nums = [1,2,3,4], k = 3
输出;false
解释;数组不能分成几个大小为 3 的子数组。
提示;
1 <= nums.length
<= 10^5
1 <= nums[i] <= 10^9
1 <= k <= nums.length
HBase是一种分布式、可扩展、支持海量数据存储的NoSQL数据库。
逻辑上;HBase的数据模型同关系型数据库很类似;数据存储在一张表中;有行有列。但从HBase的底层物理存储结构;K-V;来看;HBase更像是一个multi-dimensional map。
HBase逻辑结构;
HBase物理存储结构;
1;Name Space
命名空间;类似于关系型数据库的database概念;每个命名空间下有多个表。HBase有两个自带的命名空间;分别是hbase和default;hbase中存放的是HBase内置的表;default表是用户默认使用的命名空间。
2;Table
类似于关系型数据库的表概念。不同的是;HBase定义表时只需要声明列族即可;不需要声明具体的列。这意味着;往HBase写入数据时;字段可以动态、按需指定。因此;和关系型数据库相比;HBase能够轻松应对字段变更的场景。
3;Row
HBase表中的每行数据都由一个RowKey和多个Column;列;组成;数据是按照RowKey的字典顺序存储的;并且查询数据时只能根据RowKey进行检索;所以RowKey的设计十分重要。
4;Column
HBase中的每个列都由Column Family(列族)和Column Qualifier;列限定符;进行限定;例如info;name;info;age。建表时;只需指明列族;而列限定符无需预先定义。
5;Time Stamp
用于标识数据的不同版本;version;;每条数据写入时;系统会自动为其加上该字段;其值为写入HBase的时间。
6;Cell
由{rowkey, column Family;column Qualifier, time Stamp} 唯一确定的单元。cell中的数据全部是字节码形式存贮。
架构角色;
1;Region Server
Region Server为 Region的管理者;其实现类为HRegionServer;主要作用如下;
对于数据的操作;get, put, delete;
对于Region的操作;splitRegion、compactRegion。
2;Master
Master是所有Region Server的管理者;其实现类为HMaster;主要作用如下;
对于表的操作;create, delete, alter
对于RegionServer的操作;分配regions到每个RegionServer;监控每个RegionServer的状态;负载均衡和故障转移。
3;Zookeeper
HBase通过Zookeeper来做master的高可用、RegionServer的监控、元数据的入口以及集群配置的维护等工作。
4;HDFS
HDFS为Hbase提供最终的底层数据存储服务;同时为HBase提供高可用的支持。
1. Zookeeper正常部署
首先保证Zookeeper集群的正常部署;并启动之;
[yyds;hadoop102 zookeeper-3.5.7]$ bin/zkServer.sh start
[yyds;hadoop103 zookeeper-3.5.7]$ bin/zkServer.sh start
[yyds;hadoop104 zookeeper-3.5.7]$ bin/zkServer.sh start
2. Hadoop正常部署
Hadoop集群的正常部署并启动;
[yyds;hadoop102 hadoop-3.1.3]$ sbin/start-dfs.sh
[yyds;hadoop103 hadoop-3.1.3]$ sbin/start-yarn.sh
3. HBase的解压
解压Hbase到指定目录;
[yyds;hadoop102 software]$ tar -zxvf hbase-2.0.5-bin.tar.gz -C /opt/module
[yyds;hadoop102 software]$ mv /opt/module/hbase-2.0.5 /opt/module/hbase
配置环境变量;
[yyds;hadoop102 ~]$ sudo vim /etc/profile.d/my_env.sh
添加
#HBASE_HOME
export HBASE_HOME=/opt/module/hbase
export PATH=$PATH:$HBASE_HOME/bin
4. HBase的配置文件
修改HBase对应的配置文件。
1;hbase-env.sh修改内容
export HBASE_MANAGES_ZK=false
2;hbase-site.xml修改内容
<configuration>
<property>
<name>hbase.rootdir</name>
<value>hdfs://hadoop102:8020/hbase</value>
</property>
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<property>
<name>hbase.zookeeper.quorum</name>
<value>hadoop102,hadoop103,hadoop104</value>
</property>
</configuration>
3;regionservers
hadoop102
hadoop103
hadoop104
5. HBase远程发送到其他集群
[yyds;hadoop102 module]$ xsync hbase/
6. HBase服务的启动
1;单点启动
[yyds;hadoop102 hbase]$ bin/hbase-daemon.sh start master
[yyds;hadoop102 hbase]$ bin/hbase-daemon.sh start regionserver
提示;如果集群之间的节点时间不同步;会导致regionserver无法启动;抛出ClockOutOfSyncException异常。
修复提示;
a、同步时间服务
b、属性;hbase.master.maxclockskew设置更大的值
<property>
<name>hbase.master.maxclockskew</name>
<value>180000</value>
<description>Time difference of regionserver from master</description>
</property>
2;群启
[yyds;hadoop102 hbase]$ bin/start-hbase.sh
对应的停止服务;
[yyds;hadoop102 hbase]$ bin/stop-hbase.sh
7. 查看HBase页面
启动成功后;可以通过“host:port”的方式来访问HBase管理页面;例如;
http://hadoop102:16010
8. 高可用
在HBase中HMaster负责监控HRegionServer的生命周期;均衡RegionServer的负载;如果HMaster挂掉了;那么整个HBase集群将陷入不健康的状态;并且此时的工作状态并不会维持太久。
所以HBase支持对HMaster的高可用配置;
1;关闭HBase集群;如果没有开启则跳过此步;
[yyds;hadoop102 hbase]$ bin/stop-hbase.sh
2;在conf目录下创建backup-masters文件
[yyds;hadoop102 hbase]$ touch conf/backup-masters
3;在backup-masters文件中配置高可用HMaster节点
[yyds;hadoop102 hbase]$ echo hadoop103 > conf/backup-masters
4;将整个conf目录scp到其他节点
[yyds;hadoop102 hbase]$ scp -r conf/ hadoop103:/opt/module/hbase/
[yyds;hadoop102 hbase]$ scp -r conf/ hadoop104:/opt/module/hbase/
5;打开页面测试查看
http://hadooo102:16010
1. 基本操作
1;进入HBase客户端命令行
[yyds;hadoop102 hbase]$ bin/hbase shell
2;查看帮助命令
hbase(main):001:0> help
2. namespace的操作
1;查看当前Hbase中有哪些namespace
hbase(main):002:0> list_namespace
NAMESPACE
default(创建表时未指定命名空间的话默认在default下)
hbase(系统使用的;用来存放系统相关的元数据信息等;勿随便操作)
2;创建namespace
hbase(main):010:0> create_namespace ;test;
hbase(main):010:0> create_namespace ;test01;, {;author;=>;wyh;, ;create_time;=>;2020-03-10 08:08:08;}
3;查看namespace
hbase(main):010:0> describe_namespace ;test01;
4;修改namespace的信息;添加或者修改属性;
hbase(main):010:0> alter_namespace ;test01;, {METHOD => ;set;, ;author; => ;weiyunhui;}
添加或者修改属性;
alter_namespace ;ns1;, {METHOD => ;set;, ;PROPERTY_NAME; => ;PROPERTY_VALUE;}
删除属性;
alter_namespace ;ns1;, {METHOD => ;unset;, NAME => ; PROPERTY_NAME ;}
5;删除namespace
hbase(main):010:0> drop_namespace ;test01;
注意: 要删除的namespace必须是空的;其下没有表。
3. 表的操作
0;查看当前数据库中有哪些表
hbase(main):002:0> list
1;创建表
hbase(main):002:0> create ;student;,;info;
2;插入数据到表
hbase(main):003:0> put ;student;,;1001;,;info:sex;,;male;
hbase(main):004:0> put ;student;,;1001;,;info:age;,;18;
hbase(main):005:0> put ;student;,;1002;,;info:name;,;Janna;
hbase(main):006:0> put ;student;,;1002;,;info:sex;,;female;
hbase(main):007:0> put ;student;,;1002;,;info:age;,;20;
3;扫描查看表数据
hbase(main):008:0> scan ;student;
hbase(main):009:0> scan ;student;,{STARTROW => ;1001;, STOPROW => ;1001;}
hbase(main):010:0> scan ;student;,{STARTROW => ;1001;}
4;查看表结构
hbase(main):011:0> describe ;student;
5;更新指定字段的数据
hbase(main):012:0> put ;student;,;1001;,;info:name;,;Nick;
hbase(main):013:0> put ;student;,;1001;,;info:age;,;100;
6;查看“指定行”或“指定列族:列”的数据
hbase(main):014:0> get ;student;,;1001;
hbase(main):015:0> get ;student;,;1001;,;info:name;
7;统计表数据行数
hbase(main):021:0> count ;student;
8;删除数据
删除某rowkey的全部数据;
hbase(main):016:0> deleteall ;student;,;1001;
删除某rowkey的某一列数据;
hbase(main):017:0> delete ;student;,;1002;,;info:sex;
9;清空表数据
hbase(main):018:0> truncate ;student;
提示;清空表的操作顺序为先disable;然后再truncate。
10;删除表
首先需要先让该表为disable状态;
hbase(main):019:0> disable ;student;
然后才能drop这个表;
hbase(main):020:0> drop ;student;
提示;如果直接drop表;会报错;ERROR: Table student is enabled. Disable it first。
11;变更表信息
将info列族中的数据存放3个版本;
hbase(main):022:0> alter ;student;,{NAME=>;info;,VERSIONS=>3}
hbase(main):022:0> get ;student;,;1001;,{COLUMN=>;info:name;,VERSIONS=>3}
1. RegionServer 架构
1;StoreFile
保存实际数据的物理文件;StoreFile以Hfile的形式存储在HDFS上。每个Store会有一个或多个StoreFile;HFile;;数据在每个StoreFile中都是有序的。
2;MemStore
写缓存;由于HFile中的数据要求是有序的;所以数据是先存储在MemStore中;排好序后;等到达刷写时机才会刷写到HFile;每次刷写都会形成一个新的HFile。
3;WAL
由于数据要经MemStore排序后才能刷写到HFile;但把数据保存在内存中会有很高的概率导致数据丢失;为了解决这个问题;数据会先写在一个叫做Write-Ahead logfile的文件中;然后再写入MemStore中。所以在系统出现故障的时候;数据可以通过这个日志文件重建。
4;BlockCache
读缓存;每次查询出的数据会缓存在BlockCache中;方便下次查询。
2. HBase 写流程
写流程;
1;Client先访问zookeeper;获取hbase:meta表位于哪个Region Server。
2;访问对应的Region Server;获取hbase:meta表;根据读请求的namespace:table/rowkey;查询出目标数据位于哪个Region Server中的哪个Region中。并将该table的region信息以及meta表的位置信息缓存在客户端的meta cache;方便下次访问。
3;与目标Region Server进行通讯;
4;将数据顺序写入;追加;到WAL;
5;将数据写入对应的MemStore;数据会在MemStore进行排序;
6;向客户端发送ack;
7;等达到MemStore的刷写时机后;将数据刷写到HFile。
3. MemStore Flush
MemStore刷写时机;
1. 当某个memstore的大小达到了hbase.hregion.memstore.flush.size;默认值128M;;其所在region的所有memstore都会刷写。
当memstore的大小达到了
hbase.hregion.memstore.flush.size;默认值128M;
* hbase.hregion.memstore.block.multiplier;默认值4;
时;会阻止继续往该memstore写数据。
2. 当region server中memstore的总大小达到
java_heapsize
*hbase.regionserver.global.memstore.size;默认值0.4;
*hbase.regionserver.global.memstore.size.lower.limit;默认值0.95;;
region会按照其所有memstore的大小顺序;由大到小;依次进行刷写。直到region server中所有memstore的总大小减小到上述值以下。
当region server中memstore的总大小达到
java_heapsize
*hbase.regionserver.global.memstore.size;默认值0.4;
时;会阻止继续往所有的memstore写数据。
3. 到达自动刷写的时间;也会触发memstore flush。自动刷新的时间间隔由该属性进行配置hbase.regionserver.optionalcacheflushinterval;默认1小时;。
4. 当WAL文件的数量超过hbase.regionserver.max.logs;region会按照时间顺序依次进行刷写;直到WAL文件数量减小到hbase.regionserver.max.logs以下;该属性名已经废弃;现无需手动设置;最大值为32;。
4. HBase 读流程
1;整体流程
2;Merge细节
读流程;
1;Client先访问zookeeper;获取hbase:meta表位于哪个Region Server。
2;访问对应的Region Server;获取hbase:meta表;根据读请求的namespace:table/rowkey;查询出目标数据位于哪个Region Server中的哪个Region中。并将该table的region信息以及meta表的位置信息缓存在客户端的meta cache;方便下次访问。
3;与目标Region Server进行通讯;
4;分别在MemStore和Store File;HFile;中查询目标数据;并将查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本;time stamp;或者不同的类型;Put/Delete;。
5;将查询到的新的数据块;Block;HFile数据存储单元;默认大小为64KB;缓存到Block Cache。
6;将合并后的最终结果返回给客户端。
5. StoreFile Compaction
由于memstore每次刷写都会生成一个新的HFile;且同一个字段的不同版本;timestamp;和不同类型;Put/Delete;有可能会分布在不同的HFile中;因此查询时需要遍历所有的HFile。为了减少HFile的个数;以及清理掉过期和删除的数据;会进行StoreFile Compaction。
Compaction分为两种;分别是Minor Compaction和Major Compaction。Minor Compaction会将临近的若干个较小的HFile合并成一个较大的HFile;并清理掉部分过期和删除的数据。Major Compaction会将一个Store下的所有的HFile合并成一个大HFile;并且会清理掉所有过期和删除的数据。
6. Region Split
默认情况下;每个Table起初只有一个Region;随着数据的不断写入;Region会自动进行拆分。刚拆分时;两个子Region都位于当前的Region Server;但处于负载均衡的考虑;HMaster有可能会将某个Region转移给其他的Region Server。
Region Split时机;
1.当1个region中的某个Store下所有StoreFile的总大小超过hbase.hregion.max.filesize;该Region就会进行拆分;0.94版本之前;。
2.当1个region中的某个Store下所有StoreFile的总大小超过Min(initialSize*R^3 ,hbase.hregion.max.filesize;);该Region就会进行拆分。其中initialSize的默认值为2*hbase.hregion.memstore.flush.size;R为当前Region Server中属于该Table的Region个数;0.94版本之后;。
具体的切分策略为;
第一次split;1^3 * 256 = 256MB
第二次split;2^3 * 256 = 2048MB
第三次split;3^3 * 256 = 6912MB
第四次split;4^3 * 256 = 16384MB > 10GB;因此取较小的值10GB
后面每次split的size都是10GB了。
3.Hbase 2.0引入了新的split策略;如果当前RegionServer上该表只有一个Region;按照2 * hbase.hregion.memstore.flush.size分裂;否则按照hbase.hregion.max.filesize分裂。
Redgion Split;
新建项目后在pom.xml中添加依赖;
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-server</artifactId>
<version>2.0.5</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>2.0.5</version>
</dependency>
1. DDL
创建HBase_DDL类。
1;判断表是否存在
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.NamespaceDescriptor;
import org.apache.hadoop.hbase.NamespaceExistException;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import java.io.IOException;
public class HBase_DDL {
//TODO 判断表是否存在
public static boolean isTableExist(String tableName) throws IOException {
//1.创建配置信息并配置
Configuration configuration = HBaseConfiguration.create();
configuration.set(;hbase.zookeeper.quorum;, ;hadoop102,hadoop103,hadoop104;);
//2.获取与HBase的连接
Connection connection = ConnectionFactory.createConnection(configuration);
//3.获取DDL操作对象
Admin admin = connection.getAdmin();
//4.判断表是否存在操作
boolean exists = admin.tableExists(TableName.valueOf(tableName));
//5.关闭连接
admin.close();
connection.close();
//6.返回结果
return exists;
}
}
2;创建表
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.NamespaceDescriptor;
import org.apache.hadoop.hbase.NamespaceExistException;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import java.io.IOException;
public class HBase_DDL {
//TODO 创建表
public static void createTable(String tableName, String... cfs) throws IOException {
//1.判断是否存在列族信息
if (cfs.length <= 0) {
System.out.println(;请设置列族信息;;);
return;
}
//2.判断表是否存在
if (isTableExist(tableName)) {
System.out.println(;需要创建的表已存在;;);
return;
}
//3.创建配置信息并配置
Configuration configuration = HBaseConfiguration.create();
configuration.set(;hbase.zookeeper.quorum;, ;hadoop102,hadoop103,hadoop104;);
//4.获取与HBase的连接
Connection connection = ConnectionFactory.createConnection(configuration);
//5.获取DDL操作对象
Admin admin = connection.getAdmin();
//6.创建表描述器构造器
TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(TableName.valueOf(tableName));
//7.循环添加列族信息
for (String cf : cfs) {
ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(cf));
tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptorBuilder.build());
}
//8.执行创建表的操作
admin.createTable(tableDescriptorBuilder.build());
//9.关闭资源
admin.close();
connection.close();
}
}
3;删除表
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.NamespaceDescriptor;
import org.apache.hadoop.hbase.NamespaceExistException;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import java.io.IOException;
public class HBase_DDL {
//TODO 删除表
public static void dropTable(String tableName) throws IOException {
//1.判断表是否存在
if (!isTableExist(tableName)) {
System.out.println(;需要删除的表不存在;;);
return;
}
//2.创建配置信息并配置
Configuration configuration = HBaseConfiguration.create();
configuration.set(;hbase.zookeeper.quorum;, ;hadoop102,hadoop103,hadoop104;);
//3.获取与HBase的连接
Connection connection = ConnectionFactory.createConnection(configuration);
//4.获取DDL操作对象
Admin admin = connection.getAdmin();
//5.使表下线
TableName name = TableName.valueOf(tableName);
admin.disableTable(name);
//6.执行删除表操作
admin.deleteTable(name);
//7.关闭资源
admin.close();
connection.close();
}
}
4;创建命名空间
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.NamespaceDescriptor;
import org.apache.hadoop.hbase.NamespaceExistException;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import java.io.IOException;
public class HBase_DDL {
//TODO 创建命名空间
public static void createNameSpace(String ns) throws IOException {
//1.创建配置信息并配置
Configuration configuration = HBaseConfiguration.create();
configuration.set(;hbase.zookeeper.quorum;, ;hadoop102,hadoop103,hadoop104;);
//2.获取与HBase的连接
Connection connection = ConnectionFactory.createConnection(configuration);
//3.获取DDL操作对象
Admin admin = connection.getAdmin();
//4.创建命名空间描述器
NamespaceDescriptor namespaceDescriptor = NamespaceDescriptor.create(ns).build();
//5.执行创建命名空间操作
try {
admin.createNamespace(namespaceDescriptor);
} catch (NamespaceExistException e) {
System.out.println(;命名空间已存在;;);
} catch (Exception e) {
e.printStackTrace();
}
//6.关闭连接
admin.close();
connection.close();
}
}
2. DML
创建类HBase_DML。
1;插入数据
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import java.io.IOException;
public class HBase_DML {
//TODO 插入数据
public static void putData(String tableName, String rowKey, String cf, String cn, String value) throws IOException {
//1.获取配置信息并设置连接参数
Configuration configuration = HBaseConfiguration.create();
configuration.set(;hbase.zookeeper.quorum;, ;hadoop102,hadoop103,hadoop104;);
//2.获取连接
Connection connection = ConnectionFactory.createConnection(configuration);
//3.获取表的连接
Table table = connection.getTable(TableName.valueOf(tableName));
//4.创建Put对象
Put put = new Put(Bytes.toBytes(rowKey));
//5.放入数据
put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(cn), Bytes.toBytes(value));
//6.执行插入数据操作
table.put(put);
//7.关闭连接
table.close();
connection.close();
}
}
2;单条数据查询
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import java.io.IOException;
public class HBase_DML {
//TODO 单条数据查询(GET)
public static void getDate(String tableName, String rowKey, String cf, String cn) throws IOException {
//1.获取配置信息并设置连接参数
Configuration configuration = HBaseConfiguration.create();
configuration.set(;hbase.zookeeper.quorum;, ;hadoop102,hadoop103,hadoop104;);
//2.获取连接
Connection connection = ConnectionFactory.createConnection(configuration);
//3.获取表的连接
Table table = connection.getTable(TableName.valueOf(tableName));
//4.创建Get对象
Get get = new Get(Bytes.toBytes(rowKey));
// 指定列族查询
// get.addFamily(Bytes.toBytes(cf));
// 指定列族:列查询
// get.addColumn(Bytes.toBytes(cf), Bytes.toBytes(cn));
//5.查询数据
Result result = table.get(get);
//6.解析result
for (Cell cell : result.rawCells()) {
System.out.println(;ROW:; ; Bytes.toString(CellUtil.cloneRow(cell)) ;
; CF:; ; Bytes.toString(CellUtil.cloneFamily(cell));
; CL:; ; Bytes.toString(CellUtil.cloneQualifier(cell));
; VALUE:; ; Bytes.toString(CellUtil.cloneValue(cell)));
}
//7.关闭连接
table.close();
connection.close();
}
}
3;扫描数据
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import java.io.IOException;
public class HBase_DML {
//TODO 扫描数据(Scan)
public static void scanTable(String tableName) throws IOException {
//1.获取配置信息并设置连接参数
Configuration configuration = HBaseConfiguration.create();
configuration.set(;hbase.zookeeper.quorum;, ;hadoop102,hadoop103,hadoop104;);
//2.获取连接
Connection connection = ConnectionFactory.createConnection(configuration);
//3.获取表的连接
Table table = connection.getTable(TableName.valueOf(tableName));
//4.创建Scan对象
Scan scan = new Scan();
//5.扫描数据
ResultScanner results = table.getScanner(scan);
//6.解析results
for (Result result : results) {
for (Cell cell : result.rawCells()) {
System.out.println(
Bytes.toString(CellUtil.cloneRow(cell));;:;;
Bytes.toString(CellUtil.cloneFamily(cell));;:; ;
Bytes.toString(CellUtil.cloneQualifier(cell)) ;;:; ;
Bytes.toString(CellUtil.cloneValue(cell))
);
}
}
//7.关闭资源
table.close();
connection.close();
}
}
4;删除数据
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import java.io.IOException;
public class HBase_DML {
//TODO 删除数据
public static void deletaData(String tableName, String rowKey, String cf, String cn) throws IOException {
//1.获取配置信息并设置连接参数
Configuration configuration = HBaseConfiguration.create();
configuration.set(;hbase.zookeeper.quorum;, ;hadoop102,hadoop103,hadoop104;);
//2.获取连接
Connection connection = ConnectionFactory.createConnection(configuration);
//3.获取表的连接
Table table = connection.getTable(TableName.valueOf(tableName));
//4.创建Delete对象
Delete delete = new Delete(Bytes.toBytes(rowKey));
// 指定列族删除数据
// delete.addFamily(Bytes.toBytes(cf));
// 指定列族:列删除数据(所有版本)
// delete.addColumn(Bytes.toBytes(cf), Bytes.toBytes(cn));
// 指定列族:列删除数据(指定版本)
// delete.addColumns(Bytes.toBytes(cf), Bytes.toBytes(cn));
//5.执行删除数据操作
table.delete(delete);
//6.关闭资源
table.close();
connection.close();
}
}
1. 预分区
每一个region维护着startRow与endRowKey;如果加入的数据符合某个region维护的rowKey范围;则该数据交给这个region维护。那么依照这个原则;我们可以将数据所要投放的分区提前大致的规划好;以提高HBase性能。
1;手动设定预分区
hbase> create ;staff1;,;info;,SPLITS => [;1000;,;2000;,;3000;,;4000;]
2;生成16进制序列预分区
create ;staff2;,;info;,{NUMREGIONS => 15, SPLITALGO => ;HexStringSplit;}
3;按照文件中设置的规则预分区
创建splits.txt文件内容如下;
aaaa
bbbb
cccc
dddd
然后执行;
create ;staff3;,;info;,SPLITS_FILE => ;splits.txt;
4;使用JavaAPI创建预分区
//自定义算法;产生一系列Hash散列值存储在二维数组中
byte[][] splitKeys = 某个散列值函数
//创建HbaseAdmin实例
HBaseAdmin hAdmin = new HBaseAdmin(HbaseConfiguration.create());
//创建HTableDescriptor实例
HTableDescriptor tableDesc = new HTableDescriptor(tableName);
//通过HTableDescriptor实例和散列值二维数组创建带有预分区的Hbase表
hAdmin.createTable(tableDesc, splitKeys);
2. RowKey设计
一条数据的唯一标识就是rowkey;那么这条数据存储于哪个分区;取决于rowkey处于哪个一个预分区的区间内;设计rowkey的主要目的 ;就是让数据均匀的分布于所有的region中;在一定程度上防止数据倾斜。接下来我们就谈一谈rowkey常用的设计方案。
1;生成随机数、hash、散列值
比如;
原本rowKey为1001的;SHA1后变成;dd01903921ea24941c26a48f2cec24e0bb0e8cc7
原本rowKey为3001的;SHA1后变成;49042c54de64a1e9bf0b33e00245660ef92dc7bd
原本rowKey为5001的;SHA1后变成;7b61dec07e02c188790670af43e717f0f46e8913
在做此操作之前;一般我们会选择从数据集中抽取样本;来决定什么样的rowKey来Hash后作为每个分区的临界值。
2;字符串反转
20170524000001转成10000042507102
20170524000002转成20000042507102
这样也可以在一定程度上散列逐步put进来的数据。
3;字符串拼接
20170524000001_a12e
20170524000001_93i7
3. 内存优化
HBase操作过程中需要大量的内存开销;毕竟Table是可以缓存在内存中的;但是不建议分配非常大的堆内存;因为GC过程持续太久会导致RegionServer处于长期不可用状态;一般16~36G内存就可以了;如果因为框架占用内存过高导致系统内存不足;框架一样会被系统服务拖死。
4. 基础优化
1;Zookeeper会话超时时间
hbase-site.xml
属性;zookeeper.session.timeout
解释;默认值为90000毫秒;90s;。当某个RegionServer挂掉;90s之后Master才能察觉到。可适当减小此值;以加快Master响应;可调整至60000毫秒。
2;设置RPC监听数量
hbase-site.xml
属性;hbase.regionserver.handler.count
解释;默认值为30;用于指定RPC监听的数量;可以根据客户端的请求数进行调整;读写请求较多时;增加此值。
3;手动控制Major Compaction
hbase-site.xml
属性;hbase.hregion.majorcompaction
解释;默认值;604800000秒;7天;; Major Compaction的周期;若关闭自动Major Compaction;可将其设为0
4;优化HStore文件大小
hbase-site.xml
属性;hbase.hregion.max.filesize
解释;默认值10737418240;10GB;;如果需要运行HBase的MR任务;可以减小此值;因为一个region对应一个map任务;如果单个region过大;会导致map任务执行时间过长。该值的意思就是;如果HFile的大小达到这个数值;则这个region会被切分为两个Hfile。
5;优化HBase客户端缓存
hbase-site.xml
属性;hbase.client.write.buffer
解释;默认值2097152bytes;2M;用于指定HBase客户端缓存;增大该值可以减少RPC调用次数;但是会消耗更多内存;反之则反之。一般我们需要设定一定的缓存大小;以达到减少RPC次数的目的。
6;指定scan.next扫描HBase所获取的行数
hbase-site.xml
属性;hbase.client.scanner.caching
解释;用于指定scan.next方法获取的默认行数;值越大;消耗内存越大。
7;BlockCache占用RegionServer堆内存的比例
hbase-site.xml
属性;hfile.block.cache.size
解释;默认0.4;读请求比较多的情况下;可适当调大
8;MemStore占用RegionServer堆内存的比例
hbase-site.xml
属性;hbase.regionserver.global.memstore.size
解释;默认0.4;写请求较多的情况下;可适当调大
1. Phoenix定义
Phoenix是HBase的开源SQL皮肤。可以使用标准JDBC API代替HBase客户端API来创建表;插入数据和查询HBase数据。
2. Phoenix特点
1;容易集成;如Spark;Hive;Pig;Flume和Map Reduce;
2;操作简单;DML命令以及通过DDL命令创建和操作表和版本化增量更改;
3;支持HBase二级索引创建。
3. Phoenix架构
4. Phoenix安装
官网地址;Overview | Apache Phoenix
Phoenix部署;
1;上传并解压tar包
[yyds;hadoop102 module]$ tar -zxvf apache-phoenix-5.0.0-HBase-2.0-bin.tar.gz -C /opt/module/
[yyds;hadoop102 module]$ mv apache-phoenix-5.0.0-HBase-2.0-bin phoenix
2;复制server包并拷贝到各个节点的hbase/lib
[yyds;hadoop102 module]$ cd /opt/module/phoenix/
[yyds;hadoop102 phoenix]$ cp /opt/module/phoenix/phoenix-5.0.0-HBase-2.0-server.jar /opt/module/hbase/lib/
[yyds;hadoop102 phoenix]$ xsync /opt/module/hbase/lib/phoenix-5.0.0-HBase-2.0-server.jar
4;配置环境变量
#phoenix
export PHOENIX_HOME=/opt/module/phoenix
export PHOENIX_CLASSPATH=$PHOENIX_HOME
export PATH=$PATH:$PHOENIX_HOME/bin
5;重启HBase
[yyds;hadoop102 ~]$ stop-hbase.sh
[yyds;hadoop102 ~]$ start-hbase.sh
6;连接Phoenix
[yyds;hadoop101 phoenix]$ /opt/module/phoenix/bin/sqlline.py hadoop102,hadoop103,hadoop104:2181
5. Phoenix Shell操作
1;schema的操作
① 创建schema
默认情况下;在phoenix中不能直接创建schema。需要将如下的参数添加到Hbase中conf目录下的hbase-site.xml 和 phoenix中bin目录下的 hbase-site.xml中
<property>
<name>phoenix.schema.isNamespaceMappingEnabled</name>
<value>true</value>
</property>
重新启动Hbase和连接phoenix客户端;
[yyds;hadoop102 ~]$ stop-hbase.sh
[yyds;hadoop102 ~]$ start-hbase.sh
[yyds;hadoop102 ~]$ /opt/module/phoenix/bin/sqlline.py hadoop102,hadoop103,hadoop104:2181
创建schema;
create schema bigdata;
注意:在phoenix中;schema名;表名;字段名等会自动转换为大写;若要小写;使用双引号;如;student;。
2;表的操作
显示所有表;
!table 或 !tables
创建表;
直接指定单个列作为RowKey;
CREATE TABLE IF NOT EXISTS student(
id VARCHAR primary key,
name VARCHAR,
addr VARCHAR);
指定多个列的联合作为RowKey;
CREATE TABLE IF NOT EXISTS us_population (
State CHAR(2) NOT NULL,
City VARCHAR NOT NULL,
Population BIGINT
CONSTRAINT my_pk PRIMARY KEY (state, city));
插入数据;
upsert into student values(;1001;,;zhangsan;,;beijing;);
查询记录;
select * from student;
select * from student where id=;1001;;
删除记录;
delete from student where id=;1001;;
删除表;
drop table student;
退出命令行;
!quit
3;表的映射
① 表的关系
默认情况下;直接在HBase中创建的表;通过Phoenix是查看不到的。如果要在Phoenix中操作直接在HBase中创建的表;则需要在Phoenix中进行表的映射。映射方式有两种;视图映射和表映射。
② 命令行中创建表test
HBase 中test的表结构如下;两个列族info1、info2。
Rowkey
info1
info2
id
name
address
启动HBase Shell;
[yyds;hadoop102 ~]$ /opt/module/hbase/bin/hbase shell
创建HBase表test;
hbase(main):001:0> create ;test;,;info1;,;info2;
③ 视图映射
Phoenix创建的视图是只读的;所以只能用来做查询;无法通过视图对源数据进行修改等操作。在phoenix中创建关联test表的视图
0: jdbc:phoenix:hadoop101,hadoop102,hadoop103> create view ;test;(id varchar primary key,;info1;.;name; varchar, ;info2;.;address; varchar);
删除视图;
0: jdbc:phoenix:hadoop101,hadoop102,hadoop103> drop view ;test;;
④ 表映射
使用Apache Phoenix创建对HBase的表映射;有两种方法;
;1;HBase中不存在表时;可以直接使用create table指令创建需要的表,系统将会自动在Phoenix和HBase中创建person_infomation的表;并会根据指令内的参数对表结构进行初始化。
;2;当HBase中已经存在表时;可以以类似创建视图的方式创建关联表;只需要将create view改为create table即可。
0: jdbc:phoenix:hadoop101,hadoop102,hadoop103> create table ;test;(id varchar primary key,;info1;.;name; varchar, ;info2;.;address; varchar) column_encoded_bytes=0;
4;表映射中数值类型的问题
Hbase中存储数值类型的值(如int,long等)会按照正常数字的补码进行存储. 而phoenix对数字的存储做了特殊的处理. phoenix 为了解决遇到正负数同时存在时;导致负数排到了正数的后面;负数高位为1;正数高位为0;字典序0 < 1;的问题。 phoenix在存储数字时会对高位进行转换.原来为1,转换为0; 原来为0;转换为1.
因此;如果hbase表中的数据的写是由phoenix写入的,不会出现问题;因为对数字的编解码都是phoenix来负责。如果hbase表中的数据不是由phoenix写入的;数字的编码由hbase负责. 而phoenix读数据时要对数字进行解码。 因为编解码方式不一致。导致数字出错.
在hbase中创建表;并插入数值类型的数据;
hbase(main):001:0> create ;person;,;info;
hbase(main):001:0> put ;person;,;1001;, ;info:salary;,Bytes.toBytes(123456)
注意: 如果要插入数字类型;需要通过Bytes.toBytes(123456)来实现。
在phoenix中创建映射表并查询数据;
create table ;person;(id varchar primary key,;info;.;salary; integer )
column_encoded_bytes=0;
select * from ;person;
会发现数字显示有问题。
解决办法;
在phoenix中创建表时使用无符号的数值类型. unsigned_long;
create table ;person;(id varchar primary key,;info;.;salary; unsigned_long )
column_encoded_bytes=0;
6. Phoenix JDBC操作
① Thin Client;
1;启动query server
[yyds;hadoop102 ~]$ queryserver.py start
2;创建项目并导入依赖
<dependencies>
<dependency>
<groupId>org.apache.phoenix</groupId>
<artifactId>phoenix-queryserver-client</artifactId>
<version>5.0.0-HBase-2.0</version>
</dependency>
</dependencies>
3;编写代码
package com.yyds;
import java.sql.*;
import org.apache.phoenix.queryserver.client.ThinClientUtil;
public class PhoenixTest {
public static void main(String[] args) throws SQLException {
String connectionUrl = ThinClientUtil.getConnectionUrl(;hadoop102;, 8765);
Connection connection = DriverManager.getConnection(connectionUrl);
PreparedStatement preparedStatement = connection.prepareStatement(;select * from student;);
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
System.out.println(resultSet.getString(1) ; ; ; ; resultSet.getString(2));
}
//关闭
connection.close();
}
}
② Thick Client
1;在pom中加入依赖
<dependencies>
<dependency>
<groupId>org.apache.phoenix</groupId>
<artifactId>phoenix-core</artifactId>
<version>5.0.0-HBase-2.0</version>
<exclusions>
<exclusion>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.1-b06</version>
</dependency>
</dependencies>
2;编写代码
package com.yyds.phoenix.thin;
import java.sql.*;
import java.util.Properties;
public class TestThick {
public static void main(String[] args) throws SQLException {
String url = ;jdbc:phoenix:hadoop102,hadoop103,hadoop104:2181;;
Properties props = new Properties();
props.put(;phoenix.schema.isNamespaceMappingEnabled;,;true;);
Connection connection = DriverManager.getConnection(url,props);
PreparedStatement ps = connection.prepareStatement(;select * from ;test;;);
ResultSet rs = ps.executeQuery();
while(rs.next()){
System.out.println(rs.getString(1);;:; ;rs.getString(2));
}
}
}
7. Phoenix二级索引
① 二级索引配置文件
1;添加如下配置到HBase的HRegionserver节点的hbase-site.xml
<!-- phoenix regionserver 配置参数-->
<property>
<name>hbase.regionserver.wal.codec</name>
<value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>
</property>
<property>
<name>hbase.region.server.rpc.scheduler.factory.class</name>
<value>org.apache.hadoop.hbase.ipc.PhoenixRpcSchedulerFactory</value>
<description>Factory to create the Phoenix RPC Scheduler that uses separate queues for index and metadata updates</description>
</property>
<property>
<name>hbase.rpc.controllerfactory.class</name>
<value>org.apache.hadoop.hbase.ipc.controller.ServerRpcControllerFactory</value>
<description>Factory to create the Phoenix RPC Scheduler that uses separate queues for index and metadata updates</description>
</property>
② 全局二级索引
Global Index是默认的索引格式;创建全局索引时;会在HBase中建立一张新表。也就是说索引数据和数据表是存放在不同的表中的;因此全局索引适用于多读少写的业务场景。
写数据的时候会消耗大量开销;因为索引表也要更新;而索引表是分布在不同的数据节点上的;跨节点的数据传输带来了较大的性能消耗。
在读数据的时候Phoenix会选择索引表来降低查询消耗的时间。
1;创建单个字段的全局索引
CREATE INDEX my_index ON my_table (my_col);
HBase全局索引一;
如果想查询的字段不是索引字段的话索引表不会被使用;也就是说不会带来查询速度的提升。
2;创建携带其他字段的全局索引
CREATE INDEX my_index ON my_table (v1) INCLUDE (v2);
HBase全局索引二;
③ 本地二级索引
Local Index适用于写操作频繁的场景。
索引数据和数据表的数据是存放在同一张表中;且是同一个Region;;避免了在写操作的时候往不同服务器的索引表中写索引带来的额外开销。
CREATE LOCAL INDEX my_index ON my_table (my_column);
1;HBase与Hive的对比
1. Hive
(1) 数据分析工具
Hive的本质其实就相当于将HDFS中已经存储的文件在Mysql中做了一个双射关系;以方便使用HQL去管理查询。
(2) 用于数据分析、清洗
Hive适用于离线的数据分析和清洗;延迟较高。
(3) 基于HDFS、MapReduce
Hive存储的数据依旧在DataNode上;编写的HQL语句终将是转换为MapReduce代码执行。
2. HBase
(1) 数据库
是一种面向列族存储的非关系型数据库。
(2) 用于存储结构化和非结构化的数据
适用于单表非关系型数据的存储;不适合做关联查询;类似JOIN等操作。
(3) 基于HDFS
数据持久化存储的体现形式是HFile;存放于DataNode中;被ResionServer以region的形式进行管理。
(4) 延迟较低;接入在线业务使用
面对大量的企业数据;HBase可以直线单表大量数据的存储;同时提供了高效的数据访问速度。
2;HBase与Hive集成使用
在hive-site.xml中添加zookeeper的属性;如下;
<property>
<name>hive.zookeeper.quorum</name>
<value>hadoop102,hadoop103,hadoop104</value>
</property>
<property>
<name>hive.zookeeper.client.port</name>
<value>2181</value>
</property>
案例一
目标;建立Hive表;关联HBase表;插入数据到Hive表的同时能够影响HBase表。
分步实现;
1. 在Hive中创建表同时关联HBase
CREATE TABLE hive_hbase_emp_table(
empno int,
ename string,
job string,
mgr int,
hiredate string,
sal double,
comm double,
deptno int)
STORED BY ;org.apache.hadoop.hive.hbase.HBaseStorageHandler;
WITH SERDEPROPERTIES (;hbase.columns.mapping; = ;:key,info:ename,info:job,info:mgr,info:hiredate,info:sal,info:comm,info:deptno;)
TBLPROPERTIES (;hbase.table.name; = ;hbase_emp_table;);
提示;完成之后;可以分别进入Hive和HBase查看;都生成了对应的表。
2. 在Hive中创建临时中间表;用于load文件中的数据
提示;不能将数据直接load进Hive所关联HBase的那张表中。
CREATE TABLE emp(
empno int,
ename string,
job string,
mgr int,
hiredate string,
sal double,
comm double,
deptno int)
row format delimited fields terminated by ; ;;
3. 向Hive中间表中load数据
hive> load data local inpath ;/home/admin/softwares/data/emp.txt; into table emp;
4. 通过insert命令将中间表中的数据导入到Hive关联Hbase的那张表中
hive> insert into table hive_hbase_emp_table select * from emp;
5. 查看Hive以及关联的HBase表中是否已经成功的同步插入了数据
Hive;
hive> select * from hive_hbase_emp_table;
HBase;
Hbase> scan ;hbase_emp_table;
案例二
目标;在HBase中已经存储了某一张表hbase_emp_table;然后在Hive中创建一个外部表来关联HBase中的hbase_emp_table这张表;使之可以借助Hive来分析HBase这张表中的数据。
注;该案例2紧跟案例1的脚步;所以完成此案例前;请先完成案例1。
分步实现;
1. 在Hive中创建外部表
CREATE EXTERNAL TABLE relevance_hbase_emp(
empno int,
ename string,
job string,
mgr int,
hiredate string,
sal double,
comm double,
deptno int)
STORED BY
;org.apache.hadoop.hive.hbase.HBaseStorageHandler;
WITH SERDEPROPERTIES (;hbase.columns.mapping; =
;:key,info:ename,info:job,info:mgr,info:hiredate,info:sal,info:comm,info:deptno;)
TBLPROPERTIES (;hbase.table.name; = ;hbase_emp_table;);
2. 关联后就可以使用Hive函数进行一些分析操作了
hive (default)> select * from relevance_hbase_emp;