内存数据库可以怎么通过存储引擎处理并发查询?

1、什么是数据库事务?事务的属性?

事务指单个逻辑单元执行的一系列操作,要么都执行,要么都不执行。

一个逻辑单元要成为事务,必须满足ACID的属性,即原子性、一致性、隔离性、持久性

一件事情会有多个动作,必须都执行或都不执行。事务是最小的执行单位,不可分割

对同一个表并发进行多个事务,事务间相互隔离。

一旦事情commit,不可更改,持久生效。

MySQL 默认采用自动提交模式。也就是说,如果不显式使用 START TRANSACTION 语句来开始一个事务,那么每个查询都会被当做一个事务自动提交。

2、并发事务带来哪些问题?

一个事务读取到被另一个事务修改当还未提交的数据,依据“脏数据”所做的操作可能是不正确的。

一个事务修改了被另一个事务修改当还未提交的数据,先提交的事务的修改就会丢失

指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务修改了该数据。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。

一个事务读取一个范围内的数据,另一个事务向这个范围内插入了一些数据,在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

不可重复读和幻读区别:

不可重复读的重点是对单条记录的修改,比如多次读取一条记录发现其中某些列的值被修改;幻读的重点在于对多条记录的新增或者删除,比如多次读取一个范围的记录发现记录增多或减少了。

产生并发不一致性问题主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性。并发控制可以通过来实现,但是加锁操作需要用户自己控制,相当复杂。数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题。

3、事务隔离级别有哪些? MySQL的默认隔离级别是?

SQL 标准定义了四个隔离级别:

最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读

允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生

对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生

最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读

4、事务隔离级别的RR和RC是怎么实现的

1、建表的原则(三大范式)

符合第一范式,且非主属性完全依赖于码,消除了部分依赖 --> 非主属性不能完全依赖于码的一部分,如(A, B)是码,非主属性 C 依赖于 (A, B), 但是如果同时 A -> C, 即 C 又依赖于 A ,那么就存在部分依赖,这时 C 属性应该从表中脱离出来,与 A共同 成为一张表

符合第二范式,且消除传递依赖,也就是每个非主属性都不传递依赖于候选键,即如果存在 A -> B -> C, 这时就存在传递依赖,C 应该从表中脱离出来 与 B 共同形成一张表。

符合3NF,并且,消除每一个属性对候选键的传递依赖

2、MySQL外连接知道吗?左外连接和右外连接是什么,有什么区别?什么是内连接,完全连接

外连接分为左外连接和右外连接

左(外)连接,左表(a_table)的记录将会全部表示出来,而右表(b_table)只会显示符合搜索条件的记录。右表记录不足的地方均为NULL

与左(外)连接相反,右(外)连接,左表(a_table)只会显示符合搜索条件的记录,而右表(b_table)的记录将会全部表示出来。左表记录不足的地方均为NULL。

完全连接就是左表和右表都是展示所有记录

内连接是左右表都只显示符合搜索条件的记录

索引是在存储引擎层实现的,而不是在服务器层实现的,所以不同存储引擎具有不同的索引类型和实现。

1. 通过建立索引树,从上往下搜索索引树大大减少了服务器需要扫描的数据行数。

2. 帮助服务器避免进行排序和分组,以及避免创建临时表(B+Tree 索引是有序的,可以用于 ORDER BY 和 GROUP BY 操作。临时表主要是在排序和分组过程中创建,不需要排序和分组,也就不需要创建临时表)。

3. 将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,会将相邻的数据都存储在一起)。

为什么不对表的每个列创建一个索引

1. 索引会占内存空间

2. 维护索引和修改索引有一定的开销

    1、越小的数据类型通常更好:越小的数据类型通常在磁盘、内存和CPU缓存中都需要更少的空间,处理起来更快。

    2、简单的数据类型更好:整型数据比起字符,处理开销更小,因为字符串的比较更复杂。

    3、尽量避免NULL:应该指定列为NOT nuLL,在MySQL中, 含有空值的列很难进行查询优化,因为它们使得索引、索引的统计信息以及比较运算更加复杂

什么场景不适合创建索引

    第一,对于那些在查询中很少使用或者参考的列不应该创建索引。这是因 为,既然这些列很少使用到,因此有索引或者无索引,并不能提高查询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求。

    第二,对于那 些只有很少数据值的列也不应该增加索引。因为本来结果集合就是相当于全表查询了,所以没有必要。这是因为,由于这些列的取值很少,例如人事表的性别列,在查询的结果中,结果集的数据行占了表中数据行的很大比 例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度。

    第三,对于那些定义为text, image和bit数据类型的列不应该增加索引。这是因为,这些列的数据量要么相当大,要么取值很少。

    第四,当修改开销远远大于检索开销时,不应该创建索 引。这是因为,修改性能和检索性能是互相矛盾的。当增加索引时,会提高检索性能,但是会降低修改性能。当减少索引时,会提高修改性能,降低检索性能。因 此,当修改性能远远大于检索性能时,不应该创建索引。

什么样的字段适合创建索引

1、表的主键、外键必须有索引;外键是唯一的,而且经常会用来查询

2、数据量超过300的表应该有索引;

3、经常与其他表进行连接的表,在连接字段上应该建立索引;经常连接查询,需要有索引

4、经常出现在Where子句中的字段,加快判断速度,特别是大表的字段,应该建立索引,建立索引,一般用在select ……where f1 and f2 ,我们在f1或者f2上建立索引是没用的。只有两个使用联合索引才能有用

5、经常用到排序的列上,因为索引已经排序。

6、经常用在范围内搜索的列上创建索引,因为索引已经排序了,其指定的范围是连续的

所谓的索引失效指的是:假如or连接的俩个查询条件字段中有一个没有单列索引的话,引擎会放弃索引而产生全表扫描。

所谓聚集和非聚集:非聚集索引叶子页包含一个指向表中的记录的指针地址,记录的物理顺序和索引的顺序不一致;聚集索引则数据行和键值一起保存在叶子页  而且记录的排列顺序与索引的排列顺序一致。

由于InnoDB正式按照 聚集索引的结构来存储表的,聚簇索引的索引是主键,所以只能故一张表只能有一个聚簇索引。辅助索引的存在不影响聚簇索引中数据的组织,所以一张表可以有多个辅助索引

InnoDB 的主键索引是 聚簇索引, 辅助索引是 非聚簇索引,叶子结点存储的的是主键和关键字。

MyISAM 的主键索引和辅助索引都是 非聚簇索引。

1.数据访问更快,因为聚簇索引将索引和数据保存在同一个B+树中,因此从聚簇索引中获取数据比非聚簇索引更快,

2.  聚簇索引对于主键的排序查找和范围查找速度非常快

   1.  插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,我们一般都会定义一个自增的ID列为主键

   2.  更新主键的代价很高,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新。

   3.二级索引访问需要两次索引查找,第一次找到主键值,第二次根据主键值找到行数据。

mysql的索引分为单列索引(主键索引,唯一索引,普通索引)和组合索引。
单列索引:一个索引只包含一个列,一个表可以有多个单列索引。
组合索引:一个组合索引包含两个或两个以上的列,

对于列的值较长,比如BLOB、TEXT、VARCHAR,就必须建立前缀索引,即将值的前一部分作为索引。这样既可以节约空间,又可以提高查询效率。但无法使用前缀索引做 ORDER BY 和 GROUP BY,也无法使用前缀索引做覆盖扫描。

