1、简单说一下MVCC
MVCC是一种多版本并发控制机制。
MVCC是为了解决什么问题?
- 大多数的MYSQL事务型存储引擎,如,InnoDB等都不使用一种简单的行锁机制.事实上,他们都和MVCC–多版本并发控制来一起使用.
- 锁机制可以控制并发操作,但是其系统开销较大,而MVCC可以在大多数情况下代替行级锁,使用MVCC,能降低其系统开销.
MVCC实现:MVCC是通过保存数据在某个时间点的快照来实现的.不同存储引擎的MVCC实现是不同的,典型的有乐观并发控制和悲观并发控制.
在每一行数据中额外保存两个隐藏的列:当前行创建时的版本号和删除时的版本号(可能为空,其实还有一列称为回滚指针,用于事务回滚,不在本文范畴)。这里的版本号并不是实际的时间值,而是系统版本号。每开始新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询每行记录的版本号进行比较。
每个事务又有自己的版本号,这样事务内执行CRUD操作时,就通过版本号的比较来达到数据版本控制的目的。
查询操作:
从上面的描述可以看到,在查询时要符合以下两个条件的记录才能被事务查询出来:
1) 删除版本号未指定或者大于当前事务版本号,即查询事务开启后确保读取的行未被删除。(即上述事务id为2的事务查询时,依然能读取到事务id为3所删除的数据行)
2) 创建版本号 小于或者等于 当前事务版本号 ,就是说记录创建是在当前事务中(等于的情况)或者在当前事务启动之前的其他事物进行的insert。
- MVCC手段只适用于Msyql隔离级别中的读已提交(Read committed)和可重复读(Repeatable Read).
- Read uncimmitted由于存在脏读,即能读到未提交事务的数据行,所以不适用MVCC.
- 原因是MVCC的创建版本和删除版本只要在事务提交后才会产生。
- 串行化由于是会对所涉及到的表加锁,并非行锁,自然也就不存在行的版本控制问题。
通过以上总结,可知,MVCC主要作用于事务性的,有行锁控制的数据库模型。
2、线上的mysql故障的问题排查过程和原因和事故后处理全过程。
主要是因为sql写的不行,行锁占用的多,在加上运维人员失误,线上直接DDL加索引,造成MDL读写锁冲突,并且把后面的读请求全部卡住,客户端超时重试之后,mysql的线程池就爆了。这就是整个故障
3、设计一个秒杀活动,上亿并发抢5个手机,怎么保证服务的高可用,并且商品不超卖。
对于一个秒杀系统来说,瞬时的大量请求会对后台服务造成冲击,需要保证服务的可用性以及业务的正确性。
设计了一个高并发高可用的系统简要流程架构如下图:
- 将商品(或券)的信息等静态数据放到cdn节点,实现动静分离
- 业务请求和业务处理之间使用MQ对请求进行削峰
- 读写分离:对于逻辑复杂(用户验证,风控管理,行为分析)的系统,可以将读写部署两套服务进行分离
- 使用缓存:
- 像库存这种信息无法放到静态页面,为了应对大并发读问题,可以在服务端做本地缓存用于读取,一般缓存时间设置为数秒,缓存失效时(最好在失效前一两秒)重新拉取redis中的库存信息。防止超买问题由扣减行为保证,因此即使和缓存不一致,读本地缓存也不会导致超卖,读场景一般允许脏数据情况。
- 高并发时,大量的mysql操作会有很大性能问题,因此使用redis集群作为缓存进行读取。
- 防止缓存雪崩:
- 减少缓存渗透:可以在服务中进行布隆过滤,可以将不存在的商品或券访问在服务端过滤掉;或者对缓存中没有的数据key置为null并设置较短的过期时间,优点是简单易操作,缺点要增加大量的null key,增大了与mysql的不一致性,需要消息机制和mysql保持同步
- 限流:对key的操作加锁排队(利用setnx进行分布式锁),即同时只允许一个线程访问。
- 防止缓存同时失效:设置key的过期时间时,采用固定时间+随机时间的方法,可以有效防止缓存同时过期引起雪崩的问题
- 增加二级缓存: A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期。
- 合理设置redis中的key,使其均匀的分布到各个节点,防止大量请求访问到少量节点的热点问题,而且可以减少单个节点宕机造成的影响。
- 由于商品或券具有独立性, 每个商品或券分配一个独有id,可以根据id范围划分到各个服务集群中,服务在访问对应范围的的redis缓存,这样可以大大增加访问的并发性。实现方式一个是在web服务中将id路由到对应的消息队列topic中,另一个是在消息队列后部署消息路由集群,相对来说方案一更好,因为方案而增加了链路长度和故障点。
- 在上一步的基础上,为了进一步提升性能防止少量热点数据影响全量数据的操作性能,可在服务中统计热点数据(比如LRU),并将热点数据独立存放到一个集群中去,这样可以将热点数据和其它数据隔离开来。当然这样会增加热点数据迁移的工作,增加了系统的复杂性。
4、设计模式、重构的区别与联系。
设计模式、重构是从不同的出发点,用了一些相近的思想和原则来指导我们如何写出更合适的代码。
*设计模式更多的是从设计的角度出发,来指导的。是从上到下
过程 是【问题-- 分析、设计(key )-- 最优答案】
*重构更多的是从代码角度,从优化角度,来指导的。是从下到上
过程 是【问题-- 一般答案-- 优化(key )-- 最优答案】
- 设计模式强调 分析、设计,强调思考、思想
- 重构强调 先动手,有错就改
尽量少用设计模式,一定要多重构
设计模式会让开发进度漫长,很容易过度设计,可能会让代码变的复杂,易读性降低。另外,重构后的代码就会慢慢趋向模式,因为这两种手段的最终最优结果是一样的
5、Servlet的生命周期,何时创建,整个调用过程。
主要分为以下几个阶段:
加载类—>实例化(为对象分配空间)—>初始化(为对象的属性赋值)—>请求处理(服务阶段)—>销毁
Servlet实例为一个单例对象,从头到尾只有一个。创建在于第一次被调用(第一次处理请求),这是一种默认行为。
可以通过修改web.xml这个配置文件:
<servlet>
<load-on-startup>1</load-on-startup>
</servlet>
通过增加<load-on-startup>1</load-on-startup>属性可以将servlet对象的创建修改为:服务器启动时就创建该对象。
调用过程
- 浏览器向服务器发出GET请求
- 服务器上的容器逻辑接收到该url,根据该url判断为Servlet请求,此时容器逻辑将产生两个对象:请求对象(HttpServletRequest)和响应对象(HttpServletResponce)
- 容器逻辑根据url找到目标Servlet, 且创建一个线程A
- 容器逻辑将刚才创建的请求对象和响应对象传递给线程A
- 容器逻辑调用Servlet的service()方法
- service()方法根据请求类型调用doGet()/doPost()方法
- doGet()执行完后,将结果返回给容器逻辑
- 线程A被销毁或被放在线程池中
注意:
- 在容器中的每个Servlet原则上只有一个实例
- 每个请求对应一个线程
- 多个线程可作用于同一个Servlet(这是造成Servlet线程不安全的根本原因)
- 每个线程一旦执行完任务,就被销毁或放在线程池中等待回收
6、Servlet,Filter,Listener,拦截器的区别。
- servlet:servlet是一种运行服务器端的java应用程序,具有独立于平台和协议的特性,并且可以动态的生成web页面,它工作在客户端请求与服务器响应的中间层。
- filter:filter是一个可以复用的代码片段,可以用来转换HTTP请求、响应和头信息。Filter不像Servlet,它不能产生一个请求或者响应,它只是修改对某一资源的请求,或者修改从某一的响应。
- listener:监听器,从字面上可以看出listener主要用来监听只用。通过listener可以监听web服务器中某一个执行动作,并根据其要求作出相应的响应。通俗的语言说就是在application,session,request三个对象创建消亡或者往其中添加修改删除属性时自动执行代码的功能组件。
- interceptor:是在面向切面编程的,就是在你的service或者一个方法,前调用一个方法,或者在方法后调用一个方法,比如动态代理就是拦截器的简单实现,在你调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在你调用方法后打印出字符串,甚至在你抛出异常的时候做业务逻辑的操作。
- servlet、filter、listener是配置到web.xml中,interceptor不配置到web.xml中,struts的拦截器配置到struts.xml中。spring的拦截器配置到spring.xml中。
*servlet 流程是短的,url传来之后,就对其进行处理,之后返回或转向到某一自己指定的页面。它主要用来在 业务处理之前进行控制.
*filter 流程是线性的, url传来之后,检查之后,可保持原来的流程继续向下执行,被下一个filter, servlet接收等,而servlet 处理之后,不会继续向下传递。filter功能可用来保持流程继续按照原来的方式进行下去,或者主导流程,而servlet的功能主要用来主导流程。filter可用来进行字符编码的过滤,检测用户是否登陆的过滤,禁止页面缓存等
*servlet,filter都是针对url之类的,而listener是针对对象的操作的,如session的创建,session.setAttribute的发生,在这样的事件发生时做一些事情。
*interceptor 拦截器,类似于filter,不过在struts.xml中配置,不是在web.xml,并且不是针对URL的,而是针对action,当页面提交action时,进行过滤操作,
与filter不同点:(1)不在web.xml中配置,而是在struts.xml中完成配置,与action在一起
( 2 ) 可由action自己指定用哪个interceptor 来在接收之前做事