常见问题整理(2): Mysql+Redis

Database

事务的特性

1、原子性(Atomic):指的是事物所有的操作要么全做、要么全不做。

2、一致性(Consistency):事物应该确认数据库从一个一致状态转变为另一个一致状态。(数据库中的数据应满足完整性约束)

  • 修改丢失:丢失修改是事务A和B先后更改数据数据x(假设初始是x0),但是在A未正式更改前,B已经读取了原先的数据x0,最后A更改后为x1,B更改的并不是A更新后的x1,而是更改的x0,更改后假设为x2,这时x2将x1覆盖了,相当于事务A针对x的更改丢失了。
  • 脏读: 读到了未提交的 事务T1读取了T2更改的x,但是T2在实际存储数据时可能出错回滚了,这时T1读取的实际是无效的数据,这种情况下就是脏读
  • 不可重复读:读到了别人update过的 是说在T1读取x时,由于中间T2更改了x,所以T1前后两次读取的x值不相同,这就是所谓的不可重复读
  • 幻读:插入发现别人已经insert了 在T1读取符合某个条件的所有记录时,T2增加了一条符合该条件的记录,这就导致T1执行过程中前后读取的记录可能不一致,即T2之后读取时会多出一条记录。

3、隔离性(Isolation):指的是多个事物并发执行的时候、一个事物的执行不应当影响到其它的事物、即事物与事物之间是隔离的。

4、持久性(Durability):指的是一个事物一旦提交、它对数据库的修改应该永久保存在数据库中。

如何分库分表

https://www.imooc.com/article/301836

垂直切分又细分为垂直分库垂直分表

在这里插入图片描述

水平切分又分有库内分表分库分表

问题:一致性,排序,分布式id(实现方法:uuid redis incre,数据库自增,雪花算法)

SnowFlake算法的优点:
(1)高性能高可用:生成时不依赖于数据库,完全在内存中生成。

(2)容量大:每秒中能生成数百万的自增ID。

(3)ID自增:存入数据库中,索引效率高。

SnowFlake算法的缺点:
依赖与系统时间的一致性,如果系统时间被回调,或者改变,可能会造成id冲突或者重复。

中间件:sharding-jdbc,Cobar

mysql四种事务隔离级别,默认的隔离级别是什么,怎么实现的

mysql默认的事务隔离级别为repeatable-read

https://zhuanlan.zhihu.com/p/117476959

mvcc是什么

https://www.jianshu.com/p/8845ddca3b23

Multi-Version Concurrency Control

MVCC就是为了实现读-写冲突不加锁,而这个读指的就是快照读, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现

事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照.

  • 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
  • 同时还可以解决脏读、不可重复读等事务隔离问题,但不能解决更新丢失问题
  • (MVCC不能解决幻读问题,在可重复读的隔离级别下,使用MVCC+Next-Key Locks可以解决幻读的问题。

    Next-KeyLocks是InnoDB行锁和间隙锁的默认组合。间隙锁是在锁定索引记录间隙,确保索引记录的间隙不变。

    Next-KeyLocks当对数据进行条件范围检索的时候,对其范围内存在的值进行加锁,防止其他事务的插入操作来达到防止幻影读的目的。)

实现原理

它的实现原理主要是依赖记录中的 3个隐式字段,undo日志 ,Read View 来实现的

隐式字段:隐式主键、事务id、回滚指针(指向上一个旧版本)

roll_pointer
回滚指针,指向这条记录的上一个版本
trx_id
事务ID,记录创建/修改这条记录的事务ID,用于版本比较,从而找到快照

undo log:update undo log
事务在进行update或delete时产生的undo log; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除

不同事务或者相同事务的对同一记录的修改,会导致该记录的undo log成为一条记录版本线性表,既链表,undo log的链首就是最新的旧记录,链尾就是最早的旧记录

Read View(读视图/一致性视图):事务进行快照读操作的时候生产的读视图。在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID。

把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。

https://blog.csdn.net/longgeqiaojie304/article/details/98872857

low_limit_id是“高水位”,即当时活跃事务的最大id,如果读到row的db_trx_id>=low_limit_id,说明这些id在此之前的数据都没有提交,如注释中的描述,这些数据都不可见。

up_limit_id是“低水位”,即当时活跃事务列表的最小事务id,如果row的db_trx_id<up_limit_id,说明这些数据在事务创建id的时都已经提交,如注释中的描述,这些数据均可见

分成了三个区间。https://www.jianshu.com/p/7f5f22c28100

当事务第一次执行查询sql时会生成一致性视图read-view,它由执行查询时所有未提交事务ID数组(数组里最小的ID为min_id)和已创建的最大事务id(max_id)组成,查询的数据结果需要跟read-view做比较从而得到快照结果

