数据库 – Wasting_Misaka.Blog https://forelink.top Hso! Mon, 09 Sep 2024 15:36:54 +0000 zh-Hans hourly 1 https://wordpress.org/?v=6.7.1 SQL触发器 https://forelink.top/index.php/2024/09/07/%e8%a7%a6%e5%8f%91%e5%99%a8/ Sat, 07 Sep 2024 15:17:49 +0000 https://forelink.top/?p=509 简介:

触发器是与表有关的数据库对象,在 insert/update/delete 之前或者之后,触发并执行触发器中定义的SQL语句集合。

触发器适合在数据库端确保数据的完整性日志记录数据校验 现在的触发器还只支持行级触发,不支持语句触发。

创建查看删除:

-- 创建 --
CREATE TRIGGER [trigger_name]
BEFORE/AFTER INSERT/UPDATE/DELETE
ON [table_name] FOR EACH ROW -- 指定表并加入触发器
BEGIN
    ...
END;
-- 查看 --
SHOW TRIGGERS;
-- 删除 不指定数据库.名称,默认删除当前数据库下的 --
DROP TRIGGER [database_name.]trigger_name

示例:日志表

为hso表建立日志表hso_logs,并通过触发器记录该表的数据变更日志。

-- 建表语句 --
CREATE TABLE hso_logs(
    id int(11) not null auto_increment,
    operation varchar(20) not null comment '操作类型',
    operate_time datetime not null comment '操作时间',
    operate_id int(11) not null comment '操作的id',
    operate_params varchar(500) comment '操作参数',
    primary key(id)
)engine=innodb default charset=utf8;

-- insert触发器 --
create trigger hso_insert_trigger
    after insert on hso for each row
begin
    insert into hso_logs(id,
                         operation,
                         operate_time,
                         operate_id,
                         operate_params) 
                       values(null,'insert',now(),new.id,concat(
    '插入的数据内容为id=',new.id,',var1=',new.var1
    ))     
end;


-- update触发器 --
create trigger hso_insert_trigger
    after update on hso for each row
begin
    insert into hso_logs(id,
                         operation,
                         operate_time,
                         operate_id,
                         operate_params) 
                       values(null,'update',now(),new.id,concat(
                           '更新之前的数据:id=',old.id,',var1=',old.var1,
    ' | 更新的数据内容为id=',new.id,',var1=',new.var1
    ))
end;

-- delete触发器 --
create trigger hso_insert_trigger
    after delete on hso for each row
begin
    insert into hso_logs(id,
                         operation,
                         operate_time,
                         operate_id,
                         operate_params) 
                       values(null,'delete',now(),old.id,concat(
    '删除的数据内容为id=',new.id,',var1=',new.var1
    ))
    -- 删除前的ID --
end;
]]>
SQL存储过程 https://forelink.top/index.php/2024/09/07/sql%e5%ad%98%e5%82%a8%e8%bf%87%e7%a8%8b/ Sat, 07 Sep 2024 14:20:29 +0000 https://forelink.top/?p=505 简介:

在一个业务逻辑中,可能需要查询多次数据库(多次网络请求) 可以将多条SQL语句封装在一个集合当中,调用业务对应的SQL集合。

存储过程是事先经过编译并存储在数据库中的一段SQL语句的集合,调用存储过程可以简化开发人员的重复工作,减少网络请求的数量,提升安全(更细粒度的权限控制),从而提高开发和数据处理的效率。

存储过程思想上,就是数据库SQL语言层面的代码封装与重用。

创建存储过程:

CREATE PROCEDURE [name]([参数列表])
begin
    --SQL语句;
end;

在以终端方式创建存储过程

-- 先将结束符修改成其他符号,以避免误解析SQL语句
DELIMITER //
CREATE PROCEDURE [name]([参数列表])
BEGIN
    --SQL语句;
END //
-- 定义结束,将结束符恢复成分号 ;
DELIMITER ;

调用存储过程:

CALL [name];

查看存储过程:

-- 查询指定数据库的存储过程以及状态信息
select * from information_schema.ROUTINES where ROUTINE_SCHEMA = '[database]'
SHOW CREATE PROCEDURE [name]; --查询某个存储过程的定义sql
image-20240907195550643

删除存储过程:

DROP PROCEDURE [if exists][name]; --删除存储过程

