面经-万物心选php开发实习
实习项目的监听字段是基于什么实现的?
前端进行某个字段变更之后,后端提供接口,将修改后的数据打入消息队列,服务端作为消费者进行监听判断某个字段是否等于某个值然后进行对应的业务逻辑操作。
通过redis作为缓存层,实现分布式锁,保证消息不会被多次消费怎么实现的?(具体的数据流程)
通过业务id来保证,业务id作为锁的key,当消费信息的时候,把工单id作为key去申请锁,如果成功拿到了锁,说明此时不存在竞争关系,如果当前进程被阻塞了,说明有进程对该数据进行了消费。
不过后面发现如果有两个并发的线程的时候,被阻塞的业务后面拿到了还是可能会造成重复消费,于是又加了一层缓存,缓存已经消费了的业务id,用来表示是否被消费过,也就是保证业务的幂等性。
当时为什么考虑到设置一个重试机制降低消费的失败率?
当初实现合同是临时合同的时候,要删除销售合同的主从表,因为当时是两个表,涉及到了一个同步操作,即先删除主表之后再删除从表的操作,要保证事务的一致性和原子性,可能两个删除操作会有一个或者两个操作均失败的情况,就设计了这个重试机制,如果其中一个操作失败就进行事务回滚,然后将两个同步操作打入重试的队列中进行一次重试。
有没有其他的措施避免消费失败和重复消费?(除开分布式锁的方式)
避免消费失败的措施:
- 失败重试机制
- 死信队列
- 消息超时机制:避免因为一个消息阻塞导致后续的所有消息超时
- 负载均衡:如果是集群分布式服务,可以通过负载均衡算法分配给多个消费者。
- 消息确认机制:当消费者消费成功之后,发送确认信号给消息队列,表示消息处理成功,如果消费者在一定时间内没有返回确认信号,消息队列就重新分发消息给其他的消费者进行消费。
避免重复消费的措施:
- 实现幂等性:即相同的多个操作所产生的影响与仅执行一次的操作所产生的影响相同。确保相同的消息被多次发送之后,只会被处理一次。
- 消息确认机制:消息队列消费者消费之后会给生产者发送一个确认信号告诉已经消费信息成功,如果在一定时间内没有发送确认消息,消息队列就会认为此消息未被成功消费,会将该消息重新发送给消费者。
- 消费者限流:在高并发的场景中,为了消费者过度消费,就会在一定时间内只允许消费者消费一定数量的消息,以避免被过度消费而导致的重复消费。
如何解决数据库offset超过10000时失效的问题?
通过分页查询的方式,偏移量超过一万的情况下,我们可以通过limit的分页查询机制,一直查到第10001条数据的主键id,然后从此id开始进一步根据条件查询需要获取的数据。
为什么offset超过10000会查询不到数据呢?
OFFSET工作原理是通过全表扫描,先查询所有匹配的记录,然后再跳过指定数量的记录,如果偏移量特别大,数据库可能会需要一次性加载大量数据到内存中,会导致内存不足的问题。
实习时候的成长经历
1、有一次出现过一次消息队列重复消费的情况,当时情况是两个不同的测试环境,做相同业务消费时消费了两次,然后查代码一直也没有查到原因,就去看配置环境,原来是当时运维那边部署消息队列,两个环境都部署到了一个队列上面,没有做环境隔离。。。
2、在开发流程中先根据需求,写出开发思路,
JWT代替Cookie-Session机制的业务流程,以及如何管理用户状态?token的存在什么位置?服务端是如何校验token是否有效的?
业务及校验流程:
用户通过学号+教务密码向Java后端发起请求,然后Java后端调用Python的服务进行校验账号密码是否正确,如果正确就会向前端返回一个token,客户端将Token保存在本地缓存之中,方便以后在一些业务操作的时候,都会从本地缓存中获取Token并将其放在Http请求的Header中发送请求,服务端获取到请求之后从Http的header里面获取token,并通过服务端保存的密匙进行重新生成新的signature和客户端发过来的token的signature进行比较确认token是否有效,再从payload部分获取相关用户信息进行进一步身份校验。
每次服务端在校验token有效的时候会根据payload的签发token时间+超时时间,当离过期时间只有1天的时候,就会重新签发一个Token给前端。
Token存放位置:
HTTP的Header部分。
Java为什么需要垃圾回收机制呢?
主要是为了管理内存,自动释放不再使用的对象,以避免内存泄漏和提高程序性能。
什么情况下会出现资源的浪费(内存泄漏)?
1、长期存活的对象占用过多内存,就是长生命周期对象持有短生命周期的引用导致部分短生命周期的对象一直没有被释放,进而积累过多的情况下出现内存泄漏的问题。
2、各种连接:比如数据库连接、网络连接,对于这些连接操作都需要在不使用的时候进行关闭操作,垃圾回收期才会进行回收。
3、变量作用域不合理:比如要对一个msg进行保存操作,但是msg定义成一个成员变量,然后通过receiveMsg()方法进行相关保存业务逻辑操作,但是因为msg是成员变量,生命周期和类的生命周期一样,所以导致存储操作完成之后,msg还没有被回收释放,可能就会导致内存泄漏。
创建了一个String类型的数据什么时候会被垃圾回收机制回收?
1、对象不再引用。
2、对象引用被显式设置为null。
3、对象的引用超出作用域:在一个方法内部创建的,那么在这个方法执行完之后就会被收回。
3、垃圾回收机制触发:Java虚拟机判断系统内存不足就会触发此机制,尝试回收不再被使用的对象。
了解哪些垃圾回收算法并介绍一下
标记-清除算法:
首先标记处所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。
导致的问题:
1、标记清除的过程效率都不高
2、标记清除后会产生大量不连续的内存碎片。
复制算法:
将内存空间分为两半,每次使用其中的一块,当这块使用完之后,将这块还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。
导致的问题:
1、可用内存变小。
2、不适合老年代,如果存活对象数量大,复制性能会比较差。
标记-整理算法:
标记不需要回收的对象,然后将没有标记的对象统一向一端移动,然后直接清理掉端边界以外的内存。
导致问题:
只适合老年代回收频率较低的场景
分代收集算法:
根据不同对象的生命周期将内存分为几块。一般将Java堆分为新生代和老年代,可以根据各个年代的特点选择合适的垃圾收集算法。
比如:新生代中,每次都会有大量对象死去,可以选择复制算法,只需要付出少量对象的复制成本就可以完成垃圾收集。而对于老年代生命周期较长,存活率较高,可以选择标记-清除算法或者标记-整理算法实现。
垃圾回收机制为什么要分新生代和老年代呢?
根据不同对象的生命周期将内存分为几块。一般将Java堆分为新生代和老年代,可以根据各个年代的特点选择合适的垃圾收集算法。
为什么新生代和老年代不放在一起进行回收呢?
1、不同对象生命周期:可以根据不同的生命周期对象采用不同的回收策略
2、不同回收算法:一般新生代会采用复制算法清除,因为可以标记较少的对象复制到另一块内存上面去,然后一次性清理整个区域,效率较高,对于老年代生命周期长且存活率高,就可以采用标记-整理或者标记-删除的办法,将不需要回收的对象进行标记,然后直接删除没有标记的即可。
3、避免频繁回收老年代:混在一起会导致老年代频繁的回收,降低系统的性能。
数组和链表的优缺点(Array和ArrayList),谁的访问效率会更高?
- 数组访问效率比链表高,因为链表通常是动态扩容的,每次的动态调整都会带来一定的开销以及维护指针等,所以访问效率较低。
- 链表能灵活进行动态扩容或缩容,但是数组一旦确定大小就不能进行扩容和缩容操作了。
- 链表有丰富的api可以直接动态进行增删查改,而数组是一个固定长度的数组,只能按下标访问其中的元素,不具备动态增删的能力。
- 链表只能存储对象(对于基本数据类型可以采用包装类进行存储),但是数组既可以存储对象也可以存储基本数据类型。
- 链表可以通过泛型保证类型安全,但是数组不可以。
有没有一个数据结构可以访问元素时间是O(1),又可以灵活的调整他的容量大小?
哈希表可以将key映射到存储位置来实现快速访问,使用哈希函数将键转换成索引,然后将值存在对应索引的位置,在理想情况下,能够在O(1)情况下根据键找到对应的值。哈希表具有灵活的扩容机制,当哈希表中元素过多的时候就会自动扩容,重新分配更大的空间。
HashMap是如何解决哈希冲突的呢?为什么要选择红黑树来作为解决哈希冲突的数据结构?(即:选择红黑树的优势是什么?)
hashmap是基于数组+链表/红黑树的数据结构来具体实现的,首先hashmap产生哈希冲突的时候会判断链表的长度是否大于8,如果大于的话就会转换成红黑树(此时进入红黑树转换的函数的时候,会判断数组的长度是否大于64,如果数组长度小于64,就不会转换成红黑树,而是选择扩容的机制解决哈希冲突)
选择红黑树的原因:
1、如果是二叉查询树比较极端的情况下,当子节点都比父节点大或者小的时候,二叉查找树又会退化成链表,此时时间复杂度又是O(n)。
2、如果是平衡二叉树(AVL),因为它每个节点的左子树和右子树的高度差至多等于1,如果大于1了就会通过左旋或者右旋的方式,使其复杂度一直维持在O(logN),但是因为这个左旋和右旋的原因,导致插入数据的时候需要消耗大量的时间。
场景题:现在有路由器a,b,c,d,有一个数据包按照正常情况下应该是从a转发到b,从b转发到c,从c转发到d,但是由于路由器c配置出错了,导致从a转发到b,再从b转发到c,最后又从c转发回了a,形成了一个循环路由,这时候这个数据包在IP层会被怎么处理掉呢?
在IP层遇到循环路由的情况下,数据包会被丢弃。IP协议是一种无连接的、不可靠的协议,它不会主动检查循环路由或处理数据包的循环传递。
为了防止数据包在网络中无限循环,网络管理员通常会配置路由器使用一些防循环机制,比如距离矢量路由协议中的Split Horizon技术,或链路状态路由协议中的Reverse Path Forwarding(RPF)检查。这些机制有助于检测和避免数据包在网络中形成循环路径。
但是,即使配置了防循环机制,由于网络的复杂性和问题可能的多样性,有时候循环路由问题仍然可能发生。在这种情况下,IP层不会继续无限传递数据包,而是丢弃它,从而避免对网络产生更严重的影响。丢弃数据包是为了确保网络中的数据传输是可靠和有效的,即使牺牲了某些数据包。
Linux看文件大小怎么做?stat命令显示的文件大小是以块为单位,怎么转换成人类可识别的大小?
stat的基本单位是块,也就是byte。转换成可识别的大小的话,通过1 kb = 1024byte实现。
可以通过ls -lh