(查询时-----可见------【(未提交事务IDmin低水位)-------------

row的db_trx_id在low_limit_id和up_limit_id之间,则查找该记录的db_trx_id是否在自己事务的read_view->trx_ids列表中,如果在则该记录的当前版本不可见,否则该记录的当前版本可见。

------------(已创建的最大事务idmax高水位)----------不可见-------)

https://blog.csdn.net/weichi7549/article/details/107991872

1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据

2、不可重复读:通常针对数据更新(UPDATE)操作。事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。

3、幻读:幻读是针对数据插入(INSERT)操作来说的。假设事务A对某些行的内容作了更改,但是还未提交,此时事务B插入了与事务A更改前的记录相同的记录行,并且在事务A提交之前先提交了,而这时,在事务A中查询,会发现好像刚刚的更改对于某些数据未起作用,但其实是事务B刚插入进来的,让用户感觉很魔幻,感觉出现了幻觉,这就叫幻读。

小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

数据库调优/如果一个sql运行得非常慢,如何解决

explain如何使用?type扫描范围+key使用的索引+rows估计要找到所需的行而要读取的行数

type:all index ref const

system:最快,加载到内存里,不进行磁盘IO
const:PK或者unique上的等值查询
eq_ref:PK或者unique上的join查询,等值匹配,对于前表的每一行(row),后表只有一行命中
ref:非唯一索引,等值匹配,可能有多行命中
range:索引上的范围扫描,例如:between/in/>
index:索引上的全集扫描
ALL最慢:全表扫描(full table scan)

上面各类扫描方式由快到慢:system > const > eq_ref > ref > range > index > ALL

https://blog.csdn.net/weixin_43120613/article/details/106681413

extra:

Using index:使用覆盖索引,表示查询索引就可查到所需数据,不用扫描表数据文件,往往说明性能不错。
Using Where:在存储引擎检索行后再进行过滤,使用了where从句来限制哪些行将与下一张表匹配或者是返回给用户。
Using temporary:在查询结果排序时会使用一个临时表,一般出现于排序、分组和多表 join 的情况,查询效率不高,建议优化。
Using filesort:对结果使用一个外部索引排序,而不是按索引次序从表里读取行,一般有出现该值,都建议优化去掉,因为这样的查询 CPU 资源消耗大。

https://www.cnblogs.com/Java3y/p/10075578.html

  • 使用sql或者尽量让sql走索引

为什么mysql索引要使用b+树,为什么不使用红黑树

B-Tree中一次检索最多需要h-1次I/O。渐进复杂度为O(h)=O(logdN)O(h)=O(logdN)。一般实际应用中,出度d是非常大的数字,通常超过100,因此h非常小(通常不超过3)。

而红黑树这种结构,h明显要深的多。由于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性,所以红黑树的I/O渐进复杂度也为O(h),效率明显比B-Tree差很多。

B+Tree更适合外存索引,原因和内节点出度d有关。从上面分析可以看到,d越大索引的性能越好,而出度的上限取决于节点内key和data的大小。由于B+Tree内节点去掉了data域,因此可以拥有更大的出度,拥有更好的性能。

http://blog.codinglabs.org/articles/theory-of-mysql-index.html

b+b-的区别:

1.select*扫表,b+只需要拿出叶子结点即可,b树要遍历。

2.基于索引的排序更优,叶子节点是个链表

3.b+树非叶子节点删除了数据区内容,16kb中的关键字和指针更多。io吞吐能力更强。

4.b树稳定三次io,而b树未必,可能两次。

limit分页性能问题

三大范式

第一范式(1NF):每一列都是不可分割的原子数据项。

第二范式(2NF):在1NF的基础上,非码属性必须完全依赖于候选码(在1NF基础上消除非主属性对主码的部分函数依赖)每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键)

第三范式(3NF):在2NF基础上,任何非主属性不依赖于其它非主属性(在2NF基础上消除传递依赖)。每一列数据都和主键直接相关,而不能间接相关。传递

https://www.jianshu.com/p/5a8bb84289a9

手写SQL 查找成绩总和top3的学生

1

2

3

student (id, name,sex..)

course(id, name)

sc (id, s_id, c_id...)

查询选了两门课的学生的姓名
(两种方法,使用子查询或者group)

索引越多越好吗

  • 数据量小的表不需要建立索引,因为建立索引会增加额外的开销。
  • 数据变更需要维护索引,因此更多的索引意味着更多的维护成本。
  • 更多的索引意味着更多的存储空间。

Mysql引擎知道哪些,说下myisam和innodb的区别

https://www.jianshu.com/p/4bb9f78b4f6d

