殁舞

导航

 

华为赛意16

jvm运行时内存

jvm运行时内存主要分5块,分别是栈、程序计数器、本地方法栈、方法区、堆,其中栈、程序计数器、本地方法栈都是线程私有的,只有元空间和堆是共用的。
比如栈中存放的都是线程相关的资源,栈里面每个线程又可以细分为一个个栈帧,每个栈帧就是一次方法调用,里面存放了这次调用的相关数据,比如局部变量表、动态链接、操作数栈、方法返回地址等。一般来说一个线程的栈资源默认为1MB,一个栈帧为1KB,所以递归调用太深可能会导致栈溢出。
另外像本地方法栈类似,只不过是给native方法使用的。
再像程序计数器主要就是用来记录当前指令执行的位置,可以为每个线程保存各自的执行位置,方便线程切换后能恢复原执行的位置
再比如方法区,现在用的1.8也就是元空间,就是用来存放一些元数据的,包括加载的class类、方法、常量、符号引用等
最后一块就是堆了,也是内存占用最大的一块,也是我们应该重点关注的地方,因为绝大部分时间GC都发生在这里,像我们现在生产用的jdk1.8,gc用的cms,特点就是停顿时间短,符合toC的业务,像fgc后每次老年代清理不多就要考虑是不是内存泄漏了,这里可以通过jstat看内存变化,如果有明显特征可以用jmap dump出快照,用mat分析,看看哪个对象深堆最大,一般就是泄漏点

栈帧的动态链接

栈帧中的动态链接存放的是运行时常量池中该方法所属类的引用,通过查询常量池中的符号引用可以拿到目标方法的直接引用,从而完成方法调用

arrayList初始化及扩容

默认初始化大小是10,当满了之后会进行扩容,默认是1.5倍,扩容会新建一个数组,然后通过arrayCopy操作复制过来,再切换引用完成扩容,一般建议在新建list时就指定好预期大小,尽量减小扩容次数。

arraylist按照某实例字段分组求个数

思路就是遍历list,用map进行存储,key为姓名,value为实例个数,具体实现可以直接使用lambda表达式,stream.collect.group.by

springBean的循环依赖问题

循环依赖是发生在spring中bean的初始化过程中的,bean完成反射实例化后开始进行属性注入,如果这个时候A依赖了B,B又依赖了A,这个时候就会发生循环依赖。spring在这里使用了三级缓存来帮我们解决这个问题,具体来说,一级缓存singletonobject,存放的是完整的最终的bean,查找时优先从这个map取bean,如果没找到就会到二级缓存中找,二级缓存存放的就是earlySingletonObject,这里面的就是已经完成实例化,但是未进行初始化的半成品bean,虽然不能对外提供完整服务,但是作为依赖注入还是没问题的。如果二级缓存中也没有就会去三级缓存中查询,三级缓存中存放的就是这个bean的一个工厂类objectFactory,拿到工厂类后如果注入的是代理类就会在这里先生成代理同时把代理类放到二级缓存,接着完成B的初始化,等B完成后再回头到A的属性注入,完成A的初始化,完了之后会把A放到一级缓存并清空二三级缓存。
用2层缓存行不行?
光看循环依赖这个问题来说是可以实现的,但是放到整个springbean的生命周期流程来说是不行的,正常情况是先进行bean实例化、属性注入、初始化、生成代理。如果是二级缓存,意味着无论是否有循环依赖,代理类都必须在属性注入阶段提前生成代理,这是与bean的生命周期相违背的,因此采用三级缓存,先把实例化的bean封装到objectFactory里,如果有循环依赖且是代理类就不得不先生成代理类,如果没有循环依赖,就可以和原流程一致,在初始化后进行生成代理。

springioc的扩展点,实例化、初始化

spring bean生命周期扩展点很多,主要分几个阶段,
第一个就是bean工厂的后置处理器beanFactoryPostProcessor,这个主要是针对beanDefinition或者beanFactory的扩展点,可以在完成beanDefinition扫描后对holder中的beanDefition做一些操作,比如新增属性等,也可以在这个时机往里面新加一个beanDefinition,像mybatis里的mapper接口就是在这个时机完成扫描和注册的,mybatis通过import注解引入了一个MapperScanner解析类,实现了importBeanDefinitionRegistrar,从而完成扫描注册
再一个就是在bean实例化属性注入之后,在初始化的前后也有扩展点,实现了beanPostProcessor接口的逻辑会在这个时机执行。比如aop的主要入口就在初始化之后执行的。
其他还有一些扩展点,比如aware接口,可以帮你注入applicationContext、beanFactory等等,在beanPostProcess之前执行,所以我们实现了一个beanPostProcess的话可以再实现aware接口,从而拿到上下文
另外还有当bean实例化之后属性注入前,也有一个扩展点,可以在这里直接返回代理类,跳过后续流程等。

配置中心实现原理