变量:

系统变量:

非用户定义,属于服务器层面。分为global(全局变量)和session(会话/局部变量)

查看设置系统变量:

SHOW [SESSION|GLOBAL] VARIABLES; -- 查询所有系统变量
SHOW [SESSION|GLOBAL] VARIABLES LIKE '...'; -- 查看指定系统变量
SELECT @@[SESSION|GLOBAL] [系统变量名]; -- @@代表的是系统变量
-- 设置系统变量 --
SET [SESSION|GLOBAL] [系统变量名] = ...;
set @@[SESSION.|GLOBAL.][系统变量名] = ...;

在重启后,全局变量会恢复成my.cnf中的配置。

用户自定义变量:

格式:@变量名 作用域为当前session

赋值和查看:

set @[var_name] = ...;
set @[var_name] := ...; -- 为了区分,建议使用此方式
select @[var_name] := ...;
SELECT ... into @[var_name] from [table_name];
-- 查看 --
select @[var_name] := 表达式;

局部变量:

用DECLARE声明,局部变量的范围是在其声明的BEGIN … END块。

声明赋值

-- INT/BIGINT/CHAR/VARCHAR/DATE/TIME等 default是默认值
DECLARE [变量名] [变量类型] [default ...]
-- 赋值
set [变量名] = ...;
set [变量名] := ...;
select ... into [变量名] from [table_name];

语法

参数:

参数分为三种类型

IN : 作为输入,需要调用时传入值(默认) OUT:作为输出,参数可以作为返回值 INOUT:既可以作为输入参数,也可以作为输出参数

create procedure [存储过程名称](IN/OUT/INOUT [变量名] [变量类型])

条件控制语句:

if判断:

语法:

IF 条件1 THEN ...
ELSEIF 条件2 THEN ...
...
ELSE ...
END IF;

例:

create procedure judging(in score int, out result varchar(4))
begin
    if score >= 85 then
        set result := 'A';
    elseif score>=60 then
        set result := 'B';
    else
        set result := 'C';
    END IF;
end;

使用:

call judging(59 , @result);

case:

语法

-- value == vx时 执行对应代码
CASE [value]
    WHEN [v1] THEN ...
    WHEN [v2] THEN ...
    ELSE ...
END CASE;
-- 表达式成立时
CASE
    WHEN [表达式1] THEN ...
    WHEN [表达式2] THEN ...
    ELSE ...
END CASE;

循环语句:

while:

在一开始就进行判断。

WHILE [表达式] DO
    ...
END WHILE;

repeat:

repeat在执行完逻辑,判断满足条件时,才退出循环(至少执行一次)

REPEAT
    ...
    UNTIL [表达式]
END REPEAT;

loop:

实现简单的循环,退出循环需要自行加入 LEAVE 或者 ITERATE 需要在首位指定loop的label。

LEAVE :退出循环(break) ITERATE :开始下一次循环(continue)

这两条语句需要加入循环的label来控制

语法:

[begin_label:] LOOP
    ...
    LEAVE [label]
    ITERATE [label]
END LOOP [end_label];

游标:

游标(CURSOR) 是用来存储查询结果集的数据类型,在存储过程和函数中可以使用游标对结果集进行循环的处理。游标的使用包括游标的声明、open、fetch和close

声明游标要在声明普通变量之后。否则会报错

语法:

-- 声明游标 --
DECLARE [cursor_name] CURSOR FOR [查询语句];
-- 打开游标 --
OPEN [cursor_name];
-- 获取游标记录 --
FETCH [cursor_name] INTO [变量1],[变量2];
-- 关闭游标 --
CLOSE [cursor_name];

条件处理程序:

条件处理程序(Handler)可以用来定义并自动执行在流程控制结构执行过程中遇到定义的问题时的相应处理步骤。

在出现对应SQL状态时,会自动执行。

语法:

DECLARE [handler_action] HANDLER FOR [condition_value] [sqlstate_value];

-- handler_action --
    CONTINUE: 继续执行当前程序
    EXIT: 终止执行当前程序
-- condition_value --
    SQLSTATE :状态码(sqlstate_value)
    SQLWARNING:所有以01开头的sqlstate代码简写
    NOT FOUND:所有以02开头的sqlstate代码简写
    SQLEXCEPTION:其他sqlstate代码简写

