六万+字带你了解数据库---mysql--JDBC-Redis--MongoDB

在这里插入图片描述
路漫漫其修远兮,读者朋友们加油

MYSQL

1 常见的数据库

数据库分两大类,一类是 关系型数据库。另一类叫做 非关系型数据库。
关系型数据库: MySQL,Oracle,PostgreSQL,SQLserver。。。。
非关系型数据库:Redis内存数据库,MongoDB文档数据库。。。

2 常用的数据类型

在这里插入图片描述
在这里插入图片描述
decimal(5, 2) 表示数值总共5位, 小数占2位
tinyint 1字节(8位) 0-255。-128,127
int 4字节。 -21亿,21亿。0-42亿
float.
MySQL中没有专门存储货币的数据类型,一般情况下使用DECIMAL(8, 2)
在这里插入图片描述
在这里插入图片描述
二进制数据类型可存储任何数据(甚至包括二进制信息),如图像、多媒体、字处理文档等

3 表字段的约束

unsigned 无符号(给数值类型使用,表示为正数,不写可以表示正负数都可以)
字段类型后面加括号限制宽度
char(5). varchar(7) 在字符类型后面加限制 表示 字符串的长度
int(4) 没有意义,默认无符号的int为int(11),有符号的int(10)
int(4) unsigned zerofill只有当给int类型设置有前导零时,设置int的宽度才有意义。
not null 不能为空,在操作数据库时如果输入该字段的数据为NULL ,就会报错
default 设置默认值
primary key 主键不能为空,且唯一.一般和自动递增一起配合使用。
auto_increment 定义列为自增属性,一般用于主键,数值会自动加1
unique 唯一索引(数据不能重复:用户名)可以增加查询速度,但是会降低插入和更新速度

4 MYSQL的运算符

算术运算符: +、 -、 *、 /、 %
比较运算符: =、 >、 <、 >=、 <=、!=
数据库特有的比较: in、not in、is null、is not null、like、between、and
逻辑运算符: and、or、not
like: 支持特殊符号%和_ ;
其中%表示任意数量的任意字符,_表示任意一位字符

5 数据库的常用操作

1 创建数据库

# 1. 创建库
create database if not exists tlxy default charset=utf8;
-- 1. 数据库 tlxy 如果不存在则创建数据库,存在则不创建
-- 2. 创建 tlxy 数据库,并设置字符集为utf8
-- 3. 无特殊情况都要求字符集为utf8或者utf8mb4的字符编码

2查看所有库

# 1. 查看所有库
show databases;

3打开/进入库

# use 库名
use tlxy

4删除库

# 删除库,那么库中的所有数据都将在磁盘中删除。
drop database 库名

5创建表

# 以下创建一个 users 的表
create table users(
-- 创建ID字段,为正整数,不允许为空 主键,自动递增
id int unsigned not null primary key auto_increment,
-- 创建 存储 名字的字段,为字符串类型,最大长度 5个字符,不允许为空
username varchar(5) not null,
-- 创建存储 密码 的字段,固定长度 32位字符, 不允许为空
password char(32) not null,
-- 创建 年龄 字段,不允许为空,默认值为 20
age tinyint not null default 20
)engine=innodb default charset=utf8;
# 查看表结构
desc users;
#查看建表语句
show create table users;

创建表的基本原则:
表明和字段名 尽可能的符合命名规范,并且最好能够‘见名之意’
表中数据必须有唯一标示,即主键定义。无特殊情况,主键都为数字并自增即可
表中字段所对应的类型设置合理,并限制合理长度
表引擎推荐使用innodb,并无特殊情况都要求为utf8或者utf8mb4的字符编码

6修改表

语法格式:alter table 表名 action (更改的选项)

6.1 添加

# 语法:alter table 表名 add 添加的字段信息
-- 在 users 表中 追加 一个 num 字段
alter table users add num int not null;
-- 在指定字段后面追加字段 在 users 表中 age字段后面 添加一个 email 字段
alter table users add email varchar(50) after age;
-- 在指定字段后面追加字段,在 users 表中 age字段后面 添加一个 phone
alter table users add phone char(11) not null after age;
-- 在表的最前面添加一个字段
alter table users add aa int first;

6.2 删除

# 删除字段 alter table 表名 drop 被删除的字段名
alter table users drop aa;

6.3 修改

语法格式: alter table 表名 change|modify 被修改的字段信息
change: 可以修改字段名,
modify: 不能修改字段名。
# 修改表中的 num 字段 类型,使用 modify 不修改表名
alter table users modify num tinyint not null default 12;
# 修改表中的 num 字段 为 int并且字段名为 nn
alter table users change num mm int;
# 注意:一般情况下,无特殊要求,不要轻易修改表结构

6.4修改表名

# 语法:alter table 原表名 rename as 新表名

6.5更改表中自增的值

# 在常规情况下,auto_increment 默认从1开始继续递增
alter table users auto_increment = 1000;

6.6 修改表引擎

# 推荐在定义表时,表引擎为 innodb。
# 通过查看建表语句获取当前的表引擎
mysql> show create table users\G;
*************************** 1. row ***************************
Table: users
Create Table: CREATE TABLE `users` (
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
# 直接查看当前表状态信息
mysql> show table status from tlxy where name = 'users'\G;
*************************** 1. row ***************************
Name: users
Engine: InnoDB
# 修改表引擎语句
alter table users engine = 'myisam';

7删除表

drop table 表名

6 数据库的DML操作表

6.1 添加数据

--标准添加(指定所有字段,给定所有的值)
mysql> insert into stu(id,name,age,sex,classid) values(1,'zhangsan',20,'m','lamp138');
Query OK, 1 row affected (0.13 sec)
mysql>
--指定部分字段添加值
mysql> insert into stu(name,classid) value('lisi','lamp138');
Query OK, 1 row affected (0.11 sec)
-- 不指定字段添加值
mysql> insert into stu value(null,'wangwu',21,'w','lamp138');
Query OK, 1 row affected (0.22 sec)
-- 批量添加值
mysql> insert into stu values
-> (null,'zhaoliu',25,'w','lamp94'),
-> (null,'uu01',26,'m','lamp94'),
-> (null,'uu02',28,'w','lamp92'),
-> (null,'qq02',24,'m','lamp92'),
-> (null,'uu03',32,'m','lamp138'),
-> (null,'qq03',23,'w','lamp94'),
-> (null,'aa',19,'m','lamp138');
Query OK, 7 rows affected (0.27 sec)
Records: 7 Duplicates: 0 Warnings: 0

6.2 修改数据

-- 将id为11的age改为35,sex改为m值
mysql> update stu set age=35,sex='m' where id=11;
Query OK, 1 row affected (0.16 sec)
Rows matched: 1 Changed: 1 Warnings: 0
-- 将id值为1214的数据值sex改为m,classid改为lamp92
mysql> update stu set sex='m',classid='lamp92' where id=12 or id=14 --等价于下面
mysql> update stu set sex='m',classid='lamp92' where id in(12,14);
Query OK, 2 rows affected (0.09 sec)
Rows matched: 2 Changed: 2 Warnings: 0

6.3 删除数据

-- 删除stu表中id值为100的数据
mysql> delete from stu where id=100;
Query OK, 0 rows affected (0.00 sec)
-- 删除stu表中id值为2030的数据
mysql> delete from stu where id>=20 and id<=30;
Query OK, 0 rows affected (0.00 sec)
-- 删除stu表中id值为2030的数据(等级于上面写法)
mysql> delete from stu where id between 20 and 30;
Query OK, 0 rows affected (0.00 sec)
-- 删除stu表中id值大于200的数据
mysql> delete from stu where id>200;
Query OK, 0 rows affected (0.00 sec

7 DQL操作MySQL

7.1 and 和or 区分

假设要求 查询 users 表中 年龄为22或者25 的女生信息
select * from users where age=22 or age = 25 and sex = ‘女’;
思考上面的语句能否返回符合条件的数据? 答案肯定是否定的
问题出在 sql 计算的顺序上,sql会优先处理and条件,所以上面的sql语句就变成了-- 查询变成了为年龄22的不管性别,或者年龄为 25的女生
正确的查询结果:

select * from users where (age=22 or age = 25) and sex = '女';

7.2 Like

可以使用like语句进行某个字段的模糊搜索,
例如: 查询 name字段中包含五的数据

select * from users where name like '王五';

在这里插入图片描述

- 使用 % 模糊搜索。%代表任意个任意字符
-- 查询name字段中包含五的
select * from users where name like '%五%'
-- 查询name字段中最后一个字符 为 五的
select * from users where name like '%五';
-- 查询name字段中第一个字符 为 王 的
select * from users where name like '王%';
-- 使用 _ 单个的下划线。表示一个任意字符,使用和%类似
-- 查询表中 name 字段为两个字符的数据
select * from users where name like '__';
-- 查询 name 字段最后为五,的两个字符的数据
select * from users where name like '_五';

注意:where子句中的like在使用%或者_进行模糊搜索时,效率不高,使用时注意:
尽可能的不去使用%或者_
如果需要使用,也尽可能不要把通配符放在开头处

7.3 聚合函数

# 计算 users 表中 最大年龄,最小年龄,年龄和及平均年龄
select max(age),min(age),sum(age),avg(age) from users;
+----------+----------+----------+----------+
| max(age) | min(age) | sum(age) | avg(age) |
+----------+----------+----------+----------+
| 28 | 20 | 202 | 22.4444 |
+----------+----------+----------+----------+
-- 上面数据中的列都是在查询时使用的函数名,不方便阅读和后期的调用,可以通过别名方式 美化
select max(age) as max_age,
min(age) min_age,sum(age) as sum_age,
avg(age) as avg_age
from users;
+---------+---------+---------+---------+
| max_age | min_age | sum_age | avg_age |
+---------+---------+---------+---------+
| 28 | 20 | 202 | 22.4444 |
+---------+---------+---------+---------+
-- 统计 users 表中的数据量
select count(*) from users;
+----------+
| count(*) |
+----------+
| 9 |
+----------+
select count(id) from users;
+-----------+
| count(id) |
+-----------+
| 9 |
+-----------+
-- 上面的两个统计,分别使用了 count(*)count(id),结果目前都一样,有什么区别?
-- count(*) 是按照 users表中所有的列进行数据的统计,只要其中一列上有数据,就可以计算
-- count(id) 是按照指定的 id 字段进行统计,也可以使用别的字段进行统计,
-- 但是注意,如果指定的列上出现了NULL值,那么为NULL的这个数据不会被统计
-- 假设有下面这样的一张表需要统计
+------+-----------+------+--------+-----------+------+------+
| id | name | age | phone | email | sex | mm |
+------+-----------+------+--------+-----------+------+------+
| 1 | 章三 | 22 | | NULL || 0 |
| 2 | 李四 | 20 | | NULL || 0 |
| 5 | 王五 | 24 | 10011 | ww@qq.com || 0 |
| 1000 | aa | 20 | 123 | NULL || NULL |
| 1001 | bb | 20 | 123456 | NULL || NULL |
| 1002 | cc | 25 | 123 | NULL || NULL |
| 1003 | dd | 20 | 456 | NULL || NULL |
| 1004 | ff | 28 | 789 | NULL || NULL |
| 1005 | 王五六 | 23 | 890 | NULL | NULL | NULL |
+------+-----------+------+--------+-----------+------+------+
9 rows in set (0.00 sec)
-- 如果按照sex这一列进行统计,结果就是8个而不是9个,因为sex这一列中有NULL值存在
mysql> select count(sex) from users;
+------------+
| count(sex) |
+------------+
| 8 |
+------------+

聚合函数除了以上简单的使用意外,通常情况下都是配合着分组进行数据的统计和计算

7.4 GroupBy分组

group by 语句根据一个或多个列对结果集进行分组
一般情况下,是用与数据的统计或计算,配合聚合函数使用

-- 统计 users 表中 男女生人数,
-- 很明显按照上面的需要,可以写出两个语句进行分别统计
select count(*) from users where sex = '女';
select count(*) from users where sex = '男';
-- 可以使用分组进行统计,更方便
select sex,count(*) from users group by sex;
+------+----------+
| sex | count(*) |
+------+----------+
|| 4 |
|| 5 |
+------+----------
-- 统计1班和2班的人数
select classid,count(*) from users group by classid;
+---------+----------+
| classid | count(*) |
+---------+----------+
| 1 | 5 |
| 2 | 4 |
+---------+----------+
-- 分别统计每个班级的男女生人数
select classid,sex,count(*) as num from users group by classid,sex;
+---------+------+-----+
| classid | sex | num |
+---------+------+-----+
| 1 || 2 |
| 1 || 3 |
| 2 || 2 |
| 2 || 2 |
+---------+------+-----+
# 注意,在使用。group by分组时,一般除了聚合函数,其它在select后面出现的字段列都需要出现在grouop by 后
面

7.5 Having 字句

having时在分组聚合计算后,对结果再一次进行过滤,类似于where,
where过滤的是行数据,having过滤的是分组数据

-- 要统计班级人数
select classid,count(*) from users group by classid;
-- 统计班级人数,并且要人数达到5人及以上
select classid,count(*) as num from users group by classid having num >=5;

7.6 Order By 排序

我们在mysql中使用select的语句查询的数据结果是根据数据在底层文件的结构来排序的,
首先不要依赖默认的排序,另外在需要排序时要使用orderby对返回的结果进行排序
Asc 升序,默认
desc降序

-- 按照年龄对结果进行排序,从大到小
select * from users order by age desc;
-- 从小到大排序 asc 默认就是。可以不写
select * from users order by age;
-- 也可以按照多个字段进行排序
select * from users order by age,id; # 先按照age进行排序,age相同情况下,按照id进行排序
select * from users order by age,id desc;

7.7 Limit 数据分页

limit n 提取n条数据,
limit m,n 跳过m跳数据,提取n条数据

-- 查询users表中的数据,只要3条
select * from users limit 3;
-- 跳过前4条数据,再取3条数据
select * from users limit 4,3;
-- limit一般应用在数据分页上面
-- 例如每页显示10条数据,第三页的 limit应该怎么写? 思考
第一页 limit 0,10
第二页 limit 10,10
第三页 limit 20,10
第四页 limit 30,10
-- 提取 user表中 年龄最大的三个用户数据 怎么查询?
select * from users order by age desc limit 3;

8 存储引擎

MySQL 服务器把数据的存储和提取操作都封装到了一个叫 存储引擎 的模块里。我们知道 表 是由一行一行的记录组成的,但这只是一个逻辑上的概念,物理上如何表示记录,怎么从表中读取数据,怎么把数据写入具体的物理存储器上,这都是 存储引擎 负责的事情。为了实现不同的功能, MySQL 提供了各式各样的 存储引擎 ,不同 存储引擎 管理的表具体的存储结构可能不同,采用的存取算法也可能不同。
存储引擎以前叫做 表处理器 ,它的功能就是接收上层传下来的指令,然后对表中的数据进行提取或写入操作。为了管理方便,人们把 连接管理 、 查询缓存 、 语法解析 、 查询优化 这些并不涉及真实数据存储的功能划分为MySQL server 的功能,把真实存取数据的功能划分为 存储引擎 的功能。各种不同的存储引擎向上边的 MySQL server 层提供统一的调用接口(也就是存储引擎API),包含了几十个底层函数,像"读取索引第一条内容"、“读取索引下一条内容”、"插入记录"等等。
所以在 MySQL server 完成了查询优化后,只需按照生成的执行计划调用底层存储引擎提供的API,获取到数据后返回给客户端就好了。
MySQL 支持非常多种存储引擎:
在这里插入图片描述

8.1 ,MyISAM和InnoDB表引擎的区别

1) 事务支持
MyISAM不支持事务,而InnoDB支持。
事务:访问并更新数据库中数据的执行单元。事务操作中,要么都执行要么都不执行
2) 存储结构
MyISAM:每个MyISAM在磁盘上存储成三个文件。
.frm文件存储表结构。
.MYD文件存储数据。.MYI文件存储索引。
InnoDB:主要分为两种文件进行存储
.frm 存储表结构
.ibd 存储数据和索引 (也可能是多个.ibd文件,或者是独立的表空间文件)
3) 表锁差异
MyISAM:只支持表级锁,用户在操作myisam表时,select,update,delete,insert语句都会给表自动加锁,如果加锁以后的表满足insert并发的情况下,可以在表的尾部插入新的数据。
InnoDB:支持事务和行级锁,是innodb的最大特色。行锁大幅度提高了多用户并发操作的新能。但是InnoDB的行锁,只是在WHERE的主键是有效的,非主键的WHERE都会锁全表的。
4) 表主键
MyISAM:允许没有任何索引和主键的表存在,索引都是保存行的地址。 InnoDB:如果没有设定主键或者非空唯一索引,就会自动生成一个6字节的主键(用户不可见),数据是主索引的一部分,附加索引保存的是主索引的值。
InnoDB的主键范围更大,最大是MyISAM的2倍。
5) 表的具体行数
MyISAM:保存有表的总行数,如果select count() from table;会直接取出出该值。
InnoDB:没有保存表的总行数(只能遍历),如果使用select count() from table;就会遍历整个表,消耗相当大,但是在加了wehre条件后,myisam和innodb处理的方式都一样。
6) CURD操作
MyISAM:如果执行大量的SELECT,MyISAM是更好的选择。
InnoDB:如果你的数据执行大量的INSERT或UPDATE,出于性能方面的考虑,应该使用InnoDB表。DELETE 从性能上InnoDB更优,但DELETE FROM table
时,InnoDB不会重新建立表,而是一行一行的删除,在innodb上如果要清空保存有大量数据的表,最好使用truncate table这个命令。
7) 外键
MyISAM:不支持
InnoDB:支持
8) 查询效率
MyISAM相对简单,所以在效率上要优于InnoDB,小型应用可以考虑使用MyISAM。
推荐考虑使用InnoDB来替代MyISAM引擎,原因是InnoDB自身很多良好的特点,比如事务支持、存储 过程、视图、行级锁定等等,在并发很多的情况下,相信InnoDB的表现肯定要比MyISAM强很多。另外,任何一种表都不是万能的,只用恰当的针对业务类型来选择合适的表类型,才能最大的发挥MySQL的性能优
势。如果不是很复杂的Web应用,非关键应用,还是可以继续考虑MyISAM的,这个具体情况可以自己斟酌。
9)MyISAM和InnoDB两者的应用场景:
MyISAM管理非事务表。它提供高速存储和检索,以及全文搜索能力。如果应用中需要执行大量的SELECT查询,那么MyISAM是更好的选择。
InnoDB用于事务处理应用程序,具有众多特性,包括ACID事务支持。如果应用中需要执行大量的INSERT或UPDATE操作,则应该使用InnoDB,这样可以提高多用户并发操作的性能。现在默认使用InnoDB。

