文章目录
题目来源
链接:https://www.nowcoder.com/discuss/589908
来源:牛客网
1. 出于什么样的考虑,对系统进行了重构
1、业务需求。业务拓展,扩容。如系统微服务改造
2、技术转型升级:旧系统扩容,整体扩容,资源浪费。拆分微服务,精准扩容,也便于后续云部署等。前后端分离 ->
3、管理要求:原系统旧架构,代码权限svn不利于细分,开发会拿到较多不必要代码权限。
参考 为什么要抛离jsp
2. 有了解过领域模型相关知识吗
领域模型是一种分析模型,用于分析理解复杂业务领域问题,
具体到软件开发过程中就是在分析阶段分析如何满足系统功能性需求。
3. 项目中最大的亮点,或者说项目中最大的难题是怎么解决的
1、线上系统句柄打开过多排查分析
场景:原旧系统weblogic 每天凌晨定时重启,新系统没有定时重启 ->too many open files
思路:①查限制
ulimit -a
lsof -p 进程id | wc -l
strace -p
cat /proc/pid/limits
原因:Ehcache硬盘的存储,文件读写限制->运维提升机器性能
2、Oracle 数据行转列查询+多次递归查询并合并查询结果
①行转列-----4-6参数确定一结果
②递归-------根据①结果进行条件递归有序查询
③返回②有序查找命中结果
实现方式:缓存+存储过程+事务临时表
操作时将行转列结果 按照 key:map String 存储,并保存hashCode
存储过程接受查询参数并生成递归查询数据,插入临时表中,临时表与缓存表做关联查询
3、微服务改造不影响业务情况,进行数据验证校对
难点:验证方法设计。
场景:新旧系统,esb+dubbo
方案:验证应用+业务表数据对比,异常数据插入数据库后续分析。
4. 索引失效场景有哪些
1、or情况下 如果部分列是组合索引,不会走索引 [https://www.cnblogs.com/yuerdongni/p/4255395.html]
2、对于复合索引,如果不使用前列,后续列也将无法使用
3、like查询是以%开头
4、需要类型转换;
5、where中索引列有运算;
6、where中索引列使用了函数;
7、如果mysql觉得全表扫描更快时(数据少);
5. dubbo服务暴露过程
6. Dubbo的spi和jdk的什么区别
1、加载机制不同:jdk配置全量加载,dubbo 键值对加载,灵活配置
2、dubbo增加拓展节点IOC,AOP支持拓展节点可以直接setter注入其他节点
答题角度:
缓存:dubbo缓存拓展节点getExtensionLoader =>getExtension=>holder=[缓存]=>双重锁检测[避免重排序
注入:dubbo@SPI
1、JDK的spi要用for循环,然后if判断才能获取到指定的spi对象,dubbo用指定的key就可以获取
//返回指定名字的扩展
public T getExtension(String name){}
2、JDK的spi不支持默认值,dubbo增加了默认值的设计
增加功能:spi增加了IoC、AOP
装饰者设计模式->静态代理->JDK、cglib、Javassist优缺点对比->AOP源码
//@SPI("javassist")代表默认的spi对象,比如Compiler默认使用的是javassist,可通过
ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
compiler = loader.getDefaultExtension();
//方式获取实现类,根据配置,即为
//com.alibaba.dubbo.common.compiler.support.JavassistCompiler
7. Rabbitmq和kafka区别
待补充
1、kafka吞吐量高:zero copy 内核->网络设备
2、rabbitMq可靠性好:确认机制,支持事务,委托
3、高可用:r:主从模式 k:同步/异步同步消息
4、负载均衡:k zk+分区机制 r:需要额外
5、模型:r: AMOP k 未遵循AMQP
8. Kafka适合什么场景,项目中是什么场景
日志收集:logstash+kafka
消息系统:解耦生产者和消费者、缓存消息
用户活动跟踪:kafka经常被用来记录web用户或者app用户的各种活动,
如浏览网页、搜索、点击等活动,这些活动信息被各个服务器发布到kafka的topic中,
然后消费者通过订阅这些topic来做实时的监控分析,亦可保存到数据库;
运营指标:kafka也经常用来记录运营监控数据。
包括收集各种分布式应用的数据,
生产各种操作的集中反馈,比如报警和报告;
9. Rabbit如何保证消息不丢失
思路:丢失场景?
mq把消息丢失|消费时消息丢失
A:生产者丢失数据:生产者将数据发送到mq->网络等问题数据丢失
B:rabbitmq自己丢失数据:未开启持久化,rabbitmq重启数据丢失。
C:消费端丢失数据:消费者未来得及处理,消费者挂掉。重启后,rabbitMq认为数据已经消费
如何防止丢失?
------------------
生产者:
①rabbitmq事务机制: 缺点,事务开启 变为同步阻塞操作,性能下降。
channel.txSelect();//开启事物
try{
//发送消息
}catch(Exection e){
channel.txRollback();//回滚事物
//重新提交
}
②开启confir模式:生产者开启confirm模式,每次写的消息都会分配唯一id,写入mq,mq回传回一个ack.
否则回调nack接口,告之失败,进行重试。
//开启confirm
channel.confirm();
//发送成功回调
public void ack(String messageId){
}
// 发送失败回调
public void nack(String messageId){
//重发该消息
}
----------------------------
rabbitmq自己丢数据
设置消息持久化到磁盘:
①创建queue时候设置为持久化的。保证持久化queue元数据,但是不会持久化queue里边的数据
②发送小时deliveryMode设置2,消息设置成持久化
持久化与生产者的confir机制配合使用,消息持久化到了磁盘,才通知生产者ack。这样在持久化钱mq挂了,数据丢了,生产者收不到ack回调也会进行消息重发。
-----------------------------
消费者丢数据
使用mq提供的ack机制,关闭自动ack,每次确保处理完这个消息后,代码中手动ack。避免消息没处理完就ack.
10. Zk满足了CAP哪些特性 cp
zookeeper是一种提供强一致性的服务,在分区容错性和数据一致性上做了一定的折中。
11. 项目中缓存使用场景,双写一致性怎么保证的
https://www.cnblogs.com/rjzheng/p/9041659.html
12. 缓存穿透,击穿,雪崩场景,怎么解决
缓存穿透:key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源。
击穿:key对应的数据存在,但在redis中过期,
此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
雪崩:当缓存服务器重启或者大量缓存集中在某一个时间段失效,
这样在失效的时候,也会给后端系统(比如DB)带来很大压力。
处理方案:
穿透:
1、布隆过滤器
2、简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟
击穿:
“热点”的数据-互斥锁(mutex key) Redis的SETNX 「SET if Not eXists」
-----
public String get(key) {
String value = redis.get(key);
if (value == null) { //代表缓存值过期
//设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //代表设置成功
value = db.get(key);
redis.set(key, value, expire_secs);
redis.del(key_mutex);
} else { //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
sleep(50);
get(key); //重试
}
} else {
return value;
}
}
缓存雪崩解决方案
1、加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写
2、缓存失效时间分散开
-------
加锁排队,伪代码如下:
加锁排队的解决方式分布式环境的并发问题,有可能还要解决分布式锁的问题;线程还会被阻塞,用户体验很差!
因此,在真正的高并发场景下很少使用!
//伪代码
public object GetProductListNew() {
int cacheTime = 30;
String cacheKey = "product_list";
String lockKey = cacheKey;
String cacheValue = CacheHelper.get(cacheKey);
if (cacheValue != null) {
return cacheValue;
} else {
synchronized(lockKey) {
cacheValue = CacheHelper.get(cacheKey);
if (cacheValue != null) {
return cacheValue;
} else {
//这里一般是sql查询数据
cacheValue = GetProductListFromDB();
CacheHelper.Add(cacheKey, cacheValue, cacheTime);
}
}
return cacheValue;
}
}
=----
随机值伪代码:
//伪代码
public object GetProductListNew() {
int cacheTime = 30;
String cacheKey = "product_list";
//缓存标记
String cacheSign = cacheKey + "_sign";
String sign = CacheHelper.Get(cacheSign);
//获取缓存值
String cacheValue = CacheHelper.Get(cacheKey);
if (sign != null) {
return cacheValue; //未过期,直接返回
} else {
CacheHelper.Add(cacheSign, "1", cacheTime);
ThreadPool.QueueUserWorkItem((arg) -> {
//这里一般是 sql查询数据
cacheValue = GetProductListFromDB();
//日期设缓存时间的2倍,用于脏读
CacheHelper.Add(cacheKey, cacheValue, cacheTime * 2);
});
return cacheValue;
}
}
13. HashMap初始化10000,如何扩容
扩容流程
threshold=tabSizeFor(initialCapacity)
threshold =2^14 = 1024*16 = 16384
loadfactor=0.75
tblesize=2^14*0.75=12288
10000 不需要
1000
threshold=2*10 =1024
table=1024*0.75<1000
threshold*2 =2048
14. Spring循环依赖有几种,如何解决
1、构造器参数循环依赖
2、setter方式单例,默认方式
3、setter方式原型,prototype
对于“prototype”作用域Bean,Spring容器无法完成依赖注入,
因为“prototype”作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。
参考
Spring循环依赖的三种方式
https://blog.csdn.net/xiamiflying/article/details/90178211