非关系型数据库----NoSql
Nosql
一、简介
NoSQL,泛指非关系型的数据库。
优势:
- 易扩展
- 大数据量,高性能
- 灵活的数据模型
- 高可用
二、Redis
-
全称:REmote DIctionary Server(远程字典服务器)
-
特点:
- Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用
- Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存
- Redis支持数据的备份,即master-slave(主从)模式的数据备份
-
优势:
(1) 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
(2) 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
(3) 原子 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
(4) 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性
(5) 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不
用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
(6) 使用多路I/O复用模型,非阻塞IO;
-
Linux环境安装redis
1、安装编译环境:
yum install gcc-c++
启动服务:
./bin/redis-server ./redis.conf
启动客户端:
./bin/redis-cli -h 192.168.78.12 -p 6381
关闭服务:
kill -9 pid
查看主从关系:
info replication
数据结构:
当前的 Redis 支持 6 种数据类型,它们分别是字符串(String)、列表(List)、集合(set)、哈希结构 (hash)、有序集合(zset)和基数(HyperLogLog)。
常用命令:
命令学习网站:http://doc.redisfans.com/index.htm
赋值:set key
取值:get key
设置多个键值:mset key
获取多个键值:mget key
删除语法:del key
递增数字语法: INCR key
递减数值语法: DECR key
增加指定的整数语法: INCRBY key increment
减少指定的整数 语法:DECRBY key decrement
Java连接Redis:
- 代码连接:
pom.xml配置:
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.2</version>
</dependency>
</dependencies>
idea配置:检查这几处的jdk版本是否和本地安装的版本相同
代码:
public static void main(String[] args) {
Jedis jedis = new Jedis("192.168.78.12", 6379);
jedis.set("demo1","demo1111");
String demo1 = jedis.get("demo1");
System.out.println(demo1);
}
2.连接池连接:
public static void main(String[] args) {
// 1.获取连接池配置对象,设置配置项
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 1.1最大的连接数
jedisPoolConfig.setMaxIdle(10);
//总连接数
jedisPoolConfig.setMaxTotal(20);
JedisPool jedisPool = null;
Jedis jedis = null;
try {
//创建连接对象
jedisPool=new JedisPool(jedisPoolConfig,"192.168.78.12",6379);
//获得jedis数据
jedis=jedisPool.getResource();
//操作数据
jedis.set("stu1","zhangsan");
String stu1 = jedis.get("stu1");
System.out.println(stu1);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null){
jedis.close();
}
if (jedisPool != null){
jedisPool.close();
}
}
}
持久化方式:
1.RDB:
RDB 是以二进制文件,是在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化 的文件,达到数据恢复。
优点:使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
缺点:RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合 数据要求不严谨的时候 。
#dbfilename:持久化数据存储在本地的文件
dbfilename dump.rdb
#dir:持久化数据存储在本地的路径,如果是在/redis/redis-5.0.5/src下启动的redis-cli,则数据会存储在当前 src目录下
dir ./ ##snapshot触发的时机,save ##如下为900秒后,至少有一个变更操作,才会snapshot ##对于此值的设置,需要谨慎,评估系统的变更操作密集程度 ##可以通过“save”来关闭snapshot功能 #save时间,以下分别表示更改了1个key时间隔900s进行持久化存储;更改了10个key300s进行存储;更改10000个 key60s进行存储。
save 900 1
save 300 10
save 60 10000
##当snapshot时出现错误无法继续时,是否阻塞客户端“变更操作”,“错误”可能因为磁盘已满/磁盘故障/OS级别异常等
stop-writes-on-bgsave-error yes
##是否启用rdb文件压缩,默认为“yes”,压缩往往意味着“额外的cpu消耗”,同时也意味这较小的文件尺寸以及较短的网 络传输时间
rdbcompression yes
2.AOF:
Append-Only File,将“操作 + 数据”以格式化指令的方式追加到操作日志文件的尾部,在 append 操作返回后(已经
写入到文件或者将要写入),才进行实际的数据变更,“日志文件”保存了历史所有的操作过程;当 server 需要数据
恢复时,可以直接 replay 此日志文件,即可还原所有的操作过程。AOF 相对可靠,AOF 文件内容是字符串,非常
容易阅读和解析。
优点:可以保持更高的数据完整性,如果设置追加 fifile 的时间是 1s,如果 redis 发生故障,最多会丢失 1s 的数
据;且如果日志写入不完整支持 redis-check-aof 来进行日志修复;AOF 文件没被 rewrite 之前(文件过大时会对
命令进行合并重写),可以删除其中的某些命令(比如误操作的 flflushall)。
缺点:AOF 文件比 RDB 文件大,且恢复速度慢.
AOF 默认关闭,开启方法,修改配置文件 reds.conf:appendonly yes
##此选项为aof功能的开关,默认为“no”,可以通过“yes”来开启aof功能 ##只有在“yes”下,aof重写/文件同步等特性才会生效
appendonly yes
##指定aof文件名称
appendfilename appendonly.aof
##指定aof操作中文件同步策略,有三个合法值:
always everysec no,默认为everysec appendfsync everysec
##在aof-rewrite期间,appendfsync是否暂缓文件同步,"no"表示“不暂缓”,“yes”表示“暂缓”,默认为“no”
no-appendfsync-on-rewrite no
##aof文件rewrite触发的最小文件尺寸(mb,gb),只有大于此aof文件大于此尺寸是才会触发rewrite,默认“64mb”,建 议“512mb”
auto-aof-rewrite-min-size 64mb
##相对于“上一次”rewrite,本次rewrite触发时aof文件应该增长的百分比。 ##每一次rewrite之后,redis都会记录下此时“新aof”文件的大小(例如A),那么当aof文件增长到A*(1 + p)之后 ##触发下一次rewrite,每一次aof记录的添加,都会检测当前aof文件的尺寸。
auto-aof-rewrite-percentage 100
主从复制:
- 持久化保证了即使redis服务重启也不会丢失数据,但是当redis服务器的硬盘损坏了可能会导致数据丢失,通 过redis的主从复制机制就可以避免这种单点故障(单台服务器的故障)。
- 主redis中的数据和从上的数据保持实时同步,当主redis写入数据时通过主从复制机制复制到两个从服务上。
- 主从复制不会阻塞master,在同步数据时,master 可以继续处理client 请求.
- 主机master配置:无需配置
- 复制出一个从机,注意使用root用户:cp 命令
- 第二步:修改从机的redis.conf 语法: replicaof // replicaof 主机ip 主机端口号
- 第三步:修改从机的port地址为6380 port 6380
- 第四步:清除从机中的持久化文件 rm -rf appendonly.aof dump.rdb
复制的过程原理
当从库和主库建立MS(master slaver)关系后,会向主数据库发送SYNC命令;
主库接收到SYNC命令后会开始在后台保存快照(RDB持久化过程),并将期间接收到的写命令缓存起来;
快照完成后,主Redis会将快照文件和所有缓存的写命令发送给从Redis;
从Redis接收到后,会载入快照文件并且执行收到的缓存命令;
主Redis每当接收到写命令时就会将命令发送从Redis,保证数据的一致;【内部完成,所以不支持客户端在从
机人为写数据。】
哨兵模式:
一主两从。
作用:
-
监控主数据库和从数据库是否运行正常;
-
主数据出现故障后自动将从数据库转化为主数据库;
配置步骤:
创建sentinel.conf文件,写入配置信息:
配置信息:
sentinel monitor mastername 内网IP(127.0.0.1) 6379 1
mastername 监控主数据的名称,自定义
127.0.0.1:监控主数据库的IP;
6379:端口
1:最低通过票数
把日志写入文件中:
./redis-sentinel ./sentinel.conf >sent.log &
启动:
./redis-server sentinel.conf --sentinel
搭建redis集群:
搭建成功:
连接redis集群:
public static void main(String[] args) {
//创建一个集合,保存集群信息
Set set = new HashSet<HostAndPort>();
set.add(new HostAndPort("192.168.78.12",7001));
set.add(new HostAndPort("192.168.78.12",7002));
set.add(new HostAndPort("192.168.78.12",7003));
set.add(new HostAndPort("192.168.78.12",7004));
set.add(new HostAndPort("192.168.78.12",7005));
set.add(new HostAndPort("192.168.78.12",7006));
//创建集群操作对象
JedisCluster jedisCluster=new JedisCluster(set);
//操作数据
jedisCluster.set("stu12","5004");
String stu12 = jedisCluster.get("stu12");
System.out.println(stu12);
try {
jedisCluster.close();
} catch (IOException e) {
e.printStackTrace();
}
}
缓存雪崩:
缓存雪崩通俗简单的理解就是:由于原有缓存失效(或者数据未加载到缓存中),新缓存未到期间(缓存正常从
Redis中获取)所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,
严重的会造成数据库宕机,造成系统的崩溃。
解决方案:
1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据 和写缓存,其他线程等待。虽然能够在一定的程度上缓解了数据库的压力但是与此同时又降低了系统的吞吐量。
2: 分析用户的行为,不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
// 1.先查询redis
String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace() [1].getMethodName() + "-id:" + id;
String userJson = redisService.getString(key);
if (!StringUtils.isEmpty(userJson)) {
Users users = JSONObject.parseObject(userJson, Users.class); return users;
}
Users user = null;
try {
lock.lock();
// 查询db
user = userMapper.getUser(id);
redisService.setSet(key, JSONObject.toJSONString(user));
} catch (Exception e) {
} finally {
lock.unlock();
// 释放锁
}
eturn user;
}
缓存穿透:
缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找
不到,每次都要去数据库再查询一遍,然后返回空。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中
率问题。
解决方案:
1.如果查询数据库也为空,直接设置一个默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问
数据库,这种办法最简单粗暴。
2.把空结果,也给缓存起来,这样下次同样的请求就可以直接返回空了,既可以避免当查询的值为空时引起的缓存
穿透。同时也可以单独设置个缓存区域存储空值,对要查询的key进行预先校验,然后再放行给后面的正常缓存处
理逻辑。
public String getByUsers2(Long id) {
// 1.先查询redis
String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace() [1].getMethodName()+ "-id:" + id;
String userName = redisService.getString(key);
if (!StringUtils.isEmpty(userName)) {
return userName;
}
System.out.println("######开始发送数据库DB请求########");
Users user = userMapper.getUser(id);
String value = null;
if (user == null) {
// 标识为null
value = "";
} else {
value = user.getName();
}
redisService.setString(key, value);
return value;
}
缓存击穿:
对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。
这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是
很多key。
热点key:
某个key访问非常频繁,当key失效的时候有大量线程来构建缓存,导致负载增加,系统崩溃。
解决办法:
①使用锁,单机用synchronized,lock等,分布式用分布式锁。
②缓存过期时间不设置,而是设置在key对应的value里。如果检测到存的时间超过过期时间则异步更新缓存。
分布式锁:
三、MongoDB
MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。Mongo最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。
说明:BSON是一种计算机数据交换格式,主要被用作MongoDB数据库中的数据存储和网络传输格式。它是一种二进制表示形式,能用来表示简单数据结构、关联数组(MongoDB中称为“对象”或“文档”)以及MongoDB中的各种数据类型。BSON之名缘于JSON,含义为Binary JSON(二进制JSON)。
特点:
(1) 面向集合存储,易存储对象类型的数据
(2) 支持动态查询
(3) 支持完全索引,包含内部对象
(4) 支持复制和故障恢复
(5) 支持多种开发语言
(6) 使用高效的二进制数据存储,包括大型对象(如视频等)
场景:
1)网站实时数据处理。它非常适合实时的插入、更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性。
2)缓存。由于性能很高,它适合作为信息基础设施的缓存层。在系统重启之后,由它搭建的持久化缓存层可以避免下层的数据源过载。
3)高伸缩性的场景。非常适合由数十或数百台服务器组成的数据库,它的路线图中已经包含对MapReduce引擎的内置支持。
Linux安装MongoDB:
//解压
tar -zxvf mongodb-linux-x86_64-4.0.19-rc0.tgz
//移动到另一个目录并切换目录
mv mongodb-linux-x86_64-4.0.19-rc0
cd /home/admin/myapps/mongodb
//系统profile配置
vim /etc/profile
编辑内容: export mongodb_home=/home/admin/myapps/mongodb
export PATH=$PATH:$mongodb_home/bin
//重启系统配置
source /etc/profile
//创建数据库目录
mkdir mongodbdata
//创建日志文件和配置文件
mkdir logs
cd logs
ouch mongodb.log
cd ..
cd mongodb
cd bin
vim mongodb.conf //配置文件存放在bin目录下
配置文件内容:
dbpath = /home/admin/myapps/mongodbdata #数据文件存放目录
logpath = /home/admin/myapps/logs/mongodb.log #日志文件存放目录
port = 27017 #端口
fork = true #以守护程序的方式启用,即在后台运行
//:启动mongodb服务端
./mongod -f mongodb.conf
//运行客户端
./mongo
常用指令:
登录: mongo ip地址
查看数据库(至少存在一条数据才会显示出来): show dbs
切换数据库:use 数据库名
查看当前数据库:db
查看所有的数据集:show collections
删除当前数据库:db.dropDatabase()
创建集合(相当于创建表):db.createCollection("user1")
删除集合:db.collectionName.drop()
集合重命名:db.oldCollectionName.renameCollection("newName")
新增数据:db.collectionName.insert({"key":value,"key":value}) 或者
db.collectionName.save({"key":value,"key":value})
查看所有数据:db.collectionName.find()
条件查询:
db.collectionName.find({"age":26}) //查询等值关系
db.collectionName.find({age : {$gt : 100}}) // 大于100
db.collectionName.find({age : {$gte : 100}}) //大于等于100
db.collectionName.find({age : {$lt : 150}}) //小于150
db.collectionName.find({age : {$lte : 150}}) //小于等于150
db.collectionName.find({age : {$lt :200, $gt : 100}}) //大于100,小于200
清空集合数据:db.collectionName.remove({})
查询一条数据:db.collectionName.findOne();
查询指定列:db.collectionName.find({},{name:1,age:1,sex_orientation:true})
查询指定字段的数据并去重:db.collectionName.distinct('sex')
对结果集排序:
db.collectionName.find().sort({salary:1}) //升序
db.collectionName.find().sort({salary:-1}) //降序
查询限定条数:db.collectionName.find().limit(number)
Java连接MongoDB:
1、添加依赖:添加后记得重新导入
<dependencies>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.2.2</version>
</dependency>
</dependencies>
代码:
public static void main(String[] args) {
// 1.连接到 mongodb 服务
MongoClient mongoClient = new MongoClient("192.168.78.12", 27017);
// 2.连接到数据库
MongoDatabase mongoDatabase = mongoClient.getDatabase("test1");
// System.out.println(mongoDatabase);
//创建集合
// mongoDatabase.createCollection("users");
//获得集合
MongoCollection<Document> test1 = mongoDatabase.getCollection("test2");
//新增
// Document document = new Document("name","haa");
// document.append("age",15);
// test1.insertOne(document);
// System.out.println("新增成功");
//
//修改 updateone 修改一行 Updatemany 修改多行
// test1.updateOne(Filters.eq("name","haa"),new Document("$set",new Document("age",52)));
//删除
test1.deleteOne(new Document("name","haa"));
//查询
FindIterable<Document> documents = test1.find();
MongoCursor<Document> iterator = documents.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
MongoDB索引
创建索引:
db.collection.createIndex(keys, options)
索引分类:
1、默认索引 db.collectionName.getIndexes()
2、单列索引 db.collectionName.createIndex({“title”:1})
3、组合索引 db.collectionName.createIndex({“userid”:1,“username”:-1})
4、唯一索引 db.collectionName.CreateIndex({“UserId”:1}, { unique: true });
5、TTL索引 db.log_events.createIndex( { “createdAt”: 1 }, { expireAfterSeconds: 3600 })
6、删除索引 db.collectionName.dropIndexes()
备份与恢复:
备份:
mongodump -h dbhost -d dbname -o dbdirectory
-h:MongDB所在服务器地址,例如:127.0.0.1,当然也可以指定端口号:127.0.0.1:27017
-d:需要备份的数据库实例,例如:test
-o: 备份的数据存放位置
恢复:
mongorestore -h <hostname><:port> -d dbname <path>
--host <:port>, -h <:port>:MongoDB所在服务器地址,默认为: localhost:27017
--db , -d :需要恢复的数据库实例,例如:test,当然这个名称也可以和备份时候的不一样,比如test2
--drop:恢复的时候,先删除当前数据,然后恢复备份的数据。就是说,恢复后,备份后添加修改的数据都
会被删除,慎用哦!
:mongorestore 最后的一个参数,设置备份数据所在位置,例如:c:\data\dump\test。
你不能同时指定 和 --dir 选项,--dir也可以设置备份目录。
--dir:指定备份的目录
注意:
如果报错Error:(4, 26) java: 程序包com.mongodb.client不存在
在下方terminal中运行以下命令:
mvn idea:idea