9 字符集

字符集简介
我们知道在计算机中只能存储二进制数据,那该怎么存储字符串呢?当然是建立字符与二进制数据的映射关系了,建立这个关系最起码要搞清楚两件事儿:

  1. 你要把哪些字符映射成二进制数据?
    也就是界定清楚字符范围。
  2. 怎么映射?
    将一个字符映射成一个二进制数据的过程也叫做 编码 ,将一个二进制数据映射到一个字符的过程叫做 解码 。
    人们抽象出一个 字符集 的概念来描述某个字符范围的编码规则
    我们看一下一些常用字符集的情况:
    ASCII 字符集
    共收录128个字符,包括空格、标点符号、数字、大小写字母和一些不可见字符。由于总共才128个字符,所以可以使用1个字节来进行编码,我们看一些字符的编码方式:
    ‘L’ -> 01001100(十六进制:0x4C,十进制:76)
    ‘M’ -> 01001101(十六进制:0x4D,十进制:77)
    ISO 8859-1 字符集
    共收录256个字符,是在 ASCII 字符集的基础上又扩充了128个西欧常用字符(包括德法两国的字母),也可以使用1个字节来进行编码。这个字符集也有一个别名 latin1 。
    GB2312 字符集
    收录了汉字以及拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母。其中收录汉字6763个,其他文字符号682个。同时这种字符集又兼容 ASCII 字符集,所以在编码方式上显得有些奇怪:
    如果该字符在 ASCII 字符集中,则采用1字节编码。
    否则采用2字节编码。
    这种表示一个字符需要的字节数可能不同的编码方式称为 变长编码方式 。比方说字符串 ‘爱u’ ,其中 ‘爱’ 需要用2个字节进行编码,编码后的十六进制表示为 0xCED2 , ‘u’ 需要用1个字节进行编码,编码后的十六进制表示为 0x75 ,所以拼合起来就是 0xCED275 。
    我们怎么区分某个字节代表一个单独的字符还是代表某个字符的一部分呢?别忘了 ASCII 字
    符集只收录128个字符,使用0~127就可以表示全部字符,所以如果某个字节是在0~127之内的,就意味着一个字节代表一个单独的字符,否则就是两个字节代表一个单独的字符。
    GBK 字符集GBK 字符集只是在收录字符范围上对 GB2312 字符集作了扩充,编码方式上兼容 GB2312 。
    utf8 字符集
    收录地球上能想到的所有字符,而且还在不断扩充。这种字符集兼容 ASCII 字符集,采用变长编码方式,编码一个字符需要使用1~4个字节,比方说这样:
    ‘L’ -> 01001100(十六进制:0x4C)
    ‘啊’ -> 111001011001010110001010(十六进制:0xE5958A)
    其实准确的说,utf8只是Unicode字符集的一种编码方案,Unicode字符集可以采用utf8、
    utf16、utf32这几种编码方案,utf8使用1~4个字节编码一个字符,utf16使用2个或4个字节编码一个字符,utf32使用4个字节编码一个字符。
    对于同一个字符,不同字符集也可能有不同的编码方式。比如对于汉字 ‘我’ 来说, ASCII 字符集中根本没有收录这个字符, utf8 和 gb2312 字符集对汉字 我 的编码方式如下:
    utf8编码:111001101000100010010001 (3个字节,十六进制表示是:0xE68891)
    gb2312编码:1100111011010010 (2个字节,十六进制表示是:0xCED2)

10 MYSQL中的utf8和utf8mb4

我们上边说 utf8 字符集表示一个字符需要使用1~4个字节,但是我们常用的一些字符使用1~3个字节就可以表示了。而在 MySQL 中字符集表示一个字符所用最大字节长度在某些方面会影响系统的存储和性能,所以设计 MySQL
的大叔偷偷的定义了两个概念:
utf8mb3 :阉割过的 utf8 字符集,只使用1~3个字节表示字符。
utf8mb4 :正宗的 utf8 字符集,使用1~4个字节表示字符。
有一点需要大家十分的注意,在 MySQL 中 utf8 是 utf8mb3 的别名,所以之后在 MySQL 中提到 utf8 就意味着使用1~3个字节来表示一个字符,如果大家有使用4字节编码一个字符的情况,比如存储一些emoji表情啥的,那请使
用 utf8mb4 。
字符集的查看
MySQL 支持好多好多种字符集,查看当前 MySQL 中支持的字符集可以用下边这个语句:

show charset;

11 MySQL数据库导入导出数据库 授权

11.1 数据导入导出

在这里插入图片描述

# 不要进入mysql,然后输入以下命令 导出某个库中的数据
mysqldump -u root -p tlxy > ~/Desktop/code/tlxy.sql

# 不要进入mysql,然后输入以下命令 导出某个库中指定的表的数据
mysqldump -u root -p tlxy tts > ~/Desktop/code/tlxy-tts.sql

在这里插入图片描述

# 在新的数据库中 导入备份的数据,导入导出的sql文件
mysql -u root -p ops < ./tlxy.sql
# 把导出的表sql 导入数据库
mysql -u root -p ops < ./tlxy-tts.sql

11.2 权限管理

mysql中的root用户是数据库中权限最高的用户,千万不要用在项目中。
可以给不同的用户,或者项目,创建不同的mysql用户,并适当的授权,完成数据库的相关操作
这样就一定程度上保证了数据库的安全。
创建用户的语法格式:
grant 授权的操作 on 授权的库.授权的表 to 账户@登录地址 identified by ‘密码’;

# 在mysql中 创建一个 zhangsan 用户,授权可以对tlxy这个库中的所有表 进行 添加和查询 的权限
grant select,insert on tlxy.* to zhangsan@'%' identified by '123456';
# 用户 lisi。密码 123456 可以对tlxy库中的所有表有 所有操作权限
grant all on tlxy.* to lisi@'%' identified by '123456';
# 删除用户
drop user 'lisi'@'%'

12 MYSQL的事务性

事务(Transaction)是由⼀系列对系统中数据进⾏访问与更新的操作所组成的⼀个程序执⾏逻辑单元
在这里插入图片描述

12.1 ,事务的语法

  1. start transaction;/ begin;
  2. commit; 使得当前的修改确认
  3. rollback; 使得当前的修改被放弃

12.2 ,事务的ACID特性

1. 原⼦性(Atomicity)
事务的原⼦性是指事务必须是⼀个原⼦的操作序列单元。事务中包含的各项操作在⼀次执⾏过程中,只允许出现两种状态之⼀。
全部执⾏成功
全部执⾏失败
事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执⾏过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发⽣⼀样。也就是说事务是⼀个不可分割的整体,就像化学中学过的原⼦,是物质构成的基本单位。
2. ⼀致性(Consistency)
事务的⼀致性是指事务的执⾏不能破坏数据库数据的完整性和⼀致性,⼀个事务在执⾏之前和执⾏之后,数据库都必须处以⼀致性状态。
⽐如:如果从A账户转账到B账户,不可能因为A账户扣了钱,⽽B账户没有加钱。
3. 隔离性(Isolation)
事务的隔离性是指在并发环境中,并发的事务是互相隔离的。也就是说,不同的事务并发操作相同的数据时,每个事务都有各⾃完整的数据空间。
⼀个事务内部的操作及使⽤的数据对其它并发事务是隔离的,并发执⾏的各个事务是不能互相⼲扰的。
隔离性分4个级别,下⾯会介绍。
4. 持久性(Duration)
事务的持久性是指事务⼀旦提交后,数据库中的数据必须被永久的保存下来。即使服务器系统崩溃或服务器宕机等故障。只要数据库重新启动,那么⼀定能够将其恢复到事务成功结束后的状态。

12.3 事务的并发问题

脏读读取到了没有提交的数据, 事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据。
不可重复读同⼀条命令返回不同的结果集(更新).事务 A 多次读取同⼀数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同⼀数据时,结果 不⼀致。
幻读:重复查询的过程中,数据就发⽣了量的变化(insert, delete)。

12.4 事务的隔离级别

在这里插入图片描述
4种事务隔离级别从上往下,级别越⾼,并发性越差,安全性就越来越⾼。 ⼀般数据默认级别是读以提交或可重复读
查看当前的事务隔离级别:

mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.93 sec

设置当前会话中的事务隔离级别
mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00sec

1. 读未提交(READ_UNCOMMITTED)

读未提交,该隔离级别允许脏读取,其隔离级别是最低的。换句话说,如果⼀个事务正在处理某⼀数据,并对其进⾏了更新,但同时尚未完成事务,因此还没有提交事务;⽽以此同时,允许另⼀个事务也能够访问该数据。
脏读示例:
在这里插入图片描述
余额应该为1500元才对。请看T5时间点,事务A此时查询的余额为0,这个数据就是脏数据,他是事务B造成的,很明显是事务没有进⾏隔离造成的。

2. 读已提交(READ_COMMITTED)

读已提交是不同的事务执⾏的时候只能获取到已经提交的数据。 这样就不会出现上⾯的脏读的情况了。但是在同⼀个事务中执⾏同⼀个读取,结果不⼀致
不可重复读示例
可是解决了脏读问题,但是还是解决不了可重复读问题
在这里插入图片描述

3. 可重复读(REPEATABLE_READ)

可重复读就是保证在事务处理过程中,多次读取同⼀个数据时,该数据的值和事务开始时刻是⼀致的。
因此该事务级别限制了不可重复读和脏读,但是有可能出现幻读的数据。
幻读
幻读就是指同样的事务操作,在前后两个时间段内执⾏对同⼀个数据项的读取,可能出现不⼀致的结果:
在这里插入图片描述
4. 顺序读(SERIALIZABLE)
顺序读是最严格的事务隔离级别。它要求所有的事务排队顺序执⾏,即事务只能⼀个接⼀个地处理,不能并发。

13 MYSQL的索引与SQL 优化

什么是索引?
索引类似图书的目录索引,可以提高数据检索的效率,降低数据库的IO成本。
MySQL官方对索引的定义为:
索引(Index)是帮助MySQL高效获取数据的数据结构。我们可以简单理解为:快速查找排好序的一种数据结构。

索引的分类

13.1 主键索引

: 根据主键建立索引,不允许重复,不允许空值;
在这里插入图片描述
如果表中没有定义主键,InnoDB 会选择⼀个唯⼀的⾮空索引代替。
如果没有这样的索引,InnoDB 会隐式定义⼀个主键来作为聚簇索引

13.2 唯一索引

:用来建立索引的值必须是唯一的,可以为null。
在这里插入图片描述

13.3普通索引

:用表中的普通列构建的索引,没有任何限制。
在这里插入图片描述

13.4 全文索引