myisam:

读写互相阻塞

表锁

.myi存储index,挂载地址值  .myd存储data 。MyISAM引擎索引文件和数据文件是分离的,叶节点的data域存放的是数据记录的地址。

innodb:

读写阻塞与事务隔离级别相关

行锁

对于主键(聚集索引).ibd存储index,叶子节点存储row,但是非主键索引,叶子节点数据区挂载的主键的值。InnoDB的辅助索引data域存储相应记录主键的值而不是地址。

https://blog.csdn.net/qq_27607965/article/details/79925288

为什么 select count(*) from t,在 InnoDB 引擎中比 MyISAM 慢?

MyISAM 存储引擎中,把表的总行数存储在磁盘上,当执行 select count(*) from t 时,直接返回总数据

https://blog.csdn.net/z694644032/article/details/105134631

https://www.runoob.com/w3cnote/mysql-different-nnodb-myisam.html

聚簇索引和非聚簇索引?

聚集索引(聚簇索引):这种以主键作为 B+ 树索引的键值而构建的 B+ 树索引,我们称之为聚集索引

非聚集索引(非聚簇索引):以主键以外的列值作为键值构建的 B+ 树索引,我们称之为非聚集索引。

非聚集索引与聚集索引的区别在于非聚集索引的叶子节点不存储表中的数据,而是存储该列对应的主键,想要查找数据我们还需要根据主键再去聚集索引中进行查找,这个再根据聚集索引查找数据的过程,我们称为回表。

避免二次查询:复合索引(覆盖索引)。建立两列以上的索引,即可查询复合索引里的列的数据而不需要进行回表二次查询

如index(col1, col2),执行下面的语句

select col1, col2 from t1 where col1 = '213';

http://www.liuzk.com/410.html

https://blog.csdn.net/weixin_41565013/article/details/94573960

每次给字段建一个新索引, 字段中的数据就会被复制一份出来, 用于生成索引。 因此, 给表添加索引,会增加表的体积, 占用磁盘存储空间。

内连接和左连接 区别?

内连接与左外链接的区别描述:

使用内连接时,不符合连接条件的数据,(不管是左表中的还是右表中的)都不会被组织到结果集中

使用左外连接时,对于不符合连接条件的数据,左表中的内容依然会被组织到结果集中,结果集中该条数据对应的右表部分为 null

一个MySQL查询过程发生了什么/一条SQL的执行过程

https://www.cnblogs.com/jacian/p/11511976.html

https://zhuanlan.zhihu.com/p/114794891

MySQL逻辑架构图

2.查询缓存,查到返回。3.解析

redo log 和 undo log

https://www.cnblogs.com/f-ck-need-u/archive/2018/05/08/9010872.html

innodb事务日志包括redo log和undo log。redo log是重做日志,提供前滚操作,undo log是回滚日志,提供回滚操作。undo log不是redo log的逆向过程,其实它们都算是用来恢复的日志:
1.redo log通常是物理日志,记录的是数据页的物理修改,而不是某一行或某几行修改成怎样怎样,它用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置)。每当有操作执行前,将数据真正更改时,先前相关操作写入重做日志。这样当断电,或者一些意外,导致后续任务无法完成时,系统恢复后,可以继续完成这些更改
2.undo用来回滚行记录到某个版本。undo log一般是逻辑日志,根据每行记录进行记录。当一些更改在执行一半时,发生意外,而无法完成,则可以根据撤消日志恢复到更改之前的壮态.

因为innodb存储引擎存储数据的单元是页(和SQL Server中一样),所以redo log也是基于页的格式来记录的。默认情况下,innodb的页大小是16KB(由 innodb_page_size 变量控制),一个页内可以存放非常多的log block(每个512字节),而log block中记录的又是数据页的变化。

通过undo日志数据库可以实现MVCC:Mutil-Version Concurrency Control

无非就是乐观锁的一种实现方式。在Java编程中,如果把乐观锁看成一个接口,MVCC便是这个接口的一个实现类而已。

每开始新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询每行记录的版本号进行比较。每个事务又有自己的版本号,这样事务内执行CRUD操作时,就通过版本号的比较来达到数据版本控制的目的。

https://www.cnblogs.com/shujiying/p/11347632.html

怎么建索引?

MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址。

InnoDB的数据文件本身就是索引文件。从上文知道,MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。

http://blog.codinglabs.org/articles/theory-of-mysql-index.html

Hash索引和B+索引的区别?

B-tree 索引可以用于使用 =, >, >=, <, <= 或者 BETWEEN 运算符的列比较。如果 LIKE 的参数是一个没有以通配符起始的常量字符串的话也可以使用这种索引。

