PHP面试题

mysql

1. mysql 存储引擎

常用的主要分为两种、一种是 innodb,一种是 myisam,两者的主要区别是:

1⃣️ myisam 不支持事务处理,而 innoDB 支持事务处理

2⃣️ myisam 不支持外键,innoDB 支持外键

3⃣️ myisam 支持全文检索,而 innoDB 在 MySQL5.6 版本之后才支持全文检索

4⃣️数据的存储形式不一样,mysiam 表存放在三个文件:结构、索引、数据,innoDB 存储把结构存储为一个文件,索引和数据存储为一个文件

5⃣️ myisam 在查询和增加数据性能更优于 innoDB,innoDB 在更改一些数据的情况下性能更高

6⃣️ myisam 支持表锁,而 innoDB 支持行锁

2. mysql 优化咋做的

1⃣️设计角度:存储引擎的选择,字段类型选择,范式

2⃣️功能角度:可以利用 mysql 自身的特性,如索引,查询缓存,碎片整理,分区、分表等

3⃣️ sql 语句的优化方面:尽量简化查询语句,能查询字段少就尽量少查询字段,优化分

页语句、分组语句等。

4⃣️部署大负载架构体系:数据库服务器单独出来,负载大时可以采用主从复制,读写

分离机制进行设计

5⃣️从硬件上升级数据库服务器。

3. mysql 的三大范式

1⃣️每个列都不可以再拆分

2⃣️ 在第一范式的基础上,非主键列完全依赖于主键,而不能是依赖于主键的一部分。

3⃣️第三范式(3NF):在第二范式的基础上,非主键列只依赖于主键,不依赖于其他非主键。

4. mysql索引有几种

1⃣️主键索引:数据记录里面不能有 null,数据内容不能重复,在一张表里面不能有

多个主键索引。

2⃣️普通索引:使用字段关键字建立的索引,主要是提高查询速度。

3⃣️唯一索引:字段数据是唯一的,数据内容里面能否为 null,在一张表里面,是可

以添加多个唯一索引。

4⃣️联合索引:索引不能包含null值,索引的使用要遵循最左前缀匹配原则。(a,b,c)其实是创建了(a,b)(a,c)(a,b,c)三个索引。

5⃣️覆盖索引: 如果查询到的索引包含了要查找的值就无需回表查询 为覆盖索引。

6⃣️聚簇索引:将数据存储与索引放到了一块,找到索引也就找到了数据。

7⃣️非聚簇索引:将数据存储于索引分开结构,索引结构的叶子节点指向了数据的对应行,它的叶子节点只包含一个主键值,通过非聚簇索引查找记录要先找到主键,然后通过主键再到聚簇索引中找到对应的记录行,这个过程被称为回表。

8⃣️索引下推 索引下推的主要功能就是改善这一点,在联合索引中,先通过条件过滤掉不用回表的记录,然后再回表查询索引,减少回表查询次数。比如(张三,10)(张三,20)查询张三 30 的时候先查询张三 满足以后在看是否满足30 不满足也就不用在回表查询了。

5. mysql 事物的特性

1⃣️ 原子性: 要不全部成功,要不全部撤销

2⃣️一致性:数据库正确地改变状态后,数据库的一致性约束没有被破坏

3⃣️ 隔离性:事务之间相互独立,互不干扰

4⃣️持久性:事务的提交结果,将持久保存在数据库中

6. mysql 事务并发问题

1⃣️丢失更新:在没有事务隔离的情况下,两个事务都同时更新一行数据,但是第二个事务却中途失败退出, 导致对数据的两个修改都失效了。

例如:

张三的工资为5000,事务A中获取工资为5000,事务B获取工资为5000,汇入100,并提交数据库,工资变为5100,
随后
事务A发生异常,回滚了,恢复张三的工资为5000,这样就导致事务B的更新丢失了。

2⃣️脏读:脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。

例如:

张三的工资为5000,事务A中把他的工资改为8000,但事务A尚未提交。
与此同时,
事务B正在读取张三的工资,读取到张三的工资为8000。
随后,
事务A发生异常,而回滚了事务。张三的工资又回滚为5000。
最后,
事务B读取到的张三工资为8000的数据即为脏数据,事务B做了一次脏读。