:用大文本对象的列构建的索引;
在这里插入图片描述
5.6之前的版本中,全⽂索引只能⽤于MyISAM存储引擎
5.6 及以后的版本,MyISAM 和 InnoDB 均⽀持全⽂索引
在之前的MySQL中,全⽂索引只对英⽂有⽤,⽬前对中⽂还不⽀持
MySQL8的版本中⽀持了对中⽂分词的全⽂索引
为什么对中⽂不⽀持呢?因为中⽂和英⽂有本质上的区别。英⽂是单词语句组成,⽽且都有空格,好分辨,中⽂是有字组成的句,⽽且有不同的意思,因此不⼀样。
当然⽬前中⽂分词技术已经⾮常成熟了,因此的mysql8种⽀持了中⽂的全⽂索引

13.5 组合索引

:用多个列组合构建的索引,这些列中不允许有空值
在这里插入图片描述
组合索引有着最左原则:
在这里插入图片描述
就是对最左边的列进行排序。

索引的原理与B+Tree

只有memory(内存)存储引擎⽀持哈希索引,哈希索引⽤索引列的值计算该值的hashCode,然后在hashCode相应的位置存执该值所在⾏数据的物理位置,因为使⽤散列算法,因此访问速度⾮常快,但是⼀个值只能对应⼀个hashCode,⽽且是散列的分布⽅式,因此哈希索引不⽀持范围查找和排序的功能
正常情况下,如果不指定索引的类型,那么⼀般是指B+Tree索引。存储引擎以不同的⽅式使⽤B+Tree索引。性能也各有不同,但是InnoDB按照原数据格式进⾏存储。
其实在了解B+树之前需要了解一下二叉树–>平衡树—>红黑树—>B树,由于这里是对数据库的介绍,所以那块知识还是移步到数据结构那块,在不久后博主将会对数据结构做一篇详细的文章。
在这里插入图片描述
在计算机中,所有与空间相关的东⻄都是按照块(block)进⾏存取和操作的
每次读取都意味着⼀次I/O,假设计算机中每个块的⼤⼩为4K,⾏的⼤⼩为1k,索引的⼤⼩为0.06K
如果需要寻址遍历的次数多,就意味着更多的IO
1.磁盘读写代价更低
B树的数据和索引都在同一个节点上,那么每个块中包含的索引是少量的,如果想要取出比较深层的数据,意味着要读取更多的块,才能得到想要的索引和数据,那么就增加了IO次数
而B+树中每个块能存储的索引是B树的很多倍,那么获取比较深层的数据,也只需要读取少量的块就可以,那么就减少了磁盘的IO次数

⒉ B+树的优势是什么? 随机IO的次数更少
随机I/O是指读写操作时间连续,但访问地址不连续,时长约为10ms。
顺序I/O是指读取和写入操作基于逻辑块逐个连续访问来自相邻地址的数据,时长约为0.1ms在相同情况下,B树要进行更多的随机IO,而B+树需要更多的顺序IO,因此B+树,效率也更快3.查询速度更稳定
由于B+Tree非叶子节点不存储数据(data),因此所有的数据都要查询至叶子节点,而叶子节点的高度都是相同的,因此所有数据的查询速度都是一样的。

总结:
在数据库中,索引是提高数据的检索速度的,而索引是基于B+Tree的数据结构实现的。而使用B+Tree的好处是:
1.降低了磁盘读写代价
⒉顺序I/O提高效率3.查询速度更稳定

聚簇索引和非聚簇索引

索引又分为聚簇索引和非聚簇索引两种。
在索引的分类中,我们可以按照索引的键是否为主键来分为“主索引”和“辅助索引”
使用主键键值建立的索引称为“主索引”,其它的称为“辅助索引”。
因此主索引只能有一个,辅助索引可以有很多个。
在这里插入图片描述
至此,我们介绍的都是nmnoDB存储引擎中的索引方案,为了内容的完整性,以及各位可能在面试的时候遇到这类的问题,我们有必要再简单介绍一下MyISAM存储引擎中的索引方案。我们知道lnoDB中索引即数据,也就是聚簇索引的那棵8+树的叶子节点中已经把所有完整的用户记录都包含了,而MyISAM的索引方案虽然也使用树形结构,但是却将索引和数据分开存储:
也就是把索引信息单独存到⼀个⽂件中,这个⽂件称为索引⽂件
在这里插入图片描述
MyISAM会单独为表的主键创建一个索引,只不过在索引的叶子节点中存储的不是完整的数据记录,而是主键值+行号的组合。也就是先通过索引找到对应的行号,再通过行号去找对应的记录!其它非主键索引也是一样的,这种情况我们称为"回行’。所以在MyISAM中所有的索引都是非聚簇索引,也叫二级索引。

MylSAM和InnoDB的区别
·数据存储方式:

  • InnoDB由两种文件组成,表结构,数据和索引-
  • MylSAM由三种文件组成,表结构、数据、索引·索引的方式:
    ·索引的底层都是基于B+Tree的数据结构建立
    . InnoDB中主键索引为聚簇索引,辅助索引是非聚簇索引
    . MyISAM中数据和索引存在不同的文件中,因此都是非聚簇索引·
    事务的支持:
  • lnnoDB支持事务.
  • MyISAM不支持事务

慢查询与SQL优化

MySQL的慢查询,全名是慢查询日志。
是MySQL提供的一种日志记录,用来记录在MYSQL中响应时间超过阀值的语句。默认情况下,MySQL数据库并不启动慢查询日志,需要手动来设置这个参数。
如果不是调优需要的话,一般不建议启动该参数,开启慢查询日志会或多或少带来一定的性能影响。

慢查询日志可用于查找需要很长时间才能执行的查询,因此是优化的候选者。
在这里插入图片描述
一条查询语句在经过MySQL查询优化器的各种基于成本和规则的优化后会生成一个所谓的执行计划
这个执行计划展示了接下来具体执行查询的方式,比如多表连接的顺序是什么,对于每个表采用什么访问方法来具体执行查询等等。
MySQL为我们提供了EXPLAIN语句来帮助我们查看某个语句的具体执行计划。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
给name字段添加索引:
在这里插入图片描述
在这里插入图片描述
大家看到,索引能给数据检索提高的效率非常明显
那么是否意味着我们只要尽可能多的去建立索引就可以了呢?
每建立一个索引都会建立一棵B+树,并且需要维护,这是很费性能和存储空间的。

适当建立索引
1.创建并使用自增数字来建立主键索引
2经常作为where条件的字段建立索引
3.添加索引的字段尽可能的保持唯一性
4.可考虑使用联合索引并进行索引覆盖
联合索引的索引覆盖 (多个字段组合成了⼀个联合索引,在查询时,所要的字段和查询条件中的索引是⼀致)
注意索引绝不是加的越多越好
1.索引会占空间.
2.索引会影响写⼊性能

合理使用索引
MySQL索引通常是被用于提高WHERE条件的数据行匹配时的搜索速度,在索引的使用过程中,存在一些使用细节和注意事项。
因为不合理的使用可能会导致建立了索引之后,不一定就使用上了索引

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当查询条件左右两侧类型不匹配的时候会发⽣隐式转换,隐式转换带来的影响就是可能导致索引失效⽽进⾏全表扫描。
在这里插入图片描述
多个单列索引并不是最佳选择
事实上,MySQL只能使用一个单列索引。这样既浪费了空间,又没有提高性能(因为需要回行)为了提高性能,可以使用复合索引保证列都被索引覆盖。

SQL语句的优化
1.避免嵌套语句(子查询)
2.避象多表查询(复杂查询简单化)

索引优化
1.适当建立索引2.
合理使用索引

常见索引原则

1.选择唯一性索引;唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。
2.为经常需要排序、分组和联合操作的字段建立索引:
3.为常作为查询条件的字段建立索引。
4.限制索引的数目:越多的索引,会使更新表变得很浪费时间。尽量使用数据量少的索引
6.如果索引的值很长,那么查询的速度会受到影响。尽量使用前缀来索引
7.如果索引字段的值很长,最好使用值的前缀来索引。
7.删除不再使用或者很少使用的索引
8 . 最左前缀匹配原则,非常重要的原则。
10 . 尽量选择区分度高的列作为索引;区分度的公式是表示字段不重复的比例
11 .索引列不能参与计算,保持列“干净”:带函数的查询不参与索引。
12 .尽量的扩展索引,不要新建索引。

14 数据库的三范式

14.1 第一范式(1st NF -列都是不可再分)

第一范式的目标是确保每列的原子性:如果每列都是不可再分的最小数据单元(也称为最小的原子单元),则满足第一范式(1NF)
在这里插入图片描述

14.2 第二范式(2nd NF-每个表只描述一件事情)

首先满足第一范式,并且表中非主键列不存在对主键的部分依赖。 第二范式要求每个表只描述一件事情。
在这里插入图片描述

14.3 第三范式(3rd NF- 不存在对非主键列的传递依赖)

第三范式定义是,满足第二范式,并且表中的列不存在对非主键列的传递依赖。除了主键订单编号外,顾客姓名依赖于非主键顾客编号。
在这里插入图片描述

JDBC

Java DataBase Connectivity Java数据库连接

1 JDBC使用步骤

  1. 引入jar文件.
  2. 加载数据库驱动 (JavaSE项目中可以省略 , JavaWeb项目必须编写此步骤)
    Class.forName(“com.mysql.jdbc.Driver”);
  3. 通过驱动管理器, 获取JDBC连接对象.
    Connection conn = DriverManager.getConnection(“数据库连接地址”,“帐号”,“密码”);
    // 数据库连接地址格式: 主协议:子协议://ip地址:端口号/数据库名称
    // mysql的连接地址: jdbc:mysql://localhost:3306/java35
    // oracle的连接地址: jdbc:oracle:thin:@localhost:1521:ORCL
  4. 通过连接对象, 创建SQL执行对象 (SQL执行环境)
    Statement state = conn.createStatement();
  5. 通过SQL执行对象 ,执行SQL语句.
    state.execute(String sql语句);
  6. 释放资源
    state.close();
    conn.close();

jdbc在连接的时候需要在lib文件夹下引入一个jar包;本次项目用到的所有包都会上传到我的资源里边,读者可自行下载,也可到网上去下载对应版本。
在这里插入图片描述
然后接下来引入jar文件:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
然后引入测试包,这个包的作用是进行单元测试的;
在这里插入图片描述
在这里插入图片描述
可以点这里生成测试方法,也可以按快捷键 Ctrl shift t
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
运行完成
在这里插入图片描述
可以看到表已经成功创建,如果没有请关闭重新打开数据库
在这里插入图片描述
给数据库中插入数据:
在这里插入图片描述
注意设置编码格式,要不然存到数据库里就变成了乱码
在这里插入图片描述
在这里插入图片描述
2 JDBC中常用的类型和方法
1. DriverManager : 驱动管理器
常用方法:

  • 获取数据库连接:static Connection getConnection(String 数据库地址,String 账号 ,String 密码)
    2. Connection : 数据库连接对象
    常用方法:
  • 创建SQL执行对象: Statement createStatement();
    3. Statement : SQL执行对象
    常用方法:
  • 执行SQL语句(查询语句返回true, 其它语句返回false)
    boolean execute(String sql);
  • 执行DML语句(INSERT UPDATE DELETE) 和 DDL语句(create alter drop)
    (返回int值, 表示语句对数据库表格的影响行数 !)
    (通常我们认为 返回值>0 表示执行成功.)
    int executeUpdate(String sql);
    在这里插入图片描述
  • 执行DQL语句 (select)
    ResultSet executeQuery(String sql);
    4. ResultSet : 结果集对象 (指的是一个select语句的查询结果)
    常用方法:
  1. 控制游标移动的常用方法:
  • boolean next() ****
    作用: 控制游标向下一行移动.
    返回值: 移动成功返回true , 下一行不存在移动失败, 返回false
  • boolean privious() 了解
    作用: 控制游标向上一行移动.
    返回值: 移动成功返回true , 上一行不存在移动失败, 返回false
  • boolean absolute(int 行号) 了解
    作用: 控制游标向指定行移动
    返回值: 移动成功返回true , 行不存在移动失败, 返回false
  • boolean beforeFirst() 了解
    作用: 控制游标移动到第一行
    返回值: 移动成功返回true, 没有第一行数据返回false
  • boolean afterLast() 了解
    作用: 控制游标移动到最后一行
    返回值: 移动成功返回true, 没有最后一行数据返回false
  1. 获取游标指向行的字段值的常用方法:
  • XXX getXXX(String 列名) ***
  • 根据字段名, 得到此字段的值
  • XXX getXXX(int 字段的索引) *
    根据字段的索引, 得到字段的值 , 索引从1开始

在这里插入图片描述
上面的方法测试:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2 SQL 注入问题:

在这里插入图片描述
解决方案:
我们可以将SQL语句与参数分离,将参数作为SQL的特殊部分进行预处理.

PreparedStatement 预编译的SQL执行环境
内部实现原理:

  1. 将未拼接参数的SQL语句, 作为SQL指令, 先传递给数据库 进行编译.
  2. 再将参数传递给数据库, 此时传递的参数不会再作为指令执行, 只会被当作文本存在.
    操作流程与Statement基本一致:
  3. 如何得到一个PreparedStatement 对象
    PreparedStatement state = conn.prepareStatement(“预编译的SQL语句”);
  4. 预编译的SQL语句如何编写
    需要填充参数的位置, 使用?代替即可! 例如:
    select id from xzk_user where username=? and password=?
  5. 参数如何填充
    state.setXXX(int index,XXX value);
    setXXX中XXX指的是数据类型,
    参数1: index : SQL语句中?的索引值 , 从1开始
    参数2: value : 填充的参数值.
  6. 如何执行填充完毕参数的SQL
  • boolean execute();
  • int executeUpdate();
  • ResultSet executeQuery();

PreparedStatement与Statement谁的性能高?
看是什么数据库
在mysql中, preparedStatement原理是拼接SQL, 所以Statement性能高.
在Oracle中, preparedStatement原理是对SQL指令进行预处理, 再传递的参数不具备特殊含义.有更好的SQL缓存策略,PreparedStatement高.

在这里插入图片描述
在这里插入图片描述

3 批处理

将多条语句, 放到一起批量处理 .
批处理的原理: 将多条SQL语句, 转换为一个SQL指令. 显著的提高大量SQL语句执行时的数据库性能.
Statement对象使用流程:

  1. 得到Statement对象
    Statement state = conn.createStatement();
  2. 将一条SQL语句, 加入到批处理中.
    state.addBatch(String sql);
  3. 执行批处理
    state.executeBatch();
  4. 清空批处理
    state.clearBatch();