用来处理死循环:

declare exit handler for sqlstate '02000' close u_cursor;
while true do
    fetch u_cursor into uname,ujob;
end while;

可以在官方文档查找所有 sql 状态码的具体描述。

存储函数:

存储函数是有返回值的存储过程,存储函数的参数只能是IN类型的

语法:

CREATE FUNCTION [存储函数名称]([参数列表])
RETURNS [数据类型] [characteristic]
BEGIN
    ...SQL
    RETURN ...;
END;
-- 必须指定的函数特性 --
characteristic:
    DETERMINISTIC 相同的输入参数总是产生相同的结果
    NO SQL 不包含SQL语句
    READS SQL DATA 包含数据数据的语句,但不包含写入数据的语句
]]>
MySQL视图 https://forelink.top/index.php/2024/09/07/mysql%e8%a7%86%e5%9b%be/ Sat, 07 Sep 2024 11:33:01 +0000 https://forelink.top/?p=503 简介:

MySQL 5.0版本之后引入视图。

视图本身是一个虚拟表,不存放任何数据。使用SQL语句访问视图时,它返回的数据都是从其他表里生成的。视图和表是在同一个命名空间,MySQL对于二者大多是同样对待的。

不过也有不同,例如:不能对视图创建触发器,也不能使用DROP TABLE命令删除视图。

视图不仅可以简化用户对数据的理解,也可以简化他们的操作。 那些被经常使用的查询可以被定义为视图,从而使用户不必为之后的操作指定全部的条件。同时通过视图,用户只能查询和修改指定的数据。 且视图可帮助用户屏蔽真实表结构变化带来的影响。

创建视图:

CREATE [or replace] view [view_name] as [table];
image-20240906205548245

视图查询:

视图是一张虚拟存在的表。大多数表的操作也可以对视图执行。

image-20240906205759734

视图修改:

alter view [view_name] as [table];
image-20240906210443893

视图删除:

然而删除表和删除视图的SQL语句是不相同的

drop view [view_name];
image-20240906210521339

视图检查选项

视图和表有绑定关系,向视图中添加数据,同时也会向表中添加数据。 但是如果视图表有条件,添加的数据如果不满足条件,在视图中则不会显示。

当使用检查选项字句创建视图时,MySQL会通过视图检查正在更改的每个行。以使其符合视图的定义。MySQL同时允许基于另一个视图创建视图,为了确定检查的范围mysql提供了 CASCADED(默认) 和 LOCAL 两个选项。

cascaded

在视图创建提供了语法 with cascaded check option 则对该视图进行写操作时,会对当前视图和依赖的所有视图进行检查,只有数据合法时,才能添加成功。

local

MySQL8.0 加入。会检查当前视图和依赖的所有视图。只有视图存在检查选项时,才对视图中的条件进行检查,否则不检查。

视图更新:

条件:

视图可更新的条件,视图中的行和基础表的行之间必须存在一对一的关系。

包含以下任何一项时,视图是不可更新的 聚合函数或窗口函数 DISTINCT GROUP BY HAVING UNION

]]>
SQL优化 https://forelink.top/index.php/2024/09/06/sql%e4%bc%98%e5%8c%96/ Fri, 06 Sep 2024 12:40:07 +0000 https://forelink.top/?p=501 插入数据:

批量插入 手动提交事务 主键顺序插入性能高于乱序插入

大批量插入数据,可以使用MySQL数据库提供的load指令进行插入:

Load指令:

#在连接服务器时,加上参数 --local-infile 表示读取本地文件。
mysql --local-infile -u root -p
#设置全局参数local_infile为1,开启从本地加载文件导入的开关。
set global local_infile = 1;
#执行load指令将准备好的数据,加载到表结构中。
load data local infile 'path/to/file' into table [table_name] fields terminated by '[separator]' lines terminated by '\n';

用Load指令将本地文件导入数据库,速度会比insert语句快。

image-20240906161003603

主键优化:

数据组织方式 在InnoDB中,表数据是根据主键顺序存放,这种存储方式的表称为聚集索引,(索引组织表)。

InnoDB的逻辑存储结构为 Tablespace – Segment – Extent – Page – Row

页分裂

基于聚集索引的表插入新行,或主键更新导致需要移动行的时候,可能会出现“页分裂”问题。

