---慢查询
#查询慢日志是否开启(slow_query_log)、查询慢日志保存文件(slow_query_log_file)
SHOW VARIABLES LIKE '%slow%';
#查慢查询触发的时间
SHOW VARIABLES LIKE '%long_query_time%';
#查询mysql安装目录
SELECT @@basedir;
#查询mysql数据文件保存目录
SELECT @@datadir;
#查询所有表状态
SHOW TABLE STATUS;
####
mysql 索引优化
框架底层
大数据高并发解决方案
负载均衡
主从复制
微服务
设计模式
分库分表
索引优化
####
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by涉及的列上建立索引。
2.应尽量避免在 where 子句中使用 !=或<> 操作符,否则将引擎放弃使用索引而进行全表扫描。 YES
3.应尽量避免在 where 子句中对字段进行 null 值 判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
select id from t where num=0
4.应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num=10 or num=20
可以这样查询:
select id from t where num=10
union all
select id from t where num=20
5.下面的查询也将导致全表扫描:YES
select id from t where name like '%abc%'
对于 like '..%' (不以 % 开头),可以应用 colunm上的index
6.in 和 not in 也要慎用,否则会导致全表扫描,如: IN(NO),NOT IN(YES)
select id from t where num in(1,2,3)
对于连续的数值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
7.如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:
select id from t where num=@num
可以改为强制查询使用索引:
select id from t with(index(索引名)) where num=@num
8.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如: YES
select id from t where num/2=100
应改为:
select id from t where num=100*2
9.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如: YES
select id from t where substring(name,1,3)='abc'--name以abc开头的id
select id from t where datediff(day,createdate,'2005-11-30')=0--'2005-11-30'生成的id
应改为:
select id from t where name like 'abc%'
select id from t where createdate>='2005-11-30' and createdate<'2005-12-1'
10.不要在 where 子句中的“=”【左边】进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。 YES
11.在使用索引字段作为条件时,如果该索引是【复合索引】,那么必须使用到该索引中的【第一个字段】作为条件时才能保证系统使用该索引,否则该索引将不会被使用。并且应【尽可能】的让字段顺序与索引顺序相一致。(字段顺序也可以不与索引顺序一致,但是一定要包含【第一个字段】。) YES
12.不要写一些没有意义的查询,如需要生成一个空表结构:
select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(...)
13.很多时候用 exists 代替 in 是一个好的选择:
select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)
14.并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。
15.索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
16.应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。
17.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
18.尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
19.任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
20.尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。
21.避免频繁创建和删除临时表,以减少系统表资源的消耗。
22.临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。
23.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
24.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
25.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
26.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
27.与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
28.在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。
29.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
30.尽量避免大事务操作,提高系统并发能力。
上面有几句写的有问题。
第二方面:select Count (*)和Select Count(1)以及Select Count(column)区别
一般情况下,Select Count (*)和Select Count(1)两着返回结果是一样的
假如表沒有主键(Primary key), 那么count(1)比count(*)快,
如果有主键的話,那主键作为count的条件时候count(主键)最快
如果你的表只有一个字段的话那count(*)就是最快的
count(*) 跟 count(1) 的结果一样,都包括对NULL的统计,而count(column) 是不包括NULL的统计
第三方面:索引列上计算引起的索引失效及优化措施以及注意事项
创建索引、优化查询以便达到更好的查询优化效果。但实际上,MySQL有时并不按我们设计的那样执行查询。MySQL是根据统计信息来生成执行计划的,这就涉及索引及索引的刷选率,表数据量,还有一些额外的因素。
Each table index is queried, and the best index is used unless the optimizer believes that it is more efficient to use a table scan. At one time, a scan was used based on whether the best index spanned more than 30% of the table, but a fixed percentage no longer determines the choice between using an index or a scan. The optimizer now is more complex and bases its estimate on additional factors such as table size, number of rows, and I/O block size.
简而言之,当MYSQL认为符合条件的记录在30%以上,它就不会再使用索引,因为mysql认为走索引的代价比不用索引代价大,所以优化器选择了自己认为代价最小的方式。事实也的确如此
是MYSQL认为记录是30%以上,而不是实际MYSQL去查完再决定的。都查完了,还用什么索引啊?!
MYSQL会先估算,然后决定是否使用索引。
###
28种 SQL 查询语句的优化方法:
1、应尽量避免在 where 子句中使用 != 或者 <> 操作符,否则将引擎放弃使用索引而进行全表扫描。
2、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null;
可以在 num 上设置默认值 0 ,确保表中 num 列没有 null 值,然后这样查询:
select id from t where num = 0;
3、查询语句的查询条件中只有OR关键字,并且OR前后的两个条件中的列都是索引时,查询中才使用索引。否则,查询将不使用索引。
4、对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
5、前导模糊查询不能利用索引(like '%XX'或者like '%XX%'),可以使用索引覆盖避免。
6、in 和 not in 也要慎用,否则会导致全表扫描。如:
select id from t where num in(1,2,3)
对于连续的数值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
7、如果在 where 子句中使用参数,也会导致全表扫描。因为 SQL 只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择到运行时;它必须在编译时进行选择。然而,如果在编译时简历访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:
select id from t where num=@num
可以改为强制查询使用索引:
select id from t with(index(索引名)) where num=@num
8、应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2 = 100;
应改为:
select id from t where num = 100 * 2;
9、应尽量避免在 where 子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name, 1, 3) = ’abc’–name; //以abc开头的id
select id from t where datediff(day,createdate,’2005-11-30′) = 0–’2005-11-30′; //生成的id
应改为:
select id from t where name like ‘abc%’
select id from t where createdate >= ’2005-11-30′ and createdate < ’2005-12-1′;
10、不要在 where 子句中的 “=” 左边进行函数,算术运算或者其他表达式运算,否则系统将可能无法正确使用索引。
11、在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
12、很多时候用 exists 代替 in 是一个好的选择:
select num from a where num in(select num from b);
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num);
13、索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
14、并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段 sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。
15、尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会 逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
16、尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。较一次就够了。
17、任何地方都不要使用 select * from t ,用具体的字段列表代替 *,不要返回用不到的任何字段。
18、尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。
19、避免频繁创建和删除临时表,以减少系统表资源的消耗。
20、临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。
21、在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先 create table,然后 insert。
22、如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
23、尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
24、使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
25、与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时 间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
26、在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。
27、尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
28、尽量避免大事务操作,提高系统并发能力。
###
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:select id from t where num=0
3.应尽量避免在 where 子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行全表扫描。
4.应尽量避免在 where 子句中使用or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:select id from t where num=10 or num=20可以这样查询:select id from t where num=10 union all select id from t where num=20
5.in 和 not in 也要慎用,否则会导致全表扫描,如:select id from t where num in(1,2,3) 对于连续的数值,能用 between 就不要用 in 了:select id from t where num between 1 and 3
6.下面的查询也将导致全表扫描:select id from t where name like ‘%李%’若要提高效率,可以考虑全文检索。
7. 如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然 而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:select id from t where num=@num可以改为强制查询使用索引:select id from t with(index(索引名)) where num=@num
8.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:select id from t where num/2=100应改为:select id from t where num=100*2
9.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:select id from t where substring(name,1,3)=’abc’ ,name以abc开头的id应改为:
select id from t where name like ‘abc%’
10.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
11.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
12.不要写一些没有意义的查询,如需要生成一个空表结构:select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(…)
13.很多时候用 exists 代替 in 是一个好的选择:select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)
14.并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。
15. 索引并不是越多越好,索引固然可 以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为
insert 或 update
时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有
必要。
16. 应尽可能的避免更新 clustered 索引数据列,因为 clustered
索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新
clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。
17.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
18.尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
19.任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
20.尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。
21.避免频繁创建和删除临时表,以减少系统表资源的消耗。
22.临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。
23.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
24.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
25.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
26.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
27. 与临时表一样,游标并不是不可使 用。对小型数据集使用 FAST_FORWARD
游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时
间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
28.在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送DONE_IN_PROC 消息。
29.尽量避免大事务操作,提高系统并发能力。
30.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
###
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在WHERE及ORDER BY涉及的列上建立索引。
缺省情况下建立的索引是非群集索引,但有时它并不是最佳的。在非群集索引下,数据在物理上随机存放在数据页上。合理的索引设计要建立在对各种查询的分析和预测上。一般来说:
a.有大量重复值,且经常有范围查询(>,=,<=)和ORDER BY、 GROUP BY发生的列,可考虑建立群集索引;
b.经常同时存取多列,且没列都含有重复值,可考虑建立组合索引,选择度高的列建议作为索引的第一个字段;
c.组合索引要尽量使关键查询形成索引覆盖,其前导列一定是使用最频繁的列。索引虽有助于提高性能,但不是索引越多越好,恰好相反,过多的索引会导致系统低效。用户在表中没加进一个索引,维护索引集合就要做相应的更新工作。
2.应尽量避免在WHERE子句中对字段进行NULL值判断,否则将导致引擎放弃使用索引而进行全表扫描;
SQL代码:SELECT id FROM t WHERE num IS NULL;
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
SQL代码:SELECT id FROM t WHERE num = 0;
3.应尽量避免在WHERE子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行全表扫描。
4.应尽量避免在WHERE子句中使用OR来连接条件,否则将导致引擎放弃使用索引而进行全表扫描。
SQL代码:SELECT id FROM t WHERE num = 10 OR num = 20;
可以这样查询:
SQL代码:SELECT id FROM t WHERE num = 10 UNION ALL SELECT id FROM t WHERE num = 20;
5.IN和NOT IN也要慎用,否则会导致全表扫描,如:
SQL代码:SELECT id FROM t WHERE num IN(1,2,3);
对于连续的数值,能用BETWEEN就不要用IN了
SQL代码:SELECT id FROM t WHERE num BETWEEN 1 AND 3;
6.下面的查询也将导致全表扫描:
SQL代码:SELECT id FROM t WHERE name LIKE '%c%';
若要提高效率,可以考虑全文检索。
7.如果在WHERE子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时,它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:
SQL代码:SELECT id FROM t WHERE num = @numl;
可以改为强制查询使用索引:
SQL代码:SELECT id FROM t WITH(INDEX(索引名)) WHERE num = @num;
8.应尽量避免在WHERE子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描:
SQL代码:SELECT id FROM t WHERE num / 20 = 100;
可以这样查询:
SQL代码:SELECT id FROM t WHERE num = 100 * 2;
9.应尽量避免在WHERE子句中对该字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描,如:
SQL代码:SELECT id FROM t WHERE SUBSTRING(name, 1, 3) = 'name';
应改为:
SQL代码:SELECT id FROM t WHERE name LIKE 'abc%';
10.不要在WHERE子句中的=左边进行函数、算数运算或其他表达式运算,否则系统将可能无法正确使用索引。
11.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序一致、
12.不要写一些没有意义的查询,如需要生成一个空表结构:
SQL代码:SELECT col1, col2 INTO #t FROM t WHERE 1 = 0;
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
SQL代码:CREATE TABLE #t(...);
13.很多时候用EXISTS代替IN是个好的选择:
SQL代码:SELECT num FROM a WHERE num IN (SELECT num FROM b);
用下面的语句替换:
SQL代码:SELECT num FROM a WHERE EXISTS (SELECT 1 FROM b WHERE num = a.num);
14.并不是所有的索引都对查询有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段male、female几乎各占一半,那么即使在这一列上建立了索引也对查询效率起不了作用。
15.索引并不是越多越好,索引固然可以提高相应的SELECT的效率,但同时也降低了INSERT及UODATE的效率,因为INSERT或UPDATE时有可能会重建索引,索引怎样检索因需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应该考虑一些不常用到的列上建的索引是否有必要。
16.应尽可能的避免更新CLUSTERED索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。所应用系统需要频繁更新CLUSTERED索引数据列,那么需要考虑是否应将该索引建为CLUSTERED索引。
17.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言,只需要比较一次就够了。
18.尽可能的使用VARCHAR/NVARCHAR代替CHAR/NCAHR,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然是要高一些。
19.任何地方都不要使用SELECT * FROM t;,用具体的字段列表代替“*”,不要返回用不到的任何字段。
20.尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。
21.避免频繁创建和删除临时表,以减少系统表资源的消耗。
22.临时表并不是不可使用,适当地使用塔门可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。
23.在新建临时表时,如果一次性插入数据量很大,那么可以使用SELECT INTO代替CREAT TABEL,避免造成大量LOG,以提高速度,如果数据量不大,为了缓和系统表的资源,应先CREATE TABLE,然后INSERT。
24.如果使用到了临时表,在存储过程的最后务必将所有临时表显式删除,先TRUNCATE TABLE,然后DROP TABLE,这样可以避免系统表的较长时间锁定。
25.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑该下改写。
26.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
27.与临时表一样,游标并不是不可使用,对小型数据集使用FAST_FORWARD游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
28.在所有的存储过程和触发器的开始处设置SET NOCOUNT ON,在结束时设置SET NOCOUNT OFF。无需在执行存储过程和触发器的每个语句后向客户端发送DONE_IN_PROC消息。
29.尽量避免大事务操作,提高系统并发能力。
30.顶起分析表和检查表。
分析表的语法:ANALYZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tb1_name[, tb1_name]...以上语句用于分析和存储表的关键字分布,分析的结果将可以使得系统得到准确的统计信息,使得SQL能够生成正确的执行计划。如果用户感觉实际执行计划并不是预期的执行计划,执行一次分析表可能会解决问题。在分析期间,使用一个读取锁定对表进行锁定。这对于MyISAMDB、BDB和InniDB有作用。
例如分析一个数据表:ANALYZE TABLE table_name
检查表的语法:CHECK TABLE tb1_name[, tb1_name]...[option]...option = {QUICK | FAST | MEDIUM | EXTENDED | CHANGED}
检查表的作用是检查一个或者多个表是否有错误,CHECK TABLE对MyISAM和InnoDB表有作用,对于MyISAM表,关键字统计数据被更新。
CHECK TABLE也可以检查视图是否有错误,比如在视图定义中被引用的表不存在。
31.定期优化表
优化表的语法:OPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tb1_name [, tb1_name]...如果删除了标的一大部分,或者如果已经对含有可变长度行的表(含有VARCHAR、BLOB或者TEXT列的表)进行更多更改,则应使用OPTIMIZE TABLE命令来进行表优化。这个命令可以将表中的空间碎片进行合并,并且可以消除由于删除或者更新造成的空间浪费,但OPTIMIZE TABLE命令只对MyISAM、BDB和InnoDB表起作用。
例如:OPTIMIZE TABLE table_name;
注意:ANALYZE、CHECK、OPTIMIZE执行期间将对表进行锁定,因此一定注意要在MySQL数据库不繁忙的时候执行相关的操作。
32.存储引擎的选择。如果数据表需要事务处理,应该考虑使用InnoDB,因为它完全符合ACID特性。如果不需要事务处理,使用默认存储引擎MyISAM是比较明智的。
MyISAM适用于一些需要大量查询的应用,但其对于有大量写操作并不是很好。设置你只是需要UPDATE一个字段,整个表都会被锁起来,而别的进程,就算是读进程都无法操作直到读操作完成。另外,MyISAM对于SELECT COUNT(*)这类的计算是超快无比的。
33.InnoDB的趋势会是一个非常复杂的存储引擎,对于一些小的应用,它会比MyISAM还慢。但是它支持行锁,于是在写操作比较多的时候,会更优秀。并且,它还支持更多的高级操作,比如,事务。
34.EXPLAIN你的SELECT查询
使用EXPLAIN关键字可以让你知道MySQL是如何处理你的SQL语句的。这可以帮助你分析你的查询语句或是表结构的性能瓶颈。
EXPLAIN的查询结果还会告诉你你的索引主键如何被利用,你的数据表是如何被搜索和排序的……等等,等等。
挑一个你的SELECT语句(推荐挑选那个最复杂的,有多表级联的),把关键字EXPLAIN加到前面。你可以使用phpmyadmin来做这个事。然后,你会看到一张表格。下面的这个示例中,我们忘记加上了GROUP_ID索引,并且有表连接:
35.当是要一行数据是使用LIMIT 1
当你查询表的有些时候,你已经知道结果只会有一条,但因为你可能需要去FETCH游标,或是你也许会去检查返回的记录数。在这种情况下,加上LIMIT 1可能增加性能。这样MySQL引擎会在找到一条数据后停止搜索,而不是继续往后查找下一条符合记录的数据。
没有效率的:
$r = MYSQL_QUERY("SELECT * FROM user WHERE country = 'China'");IF(MYSQL_NUM_ROWS($r) > 0){//...}
有效率的:
$r = MYSQL_QUERY("SELECT * FROM user WHERE country = 'China' LIMIT 1");IF(MYSQL_NUM_ROWS($r) > 0){//...}
36.在JION表的时候使用相同类型的列,并将其索引
如果你的应用程序有很多JION查询,你应该确认两个表中JION字段是被建过索引的。这样MySQL内部会启动为你优化JION的语句的机制。
而且,这些被用来JION的字段,应该是相同的类型的。例如:如果你要把DECIMAL字段和一个INT字段JION在一起,MySQL就无法使用它们的索引。对于那些STRING类型,还需要有相同的字符集才行。
//在STATE中查找company
$r = MYSQL_QUERY("SELECT company_name
FROM users
LEFT JION companies ON (users.state = companies.state)
WHERE users.id = $user_id");
37.千万不要ORDER BY RAND()
想打乱返回的数据行?随机挑一个数据?真不知道谁发明了这种用法,但很多新手很喜欢这样用。但你却不了解这样做有多么可怕的性能问题。
=如果你真的想把返回的数据行打乱了,你有N种方法可以达到这个目的,这样使用只让你的数据库性能惩治书记的下降。这里的问题是:MySQL会不得不去执行RAND()函数(很耗CPU时间),而且这是为了每一行汲取去记行,然后再对其排序。就算是你用了LIMIT 1也无济于事(因为要排序)
下面的示例是随机挑一条记录
//千万不要这样做:
$r = MYSQL_QUERY("SELECT username FROM user ORDER BY RAND() LIMIT 1");
//这样做会更好:
$r = MYSQL_QUERY("SELECT COUNT(*) FROM user");
$d = MYSQL_FETCH_ROW($r);
$rand = MT_RAND(0, $d[0] - 1);
$r = MYSQL_QUERY("SELECT username FROM user LIMIT $rand, 1");
38.永远为每一张表设置一个ID
我们应该为数据库里的每张表都设置一个ID作为其主键,而且最好的是一个INT型的(推荐使用UNSIGNED),并设置上自动增加的AUTO_INCREMENT标志。
就算是你users表有一个主键叫“email”的字段,你也别让它成为主键。使用VARCHAR类型来当主键会使性能下降。另外,在你的程序中,你应该使用表的ID来构造你的数据结构。
而且,在MySQL数据引擎下,还有一些操作需要使用主键,在这些情况下,主键的性能和设置变得非常重要,比如,集群,分区……
在这里,只有一个情况是例外,那就是“关联表”的“外键”。比如:有一个“学生表”有学生的ID,有一个“课程表”有课程ID,那么“成绩表”就是“关联表”了,其关联了学生表和课程表吗,在成绩表中,学生ID和课程ID叫做“外键”其共同组成主键。
39.从PROCEDURE ANALYSE()取得建议
PROCEDURE ANALYSE()会让MySQL帮你去分析你的字段和其实际的数据,并会给你一些有用的建议。只有表中有实际的数据,这些建议才会变得有用,因为要做一些大的决定是需要有数据作为基础的。
例如,如果你创建一个INT字段作为你的主键,然而并没有太多的数据,那么,PROCEDURE ANALYSE()会建议你把这个字段的类型改为MEDIUMINT。或是你使用了一个VARCHAR字段,因为数据不多,你可能会得到一个让你把它改成ENUM的建议。这些建议可能因为数据不够多,所以决策做得不够准。
一定要注意,这些只是建议,只有当你的表里的数据越来越多是,这些建议才会变得准确。一定要记住,你才是最终做决定的人。
40.字段尽可能的使用NOT NULL约束
除非你有一个很特别的原因去使用NULL值,你应该总是让你的字段保持NOT NULL。这看起来好像有点争议,请往下看:
首先,问问自己“Empty”和“NULL”有多大区别(如果是INT,那就是0和NULL)?如果你觉得它们之间没什么区别,那么你就不要使用NULL
。(你知道吗?在Oracle里,NULL和Empty的字符串是一样的!)
不要以为NULL不需要空间,其需要额外的空间,并且,在你进行比较的时候,你的程序会更复杂。当然,这里并不是说你就不要使用NULL了,现实情况是很复杂的,依然在有些情况下,你需要使用NULL值。
下面摘自MySQL自己的文档:
“NULL columns require additional space in the row to record whether their values are NULL. For MyISAM tables, each NULL column takes one bit extra, rounded up to the nearest byte.”
如果你要保存NULL,手动去设置它,而不是把它设为默认。建议使用0、特殊值或者空串代替NULL值
41.Prepared Statements
Prepared Statements很像存储过程,是一种运行在后台的SQL语句集合,我们可以从使用Prepared Statements获得很多好处,无论是性能问题还是安全问题。
Prepared Statements可以检查一些你绑定好的变量,这样可以保护你的程序不会受到“SQL注入式攻击”,当然,你也可以手动地检查你的这些变量,然而,手动的检查容易出问题,而且很经常会被程序员忘了。当我们使用一些framework或是ORM的时候,这样的问题会好一些。
在性能方面,当一个相同的查询被使用多次的时候,这回为你带来客观的性能优势。你可以给这些Prepared Statements定义一些参数,而MySQL只会解析一次。
因为最新版本的MySQL在传输Prepared Statements时使用二进制形式,所以这会使得网络传输非常有效率。
当然,也有一些情况下,我们需要避免使用Prepared Statements,因为其不支持查询缓存。但据说版本5.1后支持了。
42.把IP地址存成UNSIGNED INT
很多程序员都会创建一个VARCHAR(15)字段来存放字符串形式的IP而不是整型的IP。如果你用整型来存放,只需要4个字节,并且你可以有定长的字段。而且,这会为你带来查询上的优势,尤其是当你需要使用这样的WHERE条件:IP BETWEEN ip1 AND ip2。
我们必需要使用UNSIGNED INT因为IP地址会使用整个32位的无符号整型。
而你的查询,你可以使用INET_NTOA()把一个整型转成一个字符串IP。在PHP中,也有这样的函数IP2LONG()和LONG2IP()。
43.固定长度的表会更快
如果表中所有字段都是“固定长度”的,整个表会被认为是“static”或“fixed-length”。例如,表中没有如下类型的字段:VARCHAR,TEXT,BLOB。只要你包括了其中一个这些字段,那么这个表就不是“固定长度静态表”了,这样,MySQL引擎会用另一种方法来处理。
固定长度的表会提高性能,因为MySQL搜索得会更快一些,因为这些固定的长度是很容易计算下一个数据的偏移量的,所以读取的自然也会很快。而如果字段不是定长的,那么,每一次要找下一条的话,需要程序找到主键。
而且,固定长度的表也更容易被缓存和重建。不过,唯一的副作用是,固定长度的字段会浪费一些空间,因为定长的字段无论你用不用,他都要分配那么多的空间。
使用“垂直分割”技术,你可以分割你的表成为两个:一个是定长的,一个是不定长的。
45.垂直分割表
“垂直分割”是一种把数据库中的表按列变成几张表的方法,这样可以降低表的复杂度和字段的数目,从而达到优化的目的。
示例一:在users表中有一个字段是家庭住址,这个字段是可选字段,相比起,而且你在数据库操作的时候除了个人信息外,你并不需要经常读取或是改写这个字段。那么,你为什么不把他放在另一张表中呢?这样会让你的表有更好的性能,大家想想是不是,大量的时候,我对于用户表来说,只有用户ID、用户名、口令和用户角色等会被经常使用。小一点的表总会有好的性能。
示例二:你有一个叫“last_login”的字段,它会在每次用户登录时被更新。但是,每次更新时会导致该表的查询缓存被清空。所以,你可以把这个字段放在另一个表中,这样就不会影响你对用户ID、用户名和用户角色的不停读取了,因为查询缓存会帮你增加很多性能。
另外,你需要注意的是,这些被分出去的字段所形成的表,你不会经常性地去JION他们,不然的话,这样的性能会比不分割时还要差,而且,会是指数级地下降。
46.拆分大的DELETE或INSERT语句
如果你需要在一个在线的网站上去执行一个大的DELETE或INSERT查询,你需要非常小心,要避免你的操作让你的整个网站停止服务,因为这两个操作是会锁表的,表一锁住了,别的操作都进不来了。
Apache会有很多的子进程或线程。所以,其工作起来想当有效率,而我们的服务器也不希望有太多的子进程、线程和数据库链接,这是极大的占服务器资源的事情,尤其是内存。
如果你把你的表锁上一段时间,比如30秒钟,那么对于一个有很高访问量的站点来说,这30秒所积累的访问进程、线程、数据库链接、打开的文件数,可能不仅仅会让你的WEB服务Crash,还可能会让你整台服务器马上挂了。
所以,如果你有一个很大的处理,你一定要把其拆分,使用LIMIT条件是一个很好的办法。下面是一个示例:
WHILE(1){
//每次只做1000条
MYSQL_QUERY("DELETE FROM logs WHERE log_date <= '2009-11-01' LIMIT 1000");
IF(MYSQL_AFFECTED_ROWS() == 0){
//没得删了,退出!
//BREAK;
}
//每次都要休息一会儿
USLEEP(50000)
}
47.越小的列会越快
对于大多数的数据库引擎来说,硬盘操作可能是最重大的瓶颈。所以,把你的数据变得紧凑会对这种情况非常有帮助,因为这减少了对硬盘的访问。
参看MySQL的文档Storage Requirements查看所有的数据类型。
如果一个表只有几列罢了(比如说字典表,配置表),那么,我们就没理由使用INT来做主键,使用MEDIUMINT、SMALLINT、或是更小的TINYINT会更经济一些。如果你不需要记录时间,使用DATE要比DATETIME好很多。
当然,你也需要留够足够的扩展空间,参看Slashdot的例子(2009年11月06日),一个简单的ALTER TABLE语句花了3个多小时,因为里面有一千六百万条数据。
48.使用一个对象关系映射器(Object Relational Mapper)
使用ORM你能获得可靠的性能增长。一个ORM可以做的所有事情,也能被手动地边写出来,但是这需要一个高级专家。
ORM的最重要的是“LAZY LOADING”,也就是说,只有在需要去取值的时候才会去真正地去做。但你也需要小心这种机制的副作用,因为这很有可能会因为要去创建很多很多小的查询,反而会降低性能。ORM还可以把你的SQL语句打包成一个事务,这会比单独执行它们快得多得多。
49.小心“永久链接”
“永久链接”的目的是用来减少重新创建MySQL链接的次数。当一个链接被创建,它会永远处在连接的状态,就算是数据库操作已经结束了。而且,自从我们的Apache开始重用它的子进程后——也就是说,下一次的HTTP请求会重用Apache的子进程,并重用相同的MySQL链接。
50.范围列(>,
51.如果需要在大字段上建立索引,可以考虑使用前缀索引。
建立前缀索引的语法为:
ALTER TABLE table_name ADD KEY(column_name(prefix_length));
52.将大字段、访问频率低的字段拆分到单独的表中存储。分离冷热数据,有利于有效利用缓存,防止读入无用的冷数据,较少磁盘IO,同时保证热数据常驻内存提高缓存命中率。
53.MySQL的新增和修改列的操作相当于重建表,表设计要一步到位,尽量避免大表的DDL操作。(TIPS:可以预定义一些列留作将来业务扩展,如:当前只需要10个字段,考虑到未来发展,可以预留10个字段,表上总共创建20个字段)
54.为了降低索引维护成本,禁止冗余索引,增大IO压力。(a,b,c)、(a,b),后者为冗余索引。可以利用前缀索引来达到加速目的,减轻维护负担。
55.WHERE子句中的数据扫描不超过表总数据量的30%。
如何选择prefic_length的长度,具体参考:前缀索引,一种优化索引大小的解决方案
补充:
在海量查询时尽量少用格式转换。
任何对列的操作都将导致表扫描,它包括数据库教程函数、计算表达式等等,查询时要尽可能将操作移至符号右边。
IN、OR子句常会使用工作表,是索引失效。如果不产生大量重复值,可以考虑把子句拆开。拆开的子句中应该包含索引。
尽量少用CLOB、TEXT、BLOB大类型。
如果你的数据只有你所知的少量的几个量,最好使用ENUM类型。
ENUM类型是非常快和紧凑的。在实际上,其保存的是TINYINT,但其外表上显示为字符串。这样一来,用这个字段来做一些选项列表变得相当完美。
如果你有一个字段,比如“性别”,“国家”,“民族”,“状态”或者“部门”,你知道这些字段的取值是有限而且固定的,那么,你应该使用ENUM而不是VARCHAR。
MySQL也有一个“建议”(见第十条)告诉你怎么去重新组织你的表结构。当你有一个VARCHAR字段时,这建议会告诉你把其改成ENUM类型。使用PROCEDURE ANALYSE()你可以得到相关的建议。
合理运用库、分表、与分区表提高数据存放和提取速度。具体参考:MySQL分表和分区的区别、分库分表区别
###
system const eq_ref ref fulltext ref_or_null index_merge unique_subquery index_subquery range index all
至少达到range,最好达到ref。
--
system const的特例,仅返回一条数据的时候。
const 查找主键索引,返回的数据至多一条(0或者1条)。 属于精确查找
eq_ref 查找唯一性索引,返回的数据至多一条。属于精确查找
ref 查找非唯一性索引,返回匹配某一条件的多条数据。属于精确查找、数据返回可能是多条
range 查找某个索引的部分索引,一般在where子句中使用 < 、>、in、between等关键词。只检索给定范围的行,属于范围查找
index 查找所有的索引树,比ALL要快的多,因为索引文件要比数据文件小的多。
ALL 不使用任何索引,进行全表扫描,性能最差。
从下到上逐渐变好,使用的索引至少要达到range 级别。
###
建主键的时候,应该越小越好,越小的主键将会获得更好的查询性能。
索引会提升查询性能,但是会降低插入、更新和删除的性能。如果执行查询语句的时候更多是和其他列联合查询,建联合索引比给每个column建索引要更好。
如果性能比空间更重要,则可以适当放宽标准化限制,可将一些列信息冗余或者创建汇总表来加快查询速度。
###
###
SAPI: Server Application Programming Interface 服务器端应用编程端口。
在 PHP 应用中,SAPI 是应用层(比如 Apache,Nginx,CLI 等)和 PHP 交互数据的入口。
SAPI 实现了和各种应用层的兼容,应用层可以根据自身情况,定制 SAPI,主要有apache2handler 和 apache2filter,cgi,fastcgi,cli。
fastcgi和php-fpm之间的关系
CGI是个协议,是为了保证web server传递过来的数据是标准格式的,规定要传哪些数据、以什么样的格式传递给后方处理这个请求的协议。
Fastcgi是用来提高CGI程序性能的。标准的CGI对每个请求都会解析php.ini配置文件,初始化执行环境,所以处理每个请求的时间会比较长。而Fastcgi会先启动一个master,解析配置文件,初始化执行环境,然后再启动多个worker。当请求过来时,master会传递给一个worker,然后立即可以接受下一个请求。这样就避免了重复的劳动,效率自然是高。而且当worker不够用时,master可以根据配置预先启动几个worker等着;当然空闲worker太多时,也会停掉一些,这样就提高了性能,也节约了资源。这就是fastcgi对进程的管理。
PHP-FPM是一个实现了Fastcgi的程序。PHP的解释器是php-cgi。php-cgi只是个CGI程序,他自己本身只能解析请求,返回结果,不会进程管理,所以就出现了一些能够调度php-cgi进程的程序。
---
###
XSS跨站脚本攻击(Cross Site Scripting)缩写为CSS,但这会与层叠样式表(Cascading Style Sheets,CSS)的缩写混淆。因此,有人将跨站脚本攻击缩写为XSS。
CSRF,本名为Cross-site requestforgery,也就是跨站请求伪造。
SSRF,也就是Server Side RequestForgery,服务器端请求伪造。
相同点:
XSS,CSRF,SSRF三种常见的Web服务端漏洞均是由于,服务器端对用户提供的可控数据过于信任或者过滤不严导致的。
不同点:
XSS是服务器对用户输入的数据没有进行足够的过滤,导致客户端浏览器在渲染服务器返回的html页面时,出现了预期值之外的 脚本语句被执行。
CSRF是服务器端没有对用户提交的数据进行随机值校验,且对http请求包内的refer字段校验不严,导致攻击者可以 利用用户的Cookie信息伪造用户请求发送至服务器。
SSRF是服务器对 用户提供的可控URL过于信任,没有对攻击者提供的RUL进行地址限制和足够的检测,导致攻击者可以以此为跳板攻击内网或其他服务器。
-
XSS和CSRF攻击是什么,如何防御?
XSS(跨站脚本攻击),CSRF(跨站请求伪造)
对 XSS 攻击,经常有以下两个方式来进行防御:
设置重要的cookie信息为 httpOnly 对于重要的 cookie字段,如:可以通过 cookie 某个字段和某个接口获取好友关系的,需要将其设置为 httpOnly,使得恶意用户无法获取。
对输入进行检测和转义 对用户输入的或者从链接获取参数需要展示到页面中需要校验合法性和使用转义函数进行转义,如常见的函数如下:
filter_var,mysql_real_escape_string,htmlentities,htmlspecialchars,strip_tags
针对 CSRF 攻击,常用的防御方式如下:
检测请求来源 在请求头中有一个refree字段,refree记录了发送请求的域名,比如:hack.com向example.com中发送请求,那么refree就为hack.com,只要在处理请求中做相应的校验就可以中断请求。
加入token校验 crsf之所以能够伪造请求成功,其原因之一在于所有的用户信息放于cookie中;因此可以在每次请求中加入token,然后后台进行校验,如果校验通过则进行处理。生成token方式之一如下:
参考:
https://segmentfault.com/a/1190000018266262?utm_source=sf-similar-article
###
什么是nginx负载均衡,常用的nginx负载均衡算法有哪些?
轮询(默认)、加权轮询(weight)、ip_hash、fair(第三方,最少连接、最少时间)、url_hash(第三方)
###
算法(排序、递归、链表、动态规划)
###
分布式锁的实现有哪些?
说到分布式锁的实现,还是有很多的,有数据库方式的,有 Redis 分布式锁,有 Zookeeper 分布式锁等等。
我们如果采用 Redis 作为分布式锁,那么上图中负责“红包的妹子(服务)”,就可以替换成 Redis,请自行脑补。
①为什么 Redis 可以实现分布式锁?
首先 Redis 是单线程的,这里的单线程指的是网络请求模块使用了一个线程(所以不需考虑并发安全性),即一个线程处理所有网络请求,其他模块仍用了多个线程。
在实际的操作中过程大致是这样子的:服务器 1 要去访问发红包的妹子,也就是 Redis,那么它会在 Redis 中通过"setnx key value" 操作设置一个 Key 进去,Value 是啥不重要,重要的是要有一个 Key,也就是一个标记。
而且这个 Key 你爱叫啥叫啥,只要所有的服务器设置的 Key 相同就可以。
假设我们设置一个,如下图:
c3625edde633d6ac763c7cd3a46db0d8.png
那么我们可以看到会返回一个 1,那就代表了成功。
如果再来一个请求去设置同样的 Key,如下图:
ab8147dc999cfa748237e9ef1534b4e7.png
这个时候会返回 0,那就代表失败了。
那么我们就可以通过这个操作去判断是不是当前可以拿到锁,或者说可以去访问“负责发红包的妹子”,如果返回 1,那我就开始去执行后面的逻辑,如果返回 0,那就说明已经被人占用了,我就要继续等待。
当服务器 1 拿到锁之后,进行了业务处理,完成后,还需要释放锁,如下图所示:
a753340cd8db501a0926c31196a63934.png
删除成功返回 1,那么其他的服务器就可以继续重复上面的步骤去设置这个 Key,以达到获取锁的目的。
当然以上的操作是在 Redis 客户端直接进行的,通过程序调用的话,肯定就不能这么写,比如 Java 就需要通过 Jedis 去调用,但是整个处理逻辑基本都是一样的。
通过上面的方式,我们好像是解决了分布式锁的问题,但是想想还有没有什么问题呢?
对,问题还是有的,可能会有死锁的问题发生,比如服务器 1 设置完之后,获取了锁之后,忽然发生了宕机。
那后续的删除 Key 操作就没法执行,这个 Key 会一直在 Redis 中存在,其他服务器每次去检查,都会返回 0,他们都会认为有人在使用锁,我需要等。
为了解决这个死锁的问题,我们就需要给 Key 设置有效期了。设置的方式有 2 种:
第一种就是在 Set 完 Key 之后,直接设置 Key 的有效期 "expire key timeout" ,为 Key 设置一个超时时间,单位为 Second,超过这个时间锁会自动释放,避免死锁。
这种方式相当于,把锁持有的有效期,交给了 Redis 去控制。如果时间到了,你还没有给我删除 Key,那 Redis 就直接给你删了,其他服务器就可以继续去 Setnx 获取锁。
第二种方式,就是把删除 Key 权利交给其他的服务器,那这个时候就需要用到 Value 值了,比如服务器 1,设置了 Value 也就是 Timeout 为当前时间 +1 秒 。
这个时候服务器 2 通过 Get 发现时间已经超过系统当前时间了,那就说明服务器 1 没有释放锁,服务器 1 可能出问题了,服务器 2 就开始执行删除 Key 操作,并且继续执行 Setnx 操作。
但是这块有一个问题,也就是不光你服务器 2 可能会发现服务器 1 超时了,服务器 3 也可能会发现,如果刚好服务器 2 Setnx 操作完成,服务器 3 就接着删除,是不是服务器 3 也可以 Setnx 成功了?
那就等于是服务器 2 和服务器 3 都拿到锁了,那就问题大了。这个时候怎么办呢?
这个时候需要用到“GETSET key value”命令了。这个命令的意思就是获取当前 Key 的值,并且设置新的值。
假设服务器 2 发现 Key 过期了,开始调用 getset 命令,然后用获取的时间判断是否过期,如果获取的时间仍然是过期的,那就说明拿到锁了。
如果没有,则说明在服务 2 执行 getset 之前,服务器 3 可能也发现锁过期了,并且在服务器 2 之前执行了 getset 操作,重新设置了过期时间。
那么服务器 2 就需要放弃后续的操作,继续等待服务器 3 释放锁或者去监测 Key 的有效期是否过期。
这块其实有一个小问题是,服务器 3 已经修改了有效期,拿到锁之后,服务器 2 也修改了有效期,但是没能拿到锁。
但是这个有效期的时间已经被在服务器 3 的基础上又增加一些,但是这种影响其实还是很小的,几乎可以忽略不计。
###
###
###
###
###
###
###
###
###
###
###
###
###
###
秒懂 23 种设计模式!有点污,但真得秒啊...
01、工厂方法
追 MM 少不了请吃饭了,麦当劳的鸡翅和肯德基的鸡翅都是 MM 爱吃的东西,虽然口味有所不同,但不管你带 MM 去麦当劳或肯德基,只管向服务员说「来四个鸡翅」就行了。麦当劳和肯德基就是生产鸡翅的 Factory
工厂模式:客户类和工厂类分开。
消费者任何时候需要某种产品,只需向工厂请求即可。消费者无须修改就可以接纳新产品。缺点是当产品修改时,工厂类也要做相应的修改。如:如何创建及如何向客户端提供。
02、建造者模式
MM 最爱听的就是「我爱你」这句话了,见到不同地方的 MM,要能够用她们的方言跟她说这句话哦,我有一个多种语言翻译机,上面每种语言都有一个按键,见到 MM 我只要按对应的键,它就能够用相应的语言说出「我爱你」这句话了,国外的 MM 也可以轻松搞掂,这就是我的「我爱你」builder。
建造模式:将产品的内部表象和产品的生成过程分割开来,从而使一个建造过程生成具有不同的内部表象的产品对象。建造模式使得产品内部表象可以独立的变化,客户不必知道产品内部组成的细节。建造模式可以强制实行一种分步骤进行的建造过程。
03、抽象工厂
请 MM 去麦当劳吃汉堡,不同的 MM 有不同的口味,要每个都记住是一件烦人的事情,我一般采用 Factory Method 模式,带着 MM 到服务员那儿,说「要一个汉堡」,具体要什么样的汉堡呢,让 MM 直接跟服务员说就行了。
工厂方法模式:核心工厂类不再负责所有产品的创建,而是将具体创建的工作交给子类去做,成为一个抽象工厂角色,仅负责给出具体工厂类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。
04、原型模式
跟 MM 用 QQ 聊天,一定要说些深情的话语了,我搜集了好多肉麻的情话,需要时只要 copy 出来放到 QQ 里面就行了,这就是我的情话 prototype 了。(100 块钱一份,你要不要)
原始模型模式:通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的方法创建出更多同类型的对象。原始模型模式允许动态的增加或减少产品类,产品类不需要非得有任何事先确定的等级结构,原始模型模式适用于任何的等级结构。缺点是每一个类都必须配备一个克隆方法。
05、单态模式
俺有 6 个漂亮的老婆,她们的老公都是我,我就是我们家里的老公 Sigleton,她们只要说道「老公」,都是指的同一个人,那就是我 (刚才做了个梦啦,哪有这么好的事)
单例模式:单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例单例模式。单例模式只应在有真正的 “单一实例” 的需求时才可使用。
06、适配器模式
在朋友聚会上碰到了一个美女 Sarah,从香港来的,可我不会说粤语,她不会说普通话,只好求助于我的朋友 kent 了,他作为我和 Sarah 之间的 Adapter,让我和 Sarah 可以相互交谈了 (也不知道他会不会耍我)
适配器(变压器)模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口原因不匹配而无法一起工作的两个类能够一起工作。适配类可以根据参数返还一个合适的实例给客户端。
07、桥梁模式
早上碰到 MM,要说早上好,晚上碰到 MM,要说晚上好;碰到 MM 穿了件新衣服,要说你的衣服好漂亮哦,碰到 MM 新做的发型,要说你的头发好漂亮哦。不要问我 “早上碰到 MM 新做了个发型怎么说” 这种问题,自己用 BRIDGE 组合一下不就行了
桥梁模式:将抽象化与实现化脱耦,使得二者可以独立的变化,也就是说将他们之间的强关联变成弱关联,也就是指在一个软件系统的抽象化和实现化之间使用组合 / 聚合关系而不是继承关系,从而使两者可以独立的变化。
08、合成模式
Mary 今天过生日。“我过生日,你要送我一件礼物。”“嗯,好吧,去商店,你自己挑。”“这件 T 恤挺漂亮,买,这条裙子好看,买,这个包也不错,买。”“喂,买了三件了呀,我只答应送一件礼物的哦。”“什么呀,T 恤加裙子加包包,正好配成一套呀,小姐,麻烦你包起来。”“……”,MM 都会用 Composite 模式了,你会了没有?
合成模式:合成模式将对象组织到树结构中,可以用来描述整体与部分的关系。合成模式就是一个处理对象的树结构的模式。合成模式把部分与整体的关系用树结构表示出来。合成模式使得客户端把一个个单独的成分对象和由他们复合而成的合成对象同等看待。
09、装饰模式
Mary 过完轮到 Sarly 过生日,还是不要叫她自己挑了,不然这个月伙食费肯定玩完,拿出我去年在华山顶上照的照片,在背面写上 “最好的的礼物,就是爱你的 Fita”,再到街上礼品店买了个像框(卖礼品的 MM 也很漂亮哦),再找隔壁搞美术设计的 Mike 设计了一个漂亮的盒子装起来……,我们都是 Decorator,最终都在修饰我这个人呀,怎么样,看懂了吗?
装饰模式:装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案,提供比继承更多的灵活性。动态给一个对象增加功能,这些功能可以再动态的撤消。增加由一些基本功能的排列组合而产生的非常大量的功能。
10、门面模式
我有一个专业的 Nikon 相机,我就喜欢自己手动调光圈、快门,这样照出来的照片才专业,但 MM 可不懂这些,教了半天也不会。幸好相机有 Facade 设计模式,把相机调整到自动档,只要对准目标按快门就行了,一切由相机自动调整,这样 MM 也可以用这个相机给我拍张照片了。门面模式:外部与一个子系统的通信必须通过一个统一的门面对象进行。
门面模式提供一个高层次的接口,使得子系统更易于使用。每一个子系统只有一个门面类,而且此门面类只有一个实例,也就是说它是一个单例模式。但整个系统可以有多个门面类。
11、享元模式
每天跟 MM 发短信,手指都累死了,最近买了个新手机,可以把一些常用的句子存在手机里,要用的时候,直接拿出来,在前面加上 MM 的名字就可以发送了,再不用一个字一个字敲了。共享的句子就是 Flyweight,MM 的名字就是提取出来的外部特征,根据上下文情况使用。享元模式:FLYWEIGHT 在拳击比赛中指最轻量级。
享元模式以共享的方式高效的支持大量的细粒度对象。享元模式能做到共享的关键是区分内蕴状态和外蕴状态。内蕴状态存储在享元内部,不会随环境的改变而有所不同。外蕴状态是随环境的改变而改变的。外蕴状态不能影响内蕴状态,它们是相互独立的。
将可以共享的状态和不可以共享的状态从常规类中区分开来,将不可以共享的状态从类里剔除出去。客户端不可以直接创建被共享的对象,而应当使用一个工厂对象负责创建被共享的对象。享元模式大幅度的降低内存中对象的数量。
12、代理模式
跟 MM 在网上聊天,一开头总是 “hi, 你好”,“你从哪儿来呀?”“你多大了?”“身高多少呀?” 这些话,真烦人,写个程序做为我的 Proxy 吧,凡是接收到这些话都设置好了自己的回答,接收到其他的话时再通知我回答,怎么样,酷吧。
代理模式:代理模式给某一个对象提供一个代理对象,并由代理对象控制对源对象的引用。代理就是一个人或一个机构代表另一个人或者一个机构采取行动。某些情况下,客户不想或者不能够直接引用一个对象,代理对象可以在客户和目标对象直接起到中介的作用。
客户端分辨不出代理主题对象与真实主题对象。代理模式可以并不知道真正的被代理对象,而仅仅持有一个被代理对象的接口,这时候代理对象不能够创建被代理对象,被代理对象必须有系统的其他角色代为创建并传入。
13、责任链模式
晚上去上英语课,为了好开溜坐到了最后一排,哇,前面坐了好几个漂亮的 MM 哎,找张纸条,写上 “Hi, 可以做我的女朋友吗?如果不愿意请向前传”,纸条就一个接一个的传上去了,糟糕,传到第一排的 MM 把纸条传给老师了,听说是个老处女呀,快跑!
责任链模式:在责任链模式中,很多对象由每一个对象对其下家的引用而接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。客户并不知道链上的哪一个对象最终处理这个请求,系统可以在不影响客户端的情况下动态的重新组织链和分配责任。处理者有两个选择:承担责任或者把责任推给下家。一个请求可以最终不被任何接收端对象所接受。
14、命令模式
俺有一个 MM 家里管得特别严,没法见面,只好借助于她弟弟在我们俩之间传送信息,她对我有什么指示,就写一张纸条让她弟弟带给我。这不,她弟弟又传送过来一个 COMMAND,为了感谢他,我请他吃了碗杂酱面,哪知道他说:“我同时给我姐姐三个男朋友送 COMMAND,就数你最小气,才请我吃面。”
命令模式:命令模式把一个请求或者操作封装到一个对象中。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。命令模式允许请求的一方和发送的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否执行,何时被执行以及是怎么被执行的。系统支持命令的撤消。
15、解释器模式
俺有一个《泡 MM 真经》,上面有各种泡 MM 的攻略,比如说去吃西餐的步骤、去看电影的方法等等,跟 MM 约会时,只要做一个 Interpreter,照着上面的脚本执行就可以了。
解释器模式:给定一个语言后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器。客户端可以使用这个解释器来解释这个语言中的句子。解释器模式将描述怎样在有了一个简单的文法后,使用模式设计解释这些语句。
在解释器模式里面提到的语言是指任何解释器对象能够解释的任何组合。在解释器模式中需要定义一个代表文法的命令类的等级结构,也就是一系列的组合规则。每一个命令对象都有一个解释方法,代表对命令对象的解释。命令对象的等级结构中的对象的任何排列组合都是一个语言。
16、迭代模式
我爱上了 Mary,不顾一切的向她求婚。Mary:“想要我跟你结婚,得答应我的条件” 我:“什么条件我都答应,你说吧” Mary:“我看上了那个一克拉的钻石” 我:“我买,我买,还有吗?” Mary:“我看上了湖边的那栋别墅” 我:“我买,我买,还有吗?” Mary:“我看上那辆法拉利跑车” 我脑袋嗡的一声,坐在椅子上,一咬牙:“我买,我买,还有吗?”
迭代模式:迭代模式可以顺序访问一个聚集中的元素而不必暴露聚集的内部表象。多个对象聚在一起形成的总体称之为聚集,聚集对象是能够包容一组对象的容器对象。迭代子模式将迭代逻辑封装到一个独立的子对象中,从而与聚集本身隔开。
迭代模式简化了聚集的界面。每一个聚集对象都可以有一个或一个以上的迭代子对象,每一个迭代子的迭代状态可以是彼此独立的。迭代算法可以独立于聚集角色变化。
17、调停者模式
四个 MM 打麻将,相互之间谁应该给谁多少钱算不清楚了,幸亏当时我在旁边,按照各自的筹码数算钱,赚了钱的从我这里拿,赔了钱的也付给我,一切就 OK 啦,俺得到了四个 MM 的电话。调停者模式:调停者模式包装了一系列对象相互作用的方式,使得这些对象不必相互明显作用。从而使他们可以松散偶合。
当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用。保证这些作用可以彼此独立的变化。调停者模式将多对多的相互作用转化为一对多的相互作用。调停者模式将对象的行为和协作抽象化,把对象在小尺度的行为上与其他对象的相互作用分开处理。
18、备忘录模式
同时跟几个 MM 聊天时,一定要记清楚刚才跟 MM 说了些什么话,不然 MM 发现了会不高兴的哦,幸亏我有个备忘录,刚才与哪个 MM 说了什么话我都拷贝一份放到备忘录里面保存,这样可以随时察看以前的记录啦。
备忘录模式:备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捉住,并外部化,存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。
19、观察者模式
想知道咱们公司最新 MM 情报吗?加入公司的 MM 情报邮件组就行了,tom 负责搜集情报,他发现的新情报不用一个一个通知我们,直接发布给邮件组,我们作为订阅者(观察者)就可以及时收到情报啦。
观察者模式:观察者模式定义了一种一队多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使他们能够自动更新自己。
20、状态模式
跟 MM 交往时,一定要注意她的状态哦,在不同的状态时她的行为会有不同,比如你约她今天晚上去看电影,对你没兴趣的 MM 就会说 “有事情啦”,对你不讨厌但还没喜欢上的 MM 就会说 “好啊,不过可以带上我同事么?”,已经喜欢上你的 MM 就会说 “几点钟?看完电影再去泡吧怎么样?”,当然你看电影过程中表现良好的话,也可以把 MM 的状态从不讨厌不喜欢变成喜欢哦。
状态模式:状态模式允许一个对象在其内部状态改变的时候改变行为。这个对象看上去象是改变了它的类一样。状态模式把所研究的对象的行为包装在不同的状态对象里,每一个状态对象都属于一个抽象状态类的一个子类。
状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。状态模式需要对每一个系统可能取得的状态创立一个状态类的子类。当系统的状态变化时,系统便改变所选的子类。
21、策略模式
跟不同类型的 MM 约会,要用不同的策略,有的请电影比较好,有的则去吃小吃效果不错,有的去海边浪漫最合适,单目的都是为了得到 MM 的芳心,我的追 MM 锦囊中有好多 Strategy 哦。策略模式:策略模式针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。
策略模式使得算法可以在不影响到客户端的情况下发生变化。策略模把行为和环境分开。环境类负责维持和查询行为类,各种算法在具体的策略类中提供。由于算法和环境独立开来,算法的增减,修改都不会影响到环境和客户端。
22、模板方法模式
看过《如何说服女生上床》这部经典文章吗?女生从认识到上床的不变的步骤分为巧遇、打破僵局、展开追求、接吻、前戏、动手、爱抚、进去八大步骤 (Template method),但每个步骤针对不同的情况,都有不一样的做法,这就要看你随机应变啦 (具体实现);
模板方法模式:模板方法模式准备一个抽象类,将部分逻辑以具体方法以及具体构造子的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。先制定一个顶级逻辑框架,而将逻辑的细节留给具体的子类去实现。
23、访问者模式
情人节到了,要给每个 MM 送一束鲜花和一张卡片,可是每个 MM 送的花都要针对她个人的特点,每张卡片也要根据个人的特点来挑,我一个人哪搞得清楚,还是找花店老板和礼品店老板做一下 Visitor,让花店老板根据 MM 的特点选一束花,让礼品店老板也根据每个人特点选一张卡,这样就轻松多了;
访问者模式:访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变。访问者模式适用于数据结构相对未定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由的演化。访问者模式使得增加新的操作变的很容易,就是增加一个新的访问者类。
访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个的节点类中。当使用访问者模式时,要将尽可能多的对象浏览逻辑放在访问者类中,而不是放到它的子类中。访问者模式可以跨过几个类的等级结构访问属于不同的等级结构的成员类。
=============================
23种经典设计模式的原理、背后的思想、应用场景
对程序员来说,除了算法,设计模式也是提升代码能力的关键技能,今天我就想跟你聊聊 23 种经典的设计模式。
23种经典设计模式共分为3种类型,分别是创建型、结构型和行为型。今天,我们把这3种类型分成3个对应的小模块,逐一带你看一下每一种设计模式的原理、实现、设计意图和应用场景。
一、创建型设计模式
创建型设计模式包括:单例模式、工厂模式、建造者模式、原型模式。它主要解决对象的创建问题,封装复杂的创建过程,解耦对象的创建代码和使用代码。
1.单例模式
单例模式用来创建全局唯一的对象。一个类只允许创建一个对象(或者叫实例),那这个类就是一个单例类,这种设计模式就叫作单例模式。单例有几种经典的实现方式,它们分别是:饿汉式、懒汉式、双重检测、静态内部类、枚举。
尽管单例是一个很常用的设计模式,在实际的开发中,我们也确实经常用到它,但是,有些人认为单例是一种反模式(anti-pattern),并不推荐使用,主要的理由有以下几点:
单例对OOP特性的支持不友好
单例会隐藏类之间的依赖关系
单例对代码的扩展性不友好
单例对代码的可测试性不友好
单例不支持有参数的构造函数
那有什么替代单例的解决方案呢?如果要完全解决这些问题,我们可能要从根上寻找其他方式来实现全局唯一类。比如,通过工厂模式、IOC容器来保证全局唯一性。
有人把单例当作反模式,主张杜绝在项目中使用。我个人觉得这有点极端。模式本身没有对错,关键看你怎么用。如果单例类并没有后续扩展的需求,并且不依赖外部系统,那设计成单例类就没有太大问题。对于一些全局类,我们在其他地方new的话,还要在类之间传来传去,不如直接做成单例类,使用起来简洁方便。
除此之外,我们还讲到了进程唯一单例、线程唯一单例、集群唯一单例、多例等扩展知识点,这一部分在实际的开发中并不会被用到,但是可以扩展你的思路、锻炼你的逻辑思维。这里我就不带你回顾了,你可以自己回忆一下。
2.工厂模式
工厂模式包括简单工厂、工厂方法、抽象工厂这3种细分模式。其中,简单工厂和工厂方法比较常用,抽象工厂的应用场景比较特殊,所以很少用到,不是我们学习的重点。
工厂模式用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。实际上,如果创建对象的逻辑并不复杂,那我们直接通过new来创建对象就可以了,不需要使用工厂模式。当创建逻辑比较复杂,是一个“大工程”的时候,我们就考虑使用工厂模式,封装对象的创建过程,将对象的创建和使用相分离。
当每个对象的创建逻辑都比较简单的时候,我推荐使用简单工厂模式,将多个对象的创建逻辑放到一个工厂类中。当每个对象的创建逻辑都比较复杂的时候,为了避免设计一个过于庞大的工厂类,我们推荐使用工厂方法模式,将创建逻辑拆分得更细,每个对象的创建逻辑独立到各自的工厂类中。
详细点说,工厂模式的作用有下面4个,这也是判断要不要使用工厂模式最本质的参考标准。
封装变化:创建逻辑有可能变化,封装成工厂类之后,创建逻辑的变更对调用者透明。
代码复用:创建代码抽离到独立的工厂类之后可以复用。
隔离复杂性:封装复杂的创建逻辑,调用者无需了解如何创建对象。
控制复杂度:将创建代码抽离出来,让原本的函数或类职责更单一,代码更简洁。
除此之外,我们还讲了工厂模式一个非常经典的应用场景:依赖注入框架,比如Spring IOC、Google Guice,它用来集中创建、组装、管理对象,跟具体业务代码解耦,让程序员聚焦在业务代码的开发上。DI框架已经成为了我们平时开发的必备框架,在专栏中,我还带你实现了一个简单的DI框架,你可以再回过头去看看。
3.建造者模式
建造者模式用来创建复杂对象,可以通过设置不同的可选参数,“定制化”地创建不同的对象。建造者模式的原理和实现比较简单,重点是掌握应用场景,避免过度使用。
如果一个类中有很多属性,为了避免构造函数的参数列表过长,影响代码的可读性和易用性,我们可以通过构造函数配合set()方法来解决。但是,如果存在下面情况中的任意一种,我们就要考虑使用建造者模式了。
我们把类的必填属性放到构造函数中,强制创建对象的时候就设置。如果必填的属性有很多,把这些必填属性都放到构造函数中设置,那构造函数就又会出现参数列表很长的问题。如果我们把必填属性通过set()方法设置,那校验这些必填属性是否已经填写的逻辑就无处安放了。
如果类的属性之间有一定的依赖关系或者约束条件,我们继续使用构造函数配合set()方法的设计思路,那这些依赖关系或约束条件的校验逻辑就无处安放了。
如果我们希望创建不可变对象,也就是说,对象在创建好之后,就不能再修改内部的属性值,要实现这个功能,我们就不能在类中暴露set()方法。构造函数配合set()方法来设置属性值的方式就不适用了。
4.原型模式
如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同),在这种情况下,我们可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式,来创建新对象,以达到节省创建时间的目的。这种基于原型来创建对象的方式就叫作原型模式。
原型模式有两种实现方法,深拷贝和浅拷贝。浅拷贝只会复制对象中基本数据类型数据和引用对象的内存地址,不会递归地复制引用对象,以及引用对象的引用对象……而深拷贝得到的是一份完完全全独立的对象。所以,深拷贝比起浅拷贝来说,更加耗时,更加耗内存空间。
如果要拷贝的对象是不可变对象,浅拷贝共享不可变对象是没问题的,但对于可变对象来说,浅拷贝得到的对象和原始对象会共享部分数据,就有可能出现数据被修改的风险,也就变得复杂多了。操作非常耗时的情况下,我们比较推荐使用浅拷贝,否则,没有充分的理由,不要为了一点点的性能提升而使用浅拷贝。
二、结构型设计模式
结构型模式主要总结了一些类或对象组合在一起的经典结构,这些经典的结构可以解决特定应用场景的问题。结构型模式包括:代理模式、桥接模式、装饰器模式、适配器模式、门面模式、组合模式、享元模式。
1.代理模式
代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。一般情况下,我们让代理类和原始类实现同样的接口。但是,如果原始类并没有定义接口,并且原始类代码并不是我们开发维护的。在这种情况下,我们可以通过让代理类继承原始类的方法来实现代理模式。
静态代理需要针对每个类都创建一个代理类,并且每个代理类中的代码都有点像模板式的“重复”代码,增加了维护成本和开发成本。对于静态代理存在的问题,我们可以通过动态代理来解决。我们不事先为每个原始类编写代理类,而是在运行的时候动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。
代理模式常用在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放到代理类统一处理,让程序员只需要关注业务方面的开发。除此之外,代理模式还可以用在RPC、缓存等应用场景中。
2.桥接模式
桥接模式的代码实现非常简单,但是理解起来稍微有点难度,并且应用场景也比较局限,所以,相对来说,桥接模式在实际的项目中并没有那么常用,你只需要简单了解,见到能认识就可以了,并不是我们学习的重点。
桥接模式有两种理解方式。第一种理解方式是“将抽象和实现解耦,让它们能独立开发”。这种理解方式比较特别,应用场景也不多。另一种理解方式更加简单,等同于“组合优于继承”设计原则,这种理解方式更加通用,应用场景比较多。不管是哪种理解方式,它们的代码结构都是相同的,都是一种类之间的组合关系。
对于第一种理解方式,弄懂定义中“抽象”和“实现”两个概念,是理解它的关键。定义中的“抽象”,指的并非“抽象类”或“接口”,而是被抽象出来的一套“类库”,它只包含骨架代码,真正的业务逻辑需要委派给定义中的“实现”来完成。而定义中的“实现”,也并非“接口的实现类”,而是的一套独立的“类库”。“抽象”和“实现”独立开发,通过对象之间的组合关系组装在一起。
3.装饰器模式
装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承,给原始类添加增强功能。这也是判断是否该用装饰器模式的一个重要的依据。除此之外,装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。为了满足这样的需求,在设计的时候,装饰器类需要跟原始类继承相同的抽象类或者接口。
4.适配器模式
代理模式、装饰器模式提供的都是跟原始类相同的接口,而适配器提供跟原始类不同的接口。适配器模式是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。适配器模式有两种实现方式:类适配器和对象适配器。其中,类适配器使用继承关系来实现,对象适配器使用组合关系来实现。
适配器模式是一种事后的补救策略,用来补救设计上的缺陷。应用这种模式算是“无奈之举”。如果在设计初期,我们就能规避接口不兼容的问题,那这种模式就无用武之地了。在实际的开发中,什么情况下才会出现接口不兼容呢?我总结下了下面这5种场景:
封装有缺陷的接口设计
统一多个类的接口设计
替换依赖的外部系统
兼容老版本接口
适配不同格式的数据
5.门面模式
门面模式原理、实现都非常简单,应用场景比较明确。它通过封装细粒度的接口,提供组合各个细粒度接口的高层次接口,来提高接口的易用性,或者解决性能、分布式事务等问题。
6.组合模式
组合模式跟我们之前讲的面向对象设计中的“组合关系(通过组合来组装两个类)”,完全是两码事。这里讲的“组合模式”,主要是用来处理树形结构数据。正因为其应用场景的特殊性,数据必须能表示成树形结构,这也导致了这种模式在实际的项目开发中并不那么常用。但是,一旦数据满足树形结构,应用这种模式就能发挥很大的作用,能让代码变得非常简洁。
组合模式的设计思路,与其说是一种设计模式,倒不如说是对业务场景的一种数据结构和算法的抽象。其中,数据可以表示成树这种数据结构,业务需求可以通过在树上的递归遍历算法来实现。组合模式,将一组对象组织成树形结构,将单个对象和组合对象都看作树中的节点,以统一处理逻辑,并且它利用树形结构的特点,递归地处理每个子树,依次简化代码实现。
7.享元模式
所谓“享元”,顾名思义就是被共享的单元。享元模式的意图是复用对象,节省内存,前提是享元对象是不可变对象。
具体来讲,当一个系统中存在大量重复对象的时候,我们就可以利用享元模式,将对象设计成享元,在内存中只保留一份实例,供多处代码引用,这样可以减少内存中对象的数量,以起到节省内存的目的。实际上,不仅仅相同对象可以设计成享元,对于相似对象,我们也可以将这些对象中相同的部分(字段),提取出来设计成享元,让这些大量相似对象引用这些享元。
三、行为型设计模式
我们知道,创建型设计模式主要解决“对象的创建”问题,结构型设计模式主要解决“类或对象的组合”问题,那行为型设计模式主要解决的就是“类或对象之间的交互”问题。行为型模式比较多,有11种,它们分别是:观察者模式、模板模式、策略模式、职责链模式、迭代器模式、状态模式、访问者模式、备忘录模式、命令模式、解释器模式、中介模式。
1.观察者模式
观察者模式将观察者和被观察者代码解耦。观察者模式的应用场景非常广泛,小到代码层面的解耦,大到架构层面的系统解耦,再或者一些产品的设计思路,都有这种模式的影子,比如,邮件订阅、RSS Feeds,本质上都是观察者模式。
不同的应用场景和需求下,这个模式也有截然不同的实现方式:有同步阻塞的实现方式,也有异步非阻塞的实现方式;有进程内的实现方式,也有跨进程的实现方式。同步阻塞是最经典的实现方式,主要是为了代码解耦;异步非阻塞除了能实现代码解耦之外,还能提高代码的执行效率;进程间的观察者模式解耦更加彻底,一般是基于消息队列来实现,用来实现不同进程间的被观察者和观察者之间的交互。
框架的作用有隐藏实现细节,降低开发难度,实现代码复用,解耦业务与非业务代码,让程序员聚焦业务开发。针对异步非阻塞观察者模式,我们也可以将它抽象成EventBus框架来达到这样的效果。EventBus翻译为“事件总线”,它提供了实现观察者模式的骨架代码。我们可以基于此框架非常容易地在自己的业务场景中实现观察者模式,不需要从零开始开发。
2.模板模式
模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。这里的“算法”,我们可以理解为广义上的“业务逻辑”,并不特指数据结构和算法中的“算法”。这里的算法骨架就是“模板”,包含算法骨架的方法就是“模板方法”,这也是模板方法模式名字的由来。
模板模式有两大作用:复用和扩展。其中复用指的是,所有的子类可以复用父类中提供的模板方法的代码。扩展指的是,框架通过模板模式提供功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制化框架的功能。
除此之外,我们还讲到回调。它跟模板模式具有相同的作用:代码复用和扩展。在一些框架、类库、组件等的设计中经常会用到,比如JdbcTemplate就是用了回调。
相对于普通的函数调用,回调是一种双向调用关系。A类事先注册某个函数F到B类,A类在调用B类的P函数的时候,B类反过来调用A类注册给它的F函数。这里的F函数就是“回调函数”。A调用B,B反过来又调用A,这种调用机制就叫作“回调”。
回调可以细分为同步回调和异步回调。从应用场景上来看,同步回调看起来更像模板模式,异步回调看起来更像观察者模式。回调跟模板模式的区别,更多的是在代码实现上,而非应用场景上。回调基于组合关系来实现,模板模式基于继承关系来实现。回调比模板模式更加灵活。
3.策略模式
策略模式定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。策略模式用来解耦策略的定义、创建、使用。实际上,一个完整的策略模式就是由这三个部分组成的。
策略类的定义比较简单,包含一个策略接口和一组实现这个接口的策略类。策略的创建由工厂类来完成,封装策略创建的细节。策略模式包含一组策略可选,客户端代码选择使用哪个策略,有两种确定方法:编译时静态确定和运行时动态确定。其中,“运行时动态确定”才是策略模式最典型的应用场景。
在实际的项目开发中,策略模式也比较常用。最常见的应用场景是,利用它来避免冗长的if-else或switch分支判断。不过,它的作用还不止如此。它也可以像模板模式那样,提供框架的扩展点等等。实际上,策略模式主要的作用还是解耦策略的定义、创建和使用,控制代码的复杂度,让每个部分都不至于过于复杂、代码量过多。除此之外,对于复杂代码来说,策略模式还能让其满足开闭原则,添加新策略的时候,最小化、集中化代码改动,减少引入bug的风险。
4.职责链模式
在职责链模式中,多个处理器依次处理同一个请求。一个请求先经过A处理器处理,然后再把请求传递给B处理器,B处理器处理完后再传递给C处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作职责链模
在GoF的定义中,一旦某个处理器能处理这个请求,就不会继续将请求传递给后续的处理器了。当然,在实际的开发中,也存在对这个模式的变体,那就是请求不会中途终止传递,而是会被所有的处理器都处理一遍。
职责链模式常用在框架开发中,用来实现过滤器、拦截器功能,让框架的使用者在不需要修改框架源码的情况下,添加新的过滤、拦截功能。这也体现了之前讲到的对扩展开放、对修改关闭的设计原则。
5.迭代器模式
迭代器模式也叫游标模式,它用来遍历集合对象。这里说的“集合对象”,我们也可以叫“容器”“聚合对象”,实际上就是包含一组对象的对象,比如,数组、链表、树、图、跳表。迭代器模式主要作用是解耦容器代码和遍历代码。大部分编程语言都提供了现成的迭代器可以使用,我们不需要从零开始开发。
遍历集合一般有三种方式:for循环、foreach循环、迭代器遍历。后两种本质上属于一种,都可以看作迭代器遍历。相对于for循环遍历,利用迭代器来遍历有3个优势:
迭代器模式封装集合内部的复杂数据结构,开发者不需要了解如何遍历,直接使用容器提供的迭代器即可;
迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一;
迭代器模式让添加新的遍历算法更加容易,更符合开闭原则。除此之外,因为迭代器都实现自相同的接口,在开发中,基于接口而非实现编程,替换迭代器也变得更加容易。
在通过迭代器来遍历集合元素的同时,增加或者删除集合中的元素,有可能会导致某个元素被重复遍历或遍历不到。针对这个问题,有两种比较干脆利索的解决方案,来避免出现这种不可预期的运行结果。一种是遍历的时候不允许增删元素,另一种是增删元素之后让遍历报错。第一种解决方案比较难实现,因为很难确定迭代器使用结束的时间点。第二种解决方案更加合理,Java语言就是采用的这种解决方案。增删元素之后,我们选择fail-fast解决方式,让遍历操作直接抛出运行时异常。
6.状态模式
状态模式一般用来实现状态机,而状态机常用在游戏、工作流引擎等系统开发中。状态机又叫有限状态机,它由3个部分组成:状态、事件、动作。其中,事件也称为转移条件。事件触发状态的转移及动作的执行。不过,动作不是必须的,也可能只转移状态,不执行任何动作。
针对状态机,我们总结了三种实现方式。
第一种实现方式叫分支逻辑法。利用if-else或者switch-case分支逻辑,参照状态转移图,将每一个状态转移原模原样地直译成代码。对于简单的状态机来说,这种实现方式最简单、最直接,是首选。
第二种实现方式叫查表法。对于状态很多、状态转移比较复杂的状态机来说,查表法比较合适。通过二维数组来表示状态转移图,能极大地提高代码的可读性和可维护性。
第三种实现方式就是利用状态模式。对于状态并不多、状态转移也比较简单,但事件触发执行的动作包含的业务逻辑可能比较复杂的状态机来说,我们首选这种实现方式。
7.访问者模式
访问者模式允许一个或者多个操作应用到一组对象上,设计意图是解耦操作和对象本身,保持类职责单一、满足开闭原则以及应对代码的复杂性。
对于访问者模式,学习的主要难点在代码实现。而代码实现比较复杂的主要原因是,函数重载在大部分面向对象编程语言中是静态绑定的。也就是说,调用类的哪个重载函数,是在编译期间,由参数的声明类型决定的,而非运行时,根据参数的实际类型决定的。除此之外,我们还讲到Double Disptach。如果某种语言支持Double Dispatch,那就不需要访问者模式了。
正是因为代码实现难理解,所以,在项目中应用这种模式,会导致代码的可读性比较差。如果你的同事不了解这种设计模式,可能就会读不懂、维护不了你写的代码。所以,除非不得已,不要使用这种模式。
8.备忘录模式
备忘录模式也叫快照模式,具体来说,就是在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。这个模式的定义表达了两部分内容:一部分是,存储副本以便后期恢复;另一部分是,要在不违背封装原则的前提下,进行对象的备份和恢复。
备忘录模式的应用场景也比较明确和有限,主要用来防丢失、撤销、恢复等。它跟平时我们常说的“备份”很相似。两者的主要区别在于,备忘录模式更侧重于代码的设计和实现,备份更侧重架构设计或产品设计。
对于大对象的备份来说,备份占用的存储空间会比较大,备份和恢复的耗时会比较长。针对这个问题,不同的业务场景有不同的处理方式。比如,只备份必要的恢复信息,结合最新的数据来恢复;再比如,全量备份和增量备份相结合,低频全量备份,高频增量备份,两者结合来做恢复。
9.命令模式
命令模式在平时工作中并不常用,你稍微了解一下就可以。
落实到编码实现,命令模式用到最核心的实现手段,就是将函数封装成对象。我们知道,在大部分编程语言中,函数是没法作为参数传递给其他函数的,也没法赋值给变量。借助命令模式,我们将函数封装成对象,这样就可以实现把函数像对象一样使用。
命令模式的主要作用和应用场景,是用来控制命令的执行,比如,异步、延迟、排队执行命令、撤销重做命令、存储命令、给命令记录日志等,这才是命令模式能发挥独一无二作用的地方。
10.解释器模式
解释器模式为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法。实际上,这里的“语言”不仅仅指我们平时说的中、英、日、法等各种语言。从广义上来讲,只要是能承载信息的载体,我们都可以称之为“语言”,比如,古代的结绳记事、盲文、哑语、摩斯密码等。
要想了解“语言”要表达的信息,我们就必须定义相应的语法规则。这样,书写者就可以根据语法规则来书写“句子”(专业点的叫法应该是“表达式”),阅读者根据语法规则来阅读“句子”,这样才能做到信息的正确传递。而我们要讲的解释器模式,其实就是用来实现根据语法规则解读“句子”的解释器。
解释器模式的代码实现比较灵活,没有固定的模板。我们前面说过,应用设计模式主要是应对代码的复杂性,解释器模式也不例外。它的代码实现的核心思想,就是将语法解析的工作拆分到各个小类中,以此来避免大而全的解析类。一般的做法是,将语法规则拆分一些小的独立的单元,然后对每个单元进行解析,最终合并为对整个语法规则的解析。
11.中介模式
中介模式的设计思想跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系(或者说依赖关系)从多对多(网状关系)转换为一对多(星状关系)。原来一个对象要跟n个对象交互,现在只需要跟一个中介对象交互,从而最小化对象之间的交互关系,降低了代码的复杂度,提高了代码的可读性和可维护性。
观察者模式和中介模式都是为了实现参与者之间的解耦,简化交互关系。两者的不同在于应用场景上。在观察者模式的应用场景中,参与者之间的交互比较有条理,一般都是单向的,一个参与者只有一个身份,要么是观察者,要么是被观察者。而在中介模式的应用场景中,参与者之间的交互关系错综复杂,既可以是消息的发送者、也可以同时是消息的接收者。
=============================
=============================
=============================
=============================
1.get,post 的区别
1、GET在浏览器回退时是无害的,而POST会再次提交请求。
2、GET产生的URL地址可以被Bookmark,而POST不可以。
3、GET请求会被浏览器主动cache,而POST不会,除非手动设置。
4、GET请求只能进行url编码,而POST支持多种编码方式。
5、GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
6、GET请求在URL中传送的参数是有长度限制的,而POST没有。
7、对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
8、GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
9、GET参数通过URL传递,POST放在Request body中。
10、GET产生一个TCP数据包,POST产生两个TCP数据包
2.require,include 区别
require是无条件包含也就是如果一个流程里加入require,无论条件成立与否都会先执行require include有返回值,而require没有(可能因为如此require的速度比include快) 包含文件不存在或者语法错误的时候require是致命的错误终止执行,include不是
3.PHP 的垃圾回收机制
PHP 可以自动进行内存管理,清除不需要的对象。
PHP 使用了引用计数 (reference counting) GC 机制。
每个对象都内含一个引用计数器 refcount,每个 reference 连接到对象,计数器加 1。当 reference 离开生存空间或被设为 NULL,计数器减 1。当某个对象的引用计数器为零时,PHP 知道你将不再需要使用这个对象,释放其所占的内存空间。
4.常见的排序算法
1. 冒泡排序
思路分析:在要排序的一组数中,对当前还未排好的序列,从前往后对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即,每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。
代码实现:
$arr=array(1,43,54,62,21,66,32,78,36,76,39);
function bubbleSort($arr)
{
$len=count($arr);
//该层循环控制 需要冒泡的轮数
for($i=0;$i<$len-1;$i++)
{ //该层循环用来控制每轮 冒出一个数 需要比较的次数
for($k=0;$k<$len-$i-1;$k++)
{
if($arr[$k]>$arr[$k+1])
{
$tmp=$arr[$k+1];
$arr[$k+1]=$arr[$k];
$arr[$k]=$tmp;
}
}
}
return $arr;
}
2. 选择排序
思路分析:在要排序的一组数中,选出最小的一个数与第一个位置的数交换。然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。
代码实现:
function selectSort($arr) {
//双重循环完成,外层控制轮数,内层控制比较次数
$len=count($arr);
for($i=0; $i<$len-1; $i++) {
//先假设最小的值的位置
$p = $i;
for($j=$i+1; $j<$len; $j++) {
//$arr[$p] 是当前已知的最小值
if($arr[$p] > $arr[$j]) {
//比较,发现更小的,记录下最小值的位置;并且在下次比较时采用已知的最小值进行比较。
$p = $j;
}
}
//已经确定了当前的最小值的位置,保存到$p中。如果发现最小值的位置与当前假设的位置$i不同,则位置互换即可。
if($p != $i) {
$tmp = $arr[$p];
$arr[$p] = $arr[$i];
$arr[$i] = $tmp;
}
}
//返回最终结果
return $arr;
}
3.插入排序
思路分析:在要排序的一组数中,假设前面的数已经是排好顺序的,现在要把第n个数插到前面的有序数中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。
代码实现:
function insertSort($arr) {
$len=count($arr);
for($i=1, $i<$len; $i++) {
$tmp = $arr[$i];
//内层循环控制,比较并插入
for($j=$i-1;$j>=0;$j--) {
if($tmp < $arr[$j]) {
//发现插入的元素要小,交换位置,将后边的元素与前面的元素互换
$arr[$j+1] = $arr[$j];
$arr[$j] = $tmp;
} else {
//如果碰到不需要移动的元素,由于是已经排序好是数组,则前面的就不需要再次比较了。
break;
}
}
}
return $arr;
}
4.快速排序
思路分析:选择一个基准元素,通常选择第一个元素或者最后一个元素。通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素。此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。
代码实现:
function quickSort($arr) {
//先判断是否需要继续进行
$length = count($arr);
if($length <= 1) {
return $arr;
}
//选择第一个元素作为基准
$base_num = $arr[0];
//遍历除了标尺外的所有元素,按照大小关系放入两个数组内
//初始化两个数组
$left_array = array(); //小于基准的
$right_array = array(); //大于基准的
for($i=1; $i<$length; $i++) {
if($base_num > $arr[$i]) {
//放入左边数组
$left_array[] = $arr[$i];
} else {
//放入右边
$right_array[] = $arr[$i];
}
}
//再分别对左边和右边的数组进行相同的排序处理方式递归调用这个函数
$left_array = quick_sort($left_array);
$right_array = quick_sort($right_array);
//合并
return array_merge($left_array, array($base_num), $right_array);
}
5、给出一个字符串,返回里面连续字母的个数,比如:abbcddde,返回 1a2b1c3de;
代码实现:
public function str($str)
{
$re = '';
$arr = str_split($str);//把字符串变成数组,开始我想到的是用for循环来处理,这个函数同事提醒了才发现
$key = 0; //key 用来记录下标,为了方便计算前面的数字
for ($i = 0; $i < count($arr); $i++) {
$v = $arr[$i];
if ($arr[$i] == $arr[$i + 1]) {
continue;//如果当前的值和下一个值相等,跳出当前循环,进入下一个
} else {
$re .= ($i - $key + 1) . $v; //不相等时计算出前面的数字,
$key = $i + 1;// 同时 key 下标重新复制
}
}
return $re;
}
6、约瑟夫环问题,猴子选大王
一群猴子排成一圈,按1,2,…,n依次编号。然后从第1只开始数,数到第m只,把它踢出圈,从它后面再开始数,再数到第m只,在把它踢出去…,如此不停的进行下去,直到最后只剩下一只猴子为止,那只猴子就叫做大王。要求编程模拟此过程,输入m、n, 输出最后那个大王的编号。用程序模拟该过程。
function mk($n ,$m){
$arr = range(1,$n);//构造一个数组
$i = 1; //从第一个开始循环
while(count($arr)>1){ //如果总数大于1
($i % $m != 0) && array_push($arr,$arr[$i-1]);//不被踢出则压入数组尾部
unset($arr[$i-1]);//压入数组然后删除
$i++;//继续循环
}
return $arr[$i-1]; //直至最后剩下一个为大王
}
print_r(mk(6,8)); //第3只为大王
一.什么是 CGI?什么是 FastCGI?php-fpm,FastCGI,Nginx 之间是什么关系?
CGI,通用网关接口,用于WEB服务器和应用程序间的交互,定义输入输出规范,用户的请求通过WEB服务器转发给FastCGI进程,FastCGI进程再调用应用程序进行处理,如php解析器,应用程序的处理结果如html返回给FastCGI,FastCGI返回给Nginx 进行输出。假设这里WEB服务器是Nginx,应用程序是 PHP,而 php-fpm 是管理 FastCGI 的,这也就是 php-fpm,FastCGI,和 Nginx 之间的关系。
FastCGI 用来提高 cgi 程序性能,启动一个master,再启动多个 worker,不需要每次解析 php.ini. 而 php-fpm 实现了 FastCGI 协议,是 FastCGI 的进程管理器,支持平滑重启,可以启动的时候预先生成多个进程。
二.memcache 和 Redis 的区别
数据结构:memcache仅支持简单的key-value形式,Redis支持的数据更多(string字符串,set集合,list列表,hash散列,zset有序集合);
多线程:memcache支持多线程,Redis支持单线程
持久化:Redis支持持久化,memcache不支持持久化
分布式:Redis做主从结构,memcache服务器需要通过hash一致化来支撑主从结构
实际运用中可以redis,memcache结合,memcache可作为session存储的方式,session都是KV类型键值对。
Redis中,并不是所有的数据都一直存储在内存中的,这是和Memcache相比一个最大的区别。
Redis在很多方面具备数据库的特征,或者说就是一个数据库系统,而Memcache只是简单的K/V缓存。
他们的扩展都需要做集群;实现方式:master-slave、Hash。
在100k以上的数据中,Memcache性能要高于Redis。
如果要说内存使用效率,使用简单的key-value存储的话,Memcached的内存利用率更高,而如果Redis采用hash结构来做key-value存储,由于其组合式的压缩,其内存利用率会高于Memcache。当然,这和你的应用场景和数据特性有关。
如果你对数据持久化和数据同步有所要求,那么推荐你选择Redis,因为这两个特性Memcache都不具备。即使你只是希望在升级或者重启系统后缓存数据不会丢失,选择Redis也是明智的。
Redis和Memcache在写入性能上面差别不大,读取性能上面尤其是批量读取性能上面Memcache更强
Redis 提供了多种不同级别的持久化方式:
RDB 持久化可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot)。
AOF 持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集。 AOF 文件中的命令全部以 Redis 协议的格式来保存,新命令会被追加到文件的末尾。 Redis 还可以在后台对 AOF 文件进行重写(rewrite),使得 AOF 文件的体积不会超出保存数据集状态所需的实际大小。
Redis 还可以同时使用 AOF 持久化和 RDB 持久化。 在这种情况下, 当 Redis 重启时, 它会优先使用 AOF 文件来还原数据集, 因为 AOF 文件保存的数据集通常比 RDB 文件所保存的数据集更完整。
你甚至可以关闭持久化功能,让数据只在服务器运行时存在。
三.什么是 Redis 穿透和雪崩
缓存穿透
当访问redis中一个不存在的key的时候,会直接穿过缓存,去数据库中进行查询.
如果是黑客,进行恶意攻击的时候,每次都请求超过2000个/秒的时候,这个时候mysql基本上就挂了.
解决办法是:每次从数据库中查询到一个不存在的key的时候,就写一个空值到缓存库中,有恶意攻击的时候,直接从缓存中取到这个空值.
缓存雪崩
假设每秒有5000个请求过来时候,redis缓存库崩了,然后这些请求瞬间落在了mysql数据库上,直接导致数据库死机.
解决方案就是:
事前:提高缓存库的高可用, 使用主从结构加哨兵 cluster集群
事中:使用ehcache+hystrix限流组件(当请求量非常巨大的时候,就调用自己开发好的一个降级饿组件,返回一些默认值,如友情提示,或者空白值)
事后:做持久化,尽快恢复缓存集群,一旦恢复,自动从磁盘上读取数据,恢复内存中的数据。
四.redis 消息队列先进先出需要注意什么?
通常使用一个list来实现队列操作,这样有一个小限制,所以的任务统一都是先进先出,如果想优先处理某个任务就不太好处理了,这就需要让队列有优先级的概念,我们就可以优先处理高级别的任务,实现方式有以下几种方式:
单一列表实现:队列正常的操作是 左进右出(lpush,rpop)为了先处理高优先级任务,在遇到高级别任务时,可以直接插队,直接放入队列头部(rpush),这样,从队列头部(右侧)获取任务时,取到的就是高优先级的任务(rpop)
使用两个队列,一个普通队列,一个高级队列,针对任务的级别放入不同的队列,获取任务时也很简单,redis的BRPOP命令可以按顺序从多个队列中取值,BRPOP会按照给出的 key 顺序查看,并在找到的第一个非空 list 的尾部弹出一个元素,
redis> BRPOP list1 list2 0
list1 做为高优先级任务队列
list2 做为普通任务队列
这样就实现了先处理高优先级任务,当没有高优先级任务时,就去获取普通任务
方式1最简单,但实际应用比较局限,方式3可以实现复杂优先级,但实现比较复杂,不利于维护
方式2是推荐用法,实际应用最为合适
五.Redis 如何防止高并发?
其实redis是不会存在并发问题的,因为他是单进程的,再多的命令都是一个接一个地执行的。我们使用的时候,之所以可能会出现并发问题,比如获得和设定这一对。
Redis的为什么会有高并发问题?
【Redis的的出身决定】
Redis是一种单线程机制的nosql数据库,基于key-value,数据可持久化落盘。由于单线程所以redis本身并没有锁的概念,多个客户端连接并不存在竞争关系,但是利用jedis等客户端对redis进行并发访问时会出现问题。发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题,这些问题均是由于客户端连接混乱造成。
同时,单线程的天性决定,高并发对同一个键的操作会排队处理,如果并发量很大,可能造成后来的请求超时。
在远程访问redis的时候,因为网络等原因造成高并发访问延迟返回的问题。
解决办法
在客户端将连接进行池化,同时对客户端读写Redis操作采用内部锁synchronized。
服务器角度,利用setnx变向实现锁机制。
六.说说对 SQL 语句优化有哪些方法?
Where子句中:where表之间的连接必须写在其他Where条件之前,那些可以过滤掉最大数量记录的条件必须写在Where子句的末尾.HAVING最后。
用EXISTS替代IN、用NOT EXISTS替代NOT IN。
避免在索引列上使用计算
避免在索引列上使用IS NULL和IS NOT NULL
对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描
一.数据库三范式
第一范式:1NF是对属性的原子性约束,要求属性具有原子性,不可再分解;
第二范式:2NF是对记录的惟一性约束,要求记录有惟一标识,即实体的惟一性;
第三范式:3NF是对字段冗余性的约束,即任何字段不能由其他字段派生出来,它要求字段没有冗余。
范式化设计优缺点:
优点:
可以尽量得减少数据冗余,使得更新快,体积小
缺点:对于查询需要多个表进行关联,减少写得效率增加读得效率,更难进行索引优化
反范式化:
优点:可以减少表得关联,可以更好得进行索引优化
缺点:数据冗余以及数据异常,数据得修改需要更多的成本
二.Mysql 中有哪几种锁
- MyISAM支持表锁,InnoDB支持表锁和行锁,默认为行锁
- 表级锁:开销小,加锁快,不会出现死锁。锁定粒度大,发生锁冲突的概率最高,并发量最低
- 行级锁:开销大,加锁慢,会出现死锁。锁力度小,发生锁冲突的概率小,并发度最高
三.什么是存储过程
我们常用的操作数据库语言SQL语句在执行的时候需要要先编译,然后执行,而存储过程(Stored Procedure)是一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给定参数(如果该存储过程带有参数)来调用执行它。
一个存储过程是一个可编程的函数,它在数据库中创建并保存。它可以有SQL语句和一些特殊的控制结构组成。当希望在不同的应用程序或平台上执行相同的函数,或者封装特定功能时,存储过程是非常有用的。数据库中的存储过程可以看做是对编程中面向对象方法的模拟。它允许控制数据的访问方式。
优点:
存储过程增强了SQL语言的功能和灵活性。存储过程可以用流控制语句编写,有很强的灵活性,可以完成复杂的判断和较复杂的运算。
存储过程允许标准组件是编程。存储过程被创建后,可以在程序中被多次调用,而不必重新编写该存储过程的SQL语句。而且数据库专业人员可以随时对存储过程进行修改,对应用程序源代码毫无影响。
存储过程能实现较快的执行速度。如果某一操作包含大量的Transaction-SQL代码或分别被多次执行,那么存储过程要比批处理的执行速度快很多。因为存储过程是预编译的。在首次运行一个存储过程时查询,优化器对其进行分析优化,并且给出最终被存储在系统表中的执行计划。而批处理的Transaction-SQL语句在每次运行时都要进行编译和优化,速度相对要慢一些。
存储过程能过减少网络流量。针对同一个数据库对象的操作(如查询、修改),如果这一操作所涉及的Transaction-SQL语句被组织程存储过程,那么当在客户计算机上调用该存储过程时,网络中传送的只是该调用语句,从而大大增加了网络流量并降低了网络负载。
存储过程可被作为一种安全机制来充分利用。系统管理员通过执行某一存储过程的权限进行限制,能够实现对相应的数据的访问权限的限制,避免了非授权用户对数据的访问,保证了数据的安全。
四.如何处理负载、高并发
1、HTML静态化
其实大家都知道,效率最高、消耗最小的就是纯静态化的html页面,所以我们尽可能使我们的 网站上的页面采用静态页面来实现,这个最简单的方法其实也是最有效的方法。
2、图片服务器分离
把图片单独存储,尽量减少图片等大流量的开销,可以放在一些相关的平台上,如七牛等。
3、数据库集群和库表散列及缓存
数据库的并发连接为100,一台数据库远远不够,可以从读写分离、主从复制,数据库集群方面来着手。另外尽量减少数据库的访问,可以使用缓存数据库如memcache、redis。
4、镜像
尽量减少下载,可以把不同的请求分发到多个镜像端。
5、负载均衡:
Apache的最大并发连接为1500,只能增加服务器,可以从硬件上着手,如F5服务器。当然硬件的成本比较高,我们往往从软件方面着手。
负载均衡 (Load Balancing) 建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力,同时能够提高网络的灵活性和可用性。目前使用最为广泛的负载均衡软件【Nginx】、【LVS】、【HAProxy】。
我分别来说下三种的优缺点:
【Nginx】
Nginx的优点是:
工作在网络的7层之上,可以针对http应用做一些分流的策略,比如针对域名、目录结构,它的正则规则比HAProxy更为强大和灵活,这也是它目前广泛流行的主要原因之一,Nginx单凭这点可利用的场合就远多于LVS了。
Nginx对网络稳定性的依赖非常小,理论上能ping通就就能进行负载功能,这个也是它的优势之一;相反LVS对网络稳定性依赖比较大,这点本人深有体会;
Nginx安装和配置比较简单,测试起来比较方便,它基本能把错误用日志打印出来。LVS的配置、测试就要花比较长的时间了,LVS对网络依赖比较大。
可以承担高负载压力且稳定,在硬件不差的情况下一般能支撑几万次的并发量,负载度比LVS相对小些。
Nginx可以通过端口检测到服务器内部的故障,比如根据服务器处理网页返回的状态码、超时等等,并且会把返回错误的请求重新提交到另一个节点,不过其中缺点就是不支持url来检测。比如用户正在上传一个文件,而处理该上传的节点刚好在上传过程中出现故障,Nginx会把上传切到另一台服务器重新处理,而LVS就直接断掉了,如果是上传一个很大的文件或者很重要的文件的话,用户可能会因此而不满。
Nginx不仅仅是一款优秀的负载均衡器/反向代理软件,它同时也是功能强大的Web应用服务器。LNMP也是近几年非常流行的web架构,在高流量的环境中稳定性也很好。
Nginx现在作为Web反向加速缓存越来越成熟了,速度比传统的Squid服务器更快,可以考虑用其作为反向代理加速器。
Nginx可作为中层反向代理使用,这一层面Nginx基本上无对手,唯一可以对比Nginx的就只有 lighttpd了,不过 lighttpd目前还没有做到Nginx完全的功能,配置也不那么清晰易读,社区资料也远远没Nginx活跃。
Nginx也可作为静态网页和图片服务器,这方面的性能也无对手。还有Nginx社区非常活跃,第三方模块也很多。
Nginx的缺点是:
Nginx仅能支持http、https和Email协议,这样就在适用范围上面小些,这个是它的缺点。
对后端服务器的健康检查,只支持通过端口来检测,不支持通过url来检测。不支持Session的直接保持,但能通过ip_hash来解决。
【LVS】
使用Linux内核集群实现高性能、高可用的负载均衡服务器,它具有很好的可伸缩性(Scalability)、可靠性(Reliability)和可管理性(Manageability)。
LVS的优点是:
抗负载能力强、是工作在网络4层之上仅作分发之用,没有流量的产生,这个特点也决定了它在负载均衡软件里的性能最强的,对内存和cpu资源消耗比较低。
配置性比较低,这是一个缺点也是一个优点,因为没有可太多配置的东西,所以并不需要太多接触,大大减少了人为出错的几率。
工作稳定,因为其本身抗负载能力很强,自身有完整的双机热备方案,如LVS+Keepalived,不过我们在项目实施中用得最多的还是LVS/DR+Keepalived。
无流量,LVS只分发请求,而流量并不从它本身出去,这点保证了均衡器IO的性能不会受到大流量的影响。
应用范围比较广,因为LVS工作在4层,所以它几乎可以对所有应用做负载均衡,包括http、数据库、在线聊天室等等。
LVS的缺点是:
软件本身不支持正则表达式处理,不能做动静分离;而现在许多网站在这方面都有较强的需求,这个是Nginx/HAProxy+Keepalived的优势所在。
如果是网站应用比较庞大的话,LVS/DR+Keepalived实施起来就比较复杂了,特别后面有 Windows Server的机器的话,如果实施及配置还有维护过程就比较复杂了,相对而言,Nginx/HAProxy+Keepalived就简单多了。
【HAProxy】
HAProxy的特点是:
HAProxy也是支持虚拟主机的。
HAProxy的优点能够补充Nginx的一些缺点,比如支持Session的保持,Cookie的引导;同时支持通过获取指定的url来检测后端服务器的状态。
HAProxy跟LVS类似,本身就只是一款负载均衡软件;单纯从效率上来讲HAProxy会比Nginx有更出色的负载均衡速度,在并发处理上也是优于Nginx的。
HAProxy支持TCP协议的负载均衡转发,可以对MySQL读进行负载均衡,对后端的MySQL节点进行检测和负载均衡,大家可以用LVS+Keepalived对MySQL主从做负载均衡。
HAProxy负载均衡策略非常多,HAProxy的负载均衡算法现在具体有如下8种:
① roundrobin,表示简单的轮询,这个不多说,这个是负载均衡基本都具备的;
② static-rr,表示根据权重,建议关注;
③ leastconn,表示最少连接者先处理,建议关注;
④ source,表示根据请求源IP,这个跟Nginx的IP_hash机制类似,我们用其作为解决session问题的一种方法,建议关注;
⑤ ri,表示根据请求的URI;
⑥ rl_param,表示根据请求的URl参数’balance url_param’ requires an URL parameter name;
⑦ hdr(name),表示根据HTTP请求头来锁定每一次HTTP请求;
⑧ rdp-cookie(name),表示根据据cookie(name)来锁定并哈希每一次TCP请求。
Nginx和LVS对比的总结:
Nginx工作在网络的7层,所以它可以针对http应用本身来做分流策略,比如针对域名、目录结构等,相比之下LVS并不具备这样的功能,所以Nginx单凭这点可利用的场合就远多于LVS了;但Nginx有用的这些功能使其可调整度要高于LVS,所以经常要去触碰触碰,触碰多了,人为出问题的几率也就会大。
Nginx对网络稳定性的依赖较小,理论上只要ping得通,网页访问正常,Nginx就能连得通,这是Nginx的一大优势!Nginx同时还能区分内外网,如果是同时拥有内外网的节点,就相当于单机拥有了备份线路;LVS就比较依赖于网络环境,目前来看服务器在同一网段内并且LVS使用direct方式分流,效果较能得到保证。另外注意,LVS需要向托管商至少申请多一个ip来做Visual IP,貌似是不能用本身的IP来做VIP的。要做好LVS管理员,确实得跟进学习很多有关网络通信方面的知识,就不再是一个HTTP那么简单了。
Nginx安装和配置比较简单,测试起来也很方便,因为它基本能把错误用日志打印出来。LVS的安装和配置、测试就要花比较长的时间了;LVS对网络依赖比较大,很多时候不能配置成功都是因为网络问题而不是配置问题,出了问题要解决也相应的会麻烦得多。
Nginx也同样能承受很高负载且稳定,但负载度和稳定度差LVS还有几个等级:Nginx处理所有流量所以受限于机器IO和配置;本身的bug也还是难以避免的。
Nginx可以检测到服务器内部的故障,比如根据服务器处理网页返回的状态码、超时等等,并且会把返回错误的请求重新提交到另一个节点。目前LVS中 ldirectd也能支持针对服务器内部的情况来监控,但LVS的原理使其不能重发请求。比如用户正在上传一个文件,而处理该上传的节点刚好在上传过程中出现故障,Nginx会把上传切到另一台服务器重新处理,而LVS就直接断掉了,如果是上传一个很大的文件或者很重要的文件的话,用户可能会因此而恼火。
Nginx对请求的异步处理可以帮助节点服务器减轻负载,假如使用 apache直接对外服务,那么出现很多的窄带链接时apache服务器将会占用大 量内存而不能释放,使用多一个Nginx做apache代理的话,这些窄带链接会被Nginx挡住,apache上就不会堆积过多的请求,这样就减少了相当多的资源占用。这点使用squid也有相同的作用,即使squid本身配置为不缓存,对apache还是有很大帮助的。
Nginx能支持http、https和email(email的功能比较少用),LVS所支持的应用在这点上会比Nginx更多。在使用上,一般最前端所采取的策略应是LVS,也就是DNS的指向应为LVS均衡器,LVS的优点令它非常适合做这个任务。重要的ip地址,最好交由LVS托管,比如数据库的 ip、webservice服务器的ip等等,这些ip地址随着时间推移,使用面会越来越大,如果更换ip则故障会接踵而至。所以将这些重要ip交给 LVS托管是最为稳妥的,这样做的唯一缺点是需要的VIP数量会比较多。Nginx可作为LVS节点机器使用,一是可以利用Nginx的功能,二是可以利用Nginx的性能。当然这一层面也可以直接使用squid,squid的功能方面就比Nginx弱不少了,性能上也有所逊色于Nginx。Nginx也可作为中层代理使用,这一层面Nginx基本上无对手,唯一可以撼动Nginx的就只有lighttpd了,不过lighttpd目前还没有能做到 Nginx完全的功能,配置也不那么清晰易读。另外,中层代理的IP也是重要的,所以中层代理也拥有一个VIP和LVS是最完美的方案了。具体的应用还得具体分析,如果是比较小的网站(日PV小于1000万),用Nginx就完全可以了,如果机器也不少,可以用DNS轮询,LVS所耗费的机器还是比较多的;大型网站或者重要的服务,机器不发愁的时候,要多多考虑利用LVS。
===========
mysql的Row Format:
在 mysql中, 若一张表里面不存在varchar、text以及其变形、blob以及其变形的字段的话,那么张这个表其实也叫静态表,即该表的row_format是fixed,就是说每条记录所占用的字节一样。其优点读取快,缺点浪费额外一部分空间。
若一张表里面存在varchar、text以及其变形、blob以及其变形的字段的话,那么张这个表其实也叫动态表,即该表的row_format是dynamic,就是说每条记录所占用的字节是动态的。其优点节省空间,缺点增加读取的时间开销。
所以,做搜索查询量大的表一般都以空间来换取时间,设计成静态表。
===========
一、为什么要分库分表
如果一个网站业务快速发展,那这个网站流量也会增加,数据的压力也会随之而来,比如电商系统来说双十一大促对订单数据压力很大,Tps十几万并发量,如果传统的架构(一主多从),主库容量肯定无法满足这么高的Tps,
业务越来越大,单表数据超出了数据库支持的容量,持久化磁盘IO,传统的数据库性能瓶颈,产品经理业务·必须做,改变程序,数据库刀子切分优化。数据库连接数不够需要分库,表的数据量大,优化后查询性能还是很低,需要分。
二、什么是分库分表
分库分表方案是对关系型数据库数据存储和访问机制的一种补充。
分库:将一个库的数据拆分到多个相同的库中,访问的时候访问一个库
分表:把一个表的数据放到多个表中,操作对应的某个表就行
三、分库分表的几种方式
1.垂直拆分
(1) 数据库垂直拆分
根据业务拆分,如图,电商系统,拆分成订单库,会员库,商品库
(2)表垂直拆分
根据业务去拆分表,如图,把user表拆分成user_base表和user_info表,use_base负责存储登录,user_info负责存储基本用户信息
垂直拆分特点
1.每个库(表)的结构都不一样
2.每个库(表)的数据至少一列一样
3.每个库(表)的并集是全量数据
垂直拆分优缺点
优点:
1.拆分后业务清晰(专库专用按业务拆分)
2.数据维护简单,按业务不同,业务放到不同机器上
缺点:
1.如果单表的数据量,写读压力大
2.受某种业务决定,或者被限制,也就是说一个业务往往会影响到数据库的瓶颈(性能问题,如双十一抢购)
3.部分业务无法关联join,只能通过java程序接口去调用,提高了开发复杂度
2. 水平拆分
(1) 数据库水平拆分
如图,按会员库拆分,拆分成会员1库,会员2库,以userId拆分,userId尾号0-5为1库
6-9为2库,还有其他方式,进行取模,偶数放到1库,奇数放到2库
(2) 表水平拆分
如图把users表拆分成users1表和users2表,以userId拆分,进行取模,偶数放到users1表,奇数放到users2表
水平拆分的其他方式
range来分,每个库一段连续的数据,这个一般是按比如时间范围来的,但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了,优点:扩容的时候,就很容易,因为你只要预备好,给每个月都准备一个库就可以了,到了一个新的月份的时候,自然而然,就会写新的库了 缺点:大部分的 请求,都是访问最新的数据。实际生产用range,要看场景,你的用户不是仅仅访问最新的数据,而是均匀的访问现在的数据以及历史的数据
hash分发,优点:可以平均分配每个库的数据量和请求压力 缺点:扩容起来比较麻烦,会有一个数据迁移的这么一个过程
水平拆分特点
1.每个库(表)的结构都一样
2.每个库(表)的数据都不一样
3.每个库(表)的并集是全量数据
水平拆分优缺点
优点:
1.单库/单表的数据保持在一定量(减少),有助于性能提高
2.提高了系统的稳定性和负载能力
3.拆分表的结构相同,程序改造较少。
缺点:
1.数据的扩容很有难度维护量大
2.拆分规则很难抽象出来
3.分片事务的一致性问题部分业务无法关联join,只能通过java程序接口去调用
四、分库分表带来的问题
分布式事务
跨库join查询
分布式全局唯一id
开发成本 对程序员要求高
五、分库分表技术如何选型
分库分表的开源框架
jdbc 直连层:shardingsphere、tddl
proxy 代理层:mycat,mysql-proxy(360)
proxy代理层
如图,proxy代理层,所有分片规则,所有分片逻辑,包括处理分布式事务都在mycat写好了,所有分片逻辑都是基于mycat方进行操作
jdbc直连层和proxy代理层优缺点
jdbc直连层性能高,只支持java语言,支持跨数据库
proxy代理层开发成本低,支持跨语言,不支持跨数据库
================
索引的优缺点
索引有其明显的优势,也有其不可避免的缺点。
优点
索引的优点如下:
通过创建唯一索引可以保证数据库表中每一行数据的唯一性。
可以给所有的 MySQL 列类型设置索引。
可以大大加快数据的查询速度,这是使用索引最主要的原因。
在实现数据的参考完整性方面可以加速表与表之间的连接。
在使用分组和排序子句进行数据查询时也可以显著减少查询中分组和排序的时间
缺点
增加索引也有许多不利的方面,主要如下:
创建和维护索引组要耗费时间,并且随着数据量的增加所耗费的时间也会增加。
索引需要占磁盘空间,除了数据表占数据空间以外,每一个索引还要占一定的物理空间。如果有大量的索引,索引文件可能比数据文件更快达到最大文件尺寸。
当对表中的数据进行增加、删除和修改的时候,索引也要动态维护,这样就降低了数据的维护速度。
使用索引时,需要综合考虑索引的优点和缺点。
索引可以提高查询速度,但是会影响插入记录的速度。因为,向有索引的表中插入记录时,数据库系统会按照索引进行排序,这样就降低了插入记录的速度,插入大量记录时的速度影响会更加明显。这种情况下,最好的办法是先删除表中的索引,然后插入数据,插入完成后,再创建索引。
----
索引在什么情况下不会被使用?
================
----
注册树模式
what
注册树模式是通过将对象实例注册到一棵全局的对象树上,需要的时候从对象树上采摘的模式设计方法。
why
单例模式、工厂模式目前面临的局限。
单例模式创建唯一对象的过程本身还有一种判断,即判断对象是否存在。存在则返回对象,不存在则创建对象并返回。每次创建实例对象都要存在这么一层判断。
工厂模式更多考虑的是扩展维护的问题。
为了方便调用这些对象呢,也便统筹管理安排建立的对象。因而,注册树模式应运而生。不管你是通过单例模式还是工厂模式还是二者结合生成的对象,都统统给我“插到”注册树上。
我用某个对象的时候,直接从注册树上取一下就好。这和我们使用全局变量一样的方便实用。
How
需要一个作为注册树的类,将所有的对象“插入”到注册树上。
这个注册树应该由一个静态变量来充当。而且这个注册树应该是一个二维数组。
这个类应该有一个插入对象实例的方法(set()),应该有一个撤销对象实例的方法(_unset())。还需要有一个读取对象的方法(get())。
----
工厂模式
工厂模式解决的是如何不通过new建立实例对象的方法。
工厂模式有一个关键的构造,根据一般原则命名为Factory的静态方法,然而这只是一种原则,虽然工厂方法可以任意命名这个静态还可以接受任意数据的参数,必须返回一个对象。
What
工厂模式有一个关键的构造,根据一般原则命名为Factory的静态方法,然而这只是一种原则,虽然工厂方法可以任意命名这个静态还可以接受任意数据的参数,必须返回一个对象。
Why
易维护,可扩展,高内聚低耦合
一个对象,拥有相同的功能,“团结一致,一致对外”。
How
工厂类调用自身静态方法来生产对象实例。
工厂类里面的静态方法。静态方法里面new一下需要创建的对象实例就搞定了。
根据工厂类静态方法的参数,判断该创建哪个类。
工厂类静态方法返回一个对象。不是两个,更不是三个。
----
单例模式 final static protected
*三私一公 私有的静态变量(存放实例)、私有的构造方法(防止创建实例)、私有的克隆方法(防止克隆对象)、公有的静态方法(对外界提供实例)
*应用场景:程序应用中,涉及到数据库操作时,如果每次操作的时候连接数据库,会带来大量的资源消耗。可以通过单例模式,创建唯一的数据库连接对象。
-
单例模式解决的是如何在整个项目中创建唯一对象实例的问题。
单例模式指的是在整个应用中只有一个对象实例的设计模式。
为什么要使用单例模式,比如操作数据库,如果在应用中频繁建立连接对象,进行new操作的话,会消耗大量的系统内存资源。再则,在团队合作项目中,单例模式可以有效避免不同程序员new自己的对象,造成人为的系统消耗。
单例模式是只有一个对象实例的设计模式。
我们平常创建的类不是能创建很多对象的,就是不能创建对象的(抽象类)。要创建对象需要有类这是必须的,而且不能是抽象类。这个类要防止别人可以多次创建函数。我们自然而然考虑到了从构造函数入手。
但是,每次new操作都会调用构造函数,也就是会多次创建对象实例。这和我们设计初衷相悖了。在此处务必申明构造函数为private或者protected这样才能解决这个问题。
构造函数被申明为private或者protected这注定无法通过new的方法创建实例对象了。
既然无法通过new方法创建对象实例,那么我们只能通过类内的方法来创建对象实例了。
我们往往往往是创建了对象后才调用对象的方法,而此时需要调用类里面的方法来创建对象。不受是否创建对象影响都能调用的方法的解决方案毋庸置疑那就是利用关键字static。
在类内创建静态方法完成的工作就是确保只创建一个实例对象。
判断实例对象,存在的话直接返回,不存在自己创建一个。当然这个实例对象是类的静态属性。
如果有个类继承本类,将构造方法申明为public那不又坏事儿了?那有必要在构造方法前加final关键字了。
不管构造方法被申明为public,private还是protected,最终创建对象的时候都会调用。一直是new创建对象实例,单例模式也用new创建对象,只是换个地方而已,从类外到类内。
what
单例模式 整个应用系统中只有一个对象实例的设计模式。
why
频繁进行new操作建立连接对象,消耗大量的系统内存资源,也可避免不同的程序员new自己的对象造成人为的系统消耗。
How
类(不是抽象类、不允许多次创建、申明构造函数为private或者protected)
构造函数被申明为private或者protected这注定无法通过new的方法创建实例对象了,们只能通过类内的方法来创建对象实例了。
利用关键字static,不受是否创建对象影响都能调用的方法的解决方案。
在类内创建静态方法完成确保只创建一个实例对象工作,通过判断存在的话直接返回,不存在自己创建一个,这个实例对象是类的静态属性。
那有必要在构造方法前加final关键字,禁止类被继承然后将构造方法申明为public
不管构造方法被申明为public,private还是protected,最终创建对象的时候都会调用。
一直是new创建对象实例,单例模式也用new创建对象,只是换个地方而已,从类外移到了到类内。
---
策略模式 strategy
---
适配器模式 Adapter
---
门面模式 Facade
门面模式是指提供一个统一的接口来调用多个子系统的接口。
================
函数等基础
设计模式
排序方法
底层原理
大数据量场景及解决方案
数据库的优化、工具、精通MySQL
并发架构、工具
=================
RPC,RPC 是一个分布式计算的 CS 模式,总是由 Client 向 Server 发出一个执行若干过程请求,Server 接受请求,使用者客户端提供的参数,计算完成之后将结果返回给客户端。
mysql 500万是以前的面试题 应该是背的 现在mysql看版本 看服务器配置 没有固定的值 一个普通的8c16g的独享机器 几千万随随便便 500这个值 我听过很多人 张嘴就来不知道这已经是10年前的面试题了
final 关键字。如果父类中的方法被声明为 final,则子类无法覆盖该方法。如果一个类被声明为 final,则不能被继承。 属性不能被定义为 final,只有类和方法才能被定义为 final。
一个类可以在声明中用 extends 关键字继承另一个类的方法和属性。PHP不支持多重继承,一个类只能继承一个基类。
被继承的方法和属性可以通过用同样的名字重新声明被覆盖。但是如果父类定义方法时使用了 final,则该方法不可被覆盖。可以通过 parent:: 来访问被覆盖的方法或属性。
关键词 class 也可用于类名的解析。使用 ClassName::class 你可以获取一个字符串,包含了类 ClassName 的完全限定名称。这对使用了 命名空间 的类尤其有用。
abstract,定义为抽象的类不能被实例化。任何一个类,如果它里面至少有一个方法是被声明为抽象的,那么这个类就必须被声明为抽象的。被定义为抽象的方法只是声明了其调用方式(参数),不能定义其具体的功能实现。
使用接口(interface),可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。
接口是通过 interface 关键字来定义的,就像定义一个标准的类一样,但其中定义所有的方法都是空的。
接口中定义的所有方法都必须是公有,这是接口的特性。
实现(implements)
要实现一个接口,使用 implements 操作符。类中必须实现接口中定义的所有方法,否则会报一个致命错误。类可以实现多个接口,用逗号来分隔多个接口的名称。
实现多个接口时,接口中的方法不能有重名。
接口也可以继承,通过使用 extends 操作符。
类要实现接口,必须使用和接口中所定义的方法完全一致的方式。否则会导致致命错误。
trait,Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制
TCP三次握手,四次挥手
netstat 命令
::class 魔术方法将可调用代码中的parent、self和static关键字转换为各自的类名。
AB 压测工具
in 和exists的对比:如果两个表中一个较小,一个是大表,则子查询表大的用exists,子查询表小的用in; https://segmentfault.com/a/1190000014509559
小表驱动大表:即小的数据集驱动大的数据集。
using :连接查询时如果是同名字段作为连接条件,using可以代替on出现(比on更好);using 是针对同名字段(using(id)===on A.id=B.id),
延迟关联:通过使用覆盖索引查询返回需要的主键,再根据主键关联原表获得需要的数据
延迟关联在大分页查询的场景下,可以提高查询效率
Mysql 延迟关联:https://blog.csdn.net/Jeff_fei/article/details/117220319
如果是统计某些数据,可能改用数仓进行解决;如果是业务上就有那么复杂的查询,可能就不建议继续走 SQL 了,而是采用其他的方式进行解决,比如使用 ES 等进行解决。
假设有两个索引 idx1(a, b, c),idx2(a, c),
SQL 为"select * from t where a = 1 and b in (1, 2) order by c";
如果走 idx1,那么是 type 为 range,
如果走 idx2,那么 type 是 ref;
当需要扫描的行数,使用 idx2 大约是 idx1 的 5 倍以上时,会用 idx1,否则会用 idx2
Extra:
Using filesort:MySQL 需要额外的一次传递,以找出如何按排序顺序检索行。通过根据联接类型浏览所有行并为所有匹配 WHERE 子句的行保存排序关键字和行的指针来完成排序。然后关键字被排序,并按排序顺序检索行;
Using temporary:使用了临时表保存中间结果,性能特别差,需要重点优化;
Using index:表示相应的 select 操作中使用了覆盖索引(Coveing Index),避免访问了表的数据行,效率不错!如果同时出现 using where,意味着无法直接通过索引查找来查询到符合条件的数据;
Using index condition:MySQL5.6 之后新增的 ICP,using index condtion 就是使用了 ICP(索引下推),在存储引擎层进行数据过滤,而不是在服务层过滤,利用索引现有的数据减少回表的数据。
线程和进程的区别
进程是运行中的应用程序,线程是进程的内部的一个执行序列
进程是资源分配的最小单位,线程是CPU调度的最小单位。
一个进程可以有多个线程。线程又叫做轻量级进程,多个线程共享进程的资源
进程间切换代价大,线程间切换代价小
进程拥有资源多,线程拥有资源少地址
进程是存在地址空间的,而线程本身无地址空间,线程的地址空间是包含在进程中的
举个例子:
你打开QQ,开了一个进程;打开了迅雷,也开了一个进程。
在QQ的这个进程里,传输文字开一个线程、传输语音开了一个线程、弹出对话框又开了一个线程。
所以运行某个软件,相当于开了一个进程。在这个软件运行的过程里(在这个进程里),多个工作支撑的完成QQ的运行,那么这“多个工作”分别有一个线程。
所以一个进程管着多个线程。
通俗的讲:“进程是爹妈,管着众多的线程儿子”...
说说并发与并行的区别?
并发和并行最开始都是操作系统中的概念,表示的是CPU执行多个任务的方式。
串行:上一个开始执行的任务完成后,当前任务才能开始执行
并发:无论上一个开始执行的任务是否完成,当前任务都可以开始执行
(即 A B 顺序执行的话,A 一定会比 B 先完成,而并发执行则不一定。)
串行:有一个任务执行单元,从物理上就只能一个任务、一个任务地执行
并行:有多个任务执行单元,从物理上就可以多个任务一起执行
(即在任意时间点上,串行执行时必然只有一个任务在执行,而并行则不一定。)
举个例子:
你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。
你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。
你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。
并发的关键是你有处理多个任务的能力,不一定要同时。并行的关键是你有同时处理多个任务的能力。所以我认为它们最关键的点就是:是否是同时。
死锁是指多个线程因竞争资源而造成的一种互相等待的僵局。如图感受一下:
图片
死锁的四个必要条件:
互斥:一次只有一个进程可以使用一个资源。其他进程不能访问已分配给其他进程的资源。
占有且等待:当一个进程在等待分配得到其他资源时,其继续占有已分配得到的资源。
非抢占:不能强行抢占进程中已占有的资源。
循环等待:存在一个封闭的进程链,使得每个资源至少占有此链中下一个进程所需要的一个资源。
如何预防死锁?
加锁顺序(线程按顺序办事)
加锁时限 (线程请求所加上权限,超时就放弃,同时释放自己占有的锁)
死锁检测
__call()方法
这里简单说明一下__call方法的作用。
在PHP中,当我们访问一个不存在的类方法时,就会调用这个类的__call()方法。
(如果类方法不存在,又没有写__call()方法,PHP会直接报错)
ORM层数据库的链式调用,ORM主要是采用面向对象的思想对数据库进行操作。
如果只是当前类使用,不涉及外面的类调用,控制权限给private。
我们需要特别注意:创建唯一索引的字段,都不能允许为null,否则mysql的唯一性约束可能会失效。
如果一个引用计数增加,它将继续被使用,当然就不再在垃圾中。
如果引用计数减少到零,所在变量容器将被清除(free)。
就是说,仅仅在引用计数减少到非零值时,才会产生垃圾周期。
在一个垃圾周期中,通过检查引用计数是否减1,并且检查哪些变量容器的引用次数是零,来发现哪部分是垃圾。
由于 TCP 连接的创建和销毁开销较大,且并发数受系统资源限制,会造成性能瓶颈。RabbitMQ 使用信道的方式来传输数据。信道是建立在真实的 TCP 连接内的虚拟连接,且每条 TCP 连接上的信道数量没有限制。
rabbitMQ:
1.安装Erlang;
2.安装rabbitMQ;
3.创建发送消息和接受消息2个文件,在终端中运行;
4.文件代码包括:
1)建立TCP连接,new AMQPConnection;2)消费队列消息,$queue = new AMQPQueue($channel);$queue->consume('processMessage');