索引的字段正好是覆盖查询语句[select子句]与查询条件[Where子句]中所涉及的字段。能通过检索索引就可以读取想要的数据,那就不需要再到数据表中读取行了

 覆盖索引是一种非常强大的工具,能大大提高查询性能,只需要读取索引而不需要读取数据,有以下优点:

 1、索引项通常比记录要小,所以MySQL访问更少的数据。

 2、索引都按值得大小存储,相对于随机访问记录,需要更少的I/O。

 3、数据引擎能更好的缓存索引,比如MyISAM只缓存索引。

 4、覆盖索引对InnoDB尤其有用,因为InnoDB使用聚集索引组织数据,如果二级索引包含查询所需的数据,就不再需要在聚集索引中查找了。

 1、覆盖索引也并不适用于任意的索引类型,索引必须存储列的值。

 3、不同的存储引擎实现覆盖索引都是不同的,并不是所有的存储引擎都支持覆盖索引。

 4、如果要使用覆盖索引,一定要注意SELECT列表值取出需要的列,不可以SELECT * ,因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降。

顾名思义是最左优先,以最左边的为起点任何连续的索引都能匹配上,
注:如果第一个字段是范围查询需要单独建一个索引
注:在创建联合索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。这样的话扩展性较好,比如 userid 经常需要作为查询条件,而 mobile 不常常用,则需要把 userid 放在联合索引的第一位置,即最左边

同时存在联合索引和单列索引(字段有重复的),这个时候查询mysql会怎么用索引呢?

这个涉及到mysql本身的查询优化器策略了,当一个表有多条索引可走时, Mysql 根据查询语句的成本来选择走哪条索引;

当创建(a,b,c)联合索引时,相当于创建了(a)单列索引,(a,b)联合索引以及 (a,b,c)联合索引,想要索引生效的话,只能使用 a和a,b和a,b,c三种组合;当然,我们上面测试过,a,c组合也可以,但实际上只用到了a的索引,c并没有用到!

利用索引中的附加列,您可以缩小搜索的范围,但使用一个具有两列的索引 不同于使用两个单独的索引。复合索引的结构与电话簿类似,人名由姓和名构成,电话簿首先按姓氏对进行排序,然后按名字对有相同姓氏的人进行排序。如果您知道姓,电话簿将非常有用;如果您知道姓和名,电话簿则更为有用,但如果您只知道名不姓,电话簿将没有用处。

所以说创建复合索引时,应该仔细考虑列的顺序。对索引中的所有列执行搜索或仅对前几列执行搜索时,复合索引非常有用;仅对后面的任意列执行搜索时,复合索引则没有用处。

复合索引与单列索引的比较:

1. 如果表中大多数都是单条件查询,那用单列索引更划得来

2. 有多条件联合查询时最好建联合索引,多个单列索引在多条件查询时优化器会选择最优索引策略,可能只用一个索引,也可能将多个索引全用上! 但多个单列索引底层会建立多个B+索引树,比较占用空间,也会浪费一定搜索效率,

1、需要加索引的字段,要在where条件中
2、数据量少的字段不需要加索引;因为建索引有一定开销,如果数据量小则没必要建索引(速度反而慢)
3、避免在where子句中使用or来连接条件,因为如果俩个字段中有一个没有索引的话,引擎会放弃索引而产生全表扫描
4、联合索引比对每个列分别建索引更有优势,因为索引建立得越多就越占磁盘空间,在更新数据的时候速度会更慢。另外建立多列索引时,顺序也是需要注意的,应该将严格的索引放在前面,这样筛选的力度会更大,效率更高

是大多数 MySQL 存储引擎的默认索引类型。

因为不再需要进行全表扫描,只需要对树进行搜索即可,所以查找速度快很多。

因为 B+ Tree 的有序性,所以除了用于查找,还可以用于排序和分组。

可以指定多个列作为索引列,多个索引列共同组成键。

适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。如果不是按照索引列的顺序进行查找,则无法使用索引。

InnoDB 的 B+Tree 索引分为主索引和辅助索引。主索引的叶子节点 data 域记录着完整的数据记录,这种索引方式被称为聚簇索引。因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。

辅助索引的叶子节点的 data 域记录着主键的值,因此在使用辅助索引进行查找时,需要先查找到主键值,然后再到

哈希索引能以 O(1) 时间进行查找,但是失去了有序性:

只支持精确查找,无法用于部分查找和范围查找。

InnoDB 存储引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B+Tree 索引之

上再创建一个哈希索引,这样就让 B+Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。

3、BST(二叉查找树)

  1.vs二分查找,BST在左右子树节点个数差不多时,查找性能逼近二分查找,但在增删节点时,BST需要的内存比二分查找少。2.缺点:平衡性差,动态增删节点可能导致退化为链表,查找效率降低。
3.4AVL树vsRBtree: avl树是严格平衡树,而rbtree是弱平衡树,都是通过旋转来保持平衡,而在增删节点时,严格平衡树旋转的次数比弱平衡旋转的次数多,当搜索节点的次数远远大于增删节点的次数时,旋转AVL树,当搜索节点的次数与增删节点的次数差不多时选择RBtree效率高。

4、磁盘读取及预读的过程及时间消耗?

定位柱面时间(最长)、旋转至扇区时间、读写扇区时间

1.每个节点最多有 m 个子树
2.若根不是叶子结点,则根节点至少有两个子树
3.分支节点至少拥有m/2棵子树(除根和叶子)
4.所有叶子节点都在同一层,这些叶子结点不存储有效的信息

5. 每个节点最多可以有m-1个 key 并且升序排列,相同数量的 key 在btree中生成的节点要远远小于二叉搜索树节点,相差的节点数目正比于树的高度正比与磁盘io的次数,达到一定数量时,性能差异明显。

6、为什么btree查找效率高?

多路查找-->降低树的高度-->减少磁盘io的次数-->节省磁盘访问的时间-->更快定位到数据库文件

查找效率高有两个原因,一是多路性,每个结点有若干个关键字,相同数量的 key 在btree中生成的节点要远远小于二叉搜索树节点,相差的节点数目正比于树的高度正比与磁盘io的次数,达到一定数量时,性能差异明显。二是平衡性,所以他效率稳定,不像二叉查找树那样会退化成链表。

1. 叶子节点包含了所有关键字信息以及指向这些关键字记录的指针,并且叶子节点大小本身就是从小到大的顺序链接。

2. 所有的非终端结点可以看成是索引部分,不含有效信息 (而B 树的非终节点也包含需要查找的有效信息)

9、 为什么b+tree比btree更适合做文件的索引、数据库索引?

1.btree在提高磁盘 io 性能同时并没有解决元素遍历效率低下的问题,b+tree只要遍历叶子节点就可遍历整棵树。
2.在数据库中基于范围的查找很频繁,btree每次都要从根节点查,效率低。b+tree只要找到范围左边界的叶子结点,可以顺着叶子结点,找到相应范围的所有元素。

由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当

真实数据库中的B+树应该是非常扁平的,也就是说高度非常小,也就说叉数非常多,每个结点的字树非常多,而且B+树的索引节点是非常小的,一次性可以加载到内存,这样就可以用少量的内存换取只需一次访存即可获取到数据的恐怖效率。

B+树的叉数可以达到1000多叉,存储 22G 容量的表高度也才3层,一次访存即可获取数据。

10、b+树和b树分别有什么应用

B树和B+树大量应用在数据库和文件系统当中。但是多采用B+树,文件系统和数据库的索引都是存在硬盘上的,并且如果数据量大的话,不一定能一次性加载到内存中。

因为如果采用b树的话,在数据量不是很多的情况下,数据都会“挤在”一个结点里面。这个时候遍历效率就退化成了链表。

4. 解释一下什么是池化设计思想。什么是数据库连接池?为什么需要数据库连接池?

池话设计应该不是一个新名词。我们常见的如java线程池、jdbc连接池、redis连接池等就是这类设计的代表实现。这种设计会初始预设资源,解决的问题就是抵消每次获取资源的消耗,如创建线程的开销,获取远程连接的开销等。就好比你去食堂打饭,打饭的大妈会先把饭盛好几份放那里,你来了就直接拿着饭盒加菜即可,不用再临时又盛饭又打菜,效率就高了。除了初始化资源,池化设计还包括如下这些特征:池子的初始值、池子的活跃值、池子的最大值等,这些特征可以直接映射到java线程池和数据库连接池的成员属性中。——这篇文章对介绍的还不错,直接复制过来,避免重复造轮子了。