当行的主键值要求必须将这一行插入到某个已满的页中时,存储引擎会将该页分裂成两个页面来容纳该行,这就是一次页分裂操作。页分裂操作会导致表占用更多的磁盘空间。

所以当主键写入时乱序时,InnoDB不得不频繁的做页分裂操作,以便为新的行分配空间。页分裂会导致大量移动数据,一次插入最少需要修改三个页而不是一个页。

页合并

当删除一行记录时,实际上该记录只是被标记为删除,并且它的空间变得允许被其他记录声明使用。 当页中删除的记录达到 MERGE_THRESHOLD(默认为页的50%),InnoDB会开始寻找最靠近的页,查看是否可以合并优化空间利用。

主键设计原则

满足业务需求的情况下,尽量降低主键的长度。 插入数据时,尽量选择顺序插入,选择 AUTO_INCREMENT 自增主键 尽量不要使用UUID做主键或者是其他自然主键(身份证) 业务操作时,尽量避免对主键的修改。

order by 优化

filesort排序方式

在排序缓冲区sort buffer中完成排序操作。

index排序方式

通过有序的索引直接返回有序数据,操作效率高。

image-20240906163837843

使用联合索引时,需要满足最左前缀法则。

image-20240906163802981

在对带有联合索引的字段进行order by排序时,对字段1升序,字段2降序。也会使用filesort排序。

image-20240906163727786

在创建索引时,需要指定排序方式。

// 该联合索引可以用 a asc/desc, b desc/asc 的order by排序
create index idx1 on t1(a asc , b desc);
image-20240906164152274

order by优化原则

根据排序字段建立合适的索引,多字段排序时,遵循最左前缀法则。 实现覆盖索引 多字段排序时,要注意联合索引的创建规则(ASC / DESC)。

出现不可避免的filesort时,为防止缓冲区溢出,可以修改缓冲区大小。 使用 set sort_buffer_size = [num] 完成。

group by优化:

使用group by时,对分组字段创建联合索引。 不满足最左前缀法则,索引失效会导致SQL低性能问题。

limit优化:

在大数据量情况下,搜索位置越靠后性能越低。 前缀数据都会被丢弃。

// 访问的位置越靠后,查询速度越慢。
select * from t1 limit st,num;

可以使用覆盖索引+子查询的方式进行优化

select * from table1 t1 , (select id from table1 order by id limit 9000000,10) temp where temp.id = t1.id;

count优化:

count() 是一个聚合函数,对于返回的结果集逐行判断,最后返回累计值。

MyISAM引擎把一个表的总行数存在了磁盘上,因此执行count(*)的时候会直接返回数据,效率很高。

InnoDB引擎执行count(*)时,需要累积计数,效率并不高。

count类型:

count(主键)

InnoDB引擎会遍历整张表,将每一行的主键id都返回给服务层,服务层接收到主键后,计数+1。

count(字段)

没有not null约束:InnoDB会遍历整张表把每一行的字段值都取出来,返回给服务层,服务层判断是否为null,不为null,计数累加。 有not null约束:InnoDB引擎同样会会将数据返回给服务层,服务层按行进行累加。

count(常数)

InnoDB引擎遍历整张表,但不取值。服务层对于返回的每一行,标记一个常数,按行进行累加。

count(*)

InnoDB并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行进行累加。

count优化原则

效率由高到低 count(*) ≈ count(常数) > count(主键) > count(字段)

尽量使用count(*),数据库专门做了优化。

update优化:

如果字段没有索引,SQL语句就会上表锁 所以尽量对有索引的字段作为条件执行SQL语句,避免表锁导致性能下降。

InnoDB的行锁是针对索引加的锁,不是针对记录加的锁。
如果索引失效,行锁会升级为表锁。

小结:

插入数据优化: 如果需要保存多条数据,批量插入,手动控制事务、主键顺序插入 大批量插入时,可以通过加载本地文件的方式构建表。

主键优化: 主键是乱序插入时,可能出现页分裂问题,一个操作可能会对几个页同时操作。 主键长度尽量短,顺序插入

order by优化: using index:通过索引返回数据、性能高 尽量使用覆盖索引,创建联合索引时,要考虑索引的字段顺序和排序方式。