Hash 索引只能够用于使用 = 或者 <=> 运算符的相等比较(但是速度更快)。Hash 索引不能够用于诸如 < 等用于查找一个范围值的比较运算符。依赖于这种单值查找的系统被称为 “键-值存储”;对于这种系统,尽可能地使用 hash 索引。

https://blog.csdn.net/xb_zed/article/details/109382426

sql注入怎么避免/sql预编译

prestatement

sql优化

MySQL的优化主要分为结构优化(Scheme optimization)和查询优化(Query optimization)。结构优化:

最左前缀原理与相关优化:

如果有一个 2 列的索引 (col1, col2),则已经对 (col1)、(col1, col2) 上建立了索引;
如果有一个 3 列索引 (col1, col2, col3),则已经对 (col1)、(col1, col2)、(col1, col2, col3) 上建立了索引;

 

出了个sql题,怎么建索引。有一个订单表,有用户的属性和日期。现在有三个sql查询,

CREATE INDEX PersonIndex ON Person (LastName, FirstName)
  • 查某个用户的所有订单
  • 查某个date的所有订单
  • 查某个用户最近一个月的所有订单
    当时我说建三个索引,后来反问的时候,他提醒了我一下最左前缀,我才反应过来,两个就够。(date和id-date)

三层的B+树可以存多少信息,页表自己定义,节点大小自己估算

上亿级别,按页表16KB算,long占4个字节,16KB/4B = 4K

4K × 4K × 4K = 6.4×10^10、

什么时候不能建立索引

适合创建索引条件

  1、主键自动建立唯一索引

  2、频繁作为查询条件的字段应该建立索引

  3、查询中与其他表关联的字段,外键关系建立索引

  4、单键/组合索引的选择问题,组合索引性价比更高

  5、查询中排序的字段,排序字段若通过索引去访问将大大提高排序效率

  6、查询中统计或者分组字段

不适合创建索引条件

  1、表记录少的

  2、经常增删改的表或者字段

  3、where条件里用不到的字段不创建索引

  4、过滤性不好的不适合建索引

Redis

https://zhuanlan.zhihu.com/p/93515595

持久化 Rdb 和 Aof

https://www.jianshu.com/p/1d9ab6bc0835

RDB持久化Redis DataBase

定义:在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot)(存的是快照)

RDB的优点:

1.体积更小:相同的数据量rdb数据比aof的小,因为rdb是紧凑型文件

2.恢复更快:因为rdb是数据的快照,基本上就是数据的复制,不用重新读取再写入内存

3.性能更高:父进程在保存rdb时候只需要fork一个子进程,无需父进程的进行其他io操作,也保证了服务器的性能。

缺点:

1.故障丢失:因为rdb是全量的,我们一般是使用shell脚本实现30分钟或者1小时或者每天对redis进行rdb备份,(注,也可以是用自带的策略),但是最少也要5分钟进行一次的备份,所以当服务死掉后,最少也要丢失5分钟的数据。

2.耐久性差:相对aof的异步策略来说,因为rdb的复制是全量的,即使是fork的子进程来进行备份,当数据量很大的时候对磁盘的消耗也是不可忽视的,尤其在访问量很高的时候,fork的时间也会延长,导致cpu吃紧,耐久性相对较差。

优点:RDB文件紧凑,体积小,网络传输快,适合全量复制;恢复速度比AOF快很多。当然,与AOF相比,RDB最重要的优点之一是对性能的影响相对较小。

缺点:RDB文件的致命缺点在于其数据快照的持久化方式决定了必然做不到实时持久化,而在数据越来越重要的今天,数据的大量丢失很多时候是无法接受的,因此AOF持久化成为主流。此外,RDB文件需要满足特定格式,兼容性差(如老版本的Redis不兼容新版本的RDB文件)。

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。 这就确保了极高的性能。rdb保存的文件是dump.rdb

AOF持久化Append Only File

aof的优点  (存的是语句)

1.支持秒级持久化,数据保证:我们可以设置fsync策略,一般默认是everysec,也可以设置每次写入追加,所以即使服务死掉了,咱们也最多丢失一秒数据

2.自动缩小:当aof文件大小到达一定程度的时候,后台会自动的去执行aof重写,此过程不会影响主进程,重写完成后,新的写入将会写到新的aof中,旧的就会被删除掉。但是此条如果拿出来对比rdb的话还是没有必要算成优点,只是官网显示成优点而已。

缺点呢:和rdb相反嘛,毕竟只有两种。

1.性能相对较差:它的操作模式决定了它会对redis的性能有所损耗

2.体积相对更大:尽管是将aof文件重写了,但是毕竟是操作过程和操作结果仍然有很大的差别,体积也毋庸置疑的更大。