数据库连接本质就是一个 socket 的连接。数据库服务端还要维护一些缓存和用户权限信息之类的 所以占用了一些内存。我们可以把数据库连接池是看做是维护的数据库连接的缓存,以便将来需要对数据库的请求时可以重用这些连接。为每个用户打开和维护数据库连接,尤其是对动态数据库驱动的网站应用程序的请求,既昂贵又浪费资源。在连接池中,创建连接后,将其放置在池中,并再次使用它,因此不必建立新的连接。如果使用了所有连接,则会建立一个新连接并将其添加到池中。连接池还减少了用户必须等待建立与数据库的连接的时间。

   这种设计会初始预设资源,解决的问题就是抵消每次获取资源和释放资源造成的开销。连接池也是这样,预先创建好一个连接池,在池中创建一定数量的连接,每当用户需要连接数据库,就从池中取出一个连接,使用完毕之后放回池中,这既可以减少连接创建和释放的开销,便于连接的管理,也可以降低用户等待数据库的延迟。

SQL注入是普通常见的网络攻击方式之一,它的原理是通过在参数中输入特殊符号,来篡改并通过程序SQL语句的条件判断。

1. 不允许带有特殊字符

2. 对单引号或双引号进行转义

3. 对 sql 语句进行预编译,因为SQL注入攻击只对SQL语句的编译过程有破坏作用,进行预编译后,传入的参数只作为字符串,不会再进行一次编译,SQL注入攻击也就失效了

MySQL是由连接池、管理工具和服务、SQL接口、解析器、优化器、缓存、存储引擎、文件系统组成。

由于每次建立连接需要消耗很多时间,连接池的作用就是将这些连接缓存下来,下次可以直接用已经建立好的连接,提升服务器性能。

系统管理和控制工具,例如备份恢复、Mysql复制、集群等

接受用户的SQL命令,并且返回用户需要查询的结果。比如select from就是调用SQL Interface

SQL命令传递到解析器的时候会被解析器验证和解析。比如验证是否符合语法树等

解析器是由Lex和YACC实现的,是一个很长的脚本, 主要功能:

a . 将SQL语句分解成数据结构,并将这个结构传递到后续步骤,以后SQL语句的传递和处理就是基于这个结构的

b. 如果在分解构成中遇到错误,那么就说明这个sql语句是不合理的

查询优化器,SQL语句在查询之前会使用查询优化器对查询进行优化。他使用的是“选取-投影-联接”策略进行查询。

这个select 查询先根据where 语句进行选取,而不是先将表全部查询出来以后再进行gender过滤

这个select查询先根据uid和name进行属性投影,而不是将属性全部取出以后再进行过滤

将这两个查询条件联接起来生成最终查询结果

查询缓存,如果查询缓存有命中的查询结果,查询语句就可以直接去查询缓存中取数据。

通过LRU算法将数据的冷端溢出,未来得及时刷新到磁盘的数据页,叫脏页。

这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key缓存,权限缓存等

通过 show engines; 可以查看数据库的储引擎插件

负责MySQL中数据的存储与提取。 服务器中的查询执行引擎通过API与存储引擎进行通信,通过接口屏蔽了不同存储引擎之间的差异。关系数据库中数据的存储是以表的形式存储的,所以说存储的一张张的表,而不是一个个的数据库。MySQL采用插件式的存储引擎,所以只要给数据库提供插件,就可以增加存储引擎,MySql数据库提供了多种存储引擎。用户可以根据不同的需求为数据表选择不同的存储引擎,用户也可以根据自己的需要编写自己的存储引擎。甚至一个库中不同的表使用不同的存储引擎,这些都是允许的。

由于该存储引擎不支持事务、也不支持外键,所以访问速度较快。因此当对事务完整性没有要求并以访问为主的应用适合使用该存储引擎。

.frm文件:与表相关的元数据信息都存放在frm文件,包括表结构的定义信息等。

.MYD文件:MyISAM存储引擎专用,用于存储MyISAM表的数据

.MYI文件:MyISAM存储引擎专用,用于存储MyISAM表的索引相关信息

mysql 5.5版本以后默认的存储引擎
由于该存储引擎在事务上具有优势,即支持具有提交、回滚及崩溃恢复能力等事务特性,他在运行时会在内存中建立缓冲池,用于缓冲数据和索引。支持行锁,并发度高。主键索引为聚簇索引,所以比MyISAM存储引擎占用更多的磁盘空间。因此当需要频繁的更新、删除操作,同时还对事务的完整性要求较高,需要实现并发控制,建议选择。

.frm文件:与表相关的元数据信息都存放在frm文件,包括表结构的定义信息等。

.ibd文件:存放innodb表的数据文件。

MEMORY存储引擎存储数据的位置是内存,因此访问速度最快,但是安全上没有保障。适合于需要快速的访问或临时表。

黑洞存储引擎,写入的任何数据都会消失,可以应用于主备复制中的分发主库。

存储引擎的另一个知识总结

ISAM被设计为适合处理读频率远大于写频率这样的情况,因此ISAM以及后来的MyISAM都没有考虑对事物的支持,排除了TPM,不需要事务记录,ISAM的查询效率相当可观,而且内存占用很少。

MyISAM在继承了这类优点的同时,与时俱进地提供了大量实用的新特性和相关工具。例如考虑到并发控制,提供了表级锁

InnoDB被设计成适用于高并发读写的情况,支持兼容ACID的事务(类似于PostgreSQL),以及参数 完整性(即对外键的支持)。一般来说,如果需要事务支持,并且有较高的并发读写频率,InnoDB是不错的选择。

InnoDB是一个事务型的存储引擎,支持回滚,设计目标是处理大量数据时提供高性能的服务,它在运行时会在内存中建立缓冲池,用于缓冲数据和索引

1、支持事务处理、ACID事务特性;
2、实现了SQL标准的四种隔离级别;
3、支持行级锁和外键约束,行锁优点是粒度小,适用于高并发的频繁表修改,高并发使性能优于 MyISAM。缺点是系统消耗较大。4、可以利用事务日志进行数据恢复。

1. 因为它没有保存表的行数,当使用COUNT统计时会扫描全表。

2、索引不仅缓存自身,也缓存数据,相比 MyISAM 需要更大的内存。

2.因为它保存了表的行数,当使用COUNT统计时不会扫描全表;

1、锁级别为表锁,表锁优点是开销小,加锁快;缺点是锁粒度大,发生锁冲动概率较高,容纳并发能力低,这个引擎适合查询为主的业务。
2、此引擎不支持事务,也不支持外键。
 

MyISAM适合:(1)做很多count 的计算;(2)插入不频繁,查询非常频繁;(3)没有事务。
InnoDB适合:(1)可靠性要求比较高,或者要求事务;(2)表更新和查询都相当的频繁,并且表锁定的机会比较大的情况。

OLTP(联机事务处理)和OLAP(联机分析处理)

OLTP用于存储和管理日常操作的数据;

OLAP用于分析这些数据

性能调优,无疑是个庞大的话题,也是很多项目中非常重要的一环,性能调优难做是众所周知的,毕竟性能调优涵盖的面实在是太多了,在这里我就大概的讲一下企业中最常用的四种调优——JVM调优、MySQL调优、Nginx调优以及Tomcat调优,一家之言,有什么说的不对的还请多包涵补充。

篇幅所限,有些东西是肯定写不到的,所以本文只是挑了一些重要部分来剖析,如果需要完整详细的掌握性能调优,可以来领取系统整理的

话不多说,坐稳扶好,发车喽!

1、JVM类加载机制详解

如下图所示,JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程。

在加载阶段,虚拟机需要完成以下三件事情:

1)通过一个类的全限定名来获取定义此类的二进制字节流。注意这里的二进制字节流不一定非得要从一个Class文件获取,这里既可以从ZIP包中读取(比如从jar包和war包中读取),也可以从网络中获取,也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将JSP文件转换成对应的Class类)。