group by优化: 多字段分组时,不满足最左前缀法则,会导致索引失效。 尽量覆盖索引。

limit优化: 访问的数据越靠后,查询速度越低 覆盖索引+子查询优化

count优化: 使用count(*)优化 或是自行计数,存储在专门的表中。

update优化: 写操作,覆盖索引,根据主键/索引字段来进行操作 避免表锁,导致数据库的并发性能下降。

]]>
DebianUbuntu 安装 MySQL https://forelink.top/index.php/2024/09/04/debianubuntu-%e5%ae%89%e8%a3%85-mysql/ Wed, 04 Sep 2024 09:09:01 +0000 https://forelink.top/?p=493 下载MySQL APT配置包

在MySQL官网点击社区版下载,选择长期维护版本(LTS)和操作系统

image-20240904162358158

点击 Install Using APT,复制下载链接

安装MySQL

在终端中执行:

// 下载MySQL APT 配置包
wget https://dev.mysql.com/get/mysql-apt-config_0.8.32-1_all.deb
// 安装MySQL APT 配置包
sudo dpkg -i mysql-apt-config_0.8.32-1_all.deb
需要确认MySQL的版本

// 更新apt包列表并安装MySQL
sudo apt update
sudo apt install mysql-server
需要设置root用户的密码

启动MySQL服务

systemctl start mysql //启动
systemctl restart mysql //重启
systemctl stop mysql //停止

创建一个新用户

create user 'root'@'本机ip' identified by '密码';
image-20240904170639207

然后可以在IDEA中测试连接

image-20240904170707320

]]>
MySQL存储引擎 https://forelink.top/index.php/2024/09/04/mysql%e5%ad%98%e5%82%a8%e5%bc%95%e6%93%8e/ Wed, 04 Sep 2024 08:27:09 +0000 https://forelink.top/?p=491 简介:

采用MySQL的业务的体系结构主要为: 连接层、服务层、引擎层、存储层

存储引擎是基于表建立的。别称 表类型 MySQL 5.5 后默认的存储引擎为InnoDB(行级锁、事务)

查看mysql数据库支持的存储引擎

SHOW ENGINES;
image-20240904153438828

Support DEFAULT 默认引擎 Transactions 是否支持事务

创建表时制定存储引擎

create table [name](
    ...
) ENGINE = [Engine_name]

InnoDB存储引擎

是由Oracle创建的下一代InnoDB引擎,拥有者是InnoDB而非MySQL。 Google、木下靖文、Percona、Facebook等也是重要贡献者、

特点:

DML操作(增删改)遵循 ACID 模型,支持事务 行级锁,提升高并发情景访问性能 支持外键 FOREIGN KEY约束(一般开发中不会使用)

文件:

[table_name].ibd

使用innoDB引擎的每张表都会对应一个二进制表空间文件,存储该表表结构、数据、索引。

表结构:frm、sdi
frm是早期的表结构

查看文件:

ibd2sdi xxx.ibd // 转化成sdi(json格式)
image-20240904154913150
image-20240904155638977

MyISAM

是MySQL早期的默认存储引擎

特点:

仅支持表锁,不支持行锁 不支持事务,不支持外键 访问速度快

文件:

MYI — 存储索引 MYD — 存储数据 sdi — 表结构存放文件(json格式数据)

Memory

数据存储在内存,作为临时表、缓存使用

特点:

内存存放 Hash索引(默认)

文件:

xxx.sdi

InnoDB 和 MyISAM

二者的区别

image-20240904160114558

在引擎comments中也有提到

image-20240904160213836

选择存储引擎

InnoDB:

对事务有需求,并发条件要求数据的一致性。 修改删除操作。

MyISAM:

以读操作和插入操作为主,对事务完整性要求不高,并发情况较少。

Memory:

保存在内存中,用于缓存和临时表。 对表的大小有限制,无法保障数据的安全性。

对MyISAM和Memory的需求都被非关系型数据库(Redis)替代了
Redis是基于内存的NoSQL数据库,适用于需要高性能读写的场景。
如果需要关系型数据库的特性和大量持久化的能力,
InnoDB反倒比MyISAM更稳定可靠,鲁棒性更高。
]]>
Redis6.0新特征 https://forelink.top/index.php/2024/09/03/redis6-0%e6%96%b0%e7%89%b9%e5%be%81/ Mon, 02 Sep 2024 16:01:21 +0000 https://forelink.top/?p=485 ACL