3.恢复速度更慢: 

但是明显选择aof。

安全性来讲由于aof的记录能够精确到秒级追加甚至逐条追加,而rdb只能是全量复制,aof明显高于rdb。但是从性能来讲rdb就略胜一筹,rdb是redis性能最大化的体现,它不用每秒监控是否有数据写入,当达到触发条件后就自动fork一个子进程进行全量更新,速度也很快。容灾回复方面rdb更是能够快速的恢复数据,而aof需要读取再写入,相对慢了很多。

Aof 保存的是 appendonly.aof 文件

为什么用redis

  1. 缓存,毫无疑问这是Redis当今最为人熟知的使用场景。再提升服务器性能方面非常有效;

  2. 计算器/限速器,利用Redis中原子性的自增操作,我们可以统计类似用户点赞数、用户访问数等,这类操作如果用MySQL,频繁的读写会带来相当大的压力;限速器比较典型的使用场景是限制某个用户访问某个API的频率,常用的有抢购时,防止用户疯狂点击带来不必要的压力;

  3. 好友关系,利用集合的一些命令,比如求交集、并集、差集等。可以方便搞定一些共同好友、共同爱好之类的功能;

  4. 简单消息队列,除了Redis自身的发布/订阅模式,我们也可以利用List来实现一个队列机制,比如:到货通知、邮件发送之类的需求,不需要高可靠,但是会带来非常大的DB压力,完全可以用List来完成异步解耦;

  5. Session共享,以PHP为例,默认Session是保存在服务器的文件中,如果是集群服务,同一个用户过来可能落在不同机器上,这就会导致用户频繁登陆;采用Redis保存Session后,无论用户落在那台机器上都能够获取到对应的Session信息。

  6. 一些频繁被访问的数据,经常被访问的数据如果放在关系型数据库,每次查询的开销都会很大,而放在redis中,因为redis 是放在内存中的可以很高效的访问

mysql磁盘,redis内存,查询开销大。完全基于内存所以速度很快,使用C语言实现,网络层使用epoll解决高并发问题,单线程模型避免了不必要的上下文切换及竞争条件; 注意:单线程仅仅是说在网络请求这一模块上用一个请求处理客户端的请求,在持久化时它就会重开一个线程/进程去进行处理

丰富的数据类型,Redis有8种数据类型,常用的有 String、Hash、List、Set、 SortSet 这5种,都是基于键值的方式组织数据。每一种数据类型提供了非常丰富的操作命令,可以满足绝大部分需求

常用数据类型,数据结构

  • string,list,set,zset,hash
  • string:set age 1;exists name; move name 1; get name; ttl name

    setex (set with expire) # 设置过期时间
    setnx (set if not exist) # 不存在在设置 (在分布式锁中会常常使用!) 
    set user:1 {name:zhangsan,age:3} # 设置一个 user:1 对象 值为 json字符来保存一个对象    user:{id}:{filed}
  • list: 可以把它当作堆,栈,阻塞队列;

    LPUSH list one # 将一个值或者多个值,插入到列表头部 (左) LRANGE list 0 1 # 通过区间获取具体的值!  
    Lpop list # 移除 list的第一个元素    rpop最后一个 
    lindex list 1 # 通过下标获得 list 中的某一个值
    lrem list 1 one # 移除 list 集合中指定个数的 value ,精确匹配
    ltrim mylist 1 2 # 通过下标截取指定的长度,这个 list 已经被改变了,截断了只剩下截取的元素!
  • set:sadd srem 共同关注     zset排序
  • hash:hset myhash field1 kuangshen # set一个具体 key-vlaue    hget
    hdel myhash field1
    HINCRBY myhash field3 1
    hsetnx myhash field4 hello # 如果不存在则可以设置
    hsetnx myhash field4 world # 如果存在则不能设置 
    hash 更适合于对象的存储,String 更加适合字符串存储!
    Geospatial 地理位置  Hyperloglog(网页访问量)
    Bitmap 打卡点赞  Version:0.9 StartHTML:0000000105 EndHTML:0000000421 StartFragment:0000000141 EndFragment:0000000381
    getbit sign  3 1
    getbit sign
    bitcount sign # 统计这周的打卡记录,就可以看到是否有全勤!

缓存的过期时间:不设置内存会大,缓存窗口

setex,expire

redis内存不足时的策略

redis是一个基于内存的数据库,如果存储的数据量很大,达到了内存限制的最大值,将会出现内存不足的问题。redis允许用户通过配置maxmemory-policy参数,指定redis在内存不足时的解决策略。

jedis连接池参数