2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

3)在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。

相对于类加载过程的其他阶段,加载阶段(准确地说,是加载阶段中获取类的二进制字节流的动作)是开发期可控性最强的阶段,因为加载阶段既可以使用系统提供的类加载器来完成,也可以由用户自定义的类加载器去完成,开发人员们可以通过定义自己的类加载器去控制字节流的获取方式。

这一阶段的主要目的是为了确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。注意这里所说的初始值概念,比如一个类变量定义为:

实际上变量v在准备阶段过后的初始值为0而不是8080,将v赋值为8080的putstatic指令是程序被编译后,存放于类构造器<client>方法之中,这里我们后面会解释。

解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是class文件中的:

下面我们解释一下符号引用和直接引用的概念:

  • 符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
  • 直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。

初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由JVM主导。到了初始阶段,才开始真正执行类中定义的Java程序代码。

初始化阶段是执行类构造器<clint>方法的过程。<clint>方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证<clint>方法执行之前,父类的<clint>方法已经执行完毕。p.s: 如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成<clint>()方法。

注意以下几种情况不会执行类初始化:

  • 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
  • 定义对象数组,不会触发该类的初始化。
  • 常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发定义常量所在的类。
  • 通过类名获取Class对象,不会触发类的初始化。
  • 通过Class.forName加载指定类时,如果指定参数initialize为false时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。

虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块被称为“类加载器”。

对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性。这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提之下才有意义,否则,即使这两个类是来源于同一个Class文件,只要加载它们的类加载器不同,那这两个类就必定不相等。这里所指的“相等”,包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,也包括了使用instanceof关键字做对象所属关系判定等情况。如果没有注意到类加载器的影响,在某些情况下可能会产生具有迷惑性的结果。

JVM提供了3种类加载器:

JVM通过双亲委派模型进行类的加载,当然我们也可以通过继承java.lang.ClassLoader实现自定义的类加载器。

双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码。

双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

采用双亲委派的一个好处是比如加载位于rt.jar包中的类java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个Object对象。

在有些情境中可能会出现要我们自己来实现一个类加载器的需求,由于这里涉及的内容比较广泛,我想以后单独写一篇文章来讲述,不过这里我们还是稍微来看一下。我们直接看一下jdk中的ClassLoader的源码实现:

  • 如果没有被加载过执行if (c == null)中的程序,遵循双亲委派的模型,首先会通过递归从父加载器开始找,直到父类加载器是Bootstrap ClassLoader为止。
  • 最后根据resolve的值,判断这个class是否需要解析。

而上面的findClass()的实现如下,直接抛出一个异常,并且方法是protected,很明显这是留给我们开发者自己去实现的,这里我们以后我们单独写一篇文章来讲一下如何重写findClass方法来实现我们自己的类加载器。

这几个存储区最主要的就是栈区和堆区,那么什么是栈什么是堆呢?说的简单点,栈里面存放的是基本的数据类型和引用,而堆里面则是存放各种对象实例的。

堆与栈分开设计是为什么呢?

  • 栈存储了处理逻辑、堆存储了具体的数据,这样隔离设计更为清晰
  • 堆与栈分离,使得堆可以被多个栈共享。
  • 栈保存了上下文的信息,因此只能向上增长;而堆是动态分配

线程私有,生命周期与线程相同。每个方法执行的时候都会创建一个栈帧(stack frame)用于存放 局部变量表、操作栈、动态链接、方法出口。

存放对象实例,所有的对象的内存都在这里分配。垃圾回收主要就是作用于这里的。

  • 堆得内存由-Xms指定,默认是物理内存的1/64;最大的内存由-Xmx指定,默认是物理内存的1/4。
  • 默认空余的堆内存小于40%时,就会增大,直到-Xmx设置的内存。具体的比例可以由-XX:MinHeapFreeRatio指定
  • 空余的内存大于70%时,就会减少内存,直到-Xms设置的大小。具体由-XX:MaxHeapFreeRatio指定。

因此一般都建议把这两个参数设置成一样大,可以避免JVM在不断调整大小。

这里记录了线程执行的字节码的行号,在分支、循环、跳转、异常、线程恢复等都依赖这个计数器。

类型信息、字段信息、方法信息、其他信息

有两种方式,一种是引用计数(但是无法解决循环引用的问题);另一种就是可达性分析。

判断对象可以回收的情况:

  • 显示的把某个引用置位NULL或者指向别的对象

3.2 垃圾回收的方法

这种方法优点就是减少停顿时间,但是缺点是会造成内存碎片。

这种方法不涉及到对象的删除,只是把可用的对象从一个地方拷贝到另一个地方,因此适合大量对象回收的场景,比如新生代的回收。

这种方法可以解决内存碎片问题,但是会增加停顿时间。

最后的这种方法是前面几种的合体,即目前JVM主要采取的一种方法,思想就是把JVM分成不同的区域。每种区域使用不同的垃圾回收方法。

上面可以看到堆分成两个个区域:

  • 新生代(Young Generation):用于存放新创建的对象,采用复制回收方法,如果在s0和s1之间复制一定次数后,转移到年老代中。这里的垃圾回收叫做minor GC;
  • 年老代(Old Generation):这些对象垃圾回收的频率较低,采用的标记整理方法,这里的垃圾回收叫做 major GC。

这里可以详细的说一下新生代复制回收的算法流程:

  • 然后再看from survivor,如果次数达到年老代的标准,就复制到年老代中;如果没有达到则复制到to survivor中,如果to survivor满了,则复制到年老代中。

这种收集器就是以单线程的方式收集,垃圾回收的时候其他线程也不能工作。

以多线程的方式进行收集

大致的流程为:初始标记--并发标记--重新标记--并发清除

大致的流程为:初始标记--并发标记--最终标记--筛选回收

篇幅所限,关于类字节码文件、调优工具以及GC日志分析这里就不写了,如果有感兴趣的朋友可以点击,里面会有详细叙述。

二、Mysql性能调优

1、SQL执行原理详解

1.1.1 关系引擎:主要作用是优化和执行查询。

(1)命令解析器:检查语法和转换查询树。

(2)查询执行器:优化查询。

(3)查询优化器:负责执行查询。

1.1.2 存储引擎:管理所有数据及涉及的IO

(1)事务管理器:通过锁来管理数据及维持事务的ACID属性。

(2)数据访问方法:处理对行、索引、页、行版本、空间分配等的I/O请求。

包含SQL Server的所有缓存。如计划缓存和数据缓存。

记录事务的所有更改。保证事务ACID属性的重要组件。

数据库的物理存储文件。

建立在客户端和服务器之间的网络连接的协议层

1.2 查询的底层原理

1.2.1 当客户端执行一条T-SQL语句给SQL Server服务器时,会首先到达服务器的网络接口,网络接口和客户端之间有协议层。

1.2.2 客户端和网络接口之间建立连接。使用称为“表格格式数据流”(TDS) 数据包的 Microsoft 通信格式来格式化通信数据。

1.2.3 客户端发送TDS包给协议层。协议层接收到TDS包后,解压并分析包里面包含了什么请求。

1.2.4 命令解析器解析T-SQL语句。命令解析器会做下面几件事情:

(1)检查语法。发现有语法错误就返回给客户端。下面的步骤不执行。

(2)检查缓冲池(Buffer Pool)中是否存在一个对应该T-SQL语句的执行计划缓存。

(3)如果找到已缓存的执行计划,就从执行计划缓存中直接读取,并传输给查询执行器执行。

(4)如果未找到执行计划缓存,则在查询执行器中进行优化并产生执行计划,存放到Buffer Pool中。

当Buffer Pool中没有该SQL语句的执行计划时,就需要将SQL传到查询优化器,通过一定的算法,分析SQL语句,产生一个或多个候选执行计划。选出开销最小的计划作为最终执行计划。然后将执行计划传给查询执行器。