Redis ACL 是 Access Control List (访问控制列表)的缩写,该功能允许根据可以执行的命令和可以访问的键来限制某些连接。

Redis 6 提供的ACL的功能对用户进行更细粒度的权限控制:

(1)接入权限:用户名和密码

(2)可以执行的命令

(3)可以操作的KEY

在Redis 5 版本之前,Redis安全规则只有密码控制 和rename调整高危命令(如flushdb,keys *, shutdown等)

命令:

使用 acl list 来查看当前用户的权限。

image-20240902233959748

user 用户 default 用户名字 on 启用 nopass 不需要密码 ~* &* +@all 所有命令

查看所有的操作命令 acl cat [specific usage]

image-20240902234234920

使用acl whoami命令查看当前用户

image-20240902234303486

ACL创建用户

acl setuser [username] on >[password] ~[key name] +get
创建一个用户 on启用 有密码 只能对[key name]使用get指令

切换并测试新建用户:

//切换用户
auth [username] [password]

只能使用get指令,并只能对[key name]进行操作

image-20240902234946009

IO多线程

简介:

IO多线程指的是客户端交互部分的网络IO交互处理模块多线程,而Redis6执行命令仍然是单线程。

修改配置文件开启:

// redis.conf
io-threads-do-reads yes

工具支持 Cluster:

redis5开始将redis-trib.rb的功能集成到版本中。

其他新功能:

1、RESP3 新的Redis通信协议

2、Client side caching 客户端缓存,基于RESP3协议实现的客户端缓存功能

3、Proxy集群代理模式:Proxy功能降低了cluster的使用门槛

]]>
分布式锁 https://forelink.top/index.php/2024/09/03/%e5%88%86%e5%b8%83%e5%bc%8f%e9%94%81/ Mon, 02 Sep 2024 16:00:25 +0000 https://forelink.top/?p=483 简介:

分布式集群系统演化的结果,是多线程多进程的分布在不同主机。导致原单机部署情况下的并发控制锁策略失效。

大部分Web应用都是依赖于Java的开发框架,而 Java 并没有提供分布式锁的支持。 为了解决这个问题,需要选择一种跨JVM的互斥机制来控制共享资源的访问。

分布式锁与它的不同方案,就是要解决这个问题。

目前的主流方案有:
1、基于数据库实现分布式锁
2、基于缓存实现分布式锁
3、基于Zookeeper

追求性能,基于缓存实现的分布式锁最好
希望可靠性,选择Zookeeper更胜一筹

使用redis实现分布式锁

setnx 就是redis实现分布式锁的命令

setnx lock 1
-->(integer) 1
setnx lock 1
-->(integer) 0

设置了lock之后,继续访问锁的操作。

如果锁在,会等待重试。锁不在时,才会放行其他请求。 只有把锁解开后(del lock),才能继续其他操作。

死锁问题:

锁释放会出现死锁问题。 自动化解决死锁问题,可以根据具体业务,给锁设定一个合理的过期时间:

setnx lock 1 //设置锁
expire lock 10 // 10秒后锁过期

考虑到上锁操作原子性的必要性,应该将设置锁和设置过期时间在一条指令中执行

set lock 1 nx ex 10 // nx上锁 ex过期时间

实例代码:

public void testLock(){
    // 1 获取锁 3秒后过期
    Boolean lock = redisTemplage.opsForValue().setIfAbsent("lock","1",3,TimeUnit.SECONDS);
    // 2 获取锁成功、查询num的值
    if(lock){
        Object value = redisTemplate.opsForValue().get("num");
        // 判断num为空return
        if(StringUtils.isEmpty(value))
            return;
        //将redis的num加一(业务操作),并手动释放锁
        redisTemplate.opsForValue().set("num",++num);
        redisTemplate.delete("lock");
    }else{
        // 获取锁失败,0.1s后再获取
        try{
            Thread.sleep(100);
             testLock();
        }catch(...)...
    }
}

使用UUID,给锁唯一的标识:

X设置锁后,在手动释放前宕机。Y此时在X设置的锁自动释放后,上了锁。 X在功能恢复后,会将Y设置的锁释放。