<!-- 连接池配置 -->
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <!-- 最大连接数 -->
        <property name="maxTotal" value="30" />
        <!-- 最大空闲连接数 -->
        <property name="maxIdle" value="10" />
        <!-- 每次释放连接的最大数目 -->
        <property name="numTestsPerEvictionRun" value="1024" />
        <!-- 释放连接的扫描间隔(毫秒) -->
        <property name="timeBetweenEvictionRunsMillis" value="30000" />
        <!-- 连接最小空闲时间 -->
        <property name="minEvictableIdleTimeMillis" value="1800000" />
        <!-- 连接空闲多久后释放, 当空闲时间>该值 且 空闲连接>最大空闲连接数 时直接释放 -->
        <property name="softMinEvictableIdleTimeMillis" value="10000" />
        <!-- 获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1 -->
        <property name="maxWaitMillis" value="1500" />
        <!-- 在获取连接的时候检查有效性, 默认false -->
        <property name="testOnBorrow" value="true" />
        <!-- 在空闲时检查有效性, 默认false -->
        <property name="testWhileIdle" value="true" />
        <!-- 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true -->
        <property name="blockWhenExhausted" value="false" />
    </bean>  

https://zhuanlan.zhihu.com/p/84481313

1)如何确定maxTotal?

1.命令平均执行时间0.1ms = 0.001s。

2.业务需要50000QPS

3.maxTotal理论值 = 0.001*50000=50个。但是实际设计时可能会偏大一些。

由以上可以得出,需要考虑:业务希望的Redis并发量、客户执行命令的时间、Redis资源:例如nodes(应用个数)+maxTotal是不能超过redis的最大连接数的(config get maxclients)

2)如何确定合适的maxIdle和minIdle?

建议maxIdle=maxTotal,为什么这么说呢?假如现在maxTotal是100,maxIdle是50,那么允许的最大空闲数是50,那么在一个高峰期,如果连接池中的50个已经通过连接池的getResource获取到了,这个时候第51个连接是要通过newJedis以及TCP三次握手建立一个新的连接,实际上这本身是有一定开销的。这样可以减少新的开销。建议预热minIdle,第一次getResource时是newJedis并建立TCP三次握手的,对于并发量较大的情况是无法容忍第一次开销的,那么可以在应用初始化的时候提前使用getResource做一些操作。

主从复制 哨兵

其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。

这里的哨兵有两个作用
  • 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
  • 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服 务器,修改配置文件,让它们切换主机

多个哨兵之间也会相互监控

持久化保证了即使redis服务重启也不会丢失数据,因为redis服务重启后会将硬盘上持久化的数据恢复到内存中,但是当redis服务器的硬盘损坏了可能会导致数据丢失,如果通过redis的主从复制机制就可以避免这种单点故障。

复制是高可用Redis的基础,哨兵和集群都是在复制基础上实现高可用的。复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。

在复制的基础上,哨兵实现了自动化的故障恢复。缺陷:写操作无法负载均衡;存储能力受到单机的限制。

redis单线程但是性能高

1.redis是基于内存的,内存的读写速度非常快;

2.redis是单线程的,省去了很多上下文切换线程的时间;

3.redis使用多路复用技术,可以处理并发的连接。非阻塞IO 内部实现采用epoll,采用了epoll+自己实现的简单的事件框架。epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间。

redis 核心就是 如果我的数据全都在内存里,我单线程的去操作 就是效率最高的,为什么呢,因为多线程的本质就是 CPU 模拟出来多个线程的情况,这种模拟出来的情况就有一个代价,就是上下文的切换,对于一个内存的系统来说,它没有上下文的切换就是效率最高的。redis 用 单个CPU 绑定一块内存的数据,然后针对这块内存的数据进行多次读写的时候,都是在一个CPU上完成的,所以它是单线程处理这个事。在内存的情况下,这个方案就是最佳方案

CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。

https://www.cnblogs.com/xuwc/p/14015580.html

https://blog.csdn.net/weixin_43660856/article/details/102459483

布隆过滤器

https://www.cnblogs.com/liyulong1982/p/6013002.html    https://blog.csdn.net/duwangsky/article/details/22219009 https://blog.csdn.net/weixin_42049243/article/details/89224660

空查询缺陷;为了避免形成“环”,就需要知道爬虫程序已经访问过那些URL。

解决了如何判断一个元素是否存在一个集合中的问题

它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难

bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,其典型的应用场景就是快速判断一个key是否存在于某容器,不存在就直接返回。布隆过滤器的关键就在于hash算法和容器大小

当我们往Bloom Filter中增加任意一个元素x时候,我们使用k个哈希函数得到k个哈希值,然后将数组中对应的比特位设置为1。