3⃣️不可重复读:是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。

例如:

在事务A中,读取到张三的工资为5000,操作没有完成,事务还没提交。
与此同时,
事务B把张三的工资改为8000,并提交了事务。
随后,
在事务A中,再次读取张三的工资,此时工资变为8000。在一个事务中前后两次读取的结果并不致,导致了不可重复读。

4⃣️第二类丢失更新:不可重复读的特例。有两个并发事务同时读取同一行数据,然后其中一个对它进行修改提交,而另一个也进行了修改提交。这就会造成第一次写操作失效。

例如:

在事务A中,读取到张三的存款为5000,操作没有完成,事务还没提交。
与此同时,
事务B,存储1000,把张三的存款改为6000,并提交了事务。
随后,
在事务A中,存储500,把张三的存款改为5500,并提交了事务,这样事务A的更新覆盖了事务B的更新。

5⃣️幻读:是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。

例如:

目前工资为5000的员工有10人,事务A读取所有工资为5000的人数为10人。
此时,
事务B插入一条工资也为5000的记录。
这是,事务A再次读取工资为5000的员工,记录为11人。此时产生了幻读。

提醒:
不可重复读的重点是修改,同样的条件,你读取过的数据,再次读取出来发现值不一样了。
幻读的重点在于新增或者删除,同样的条件,第 1 次和第 2 次读出来的记录数不一样。

7. mysql隔离级别

事物隔离级别脏读不可重复读幻读
读未提交 read-uncommitted
不可重复读 read-committed
可重复读 repeatable-read
串行化 serializable
提醒: Mysql默认的事务隔离级别为repeatable_read 可重复读。

8. mysql Innodb锁机制

之所以以InnoDB为主介绍锁,是因为InnoDB支持事务,支持行锁和表锁用的比较多,Myisam不支持事务,只支持表锁)

1⃣️共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。

2⃣️排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。

3⃣️意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。

4⃣️意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

说明:
1⃣️共享锁和排他锁都是行锁,意向锁都是表锁,应用中我们只会使用到共享锁和排他锁,意向锁是mysql内部使用的,不需要用户干预。

2⃣️对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁,事务可以通过以下语句显示给记录集加共享锁或排他锁。

3⃣️共享锁(S):SELECT * FROM table_name WHERE … LOCK IN SHARE MODE。

4⃣️排他锁(X):SELECT * FROM table_name WHERE … FOR UPDATE。

5⃣️ InnoDB行锁是通过给索引上的索引项加锁来实现的,因此InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!

9. mysql 主从搭建

提示:数据库版本最好一致,避免出现其他问题

主从复制原理

1⃣️数据库有个bin-log二进制文件,记录了所有sql语句。

2⃣️我们的目标就是把主数据库的bin-log文件的sql语句复制过来。

3⃣️让其在从数据的relay-log重做日志文件中再执行一次这些sql语句即可。

主从复制搭建

1⃣️ 确保mysql版本相同 创建账户 赋予权限

2⃣️配置my.cnf 开启binlog 设置server-id 重启mysql

3⃣️ 同步数据 对数据库进行read lock 保证数据的一致性

10. B-tree 和B+tree 区别

B-tree
1⃣️度(Degree)-节点的数据存储个数。

2⃣️叶节点具有相同的深度。

3⃣️叶节点的指针为空。

4⃣️节点中的数据key从左到右递增排列。

B+tree
1⃣️非叶子节点不存储data,只存储key,可以增大度的指。

2⃣️.叶子节点不存储指针

3⃣️顺序访问指针,提高区间访问的性能

11. mysql运行原理

  1. 1

Redis

1. Redis 数据类型

1⃣️ string 字符串 可以为整形、浮点型 简单的计数 存储一些特定的值。

incr 自加
decr 自减
incrby 加
decrby 减

2⃣️ list 列表(实现队列,元素不唯一,先入先出原则) 可以做消息队列 先进先出。

