1.IOC与DI的区别,原理?
原理:反射
IOC控制反转。是一个概念,是一种思想。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。把对象的创建、初始化、销毁等工作交给spring容器来做。由spring容器控制对象的生命周期。
DI依赖注入。依赖注入DI是指程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。依赖注入是目前最优秀的解耦方式。依赖注入让Spring的Bean之间以配置文件的方式组织在一起,而不是以硬编码的方式耦合在一起的。
依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情,就是指通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦。
2.Spring IOC注入(依赖注入)的方式:
<1>.构造器注入:
构造器注入主要是依赖于构造方法去实现,构造方法可以是有参也可以是无参,我们在平常都是通过类的构造方法来创建类对象,以及给他赋值,同样Spring 也可以采用反射的方式,通过构造方法来完成注入注入(赋值),这就是构造器注入的原理。
constructor-arg元素用于定义类构造方法的参数,其中type用于定义参数的类型,也可以使用index来定义参数的位置,而这里的value是用于设置值,以上的代码就是通过一个Spring去装配一个Bean。
<2>.setter注入:
setter是Spring现在最主流的注入方式,它可以利用Java Bean 规范所定义set/get方法来完成注入,可读性灵活性高,它不需要使用构造器注入时出现的多个参数,它可以把构造方法声明成无参构造,再使用setter注入设置相对应的值,其实也是通过java反射技术去实现的。
<3>.接口注入:
获取aplicationContext,然后在其他地方调用。其灵活性、易用性上不如其他两种注入模式。
3.Spring IOC底层实现原理?
<1>.读取bean的XML配置文件
<2>.使用beanId查找bean配置,并获取配置文件中class地址。
<3>.使用Java反射技术实例化对象
<4>.获取属性配置,使用反射技术进行赋值。
4.AOP原理及其应用:
原理:动态代理
jdk动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
cglib动态代理:将代理对象类的class文件加载进来,通过ASM字节码技术修改其字节码生成子类来处理。
区别:JDK动态代理只能对实现了接口的类生成代理,而不能针对类 。CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 。因为是继承,所以该类或方法最好不要声明成final ,final可以阻止继承和多态。
应用场景:
数据库:使用AOP监控用户操作并插入数据库
日志:通过aop添加日志管理
事务、异常捕获
Lazy loading 懒加载
Transactions 事务
Authentication 权限
Caching 缓存
Context passing 内容传递
Error handling 错误处理
Debugging 调试
logging, tracing, profiling and monitoring 记录跟踪 优化 校准
Performance optimization 性能优化
Persistence 持久化
Resource pooling 资源池
Synchronization 同步
6.介绍下mybatis中的#{}和KaTeX parse error: Expected 'EOF', got '#' at position 12: {}的区别: <1>.#̲{}表示一个占位符号;{}表示拼接sql串。
<2>.通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换;通过KaTeX parse error: Expected 'EOF', got '#' at position 50: …行jdbc类型转换。 <3>.#̲{}可以有效防止sql注入,{}不能防止sql注入;
<4>.#{}可以接收简单类型值或pojo属性值。 如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称;
可
以
接
收
简
单
类
型
值
或
p
o
j
o
属
性
值
,
如
果
p
a
r
a
m
e
t
e
r
T
y
p
e
传
输
单
个
简
单
类
型
值
,
{}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,
可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,{}括号中只能是value。
7.Spring MVC工作原理图:
Springmvc的生命周期:
第一步:用户发起请求到中央控制器(DispatcherServlet)
第二步:中央控制器调用HandlerMapping(处理器映射器)查找 Handler
可以根据xml配置、注解进行查找
第三步:处理器映射器HandlerMapping向中央控制器返回Handler
第四步:中央控制器调用处理器适配器HandlerAdapter去执行Handler
第五步:处理器适配器去执行Handler
第六步:Handler执行完成给处理器适配器返回ModelAndView
第七步:处理器适配器向中央控制器返回ModelAndView
ModelAndView是springmvc框架的一个底层对象,包括 Model和view
第八步:中央控制器请求视图解析器去进行视图解析
根据逻辑视图名解析成真正的视图(jsp)
第九步:视图解析器向中央控制器返回View
第十步:中央控制器进行视图渲染
视图渲染将模型数据(在ModelAndView对象中)填充到request域
第十一步:中央控制器向用户响应结果
8.Shiro与Spring Security的区别:
相同点:
<1>.认证功能
<2>.授权功能
<3>.加密功能
<4>.会话管理
<5>.缓存支持
<6>.rememberMe功能
不同点:
<1>.Spring Security 基于Spring 开发,项目若使用 Spring 作为基础,配合 Spring Security 做权限更加方便,而 Shiro 需要和 Spring 进行整合开发;
<2>.Spring Security 功能比 Shiro 更加丰富些,例如安全维护方面;
<3>.Spring Security 社区资源相对比 Shiro 更加丰富;
<4>.Shiro 的配置和使用比较简单,Spring Security 上手复杂些;
<5>.Shiro 依赖性低,不需要任何框架和容器,可以独立运行Spring Security 依赖Spring容器;
<6>.shiro 不仅仅可以使用在web中,它可以工作在任何应用环境中。在集群会话时Shiro最重要的一个好处就是它的会话是独立于容器的。
9.SQL优化技巧:
<1>.注意通配符中Like的使用:
以下写法会造成全表的扫描,例如:
select id,name from userinfo where name like ‘%name%’
或者
select id,name from userinfo where name like ‘%name’
下面的写法执行效率快很多,因为它使用了索引
select id,name from userinfo where name like ‘name%’
<2>.避免在where子句中对字段进行函数操作,比如:
select id from userinfo where substring(name,1,6) = ‘xiaomi’
或者
select id from userinfo where datediff(day,datefield,‘2017-05-17’) >= 0
上面两句都对字段进行了函数处理,会导致查询分析器放弃了索引的使用。
正确的写法:
select id from userinfo where name like’xiaomi%’
select id from userinfo where datefield <= ‘2017-05-17’
通俗理解就是where子句‘=’ 左边不要出现函数、算数运算或者其他表达式运算
<3>.在子查询当中,尽量用exists代替in
select name from userinfo a where id in(select id from userinfo b)
可以改为
select name from userinfo a where exists(select 1 from userinfo b where id = a.id)
下面的查询速度比in查询的要快很多。
<4>.where子句中尽量不要使用is null 或 is not null对字段进行判断,例如:
select id from userinfo where name is null
尽量在数据库字段中不出现null,如果查询的时候条件为 is null ,索引将不会被使用,造成查询效率低,因此数据库在设计的时候,尽可能将某个字段可能为空的时候设置默认值,那么查询的时候可以根据默认值进行查询,比如name字段设置为0,查询语句可以修改为:
select id from userinfo where name=0
<5>.避免在where子句使用or作为链接条件,例如:
select id from userinfo where name=‘xiaoming’ or name=‘xiaowang’
可以改写为:
select id from userinfo where name = ‘xiaoming’ union all
select id from userinfo where name = ‘xiaowang’
<6>.避免在 where 子句中使用 != 或 <> 操作符。例如:
select name from userinfo where id <> 0
说明:数据库在查询时,对 != 或 <> 操作符不会使用索引,
而对于 < 、 <= 、 = 、 > 、 >= 、 BETWEEN AND,数据库才会使用索引。
因此对于上面的查询,正确写法可以改为:
select name from userinfo where id < 0 union all select name from userinfo where id > 0
<7>.少用in 或 not in
对于连续的数值范围查询尽量使用BETWEEN AND,例如:
select name from userinfo where id BETWEEN 10 AND 70
10.redis数据怎么存hash,什么为key,什么为value?
第一层key-value:每个对象一个key,每个属性为一个field-value
第二层key-value:field为自己设置的键名,value为具体存储的信息
11.redis五大数据类型存储信息分
12.Dubbo的底层原理:socket
13.spring cloud中常用的组件?
服务发现——Netflix Eureka
客服端负载均衡——Netflix Ribbon
断路器——Netflix Hystrix
服务网关——Netflix Zuul
分布式配置——Spring Cloud Config
Eureka:
作用:实现服务治理(服务注册与发现)
简介:Spring Cloud Eureka是Spring Cloud Netflix项目下的服务治理模块。
由两个组件组成:Eureka服务端和Eureka客户端。
Eureka服务端用作服务注册中心。支持集群部署。
Eureka客户端是一个java客户端,用来处理服务注册与发现。
在应用启动时,Eureka客户端向服务端注册自己的服务信息,同时将服务端的服务信息缓存到本地。客户端会和服务端周期性的进行心跳交互,以更新服务租约和服务信息。
Ribbon:
作用:Ribbon,主要提供客户侧的软件负载均衡算法。
简介:Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。
注意看上图,关键点就是将外界的rest调用,根据负载均衡策略转换为微服务调用。Ribbon有比较多的负载均衡策略,以后专门讲解。
Hystrix:
作用:断路器,保护系统,控制故障范围。
简介:为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证100%可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。
Zuul:
作用:api网关,路由,负载均衡等多种作用
简介:类似nginx,反向代理的功能,不过netflix自己增加了一些配合其他组件的特性。
在微服务架构中,后端服务往往不直接开放给调用端,而是通过一个API网关根据请求的url,路由到相应的服务。当添加API网关后,在第三方调用端和服务提供方之间就创建了一面墙,这面墙直接与调用方通信进行权限控制,后将请求均衡分发给后台服务端。
Config:
作用:配置管理
简介:SpringCloud Config提供服务器端和客户端。服务器存储后端的默认实现使用git,因此它轻松支持标签版本的配置环境,以及可以访问用于管理内容的各种工具。
这个还是静态的,得配合Spring Cloud Bus实现动态的配置更新
14.什么是RESTful?
restful就是一个约定。通俗来说就是一个请求数据的路径,这个路径遵循了一些规范。后来都把遵循了这些规范的路径叫做restful。
比较重要的是HTTP方法里的 GET POST PUT DELETE,相对应可以对应到一条数据的查增改删。
15.如何选择使用InnoDB与MyISAM?
innodb引擎的特点:
<1>.支持事务
<2>.支持行锁,并发性能好
<3>. CPU及内存缓存页优化使得资源利用率高
MYISAM特点:
<1>.单条sql语句执行快
<2>.表锁
<3>.不支持事务
所以使用INNODB场景有两个,满足一个即可使用
<1>.需要事务;
<2>.并发量大,写频繁或者有复杂sql长时间执行;
使用Myisam场景有两个,满足一个即可使用
<1>.并发量小时一律使用myisam;
<2>.并发量大,少复杂查询,读多写少,锁竞争不激烈时使用MYISAM;
在使用myisam时,如果执行了复杂sql,join表数量多时可能会导致表长时间被锁,所以互联网项目一律使用INNODB
集合
1.Java中的集合类及关系图
(1)List和Set继承自Collection接口;
(2)Set无序不允许元素重复。HashSet和TreeSet是两个主要的实现类;
(3)List有序且允许元素重复。ArrayList、LinkedList和Vector是三个主要的实现类;
(4)Map也属于集合系统,但和Collection接口没关系。Map是key对value的映射集合,其中key列就是一个集合。Key不能重复,但是value可以重复。hashMap、TreeMap和Hashtable是三个主要的实现类。
(5)SortedSet和SortedMap接口对元素按指定规则排序,SortedMap是对key进行排序。
(6)关系图如下:
2.HashMap和HashTable区别
(1)HashTable的方法前有synchronized来同步,是线程安全的;HashMap是非同步的,是线程非安全的;
(2)HashTable不允许null值(key和value都不可以);Hash允许null值(key和value都可以);
(3)HashTable有一个contains(Object value)功能和containsValue(Object value)功能一样;
(4)HashTable使用Enumeration进行遍历;hashMap使用Iterator进行遍历;
(5)HashTable中hash数组默认大小时11,增加的方式old*2+1;HashMap中的默认大小是16,而且一定是2的指数。
(6)哈希值的使用不同,HashTable直接使用对象的hashCode;hashMap重新计算hash值,而且用于代替求模。
3.ArrayList和vector区别
(1)ArrayList和Vector都实现了List接口,都是通过数组实现的;
(2)Vector是线程安全的,而ArrayList是非线程安全的;
(3)List第一次创建的时候,会有一个初始大小,随着不断向List中增加元素,当List认为容量不够的时候就会进行扩容。Vector缺省情况下,自动增长原来一倍的数组长度,ArrayList增长原来的50%。
4.ArrayList和LinkedList区别及使用场景
(1)区别
①ArrayList底层是用数组实现的,可以认为ArrayList是一个可改变大小的数组,随着越来越多的元素被添加到ArrayList中,其规模是动态增加的;
②LinkedList底层是通过双向列表实现的,LinkedList和ArrayList相比,增删的速速较快,但是查询和修改值得速速较慢。同时linkedList还实现了Queue接口,所以它还提供了offer()、peek()、poll()等方法;
(2)使用场景
①LinkedList更适合从中间插入或者删除(链表的特性);
②ArrayList更适合检索和在末尾插入或删除(数组的特性)。
5.Collection和collections的区别
(1)Java.util.Collection是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在java类中有很多具体的实现规则。Collection接口的意义是为各种具体的集合提供了一个最大化的操作方式;
(2)Java.util.Conllections是一个包装类,它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于java的Collection框架。
6.那些集合类是线程安全的?
(1)Vector
(2)HashTable
7.集合的底层实现
(1)ArrayList:底层基于数组实现,是List接口的可变数组非同步实现,并允许包括空值在内的所有元素;
(2)LinkedList:底层是基于List接口的双向链表非同步实现;
(3)HashMap:底层是基于数组实现,是基于Map接口的非同步实现,允许使用空值和空键,但是不保证映射的顺序;
(4)HashTable:底层是基于数组实现,是基于Hash表的Map接口同步实现,不允许使用Null值null键;
(5)ConcurrentHashMap:
(6)HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,也会根据hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry(键值对对象)。
(7)HashSet:由哈希表(实际上是一个HashMap实例)支持,不保证set的迭代顺序,并允许使用null元素。基于HashMap实现,API也是对HashMap的行为进行了封装,可参考HashMap;
(8)LinkedHashMap继承于HashMap,底层使用哈希表和双向链表来保存所有元素,并且它是非同步,允许使用null值和null键。
Redis
1.为啥用Redis?
因为传统的关系型数据库如Mysql已经不能适用所有的场景了,比如秒杀的库存扣减,APP首页的访问流量高峰等等,都很容易把数据库打崩,所以引入了缓存中间件,目前市面上比较常用的缓存中间件有Redis 和 Memcached 不过中和考虑了他们的优缺点,最后选择了Redis。
Redis 和 Memcached 的区别:
Redis:支持持久化,储存类型除了key-value外,还可以储存多种数据,例如List,Set,Hash,Zset等,是单线程,多路IO复用;
Memcached:一般不支持持久化,只支持简单的key-value形式,只能是字符串,是多线程,加锁机制。
2.Redis有哪些数据结构呀?
字符串String、字典Hash、列表List、集合Set、有序集合SortedSet、HyperLogLog、Geo、Pub/Sub。
3.如果有大量的key需要设置同一时间过期,一般需要注意什么?
如果大量的key过期时间设置的过于集中,到过期的那个时间点,redis可能会出现短暂的卡顿现象。严重的话会出现缓存雪崩,我们一般需要在时间上加一个随机值,使得过期时间分散一些。
4.Redis分布式锁么,它是什么回事?
先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。
5.如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?
set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的!
6.假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?
使用keys指令可以扫出指定模式的key列表。
7.如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?
redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。
8.使用过Redis做异步队列么,你是怎么用的?
一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。
9.如果对方追问可不可以不用sleep呢?
list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。
10.如果对方接着追问能不能生产一次消费多次呢?
使用pub/sub主题订阅者模式,可以实现 1:N 的消息队列。
11.如果对方继续追问 pub/su b有什么缺点?
在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如RocketMQ等。
12.如果对方究极TM追问Redis如何实现延时队列?
这一套连招下来,我估计现在你很想把面试官一棒打死(面试官自己都想打死自己了怎么问了这么多自己都不知道的),如果你手上有一根棒球棍的话,但是你很克制。平复一下激动的内心,然后神态自若的回答道:使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。
13.Redis是怎么持久化的?服务主从数据怎么交互的?
RDB做镜像全量持久化,AOF做增量持久化。因为RDB会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要AOF来配合使用。在redis实例重启时,会使用RDB持久化文件重新构建内存,再使用AOF重放近期的操作指令来实现完整恢复重启之前的状态。
14.那如果突然机器掉电会怎样?
取决于AOF日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如1s1次,这个时候最多就会丢失1s的数据。
15.对方追问RDB的原理是什么?
你给出两个词汇就可以了,fork和cow。fork是指redis通过创建子进程来进行RDB操作,cow指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。
16.Pipeline有什么好处,为什么要用pipeline?
可以将多次IO往返的时间缩减为一次,前提是pipeline执行的指令之间没有因果相关性。使用redis-benchmark进行压测的时候可以发现影响redis的QPS峰值的一个重要因素是pipeline批次指令的数目。
17.Redis的同步机制了解么?
Redis可以使用主从同步,从从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将RDB文件全量同步到复制节点,复制节点接受完成后将RDB镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。后续的增量数据通过AOF日志同步即可,有点类似数据库的binlog。
18.是否使用过Redis集群,集群的高可用怎么保证,集群的原理是什么?
Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。
Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。
SQL索引优化
Mysql的四种隔离级别
SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。
Read Uncommitted(读取未提交内容)
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
Read Committed(读取提交内容)
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
Repeatable Read(可重读)
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
Serializable(可串行化)
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
这四种隔离级别采取不同的锁类型来实现,若读取的是同一个数据的话,就容易发生问题。例如:
脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。
不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。
幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就有几列数据是未查询出来的,如果此时插入和另外一个事务插入的数据,就会报错。
索引优化策略
最左前缀匹配原则,上面讲到了
主键外检一定要建索引
对 where,on,group by,order by 中出现的列使用索引
尽量选择区分度高的列作为索引,区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0
对较小的数据列使用索引,这样会使索引文件更小,同时内存中也可以装载更多的索引键
索引列不能参与计算,保持列“干净”,比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本太大。所以语句应该写成create_time = unix_timestamp(’2014-05-29’);
为较长的字符串使用前缀索引
尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可
不要过多创建索引, 权衡索引个数与DML之间关系,DML也就是插入、删除数据操作。这里需要权衡一个问题,建立索引的目的是为了提高查询效率的,但建立的索引过多,会影响插入、删除数据的速度,因为我们修改的表数据,索引也需要进行调整重建
对于like查询,”%”不要放在前面。
数据库减负
传统的企业应用中,因为企业规模小的原因,数据量并不是很多,很少考虑到海量数据的问题。不过在当今的互联网大环境下,应用面对的社会用户激增,数据量动辄上千万上亿,面对这种海量数据的情况下,数据库的减负就显得尤为重要。以下主要分享几个数据库的减负方案。
1.缓存
缓存,可以将数据直接存在本地内存中,使用如Map、缓存框架Redis等,将一些需要频繁使用的热点数据保存在缓存中,每当用户发起请求访问时,会先到缓存中查询,如果有数据则直接返回数据,没有再去数据库查询并且将查询的数据保存在缓存中,极大的降低了数据库的访问频率,从而降低服务器的压力。但一般可以缓存起来使用的数据,一般都不能对实时性要求太高。
2.页面静态化
页面静态化其实可以算作是缓存的另外一种形式,相当于直接将相关的页面渲染结果缓存起来。首先大家知道,在我们的 Web 项目中,资源分为两大类:静态资源和动态资源。
静态资源就是我们常见的 HTML、CSS、JavaScript、图片等资源,这些资源可以不经过服务端处理,就可以直接返回给前端浏览器,浏览器就可以直接显示出来。
动态资源则是指我们项目中的 Servlet 接口、Jsp 文件、Freemarker 等,这些需要经过服务端渲染之后,才可以返回前端的资源。
在实际项目中,静态资源的访问速度要远远高于动态资源,动态资源往往很容易遇到服务器瓶颈、数据库瓶颈,因此,对于一些不经常更新的页面,或者说更新比较缓慢的页面,我们可以通过页面静态化,将一个动态资源保存为静态资源,这样当服务端需要访问的时候,直接将静态资源返回,就可以避免去操作数据库了,降低数据库的压力。
一般来说,Freemarker、Velocity 等都有相关的方法可以帮助我们快速将动态页面生成静态页面。
3.数据库优化
数据库优化,可以从SQL优化、表结构优化、数据库分区分表等多个方面对数据库优化。
1.SQL优化:
2.表结构优化:
3.数据库分区分表:
4. 热点数据分离
在数据库存储的海量数据中,数据的活跃程度不尽相同,例如用户注册,部分用户登录频繁,但部分用户注册之后就难见踪影,因此,我们可以针对用户在时间段内的用户登录次数来进行判断,这样就可以分离出热点数据,将活跃用户与僵尸用户进行分离,在主要的操作的报表中只存活跃用户的数据。每次用户登录,先去主要的报表中查询记录,有的话就直接登录,没有再去查询其他的表。
5. 合并数据库操作
本方案的宗旨是减少数据库的操作次数,利用SQL语句的优化,将对数据的多次操作放到一次来进行,减少对数据库的操作,同时减少了在网络中的消耗,达到对数据库减负的作用。
6.数据库的读写分离
数据库的读写分离:
7.分布式数据库
数据库读写分离之后,无形中国增大了代码的复杂度,所以一般还需要借助分布式数据库中间件,这样可以有效提高数据库的弹性,可以方便随时为数据库进行扩容,同时也降低了数据库的耦合性。
RocketMQ
好的,下面我就介绍一下我的项目
我以技术点面对项目的模块的方式来进行讲解吧
第一个我要讲的是消息队列中的消息中间件RocketMQ:
1.是一个队列模型的消息中间件,具有高性能、高可靠、高实时、分布式特点。
2.其中它的Producer、Consumer队列都可以分布式。
3.Producer向一些队列轮流发送消息,队列集合称为 Topic,Consumer 如果做广播消费,则一个consumer实例消费这个Topic 对应的所有队列,如果做集群消费,则多个Consumer 实例平均消费这个topic对应的队列集合。(默认是集群消费)
4.能够保证严格的消息顺序(因为性能原因,不能保证消息不重复,因为总有网络不 可达的情况发生,需业务端保证)。
提供丰富的消息拉取模式
高效的订阅者水平扩展能力
实时的消息订阅机制
亿级消息堆积能力
较少的依赖
在选用消息中间件的过程中,比如我们用RocketMQ和kafaka对比
1.性能对比
Kafka单机写入TPS(吞吐量:单位时间处理请求的数量)约在百万条/秒,消息大小10个字节。
RocketMQ单机写入TPS单实例约7万条/秒,单机部署3个Broker,可以跑到最高12万条/秒,消息大小10个字节
2.消息投递实时性
Kafka使用短轮询方式,实时性取决于轮询间隔时间
RocketMQ使用长轮询,同Push方式实时性一致,消息的投递延时通常在几个毫秒。
3.消费失败重试
Kafka消费失败不支持重试
RocketMQ消费失败支持定时重试,每次重试间隔时间顺延
4.严格的消息顺序
Kafka支持消息顺序,但是一台Broker宕机后,就会产生消息乱序
RocketMQ支持严格的消息顺序,在顺序消息场景下,一台Broker宕机后,发送消息会失败,但是不会乱序。
5.定时消息
Kafka不支持定时消息
支持定时消息
简单的阐述一下RocketMQ的使用场景
1.异步处理
(1)以用户注册,并且需要注册邮件和短信为例,用户注册后,需要发送注册邮件和注册信息;相比传统的串行方式和并行方式,消息队列就会快很多,因为注册信息写入数据库后,数据库中的数据再写到消息队列中,发送消息和邮件两个异步读取,立即返回信息,这样效率就会提高很多;
2.应用解耦
(1)以用户下单购买业务为例
①订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。
②库存系统:订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操作。
③假如:在下单时库存系统不能正常使用。也不影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了。实现订单系统与库存系统的应用解耦。
3.流量削峰
(1)秒杀活动
①流量削峰也是消息队列中的常用场景,一般在秒杀或团抢活动中使用广泛。秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,需要在应用前端加入消息队列。
②可以控制活动的人数。
③可以缓解短时间内高流量压垮应用。
④用户的请求,服务器接收后,首先写入消息队列。假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面。
⑤秒杀业务根据消息队列中的请求信息,再做后续处理。
(2)以上实际是消息队列的两种消息模式,点对点或发布订阅模式。
4.RocketMQ的不足
(1)消息重复问题,它不能保证不重复,只能保证正常情况下不重复
(2)消息过滤功能扩展比较单一
(3)解决:消费端处理业务消息要保持幂等性,也就是同一个东西执行多次会得到相同结果,保证每条消息都有唯一编号切保证消息处理成功与去重表的日志同时出现。
RocketMQ消费分为push和pull两种方式,push为被动消费类型,pull为主动消费类型,push方式最终还是会从broker中pull消息。不同于pull的是,push首先要注册消费监听器,当监听器处触发后才开始消费消息,所以被称为“被动”消费。实际项目可能不止一个Topic,可设置多个一组生产者对应多个Topic,每个Topic对应各自的消费者,例如本人项目卡劵的领取和卡劵的核销,可分为两个Topic生产消息,根据需求设置各自的监听器消费消息,实现业务的解耦拆分。
多线程
8.多线程的实现方式
(1)继承Thread类
(2)实现Runnable接口
(3)使用ExecutorService、Callable、Future实现有返回结果的多线程。
9.如何正确的停止一线程
(1)停止一个线程意味着在任务处理完成之前停掉正在做的操作。停止一个线程可以用Thread。Stop()方法,但最好不要用它,虽然它确实可以停止一个正在运行的线程,但是这个方法是不安全的,而且已经被废弃的方法。
(2)在java中有以下三种方法可以终止正在运行的线程:
①使用退出标志,四线程正常退出,也就是当run方法完成后线程终止;
②使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的方法。
③使用interrupt方法中断线程。
10.判断一个线程是否停止状态
(1)This.interrupted():测试当前线程是否已经中断;
(2)This.isInterrupted();测试线程是否已经中断。
11.什么是线程安全?
(1)线程安全就是多线程访问同一代码不会产生不同的结果。
12.如何保证线程安全
(1)对非安全的代码进行加锁控制;
(2)使用线程安全的类;
(3)多线程并发的情况下,线程共享的变量改为方法级的局部变量。
13.Syschronized如何使用
(1)Syschronized是java中的关键字,是一种同步锁,它修饰的对象有以下几种:
①修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
②修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
③修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
④修饰一个类,其作用的范围是syschronized后面括号括起来的部分,作用的对象是这个类的所有对象。
14.Syschronized 和 Lock的区别
(1)相同点:lock能完成syschronized所实现的所有功能;
(2)不同点:lock有比syschronized更精确的线程语义和更好的性能。Lock的锁定是通过代码实现的,syschronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。Lock还有更强大的功能,例如,它的try Lock方法可以非阻塞方式拿锁。Lock锁的范围有局限性,块范围,而syschronized可以锁住块、对象、类。
15.多线程如何进行信息交互
(1)Void notify()唤醒在此对象监视器上等待的单个线程;
(2)Void notifyAll()唤醒在此对象监视器上等待的所有线程;
(3)Void wait() 导致当前的线程等待,直到其他线程调用此对象的notify()方法或notifyAll()方法或者超过指定的时间量;
(4)Void wait(long timeout,int nanos)导致当前的线程等待,直到其他的线程调用此对象的notify()方法或notifyAll()方法,或者其他某个线程中断这个当前线程,或者已超过某个实际时间量。
16.Sleep和wait的区别(考察的方向是是否会释放锁)
(1)Sleep()方法是Thread类中方法,而wait()方法是Object类中的方法;
(2)Sleep()方法导致了程序暂停执行指定的时间,让出cpu给其他线程,但是他的监控状态依然保持着,当指定的时间到了又会自动的恢复到运行状态,在调用sleep()方法的过程中,线程不会释放对象锁。而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池。只有针对此对象调用notify()方法后,本线程才会进入对象锁定池准备。
17.多线程与死锁
(1)死锁:指两个或两个以上的进程在执行过程中,因为争夺资源而造成的一种互相等待的现象,若无外力作用,他们都将无法推进下去,产生死锁的原因:
①因为系统的资源不足;
②进程运行推进的顺序不合适;
③资源分配不当。
18.如何才能产生死锁
产生死锁的四个必要条件:
(1)互斥条件:所谓互斥就是进程在某一时间内独占资源;
(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;
(3)不剥夺条件:进程已获得资源,在未使用完之前,不能强行剥夺;
(4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
19.打破产生死锁的四个条件中的一个或者几个,保证系统不会进入死锁状态
(1)打破互斥条件。就是允许进程同时访问这些资源。但是,有的资源是不允许被同时锁定的。
(2)打破不可抢占的条件。即允许进程强行从占有者哪里夺取某些资源。就是说,当一个进程已被占有了某些资源,它又申请新的资源,但不能立即被满足时,它必须释放所占用的全部资源,以后再重新申请。它所释放的资源可以分配给其他进程。这就相当于该进程占有的资源被隐蔽地满足时,它必须释放占用的全部资源,以后再重新申请。它所释放的资源可以分配给其他进程。这就相当于该进程占有的资源被隐蔽地强占了。
(3)打破占有且申请条件。可以私信资源预先分配策略。即进程在运行前一次性地向系统申请它所需要的全部资源。如果某个进程所需的全部资源得不到满足,则不分配任何资源,此进程暂不运行。只有当系统能够满足当前进程的全部需求时,才一次性地将所申请的资源全部分配给该进程。由于运行的进程已占有了它所需要的全部资源,所以不会发生占有又申请资源的现象,因此才不会发生死锁。
(4)打破循环条件,实行资源有序分配策略。采用这种策略,就是把资源事先分类编号,按号分配,使进程在申请,占有资源时不会形成环路。所有进程对资源的请求必须严格按资源序号递增的顺序提出。进程占用了小编号资源,才能申请大编号资源,就不会产生环路,预防了死锁。
20.什么叫守护线程,用什么方法实现守护线程
(1)守护线程是为其他线程的运行提供服务的线程。
(2)SetDaemon(boolean n)方法可以方便的设置线程的Daemon模式,true为守护模式,false为用户模式。
21.线程池的作用
(1)线程池的作用:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁的创建和销毁就会大大降低系统的效率,通过采用线程池,就可以使得线程复用,从而提高效率。
(2)线程池(ThreadPool):核心类(ThreadPoolExecutor)Executor(执行者)
22.实现线程的三种方式
(1)继承Thread
(2)实现Runnable
(3)实现Callable(这种方式有返回结果,可以用于查看线程的结束时间,从而实现异步操作)返回结果类型取决于泛型类型
23.Java中的NIO,BIO,AIO分别是什么
(1)BIO:同步并阻塞,服务器实现模式为一个连接线程,即客户端连接请求时服务端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必掏的线程开销,当然可以通过线程池机制改善。BIO方式适用于链接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4之前的唯一选择,但程序直观简单易理解。
(2)NIO:同步阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才能启动一个线程进行处理,NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
(3)AIO:异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理,AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。