PreparedStatement对象使用流程:

  1. 得到PreparedStatement对象
    PreparedStatement state = conn.prepareStatement(“预编译的SQL”);
  2. 填充预编译的参数
    state.setXXX(1,填充参数);
  3. 将一条填充完毕参数的SQL, 加入到批处理中.
    state.addBatch();
  4. 执行批处理
    state.executeBatch();
  5. 清空批处理
    state.clearBatch();
package com.java.demo5;

import java.sql.*;

public class Main {
    
    
    //statment
    public static void insert1_1() throws SQLException {
    
    

        for (int i=0;i<1000;i++) {
    
    
            Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8", "root", "123");
            Statement state = conn.createStatement();
            state.executeUpdate("insert into person values('admin','123"+i+"')");
            state.close();
            conn.close();
        }

    }
    //statement 批处理
    public static void insert1_2() throws SQLException {
    
    
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8", "root", "123");
        Statement state = conn.createStatement();
        for (int i=0;i<1000;i++) {
    
    
            //向批处理中 加入一条SQL
            state.addBatch("insert into person values('admin','123"+i+"')");
        }
        //执行批处理
        state.executeBatch();
        //清空批处理指令
        state.clearBatch();
        state.close();
        conn.close();
    }

    //PreparedStatement
    public static void insert2_1() throws SQLException {
    
    
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8", "root", "123");
        PreparedStatement state = conn.prepareStatement("insert into person values(?,?)");
        for (int i=0;i<10000;i++) {
    
    
            state.setString(1,"admin");
            state.setString(2,"123"+i);
            state.executeUpdate();
        }
        state.close();
        conn.close();
    }
    //PreparedStatement批处理
    public static void insert2_2() throws SQLException {
    
    
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8", "root", "123");
        PreparedStatement state = conn.prepareStatement("insert into person values(?,?)");
        for (int i=0;i<10000;i++) {
    
    
            state.setString(1,"admin");
            state.setString(2,"123"+i);
            state.addBatch();
        }
        //执行批处理
        state.executeBatch();
        //清空批处理指令
        state.clearBatch();
        state.close();
        conn.close();
    }

}

可以执行各自的测试方法,看到每个方法的执行效率;

4 数据库连接池

连接池用于缓存连接!
当我们需要使用连接时, 可以不用再创建连接 ! 可以直接从连接池中获取连接.
当连接池中存在空闲连接时, 会将空闲连接给到程序使用.
当连接池中不存在空闲连接时, 且连接池未满时 , 则创建连接提供给程序使用 ,并在程序使用完毕后,缓存连接.
当连接池中不存在空闲连接时, 且连接池已满时 , 则排队等候空闲连接的出现.
注意:
使用连接池中的连接对象操作数据库时, 操作完毕依然需要释放连接(调用close()).
连接池中的连接在设计时, 使用了动态代理设计模式+装饰者设计模式 . 我们调用它的close方法,代理没有关闭这个连接, 而是将连接重新放入了池中.

需要properties作为配置文件
Properties类 是Java中的Map集合的实现类.
.properties文件 用于通过文件描述一组键值对!
.properties文件 ,可以快速的转换为Properties类的对象.
文件中内容的格式:
文件内容都是字符串 , 键与值之间通过等号连接 , 多个键值对之间换行分割.
例如:
url=xxx
user=xxx
password=xxx
如何将文件 转换为 集合:
步骤:

  1. 创建Properties对象
    Properties ppt = new Properties();
  2. 创建一个字节输入流 , 指向.properties文件
    InputStream is = new FileInputStream(“文件地址”);
  3. 将字节输入流, 传递给properties对象, 进行加载.
    ppt.load(is);

4.1 DBCP连接池的使用步骤

  1. 引入相关的jar文件
  • dbcp.jar
  • poll.jar
  1. 将配置文件引入
  2. 将配置文件, 转换为Properties对象
    Properties ppt = new Properties();
    ppt.load(配置文件的输入流);
  3. 通过连接池的工厂类(BasicDataSourceFactory)的创建连接池的方法(createDataSource())
    DataSource ds = BasicDataSourceFactory.createDataSource(ppt);
  4. 从连接池中 获取连接对象
    Connection conn = ds.getConnection();

在这里插入图片描述
还是老规矩,引进包,本代码所含的包都包含在我的资源 JDBC里,读者可自行下载
在这里插入图片描述
在这里插入图片描述
在现阶段导包比较麻烦,但是在后边学习到了项目管理工具maven的时候据不需要这么麻烦了。
在这里插入图片描述

4.2 德鲁伊连接池的使用步骤 *

  1. 引入相关的jar文件
  • druid-1.0.9.jar
  1. 将配置文件引入
  2. 将配置文件, 转换为Properties对象
    Properties ppt = new Properties();
    ppt.load(配置文件的输入流);
  3. 通过连接池的工厂类(DruidDataSourceFactory)的创建连接池的方法(createDataSource())
    DataSource ds = DruidDataSourceFactory.createDataSource(ppt);
  4. 从连接池中 获取连接对象
    Connection conn = ds.getConnection();

在这里插入图片描述

配置文件从我的资源里获取,,资源名称JDBC
引入包的方法和上面的一样。
在这里插入图片描述
德鲁伊的效率比其他连接池的效率好。

4.3 工具类

每次重复获取连接和释放连接比较冗余麻烦,因此,可以把它封装成一个工具类,更方便操作

德鲁伊工具类:

public class DruidUtil{
    
    
private static DataSource data = null;
static {
    
    
InputStream is =
DruidUtil.class.getClassLoader().getResourceAsStream("druid.properties");
Properties ppt = new Properties();
try {
    
    
ppt.load(is);
data = DruidDataSourceFactory.createDataSource(ppt);
} catch (Exception e) {
    
    
e.printStackTrace();
}
}
/**
* 用于从DBCP连接池中 获取一个连接
* @return DBCP连接池中的一个连接对象.
*/
public static Connection getConnection() {
    
    
try {
    
    
return data.getConnection();
} catch (SQLException e) {
    
    
e.printStackTrace();
return null;
}
}
/**
* 用于释放连接 , 执行环境 , 结果集 等资源
* @param conn 要释放的连接资源
* @param state 要释放的执行环境资源
* @param result 要释放的结果集资源
*/
public static void close(Connection conn,Statement state,ResultSet result) {
    
    
if(result != null) {
    
    
try {
    
    
result.close();
} catch (SQLException e) {
    
    
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(state != null) {
    
    
try {
    
    
state.close();
} catch (SQLException e) {
    
    
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(conn != null) {
    
    
try {
    
    
conn.close();
} catch (SQLException e) {
    
    
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

DBCP工具类:

public class DBCPUtil{
    
    
private static DataSource data = null;
static {
    
    
InputStream is =
DBCPUtil.class.getClassLoader().getResourceAsStream("dbcp.properties");
Properties ppt = new Properties();
try {
    
    
ppt.load(is);
data = BasicDataSourceFactory.createDataSource(ppt);
} catch (Exception e) {
    
    
e.printStackTrace();
}
}
/**
* 用于从DBCP连接池中 获取一个连接
* @return DBCP连接池中的一个连接对象.
*/
public static Connection getConnection() {
    
    
try {
    
    
return data.getConnection();
} catch (SQLException e) {
    
    
e.printStackTrace();
return null;
}
}
/**
* 用于释放连接 , 执行环境 , 结果集 等资源
* @param conn 要释放的连接资源
* @param state 要释放的执行环境资源
* @param result 要释放的结果集资源
*/
public static void close(Connection conn,Statement state,ResultSet result) {
    
    
if(result != null) {
    
    
try {
    
    
result.close();
} catch (SQLException e) {
    
    
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(state != null) {
    
    
try {
    
    
state.close();
} catch (SQLException e) {
    
    
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(conn != null) {
    
    
try {
    
    
conn.close();
} catch (SQLException e) {
    
    
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

现在我们的主代码就清晰很多了;
在这里插入图片描述

Redis

之前学的mysql ,SQL server都是关系型数据库。接下来的这两种Redis 和MongoDB都是非关系型数据库。

1 NoSQL

NoSQL,泛指非关系型的数据库。随着互联网web2.0网站的兴起,传统的关系数据库在处理web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,出现了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合,多重数据种类带来的挑战,尤其是大数据应用难题。
NoSQL最常见的解释是“non-relational”, “Not Only SQL”也被很多人接受。NoSQL仅仅是一个概念,泛指非关系型的数据库,区别于关系数据库,它们不保证关系数据的ACID特性。

2 使用NoSQL的原因

现在网站的特点:
(1) 高并发读写
Web2.0网站,数据库并发负载非常高,往往达到每秒上万次的读写请求
(2) 高容量存储和高效存储
Web2.0网站通常需要在后台数据库中存储海量数据,如何存储海量数据并进行高效的查询往往是一个 挑战
(3) 高扩展性和高可用性
随着系统的用户量和访问量与日俱增,需要数据库能够很方便的进行扩展、维护

NoSql数据库的优势
(1) 易扩展NoSQL数据库种类繁多,但是一个共同的特点都是去掉关系数据库的关系型特性。数据之间无关系,这 样就
非常容易扩展。也无形之间,在架构的层面上带来了可扩展的能力。
(2)大数据量,高性能
NoSQL数据库都具有非常高的读写性能,尤其在大数据量下,同样表现优秀。这得益于它的无关系性, 数据
库的结构简单。一般MySQL使用Query Cache,每次表的更新Cache就失效,是一种大粒度的 Cache,在针对
web2.0的交互频繁的应用,Cache性能不高。而NoSQL的Cache是记录级的,是一种细 粒度的Cache,所以
NoSQL在这个层面上来说就要性能高很多了。
(3)灵活的数据模型
NoSQL无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式。而在关系数据库里,增删 字段
是一件非常麻烦的事情。如果是非常大数据量的表,增加字段简直就是一个噩梦。这点在大数据量 的web2.0时代
尤其明显。
(4) 高可用
NoSQL在不太影响性能的情况,就可以方便的实现高可用的架构。比如Cassandra,HBase模型,通过 复制
模型也能实现高可用。
常见的NoSQL:
在这里插入图片描述
在这里插入图片描述

3 什么是Redis?

全称:REmote DIctionary Server(远程字典服务器)。是完全开源免费的,用C语言编写的, 遵守BCD协议。是一个高性能的(key/value)分布式内存数据库,
基于内存运行并支持持久化的NoSQL数据库,是当前最热门的NoSql数据库之一,也被人们称为数据结构服务器。
Redis 与其他 key - value 缓存产品有以下三个特点
(1) Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用
(2) Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储
(3) Redis支持数据的备份,即master-slave(主从)模式的数据备份
Redis优势
(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;
Redis应用场景
(1) 缓存(数据查询,短连接,新闻内容,商品内容等),使用最多
(2) 聊天室在线好友列表
(3) 任务队列(秒杀,抢购,12306等)
(4) 应用排行榜
(5) 网站访问统计
(6) 数据过期处理(可以精确到毫秒)
(7) 分布式集群架构中的session问题
Redis下载
(1)Http://redis.io/ 英文地址
(2)Http://www.redis.cn/ 中文地址

4 Linux下安装Redis

4.1 环境准备

(1)虚拟机版本:VMware® Workstation 12 Pro
(2) Linux系统:Centos Release 6.5
(3) 远程命令端:xshell
(4)文件传输工具:SecureFXPortable

4.2 Redis的安装

4.2.1 Redis的编译环境

Redis是C语言开发的,安装redis需要先去官网下载源码进行编译,编译需要依赖于GCC编译环境,如果CentOS上没有安装gcc编译环境,需要提前安装,安装命令如下:(这里我们使用root用户处理这些操作)

[root@localhost ~]# yum install gcc-c++

在这里插入图片描述
在这里插入图片描述

4.2.2 Redis的安装

(1) 使用SecureFXPortable上传Redis安装文件到Linux目录
在这里插入图片描述
(2)上传Redis安装文件,这里我上传自建文件夹: /home/yhp/local
在这里插入图片描述
3 解压Redis文件

[root@localhost local]# tar -zxvf redis-5.0.5.tar.gz

在这里插入图片描述
(4)编译Redis(编译,将.c文件编译为.o文件)
进入解压文件夹,执行 make

[root@localhost local]# cd redis-5.0.5
[root@localhost redis-5.0.5]# make

在这里插入图片描述
编译成功!如果编译过程中出错,先删除安装文件目录,后解压重新编译。
(5) 安装

[root@localhost redis-5.0.5]# make PREFIX=/home/admin/myapps/redis install

在这里插入图片描述
(6)安装之后的bin目录
在这里插入图片描述
在这里插入图片描述
(7) Copy文件
Redis启动需要一个配置文件,可以修改端口号信息。将redis解压的文件夹中的redis.conf文件复制到安装目录

[root@localhost redis-5.0.5]# cp redis.conf /home/admin/myapps/redis

4.3 Redis 启动

4.3.1 前端启动

直接运行bin/redis-server将使永前端模式启动,前端模式启动的缺点是启动完成后,不能再进行其他操作,如果要操作必须使用ctrl+c,同时redis-server程序结束,不推荐此方法。

[root@localhost bin]# ./redis-server

在这里插入图片描述

4.3.2 Redis的后端启动

修改redis.conf配置文件,设置:daemonize yes,然后可以使用后端模式启动

[root@localhost redis]# vi redis.conf

在这里插入图片描述
启动时,指定配置文件(这里所在文件夹是redis)

[root@localhost redis]# ./bin/redis-server ./redis.conf

Redis默认端口:6379,通过当前服务进行查看

[root@localhost redis]# ps -ef | grep -i redis

在这里插入图片描述

4.3.3 客户端访问redis

如果想要通过指令来操作redis,可以使用redis的客户端进行操作,在bin文件夹下运行redis-cli该指令默认连接的127.0.0.1 ,端口号是6379

[root@localhost bin]# ./redis-cli
127.0.0.1:6379>

如果想要连接指定的ip地址以及端口号,则需要按照
redis-cli -h ip地址 -p 端口号 语法连接

4.3.4 向Redis服务器发送命令

Ping,测试客户端与Redis的连接是否正常,如果连接正常,回收到pong

127.0.0.1:6379> ping
PONG

4.3.5 退出客户端

127.0.0.1:6379> quit

4.3.6 Redis 停止

(1) 强制结束程序。强制终止Redis进程可能会导致redis持久化数据丢失。
在这里插入图片描述
(2) 正确停止Redis的方式应该是向Redis发送SHUTDOWN命令,方法为(关闭默认的端口)

[root@localhost redis]# ./bin/redis-cli shutdown

在这里插入图片描述

4.3.7 三方工具(redis-desktop-manager)操作redis

在这里插入图片描述
注意:需要关闭linux防火墙并且修改redis.conf文件中的bind参数
bind linux的ip地址
此时如果通过redis客户端访问的时候,代码如下:

./redis-cli -h 192.168.197.132 -p 6379

5 Redis数据结构

Redis是一种基于内存的数据库,并且提供一定的持久化功能,它是一种键值(key-value)数据库,使用 key 作为索引找到当前缓存的数据,并且返回给程序调用者。
当前的 Redis 支持 6 种数据类型,它们分别是字符串(String)、列表(List)、集合(set)、哈希结构(hash)、有序集合(zset)和基数(HyperLogLog)

在这里插入图片描述

6 Redis 常用指令

命令学习网站:http://doc.redisfans.com/index.html

6.1 String 类型

赋值语法:

127.0.0.1:6379> set k1 zhangsan
OK

取值语法

127.0.0.1:6379> get k1
"zhangsan"

设置多个键语法: MSET key value [key value …]

127.0.0.1:6379> mset k2 lisi k3 wangwu
OK

获取多个键值 MGET key [key …]

127.0.0.1:6379> mget k2 k3
1) "lisi"
2) "wangwu"

删除语法:DEL key

127.0.0.1:6379> del k3
(integer) 1
127.0.0.1:6379> get k3
(nil)

6.2 字符串数字的递增与递减

递增数字:当存储的字符串是整数时,Redis提供了一个实用的命令INCR,其作用是让当前键值递增,并返回递增后的值。
递增数字语法: INCR key
递减数值语法: DECR key
增加指定的整数语法: INCRBY key increment
减少指定的整数 语法:DECRBY key decrement

递增

127.0.0.1:6379> incr num
(integer) 1
127.0.0.1:6379> incr num
(integer) 2

递减

127.0.0.1:6379> decr num
(integer) 1

指定步长

127.0.0.1:6379> incrby num2 2
(integer) 2
127.0.0.1:6379> incrby num2 3
(integer) 5
127.0.0.1:6379> decrby num2 2
(integer) 3
127.0.0.1:6379> decrby num2 1
(integer) 2

6.3 hash散列

hash叫散列类型,它提供了字段和字段值的映射。字段值只能是字符串类型,不支持散列类型、集合类型等其它类型。相当于是对象格式的存储
赋值语法: HSET key field value
设置一个字段值, HSET命令不区分插入和更新操作,当执行插入操作时HSET命令返回1,当执行更新操作时返回0.

127.0.0.1:6379> hset user1 username zhangsan
(integer) 1
127.0.0.1:6379> hset user1 username lisi
(integer) 0

取值语法: HGET key field

127.0.0.1:6379> hget user1 username
"lisi"

设置多个字段语法: HMSET key field value [field value …]

127.0.0.1:6379> hmset user1 password 123 age 20
OK

取多个值语法:

127.0.0.1:6379> hmget user1 password age
1) "123"
2) "20"

获取所有字段值语法:HGETALL key

127.0.0.1:6379> hgetall user1
1) "username"
2) "lisi"
3) "password"
4) "123"
5) "age"
6) "20"

删除字段语法:HDEL key field [field …]

127.0.0.1:6379> hdel user1 username
(integer) 1
127.0.0.1:6379> hgetall user1
1) "password"
2) "123"
3) "age"
4) "20"

6.4 队列List

Redis的list是采用来链表来存储,双向链表存储数据,特点:增删快、查询慢(Linkedlist).这个队列是有序的。
向列表左边增加元素: LPUSH key value [value …]
从列表左边弹出元素: LPOP key(临时存储,弹出后,从队列中清除)

127.0.0.1:6379> lpush alist a1 a2 123
(integer) 3
127.0.0.1:6379> lpop alist
"123"
127.0.0.1:6379> lpop alist
"a2"

向列表右边增加元素 : RPUSH key value [value …]
从列表右边弹出元素: RPOP key

127.0.0.1:6379> rpush blist a1 a2 345
(integer) 3
127.0.0.1:6379> rpop blist
"345"

获取列表中元素的个数

127.0.0.1:6379> llen blist
(integer) 1

查看列表语法:LRANGE key start stop

127.0.0.1:6379> lrange blist 0 3
1) "a2"

将返回start、stop之间的所有元素(包含两端的元素),索引从0开始,可以是负数,如:“-1”代表最后的一个元素。

临时存储。先进先出。使用双向链表:
1,左边进,右边出

127.0.0.1:6379> lpush stulist stu1
(integer) 1
127.0.0.1:6379> lpush stulist stu2
(integer) 2
127.0.0.1:6379> lpush stulist stu3
(integer) 3
127.0.0.1:6379> lpush stulist stu4
(integer) 4
127.0.0.1:6379> lpush stulist stu4
(integer) 5
127.0.0.1:6379> lpush stulist stu5
(integer) 6
127.0.0.1:6379> rpop stulist
"stu1"
127.0.0.1:6379> rpop stulist
"stu2"
127.0.0.1:6379> rpop stulist
"stu3"
127.0.0.1:6379> rpop stulist
"stu4"

2,右边进,左边去。

127.0.0.1:6379> rpush clist stu1
(integer) 1
127.0.0.1:6379> rpush clist stu2
(integer) 2
127.0.0.1:6379> rpush clist stu3
(integer) 3
127.0.0.1:6379> rpush clist stu4
(integer) 4
127.0.0.1:6379> rpush clist stu5
(integer) 5
127.0.0.1:6379> lpop clist
"stu1"
127.0.0.1:6379> lpop clist
"stu2"

6.5 Set集合

Set集合类型:无序、不可重复
增加元素语法:SADD key member [member …]
删除元素语法: SREM key member [member …]
获得集合中的所有元素 : smembers key

127.0.0.1:6379> sadd ulist user1
(integer) 1
127.0.0.1:6379> sadd ulist user2
(integer) 1
127.0.0.1:6379> sadd ulist user3
(integer) 1
127.0.0.1:6379> smembers ulist
1) "user2"
2) "user3"
3) "user1"
127.0.0.1:6379> srem ulist user2
(integer) 1
127.0.0.1:6379> smembers ulist
1) "user3"
2) "user1"