lpush:从左边推入
lpop:从右边弹出
rpush:从右变推入
rpop:从右边弹出
llen:查看某个list数据类型的长度

3⃣️ set 集合(各不相同的元素) 存储的是不相同的元素,可以做全局去重功能。

sadd:添加数据
scard:查看set数据中存在的元素个数
sismember:判断set数据中是否存在某个元素
srem:删除某个set数据中的元素

4⃣️ hash散列值(hash的key必须是唯一的) 存放的是结构化的对象,比较方便的就是操作其中的某个字段。

hset:添加hash数据
hget:获取hash数据
hmget:获取多个hash数据

5⃣️ sort set 有序集合 可以做一些排行榜之类有序的列表。

zadd:添加
zcard:查询
zrange:数据排序

2. Redis 持久化机制

1⃣️ RDB是Redis默认的持久化方式。按照一定的时间周期策略把内存的数据以快照的形式保存到硬盘的二进制文件。即Snapshot快照存储,对应产生的数据文件为dump.rdb,通过配置文件中的save参数来定义快照的周期。( 快照可以是其所表示的数据的一个副本,也可以是数据的一个复制品。)

2⃣️ AOF:Redis会将每一个收到的写命令都通过Write函数追加到文件最后,类似于MySQL的binlog。当Redis重启是会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。

3⃣️ 当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。

3. Redis缓冲雪崩、缓冲穿透、缓冲预热、缓存更新、缓冲降级等问题

1⃣️ 缓存雪崩 :由于原有缓存失效,新缓存未存储