在判断y是否属于这个集合时,我们只需要对y使用k个哈希函数得到k个哈希值,如果所有hashi(y)的位置都是1(1≤i≤k),即k个位置都被设置为1了,那么我们就认为y是集合中的元素,否则就认为y不是集合中的元素。

网页爬虫对 URL 去重,避免爬取相同的 URL 地址;邮箱垃圾邮件过滤功能;英语单词是否拼写正确

Reids工具命令  redis-server  redis-cli redis-benchmark

#redis-server:Redis 服务器的 daemon 启动程序
#redis-cli:Redis 命令行操作工具。当然,你也可以用 telnet 根据其纯文本协议来操作
#redis-benchmark:Redis 性能测试工具,测试 Redis 在你的系统及你的配置下的读写性能
$redis-benchmark -n 100000 –c 50
#模拟同时由 50 个客户端发送 100000 个 SETs/GETs 查询
#redis-check-aof:更新日志检查
#redis-check-dump:本地数据库检查

Hash扩容

redis中的hash表采用的是渐进式hash的方式:https://www.jianshu.com/p/ea5a747ade5d  https://blog.csdn.net/cqk0100/article/details/80400811

渐进式rehash 的详细步骤:

      1、为ht[1] 分配空间,让字典同时持有ht[0]和ht[1]两个哈希表

      2、在几点钟维持一个索引计数器变量rehashidx,并将它的值设置为0,表示rehash 开始

      3、在rehash 进行期间,每次对字典执行CRUD操作时,程序除了执行指定的操作以外,还会将ht[0]中的数据rehash 到ht[1]表中,并且将rehashidx加一

      4、当ht[0]中所有数据转移到ht[1]中时,将rehashidx 设置成-1,表示rehash 结束

    采用渐进式rehash 的好处在于它采取分而治之的方式,避免了集中式rehash 带来的庞大计算量。

Redis常用管理命令 dbsize monitor info

# dbsize 返回当前数据库 key 的数量。
# info 返回当前 redis 服务器状态和一些统计信息。
# monitor 实时监听并返回redis服务器接收到的所有请求信息。
# shutdown 把数据同步保存到磁盘上,并关闭redis服务。
# config get parameter 获取一个 redis 配置参数信息。(个别参数可能无法获取)
# config set parameter value 设置一个 redis 配置参数信息。(个别参数可能无法获取)
# config resetstat 重置 info 命令的统计信息。(重置包括:keyspace 命中数、
# keyspace 错误数、 处理命令数,接收连接数、过期 key 数)
# debug object key 获取一个 key 的调试信息。
# debug segfault 制造一次服务器当机。
# flushdb 删除当前数据库中所有 key,此方法不会失败。小心慎用
# flushall 删除全部数据库中所有 key,此方法不会失败。小心慎用

redis缓存被击穿处理机制

使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法

缓存穿透https://blog.csdn.net/kongtiao5/article/details/82771694

       描述:

       缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

      解决方案:

  1. 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
  2. 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击

缓存击穿

      描述:

      缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力

      解决方案:

  1. 设置热点数据永远不过期。
  2. 加互斥锁

缓存雪崩

      描述:

      缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

     解决方案

  1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  2. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
  3. 设置热点数据永远不过期。

https://cloud.tencent.com/developer/article/1091077

redis和memcache最大的区别

https://www.cnblogs.com/aspirant/p/8883871.html

https://www.cnblogs.com/aspirant/p/8883871.html

Redis中,并不是所有的数据都一直存储在内存中的

Memcache可以利用多核优势,单实例吞吐量极高,可以达到几十万QPS,适用于最大程度扛量

  • memcached所有的值均是简单的字符串 只支持简单的key/value数据结构,redis作为其替代者,支持更为丰富的数据类型
  • redis的速度比memcached快很多
  • redis可以持久化其数据

https://zhuanlan.zhihu.com/p/55822406

memcached是多线程,非阻塞IO复用的网络模型;redis使用单线程的IO复用模型

redis丰富的数据类型

memcached 不支持内存数据的持久化操作,所有的数据都以in-memory的形式存储;redis支持持久化操作。

Memcached提供了cas命令,可以保证多个并发访问操作同一份数据的一致性问题。 Redis没有cas 命令,不过Redis提供了事务的功能,可以保证一串命令的原子性,中间不会被任何操作打断。

相较于Memcached只能采用客户端实现分布式存储,Redis更偏向于在服务器端构建分布式存储。

在Redis中,并不是所有的数据都一直存储在内存中的。这是和Memcached相比一个最大的区别。如果Redis发现内存的使用量超过了某一个阀值,将触发swap的操作。

Redis分布式锁的实现

先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。**如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?**set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的!

缓存雪崩问题

解决方案:

1、也是像解决缓存穿透一样加锁排队。