判断元素是否在集合中: SISMEMBER key member

127.0.0.1:6379> smembers ulist
1) "user3"
2) "user1"
127.0.0.1:6379> sismember ulist user2
(integer) 0
127.0.0.1:6379> sismember ulist user1
(integer) 1

6.6 Zset有序集合

Sortedset又叫zset,是有序集合,可排序的,但是唯一。 Sortedset和set的不同之处,是会给set中的元素添加一个分数,然后通过这个分数进行排序。
增加元素:ZADD key score member [score member …]
向有序集合中加入一个元素和该元素的分数(score),如果该元素已经存在则会用新的分数替换原有的分数

127.0.0.1:6379> zadd num1 20 stu1 30 stu2 40 stu3
(integer) 3

添加带分数(可用学生成绩,销售数量等来做分数,方便计算排序):
获得排名在某个范围的元素列表,并按照元素分数降序返回
语法:ZREVRANGE key start stop [WITHSCORES]

127.0.0.1:6379> zadd num1 10 stu4
(integer) 1
127.0.0.1:6379> zrevrange num1 0 4
1) "stu3"
2) "stu2"
3) "stu1"
4) "stu4"

获取元素的分数 :ZSCORE key member

127.0.0.1:6379> zscore num1 stu2
"30"

删除元素ZREM key member [member …]

127.0.0.1:6379> zrem num1 stu2
(integer) 1
127.0.0.1:6379> zrevrange num1 0 4
1) "stu3"
2) "stu1"
3) "stu4"

获得元素的分数的可以在命令尾部加上WITHSCORES参数

127.0.0.1:6379> zrevrange num1 0 4 withscores
1) "stu3"
2) "40"
3) "stu1"
4) "20"
5) "stu4"
6) "10"

给某一个属性加分数或减分,减分时使用负数:

127.0.0.1:6379> zincrby num1 2 stu1
"22"

在这里插入图片描述

6.7 HyoperLogLog命令

HyperLogLog是一种使用随机化的算法,以少量内存提供集合中唯一元素数量的近似值。
HyperLogLog 可以接受多个元素作为输入,并给出输入元素的基数估算值:
基数:集合中不同元素的数量。比如 {‘apple’, ‘banana’, ‘cherry’, ‘banana’, ‘apple’} 的基数就是 3 。
估算值:算法给出的基数并不是精确的,可能会比实际稍微多一些或者稍微少一些,但会控制在合理的范围之内。
HyperLogLog 的优点是,即使输入元素的数量或者体积非常非常大,计算基数所需的空间总是固定的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素.
在这里插入图片描述

redis 127.0.0.1:6379> PFADD mykey "redis"
1) (integer) 1
redis 127.0.0.1:6379> PFADD mykey "java"
1) (integer) 1
redis 127.0.0.1:6379> PFADD mykey "mysql"
1) (integer) 1
redis 127.0.0.1:6379> PFCOUNT mykey
(integer) 3

6.8 其他命令

(1) keys返回满足给定pattern 的所有key
keys user* //查询以user开头的key
keys * //查询所有的key
(2) exists确认一个key 是否存在,存在返回1

127.0.0.1:6379> exists num2 //语法:exists key
(integer) 1


127.0.0.1:6379> exists num23
(integer) 0

(3)删除一个key

127.0.0.1:6379> del num1 //语法: del key 删除存在的key返回1,不存在的key返回0
(integer) 1
127.0.0.1:6379> del num23
(integer) 0

(4) rename重命名key:rename oldkey newkey

127.0.0.1:6379> rename k1 k11
OK
127.0.0.1:6379> keys *
1) "ulist"
2) "k2"
3) "user1"
4) "num2"
5) "clist"
6) "k11"

(5)type返回值的类型: type key

127.0.0.1:6379> type ulist
set
127.0.0.1:6379> type k11
string
127.0.0.1:6379> type alist
list

设置key的生存时间:缓存的数据一般都是需要设置生存时间的,即:到期后数据销毁。

(6) EXPIRE key seconds
设置key的生存时间(单位:秒)key在多少秒后会自动删除
TTL key 查看key剩余的生存时间
PERSIST key 清除生存时间

127.0.0.1:6379> set a1 123
OK
127.0.0.1:6379> get a1
"123"
127.0.0.1:6379> expire a1 60
(integer) 1
127.0.0.1:6379> ttl a1
(integer) 56
127.0.0.1:6379> ttl a1
(integer) 51
127.0.0.1:6379> ttl a1
(integer) 47

(7) 获取服务器信息和统计:info
(8) 删除当前选择数据库中的所有key:flushdb
(9) 删除所有数据库中的所有key:flushall

6.9 Redis的多数据库

一个redis实例key包括多个数据库,客户端可以指定连接某个redis实例的哪个数据库,就好比一个mysql中创建多个数据库,客户端连接时指定连接哪个数据库。
一个redis实例最多可提供16个数据库,下标从0-15,客户端默认连接第0号数据库,也可以通过select选择连接哪个数据库,如下连接1号库:
在这里插入图片描述
切换到0数据库下边:
在这里插入图片描述
将key的数据移动到1号数据库: move key 数据库编号
在这里插入图片描述

7.Redis的事务管理

Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。
事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
一个事务从开始到执行会经历以下三个阶段:
开始事务
命令入队。
执行事务。

实例
以下是一个事务的例子, 它先以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令:

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set bookname java
QUEUED
127.0.0.1:6379> set bookname c++
QUEUED
127.0.0.1:6379> set bookname html
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set u1 user1
QUEUED
127.0.0.1:6379> get u1
QUEUED
127.0.0.1:6379> sadd tag c++ html java
QUEUED
127.0.0.1:6379> smembers tag
QUEUED
127.0.0.1:6379> exec
1) OK
2) "user1"
3) (integer) 3
4) 1) "java"
2) "html"
3) "c++"

8 Redis发布订阅模式

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
Redis 客户端可以订阅任意数量的频道。
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
在这里插入图片描述
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
在这里插入图片描述
在我们实例中我们创建了订阅频道名为 redisMessage:

127.0.0.1:6379> subscribe redisMessage
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "redisMessage"
3) (integer) 1

现在,我们先重新开启个 redis 客户端,然后在同一个频道 redisMessage 发布两次消息,订阅者就能接收到消息。

127.0.0.1:6379> publish redisMessage "demo1 test"
(integer) 1
127.0.0.1:6379> publish redisMessage "demo2 test"
(integer) 1
127.0.0.1:6379> publish redisMessage "demo3 test"
(integer) 1

订阅者的客户端会显示如下消息

127.0.0.1:6379> subscribe redisMessage
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "redisMessage"
3) (integer) 1
1) "message"
2) "redisMessage"
3) "demo1 test"
1) "message"
2) "redisMessage"
3) "demo2 test"
1) "message"
2) "redisMessage"
3) "demo3 test"

左边订阅:右边发布;
在这里插入图片描述

9 .Jedis连接Redis

1 创建项目导入依赖

<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.2</version>
</dependency>

1)确认远程服务器是否可以ping通: ping vm的ip地址
在这里插入图片描述
2)确认防火墙是否关闭或放行

service iptables stop
service iptables status

第二步:链接服务器
方案一
单实例链接:

Jedis jedis = new Jedis(“ip地址”, 端口号);//建立链接

核心代码:

public static void main(String[] args) {
    
    
Jedis jedis=new Jedis("192.168.197.129",6379);
//设置值
jedis.set("java001","java工程师");
String java001 = jedis.get("java001");
System.out.println(java001);
}

在这里插入图片描述
解决方案:
虚拟机客户端连接的ip是127.0.0.1,意思是连接的本机,其他机器无法连接,这里需要修改配置文件,将连接地址改为虚拟机的地址,就可以了.
修改redis.conf文件里面的 bind 连接地址,将连接地址改为自己虚拟机的ip

bind 192.168.197.129

重新启动服务,Jedis就可以正常连上了
Idea中控制台打印:
在这里插入图片描述
在这里插入图片描述
方案二 连接池

// 1.获取连接池配置对象,设置配置项
JedisPoolConfig config = new JedisPoolConfig();
// 1.1最大的连接数
config.setMaxTotal(30);
// 1.2最大的空闲
config.setMaxIdle(10);
// 2.获取连接池
JedisPool jedisPool = new JedisPool(config, "192.168.197.129", 6379);
Jedis jedis = null;
try {
    
    
	jedis = jedisPool.getResource();
	// 3.设置数据
	jedis.set("name", "张三");
	String name = jedis.get("name");
	System.out.println("name=" + name);
} catch (Exception e) {
    
    
	e.printStackTrace();
} finally {
    
    
	if (jedis != null) {
    
    
	jedis.close();
}
// 4.虚拟机关闭的时候,释放资源
if (jedisPool != null) {
    
    
		jedisPool.close();
	}
}

在这里插入图片描述

10 Redis的持久化方式