为了避免这个问题的出现,应该给锁加uuid。

String uuid = new UUID.RandomUUID().toString();
Boolean lock = redisTemplage.opsForValue().setIfAbsent("lock",uuid,3,TimeUnit.SECONDS);
if(get(lock)==uuid)
redisTemplate.delete("lock");
else{
    ~~~
}

但删除操作缺少原子性 在删除时,锁过期自动释放。此时上锁后,锁会被前置的删除操作删除。

(共享锁的问题)

LUA脚本:

弱脚本在执行时,其他指令会等待。

所以用LUA脚本可以保证删除操作的原子性。

分布式锁需要满足

-互斥性。在任意时刻,只有一个客户端能持有锁。

-不会发生死锁。

-加锁和解锁过程必须有原子性。

]]>
Redis缓存崩溃&缓存击穿&缓存雪崩 https://forelink.top/index.php/2024/09/02/redis%e7%bc%93%e5%ad%98%e5%b4%a9%e6%ba%83%e7%bc%93%e5%ad%98%e5%87%bb%e7%a9%bf%e7%bc%93%e5%ad%98%e9%9b%aa%e5%b4%a9/ Mon, 02 Sep 2024 14:30:20 +0000 https://forelink.top/?p=479 简介:

1、应用服务器压力增大

2、大量访问,但redis命中率降低(先访问缓存,未命中再访问数据库。)

3、数据库压力增大,导致崩溃

redis查询不到数据库,出现很多非正常url访问。

解决方案:

(1)对空值缓存

把空结果进行缓存,并设置空结果的过期时间

(2)设置白名单

用bitmaps类型定义可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里的id进行比较,如果访问id不在bitmaps里面,拦截不允许访问

(3)采用布隆过滤器

布隆过滤器,实际上是二进制向量(位数组)和随机映射函数(哈希函数) 用于快速判断一个一个元素是否可能在一个集合中,尽管可能会将不在集合中的元素误判在集合中,但不会把在集合中的元素误判不在集合中

(4)进行实时监控

Redis命中率下降时,将非正常访问的url和用户拉入黑名单

以上的方法可以综合使用,减少服务器压力。

Redis缓存击穿

特征:

1、数据库访问压力瞬时增加

2、redis里面没有出现大量key过期

3、redis正常运行

出现原因:

在高并发场景下,redis某个热点数据缓存过期的瞬间,收到大量请求同时访问该数据,这些请求会绕过缓存直接查询数据库,导致数据库压力骤增,甚至可能引发数据库宕机。

解决方案:

(1)预先设置热门key

在访问量增加之前,将热门key提前存入到redis中,实时调整key的过期时长

(2)实时调整

监控并实时调整key的过期时长

(3)使用锁

在缓存失效的时候(值为null),不立即去重新从数据库中取值 而是先检查缓存,如果数据存在,则直接返回 如果缓存中没有该数据,则表示该缓存可能已经过期,或数据未被缓存

缓存未命中时,尝试获取一个分布式锁(如SETNX命令),只有获取到锁的请求才可以访问数据库,其他请求则会在等待锁的释放。

获取锁的请求查询数据库后,写入Redis缓存,并根据以上的方法实时调整过期时间, 然后立即释放锁,请求会在锁被释放后,读取缓存返回给客户端。

缓存雪崩

1、在极少时间段内,查询大量key的集中过期情况

2、数据库压力变大,服务崩溃

解决方案

(1)构建多级缓存架构

如:nginx缓存 + redis缓存 + 其他缓存(ehcache等)

(2)使用锁或队列

用加锁或者队列的方式,保证不会有大量线程对数据库进行一次性读写。 不使用于高并发情况。

(3)设置过期标志更新缓存

记录缓存数据是否过期,过期时触发通知,在后台更新实际key的缓存

(4)将缓存失效时间分散

降低缓存过期的重复率,避免引发集体失效的事件。

]]>
Redis集群 https://forelink.top/index.php/2024/09/02/redis%e9%9b%86%e7%be%a4/ Mon, 02 Sep 2024 12:54:12 +0000 https://forelink.top/?p=475 简介:

容量不够时,redis如何进行扩容。 –建立服务器集群,分担写入压力。 并发写操作,redis如何分摊。