1.2.6 查询执行器执行查询

查询执行器把执行计划通过OLE DB接口传给存储引擎的数据访问方法。

1.2.7 数据访问方法生成执行代码

数据访问方法将执行计划生成SQL Server可操作数据的代码,不会实际执行这些代码,传送给缓冲区管理器来执行。

1.2.8 缓冲区管理器读取数据。

先在缓冲池的数据缓存中检查是否存在这些数据,如果存在,就把结果返回给存储引擎的数据访问方法;如果不存在,则从磁盘(数据文件)中读出数据并放入数据缓存中,然后将读出的数据返回给存储引擎的数据访问方法。

1.2.9 对于读取数据,将会申请共享锁,事务管理器分配共享锁给读操作。

1.2.10存储引擎的数据访问方法将查询到的结果返回关系引擎的查询执行器。

1.2.11 查询执行器将结果返回给协议层。

1.2.12 协议层将数据封装成TDS包,然后协议层将TDS包传给客户端。

一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性能问题,在生产环境中,我们遇到最多的,也是最容易出问题的,还是一些复杂的查询操作,因此对查询语句的优化显然是重中之重。说起加速查询,就不得不提到索引了。

索引在MySQL中也叫做“键”或者"key"(primary key,unique key,还有一个index key),是存储引擎用于快速找到记录的一种数据结构。索引对于良好的性能非常关键,尤其是当表中的数据量越来越大时,索引对于性能的影响愈发重要,减少io次数,加速查询。(其中primary key和unique key,除了有加速查询的效果之外,还有约束的效果,primary key 不为空且唯一,unique key 唯一,而index key只有加速查询的效果,没有约束效果)

索引优化应该是对查询性能优化最有效的手段了。索引能够轻易将查询性能提高好几个数量级。
索引相当于字典的音序表,如果要查某个字,如果不使用音序表,则需要从几百页中逐页去查。

强调:一旦为表创建了索引,以后的查询最好先查索引,再根据索引定位的结果去找数据

索引的目的在于提高查询效率,与我们查阅图书所用的目录是一个道理:先定位到章,然后定位到该章下的一个小节,然后找到页数。相似的例子还有:查字典,查火车车次,飞机航班等,下面内容看不懂的同学也没关系,能明白这个目录的道理就行了。 那么你想,书的目录占不占页数,这个页是不是也要存到硬盘里面,也占用硬盘空间。

你再想,你在没有数据的情况下先建索引或者说目录快,还是已经存在好多的数据了,然后再去建索引,哪个快,肯定是没有数据的时候快,因为如果已经有了很多数据了,你再去根据这些数据建索引,是不是要将数据全部遍历一遍,然后根据数据建立索引。你再想,索引建立好之后再添加数据快,还是没有索引的时候添加数据快,索引是用来干什么的,是用来加速查询的,那对你写入数据会有什么影响,肯定是慢一些了,因为你但凡加入一些新的数据,都需要把索引或者说书的目录重新做一个,所以索引虽然会加快查询,但是会降低写入的效率。

2.4 索引的数据结构

前面讲了索引的基本原理,数据库的复杂性,又讲了操作系统的相关知识,目的就是让大家了解,现在我们来看看索引怎么做到减少IO,加速查询的。任何一种数据结构都不是凭空产生的,一定会有它的背景和使用场景,我们现在总结一下,我们需要这种数据结构能够做些什么,其实很简单,那就是:每次查找数据时把磁盘IO次数控制在一个很小的数量级,最好是常数数量级。那么我们就想到如果一个高度可控的多路搜索树是否能满足需求呢?就这样,b+树应运而生。

3、Mysql锁机制与事务隔离级别详解

3.1 为什么需要学习数据库锁知识

即使我们不会这些锁知识,我们的程序在一般情况下还是可以跑得好好的。因为这些锁数据库隐式帮我们加了

  • MyISAM在执行查询语句SELECT前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁

只会在某些特定的场景下才需要手动加锁,学习数据库锁知识就是为了:

  • 能让我们在特定的场景下派得上用场
  • 在跟别人聊数据库技术的时候可以搭上几句话
  • 构建自己的知识库体系!在面试的时候不虚

首先,从锁的粒度,我们可以分成两大类:

    • 开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突概率高,并发度最低
    • 开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高

不同的存储引擎支持的锁粒度是不一样的:

  • InnoDB行锁和表锁都支持

InnoDB只有通过索引条件检索数据才使用行级锁,否则,InnoDB将使用表锁

  • 也就是说,InnoDB的行锁是基于索引的

表锁下又分为两种模式

  • 从下图可以清晰看到,在表读锁和表写锁的环境下:读读不阻塞,读写阻塞,写写阻塞

    • 读读不阻塞:当前用户在读数据,其他的用户也在读数据,不会加锁
    • 读写阻塞:当前用户在读数据,其他的用户不能修改当前用户读的数据,会加锁!
    • 写写阻塞:当前用户在修改数据,其他的用户不能修改当前用户正在修改的数据,会加锁!
    • 写锁和其他锁均布兼容,只有读和读之间兼容

从上面已经看到了:读锁和写锁是互斥的,读写操作是串行

  • 如果某个进程想要获取读锁,同时另外一个进程想要获取写锁。在mysql里边,写锁是优先于读锁的
  • MyISAM可以支持查询和插入操作的并发进行。可以通过系统变量concurrent_insert来指定哪种模式,在MyISAM中它默认是:如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。
  • 但是InnoDB存储引擎是不支持的

    数据库事务有不同的隔离级别,不同的隔离级别对锁的使用是不同的,锁的应用最终导致不同事务的隔离级别

  • 事务的隔离级别就是通过锁的机制来实现,只不过隐藏了加锁细节
    在表锁中我们读写是阻塞的,基于提升并发性能的考虑,MVCC一般读写是不阻塞的(所以说MVCC很多情况下避免了加锁的操作)
  • MVCC实现的读写不阻塞正如其名:多版本并发控制--->通过一定机制生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取。从用户的角度来看,好像是数据库可以提供同一数据的多个版本。
  • 我们在初学的时候已经知道,事务的隔离级别有4种:
  • 串行,避免以上的情况!

    Read uncommitted会出现的现象--->脏读:一个事务读取到另外一个事务未提交的数据

  • 例子:A向B转账,A执行了转账语句,但A还没有提交事务,B读取数据,发现自己账户钱变多了!B跟A说,我已经收到钱了。A回滚事务【rollback】,等B再查看账户的钱时,发现钱并没有多。
  • 出现脏读的本质就是因为操作(修改)完该数据就立马释放掉锁,导致读的数据就变成了无用的或者是错误的数据。
  • 就是把释放锁的位置调整到事务提交之后,此时在事务提交前,其他进程是无法对该行数据进行读取的,包括任何操作
    但Read committed出现的现象--->不可重复读:一个事务读取到另外一个事务已经提交的数据,也就是说一个事务可以看到其他事务所做的修改
  • 注:A查询数据库得到数据,B去修改数据库的数据,导致A多次查询数据库的结果都不一样【危害:A每次查询的结果都是受B的影响的,那么A查询出来的信息就没有意思了】
    上面也说了,Read committed是语句级别的快照!每次读取的都是当前最新的版本!

Repeatable read避免不可重复读是事务级别的快照!每次读取的都是当前事务的版本,即使被修改了,也只会读取当前事务版本的数据。

呃...如果还是不太清楚,我们来看看InnoDB的MVCC是怎么样的吧(摘抄《高性能MySQL》)

InnoDB 的 MVCC,是通过在每行记录后面保存两个隐藏的列来实现的,这两个列一个保存了行的创建时间,一个保存了行的过期(删除)时间。当然存储的并不是真正的时间值,而是系统版本号。每开始一个新的事务,系统版本号会自动递增,事务开始的时候的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。
  InnoDB 会根据以下两个条件检查每行记录:
    a. InnoDB 只查找版本早于当前事务版本的数据行,这样可以确保事务读取到的数据,要么是在事务开始前就存在的,要么是事务自身插入或更新的
    b. 行的删除版本要么未定义要么大于当前事务版本号,确保了事务读取到的行,在事务开始前未被删除