10.1 什么是Redis持久化

由于redis的值放在内存中,为防止突然断电等特殊情况的发生,需要对数据进行持久化备份。即将内存数据保存到硬盘。

10.2 Redis 持久化存储方式

10.2.1 RDB持久化

RDB 是以二进制文件,是在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。
前边所说的Redis是它在处理网络请求时是单线程,而其他模块还是多线程的
优点:使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
缺点:RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候。这里说的这个执行数据写入到临时文件的时间点是可以通过配置来自己确定的,通过配置redis 在 n 秒内如果超过m 个 key 被修改这执行一次 RDB 操作。这个操作就类似于在这个时间点来保存一次 Redis 的所有数据,一次快照数据。所以这个持久化方法也通常叫做 snapshots。
RDB 默认开启,redis.conf 中的具体配置参数如下;

#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

10.2.2 AOF持久化

Append-Only File,将“操作 + 数据”以格式化指令的方式追加到操作日志文件的尾部,在 append 操作返回后(已经写入到文件或者将要写入),才进行实际的数据变更,“日志文件”保存了历史所有的操作过程;当 server 需要数据恢复时,可以直接 replay 此日志文件,即可还原所有的操作过程。AOF 相对可靠,AOF 文件内容是字符串,非常容易阅读和解析。

优点可以保持更高的数据完整性,如果设置追加 file 的时间是 1s,如果 redis 发生故障,最多会丢失 1s 的数据;且如果日志写入不完整支持 redis-check-aof 来进行日志修复;AOF 文件没被 rewrite 之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的 flushall)。

缺点:AOF 文件比 RDB 文件大,且恢复速度慢。

我们可以简单的认为 AOF 就是日志文件,此文件只会记录“变更操作”(例如:set/del 等),如果 server 中持续的大量变更操作,将会导致 AOF 文件非常的庞大,意味着 server 失效后,数据恢复的过程将会很长;事实上,一条数据经过多次变更,将会产生多条 AOF 记录,其实只要保存当前的状态,历史的操作记录是可以抛弃的;因为 AOF
持久化模式还伴生了“AOF rewrite”
AOF 的特性决定了它相对比较安全,如果你期望数据更少的丢失,那么可以采用 AOF 模式。如果 AOF 文件正在被写入时突然 server 失效,有可能导致文件的最后一次记录是不完整,你可以通过手工或者程序的方式去检测并修正不完整的记录,以便通过 aof 文件恢复能够正常;同时需要提醒,如果你的 redis 持久化手段中有 aof,那么在server 故障失效后再次启动前,需要检测 aof 文件的完整性。

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

AOF 是文件操作,对于变更操作比较密集的 server,那么必将造成磁盘 IO 的负荷加重;此外 linux 对文件操作采取了“延迟写入”手段,即并非每次 write 操作都会触发实际磁盘操作,而是进入了 buffer 中,当 buffer 数据达到阀值时触发实际写入(也有其他时机),这是 linux 对文件系统的优化,但是这却有可能带来隐患,如果 buffer 没有刷新到磁盘,此时物理机器失效(比如断电),那么有可能导致最后一条或者多条 aof 记录的丢失。通过上述配置文件,可以得知 redis 提供了 3 中 aof 记录同步选项:
always:每一条 aof 记录都立即同步到文件,这是最安全的方式,也以为更多的磁盘操作和阻塞延迟,是 IO 开支较大。
everysec:每秒同步一次,性能和安全都比较中庸的方式,也是 redis 推荐的方式。如果遇到物理服务器故障,有可能导致最近一秒内 aof 记录丢失(可能为部分丢失)。
no:redis 并不直接调用文件同步,而是交给操作系统来处理,操作系统可以根据 buffer 填充情况 / 通道空闲时间等择机触发同步;这是一种普通的文件操作方式。性能较好,在物理服务器故障时,数据丢失量会因 OS 配置有关。
其实,我们可以选择的太少,everysec 是最佳的选择。如果你非常在意每个数据都极其可靠,建议你选择一款“关系性数据库”。

11Redis主从复制

持久化保证了即使redis服务重启也不会丢失数据,但是当redis服务器的硬盘损坏了可能会导致数据丢失,通过redis的主从复制机制就可以避免这种单点故障(单台服务器的故障)。
主redis中的数据和从上的数据保持实时同步,当主redis写入数据时通过主从复制机制复制到两个从服务上。
主从复制不会阻塞master,在同步数据时,master 可以继续处理client 请求.
主机master配置:无需配置
在这里插入图片描述
工作中一般选用:一主两从或一主一从
数据会同步到从服务器。在这个集群中的几台服务器上都有同样的数据。

主从搭建步骤:
主机:不用配置。仅仅只需要配置从机,从机slave配置:(这里是伪集群)
第一步:复制出一个从机,注意使用root用户

[root@localhost myapps]# cp redis/ redis1 -r
[root@localhost myapps]# ll
总用量 40
drwxr-xr-x. 3 root root 4096 21 09:26 redis
drwxr-xr-x. 3 root root 4096 21 09:27 redis1

第二步:修改从机的redis.conf
语法:replicaof // replicaof 主机ip 主机端口号
提示:检索文件: 输入:/replicaof 当前页没有时,输入n,查找下一页
第三步:修改从机的port地址为6380,只需修改这一项即可。不需要动其他的
在从机redis.conf中修改
在这里插入图片描述
第四步:清除从机中的持久化文件

[root@localhost bin]# rm -rf appendonly.aof dump.rdb
[root@localhost bin]# ll
总用量 15440
-rwxr-xr-x. 1 root root 4588902 71 09:27 redis-benchmark
-rwxr-xr-x. 1 root root 22225 71 09:27 redis-check-aof
-rwxr-xr-x. 1 root root 45443 71 09:27 redis-check-dump
-rwxr-xr-x. 1 root root 4691809 71 09:27 redis-cli
lrwxrwxrwx. 1 root root 12 71 09:27 redis-sentinel -> redis-server
-rwxr-xr-x. 1 root root 6450337 71 09:27 redis-server

启动从机

[root@localhost redis1]# ./bin/redis-server ./redis.conf

[root@localhost redis1]# ./bin/redis-server ./redis.conf

启动6380 的客户端

[root@localhost redis1]# ./bin/redis-cli -p 6380
127.0.0.1:6380> keys *
1) "mylist"
2) "num"
3) "bookCate1"
4) "newbook"
127.0.0.1:6380>

停止客户端: ./bin/redis-cli -p 6380 shutdown
注意:
Ø 主机一旦发生增删改操作,那么从机会自动将数据同步到从机中

127.0.0.1:6380> get username
"hehe"
127.0.0.1:6380> set username haha
(error) READONLY You can't write against a read only slave.

复制的过程原理
当从库和主库建立MS(master slaver)关系后,会向主数据库发送SYNC命令;
主库接收到SYNC命令后会开始在后台保存快照(RDB持久化过程),并将期间接收到的写命令缓存起来;
快照完成后,主Redis会将快照文件和所有缓存的写命令发送给从Redis;
从Redis接收到后,会载入快照文件并且执行收到的缓存命令;
主Redis每当接收到写命令时就会将命令发送从Redis,保证数据的一致;【内部完成,所以不支持客户端在从
机人为写数据。】
复制架构中出现宕机情况?
从Redis宕机:重启就好
主Redis宕机:从数据库(从机)中执行SLAVEOF NO ONE命令,断开主从关系并且提升为主库继续服务[把一个从做为
主机,这个时候新主机[之前的从机]就具备写入的能力];主服务器修好后,重新启动后,执行SLAVEOF命令,将其
设置为从库[老主机设置为从机]。[手动执行,过程复杂,容易出错。]是否有更好的方案?

12 哨兵模式

哨兵模式:给集群分配一个站岗的。
哨兵的作用就是对Redis系统的运行情况监控,它是一个独立进程,它的功能:

  1. 监控主数据库和从数据库是否运行正常;
  2. 主数据出现故障后自动将从数据库转化为主数据库;
    如果主机宕,开启选举工作,选择一个从做主机。环境准备:一主两从,启动任一从机时,启动哨兵模式
    虽然哨兵(sentinel) 释出为一个单独的可执行文件 redis-sentinel ,但实际上它只是一个运行在特殊模式下的 Redis服务器,你可以在启动一个普通 Redis 服务器时通过给定 --sentinel 选项来启动哨兵(sentinel)。

在这里插入图片描述

12.1. 第一步:配置哨兵:

哨兵主要是用来监听主服务器的,所以一般把哨兵部署在从服务器上监听。
配置哨兵
启动哨兵进程,首先需要创建哨兵配置文件vi sentinel.conf,可从源码配置
redis-5.0.5/sentinel.conf中复制内
容,也可以直接自定义该文件到bin目录下
在配置中输入:sentinel monitor mastername 内网IP(127.0.0.1) 6379 1
说明:
mastername 监控主数据的名称,自定义
127.0.0.1:监控主数据库的IP;
6379:端口
1:最低通过票数
在这里插入图片描述

12.2. 第二步:启动哨兵

哨兵是一个单独的进程,启动之前确保主从服务是正常的。先启动主服务,后启动从服务
在这里插入图片描述
把日志写入指定的文件

[root@localhost bin]# ./redis-sentinel ./sentinel.conf >sent.log &
[1] 3373

启动redis服务后,程序会自动配置文件sentinel.conf,并生成内容,注意:若再起启动需要删除下生成的内容。
哨兵启动方式

[root@localhost bin]# ./redis-server sentinel.conf --sentinel

在这里插入图片描述
再次查看进程可以看到哨兵进程被启动了:
在这里插入图片描述
把 6379 主机这个进程杀死:
在这里插入图片描述
观看现在主机是谁:
在这里插入图片描述
主机变成了6381.再查看6381的信息,原来的主机是6379,现在自己变成了主机
在这里插入图片描述
总结:
主从集群:主机有写入权限。从机没有,只有可读。
意外宕机方案:
手动恢复:人为重启服务器,主机宕,把从机设置为主机。
自动恢复:使用哨兵监控。自动切换主从。

13 Redis 集群

13.1 redis-cluster架构图