主从模式的从机继承,导致ip地址发生变化,应用程序中配置需要修改对应的主机地址、端口等信息。

redis3.0中提供了 无中心化集群。

无中心化集群简介

任何一台服务器都可以作为集群的入口,之间可以互相联通。

Redis集群是对Redis的水平扩容,即启动N个节点。整个数据库分布存储在这N个节点,每个节点存储总数据的 1/N。

Redis集群通过分区来提供一定程度的可用性:及时集群中有一部分节点失效或者失去通讯,也能继续进行服务。

集群搭建示例

制作6个示例并启动

在配置文件追加以下内容

cluster-enabled yes // 开启集群
cluster-config-file [filename] // 设置集群文件名称
cluster-node-timeout [time] // 设置超时时间

然后启动6个服务器

集群配置:

新版本redis不需要再装ruby环境。ruby环境配置在redis目录src文件夹中的redis-trib.rb中。 需要在该目录中执行集群配置的命令

// --cluster create 创建一个集群
// cluster-replicas 集群配置方式,1为最简单的方式
// 随后输入不同的节点[ip:port]加入集群
redis-cli --cluster create --cluster-replicas 1 192.168.199.129:6379 192.168.199.129:6380 192.168.199.129:6381 192.168.199.129:6389 192.168.199.129:6390 192.168.199.129:6391

执行后会自动分配主机和从机,输入yes后接受分配,当看到如下图片,说明集群配置成功

image-20240902144759411

测试:

用集群方式开启redis客户端,连接集群。

// -c 采用集群策略连接,设置数据会自动切换到相应的写主机
redis -c -p [port]

用 cluster nodes查看集群信息

image-20240902145144990

能看到节点的分析情况,一主一从的模式,共有三组。

Cluster自动分配:

一个集群至少要有三个主节点。 –cluster-replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。 分配原则尽量保证每个主数据库运行在不同的IP地址,每个从库和主库不在一个IP地址上。

Slots:

在集群创建成功信息中,可以看到日志包含:

image-20240902145640621

一个Redis集群包含16384个slot (0-16383),数据库中的每个键都属于这16384个slot中的其中一个。

集群使用公式 CRC16(key) % 16384 来计算键key数据哪个槽 其中 CRC16(key) 语句用于计算键 key的CRC16校验和。

集群中的每个节点是插槽的一部分,在cluster node插槽信息中可以看到。

在集群中录入值:

由于redis集群是无中心化集群,任何一个主机都可以作为集群的入口

image-20240902154547693

图中k1的slot为12706,由对应的主机完成写操作。

在集群中使用mset操作会报错,不能计算多个key的hash slot。 image-20240902154723152

如果需要添加多个key-value,需要用组的形式,格式例:

// 向数据库中加入多个值,根据{}中的值计算slot
mset name{user} lucy age{user} 20
image-20240902160451740

查询集群中的值

// 计算key的插槽值
cluster keyslot [key]
// 计算该插槽中的key数量。(在对应的主机只能看它具有的插槽)
cluster countkeysinslot [slot] [count]
// 会返回count个在slot中的key。
cluster getkeysinslot [slot] [count]

故障恢复:

如果主机下线,从节点能否自动升为主节点?(可以)

image-20240902165636533

如果主机再上线,还会成为主机吗?(不会,会成为从机)

image-20240902165756369

如果集群中某一节点(插槽段)的主机和从机都下线后,Redis集群还能继续运行吗?(不一定)

// 如果该配置属性为yes,则集群整个都会下线
// 如果为no,集群中该插槽数据全都无法访问,新数据也无法存储。
cluster-require-full-coverage [yes/no]

集群Jedis开发

Jedis可以针对集群进行操作(创建集群Jedis对象而不是Jedis对象)

// 创建对象
HostAndPort hostAndPort = new HostAndPort([host],[port]);
JedisCluster jedisCluster = new JedisCluster(hostAndPort);

// 进行操作
jedisCluster.set("b1","value1");

String value = jedisCluster.get("b1");
sout("value: "+value);

jedisCluster.close();

总结:

Redis集群实现了扩容,分摊IO压力,且无中心配置相对简单,每个主机都可以作为集群的入口。

Redis集群也有不足,多键操作不被支持,需要分组来进行操作。 多键的Redis事务是不被支持。lua脚本也不被支持。

]]>