至于虚读(幻读):是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。

  • 注:和不可重复读类似,但虚读(幻读)会读到其他事务的插入的数据,导致前后读取不一致

nginx 常用做静态内容服务和反向代理服务器,以及页面前端高并发服务器。适合做负载均衡,直面外来请求转发给后面的应用服务(tomcat什么的)

2、熟练掌握Nginx核心配置

实际运营时一般设置为很接近CPU的线程数,比如说CPU是8线程,一般设置为6、7。

我们自己开发、用时一般设置为1、2即可,不然太吃资源。

r是read,limit是限制,单个worker进程最多只能打开指定个数的文件,超过便不能再读取文件。打开一次文件便会产生一个文件描述符。

此设置是为了防止单个worker进程消耗大量的系统资源。

不管设置多少个worker进程,主进程只有一个(即运行sbin/nginx)。

主进程由Linux当前登录的账户运行,工作进程由user指令指定的账户运行。第一列数字是进程的PID。

nginx工作进程和nginx主进程都是Linux中的进程,但主进程(父进程)可以控制worker进程(子进程)的开启、结束。

master进程可以看做老板,worker进程可以看做打工仔。

惊群现象:一个网络连接到来,所有沉睡的worker进程都会被唤醒,但只用一个worker处理连接,其余被唤醒的worker又开始沉睡。

设置为on:要使用几个worker就唤醒几个,不全部唤醒,默认值就是on。

设置为off:一律全部唤醒。一片worker醒来是要占用资源的,会影响性能。

一个连接要用一个文件来保存,

Linux默认单个进程最多只能打开1024个文件描述符,需要我们修改下Linux的资源限制,设置单个进程可打开的最大文件描述符数:

ulimit命令可以限制单个进程使用的系统资源的尺寸、数量,包括内存、缓冲区、套接字、栈、队列、CPU占用时间等。

可以有多个server块。

http全局块的配置作用于整个http块(http块内的所有server块)。

sendfile on; #开启文件高效传输模式,默认为off,不开启。 #tcp_nopush on; #如果响应体积过大,默认会分多个批次传输给客户端,设置为on会一次性传给客户端,可防止网络阻塞   #tcp_nodelay on; #如果响应体积过小,默认会放在缓冲区,缓冲区满了才刷给客户端,设置为on直接刷给客户端,可防止网络阻塞 keepalive_timeout 65; #与客户端保持连接的超时时间,在指定时间内,如果客户端没有向Nginx发送任何数据(无活动),Nginx会关闭该连接。 gzip on; #使用gzip模块压缩响应数据。启用后响应体积变小,传输到客户端所需时间更少,节省带宽,但nginx压缩、客户端解压都有额外的时间、资源开销,nginx的负担也会加大。 upstream servers{ #设置负载均衡器,可同时设置多个负载均衡器。负载均衡器的名称中不能含有_,此处指定名称为servers server_name localhost; #虚拟主机(即域名),要在dns上注册过才有效,没有注册的话只能用localhost。可指定多个虚拟主机,空格分开即可          #设置默认处理方式     location / { #如果url没有指定匹配,就使用默认的处理方式来处理 #指定要使用的负载均衡器,转发给其中某个节点处理。如不设置此项(代理),则默认nginx本身作为web服务器,直接处理请求,会到root指定目录下找请求的文件 }

设置的错误页面是nginx作为web服务器(处理静态资源)出现问题时,比如nginx上的静态资源找不到,返回给客户端的。

如果是tomcat出现的问题,比如tomcat上的xxx.jsp找不到,返回的是tomcat的错误页面,不是nginx的。

如果使用nginx本身要作为web服务器,直接处理客户端请求,比如处理静态资源,要将全局块中user 设置为运行nginx的账户(即当前登陆Linux的账户),

否则worker进程(默认nobody账户)无权限读取当前账户(即运行nginx主进程的账户)的静态资源,客户端会显示403禁止访问。

可以使用正则表达式来过滤客户端ip,也可以把客户端的ip过滤规则写在文件中,然后包含进来。

3、掌握Nginx负载算法配置

将列表中的服务器排成一圈,从前往后,找空闲的服务器来处理请求。

轮询适合服务器性能差不多的情况。默认使用的就是轮询,不需要设置什么。

设置权重,权重大的轮到的机会更大,适合服务器性能有明显差别的情况。

根据客户端ip的hash值来转发请求,同一客户端(ip)的请求都会被转发给同一个服务器处理,可解决session问题。

根据请求的url来转发,会将url相同的请求转发给同一服务器处理。

一直处理某个url,服务器上一般都有该url的缓存,可直接从缓存中获取数据作为响应返回,减少时间开销。

(5)fair(第三方)

根据服务器响应时间来分发请求,响应时间短的分发的请求多。

fair 公平,nginx先计算每个节点的平均响应时间,响应时间短说明该节点负载小(闲),要多转发给它;响应时间长说明该节点负载大,要少转发给它。

ip_hash、url_hash都是使用特定节点来处理特定请求,如果特定节点故障,nginx会剔除不可用的节点,将特定请求转发给其它节点处理,url_hash影响不大,但ip_hash会丢失之前的session数据。

  • maxThreads:Tomcat使用线程来处理接收的每个请求。这个值表示Tomcat可创建的最大的线程数。
  • acceptCount:指定当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理。
  • connnectionTimeout:网络连接超时,单位:毫秒。设置为0表示永不超时,这样设置有隐患的。通常可设置为30000毫秒。
  • 2、Tomat的4种连接方式对比

    tomcat默认的http请求处理模式是bio(即阻塞型,下面第二种),每次请求都新开一个线程处理。下面做一个介绍

    我们姑且把上面四种Connector按照顺序命名为 NIO, HTTP, POOL, NIOP。测试性能对比,数值为每秒处理的请求数,越大效率越高

  • 得出结论:NIOP > NIO > POOL > HTTP 虽然Tomcat默认的HTTP效率最低,但是根据测试次数可以看出是最稳定的。且这只是一个简单页面测试,具体会根据复杂度有所波动。

配置参考:Linux系统每个进程支持的最大线程数是1000,windos是2000。具体跟服务器的内存,Tomcat配置的数量有关联。

Tomcat的部署,是一台服务器部署一个Tomcat(上线多个项目),还是一台服务器部署多个tomact(每个tomcat部署1~n个项目)。多核必选配置多个Tomcat,微服务多线程的思想模式。

需要把这个两个参数值调大,大小的可以根据服务器内存的大小进行调整。例如:

服务器是8G 内存,跑了3个tomcat服务,给分配了2G的内存,因为还有其他进程。


本篇文章写到这里差不多就结束了,当然也有很多东西还没有写到,不过限于篇幅也是没辙,我整理了很详细的,需要的朋友直接点击领取就可以了。

最后,码字不易,所以,可以点个赞和收藏吗兄弟们!


MySQL 索引使用的注意事项

说说 SQL 优化之道

一、一些常见的SQL实践

(2)前导模糊查询不能使用索引

而非前导模糊查询则可以:

(3)数据区分度不大的字段不宜使用索引

原因:性别只有男,女,每次过滤掉的数据很少,不宜使用索引。

经验上,能过滤80%数据时就可以使用索引。

对于订单状态,如果状态值很少,不宜使用索引,如果状态值很多,能够过滤大量数据,则应该建立索引。

(4)在属性上进行计算不能命中索引

即使date上建立了索引,也会全表扫描,可优化为值计算:

二、并非周知的SQL实践

(5)如果业务大部分是单条查询,使用Hash索引性能更好,例如用户中心
(6)允许为null的列,查询有潜在大坑