在这里插入图片描述
架构细节:
(1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.
(2)节点的fail是通过集群中超过半数的节点检测有效时整个集群才生效.
(3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
(4)redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value
Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点

在这里插入图片描述

13.2 redis-cluster投票:容错

在这里插入图片描述
心跳机制
(1)集群中所有master参与投票,如果半数以上master节点与其中一个master节点通信超过(cluster-node-timeout),认为该master节点挂掉.
(2):什么时候整个集群不可用(cluster_state:fail)?
Ø 如果集群任意master挂掉,且当前master没有slave,则集群进入fail状态。也可以理解成集群的[0-16383]slot映射不完全时进入fail状态。
Ø 如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态。

13.3 集群搭建步骤

第一步:安装redis
第二步:创建集群目录

[root@localhost redis]# mkdir redis-cluster

第三步:在集群目录下创建节点目录
在这里插入图片描述
搭建集群最少也得需要3台主机,如果每台主机再配置一台从机的话,则最少需要6台机器。 设计端口如下:创建6个redis实例,需要端口号7001~7006

[root@localhost myapps]# cp redis/ redis-cluster/7001 -r
[root@localhost myapps]# cd redis-cluster/7001
[root@localhost 7001]# ll
drwxr-xr-x. 2 root root 4096 71 10:22 bin
-rw-r--r--. 1 root root 3446 71 10:22 dump.rdb
-rw-r--r--. 1 root root 41404 71 10:22 redis.conf

第四步:如果存在持久化文件,则删除

[root@localhost 7001]# rm -rf appendonly.aof dump.rdb

第五步:修改redis.conf配置文件,打开Cluster-enable yes
说明:cluster-enable 是否支持集群
在这里插入图片描述
第六步:修改端口
在这里插入图片描述
第七步:复制出7002-7006机器

[root@localhost redis-cluster]# cp 7001/ 7002 -r
[root@localhost redis-cluster]# cp 7001/ 7003 -r
[root@localhost redis-cluster]# cp 7001/ 7004 -r
[root@localhost redis-cluster]# cp 7001/ 7005 -r
[root@localhost redis-cluster]# cp 7001/ 7006 -r
[root@localhost redis-cluster]# ll
total 28
drwxr-xr-x. 3 root root 4096 Jun 2 00:02 7001
drwxr-xr-x. 3 root root 4096 Jun 2 00:02 7002
drwxr-xr-x. 3 root root 4096 Jun 2 00:02 7003
drwxr-xr-x. 3 root root 4096 Jun 2 00:03 7004
drwxr-xr-x. 3 root root 4096 Jun 2 00:03 7005
drwxr-xr-x. 3 root root 4096 Jun 2 00:03 7006
-rwxr-xr-x. 1 root root 3600 Jun 1 23:52 redis-trib.rb

第八步:修改7002-7006机器的端口
第九步:启动7001-7006这六台机器,写一个启动脚本:自定义shel脚本

[root@localhost redis-cluster]# vi startall.sh

内容:

cd 7001
./bin/redis-server ./redis.conf
cd ..
cd 7002
./bin/redis-server ./redis.conf
cd ..
cd 7003
./bin/redis-server ./redis.conf
cd ..
cd 7004
./bin/redis-server ./redis.conf
cd ..
cd 7005
./bin/redis-server ./redis.conf
cd ..
cd 7006
./bin/redis-server ./redis.conf
cd ..

第十步:修改start-all.sh文件的权限

[root@localhost redis-cluster]# chmod u+x startall.sh

第十一步:启动所有的实例

[root@localhost redis-cluster]# ./startall.sh

第十二步:创建集群(关闭防火墙)
注意:在任意一台上运行 不要在每台机器上都运行,一台就够了 redis 5.0.5中使用redis-cli --cluster替代redis-trib.rb,命令如下

redis-cli --cluster create ip:port ip:port --cluster-replicas 1
[root@localhost redis_cluster]# cd /home/admin/myapps/redis-cluster/7001/bin
[root@localhost bin]# ./redis-cli --cluster create 192.168.197.132:7001 192.168.197.132:7002
192.168.197.132:7003 192.168.197.132:7004 192.168.197.132:7005 192.168.197.132:7006 --clusterreplicas 1
\>>> Creating cluster
Connecting to node 127.0.0.1:7001: OK
Connecting to node 127.0.0.1:7002: OK
Connecting to node 127.0.0.1:7003: OK
Connecting to node 127.0.0.1:7004: OK
Connecting to node 127.0.0.1:7005: OK
Connecting to node 127.0.0.1:7006: OK
\>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
127.0.0.1:7001
127.0.0.1:7002
127.0.0.1:7003
Adding replica 127.0.0.1:7004 to 127.0.0.1:7001
Adding replica 127.0.0.1:7005 to 127.0.0.1:7002
Adding replica 127.0.0.1:7006 to 127.0.0.1:7003
[OK] All 16384 slots covered.

13.4 连接集群

[root@localhost 7001]# ./bin/redis-cli -h 127.0.0.1 -p 7001 -c

-c:指定是集群连接

[root@localhost 7001]# ./bin/redis-cli -h 127.0.0.1 -p 7001 -c
127.0.0.1:7001> set username java123
-> Redirected to slot [14315] located at 127.0.0.1:7003
OK

关闭防火墙:service iptables stop
查看防火墙状态:service iptables status

13.5 查看集群消息

127.0.0.1:7003> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:3
cluster_stats_messages_sent:1186
cluster_stats_messages_received:1186

13.6 查看集群中节点信息

127.0.0.1:7003> cluster nodes
713218b88321e5067fd8ad25c3bf7db88c878ccf 127.0.0.1:7003 myself,master - 0 0 3 connected 10923-
16383
e7fb45e74f828b53ccd8b335f3ed587aa115b903 127.0.0.1:7001 master - 0 1498877677276 1 connected 0-
5460
b1183545245b3a710a95d669d7bbcbb5e09896a0 127.0.0.1:7006 slave
713218b88321e5067fd8ad25c3bf7db88c878ccf 0 1498877679294 3 connected
8879c2ed9c141de70cb7d5fcb7d690ed8a200792 127.0.0.1:7005 slave
4a312b6fc90bfee187d43588ead99d83b407c892 0 1498877678285 5 connected
4a312b6fc90bfee187d43588ead99d83b407c892 127.0.0.1:7002 master - 0 1498877674248 2 connected
5461-10922
4f8c7455574e2f0aab1e2bb341eae319ac065039 127.0.0.1:7004 slave
e7fb45e74f828b53ccd8b335f3ed587aa115b903 0 1498877680308 4 connected

13.7 Jedis连接集群

13.7.1 关闭防火墙
注意:如果redis重启,需要将redis中生成的dump.rdb和nodes.conf文件删除,然后再重启。
13.7.2 代码实现

<dependency>
	<groupId>redis.clients</groupId>
	<artifactId>jedis</artifactId>
	<version>2.9.0</version>
</dependency>	

注意jedis的版本,其他版本有可能报错:java.lang.NumberFormatException: For input string: “7002@17002”

public static void main(String[] args) throws IOException {
    
    
// 创建一连接,JedisCluster对象,在系统中是单例存在
Set<HostAndPort> nodes = new HashSet<HostAndPort>();
nodes.add(new HostAndPort("192.168.197.132", 7001));
nodes.add(new HostAndPort("192.168.197.132", 7002));
nodes.add(new HostAndPort("192.168.197.132", 7003));
nodes.add(new HostAndPort("192.168.197.132", 7004));
nodes.add(new HostAndPort("192.168.197.132", 7005));
nodes.add(new HostAndPort("192.168.197.132", 7006));
JedisCluster cluster = new JedisCluster(nodes);
// 执行JedisCluster对象中的方法,方法和redis指令一一对应。
cluster.set("test1", "test111");
String result = cluster.get("test1");
System.out.println(result);
//存储List数据到列表中
cluster.lpush("site-list", "java");
cluster.lpush("site-list", "c");
cluster.lpush("site-list", "mysql");
// 获取存储的数据并输出
List<String> list = cluster.lrange("site-list", 0 ,2);
for(int i=0; i<list.size(); i++) {
    
    
System.out.println("列表项为: "+list.get(i));
}
// 程序结束时需要关闭JedisCluster对象
cluster.close();
System.out.println("集群测试成功!");
}

14 Redis缓存

14.1 缓存的概念

什么是缓存?
广义的缓存就是在第一次加载某些可能会复用数据的时候,在加载数据的同时,将数据放到一个指定的地点做保存。再下次加载的时候,从这个指定地点去取数据。这里加缓存是有一个前提的,就是从这个地方取数据,比从数据源取数据要快的多。
java狭义一些的缓存,主要是指三大类

  1. 虚拟机缓存(ehcache,JBoss Cache)
  2. 分布式缓存(redis,memcache)
  3. 数据库缓存
    正常来说,速度由上到下依次减慢
    在这里插入图片描述

14.2 缓存雪崩

缓存雪崩产生的原因
缓存雪崩通俗简单的理解就是:由于原有缓存失效(或者数据未加载到缓存中),新缓存未到期间(缓存正常从Redis中获取,如下图)所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机,造成系统的崩溃。
在这里插入图片描述
缓存失效的时候如下图:
在这里插入图片描述
解决方案:
1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。虽然能够在一定的程度上缓解了数据库的压力但是与此同时又降低了系统的吞吐量。
注意:加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。假设在高并发下,缓存重建期间key是锁着的,这是过来1000个请求999个都在阻塞的。同样会导致用户等待超时,这是个治标不治本的方法。

2: 分析用户的行为,不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

14.3 缓存穿透

缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。
解决方案:
1.如果查询数据库也为空,直接设置一个默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。
2.把空结果,也给缓存起来,这样下次同样的请求就可以直接返回空了,既可以避免当查询的值为空时引起的缓存穿透。同时也可以单独设置个缓存区域存储空值,对要查询的key进行预先校验,然后再放行给后面的正常缓存处理逻辑。
注意:再给对应的ip存放真值的时候,需要先清除对应的之前的空缓存。

14.3 缓存击穿

对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。
热点key:某个key访问非常频繁,当key失效的时候有大量线程来构建缓存,导致负载增加,系统崩溃。
解决办法:
①使用锁,单机用synchronized,lock等,分布式用分布式锁。
②缓存过期时间不设置,而是设置在key对应的value里。如果检测到存的时间超过过期时间则异步更新缓存。

15 分布式锁

15.1 使用分布式锁要满足的几个条件:

  1. 系统是一个分布式系统(关键是分布式,单机的可以使用ReentrantLock或者synchronized代码块来实现)
  2. 共享资源(各个系统访问同一个资源,资源的载体可能是传统关系型数据库或者NoSQL)
  3. 同步访问(即有很多个进程同时访问同一个共享资源。)

15.2 什么是分布式锁?

线程锁:主要用来给方法、代码块加锁。当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段。线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如synchronized是共享对象头,显示锁Lock是共享某个变量(state)。
进程锁:为了控制同一操作系统中多个进程访问某个共享资源,因为进程具有独立性,各个进程无法访问其他进程
的资源,因此无法通过synchronized等线程锁实现进程锁。
分布式锁:当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。

15.3 应用的场景

线程间并发问题和进程间并发问题都是可以通过分布式锁解决的,但是强烈不建议这样做!因为采用分布式锁解决这些小问题是非常消耗资源的!分布式锁应该用来解决分布式情况下的多进程并发问题才是最合适的。有这样一个情境,线程A和线程B都共享某个变量X。如果是单机情况下(单JVM),线程之间共享内存,只要使用线程锁就可以解决并发问题。如果是分布式情况下(多JVM),线程A和线程B很可能不是在同一JVM中,这样线程锁就无法起到作用了,这时候就要用到分布式锁来解决。
分布式锁可以基于很多种方式实现,比如zookeeper、redis…。不管哪种方式,他的基本原理是不变的:用一个状态值表示锁,对锁的占用和释放通过状态值来标识。
这里主要讲如何用redis实现分布式锁。

15.4 使用redis的setNX命令实现分布式锁

1 实现的原理
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。redis的SETNX命令可以方便的实现分布式锁。

2、基本命令解析
1)setNX(SET if Not eXists)
语法:
SETNX key value
将 key 的值设为 value ,当且仅当 key 不存在。
若给定的 key 已经存在,则 SETNX 不做任何动作。
SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写

返回值:
设置成功,返回 1 。
设置失败,返回 0 。
例子:

redis> EXISTS job # job 不存在
(integer) 0
redis> SETNX job "programmer" # job 设置成功
(integer) 1
redis> SETNX job "code-farmer" # 尝试覆盖 job ,失败
(integer) 0
redis> GET job # 没有被覆盖
"programmer"

所以我们使用执行下面的命令SETNX可以用作加锁原语(locking primitive)。比如说,要对关键字(key) foo 加锁,
客户端可以尝试以下方式:

SETNX lock.foo <current Unix time + lock timeout + 1>

如果 SETNX返回 1 ,说明客户端已经获得了锁,SETNX将键 lock.foo 的值设置为锁的超时时间(当前时间 + 锁的有效时间)。 之后客户端可以通过 DEL lock.foo 来释放锁。
如果 SETNX返回 0 ,说明 key 已经被其他客户端上锁了。如果锁是非阻塞(non blocking lock)的,我们可以选择返回调用,或者进入一个重试循环,直到成功获得锁或重试超时(timeout)。
2)getSET
先获取key对应的value值。若不存在则返回nil,然后将旧的value更新为新的value。
语法:

GETSET key value

将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
当 key 存在但不是字符串类型时,返回一个错误。
返回值:
返回给定 key 的旧值[之前的值]。
当 key 没有旧值时,也即是, key 不存在时,返回 nil 。

注意的关键点:
1、同一时刻只能有一个进程获取到锁。setnx
2、释放锁:锁信息必须是会过期超时的,不能让一个线程长期占有一个锁而导致死锁;
(最简单的方式就是del, 如果在删除之前死锁了。)
在这里插入图片描述
设置53秒–58秒到期
当前时间为56秒,没有过期
当前时间为59秒,过期.(当前时间大于设置的时间)
死锁情况是在判断超时后,直接操作业务,设置过期时间,执行业务,然后删除释放锁。其他进程再次通过setnx来抢锁。
解决死锁:
上面的锁定逻辑有一个问题:如果一个持有锁的客户端失败或崩溃了不能释放锁,该怎么解决?
我们可以通过锁的键对应的时间戳来判断这种情况是否发生了,如果当前的时间已经大于lock.foo的值,说明该锁已失效,可以被重新使用。
发生这种情况时,可不能简单的通过DEL来删除锁,然后再SETNX一次(讲道理,删除锁的操作应该是锁拥有者执行的,这里只需要等它超时即可),当多个客户端检测到锁超时后都会尝试去释放它,这里就可能出现一个竞态条件,让我们模拟一下这个场景:
C0操作超时了,但它还持有着锁,C1和C2读取lock.foo检查时间戳,先后发现超时了。 C1 发送DEL lock.foo C1发送SETNX lock.foo 并且成功了。 C2 发送DEL lock.foo C2 发送SETNX lock.foo 并且成功了。 这样一来,C1,C2都拿到了锁!问题大了!

幸好这种问题是可以避免的,让我们来看看C3这个客户端是怎样做的:
C3发送SETNX lock.foo 想要获得锁,由于C0还持有锁,所以Redis返回给C3一个0 C3发送GET lock.foo 以检查锁是否超时了,如果没超时,则等待或重试。 反之,如果已超时,C3通过下面的操作来尝试获得锁: GETSET lock.foo 通过GETSET,C3拿到的时间戳如果仍然是超时的,那就说明,C3如愿以偿拿到锁了。 如果在C3之前,有个叫C4的客户端比C3快一步执行了上面的操作,那么C3拿到的时间戳是个未超时的值,这时,C3没有如期获得锁,需要再次等待或重试。留意一下,尽管C3没拿到锁,但它改写了C4设置的锁的超时值,不过这一点非常微小的误差带来的影响可以忽略不计。
注意:为了让分布式锁的算法更稳键些,持有锁的客户端在解锁之前应该再检查一次自己的锁是否已经超时,再去做DEL操作,因为可能客户端因为某个耗时的操作而挂起,操作完的时候锁因为超时已经被别人获得,这时就不必解锁了。

MongoDB

MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。
MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。
说明:BSON是一种计算机数据交换格式,主要被用作MongoDB数据库中的数据存储和网络传输格式。它是一种二进制表示形式,能用来表示简单数据结构、关联数组(MongoDB中称为“对象”或“文档”)以及MongoDB中的各种数据类型。BSON之名缘于JSON,含义为Binary JSON(二进制JSON)。

1 特点

(1) 面向集合存储,易存储对象类型的数据
(2) 支持动态查询
(3) 支持完全索引,包含内部对象
(4) 支持复制和故障恢复
(5) 支持多种开发语言
(6) 使用高效的二进制数据存储,包括大型对象(如视频等)

2 适用场景

1)网站实时数据处理。它非常适合实时的插入、更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性。
2)缓存。由于性能很高,它适合作为信息基础设施的缓存层。在系统重启之后,由它搭建的持久化缓存层可以避免下层的数据源过载。
3)高伸缩性的场景。非常适合由数十或数百台服务器组成的数据库,它的路线图中已经包含对MapReduce引擎的内置支持。

3 不适用的场景如下

1)要求高度事务性的系统。
2)传统的商业智能应用。
3)复杂的跨文档(表)级联查询。

4 与mysql的对比

在这里插入图片描述

5 安装MongoDB

5.1 Windows下安装

第一步:下载压缩包
地址:https://www.mongodb.com/download-center/community
在这里插入图片描述
第二步:双击.msi文件,按照提示安装即可
在这里插入图片描述
第三步:创建一个文件夹用来存储数据信息,这个数据目录不会主动创建,我们在安装完成后需要创建它(如:F:\pro\db\data等)
第四步:命令行下运行MongoDB服务器
为了从命令提示符下运行 MongoDB 服务器,你必须从 MongoDB 目录的 bin 目录中执行
mongod.exe 文件。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.2 Liunx下安装mongoDB

第一步 重复上面下载步骤:在这里插入图片描述
第二步:上传到linux,并解压

[root@localhost local]# tar -zxvf mongodb-linux-x86_64-4.0.19-rc0.tgz

第三步:移动文件到自定义目录

[root@localhost local]# mv mongodb-linux-x86_64-4.0.19-rc0
/home/admin/myapps/mongodb
[root@localhost local]# cd /home/admin/myapps/mongodb/
[root@localhost mongodb]# ll
total 200
drwxr-xr-x. 2 root root 4096 Jun 3 00:10 bin
-rw-r--r--. 1 root root 30608 Jun 2 15:01 LICENSE-Community.txt
-rw-r--r--. 1 root root 16726 Jun 2 15:01 MPL-2
-rw-r--r--. 1 root root 2601 Jun 2 15:01 README
-rw-r--r--. 1 root root 60005 Jun 2 15:01 THIRD-PARTY-NOTICES
-rw-r--r--. 1 root root 81355 Jun 2 15:02 THIRD-PARTY-NOTICES.gotools

第四步:系统profile配置

[root@localhost mongodb]# vi /etc/profile

编辑代码:

export mongodb_home=/home/admin/myapps/mongodb //mongodb的安装目录
export PATH=$PATH:$mongodb_home/bin