配置中心实现原理
配置中心主要好处有2个,一个是可以统一管理我们的项目配置,第二个就是可以动态刷新配置。像我们现在用的是一个自研的配置中心,利用springboot的自动装配,在客户端会新建一个ApplicationListen,用来监听上下文环境准备好事件,然后在事件中用http长轮询的方式从服务端获取变化的配置,服务端会挂起这个请求,当一定时间范围内如果发生了配置变更,服务端就会返回这个请求,没有的话就会重新发起一个长轮训,客户端拿到这个结果后就可以更新本地缓存的配置了。
用过nacos吗
nacos的客户端和服务端交互机制也差不多,不过nacos客户端拿到的是变化的key,然后会发布一个刷新配置的事件,借助springCloud的@RefreshScope,会去刷新环境配置,然后再删除所有refresh作用域的目标bean,对应的代理bean还是不会动,当我们再去获取某一个配置项时,就会重新实例化这个配置项的目标bean,从环境配置中拿到最新值。完成配置的动态更新

mysql索引失效

索引失效的场景还挺多,比如用了not in,is null这种,还有比如用了like左模糊,还有条件里索引字段用了函数或者计算,或者字段类型不匹配,比如字符类型传了数值类型,再或者用了or但是有个字段没有索引会导致另一个索引也失效,还有用了组合索引时,没有使用索引里左侧字段也会导致索引失效,因为组合索引是吧这几个字段的值拼在一起排序的,如果不用左边的那也就没有索引效果了。
如何知道一个索引有没有生效?
一般可以通过explain命令可以解析下sql语句,看他有没有走索引,看key字段就知道走了哪些索引,一般来说还要关注type字段,他表示走的索引类型,通常要达到ref或者eq_ref级别就是比较好的,比如用了范围查询大部分时候就是range类型,再比如index一般统计类的sql出现的比较多,表示扫描了索引树,如果extra里面有use index,说明走了覆盖索引。最后就是all了,这个就是全表扫描了,性能是最低的,一般都是要进行sql优化的。

mysql#$区别,一对多映射

$表示字符串拼接,如果我们sql中用了$符号,则可以传任意内容,比如传表名甚至一段sql,所以这种虽然便利但是很容易导致sql注入。#表示预处理中的占位符,只能替换sql中的value,一般场景我们用#就够了,但比如像我们在本地用了分库分表,可能还是要用到$符号来完成切表。
一对多映射
一般使用collection标签来标记关联查询,我们生产上未使用

线程池主要参数,拒绝策略

线程池主要参数有核心线程数,最大线程数,最大线程数超时时间、时间单位、队列、拒绝策略,工厂对象,一般我们都会用工厂来给不同的业务设定不同的线程池名称方便排查问题,我们生产上把一些性能敏感的场景都单独用线程池来并行获取页面不同数据源,用来降低耗时,像核心线程数和最大线程数都是根据生产的qps来估算的,大致为QPS*平均耗时即为线程数,队列设置的SynchronousQueue,相当于0容量,保证接口低延迟,拒绝策略是线程满了主线程执行保证请求成功率,像这些配置都是放在配置中心的,可以动态调整,除了队列类型

kafka速度快的机制

kafka是一个高性能的MQ,一般生产上用它来做日志聚合、埋点数据上报等操作。他之所以速度快主要分几个原因,第一个是从他的架构设计来看,他是一个支持水平扩展的分布式系统,一个topic可以被拆成多个partition,也就意味着可以同时往这些分区里面写,而且partition又被拆成了多个logSegment,这样通过offset来查找消息时可以更快定位。第二个原因是IO这块,他利用了操作系统的文件系统缓存机制,可以提前把数据缓存到内存,减少io次数,以及还有用了零拷贝,也是减少了线程切换和数据拷贝时间。第三个原因就是在消费这块支持消费组,一个消费组的consumer可以并行消费一个topic,提高吞吐量,当然一个partition最多一个consumer消费

mq消息堆积处理

消息堆积一般要进行事前监控,以及从消费能力、消息过期等前期设计就要考虑到,从而减小消息堆积发生的概率。当然再设计
仍然可能出现堆积情况,一般来说如果线上已经发生堆积,首先要看下堆积的原因,如果是因为突发流量或者业务量正常上涨导致的堆积,这种情况可以增加消费者加快消费速度,如果是因为消费速度变慢就需要排查代码问题了。
代码问题解决,mq堆积了很多消息,怎么不影响最新的业务消息?
这个开始有提过,最好预先设计消息过期时间,这样老消息可以转到死信队列再做补偿处理,就不会影响新消息。或者也可以临时弄个消费者把这些积压的消息写到新队列后续慢慢处理,也不会影响到最新的消息

高性能高并发实现

由于这个业务主要是分发,从整体架构来说,用了cdn加速获取缓存资源,网关这边用了多个ng集群来确保高性能和高可用。
具体到业务主要用了场景线程池隔离,下游并发调用、多级缓存caffein\redis、mq异步解耦、接口熔断限流、用的CMSGC降低单次停顿时间、异步日志埋点等

一个表id,姓名,更新时间等,查出每个姓名最新的一条数据

这里需要用到关联查询,可以先通过对姓名分组对更新时间使用聚合函数max,可以得到每个姓名的最后更新时间,然后再写一个内联,通过姓名和更新时间即可查询出每个姓名的最新一条数据
SELECT t1.* FROM t1
INNER JOIN (
SELECT max(id),userName
FROM user
GROUP BY userName
) t2
ON t1.userName= t2.userName AND t1.id= t2.id;

2个list嵌套循环,小的放里面还是大的放里面

一般来说大的放里面会好一点,这样意味着内层循环次数更少,循环切换次数更少。

posted on 2024-05-13 19:51  殁舞  阅读(1)  评论(0编辑  收藏  举报