1、在讲项目的过程中,聊到了接口这块,瞬间惨不忍睹,问我是否考虑到幂等性问题了,怎么处理的?WC要崩啊!
在实际的开发项目中,一个对外暴露的接口往往会面临,瞬间大量的重复的请求提交,如果想过滤掉重复请求造成对业务的伤害,那就需要实现幂等!
我们来解释一下幂等的概念:
任意多次执行所产生的影响均与一次执行的影响相同。按照这个含义,最终的含义就是对数据库的影响只能是一次性的,不能重复处理。
-
1、数据库建立唯一性索引,可以保证最终插入数据库的只有一条数据
-
2、token机制,每次接口请求前先获取一个token,然后再下次请求的时候在请求的header体中加上这个token,后台进行验证,如果验证通过删除token,下次请求再次判断token
-
3、悲观锁或者乐观锁,悲观锁可以保证每次for update的时候其他sql无法update数据(在数据库引擎是innodb的时候,select的条件必须是唯一索引,防止锁全表)
-
4、先查询后判断,首先通过查询数据库是否存在数据,如果存在证明已经请求过了,直接拒绝该请求,如果没有存在,就证明是第一次进来,直接放行。
Redis实现自动幂等的原理图:
大家可以根据自己项目的实际情况来。
2、因为我做的是个互联网项目所以自然而然的就聊到了项目的QPS多少,线程池参数多少,怎么配的?
每秒查询率QPS是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准,
在因特网上,作为域名系统服务器的机器的性能经常用每秒查询率来衡量。
原理:每天80%的访问集中在20%的时间里,这20%时间叫做峰值时间
公式:( 总PV数 * 80% ) / ( 每天秒数 * 20% ) = 峰值时间每秒请求数(QPS)
机器:峰值时间每秒QPS / 单台机器的QPS = 需要的机器
每天300w PV 的在单台机器上,这台机器需要多少QPS?
( 3000000 * 0.8 ) / (86400 * 0.2 ) = 139 (QPS)
如果一台机器的QPS是58,需要几台机器来支持?
139 / 58 = 3
有关线程池的参数:
corePoolSize(线程池的基本大小)
runnableTaskQueue(任务队列)
maximumPoolSize(线程池最大大小)
ThreadFactory
RejectedExecutionHandler(饱和策略
keepAliveTime(线程活动保持时间)
TimeUnit(线程活动保持时间的单位)
根据项目的访问量、并发量以及服务器的承载量等80%左右即可,过多也会消耗其性能。
3、项目服务和服务间调用的流程图怎么画的?
我们这边是用的Visio 画的,这样更直观。不过目前市场上也有很多不错的工具,可以根据自己的实际情况来做参考。
4、什么是双亲委派,如何破坏双亲委派?
对于任意一个类,都需要由加载它的类加载器和这个类本身来一同确立其在Java虚拟机中的唯一性。
为什么需要双亲委派?
双亲委派:如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;只有当父类加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载。
如果不是同一个类加载器加载,即时是相同的class文件,也会出现判断不想同的情况,从而引发一些意想不到的情况,为了保证相同的class文件,在使用的时候,是相同的对象,jvm设计的时候,采用了双亲委派的方式来加载类。
这里有几个流程要注意一下:
-
子类先委托父类加载
-
父类加载器有自己的加载范围,范围内没有找到,则不加载,并返回给子类
-
子类在收到父类无法加载的时候,才会自己去加载
jvm提供了三种系统加载器:
-
启动类加载器(Bootstrap ClassLoader):C++实现,在java里无法获取,负责加载<JAVA_HOME>/lib下的类。
-
扩展类加载器(Extension ClassLoader):Java实现,可以在java里获取,负责加载<JAVA_HOME>/lib/ext下的类。
-
系统类加载器/应用程序类加载器(Application ClassLoader):是与我们接触对多的类加载器,我们写的代码默认就是由它来加载,ClassLoader.getSystemClassLoader返回的就是它。
附上三者的关系:
为什么需要破坏双亲委派?
因为在某些情况下父类加载器需要委托子类加载器去加载class文件。受到加载范围的限制,父类加载器无法加载到需要的文件,以Driver接口为例,由于Driver接口定义在jdk当中的,而其实现由各个数据库的服务商来提供,比如mysql的就写了MySQL Connector
,那么问题就来了,DriverManager(也由jdk提供)要加载各个实现了Driver接口的实现类,然后进行管理,但是DriverManager由启动类加载器加载,只能记载JAVA_HOME的lib下文件,而其实现是由服务商提供的,由系统类加载器加载,这个时候就需要启动类加载器来委托子类来加载Driver实现,从而破坏了双亲委派,这里仅仅是举了破坏双亲委派的其中一个情况。
如何实现?
5、volatile,为什么可以保证可见性,为什么不能保证原子性?
可见性与Java的内存模型有关,模型采用缓存与主存的方式对变量进行操作,也就是说,每个线程都有自己的缓存空间,对变量的操作都是在缓存中进行的,之后再将修改后的值返回到主存中,这就带来了问题,有可能一个线程在将共享变量修改后,还没有来的及将缓存中的变量返回给主存中,另外一个线程就对共享变量进行修改,那么这个线程拿到的值是主存中未被修改的值,这就是可见性的问题。
volatile只提供了保证访问该变量时,每次都是从内存中读取最新值,并不会使用寄存器缓存该值——每次都会从内存中读取。而对该变量的修改,volatile并不提供原子性的保证。
sun官方解释:如果一个变量加了volatile关键字,就会告诉编译器和JVM的内存模型:这个变量是对所有线程共享的、可见的,每次jvm都会读取最新写入的值并使其最新值在所有CPU可见。volatile似乎是有时候可以代替简单的锁,似乎加了volatile关键字就省掉了锁。但又说volatile不能保证原子性(java程序员很熟悉这句话:volatile仅仅用来保证该变量对所有线程的可见性,但不保证原子性)。
Java中只有对基本类型变量的赋值和读取是原子操作,如i = 1的赋值操作,但是像j = i或者i++这样的操作都不是原子操作,因为他们都进行了多次原子操作,比如先读取i的值,再将i的值赋值给j,两个原子操作加起来就不是原子操作了。
所以,如果一个变量被volatile修饰了,那么肯定可以保证每次读取这个变量值的时候得到的值是最新的,但是一旦需要对变量进行自增这样的非原子操作,就不会保证这个变量的原子性了。
其他面试内容:
1、Hashmap的底层实现,效率提升了多少(用时间复杂度表示)?
2、如何用两个栈实现一个队列,最大容量是多少?
3、讲一下OSI七层模型?
4、Redis各种数据类型,以及应用场景?
5、JVM内存模型了解多少?
6、linux指令你确定会吧?
7、IO模型(BIO,NIO,AIO),讲到IO多路复用的时候讲了epoll, poll, select的区别,各自的特点?
8、object的常见方法?
9、Hashmap put的方法?resize介绍下?有那些线程安全的hash?说下hashtable,说下connrntnHashmap?线程池说下比较核心的几个参数?线程池处理逻辑。
10、kafka的原理熟悉吗?
11、怎么做到线程去关闭另一个线程?
12、怎么定位gc问题?
13、堆溢出,和栈溢出?解释下堆和栈的区别?
14、服务器内存溢出?
15、平时自己会去学习什么新的知识?这样可以了解你的知识宽度?
16、Redis的过期策略?
17、Redis的淘汰策略?
18、缓存读写不一致有什么好的方案?
就这么多吧!好好努力,希望就在前方。另外,祝福我吧!在我快要绝望的时候突突的来了两个offer,这个心情咋说呢!