单列索引不存null值,复合索引不存全为null的值,如果列允许为null,可能会得到“不符合预期”的结果集

如果name允许为null,索引不存储null值,结果集中不会包含这些记录。

所以,请使用not null约束以及默认值

(7)复合索引最左前缀,并不是值SQL语句的where顺序要和复合索引一致

也能命中索引,满足复合索引最左前缀

不能命中索引,不满足复合索引最左前缀

(8)使用ENUM而不是字符串

ENUM保存的是TINYINT,别在枚举中搞一些“中国”“北京”“技术部”这样的字符串,字符串空间又大,效率又低。

三、小众但有用的SQL实践

(9)如果明确知道只有一条结果返回,limit 1能够提高效率

原因:你知道只有一条结果,但数据库并不知道,明确告诉它,让它主动停止游标移动

(10)把计算放到业务层而不是数据库层,除了节省数据的CPU,还有意想不到的查询缓存优化效果

这不是一个好的SQL实践,应该优化为:

多次调用,传入的SQL相同,才可以利用查询缓存

(11)强制类型转换会全表扫描

你以为会命中phone索引么?大错特错了?

末了,再加一条,不要使用select *(潜台词,文章的SQL都不合格 ==),只返回需要的列,能够大大的节省数据传输量,与数据库的内存使用量哟。

MySQL 遇到的死锁问题

所谓死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

产生死锁的四个必要条件:

一个资源每次只能被一个进程使用。

(2) 请求与保持条件:

一个进程因请求资源而阻塞时,对已获得的资源保持不放。

—— 拿着红钥匙的人在没有归还红钥匙的情况下,又提出要蓝钥匙

进程已获得的资源,在未使用完之前不能被剥夺,只能在使用完时由自己释放。

—— 除非归还了钥匙,不然一直占用着钥匙

(4) 循环等待条件:

若干进程之间形成一种头尾相接的循环等待资源关系(资源的环形链)

——拿着红钥匙的人在等蓝钥匙,同时那个拿着蓝钥匙的人在等红钥匙

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

--方法一 . 破坏互斥条件---

只有一副钥匙,这是形成死锁的最关键的原因。

显然,如果我们能在两个线程跑之前,能给每个线程单独拷贝一份钥匙的副本,就能有效的避免死锁了。

当然,这种方法试用范围并不广。因为有时如果系统拷贝那副钥匙的成本极高,而线程又很多的话,这种方法就不适用了。

---方法二 . 破坏请求和保持条件---

任何一个线程“贪心”,都可能会导致死锁。大致就是说有了一把钥匙还没还就要另一把。

这里我们可以通过规定在任何情况下,一个线程获取一把钥匙之后,必须归还了钥匙之后才能请求另一把钥匙,就可以有效解决这个问题。

---方法三 . 破坏不剥夺条件---

除非线程自己还钥匙,否则线程会一直占有钥匙,是形成不可剥夺条件的原因。

这里,我们可以通过设置一个“最长占用时间”的阈值来解决这个问题——如果过了10分钟仍然没有进入下一个步骤,则归还已有的钥匙。

这样的话,两个线程都能取到所需的钥匙继续下去了。

---方法四 . 破坏环路等待条件---

会出现死锁的两两组合,一定都是一个线程先取了红钥匙而另一个线程先取了蓝钥匙,从而导致了可能形成了“环路等待”。

所以我们可以强制规定线程取钥匙的顺序只能是 “先取蓝钥匙再取红钥匙”的话,就能避免死锁了。

下列方法有助于最大限度地降低死锁:

(1)按同一顺序访问对象。

(2)避免事务中的用户交互。

(3)保持事务简短并在一个批处理中。

(4)使用低隔离级别。

MongoDB的文档必须有一个_id键。目的是为了确认在集合里的每个文档都能被唯一标识。

ObjectId 采用12字节的存储空间,每个字节两位16进制数字,是一个24位的字符串。

前四位是时间戳,可以提供秒级别的唯一性。

接下来三位是所在主机的唯一标识符,通常是机器主机名的散列值。

接下来两位是产生ObjectId的PID,确保同一台机器上并发产生的ObjectId是唯一的。

前九位保证了同一秒钟不同机器的不同进程产生的ObjectId时唯一的。

最后三位是自增计数器,确保相同进程同一秒钟产生的ObjectId是唯一的。

这个是用的最多的。加上分词插件、拼音插件什么的可以做成强大的全文搜索引擎。

挺奇葩的用法,因为ES存数相同数据,更费空间,不过确实不错,因为他的强大统计分析汇总能力,再加上分布式P2P扩展能力,现在硬件又那么便宜,所以就有人拿来当数据库了。

日志系统。logstash,不用解释了吧。可以实时动态分析数据,很是爽。

倒排索引(英语:Inverted index),也常被称为反向索引、置入档案或反向档案

是一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。

它是文档检索系统中最常用的数据结构。

有两种不同的反向索引形式:

一条记录的水平反向索引(或者反向档案索引)包含每个引用单词的文档的列表。

一个单词的水平反向索引(或者完全反向索引)又包含每个单词在一个文档中的位置。

简单的来说,反模式是指在对经常面对的问题经常使用的低效,不良,或者有待优化的设计模式/方法。甚至,反模式也可以是一种错误的开发思想/理念。

在这里我举一个最简单的例子:

在面向对象设计/编程中,有一条很重要的原则, 单一责任原则(Single responsibility principle)。其中心思想就是对于一个模块,或者一个类来说,这个模块或者这个类应该只对系统/软件的一个功能负责,而且该责任应该被该类完全封装起来。当开发人员需要修改系统的某个功能,这个模块/类是最主要的修改地方。

相对应的一个反模式就是上帝类(God Class),通常来说,这个类里面控制了很多其他的类,同时也依赖其他很多类。整个类不光负责自己的主要单一功能,而且还负责了其他很多功能,包括一些辅助功能。

很多维护老程序的开发人员们可能都遇过这种类,一个类里有几千行的代码,有很多功能,但是责任不明确单一。单元测试程序也变复杂无比。维护/修改这个类的时间要远远超出其他类的时间。

很多时候,形成这种情况并不是开发人员故意的。很多情况下主要是由于随着系统的年限,需求的变化,项目的资源压力,项目组人员流动,系统结构的变化而导致某些原先小型的,符合单一原则类慢慢的变的臃肿起来。

最后当这个类变成了维护的噩梦(特别是原先熟悉的开发人员离职后),重构该类就变成了一个不容易的工程。

在Redis中有五种数据类型

代表一个 value 对象具体是何种数据类型。

是不同数据类型在 redis 内部的存储方式

如果是 int 则代表实际 redis 内部是按数值型类存储和表示这个字符串的

当然前提是这个字符串本身可以用数值表示,比如:"123" "456"这样的字符串。

只有打开了 Redis 的虚拟内存功能,此字段才会真正的分配内存,该功能默认是关闭状态的。

Redis 使用 redisObject 来表示所有的 key/value 数据是比较浪费内存的,当然这些内存管理成本的付出主要也是为了给 Redis 不同数据类型提供一个统一的管理接口,实际作者也提供了多种方法帮助我们尽量节省内存使用。

过期数据的清除从来不容易,为每一条key设置一个timer,到点立刻删除的消耗太大,每秒遍历所有数据消耗也大,Redis使用了一种相对务实的做法:

当client主动访问key会先对key进行超时判断,过时的key会立刻删除。

它会在Master的后台,每秒10次的执行如下操作: 随机选取100个key校验是否过期,如果有25个以上的key过期了,立刻额外随机选取下100个key(不计算在10次之内)。

可见,如果过期的key不多,它最多每秒回收200条左右,如果有超过25%的key过期了,它就会做得更多,但只要key不被主动get,它占用的内存什么时候最终被清理掉只有天知道。

Key的长度限制:Key的最大长度不能超过1024字节,在实际开发时不要超过这个长度,但是Key的命名不能太短,要能唯一标识缓存的对,作者建议按照在关系型数据库中的库表唯一标识字段的方式来命令Key的值,用分号分割不同数据域,用点号作为单词连接。