保存后,重启系统配置

[root@localhost mongodb]# source /etc/profile

第五步:创建数据库目录

[root@localhost myapps]# pwd
/home/admin/myapps
[root@localhost myapps]# mkdir mongodbdata //存放数据库的目录

第六步:创建日志文件和配置文件

[root@localhost myapps]# mkdir logs
[root@localhost myapps]# cd logs
[root@localhost logs]# touch mongodb.log
[root@localhost logs]# cd ..
[root@localhost myapps]# cd mongodb
[root@localhost mongodb]# cd bin
[root@localhost bin]# vi mongodb.conf //配置文件存放在bin目录下

mongodb.conf配置内容:

dbpath = /home/admin/myapps/mongodbdata #数据文件存放目录
logpath = /home/admin/myapps/logs/mongodb.log #日志文件存放目录
port = 27017 #端口
fork = true #以守护程序的方式启用,即在后台运行

第七步:启动mongodb服务端

[root@localhost bin]# ./mongod -f mongodb.conf
about to fork child process, waiting until server is ready for connections.
forked process: 2962
child process started successfully, parent exiting

第八步 运行客户端

[root@localhost bin]# ./mongo

6 mongodb支持的数据类型

6.1 null
null用于表示空值或不存在的字段。示例如下:
{“x” : null}
6.2 布尔类型
布尔型数据有true和false两个值。示例如下:
{“x” : true}
6.3 数值类型
在Mongo shell中,默认使用64位浮点型数据。因此,会有以下两种数值形式:
{“x” : 2.32}//或{“x” : 2}
对于整数类型,可以使用NumberInt()(位有符号整型)或NumberLong()(8位有符号整型)方法进行转换。示例如下:
{“x” : NumberInt(2)}
{“x” : NumberLong(2)}

6.4 字符串
MongoDB中字符串类型使用UTF-8编码的字符表示。示例如下:
{“x” : “[email protected]”}
6.5 日期类型
MongoDB中日期使用时间戳表示,单位为毫秒,不存储时区。示例如下:

{“x” : new Date()}
创建日期对象时应该使用new Date(),而非构造函数Date()。将构造函数作为函数时返回的日期格式是字符串,而非日期对象(与JavaScript工作机制有关)。

6.6 正则表达式
MongoDB中可使用与JavaScript相同的正则表达式进行查询筛选等。示例如下:
{“x” : /kba/i}
6.7 数组
数据集可以用数组格式存储,与JavaSript中的数组表示相同。示例如下:
{“x” : [“kaikeba”, “kaikeba.com”]}

6.8 内嵌文档
文档中可以嵌套一个子文档。在MongoDB文档总大小限制为16MB,建议使用子文档的形式组织数据,子文档查询效率要高于多键查询。示例如下:
{“x” : {“kaeba” : “kaiba.com”}}
文档可以做为键的值,即:内嵌文档。MongoDB与关系型数据库相比,最大的优势就是内嵌文档。与关系型数据库的扁平化数据结构相比,使用内嵌文档可以数据的组织方式更加自然。

6.9 _id和ObjectId
MongoDB中每个文档都有一个"id"键,“id"可以是任何类型,不指”_id"时MongoDB会生成一个ObjectId对象。。示例如下:
{"_id" : ObjectId()}
ObjectId是一个12字节(24个十六进制数字)的存储空间,ObjectId的12字节数据组织方式如下:
在这里插入图片描述
{"_id" : ObjectId(“5444cce6aef53b0f343e2b9b”)}
/
上面ObjectId各位字符含义如下 /
//5444cce6,第0〜3字节(第1〜8位)为时间戳
//aef53b,第4〜6字节(第9〜14位)为机器码
//0f34,第7〜8字节(第15〜18位)为进程ID
//3e2b9b,第9〜11字节(第19〜24位)为自增计数器

6.10 代码
MongoDB的文档和代码中可以包括JavaScript代码。示例如下:

{
    
    "x" : function(){
    
     /*这里是一段JavaScript代码*/}}

6.11 二进制数据
二进制数据是一个二进制字节的字作串,要保存非UTF-8字符到数据库中,只能使用十进制数据。

7 MongoDB的常用指令

在这里插入图片描述
查看数据库(数据库中至少有一条数据,此时的数据库才会显示出来)

>show dbs

切换数据库

use 数据库名 //这个指令也可以直接创建数据库,但只有添加数据后,
show dbs才能看到该数据库

查看当前数据库,默认数据库:test

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()

条件查询-find()以非结构化的方式展示文档

db.collectionName.find({“age”:26}) //查询等值关系
db.collectionName.find({age : { KaTeX parse error: Expected 'EOF', got '}' at position 9: gt : 100}̲}) // 大于100 db.…gte : 100}}) //大于等于100
db.collectionName.find({age : { KaTeX parse error: Expected 'EOF', got '}' at position 9: lt : 150}̲}) //小于150 db.c…lte : 150}}) //小于等于150
db.collectionName.find({age : {$lt :200, $gt : 100}}) //大于100,小于200

如果你需要以易读的方式来读取数据,可以使用 pretty() 方法,语法格式如下

db.collectionName.find().pretty()

在这里插入图片描述
and 关系
MongoDB 的 find() 方法可以传入多个键(key),每个键(key)以逗号隔开,即常规 SQL 的 AND 条件
语法:

db.collectionName.find({key1:value1, key2:value2}).pretty()

db.collectionName.find({“by”:“java”, “title”:“MongoDB学习”}).pretty()

or关系
MongoDB OR 条件语句使用了关键字 $or,语法格式如下:
语法:

db.collectionName.find({ KaTeX parse error: Expected 'EOF', got '}' at position 37: … {key2:value2}]}̲).pretty() db.c…or:[{“by”:“java”},{“title”: “MongoDB学习”}]}).pretty()

清空集合数据:

db.collectionName.remove({}) //条件删除:remove({key:value})
//删除满足条件的一条数据:remove({key:value},1)

查询一条数据

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().count()

查询限定条数

db.collectionName.find().limit(number)

使用limit()方法来读取指定数量的数据外,还可以使用skip()方法来跳过指定数量的数据,skip方法同样
接受一个数字参数作为跳过的记录条数。

db.collectionName.find().limit(NUMBER).skip(NUMBER)

更新数据

db.collectionName.update(<query>,<update>,{
    
    upsert: <boolean>, multi: <boolean>)

在这里插入图片描述

db.collectionName.update({name:‘tom’},{$set:{age:23}},false,true)

8 java 调取MongoDB

第一步:添加mongodb-java-driver驱动包

<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.197.133", 27017);
// 2.连接到数据库
MongoDatabase mongoDatabase = mongoClient.getDatabase("mydb1");
//3.创建集合
// mongoDatabase.createCollection("student");
//获得集合
MongoCollection<Document> collection =
mongoDatabase.getCollection("users");
//4.新增
Document document = new Document("stu1name", "张三");
document.append("stu1age", 29);
document.append("sex", "男");
collection.insertOne(document);
//插入多行时,将document对象添加到List集合中,调取insertMany()
//5.获得所有文档
FindIterable<Document> documents = collection.find();
MongoCursor<Document> iterator = documents.iterator();
while (iterator.hasNext()) {
    
    
System.out.println(iterator.next());
}
//6.更新文档updateOne(),updateMany(条件表达式,新值表达式)
// collection.updateMany(Filters.eq("sex","男"),new Document("$set",new
Document("name","testzhangsan")));
//7.删除文档
//删除20岁用户的信息
collection.deleteOne(Filters.eq("age",20));
}

9 MongoDB的索引

索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。
索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构。

9.1 创建索引的语法:

db.collection.createIndex(keys, options)

options的取值含义:
在这里插入图片描述
在这里插入图片描述

9.2 索引的分类

(1) 默认索引
MongoDB有个默认的“id”的键,相当于“主键”的角色。集合创建后系统会自动创建一个索引在“id”键上,它是默认索引,索引名叫“id”,是无法被删除的。我们可以通过以下方式查看:

>db.collectionName.getIndexes()

(2) 单列索引
在单个键上创建的索引就是单列索引,例如我们要在Users集合上给title键创建一个单列索引,语法如下: (1表示正序,-1逆序)

db.collectionName.createIndex({“title”:1})

(3) 组合索引
另外,我们还可以同时对多个键创建组合索引。如下代码创建了按照“UserId”正序,“UserName”逆序的组合索引:

db.collectionName.createIndex({“userid”:1,“username”:-1})

唯一索引限制了对当前键添加值时,不能添加重复的信息。值得注意的是,当文档不存在指定键时,会被认为键值是“null”,所以“null”也会被认为是重复的,所以一般被作为唯一索引的键,最好都要有键值对。
对“UserId”创建唯一索引(这时候最后一个参数为“true”):

>db.collectionName.CreateIndex({
    
    "UserId":1}, {
    
     unique: true });

(5)TTL索引
TTL指生命周期的意思。即存储的document存储带有过期时间属性,超过生命周期自己主动删除。像日志数据、系统自己主动产生的暂时数据、会话数据等均符合这一场景。
构建方式如:
db.log_events.createIndex( { “createdAt”: 1 }, { expireAfterSeconds: 3600 })

(6) 删除索引
新手常陷入的误区是,认为集合被删除,索引就不存在了。关系型数据库中,表被删除了,索引也不会存在。在MongoDB中不存在删除集合的说法,就算集合数据清空,索引都是还在的,要移除索引还需要手工删除。

db.collectionName.dropIndexes()

删除集合指定索引

db.collectionName.dropIndex(“索引名称”)

说明:drop()集合时,索引也会删除,remove()集合时,索引仍然存在

> db.users.remove({
    
    })
WriteResult({
    
     "nRemoved" : 3 })
> db.users.getIndexes()
[
{
    
    
	"v" : 2,
	"key" : {
    
    
	"_id" : 1
	},
	"name" : "_id_",
	"ns" : "mydb1.users"
}
]
> db.users.drop()
true
> db.users.getIndexes()
[ ]

10 MongoDB 的备份与恢复

10.1 mongodump命令来备份数据

该命令可以导出所有数据到指定目录中。
mongodump命令可以通过参数指定导出的数据量级转存的服务器。
语法

mongodump -h dbhost -d dbname -o dbdirectory

-h:
MongDB所在服务器地址,例如:127.0.0.1,当然也可以指定端口号:127.0.0.1:27017
-d:
需要备份的数据库实例,例如:test
-o:备份的数据存放位置,例如:c:\data\dump,当然该目录需要提前建立,在备份完成后,系统自动在dump目录下建立一个test目录,这个目录里面存放该数据库实例的备份数据。
示例
在本地使用 27017 启动你的mongod服务。打开命令提示符窗口,进入MongoDB安装目录的bin目录
输入命令mongodump:
在这里插入图片描述
执行以上命令后,客户端会连接到ip为 127.0.0.1 端口号为 27017 的MongoDB服务上,并备份所有数据到 bin/dump/ 目录中。命令输出结果如下:
在这里插入图片描述
mongodump 命令可选参数列表如下所示:
在这里插入图片描述

10.2 MongoDB的数据恢复

在这里插入图片描述
在这里插入图片描述

11 集群搭建

集群搭建方式之一就是mongoDB复制集,即一组mongod的进程。他们维护同一个数据集合。复制集保证了数据的可靠性和高读取能力。

11.1 机制

一组复制集就是一组mongod实例管理同一个数据集,实例key在不同的机器上,实例包含主实例(primary),接受所有的写操作,其他的属于副本实例(Secondary),从服务器保持与主服务器数据同步,类似于redis中的主从复制。
每个复制集还有一个仲裁者(Arbiter),仲裁者的任务就是通过心跳机制来确认集群中集合的数量,并在选举主服务器的过程中进行裁决。仲裁者并不存储数据,性质等价于redis中的哨兵机制。

11.2 架构

在数据承载节点中,一个且只有一个成员被视为主节点,而其他节点则被视为辅助节点。节点接收所有写入操作,一个副本集只能有一个主实例能够写入,主节点记录所有变更到它的记录
在这里插入图片描述
在这里插入图片描述
也可以将一个额外的实例作为仲裁者添加到副本集。仲裁员不维护数据集,仲裁器的目的是通过响应其他副本集成员的心跳和选择请求来维护副本集中的仲裁。因为它们不存储数据集,所以仲裁器是提供副本集仲裁功能的一种好方法。
与具有数据集的完全功能副本集成员相比,仲裁器的资源成本更低,如果副本集的成员数为偶数,则添加一个仲裁器以在初选中获得多数票。
当一个主服务器在超过配置的周期(默认为 10 秒)内未与该组的其他成员通信时,符合条件的辅助服务器将要求选择将其自身指定为新的主服务器。集群试图完成新的初选并恢复正常操作。
在这里插入图片描述

11.3 搭建步骤

(1)准备三台虚拟机服务器,并各自安装好mongoDB
注:为了保证复制集中三个服务器之间正常连接,请保证三个服务器的防火墙都已关闭!

在这里插入图片描述
(2)修改mongodb.conf文件,添加replSet配置(三台都需要修改成同一个名称),然后启动服务器
在这里插入图片描述
(3)初始化复制集
登录任意一台执行初始化操作

rs.initiate({
    
    _id:'rep1',members:[{
    
    _id:1,host:'192.168.197.132:27017'},
{
    
    _id:2,host:'192.168.197.133:27017'},{
    
    _id:3,host:'192.168.197.134:27017'}]})

说明: _id 指复制集名称,members指复制集服务器列表,数组中的_id是服务器唯一的id,host服务器主机ip
(4)查看集群的状态

rs.status()

(5)测试

#添加数据 db.users.insert({“name”:“lisi”,“age”:11})
#查询数据 db.users.find()
#切换到从数据库查询数据 如果不允许查询,是因为默认情况下从数据库是不允许读写操作的,需要设置。

rs.slaveOK() 执行该命令后可以查询数据

(6)测试复制集主从节点故障转移功能
#关闭主数据库,注意从数据库的变化

db.shutdownServer()

(7) 主复制集添加仲裁者(arbiter)
现在我们的环境是一主两从,仲裁者对偶数集群有效。需要停止一个从机,在主服务器中运行下面命令
rs.remove(“ip:端口号”) //删除从节点

在一主一从关系中,任意节点宕机都无法选举出主节点,无法提供写操作,此时需要加入仲裁者节点即可。
rs.addArb("ip:端口号"

以上就是关于我们在实践中能用到的数据库的一个概述;
下面这篇文章是一篇数据库的面试题,读者可以自行去了解:https://blog.csdn.net/weixin_45692705/article/details/119343669

后续我会在公众号发布一些精心整理的面试题,请大家关注公众号(小小李童鞋)获取
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/delete_bug/article/details/119342721
今日推荐