2、建立备份缓存,缓存A和缓存B,A设置超时时间,B不设值超时时间,先从A读缓存,A没有读B,并且更新A缓存和B缓存;

搜索引擎用了什么数据结构

倒排索引相当于创建了关键词目录,记录了哪个单词被哪些文章包含。当我们要搜索找到有“运动”的文章时,先去关键词目录找,找到在1,2,3,5,7,8这几页,然后直接把书翻到这些页就能获取到相应的内容了。

为什么要使用跳表不用B+树

因为B+树的原理是 叶子节点存储数据,非叶子节点存储索引,B+树的每个节点可以存储多个关键字,它将节点大小设置为磁盘页的大小,充分利用了磁盘预读的功能。每次读取磁盘页时就会读取一整个节点,每个叶子节点还有指向前后节点的指针,为的是最大限度的降低磁盘的IO;因为数据在内存中读取耗费的时间是从磁盘的IO读取的百万分之一

而Redis是 内存中读取数据,不涉及IO,因此使用了跳表; 

跳表原理:https://www.cnblogs.com/jamaler/p/11397338.html

跳表是使用空间换取时间的一个算法

zookeeper分布式锁

项目里用到的分布式锁

答:当时redis是主从架构的,没用redlock,用的是setnx的原子性

Reids三种不同删除策略

定时删除:在设置键的过期时间的同时,创建一个定时任务,当键达到过期时间时,立即执行对键的删除操作

惰性删除:放任键过期不管,但在每次从键空间获取键时,都检查取得的键是否过期,如果过期的话,就删除该键,如果没有过期,就返回该键

定期删除:每隔一点时间,程序就对数据库进行一次检查,删除里面的过期键,至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。

解决redis aof文件过大的问题、

AOF rewrite重写:
(1) 随着AOF文件越来越大,里面会有大部分是重复命令或者可以合并的命令(100次incr = set key 100)
(2) 重写的好处:减少AOF日志尺寸,减少内存占用,加快数据库恢复时间。

1,redis主进程fork一个子进程
2,子进程根据当前内存的数据,构建一个新的日志,写入一个新的AOF文件中
3,这段时间内,redis接收到的client的修改操作,都会在内存中新起一个日志文件去进入,并同步到旧的AOF文件中
4,当子进程完成了任务,redis就会把新的日志文件追加到新的AOF文件中
5,使用新的AOF文件替换旧的AOF文件

https://blog.csdn.net/weixin_45154837/article/details/100568263

一致性hash算法为什么要使用

按照一定的策略将数据尽可能均匀分布到所有的存储节点上去,使得系统具有良好的负载均衡性能和扩展性。

https://blog.csdn.net/tianmohust/article/details/6838312

https://www.cnblogs.com/lpfuture/p/5796398.html

cap base

CAP原则是NOSQL数据库的基石。Consistency(一致性)。 Availability(可用性)。Partition tolerance(分区容错性)。

分布式系统的CAP理论:理论首先把分布式系统中的三个特性进行了如下归纳:

  • 一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
  • 可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
  • 分区容忍性(P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。

由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的。所以我们只能在一致性和可用性之间进行权衡,没有NoSQL系统能同时保证这三点。

BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)

Redis String数据类型底层原理是什么?

字符串的encoding有三种方式:

  • int
  • raw
  • embstr

redisobject

redis的sds(简单动态字符串)为redisobkect分配最小的内存空间

int 编码是用来保存整数值,raw编码是用来保存长字符串,而embstr是用来保存短字符串 (44)

embstr对比raw的优点:

  1. embstr创建 字符串对象(redisObject) 的次数只需1次,而raw 是两次(redisObject 和sds 分开分配)。
  2. 同理,embstr调用释放内存的函数也是1次,比raw编码的字符串对象少一次。
  3. 由于 embstr编码redisObject 的内存地址和 sds 的内存地址是连续的,而raw是不连续的,因此存取速度embstr比较快。

https://blog.csdn.net/u014453898/article/details/112292028

https://www.bilibili.com/video/BV1nk4y1m7Yv

https://segmentfault.com/a/1190000020770894 1111

Hash对象的编码可以是ziplist或者hashtable
其中,ziplist底层使用压缩列表实现:

  • 保存同一键值对的两个节点紧靠相邻,键key在前,值vaule在后
  • 先保存的键值对在压缩列表的表头方向,后来在表尾方向

file

hashtable底层使用字典实现,Hash对象种的每个键值对都使用一个字典键值对保存:

  • 字典的键为字符串对象,保存键key
  • 字典的值也为字符串对象,保存键值对的值

file

猜你喜欢

转载自blog.csdn.net/qq_43378019/article/details/113008411