目录
1 NoSQL 数据库
1.1 什么是NoSQL
NoSQL最常见的解释是“non-relational”, 很多人也说它是“Not Only SQL” 。NoSQL仅仅是一个概念,泛指非关系型的数据库。
区别于关系数据库,它们不保证关系数据的ACID特性。
1.2 NoSQL的特点
在讨论NoSQL的特点之前,我们先看看RDBMS的特点。
RDBMS:关系型数据库管理系统
- 工具:MySQL、Oracle、SQL Server……
- 应用:业务性数据存储系统:事务和稳定性
- 特点:体现数据之间的关系,支持事务,保证业务完整性和稳定性,小数据量的性能也
比较好 - 开发:SQL
业务架构中的问题:以网站后台存储为例,当并发量很大,所有高并发全部直接请求MySQL,容易导致MySQL奔溃。
需求:能实现高并发的数据库,接受高并发请求。
NoSQL:Not Only SQL,非关系型数据库
- 工具:Redis、HBASE、MongoDB……
- 应用:一般用于高并发高性能场景下的数据缓存或者数据库存储
- 特点:读写速度特别快,并发量非常高,相对而言不如RDBMS稳定,对事务性的支持不太友好
- 开发:每种NoSQL都有自己的命令语法
- 解决上面RDBMS的问题:使用高并发缓存实现读写分离
- 读请求:读请求不读取MySQL,读取Redis
- 写请求:写请求直接写入MySQL
- RDBMS的特点
- 场景:业务数据库
- 特点:稳定性高、事务支持比较完善、小数据量性能好
- NoSQL的特点
- 应用场景
- 高并发的读写
- 海量数据读写
- 高可扩展性
- 速度快
- 不适用场景
- 需要事务支持
- 基于sql的结构化查询存储,处理复杂的关系,需要即席查询(用户自定义查询条件的查询)
- 稳定性和安全性要求高
1.3 NoSQL 数据库
- memcache
- 很早出现的NoSql数据库
- 数据都在内存中,一般不持久化
- 支持简单的key-value模式
- 一般是作为缓存数据库辅助持久化的数据库
- redis
- 几乎覆盖了Memcache的绝大部分功能
- 数据都在内存中,支持持久化,主要用作备份恢复
- 除了支持简单的key-value模式,还支持多种数据结构的存储,比如 list、set、hash、zset等。
- 一般是作为缓存数据库辅助持久化的数据库
- 现在市面上用得非常多的一款内存数据库
- mongoDB
- 高性能、开源、模式自由(schema free)的文档型数据库
- 数据都在内存中, 如果内存不足,把不常用的数据保存到硬盘
- 虽然是key-value模式,但是对value(尤其是json)提供了丰富的查询功能
- 支持二进制数据及大型对象
- 可以根据数据的特点替代RDBMS ,成为独立的数据库。或者配合RDBMS,存储特定的数据。
- HBase
HBase是Hadoop项目中的数据库。它用于需要对大量的数据进行随机、实时读写操作的场景中。HBase的目标就是处理数据量非常庞大的表,可以用普通的计算机处理超过10亿行数据,还可处理有数百万列元素的数据表。
2 Redis 介绍
2.1 Redis 基本介绍
Redis是当前比较热门的NoSQL系统之一。它是一个开源的、使用ANSI C语言编写的 key-value
存储系统(区别于MySQL的二维表格形式存储)。
和Memcache类似,但很大程度补偿了Memcache的不足,Redis数据都是缓存在计算机内存中,不同的是,Memcache只能将数据缓存到内存中,无法自动定期写入硬盘,这就表示,一断电或重启,内存清空,数据丢失。
2.2 Redis的应用场景
- 取最新N个数据的操作
比如典型获取网站最新文章,可以将最新的5000条评论ID放在Redis的List集合中,并将超出集合部分从数据库获取。
- 排行榜应用,取TOP N操作
这个需求与上面需求的不同之处在于,前面操作以时间为权重,这个是以某个条件为权重,比如按顶的次数排序,可以使用Redis的sorted set,将要排序的值设置成sorted set的score,将具体的数据设置成相应的value,每次只需要执行一条ZADD命令即可。
- 需要精准设定过期时间的应用
比如可以把上面说到的sorted set的score值设置成过期时间的时间戳,那么就可以简单地通过过期时间排序,定时清除过期数据了,不仅是清除Redis中的过期数据,你完全可以把Redis里这个过期时间当成是对数据库中数据的索引,用Redis来找出哪些数据需要过期删除,然后再精准地从数据库中删除相应的记录。
- 计数器应用
Redis的命令都是原子性的,你可以轻松地利用INCR,DECR命令来构建计数器系统。
- Uniq操作,获取某段时间所有数据排重值
这个使用Redis的set数据结构最合适了,只需要不断地将数据往set中扔就行了,set意为集合,所以会自动排重。
- 实时系统,反垃圾系统
通过上面说到的set功能,你可以知道一个终端用户是否进行了某个操作,可以找到其操作的集合并进行分析统计对比等。没有做不到,只有想不到。
- 缓存
将数据直接存放到内存中,性能优于Memcached,数据结构更多样化。
2.3 Redis的特点
- 高效性
Redis读取的速度是110000次/s,写的速度是81000次/s
- 原子性
Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
- 支持多种数据结构
- string(字符串)
- list(列表)
- hash(哈希)
- set(集合)
- zset(有序集合)
-
稳定性:持久化,主从复制(集群)
-
其他特性:支持过期时间,支持事务,消息订阅。
3 Redis的Linux版单机部署
详见:Redis 的安装与部署
4 Redis 数据类型
整个Reids中所有数据以KV结构形式存在。
- K:作为唯一标识符,唯一标识一条数据,固定为String类型,写入时指定KV,读取时,根据K读取V
- V:真正存储的数据,可以有多种类型
- String、Hash、List、Set、Zset、BitMap、HypeLogLog
理解Redis:类似于Java中的一个Map集合,可以存储多个KV,根据K获取V。
redis当中一共支持五种数据类型,分别是:
- string字符串
- KV:【String,String】,类似于Java中Map集合的一条KV
- list列表
- 【String,Map集合】:Map集合的嵌套,Map集合中的元素是无序的
- set集合
- 【String,List】:有序且可重复
- hash表
- 【String,Set】:无序且不重复
- zset有序集合
- KV:【String,TreeMap集合】:Value也类似于Map集合,有序的Map集合
- 类似于List和Set集合特点的合并:有序且不可重复
通过这五种不同的数据类型,可以实现各种不同的功能,也可以应用在各种不同的场景。
4.1 通用操作
Redis 命令行返回1代表True,返回0代表False。
keys 通配符
:列举当前数据库中所有Key
del K
:删除某个KV (del 后可以跟多个key,空格分开)
exists K
:判断某个Key是否存在
type K
:判断这个K对应的V的类型的
expire K 过期时间
:设置某个K的过期时间,一旦到达过期时间,这个K会被自动删除。
ttl K
:查看某个K剩余的存活时间
select N
:切换数据库(N表数据库索引)
- Redis默认由16个数据:db0 ~ db15,个数可以通过配置文件修改,名称不能改
- Redis是一层数据存储结构:所有KV直接存储在数据库中
- 默认进入db0
move K N
:将某个Key移动到某个数据库中
-
flushdb
:清空当前数据库的所有K -
flushall
:清空所有数据库的所有K
4.2 对字符串string的操作
-
set K V
:给 String 类型的 Value 的进行赋值或者更新 -
get K
:读取 String 类型的 Value 的值 -
mset K1 V1 K2 V2
:用于批量写多个 String 类型的KV -
mget K1 K2 K3
:用于批量读取 String 类型的 Value -
setnx K V
:只能用于新增数据,当K不存在时可以进行新增应用:构建抢占锁,搭配expire来使用
-
incr K
:用于对数值类型的字符串进行递增,递增1,一般用于做计数器
-
incrby K N
:指定对数值类型的字符串增长固定的步长 -
decr K
:对数值类型的数据进行递减,递减1 -
decrby K N
:按照指定步长进行递减 -
incrbyfloat K N
:基于浮点数递增 -
strlen K
:统计字符串的长度 -
getrange K start end
:用于截取字符串(start end 索引从0开始,前闭后闭)
4.3 对hash列表的操作
无序,不可重复
hset K k v
:用于为某个K添加一个属性
hget K k
:用于获取某个K的某个属性的值
hmset K k1 v1 k2 v2 …
:批量的为某个K赋予新的属性
-
hmget K k1 k2 k3…
:批量的获取某个K的多个属性的值 -
hgetall K
:获取所有属性的值 -
hdel K k1 k2 …
:删除某个属性 -
hlen K
:统计 K 对应的 Value 总的属性的个数 -
hexists K k
:判断这个K的V中是否包含这个属性 -
hvals K
:获取所有属性的value
4.4 对list列表的操作
有序,可重复
lpush K e1 e2 e3…
:将每个元素放到集合的左边,左序放入
rpush K e1 e2 e3…
:将每个元素放到集合的右边,右序放入
lrange K start end
:通过下标的范围来获取元素的数据
注意:从左往右的下标从0开始,从右往左的下标从-1开始,一定是从小的到大的下标。
lrange K 0 -1
:所有元素。
-
llen K:统计集合的长度
-
lpop K:删除左边的一个元素
-
rpop K:删除右边的一个元素
4.5 对set集合的操作
无序,不可重复
-
sadd K e1 e2 e3 e4 e5…
:用于添加元素到Set集合中 -
smembers K
:用于查看Set集合的所有成员 -
sismember K e1
:判断是否包含这个成员 -
srem K e
:删除其中某个元素 -
scard K
:统计集合长度 -
sunion K1 K2
:取两个集合的并集 -
sinter K1 K2
:取两个集合的交集
4.6 对ZSet的操作
有序,不可重复
zadd K score1 k1 score2 k2…
:用于添加元素到Zset集合中
zrange K start end [withscores]
:范围查询
redis中的数值尽量用整型
zrevrange K start end [withscores]
:倒序查询
-
zrem K k1
:移除一个元素 -
zcard K
:统计集合长度 -
zscore K k
:获取评分
4.7 对位图BitMaps的操作
功能:通过一个String对象的存储空间,来构建位图,用每一位0和1来表示状态。
- Redis中一个String最大支持512M = 2^32,1字节 = 8位
- 使用时,可以指定每一位对应的值,要么为0,要么为1,默认全部为0
- 用下标来标记每一位,第一个位的下标为0
举例:统计UV
- 一个位图中包含很多位,可以用每一个位表示一个用户id
- 读取数据,发现一个用户id,就将这个用户id对应的那一位改为1
- 统计整个位图中所有1的个数,就得到了UV
setbit bit1 位置 0/1
:修改某一位的值
getbit K 位置
:查看某一位的值
注意:start和end表示的是字节,1 字节 = 8 位
bitcount K [start end]
:用于统计位图中所有1的个数
bitop and/or/xor/not bitrs bit1 bit2
:用于位图的运算,and/or/not/xor
4.8 对HyperLogLog结构的操作
功能:类似于Set集合,用于实现数据的去重
- 区别:底层实现原理不一样
- 应用:适合于数据量比较庞大的情况下的使用,存在一定的误差率
-
pfadd K e1 e2 e3……
:用于添加元素 -
pfcount K
:用于统计个数 -
pfmerge pfrs pf1 pf2…
:用于实现集合合并
5 Redis Java API 操作
Redis的使用方式
- 命令操作Redis,一般用于测试开发阶段
- 分布式计算或者Java程序读写Redis,一般用于实际生产开发
-
Spark/Flink读写Redis
-
所有数据库使用Java操作方式整体是类似的
//todo:1-构建客户端连接对象 Connection conn = DriverManager.getConnect(url,username,password) //todo:2-执行操作:所有操作都在客户端连接对象中:方法 prep.execute(SQL) //todo:3-释放连接 conn.close
-
Java语言使用 Jedis依赖 来操作Redis数据库。
实现Jedis的客户端连接以及相关操作
public class RedisClientTest {
//todo:1-构建客户端连接对象
//单节点连接对象:Jedis
Jedis jedis = null;
@Before
public void getConnection() {
//方式一:直接构建
//jedis = new Jedis("node01",6379);
//方式二:基于连接池来获取连接
//构建连接池配置对象
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(10);//最大连接数
config.setMaxIdle(5);//连接池中最多可以有多少个空闲的 Jedis 连接
config.setMinIdle(3);
//构建连接池对象
JedisPool jedisPool = new JedisPool(config, "node01", 6379);
//从池子中获取连接对象
jedis = jedisPool.getResource();
}
//todo:2-执行操作
@Test
public void testString() {
//set/get/incr/exists/expire/setex/ttl
// jedis.set("s1","hadoop");
// System.out.println(jedis.get("s1"));
// jedis.set("s2","5");
// jedis.incr("s2");
// System.out.println(jedis.get("s2"));
// jedis.expire("s2",15);
// while (true){
// System.out.println(jedis.ttl("s2"));
// }
// System.out.println(jedis.exists("s2"));
// System.out.println(jedis.exists("s1"));
//setex = set + expire
jedis.setex("s2", 10, "hadoop");
}
@Test
public void testHash() {
//hset/hmset/hget/hgetall/hdel/hlen/hexists
// jedis.hset("m1","name","zhangsan");
// System.out.println(jedis.hget("m1","name"));
// Map<String,String> maps = new HashMap<>();
// maps.put("age","18");
// maps.put("phone","110");
// jedis.hmset("m1",maps);
// List<String> hmget = jedis.hmget("m1", "name", "age");
// System.out.println(hmget);
// System.out.println("=");
// Map<String, String> m1 = jedis.hgetAll("m1");
// for(Map.Entry map : m1.entrySet()){
// System.out.println(map.getKey()+"\t"+map.getValue());
// }
// System.out.println("=");
System.out.println(jedis.hlen("m1"));
jedis.hdel("m1", "name");
System.out.println(jedis.hlen("m1"));
System.out.println(jedis.hexists("m1", "name"));
System.out.println(jedis.hexists("m1", "age"));
}
@Test
public void testList() {
//lpush/rpush/lrange/llen/lpop/rpop
jedis.lpush("list1", "1", "2", "3");
System.out.println(jedis.lrange("list1", 0, -1));
jedis.rpush("list1", "4", "5", "6");
System.out.println(jedis.lrange("list1", 0, -1));
System.out.println(jedis.llen("list1"));
jedis.lpop("list1");
jedis.rpop("list1");
System.out.println(jedis.lrange("list1", 0, -1));
}
@Test
public void testSet() {
//sadd/smembers/sismember/scard/srem
jedis.sadd("set1", "1", "2", "3", "1", "2", "3", "4", "5", "6");
System.out.println("长度:" + jedis.scard("set1"));
System.out.println("内容:" + jedis.smembers("set1"));
System.out.println(jedis.sismember("set1", "1"));
System.out.println(jedis.sismember("set1", "7"));
jedis.srem("set1", "2");
System.out.println("内容:" + jedis.smembers("set1"));
}
@Test
public void testZset() {
//zadd/zrange/zrevrange/zcard/zrem
jedis.zadd("zset1", 20.9, "yuwen");
jedis.zadd("zset1", 10.5, "yinyu");
jedis.zadd("zset1", 70.9, "shuxue");
jedis.zadd("zset1", 99.9, "shengwu");
Set<String> zset1 = jedis.zrange("zset1", 0, -1);
System.out.println(zset1);
System.out.println(jedis.zrevrange("zset1", 0, -1));
System.out.println(jedis.zcard("zset1"));
jedis.zrem("zset1", "yuwen");
System.out.println(jedis.zrangeWithScores("zset1", 0, -1));
}
//todo:3-释放连接
@After
//释放连接
public void closeConnection() {
jedis.close();
}
}