Key的查询:Keys,返回匹配的key,支持通配符如 ”keys a*” 、”keys a?c”,但不建议在生产环境大数据量下使用。

对Key对应的Value进行的排序:Sort命令对集合按数字或字母顺序排序后返回或另存为list,还可以关联到外部key等。因为复杂度是最高的O(N+Mlog(M))*(N是集合大小,M 为返回元素的数量),有时会安排到slave上执行。

Key的超时操作:Expire(指定失效的秒数)/ExpireAt(指定失效的时间戳)/Persist(持久化)/TTL(返回还可存活的秒数),关于Key超时的操作。默认以秒为单位,也有p字头的以毫秒为单位的版本

随着数据量的增长,MySQL 已经满足不了大型互联网类应用的需求。因此,Redis 基于内存存储数据,可以极大的提高查询性能,对产品在架构上很好的补充。在某些场景下,可以充分的利用 Redis 的特性,大大提高效率。

对于热点数据,缓存以后可能读取数十万次,因此,对于热点数据,缓存的价值非常大。例如,分类栏目更新频率不高,但是绝大多数的页面都需要访问这个数据,因此读取频率相当高,可以考虑基于 Redis 实现缓存。

例如验证码只有60秒有效期,超过时间无法使用,或者基于 Oauth2 的 Token 只能在 5 分钟内使用一次,超过时间也无法使用。

出于减轻服务器的压力或防止恶意的洪水攻击的考虑,需要控制访问频率,例如限制 IP 在一段时间的最大访问量。

数据统计的需求非常普遍,通过原子递增保持计数。例如,应用数、资源数、点赞数、收藏数、分享数等。

社交属性相关的列表信息,例如,用户点赞列表、用户分享列表、用户收藏列表、用户关注列表、用户粉丝列表等,使用 Hash 类型数据结构是个不错的选择。

记录用户判定信息的需求也非常普遍,可以知道一个用户是否进行了某个操作。例如,用户是否点赞、用户是否收藏、用户是否分享等。

在某些场景中,例如社交场景,通过交集、并集和差集运算,可以非常方便地实现共同好友,共同关注,共同偏好等社交关系。

按照得分进行排序,例如,展示最热、点击率最高、活跃度最高等条件的排名列表。

按照时间顺序排列的最新动态,也是一个很好的应用,可以使用 Sorted Set 类型的分数权重存储 Unix 时间戳进行排序。

Redis 能作为一个很好的消息队列来使用,依赖 List 类型利用 LPUSH 命令将数据添加到链表头部,通过 BRPOP 命令将元素从链表尾部取出。同时,市面上成熟的消息队列产品有很多,例如 RabbitMQ。因此,更加建议使用 RabbitMQ 作为消息中间件。

用作消息队列中防止数据丢失的解决方法

如果消费者把job给Pop走了又没处理完就死机了怎么办?

加多一个sorted set,分发的时候同时发到list与sorted set,以分发时间为score,用户把job做完了之后要用ZREM消掉sorted set里的job,并且定时从sorted set中取出超时没有完成的任务,重新放回list。 如果发生重复可以在sorted set中在查询确认一遍,或者将消息的消费接口设计成幂等性。

Redis 如何实现持久化

RDB持久化方式会在一个特定的间隔保存那个时间点的一个数据快照。

AOF持久化方式则会记录每一个服务器收到的写操作。在服务启动时,这些记录的操作会逐条执行从而重建出原来的数据。写操作命令记录的格式跟Redis协议一致,以追加的方式进行保存。

Redis的持久化是可以禁用的,就是说你可以让数据的生命周期只存在于服务器的运行时间里。

两种方式的持久化是可以同时存在的,但是当Redis重启时,AOF文件会被优先用于重建数据。

Redis 集群方案与实现

由客户端决定key写入或者读取的节点。

包括jedis在内的一些客户端,实现了客户端分片机制。

将请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上执行。

Redis 为什么是单线程的

因为CPU不是Redis的瓶颈。Redis的瓶颈最有可能是机器内存或者网络带宽。(以上主要来自官方FAQ)既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。

热点数据,缓存才有价值

频繁修改的数据,看情况考虑使用缓存

碰到这种情况,一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。

加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量

假设在高并发下,缓存重建期间key是锁着的,这是过来1000个请求999个都在阻塞的。同样会导致用户等待超时,这是个治标不治本的方法。

在大促或者某些特殊情况下,某些页面占用了一些稀缺服务资源,在紧急情况下可以对其整个降级,以达到丢卒保帅;

比如商品详情页中的商家部分因为数据错误了,此时需要对其进行降级;

比如商品详情页上有推荐信息/配送至等异步加载的请求,如果这些信息响应慢或者后端服务有问题,可以进行降级;

比如渲染商品详情页时需要调用一些不太重要的服务:相关分类、热销榜等,而这些服务在异常情况下直接不获取,即降级即可;

比如多级缓存模式,如果后端服务有问题,可以降级为只读缓存,这种方式适用于对读一致性要求不高的场景;

比如秒杀抢购,我们可以只进行Cache的更新,然后异步同步扣减库存到DB,保证最终一致性即可,此时可以将DB降级为Cache。

在大促活动时,可以将爬虫流量导向静态页或者返回空数据,从而保护后端稀缺资源。

自动降级是根据系统负载、资源使用情况、SLA等指标进行降级。

当访问的数据库/http服务/远程调用响应慢或者长时间响应慢,且该服务不是核心服务的话可以在超时后自动降级;

比如商品详情页上有推荐内容/评价,但是推荐内容/评价暂时不展示对用户购物流程不会产生很大的影响;

对于这种服务是可以超时降级的。如果是调用别人的远程服务,和对方定义一个服务响应最大时间,如果超时了则自动降级。

如果没问题会在数据库中添加一个用户记录

如果是用邮箱注册会给你发送一封注册成功的邮件,手机注册则会发送一条短信

以便将来向他推荐一些志同道合的人,或向那些人推荐他

发送给用户一个包含操作指南的系统通知

消息的重发补偿解决思路

可靠消息服务定时查询状态为已发送并超时的消息

可靠消息将消息重新投递到 MQ 组件中

下游应用监听消息,在满足幂等性的条件下,重新执行业务。

下游应用通知可靠消息服务该消息已经成功消费。

通过消息状态确认和消息重发两个功能,可以确保上游应用、可靠消息服务和下游应用数据的最终一致性。

我们实际系统中有很多操作,是不管做多少次,都应该产生一样的效果或返回一样的结果。

增加分区数,比较担心的是较多分区数导致延迟增加。

针对这个问题,其实我们主要应该搞明白我们性能的瓶颈到底在什么地方。

绝大部分时候,以及绝大部分应用,性能瓶颈都是在生产者和消费者上。

由于分区数增多导致分区复制延迟带来的性能影响往往不是瓶颈(可能复制延迟从几毫秒变化到几十毫秒)。这时候可以“爽快”的在设计之初就设置较多的分区数(考虑能够支撑近2年的生产和消费吞吐量的增速)

在设计分区数的时候,尽量设置的多点(当然也不要太大,太大影响延迟),从而提升生产和消费的并行度,避免消费太慢导致消费堆积。

瓶颈在消费吞吐量的时候,增加批次也可以改善性能

如果一些消费者组中的消费者线程还是有1个消费者线程消费多个分区的情况,建议增加消费者线程。尽量1个消费者线程对应1个分区,从而发挥现有分区数下的最大并行度。

通过轮询所有队列的方式来确定消息被发送到哪一个队列(负载均衡策略)。订单号相同的消息会被先后发送到同一个队列中,

在获取到路由信息以后,会根据算法来选择一个队列,同一个OrderId获取到的肯定是同一个队列。

我要回帖

更多关于 存储引擎节点 的文章

 

随机推荐