(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。

解决办法

大多数系统设计者考虑用加锁( 最多的解决方案)或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就时讲缓存失效时间分散开。

2⃣️ 缓存穿透 :缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。

解决办法

最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。

另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。

3⃣️布隆过滤器(推荐)

就是引入了k(k>1)k(k>1)个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过程。

它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。

Bloom-Filter算法的核心思想就是利用多个不同的Hash函数来解决“冲突”。

Hash存在一个冲突(碰撞)的问题,用同一个Hash得到的两个URL的值有可能相同。为了减少冲突,我们可以多引入几个Hash,如果通过其中的一个Hash值我们得出某元素不在集合中,那么该元素肯定不在集合中。只有在所有的Hash函数告诉我们该元素在集合中时,才能确定该元素存在于集合中。这便是Bloom-Filter的基本思想。

Bloom-Filter一般用于在大数据量的集合中判定某元素是否存在。

受提醒补充 :缓存穿透与缓存击穿的区别

4⃣️缓存击穿:是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据。

解决方案:在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。

增:给一个我公司处理的案例:背景双机拿token,token在存一份到redis,保证系统在token过期时都只有一个线程去获取token;线上环境有两台机器,故使用分布式锁实现。

5⃣️缓存预热:

缓存预热这个应该是一个比较常见的概念,相信很多小伙伴都应该可以很容易的理解,缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

解决思路:

1、直接写个缓存刷新页面,上线时手工操作下;

2、数据量不大,可以在项目启动的时候自动进行加载;

3、定时刷新缓存;

6⃣️缓存更新

除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:

(1)定时去清理过期的缓存;

(2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。

两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡。

7⃣️缓存降级

当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。

降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。

以参考日志级别设置预案:

(1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;

(2)警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;

(3)错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;

(4)严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。

服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。

4. 热点数据和冷数据是什么?

1⃣️热点数据,缓存才有价值。

2⃣️对于冷数据而言,大部分数据可能还没有再次访问到就已经被挤出内存,不仅占用内存,而且价值不大。频繁修改的数据,看情况考虑使用缓存。

3⃣️那存不存在,修改频率很高,但是又不得不考虑缓存的场景呢?有!比如,这个读取接口对数据库的压力很大,但是又是热点数据,这个时候就需要考虑通过缓存手段,减少数据库的压力,比如我们的某助手产品的,点赞数,收藏数,分享数等是非常典型的热点数据,但是又不断变化,此时就需要将数据同步保存到Redis缓存,减少数据库压力。

5. Memcache 和 Redis 区别
1⃣️存储方式 Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。 Redis有部份存在硬盘上,redis可以持久化其数据。

2⃣️数据支持类型 memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型 ,提供list,set,zset,hash等数据结构的存储。

3⃣️使用底层模型不同 它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。 Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。

4⃣️ value 值大小不同:Redis 最大可以达到 512M;memcache 只有 1mb。

5⃣️ Redis的速度比memcached快很多。

6⃣️ Redis支持数据的备份,即master-slave模式的数据备份。

6. 单线程的Redis 为什么比较快
1⃣️纯内存操作。

2⃣️单线程操作 避免了频繁的上下文切换。

3⃣️ 采用了非阻塞I/O多路复用机制。

7. Redis 的过期策略 以及淘汰机制
1⃣️ Redis 采用的是定期删除+惰性删除策略 定时删除策略会什么消耗cpu

2⃣️ 定期删除+惰性删除策略是如何工作的:

定期删除,redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。

于是,惰性删除派上用场。也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。

采用定期删除+惰性删除就没其他问题了么?

不是的,如果定期删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效。这样,redis的内存会越来越高。那么就应该采用内存淘汰机制。

8. Redis 为什么是单线程的?

1⃣️绝大部分请求是纯粹的内存操作(非常快速)。

2⃣️采用单线程,避免了不必要的上下文切换和竞争条件。

3⃣ 非阻塞 IO 优点

1⃣️速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)。

2⃣️ 支持丰富数据类型,支持string,list,set,sorted set,hash。

3⃣️支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行。

4⃣️ 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除如何解决redis的并发竞争key问题。

同时有多个子系统去set一个key。这个时候要注意什么呢? 不推荐使用redis的事务机制。因为我们的生产环境,基本都是redis集群环境,做了数据分片操作。你一个事务中有涉及到多个key操作的时候,这多个key不一定都存储在同一个redis-server上。因此,redis的事务机制,十分鸡肋。

1⃣️如果对这个key操作,不要求顺序: 准备一个分布式锁,大家去抢锁,抢到锁就做set操作即可。

2⃣️如果对这个key操作,要求顺序: 分布式锁+时间戳。 假设这会系统B先抢到锁,将key1设置为{valueB 3:05}。接下来系统A抢到锁,发现自己的valueA的时间戳早于缓存中的时间戳,那就不做set操作了。以此类推。

3⃣️利用队列,将set方法变成串行访问也可以redis遇到高并发,如果保证读写key的一致性。

对redis的操作都是具有原子性的,是线程安全的操作,你不用考虑并发问题,redis内部已经帮你处理好并发的问题了。

9. Redis集群的方案如何实现

1⃣️ twemproxy,大概概念是,它类似于一个代理方式, 使用时在本需要连接 redis 的地方改为连接 twemproxy, 它会以一个代理的身份接收请求并使用一致性 hash 算法,将请求转接到具体 redis,将结果再返回 twemproxy。

缺点: twemproxy 自身单端口实例的压力,使用一致性 hash 后,对 redis 节点数量改变时候的计算值的改变,数据无法自动移动到新的节点。

2⃣️ codis,目前用的最多的集群方案,基本和 twemproxy 一致的效果,但它支持在 节点数量改变情况下,旧节点数据可恢复到新 hash 节点。

3⃣️ Redis cluster3.0 自带的集群,特点在于他的分布式算法不是一致性 hash,而是 hash 槽的概念,以及自身支持节点设置从节点。

10. 有没有尝试进行多机Redis 的部署?如何保证数据一致的?

1⃣️主从复制,读写分离

一类是主数据库(master)一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,而从数据库一般是只读的,并接收主数据库同步过来的数据,一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。

11. 对于大量的请求如何处理

1⃣️ redis是一个单线程程序,也就说同一时刻它只能处理一个客户端请求。

2⃣️ redis是通过IO多路复用(select,epoll, kqueue,依据不同的平台,采取不同的实现)来处理多个客户端请求的。

12. Redis 常见性能问题和解决方案?

1⃣️ Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件。

2⃣️如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次。

3⃣️为了主从复制的速度和连接的稳定性, Master 和 Slave 最好在同一个局域网内。

4⃣️尽量避免在压力很大的主库上增加从库。

5⃣️主从复制不要用图状结构,用单向链表结构更为稳定,即: Master <- Slave1 <- Slave2 <-
Slave3….。

13. 为什么 Redis 的操作都是原子性的 如何保证原子性

1⃣️ 对于Redis而言,命令的原子性指的是:一个操作的不可以再分,操作要么执行,要么不执行。

2⃣️ Redis的操作之所以是原子性的,是因为Redis是单线程的。

3⃣️ Redis本身提供的所有API都是原子操作,Redis中的事务其实是要保证批量操作的原子性。

多个命令在并发中也是原子性的吗?

不一定, 将get和set改成单命令操作,incr 。使用Redis的事务,或者使用Redis+Lua==的方式实现。

14. Redis 事务
Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的

Redis会将一个事务中的所有命令序列化,然后按顺序执行。

1⃣️ redis 不支持回滚。Redis 在事务失败时不进行回滚,而是继续执行余下的命令, 所以 Redis 的内部可以保持简单且快速。

2⃣️如果在一个事务中的命令出现错误,那么所有的命令都不会执行。

3⃣️如果在一个事务中出现运行错误,那么正确的命令会被执行。

注:redis的discard只是结束本次事务,正确命令造成的影响仍然存在.

1⃣️ 、MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。

2⃣️、EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil 。

3⃣️、通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。4、WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。

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

将 key 的值设为 value ,当且仅当 key 不存在。 若给定的 key 已经存在,则 SETNX 不做任何动作

解锁:使用 del key 命令就能释放锁

解决死锁:

1⃣️通过Redis中expire()给锁设定最大持有时间,如果超过,则Redis来帮我们释放锁。

2⃣️使用 setnx key "当前系统时间+锁持有的时间"和getset key "当前系统时间+锁持有的时间"组合的命令就可以实现。

16. 快速排序

简单算法

排序法平均时间最差情形稳定度额外空间
冒泡O(n2)O(n2)稳定O(1)
快速O(nlogn)O(n2)不稳定O(logn)
插入O(n2)O(n2)稳定O(1)
选择O(n2)O(n2)不稳定O(1)
归并O(nlogn)O(nlogn)稳定O(n)
交换O(n2)O(n2)不稳定O(1)
基数O(logRB)O(logRB)稳定O(n)
ShellO(nlogn)O(ns) 1<s<2不稳定O(1)
O(nlogn)O(nlogn)不稳定O(1)

1. 冒泡排序

function BubbleSort(&$arr){    //必须是&$arr,传一个地址,如果是$arr,根据函数调用机制,排序将无法生效
        $temp=0;    //中间变量
        $flag=false;
        //外层循环控制循环次数
        for($i=0;$i<count($arr)-1;$i++){
          //内层循环控制每一次循环的交换
            for($j=0;$j<count($arr)-1-$i;$j++){
                //若前面的数比后面的数大,则交换
                if($arr[$j]>$arr[$j+1]){
                    $temp=$arr[$j];
                    $arr[$j]=$arr[$j+1];
                    $arr[$j+1]=$temp;
                    $flag=true;
                }
            }
            if(!$flag){
                //已经是有序
                break;
            }
            $flag=false;
        }
        return $arr;
    }

2. 快速排序

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 = quickSort($left_array);
            $right_array = quickSort($right_array);
            //合并
            return array_merge($left_array, array($base_num), $right_array);
        }

3. 插入排序

function insertSort(&$arr){
            //默认第一个数,即下标为0的数,已经是有序
            for($i=1;$i<count($arr);$i++){
                //准备插入的数
                $insertVal=$arr[$i];
                //准备先和$insertIndex比较
                $insertIndex=$i-1;
                while($insertIndex>=0&&$insertVal<$arr[$insertIndex]){
                    $arr[$insertIndex+1]=$arr[$insertIndex];
                    $insertIndex--;
                }
                $arr[$insertIndex+1]=$insertVal;
            }
            return $arr;
        }

3. 选择排序

function selectSort(&$arr){
            $temp=0;    //中间变量
            //外层循环
            for($i=0;$i<count($arr)-1;$i++){
                //假设第$i个数就是最小的数
                
                //记录假设的最小数的值
                $minVal=$arr[$i];
                //记录假设的最小数的下标
                $minIndex=$i;
                for($j=$i+1;$j<count($arr);$j++){
                    //如果假设的最小值,不是最小
                    if($minVal>$arr[$j]){
                        $minVal=$arr[$j];
                        $minIndex=$j;
                    }
                }
                //最后交换
                $temp=$arr[$i];
                $arr[$i]=$arr[$minIndex];    //循环外面,不能出现$j
                $arr[$minIndex]=$temp;
            }
            return $arr;
        }

4. 归并排序

// 归并排序主程序
function mergeSort($arr) {
    $len = count($arr);
    if ($len <= 1) {
        return $arr;
    } // 递归结束条件, 到达这步的时候, 数组就只剩下一个元素了, 也就是分离了数组

    $mid = intval($len / 2); // 取数组中间
    $left = array_slice($arr, 0, $mid); // 拆分数组0-mid这部分给左边left
    $right = array_slice($arr, $mid); // 拆分数组mid-末尾这部分给右边right
    $left = mergeSort($left); // 左边拆分完后开始递归合并往上走
    $right = mergeSort($right); // 右边拆分完毕开始递归往上走
    $arr = merge($left, $right); // 合并两个数组,继续递归

    return $arr;
}

// merge函数将指定的两个有序数组(arrA, arr)合并并且排序
function merge($arrA, $arrB) {
    $arrC = array();
    while (count($arrA) && count($arrB)) {
        // 这里不断的判断哪个值小, 就将小的值给到arrC, 但是到最后肯定要剩下几个值,
        // 不是剩下arrA里面的就是剩下arrB里面的而且这几个有序的值, 肯定比arrC里面所有的值都大所以使用
        $arrC[] = $arrA[0] < $arrB[0] ? array_shift($arrA) : array_shift($arrB);
    }

    return array_merge($arrC, $arrA, $arrB);
}

4. 快速排序

$a = array(2,13,42,34,56,23,67,365,87665,54,68,3);

function quick_sort($a)
{
    // 判断是否需要运行,因下面已拿出一个中间值,这里<=1
    if (count($a) <= 1) {
        return $a;
    }
    $middle = $a[0]; // 中间值

    $left = array(); // 接收小于中间值
    $right = array();// 接收大于中间值

    // 循环比较
    for ($i=1; $i < count($a); $i++) { 

        if ($middle < $a[$i]) {
            // 大于中间值
            $right[] = $a[$i];
        } else {
            // 小于中间值
            $left[] = $a[$i];
        }
    }

    // 递归排序划分好的2边
    $left = quick_sort($left);
    $right = quick_sort($right);

    // 合并排序后的数据,别忘了合并中间值
    return array_merge($left, array($middle), $right);
}
print_r(quick_sort($a));

协议

  1. Oauth2.0** 协议 ✅**

  2. Http Tcp** 的三次握手**

1⃣️第一次握手:客户端尝试连接服务器,向服务器发送 syn 包(同步序列编号Synchronize Sequence Numbers),syn=j,客户端进入 SYN_SEND 状态等待服务器确认

2⃣️第二次握手:服务器接收客户端syn包并确认(ack=j+1),同时向客户端发送一个 SYN包(syn=k),即 SYN+ACK 包,此时服务器进入 SYN_RECV 状态

3⃣️️第三次握手:第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手

  1. Http** Tcp **的四次挥手

  2. ff

  3. Http** 的三次握手**

改进的四次握手

对于一个已经建立的连接,TCP使用改进的四次握手来释放连接(使用一个带有FIN附加标记的报文段)。TCP关闭连接的步骤如下:

1⃣️当主机A的应用程序通知TCP数据已经发送完毕时,TCP向主机B发送一个带有FIN附加标记的报文段(FIN表示英文finish)。

2⃣️主机B收到这个FIN报文段之后,并不立即用FIN报文段回复主机A,而是先向主机A发送一个确认序号ACK,同时通知自己相应的应用程序:对方要求关闭连接(先发送ACK的目的是为了防止在这段时间内,对方重传FIN报文段)。

3⃣️主机B的应用程序告诉TCP:我要彻底的关闭连接,TCP向主机A送一个FIN报文段。

4⃣️主机A收到这个FIN报文段后,向主机B发送一个ACK表示连接彻底释放。

8** 、 **cookie session 的区别

1、cookie 数据存放在客户的浏览器上,session 数据放在服务器上。

2、cookie 不是很安全,别人可以分析存放在本地的 COOKIE 并进行 COOKIE 欺骗

考虑到安全应当使用 session。

3、session 会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能

考虑到减轻服务器性能方面,应当使用 COOKIE。

4、单个 cookie 保存的数据不能超过 4K,很多浏览器都限制一个站点最多保存 20 个

cookie。

5、所以个人建议:

将登陆信息等重要信息存放为 SESSION

其他信息如果需要保留,可以放在 COOKIE 中

9** 禁用 **** cookie **** 后, **session 还能用吗?

可以,在存储 session 的文件中,生成 sessionID,通过 get 传参的方式将 sessionID

传到要实现 session 共享的页面,读取 sessionID,从而从 session 中获取数据。

  1. 设计模式

46** 、 **php 的设计模式

1、单例模式 2、工厂模式 3、观察者模式 4、命令链模式 5、策略模式

单例模式:

一个类在整个应用中,只有一个对象实例的设计模式

类必须自行创建这个实例

必须自行向整个系统提供这个实例

三私:私有静态成员变量、构造函数、克隆函数

一公:公共的静态方法

2、工厂模式

可以根据输入的参数或者应用程序配置的不同一创建一种专门用来实例化并返回其它

类的实例的类

3、观察者模式

观察者模式提供了组件之间紧密耦合的另一种方法。

该模式:一个对象通过添加一个方法(该方法允许另一个对象,即观察者注册自己)全

本身变得可观察。当可观察的对象更改时,它会将消息发送到已注册的观察者。这些观

察者使用该信息执行的操作与可观察的对象无关。

4、命令链模式:

以松散耦合主题为基础,发送消息、命令和请求,或通过一组处理程序发送任意内容。

每个处理程序都会自行判断自己能否处理请求,如果可以,该请求被处理,进程停止。

5、策略模式:

此算法是从复杂类提取的,因而可以方便地替换。

  1. 常用的php框架 以及他们的优缺点 ✅

tp laravel

  1. 手写单列模式 ✅
  2. 加密方法✅
  3. http的三次握手✅

118.** 接口和抽象类的区别是什么? **** ✅**

抽象类是一种不能被实例化的类,只能作为其他类的父类来使用。抽象类是通过关键字

abstract 来声明的。抽象类与普通类相似,都包含成员变量和成员方法,两者的区别在于,抽象类中至少要包含

一个抽象方法,抽象方法没有方法体,该方法天生就是要被子类重写的。

抽象方法的格式为:

abstract function abstractMethod();

接口是通过

interface

关键字来声明的,接口中的成员常量和方法都是 public

的,方法可

以不写关键字

public

,接口中的方法也是没有方法体。接口中的方法也天生就是要被子类实

现的。

抽象类和接口实现的功能十分相似,最大的不同是接口能实现多继承。在应用中选择抽象类

还是接口要看具体实现。

子类继承抽象类使用

extends,子类实现接口使用

implements。

什么是队列?排它锁,** Myisam **** 死锁如何解决? **

在默认情况下 MYisam 是表级锁,所以同时操作单张表的多个动作只能以队列的方式进行;

排它锁又名写锁,在

SQL

执行过程中为排除其它请求而写锁,在执行完毕后会自动释放;

死锁解决:先找到死锁的线程号,然后杀掉线程

ID

常用的魔法函数✅

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值