目录
104.怎么保证缓存和数据库数据的一致性?
105.什么是缓存穿透,什么是缓存雪崩?怎么解决?
106.如何对数据库进行优化?
107.使用索引时有哪些原则?
108.存储过程如何进行优化?
109.说说如何对Tomcat进行优化?
110.BIO、NIO和AIO的区别?
111.Netty的特点?
112.Netty的线程模型?
113.如何进行JVM性能调优?
115.GC调优策略有哪些?
116.介绍下单点登录?
117.RabbitMQ的使用场景有哪些?
118.RabbitMQ有哪些重要的角色?有哪些重要的组件?
119.RabbitMQ中vhost的作用是什么?
120.介绍下RabbitMQ的架构?
121.RabbitMQ中的交换机类型有哪些?
122.除了ReetrantLock,你还接触过JUC并发包中的哪些并发API?
123.你了解哪些负载均衡算法、策略?
124.如何设计符合幂等性的高质量Restful API ?
125.如何理解Restful API 的幂等性?
126.jsp和servlet有什么区别?
127.forward和redirect的区别?
128.jsp有哪几个作用域?
1.淘汰缓存在;如果是较为复杂的数据时,进行缓存的更新操作就会变得异常复杂,因此一般推荐选择淘汰缓存,而不是更新缓存。
2.选择先淘汰缓存,再更新数据库,加入先更新数据库在淘汰缓存,如果淘汰缓存失败,那么后面的请求就会得到脏数据,直至缓存过期。加入先淘汰缓存再更新数据库,如果更新数据库失败,只会产生一次缓存穿透,相比较而言,后者对业务则没有本质上的影响。
3.延时双删除策略,如下场景:同时有一个请求A进行更新操作,另一个请求B进行查询操作。我们按照如下步骤执行:
(1)请求A进行写操作,删除缓存
(2)请求B查询发现缓存不存在
(3)请求B去数据库查询得到旧值
(4)请求B将旧值写入缓存
(5)请求A将新值写入数据库,次数便出现了数据不一致问题,此时我们可以采用延时双删策略的已解决。
public void write(String key,Object data){
redisUtils.del(key);
db.update(data);
Thread.Sleep(100);
redisUtils.del(key);
}
这么做,可以将1秒内所造成的缓存脏数据,再次删除。这个时间设定可以根据业务场景进行一个调节。
4.数据库读写分离的场景
假如有如下场景:两个请求,一个请求A进行更新操作,另一个请求B进行查询操作。我们按照如下步骤执行:
1.请求A进行写操作,删除缓存
2.请求A将数据写入数据库了
3.请求B查询缓存发现,缓存没有值
4.请求B去从库查询,这时,还没有完成主从同步,因此查询到的是旧值。
5.请求B将旧值写入缓存
6.数据库完成主从同步,从库变为新值,依旧采用延时双删策略解决此问题。
1.缓存穿透:一般的缓存系统,都是按照key去缓存查询,如果不存在对用的value,就应该去后端系统去查找(比如DB数据库)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。
2.怎么解决?
对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert之后清理缓存。对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的bitmap中,查询时通过该bitmap过滤。
3.缓存雪崩:当缓存服务器重启或者大量缓存集中在某一时间段失效,这样在失效的时候,会给后端系统带来大量的压力,导致系统崩溃。
4.如何解决?在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待;做二级缓存;不同的key,设置不同的过期时间,让缓存失效的时间尽量均匀。
1.选取适合的字段属性
为了获取更好的性能,可以将表中字段的宽度设的尽可能小。
尽量把字段设置成not null
执行查询的时候,数据库不用去比较null值
对某些省份或者性别字段,将他们定义为enum类型
enum类型被当做数值型数据来处理,而数值型数据被处理起来的速度要比文本类型快很多。
2.使用join链接代替子查询
3.使用联合union来代替手动创建的临时表union用法中,两个select语句的字段类型要匹配,而且字段个数要相同。
5.锁定表:尽管事务时维护数据库完整性的一个非常好的方法,但却因为他的独占性,有时候会影响数据库的性能,尤其是在大应用中。由于在事务执行的过程中,数据库会被锁定,因此其他用户只能暂时等待直到数据库结束。有的时候可以用锁定表的方法获得更好的性能。
共享锁:
其他用户只能看,不能修改lock table person in share mode;对于通过lock table命令主动添加的锁来说,如果要释放他们,只需发出rollback命令即可。
6.使用外键:锁定表的方法可以维护数据的完整性,但是他却不能保证数据的关联性,这个时候可以使用外键。
7.使用索引:索引时提高数据库查询速度的常用方法,尤其是查询语句中包含max(),min(),order by这些命令的时候,性能提高更为显著。一般来说索引应该建在常用于join,where,order by的字段上。尽量不要对数据库中含有大量重复的值得字段建立索引。
8.优化的查询语句:在索引的字段上尽量不要使用函数进行操作。尽量不要使用like关键字和通配符,这样做法很简单,但却是以牺牲性能为代价的。避免在查询中进行自动类型转换,因为类型转换也会使索引失效。
常见的索引原则有:
1.选择唯一性索引
唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。
2.为经常需要排序,分组和联合操作的字段建立索引;
3.为常作为查询条件的字段建立索引。
4.限制索引的数据:越多的索引,会使更新表变得很浪费时间。
5.尽量使用数据量少的索引。
6.如果索引的值很长,那么查询的速度会受到影响。
7.尽量使用前缀来索引。
8.如果索引字段的值很长,最好使用值的前缀来索引。
9.删除不再使用或者很少使用的索引。
10.最左前缀匹配原则,非常重要的原则。
11.尽量选择区分度高的列作为索引。
12.索引列不能参与计算,保持列干净:但函数的查询不参与索引。
13.尽量的扩展索引,不要新建索引。
存储过程是一组为了完成特定功能的SQL语句集,存储在数据库中,经过一次编译后再次调用不需要再次编译,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行他。存储过程是数据库中的一个重要对象。
存储过程优化思路:
1.尽量利用一些SQL语句来替代一些小循环,例如聚合函数,求平均函数等。
2.中间结果存放于临时表,加索引。
3.少使用游标。SQL是个集合语言,对于集合运算具有较高性能。而cursors是过程运算。比如一个100万行的数据进行查询。游标需要读取100万次,而不使用游标只需要少量几次读取。
4.事务越短越好。SqlServer支持并发操作。如果事务过多过长,或者隔离级别过高,都会造成并发操作的阻塞,死锁。导致查询极慢,CPU占用率极低。
5.使用try catch处理错误异常
6.查找语句尽量不要放在循环内。
Tomcat作为Web服务器,他的处理性能直接关系到用户体验,下面是几种常见的优化措施。
1.去掉对web.xml的监视,把jsp提前编辑成Servlet。有抚育物理内存的情况,加大Tomcat使用的JVM的内存。
2.服务器资源
服务器所能提供CPU,内存,硬盘的性能对处理能力有决定性影响。
2.1对于高并发情况下会有大量的运算,那么CPU的速度会直接影响到处理速度。
2.3硬盘主要问题就是读写性能,当大量文件进行读写时,硬盘极容易称为性能瓶颈。最好的办法还是利用下面提到的缓存。
3.利用缓存和压缩
对于静态页面最好是能够缓存起来,这样就不必每次从磁盘上读。这里我们采用了Nginx作为缓存服务器,将图片,css,js文件都进行了缓存,有效的减少了后端Tomcat的访问。
另外,为了能加快网络传输速度,开启gzip压缩也是必不可少的。但考虑带Tomcat已经需要处理很多东西了,所以把这个压缩的工作就交给前端的Nginx来完成。
除了文本可以用gzip压缩,其实很多图片也可以用图像处理工具预先进项压缩,找到一个平衡点可以让画质损失很小而文件可以减小很多。曾静我就见过一个图片从300多kb压缩到几十kb,自己几乎看不出来区别。
4.采用集群
单个服务器性能总是有限的,最好的办法自然是实现横向扩展,那么组建Tomcat集群是有效提升性能的手段。我们还是采用了Nginx来作为请求分流的服务器,后端多个Tomcat共享session来协同工作。
5.优化Tomcat参数
这里以Tomcat7的参数配置为例,需要修改conf/server.xml文件,主要是优化连接配置,关闭客户端dnd查询。
1.基本概念
BIO:一个连接一个线程,客户端有连接请求时服务器端就需要启动一个线程进行处理。线程开销大。
伪异步IO:将请求连接放入线程池,一对多,但线程还是很宝贵的资源。
NIO:一个请求一个线程,但客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程处理。
AIO:一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
2.常规区别
BIO是面向流的,NIO是面向缓冲区的;
BIO的各种流是阻塞的。
而NIO是非阻塞的。
BIO的Stream是单向的,而NIO的channel是双向的。NIO特点:事件驱动模型,单线程处理多任务,非阻塞I/O,I/O读写不再阻塞,而是返回0,基于block的传输比基于流的传输更高效,更高级的IO函数zero copy,IO多路复用大大提高了Java网络应用的可伸缩性和实用性。
基于Reactor线程模型。在Reactor模式中,事件分发器等待某个事件或者可应用或者某个操作的状态发生,时间分发器就把这个事件事先注册的事件处理函数或者回调函数,由后者来做实际的读写操作。
如在Reactor中实现读:注册读就绪事件和相应的事件处理器,事件分发器等待事件,事件到来,激活分发器,分发器调用事件对应的处理器,事件处理器完成实际的读操作,处理读到的数据,注册新的事件,然后返还控制权。
一个高性能,异步事件驱动的NIO框架,他提供了对TCP,UDP和文件传输的支持使用更高效的socket底层,对epoll空轮询引起的CPU占用飙升在内部进行了处理,避免了直接使用NIO的陷阱,简化了NIO的处理方式。
采用多种decoder/encoder支持,对TCP粘包/分包进行自动化处理,可使用接受/处理线程池,提高连接效率,对重连,心跳检测的简单支持。
可以配置IO线程数,TCP参数,TCP接收和发送缓冲区使用直接内存代替堆内存,通过内存池的方式循环利用ByteBuf通过引用计数器及时申请释放不再引用对象,降低了GC频率使用单线程串行化的方式,高效的Reactor线程模型大量使用了volitale,使用了CAS和原子类,线程安全类的使用,读写锁的使用。
Netty通过Reacor模型基于多路复用器接收并处理用户请求,内部实现了了两个线程池,boss线程池和work线程池。其中boss线程池的线程负责处理请求的accept,当接收到accept事件的请求时,把对应的socket封装到一个NIOSocketChannel中,并交给work线程池,其中work线程池负责请求的read和write事件,由对应的Handler处理。
单线程模型:所有I/O操作都有一个线程完成,即多路复用,事件分发和处理都是在一个Reactor线程上完成的。既要接受客户端的连接请求,向服务器端发起连接,又要发送/读取请求或者应答/响应消息。
一个NIO线程同时处理成百上千的链路,性能上无法支撑,速度慢,若线程进入死循环,整个程序不可用,对于高负载,大并发的应用场景不合适。
多线程模型:有一个NIO线程(Acceptor只负责监听服务器,接受客户端的TCP连接请求,NIO线程池负责网络IO的操作,即消息的读取,解码,编码和发送。
1个NIO线程可以同时处理N条链路,但是一个链路只对应1个NIO线程,这是为了防止发生并发操作问题。但在并发百万客户端连接或者需要安全认证时,一个Acceptor线程可能会存在性能不足问题。
主从多线程模型:Acceptor线程用于绑定监听端口,接收客户端连接,将SocketChannel从主线程池的Reactor线程的多路复用器上移除,用于处理I/O的读写等操作,从而保证mainReactor只负责接入认证,握手等操作。
1.监控GC的状态,使用各种JVM工具,查看当前日志,分析当前JVM参数设置,并且分析当前堆内存快照和gc日志,根据实际的各区域内存划分GC执行时间,觉得是否进行优化。
举一个例子:系统崩溃前的一些现象:
每次垃圾回收的时间越来越长,由之前的10ms延长到50ms左右,FullGC的时间也有之前的0.5s延长到4,5s
FullGC的次数越来越多,最频繁时隔不到1分钟就进行一次FullGC
年老代的内存越来越大并且每次FullGC后年老代没有内存被释放之后系统就会无法响应新的请求,逐渐到达OutOfMemoryError的临界值,这个时候就需要分析JVM内存快照dump。
2.生成堆的dump文件。
通过JMX的MBean生成当前的Heap信息,大小为一个3G(整个堆的大小)的hprof文件,如果没有启动JMX可以通过Java的jmap命令来生成该文件。
3.分析dump文件打开这个3g的堆信息文件,显然一般的Window系统没有那么大内存,必须借助高配置的Linux,几种工具打开该文件:
Visual VM
IBM HeapAnalyzer
JDK自带的Hprof工具
Mat(Eclipse专门的静态内存分析工具)推荐使用
备注:文件太大,建议使用Eclipse专门的静态内存分析工具Mat打开分析。
4.分析结果,判断是需要优化,如果各项参数设置合理,系统没有超时日志出现,GC频率不高,GC耗时不高,那么没有必要进行GC优化,如果GC事件超过1到3秒,或者频繁GC,则必须优化。
注:如果满足下面的指标,则一般不需要进行GC:
Minor GC执行时间不到50毫秒;
Minor GC执行不频繁,约10秒一次;
FullGC执行时间不到1秒;
FullGC执行频率不算频繁,不低于10分钟一次;
5.调整GC类型和内存分配,如果内存分配过大或者过下,或者采用的GC收集器比较慢,则应该优先调整这些参数,并且先找一台或者几台机器进行beat,然后比较优化过的机器和没有优化的机器性能对比,并有针对性的做出最后选择。
6.不读拿的分析和调整,通过不断地试验和试错,分析并找到最合适的参数,如果找到了最合适的参数,则将这些参数应用到所有的服务器。
114.JVM调优参数有哪些?
1.解决一步问题,例如用户注册,发送邮件和短信反馈注册成功,可以使用RabbitMQ消息队列,用户无需等待反馈。
2.服务间解耦,订单系统和库存系统,中间加入RabbitMQ消息队列,当库存系统出现问题时,订单系统依旧能正常使用,降低服务间耦合度。
3.秒杀系统,利用RabbitMQ的最大值,实现秒杀系统。
1.RabbitMQ有哪些重要角色?客户端,RabbitMQ服务端。
2.有哪些重要组件?
2.1connectionFactory(连接管理器)应用程序与RabbitMQ之间建立连接的管理器。
2.2Channel(信道)消息推送使用的信道。
2.3RoutingKey(路由键)用于把生产者的数据分配到交换机上。
2.4Exchange(绑定键)用于把交换机的消息绑定到队列上
2.6Queue(队列)用于存储生产者消息。
vhost可以理解为mini版的RabbitMQ,其内容均含有独立的交换机,绑定,队列,最重要的是拥有独立的权限系统,可以做到vhost范围内的用户控制。
用RabbitMQ全局考虑,不同的应用可以跑在不同的vhost上,作为不同权限隔离的手段。