面试:第二章:各种框架和中间件以及缓存数据库
讲讲SpringMVC的工作原理和常用注解
1、用户向服务器发送请求,请求被SpringMVC的前端控制器DispatcherServlet截获。
2、DispatcherServlet对请求的URL(统一资源定位符)进行解析,得到URI(请求资源标识符),然后根据该URI,
通过配置或注解调用HandlerMapping找到Handler配置的所有相关的对象,包括Handler对象以及Handler对象对应的拦截器,
这些对象都会被封装到一个HandlerExecutionChain对象当中返回DispatcherServlet。
3、前端控制器根据获得的Handler,请求HandlerAdapter处理多种Handler,调用Handler实际处理请求的方法。
4、提取请求中的模型数据,开始执行Handler(Controller)
5、Handler执行完成后,向DispatcherServlet返回一个ModelAndView对象。
6、根据返回的ModelAndView对象,请求ViewResolver(视图解析器)将逻辑视图解析成真正的视图并返回view给前端控制器。
7、渲染视图将model数据转换为response响应
8、把响应结果给返回给客户端。
url和uri的区别?
URI包括URL和URN两个类别,个人的身份证号就是URN,个人的家庭地址就是URL,URN可以唯一标识一个人,
而URL可以告诉邮递员怎么把货送到你手里。
组件型注解:
@Component 在类定义之前添加@Component注解,他会被spring容器识别,并转为bean。
@Repository 对Dao实现类进行注解 (特殊的@Component)
@Service 用于对业务逻辑层进行注解, (特殊的@Component)
@Controller 用于控制层注解 , (特殊的@Component)
请求和参数型注解:
@RequestMapping:用于处理请求地址映射,可以作用于类和方法上。
@RequestParam:用于获取传入参数的值
@PathViriable:用于定义路径参数值
@ResponseBody:作用于方法上,可以将整个返回结果以某种格式返回,如json或xml格式。
@CookieValue:用于获取请求的Cookie值
讲讲Spring的IOC(DI)和AOP动态代理
传统的程序开发(不用IOC):举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有
长得漂亮身材又好的mm,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其
所好送其所要,然后嘿嘿……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。
有IOC:
IoC是如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚
介管理了很多男男女女的资料,我可以向婚介提出一个列表,告诉它我想找个什么样的女朋友,比如长得像李
嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,然后婚介就会按照我们的要求,
提供一个mm,我们只需要去和她谈恋爱、结婚就行了。
总结控制反转:所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后
spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的
创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个
具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。
理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”
●谁依赖于谁:当然是应用程序依赖于IoC容器;
●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
IoC和DI是什么关系呢?
DI(依赖注入)其实就是IOC的另外一种说法,其实它们是同一个概念的不同角度描述
AOP的各种实现
AOP就是面向切面编程,我们可以从以下几个层面来实现AOP
- 在编译期修改源代码
- 在运行期字节码加载前修改字节码
- 在运行期字节码加载后动态创建代理类的字节码
AOP各种实现机制的比较
以下是各种实现机制的比较:
类别 | 机制 | 原理 | 优点 | 缺点 |
---|---|---|---|---|
静态AOP | 静态织入 | 在编译期,切面直接以字节码的形式编译到目标字节码文件中 | 对系统无性能影响 | 灵活性不够 |
动态AOP | 动态代理 | 在运行期,目标类加载后,为接口动态生成代理类,将切面织入到代理类中 | 相对于静态AOP更加灵活 | 切入的关注点需要实现接口。 对系统有一点性能影响 |
动态字节码生成 | CGLIB | 在运行期,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中 | 没有接口也可以织入 | 扩展类的实例方法为final时,则无法进行织入 |
自定义类加载器 | 在运行期,目标加载前,将切面逻辑加到目标字节码里 | 可以对绝大部分类进行织入 | 代码中如果使用了其他类加载器,则这些类将不会被织入 | |
字节码转换 | 在运行期,所有类加载器加载字节码前进行拦截 | 可以对所有类进行织入 |
AOP里的公民
- Joinpoint:拦截点,如某个业务方法
- Pointcut:Joinpoint的表达式,表示拦截哪些方法。一个Pointcut对应多个Joinpoint
- Advice:要切入的逻辑
- Before Advice:在方法前切入
- After Advice:在方法后切入,抛出异常则不会切入
- After Returning Advice:在方法返回后切入,抛出异常则不会切入
- After Throwing Advice:在方法抛出异常时切入
- Around Advice:在方法执行前后切入,可以中断或忽略原有流程的执行
Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成
一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理
的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。如果
目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation
Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做
的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
AOP在事务管理方面,Spring使用AOP来完成声明式的事务管理有annotation和xml两种形式。开发中,方便代码
编写,很多时候都是在spring配置文件中配置事务管理器并开启事务控制注解。在业务类或业务类方法中添加
@Transactional实现事务控制。
讲讲MyBatis框架
(1)mybatis是一个基于java的持久层框架,它内部封装了jdbc,不需要花费精力去处理加载驱动、创建连接等的过程,
消除了JDBC大量冗余的代码。
(2)mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中
sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。
(3)MyBatis 支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设
置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJO映射成数据库中的记录。
(4)提供了很多第三方插件(分页插件 / 逆向工程);
(5)能够与Spring很好的集成;
(6)MyBatis相当灵活,SQL写在XML里,从程序代码中彻底分离,解除sql与程序代码的耦合,便于统一管理,支持编写动态SQL语句。
(7) 提供映射标签,支持对象与数据库的ORM字段关系映射。
(8)SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
讲讲SpringBoot的特点
Springboot用来简化spring应用的初始搭建以及开发过程 使用特定的方式来进行配置
(properties或yml文件)
可以创建独立的spring引用程序 main方法运行
Springboot嵌入的Tomcat 无需部署war文件
简化maven配置
讲讲线程的创建及实现线程几种方式之间的区别
1:继承Therad类,2:实现Runnable接口 3:实现Callable接口 4:使用线程池
继承Thread类,并重写里面的run方法
class A extends Thread{
public void run(){
for(int i=1;i<=100;i++){
System.out.println("-----------------"+i);
}
}
}
A a = new A();
a.start();
实现Runnable接口,并实现里面的run方法
class B implements Runnable{
public void run(){
for(int i=1;i<=100;i++){
System.out.println("-----------------"+i);
}
}
}
B b = new B();
Thread t = new Thread(b);
t.start();
实现Callable
class A implements Callable<String>{
public String call() throws Exception{
//...
}
}
FutureTask<String> ft = new FutureTask<>(new A());
new Thread(ft).start();
线程池
ExcutorService es = Executors.newFixedThreadPool(10);
es.submit(new Runnable(){//任务});
es.submit(new Runnable(){//任务});
...
es.shutdown();
实现Runnable和实现Callable的区别?
实现Callable接口,任务可以有返回值,Runnable没有。
实现Callable接口,可以指定泛型,Runnable没有。
实现Callable接口,可以在call方法中声明异常,Runnable没有。
Runnable和Thread二者的区别?
实现Runnable接口的方式,更适合处理有共享资源的情况。
实现Runnable接口的方式,避免了单继承的局限性。
Java自定义类加载器与双亲委派模型
启动类加载器(Bootstrap)C++
扩展类加载器(Extension)Java
应用程序类加载器(AppClassLoader)Java
双亲委派模型工作原理:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请
求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即
ClassNotFoundException),子加载器才会尝试自己去加载。
讲讲jvm的组成与调优,内存模型,GC,tomcat调优
tomcat调优:
增加JVM堆内存大小
修复JRE内存泄漏
线程池设置
压缩
数据库性能调优
Tomcat本地库
JVM调优:
-Xms – 指定初始化时化的堆内存,默认为物理内存的1/64
-Xmx – 指定最大的内存,默认为物理内存的1/4
-XX:+PrintGCDetails:输出详细的GC处理日志
在重启你的Tomcat服务器之后,这些配置的更改才会有效。
讲讲高可用的数据与服务怎么实现,负载均衡策略以及区别,分布式(及事物),集群,高并发以及遇到的问题和解决方案
分布式 :
分布式架构:把系统按照模块拆分成多个子系统,多个子系统分布在不同的网络计算机上相互协作完成业务流
程,系统之间需要进行通信。
优点:
把模块拆分,使用接口通信,降低模块之间的耦合度。
把项目拆分成若干个子项目,不同的团队负责不同的子项目。
增加功能时只需要再增加一个子项目,调用其他系统的接口就可以。
可以灵活的进行分布式部署。
缺点:
1、系统之间交互需要使用远程通信,接口开发增加工作量。
2、各个模块有一些通用的业务逻辑无法共用。
基于soa的架构
SOA:面向服务的架构。也就是把工程拆分成服务层、表现层两个工程。服务层中包含业务逻辑,只需要对外提
供服务即可。表现层只需要处理和页面的交互,业务逻辑都是调用服务层的服务来实现。
分布式架构和soa架构有什么区别?
SOA,主要还是从服务的角度,将工程拆分成服务层、表现层两个工程。
分布式,主要还是从部署的角度,将应用按照访问压力进行归类,主要目标是充分利用服务器的资源,避免资源分配不均
集群:
一个集群系统是一群松散结合的服务器组,形成一个虚拟的服务器,为客户端用户提供统一的服务。对于这个
客户端来说,通常在访问集群系统时不会意识到它的服务是由具体的哪一台服务器提供。集群的目的,是为实
现负载均衡、容错和灾难恢复。以达到系统可用性和可伸缩性的要求。集群系统一般应具高可用性、可伸缩
性、负载均衡、故障恢复和可维护性等特殊性能。一般同一个工程会部署到多台服务器上。
常见的tomcat集群,Redis集群,Zookeeper集群,数据库集群
分布式与集群的区别:
分布式是指将不同的业务分布在不同的地方。 而集群指的是将几台服务器集中在一起,实现同一业务。一句
话:分布式是并联工作的,集群是串联工作的。
分布式中的每一个节点,都可以做集群。 而集群并不一定就是分布式的。
举例:就比如新浪网,访问的人多了,他可以做一个群集,前面放一个响应服务器,后面几台服务器完成同一
业务,如果有业务访问的时候,响应服务器看哪台服务器的负载不是很重,就将给哪一台去完成。
而分布式,从窄意上理解,也跟集群差不多, 但是它的组织比较松散,不像集群,有一个组织性,一台服务器垮了,
其它的服务器可以顶上来。分布式的每一个节点,都完成不同的业务,一个节点垮了,哪这个业务就不可访问了。
分布式是以缩短单个任务的执行时间来提升效率的,而集群则是通过提高单位时间内执行的任务数来提升效率。
举例:如果一个任务由10个子任务组成,每个子任务单独执行需1小时,则在一台服务器上执行该任务需10小
时。采用分布式方案,提供10台服务器,每台服务器只负责处理一个子任务,不考虑子任务间的依赖关系,执
行完,这个任务只需一个小时。(这种工作模式的一个典型代表就是Hadoop的Map/Reduce分布式计算模型)
而采用集群方案,同样提供10台服务器,每台服务器都能独立处理这个任务。假设有10个任务同时到达,10个
服务器将同时工作,1小时后,10个任务同时完成,这样,整身来看,还是1小时内完成一个任务!
高并发:
处理高并发常见的方法有哪些?
1)数据层
数据库集群和库表散列
分表分库
开启索引
开启缓存
表设计优化
Sql语句优化
缓存服务器(提高查询效率,减轻数据库压力)
搜索服务器(提高查询效率,减轻数据库压力)
图片服务器分离
2)项目层
采用面向服务分布式架构(分担服务器压力,提高并发能力)
采用并发访问较高的详情系统采用静态页面,HTML静态化 freemaker
使用页面缓存
用ActiveMQ使得业务进一步进行解耦,提高业务处理能力
使用分布式文件系统存储海量文件
3)应用层
Nginx服务器来做负载均衡
Lvs做二层负载
镜像
高可用:
目的:保证服务器硬件故障服务依然可用,数据依然保存并能够被访问。
高可用的服务
①分级管理:核心应用和服务具有更高的优先级,比如用户及时付款比能否评价商品更重要;
②超时设置:设置服务调用的超时时间,一旦超时,通信框架抛出异常,应用程序则根据服务调度策略选择重试or请求转移到其他服务器上
③异步调用:通过消息队列等异步方式完成,避免一个服务失败导致整个应用请求失败的情况。
不是所有服务都可以异步调用,对于获取用户信息这类调用,采用异步方式会延长响应时间,得不偿失。对于
那些必须确认服务调用成功后才能继续进行下一步的操作的应用也不适合异步调用。
④服务降级:网站访问高峰期间,为了保证核心应用的正常运行,需要对服务降级。
降级有两种手段:
一是拒绝服务,拒绝较低优先级的应用的调用,减少服务调用并发数,确保核心应用的正常运行;
二是关闭功能,关闭部分不重要的服务,或者服务内部关闭部分不重要的功能,以节约系统开销,为核心应用服务让出资源;
⑤幂等性设计:保证服务重复调用和调用一次产生的结果相同;
高可用的数据
保证数据高可用的主要手段有两种:一是数据备份,二是失效转移机制;
①数据备份:又分为冷备份和热备份,冷备份是定期复制,不能保证数据可用性。热备份又分为异步热备
和同步热备,异步热备是指多份数据副本的写入操作异步完成,而同步方式则是指多份数据副本的写入操作同时完成。
②失效转移:若数据服务器集群中任何一台服务器宕机,那么应用程序针对这台服务器的所有读写操作都
要重新路由到其他服务器,保证数据访问不会失败。
网站运行监控
”不允许没有监控的系统上线“
(1)监控数据采集
①用户行为日志收集:服务器端的日志收集和客户端的日志收集;目前许多网站逐步开发基于实时计算框架Storm的日志统计与分析工具;
②服务器性能监控:收集服务器性能指标,如系统Load、内存占用、磁盘IO等,及时判断,防患于未然;
③运行数据报告:采集并报告,汇总后统一显示,应用程序需要在代码中处理运行数据采集的逻辑;
(2)监控管理
①系统报警:配置报警阀值和值守人员联系方式,系统发生报警时,即使工程师在千里之外,也可以被及时通知;
②失效转移:监控系统在发现故障时,主动通知应用进行失效转移;
③自动优雅降级:为了应付网站访问高峰,主动关闭部分功能,释放部分系统资源,保证核心应用服务的正
常运行;—>网站柔性架构的理想状态
负载均衡:
什么是负载均衡?
当一台服务器的性能达到极限时,我们可以使用服务器集群来提高网站的整体性能。那么,在服务器集群
中,需要有一台服务器充当调度者的角色,用户的所有请求都会首先由它接收,调度者再根据每台服务器的负
载情况将请求分配给某一台后端服务器去处理。
(1)HTTP重定向负载均衡。
原理:当用户向服务器发起请求时,请求首先被集群调度者截获;调度者根据某种分配策略,选择一台服务
器,并将选中的服务器的IP地址封装在HTTP响应消息头部的Location字段中,并将响应消息的状态码设为
302,最后将这个响应消息返回给浏览器。当浏览器收到响应消息后,解析Location字段,并向该URL发起请
求,然后指定的服务器处理该用户的请求,最后将结果返回给用户。
优点:比较简单
缺点:调度服务器只在客户端第一次向网站发起请求的时候起作用。当调度服务器向浏览器返回响应信息后,
客户端此后的操作都基于新的URL进行的(也就是后端服务器),此后浏览器就不会与调度服务器产生关系,浏
览器需要每次请求两次服务器才能拿完成一次访问,性能较差。而且调度服务器在调度时,无法知道当前用户
将会对服务器造成多大的压力,只不过是把请求次数平均分配给每台服务器罢了,浏览器会与后端服务器直接交互。
(2)DNS域名解析负载均衡
原理:为了方便用户记忆,我们使用域名来访问网站。通过域名访问网站之前,首先需要将域名解析成IP地
址,这个工作是由DNS域名服务器完成的。我们提交的请求不会直接发送给想要访问的网站,而是首先发给域名
服务器,它会帮我们把域名解析成IP地址并返回给我们。我们收到IP之后才会向该IP发起请求。一个域名指向
多个IP地址,每次进行域名解析时,DNS只要选一个IP返回给用户,就能够实现服务器集群的负载均衡。
调度策略:一般DNS提供商会提供一些调度策略供我们选择,如随机分配、轮询、根据请求者的地域分配离他最近的服务器。
随机分配策略:
当调度服务器收到用户请求后,可以随机决定使用哪台后端服务器,然后将该服务器的IP封装在HTTP响应消息
的Location属性中,返回给浏览器即可。
轮询策略(RR) :
调度服务器需要维护一个值,用于记录上次分配的后端服务器的IP。那么当新的请求到来时,调度者将请求依
次分配给下一台服务器。
优点:配置简单,将负载均衡工作交给DNS,省略掉了网络管理的麻烦;
缺点:集群调度权交给了DNS服务器,从而我们没办法随心所欲地控制调度者,没办法定制调度策略,没办法了
解每台服务器的负载情况,只不过把所有请求平均分配给后端服务器罢了。某一台后端服务器发生故障时,即
使我们立即将该服务器从域名解析中去除,但由于DNS服务器会有缓存,该IP仍然会在DNS中保留一段时间,那
么就会导致一部分用户无法正常访问网站。不过动态DNS能够让我们通过程序动态修改DNS服务器中的域名解
析。从而当我们的监控程序发现某台服务器挂了之后,能立即通知DNS将其删掉。
(3)反向代理负载均衡。
原理:反向代理服务器是一个位于实际服务器之前的服务器,所有向我们网站发来的请求都首先要经过反向代
理服务器,服务器根据用户的请求要么直接将结果返回给用户,要么将请求交给后端服务器处理,再返回给用
户。反向代理服务器就可以充当服务器集群的调度者,它可以根据当前后端服务器的负载情况,将请求转发给
一台合适的服务器,并将处理结果返回给用户。
优点:
1.部署简单
2.隐藏后端服务器:与HTTP重定向相比,反向代理能够隐藏后端服务器,所有浏览器都不会与后端服务器直接
交互,从而能够确保调度者的控制权,提升集群的整体性能。
3.故障转移 :与DNS负载均衡相比,反向代理能够更快速地移除故障结点。当监控程序发现某一后端服务器出
现故障时,能够及时通知反向代理服务器,并立即将其删除。
4.合理分配任务 :HTTP重定向和DNS负载均衡都无法实现真正意义上的负载均衡,也就是调度服务器无法根据
后端服务器的实际负载情况分配任务。但反向代理服务器支持手动设定每台后端服务器的权重。我们可以根据
服务器的配置设置不同的权重,权重的不同会导致被调度者选中的概率的不同。
缺点:
1.调度者压力过大 :由于所有的请求都先由反向代理服务器处理,那么当请求量超过调度服务器的最大负载
时,调度服务器的吞吐率降低会直接降低集群的整体性能。
2.制约扩展 :当后端服务器也无法满足巨大的吞吐量时,就需要增加后端服务器的数量,可没办法无限量地增
加,因为会受到调度服务器的最大吞吐量的制约。
3.粘滞会话:反向代理服务器会引起一个问题。若某台后端服务器处理了用户的请求,并保存了该用户的
session或存储了缓存,那么当该用户再次发送请求时,无法保证该请求仍然由保存了其Session或缓存的服
务器处理,若由其他服务器处理,先前的Session或缓存就找不到了。
解决办法1: 可以修改反向代理服务器的任务分配策略,以用户IP作为标识较为合适。相同的用户IP会交由同
一台后端服务器处理,从而就避免了粘滞会话的问题。
解决办法2: 可以在Cookie中标注请求的服务器ID,当再次提交请求时,调度者将该请求分配给Cookie中标
注的服务器处理即可。
(4)IP负载均衡。
1.通过NAT实现负载均衡:响应报文一般比较大,每一次都需要NAT转换的话,大流量的时候,会导致调度器成为一个瓶颈。
2.通过直接路由实现负载均衡
3.VS/TUN 实现虚拟服务器
优点:IP负载均衡在内核进程完成数据分发,较反向代理均衡有更好的处理性能。
缺点:负载均衡的网卡带宽成为系统的瓶颈,场景:某个服务器跑的应用非高峰期间都能达到500M以
上,晚高峰一般能够超过1G,主流服务器的网卡都是千兆的,超过1G的流量明显会导致丢包的问题,此时又不
能停止业务对网卡进行更换。
(5)数据链路层负载均衡。
对于linux系统来说,数据链路层的解决方案就是实现多个网卡绑定联合提供服务,把多张网卡捆绑做成一个
逻辑网卡。避免负载均衡服务器网卡带宽成为瓶颈,是目前大型网站所使用的最广的一种负载均衡手段。
linux bonding的七种模式,mod=0~6:平衡抡循环策略,主-备份策略,平衡策略,广播策略,动态链接聚
合,适配器传输负载均衡,适配器适应性负载均衡
讲讲你是怎么优化数据库(sql,表的设计)以及索引的使用有哪些限制条件(索引失效)
a,选取最适用的字段:在创建表的时候,为了获得更好的性能,我们可以将表中字段的宽度设得尽可能小。另外一
个提高效率的方法是在可能的情况下,应该尽量把字段设置为NOTNULL,
b,使用连接(JOIN)来代替子查询(Sub-Queries)
c,使用联合(UNION)来代替手动创建的临时表
d,事物:
a)要么语句块中每条语句都操作成功,要么都失败。换句话说,就是可以保持数据库中数据的一致性和完整
性。事物以BEGIN关键字开始,COMMIT关键字结束。在这之间的一条SQL操作失败,那么,ROLLBACK命令就可以
把数据库恢复到BEGIN开始之前的状态。
b) 是当多个用户同时使用相同的数据源时,它可以利用锁定数据库的方法来为用户提供一种安全的访问方
式,这样可以保证用户的操作不被其它的用户所干扰。
e,减少表关联,加入冗余字段
f,使用外键:锁定表的方法可以维护数据的完整性,但是它却不能保证数据的关联性。这个时候我们就可以使用外键。
g,使用索引
h,优化的查询语句
i,集群
j,读写分离
k,主从复制
l,分表
m,分库
o,适当的时候可以使用存储过程
限制:尽量用全职索引,最左前缀:查询从索引的最左前列开始并且不跳过索引中的列;索引列上不操作,范围之
后全失效; 不等空值还有OR,索引影响要注意;like以通配符%开头索引失效会变成全表扫描的操作,字符串不
加单引号索引失效
讲讲Redis缓存,它的数据类型,和其他缓存之间的区别,及持久化,缓存穿透与雪崩它的解决方案
redis是内存中的数据结构存储系统,一个key-value类型的非关系型数据库,可持久化的数据库,相对于关
系型数据库(数据主要存在硬盘中),性能高,因此我们一般用redis来做缓存使用;并且redis支持丰富的数
据类型,比较容易解决各种问题,因此redis可以用来作为注册中心,数据库、缓存和消息中间件。Redis的
Value支持5种数据类型,string、hash、list、set、zset(sorted set);
String类型:一个key对应一个value
Hash类型:它的key是string类型,value又是一个map(key-value),适合存储对象。
List类型:按照插入顺序的字符串链表(双向链表),主要命令是LPUSH和RPUSH,能够支持反向查找和遍历
Set类型:用哈希表类型的字符串序列,没有顺序,集合成员是唯一的,没有重复数据,底层主要是由一个
value永远为null的hashmap来实现的。
zset类型:和set类型基本一致,不过它会给每个元素关联一个double类型的分数(score),这样就可以为
成员排序,并且插入是有序的。
Memcache和redis的区别:
数据支持的类型:redis不仅仅支持简单的k/v类型的数据,同时还支持list、set、zset、hash等数据结构
的存储;memcache只支持简单的k/v类型的数据,key和value都是string类型
可靠性:memcache不支持数据持久化,断电或重启后数据消失,但其稳定性是有保证的;redis支持数据持久
化和数据恢复,允许单点故障,但是同时也会付出性能的代价
性能上:对于存储大数据,memcache的性能要高于redis
应用场景:
Memcache:适合多读少写,大数据量的情况(一些官网的文章信息等)
Redis:适用于对读写效率要求高、数据处理业务复杂、安全性要求较高的系统
案例:分布式系统,存在session之间的共享问题,因此在做单点登录的时候,我们利用redis来模拟了
session的共享,来存储用户的信息,实现不同系统的session共享;
redis的持久化方式有两种:
RDB(半持久化方式):按照配置不定期的通过异步的方式、快照的形式直接把内存中的数据持久化到磁盘的一
个dump.rdb文件(二进制的临时文件)中,redis默认的持久化方式,它在配置文件(redis.conf)中。
优点:只包含一个文件,将一个单独的文件转移到其他存储媒介上,对于文件备份、灾难恢复而言,比较实用。
缺点:系统一旦在持久化策略之前出现宕机现象,此前没有来得及持久化的数据将会产生丢失
AOF(全持久化的方式):把每一次数据变化都通过write()函数将你所执行的命令追加到一个appendonly.aof文件里面,
Redis默认是不支持这种全持久化方式的,需要在配置文件(redis.conf)中将
appendonly no改成appendonly yes
优点:数据安全性高,对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机问题,也
不会破坏日志文件中已经存在的内容;
缺点:对于数量相同的数据集来说,aof文件通常要比rdb文件大,因此rdb在恢复大数据集时的速度大于AOF;
AOF持久化配置:
在Redis的配置文件中存在三种同步方式,它们分别是:
appendfsync always #每次有数据修改发生时都会都调用fsync刷新到aof文件,非常慢,但是安全;
appendfsync everysec #每秒钟都调用fsync刷新到aof文件中,很快,但是可能丢失一秒内的数据,推荐使用,兼顾了速度和安全;
appendfsync no #不会自动同步到磁盘上,需要依靠OS(操作系统)进行刷新,效率快,但是安全性就比较差;
二种持久化方式区别:
AOF在运行效率上往往慢于RDB,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效;
如果缓存数据安全性要求比较高的话,用aof这种持久化方式(比如项目中的购物车);
如果对于大数据集要求效率高的话,就可以使用默认的。而且这两种持久化方式可以同时使用。
redis-cluster集群,这种方式采用的是无中心结构,每个节点保存数据和整个集群的状态,每个节点都和其
他所有节点连接。如果使用的话就用redis-cluster集群。集群这块是公司运维搭建的,具体怎么搭建不是太了解。
我们项目中redis集群主要搭建了6台,3主(为了保证redis的投票机制)3从(高可用),每个主服务器都有
一个从服务器,作为备份机。所有的节点都通过PING-PONG机制彼此互相连接;客户端与redis集群连接,只
需要连接集群中的任何一个节点即可;Redis-cluster中内置了16384个哈希槽,Redis-cluster把所有的物
理节点映射到【0-16383】slot上,负责维护。
Redis是有事务的,redis中的事务是一组命令的集合,这组命令要么都执行,要不都不执行,保证一个事务中
的命令依次执行而不被其他命令插入。redis的事务是不支持回滚操作的。redis事务的实现,需要用到
MULTI(事务的开始)和EXEC(事务的结束)命令 ;
缓存穿透:缓存查询一般都是通过key去查找value,如果不存在对应的value,就要去数据库中查找。如果这
个key对应的value在数据库中也不存在,并且对该key并发请求很大,就会对数据库产生很大的压力,这就叫缓存穿透
解决方案:
1.对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃。
2.将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从
而避免了对底层存储系统的查询压力。
3.如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但
它的过期时间会很短,最长不超过五分钟。
缓存雪崩:当缓存服务器重启或者大量缓存集中在一段时间内失效,发生大量的缓存穿透,这样在失效的瞬间
对数据库的访问压力就比较大,所有的查询都落在数据库上,造成了缓存雪崩。 这个没有完美解决办法,但可
以分析用户行为,尽量让失效时间点均匀分布。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线
程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。
解决方案:
1.在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询
数据和写缓存,其他线程等待。
2.可以通过缓存reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存
3.不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀
4.做二级缓存,或者双缓存策略。A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间
设置为短期,A2设置为长期。
redis的安全机制(你们公司redis的安全这方面怎么考虑的?)
漏洞介绍:redis默认情况下,会绑定在bind 0.0.0.0:6379,这样就会将redis的服务暴露到公网上,如果
在没有开启认证的情况下,可以导致任意用户在访问目标服务器的情况下,未授权就可访问redis以及读取
redis的数据,攻击者就可以在未授权访问redis的情况下可以利用redis的相关方法,成功在redis服务器上
写入公钥,进而可以直接使用私钥进行直接登录目标主机;
解决方案:
1.禁止一些高危命令。修改redis.conf文件,用来禁止远程修改DB文件地址
2.以低权限运行redis服务。为redis服务创建单独的用户和根目录,并且配置禁止登录;
3.为redis添加密码验证。修改redis.conf文件,添加requirepass mypassword;
4.禁止外网访问redis。修改redis.conf文件,添加或修改 bind 127.0.0.1,使得redis服务只在当前主机使用;
5.做log监控,及时发现攻击;
redis的哨兵机制(redis2.6以后出现的)
哨兵机制:
监控:监控主数据库和从数据库是否正常运行;
提醒:当被监控的某个redis出现问题的时候,哨兵可以通过API向管理员或者其他应用程序发送通知;
自动故障迁移:主数据库出现故障时,可以自动将从数据库转化为主数据库,实现自动切换;
如果master主服务器设置了密码,记得在哨兵的配置文件(sentinel.conf)里面配置访问密码
redis中对于生存时间的应用
Redis中可以使用expire命令设置一个键的生存时间,到时间后redis会自动删除;
应用场景:
设置限制的优惠活动的信息;
一些及时需要更新的数据,积分排行榜;
手机验证码的时间;
限制网站访客访问频率;
讲讲ActiveMQ和其他消息中间件之间的区别
activemq的原理
原理:生产者生产消息, 把消息发送给activemq。 Activemq 接收到消息, 然后查看有多少个消费者,
然后把消息转发给消费者, 此过程中生产者无需参与。 消费者接收到消息后做相应的处理和生产者没有任何关系
对比RabbitMQ
RabbitMQ的协议是AMQP,而ActiveMQ使用的是JMS协议。顾名思义JMS是针对Java体系的传输协议,队列两
端必须有JVM,所以如果开发环境都是java的话推荐使用ActiveMQ,可以用Java的一些对象进行传递比如
Map、Blob(二进制大数据)、Stream等。而AMQP通用行较强,非java环境经常使用,传输内容就是标准字
符串。RabbitMQ安装比较麻烦。ActiveMQ解压即可用不用任何安装。
对比KafKa
Kafka性能超过ActiveMQ等传统MQ工具,集群扩展性好。
弊端是:
1、在传输过程中可能会出现消息重复的情况,
2、不保证发送顺序
3、一些传统MQ的功能没有,比如消息的事务功能。所以通常用Kafka处理大数据日志。
对比Redis
其实Redis本身利用List可以实现消息队列的功能,但是功能很少,而且队列体积较大时性能会急剧下降。对
于数据量不大、业务简单的场景可以使用。
如何解决消息重复问题
所谓消息重复,就是消费者接收到了重复的消息,一般来说我们对于这个问题的处理要把握下面几点,
①.消息不丢失(上面已经处理了)
②.消息不重复执行
一般来说我们可以在业务段加一张表,用来存放消息是否执行成功,每次业务事物commit之后,告知服务端,已经
处理过该消息,这样即使你消息重发了,也不会导致重复处理
大致流程如下:业务端的表记录已经处理消息的id,每次一个消息进来之前先判断该消息是否执行过,如果执行
过就放弃,如果没有执行就开始执行消息,消息执行完成之后存入这个消息的id
讲讲分布式事务的异步通信问题解决方案
问题介绍:一个消息发送过去了,不管结果如何发送端都不会原地等待接收端。直到接收端再推送回来回执消息,
发送端才直到结果。但是也有可能发送端消息发送后,石沉大海,杳无音信。这时候就需要一种机制能够对这种不
确定性进行补充。
解决方案:
你给有很多笔友,平时写信一去一回,但是有时候会遇到迟迟没有回信的情况。那么针对这种偶尔出现的情况,你
可以选择两种策略。一种方案是你发信的时候用定个闹钟,设定1天以后去问一下对方收没收到信。另一种方案就
是每天夜里定个时间查看一下所有发过信但是已经一天没收到回复的信。然后挨个打个电话问一下。
第一种策略就是实现起来就是延迟队列,第二种策略就是定时轮询扫描。
二者的区别是延迟队列更加精准,但是如果周期太长,任务留在延迟队列中时间的就会非常长,会把队列变得冗
长。比如用户几天后待办提醒,生日提醒。
那么如果遇到这种长周期的事件,而且并不需要精确到分秒级的事件,可以利用定时扫描来实现,尤其是比较消耗
性能的大范围扫描,可以安排到夜间执行。
讲讲怎么解单点登录的访问,分布式session跨域问题
单点登录是相互信任的系统模块登录一个模块后,其他模块不需要重复登录即认证通过。
采用CAS单点登录框架,首先CAS有两大部分:客户端和服务端。
服务端就是一个web工程部署在tomcat中。在服务端完成用户认证操作。每次访问系统模块时,需要去CAS完成获
取ticket。当验证通过后,访问继续操作。对于CAS服务端来说,我们访问的应用模块就是CAS客户端。
什么是跨域?
当异步请求时,访问的请求地址的协议、ip地址、端口号任意一个与当前站点不同时,就会涉及跨域访问。
什么时候涉及跨域问题?当涉及前端异步请求的时候才涉及跨域。
解决方案:
1、jQuery提供了jsonp实现
2、W3C标准提供了CORS(跨域资源共享)解决方案。
用了CAS,所有应用项目中如果需要登录时在web.xml中配置过滤器做请求转发到cas端工作原理是在cas登录后会
给浏览器发送一个票据(ticket),浏览器cookie中会缓存这个ticket,在登录其他项目时会拿着浏览器的
ticket转发到cas,到cas后根据票据判断是否登录
讲讲linux命令awk、cat、sort、cut、grep、uniq、wc、top、find、sed等作用
awk:相较于sed 常常作用于一整个行的处理,awk 则比较倾向于一行当中分成数个『字段』来处理。 因此,awk
相当的适合处理小型的数据数据处理
cat:主要用来查看文件内容,创建文件,文件合并,追加文件内容等功能。
sort:功能:排序文本,默认对整列有效
cut:cut命令可以从一个文本文件或者文本流中提取文本列
grep:是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来
uniq:功能:去除重复行,只会统计相邻的
wc:功能: 统计文件行数、字节、字符数
top:用来监控Linux的系统状况,比如cpu、内存的使用
find:功能: 搜索文件目录层次结构
sed:sed 是一种在线编辑器,它一次处理一行内容
讲讲什么是死锁,怎么解决死锁,表级锁和行级锁,悲观锁与乐观锁以及线程同步锁区别
死锁:打个比方,你去面试,面试官问你,你告诉我什么是死锁我就让你进公司。你回答说你让我进公司我就告诉你什么是死锁
互斥条件:资源不能被共享,只能由一个进程使用。
请求与保持条件:进程已获得了一些资源,但因请求其它资源被阻塞时,对已获得的资源保持不放。
不可抢占条件:有些系统资源是不可抢占的,当某个进程已获得这种资源后,系统不能强行收回,只能由进程
使用完时自己释放。
循环等待条件:若干个进程形成环形链,每个都占用对方申请的下一个资源。
(1) 死锁预防:破坏导致死锁必要条件中的任意一个就可以预防死锁。例如,要求用户申请资源时一次性申请
所需要的全部资源,这就破坏了保持和等待条件;将资源分层,得到上一层资源后,才能够申请下一层资源,
它破坏了环路等待条件。预防通常会降低系统的效率。
(2) 死锁避免:避免是指进程在每次申请资源时判断这些操作是否安全,例如,使用银行家算法。死锁避免算
法的执行会增加系统的开销。
(3) 死锁检测:死锁预防和避免都是事前措施,而死锁的检测则是判断系统是否处于死锁状态,如果是,则执
行死锁解除策略。
(4) 死锁解除:这是与死锁检测结合使用的,它使用的方式就是剥夺。即将某进程所拥有的资源强行收回,分
配给其他的进程。
表级锁: 开销小,加锁快;不会出现死锁(因为MyISAM会一次性获得SQL所需的全部锁);锁定粒度大,发生锁冲
突的概率最高,并发度最低。
行级锁: 开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样
别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁
等,读锁,写锁等,都是在做操作之前先上锁。再比如Java里面的同步原语synchronized关键字的实现也是悲观
锁。通过for update来实现
乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会
判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以
提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中
java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。通过version版本字段来实现
同步锁:
场景:在开发中,遇到耗时的操作,我们需要把耗时的逻辑放入子线程中执行,防止卡顿。二个线程分别执行两个
任务,同时执行完成,同时解析文件,获取数据后,同时插入数据库,由于插入的表比较多,这样容易出现插入错
乱的bug
采用synchronized:
声明该方法为同步方法,如果一个方法正在执行,别的方法调用,则处于等待状态。当这个方法执行完成后,可以
调用解锁方法,wait():释放占有的对象锁,线程进入等待池。
区别:
synchronized是在JVM层面实现的,因此系统可以监控锁的释放与否,而ReentrantLock使用代码实现的,系统
无法自动释放锁,需要在代码中finally子句中显式释放锁lock.unlock();
在并发量比较小的情况下,使用synchronized是个不错的选择,但是在并发量比较高的情况下,其性能下降
很严重,此时ReentrantLock是个不错的方案。
讲讲怎么加快访问速度,怎样进行程序性能调优
加快访问:
硬件上加大网络带宽、和服务器内存
代码的处理:静态页面、缓存、优化sql、创建索引等方案
系统性能就是两个事:
Throughput ,吞吐量。也就是每秒钟可以处理的请求数,任务数。
Latency, 系统延迟。也就是系统在处理一个请求或一个任务时的延迟。
那么Latency越好,能支持的Throughput就会越高。因为Latency短说明处理速度快,于是就可以处理更多的请求。
提高吞吐量:分布式集群,模块解藕,设计模式
系统延迟:异步通信
讲讲缓存的设计和优化,缓存和数据库一致性同步解决方案
1.降低后端负载:对于高消耗的SQL:join结果集、分组统计结果;对这些结果进行缓存。
2.加速请求响应
3.大量写合并为批量写:如计数器先redis累加再批量写入DB
4.超时剔除:例如expire
5.主动更新:开发控制生命周期(最终一致性,时间间隔比较短)
6.缓存空对象
7.布隆过滤器拦截
8.命令本身的效率:例如sql优化,命令优化
9.网络次数:减少通信次数
10.降低接入成本:长连/连接池,NIO等。
11.IO访问合并
目的:要减少缓存重建次数、数据尽可能一致、减少潜在危险。
解决方案:
1.互斥锁setex,setnx:
如果 set(nx 和 ex) 结果为 true,说明此时没有其他线程重建缓存,那么当前线程执行缓存构建逻辑。
如果 setnx(nx 和 ex) 结果为 false,说明此时已经有其他线程正在执行构建缓存的工作,那么当前线程将休
息指定时间 ( 例如这里是 50 毫秒,取决于构建缓存的速度 ) 后,重新执行函数,直到获取到数据。
2永远不过期:
热点key,无非是并发特别大一级重建缓存时间比较长,如果直接设置过期时间,那么时间到的时候,巨大的访
问量会压迫到数据库上,所以要给热点key的val增加一个逻辑过期时间字段,并发访问的时候,判断这个逻辑
字段的时间值是否大于当前时间,大于了说明要对缓存进行更新了,那么这个时候,依然让所有线程访问老的
缓存,因为缓存并没有设置过期,但是另开一个线程对缓存进行重构。等重构成功,即执行了redis set操作
之后,所有的线程就可以访问到重构后的缓存中的新的内容了
从缓存层面来看,确实没有设置过期时间,所以不会出现热点 key 过期后产生的问题,也就是“物理”不过期。
从功能层面来看,为每个 value 设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去构建缓存。
一致性问题:
1.先删除缓存,然后在更新数据库,如果删除缓存失败,那就不要更新数据库,如果说删除缓存成功,而更新
数据库失败,那查询的时候只是从数据库里查了旧的数据而已,这样就能保持数据库与缓存的一致性。
2.先去缓存里看下有没有数据,如果没有,可以先去队列里看是否有相同数据在做更新,发现队列里有一个请
求了,那么就不要放新的操作进去了,用一个while(true)循环去查询缓存,循环个200MS左右再次发送到
队列里去,然后同步等待缓存更新完成。
讲讲消息队列和消息被重复消费怎么处理,消费者接收不到消息怎么办
什么是消息队列?
就是消息的传输过程中保存消息的容器。
消息队列都解决了什么问题?
异步,并行,解耦,排队
消息模式?
订阅,点对点
一、重复消费:Queue支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费。
二、丢消息:
1.用持久化消息
2.非持久化消息及时处理不要堆积
3.启动事务,启动事务后,commit()方法会负责等待服务器的返回,也就不会关闭连接导致消息丢失。
三、消息重发:
消息被重新传递给客户端:
1、使用事务会话,并调用滚退()。
2、在调用commit()之前关闭事务会话。
3、会话使用CLIENT_ACKNOWLEDGE签收模式,并Session .recover()重发被调用。
4、客户端连接超时(也许正在执行的代码要比配置的超时周期更长)。
四、不消费:去ActiveMQ.DLQ里找找
什么是ActiveMQ.DLQ?
1.一旦消息的重发尝试超过了为重发策略配置的最大重发次数,一个“Poison ACK”被发送回the broker,让
他知道消息被认为是毒丸。the broker然后接收消息并将其发送到死信队列,以便以后可以进行分析。
2.在activemq中死信队列叫做ActiveMQ.DLQ。所有无法传递的消息将被发送到这个队列,这很难管理。
3.因此,您可以在Activemq.xml配置文件的目标策略映射中设置个体死信策略,它允许您为队列或主题指定
特定的死信队列前缀。
Mq消费者接受不到消息存在2中情况:
1. 处理失败 指的是MessageListener的onMessage方法里抛出RuntimeException。
2. Message头里有两个相关字段:Redelivered默认为false,redeliveryCounter默认为0。
3. 消息先由broker发送给consumer,consumer调用listener,如果处理失败,本地
redeliveryCounter++,给broker一个特定应答,broker端的message里redeliveryCounter++,延迟一点
时间继续调用,默认1s。超过6次,则给broker另一个特定应答,broker就直接发送消息到DLQ。
4. 如果失败2次,consumer重启,则broker再推过来的消息里,redeliveryCounter=2,本地只能再重试4次
即会进入DLQ。
5. 重试的特定应答发送到broker,broker即会在内存将消息的redelivered设置为
true,redeliveryCounter++,但是这两个字段都没有持久化,即没有修改存储中的消息记录。所以broker重
启时这两个字段会被重置为默认值。
讲讲SOA与分布式的区别,zookeeper或者activeMQ服务挂了怎么办
SOA和分布式的区别?
SOA,将工程拆分成服务层、表现层两个工程,服务层中包含业务逻辑,只需要对外提供服务即可。表现层只需要
处理和页面的交互,业务逻辑都是调用服务层的服务来实现。
分布式,主要还是从部署的角度,将应用按照访问压力进行归类,主要目标是充分利用服务器的资源,避免资源分配不均
如果activeMQ的服务挂了,怎么办?
1、在通常的情况下,非持久化消息是存储在内存中的,持久化消息是存储在文件中的,它们的最大限制在配置文
件的<systemUsage>节点中配置。但是,在非持久化消息堆积到一定程度,内存告急的时候,ActiveMQ会将内存
中的非持久化消息写入临时文件中,以腾出内存。虽然都保存到了文件里,但它和持久化消息的区别是,重启后持
久化消息会从文件中恢复,非持久化的临时文件会直接删除。
2、考虑高可用,实现activemq集群。
如果zookeeper服务挂了怎么办?
注册中心对等集群,任意一台宕掉后,会自动切换到另一台
注册中心全部宕掉,服务提供者和消费者仍可以通过本地缓存通讯
服务提供者无状态,任一台宕机后,不影响使用
服务提供者全部宕机,服务消费者会无法使用,并无限次重连等待服务者恢复
讲讲JUC的辅助类
ReentrantReadWriteLock:读写锁
CountDownLatch:减少计数
CyclicBarrier:循环栅栏
Semaphore:信号灯