目录
- 1 Spring
- Spring是什么?
- AOP的理解
- IOC的理解,如何实现一个IOC容器
- BeanFactory和ApplicationContext有什么区别?
- Spring如何使用三级缓存解决循环依赖的问题
- Spring Bean的生命周期
- Spring bean的作用域
- Spring 事务方式
- Spring 事务在哪些场景下会失效
- Spring 单例bean是线程安全的吗?
- Spring中有过哪些设计模式
- AOP代理模式
- Spring Boot听过吗?它是什么?SpringMVC?
- 2 mysql
- 面试被问 如果一个数据是树形结构,那么这个数据库表应该如何设计?
- mybatis优缺点
- #{} ${}区别
- mysql函数
- mysql 事务
- mysql锁的类型
- 索引的基本原理
- 创建索引的原则
- 索引的类型
- 创建索引的三种方式
- mysql索引的数据结构
- B+数索引原理
- 索引的使用及注意事项
- 使用索引查询一定能提高查询的性能吗?为什么
- 事物的基本特性和隔离级别
- 关心过sql耗时嘛?统计过慢查询嘛?怎么优化慢查询?
- MVCC
- mysql主从同步
- 3 Tomcat/ngix/
- Tomcat的缺省端口是多少,怎么修改?
- Tomcat有几种部署方式?
- tomcat容器是如何创建servlet类实例?用到了什么原理?
- 内存调优
- Nginx负载均衡
- 4 Redis
- redis持久化
- 过期建的删除策略
- redis事务
- 集群方案
- 主从复制
- 缓存/特点/解决了什么问题
- 本地缓存、分布式缓存的区别
- java中实现序列化接口的意义
- 如何利用mybatis自身本地缓存结合redis实现分布式缓存
- Zookeeper
- 分布式锁的解决方案
- zab协议
1 Spring
Spring是什么?
轻量级开源j2ee框架。他是一个容器框架,用来装javabean(java对象),亦是一个中间层框架,可以连接其他的框架。
Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器框架。
- 从大小和开销两方面都是轻量级的。(对Spring类/接口的依赖程度很低)
- 通过控制反转来达到松耦合的目的,解决对象之间强依赖。
- 提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统服务进行内聚的开发。
- 包含并管理应用对象bean的配置和生命周期,该意义上是个容器。
- 将简单的组件配置,组合成复杂的应用,这个是真正框架的意义。(mybatis/redis)
AOP的理解
系统是由很多不同的组件所组成的,每一个组件各负责一块特定的功能。每个组件除了实现自己的核心逻辑之外,可能还需要承担额外的职责。比如增删改等接口/还需打印日志,事务管理等核心服务。这些服务经常被称为横切关注点。
跟aop相对的就是oop,oop面向对象就是要分析一个需求事件中有哪些参与者,每个对象需要做什么(有哪些方法)。当oop中存在大量代码的重复(如日志/事务等管理),就不利于各个模块的重用。
AOP:将程序中交叉的业务逻辑,封装成一个切面,然后注入目标对象(具体的业务逻辑中),aop可以对某个对象或某些对象的功能进行增强,比如方法的增强可以在m某个方法之前额外做一些事情/或之后额外做一些事情。
IOC的理解,如何实现一个IOC容器
IOC理解:
容器概念,控制反转,依赖注入。
**IOC容器:**实际上就是一个map(k,v),里面存储的是各种对象(在xml里配置的bean节点、@repository(dao层)@service(service层)@controller(web层)@component(类上),在项目启动时会读取配置文件里的bean节点。根据全限定类名使用反射创建对象在map里,扫描到打上上述注解的类通过反射c创建对象放入map中。在使用这些对象时,通过依赖注入(autowired/resource等注解)读取xml文件中bean的属性,根据id注入。
**控制反转:**在没有引用IOC之前,对象A依赖于对象B,那么对象A在初始化时,或运行时必须自己主动创建B对象,或者使用已经创建的B对象。此时无论是使用B对象还是创建B对象,主动权都在自己手上。引入IOC后,AB失去了联系,当A需要B时,IOC会主动创建一个B注入到对象A需要的地方。这种主动获取变成被动控制权调到了过来,这就是控制反转的由来。
依赖注入:
获得依赖对象的过程被反转了。控制反转之后,获得依赖对象的过程由自身管理变成了由IOC容器依赖注入。
依赖注入就是实现IOC的方法,即IOC容器在运行期间,动态的将对象注入到容器中。
- 配置文件(包扫描路径)、递归包路径下所有class文件、反射确定哪些class类需要交给ioc实现、对需要注入的类进行依赖注入。
BeanFactory和ApplicationContext有什么区别?
BeanFactory:是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能。
ApplicationContext是应用上下文,BeanFactory的子接口,它是Spring的一各更高级的容器,提供了更多的有用的功能:
- 国际化(继承MessageSource)
- 可同时加载多个配置文件
- 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
- 提供在监听器中注册bean的事件。
- BeanFactory采用延迟加载的形式来注入Bean的,并不是一开始就创建好了,只有在使用到某个bean(调用getbean方法)才会进行实例化。那么如果一个bean的某一个属性没有注入,beanfactory加载后,直至第一次使用时错误才会发现。
- ApplicationContext是在容器启动时一次性创建所有的bean,这样在容器中启动时就能发现异常来处理。
- 那么所以ApplicationContext会更占用内存空间,当应用程序配置bean较多时程序启动较慢。
- beanfactory通常以编程的方式去创建,代码。Applicationcontext还可以以声明的方式去创建。
Spring如何使用三级缓存解决循环依赖的问题
Spring Bean的生命周期
首先解析类得到BeanDefinition,如果有多个构造方法,进行实例化得到一个对象。确定好构造方法后,进行实例化得到一个对象。对对象中加入了Autowared注解的属性进行填充,最后如果bean有实现aware接口则会回调Aware方法,如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization方法。以上工作完成以后就可以应用这个Bean了,那这个Bean是一个Singleton的。当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用那个其实现的destroy()方法;最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法
Spring bean的作用域
<bean id="" class="" scope=""></bean>
当scope的取值为singleton时:
Bean的实例化个数:1个
Bean的实例化时机:当Spring核心文件被加载时,实例化配置的Bean实例
Bean的生命周期: 对象创建:当应用加载,创建容器时,对象就被创建了 对象运行:只要容器在,对象一直活着 对象销毁:当应用卸载,销毁容器时,对象就被销毁了
当scope的取值为prototype时
Bean的实例化个数:多个
Bean的实例化时机:当调用getBean()方法时实例化
Bean Bean的生命周期:对象创建:当使用对象时,创建新的对象实例 对象运行:只要对象在使用中,就一直活着 对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了
Spring 事务方式
Spring有两种使用事务的方式,一种是编程式,一种是声明式。
**编程式:**调用方法 begin 开启 rollback回滚。
声明式:@Transaction注解就是声明式。当在某个方法上加上注解,就可以开启事务,这个方法中的sql都会在一个事务中执行,统一失败或成功。
内部原理大概是:当被声明@Transaction注解时,spring会基于该类生成一个代理对象,此时代理逻辑会先把事务的自动提交设置为false,然后去执行原本的业务逻辑方法。如果没有异常则提交,出现异常(一定要抛出异常)则回滚。
Spring的隔离级别就是数据库的隔离级别。
Spring 事务在哪些场景下会失效
Spring事务的原理是AOP,进行切面增强。所以失效的原理也是AOP不起作用了。
常见的情况有:
- 发生自调用,类里面使用this调用本类的方法,此时调用方法时对象本身而不是代理对象,就不会走代理逻辑。(解决:所有的调用都用Autowared注入去调用)
- 方法不是public的 @Transactional只能用于public的方法。只适合于外部调用
- 数据库不支持事务,spring事务是基于数据库事务的。
- 没有被Spring管理,方法加了事务注解,但是类没有放入IOC容器中(没有加注解)。
- 异常被catch,没有抛出异常,事务就不会回滚。
Spring 单例bean是线程安全的吗?
不是安全的,Spring中的bean默认是单例的,框架并没有对bean进行多线程的封装处理。
如果bean是有状态的,那就需要开发人员自己进行线程安全的保证,最简单的办法就是改变bean的作用域,把singleton改为protopyte,这样每次请求一个bean就是一个新对象。
所以一般不在bean中声明任何有状态的变量(如count计数),如果非要存,那么就可以使用treadlocal把变量变为线程私有的,如果bean中的变量需要在多个线程之间共享,那么就需要使用synchronized,lock,cas等方法实现线程同步。
Spring中有过哪些设计模式
工厂设计模式:有工厂类根据传入的参数,动态决定应该创建啊一个产品类(BeanFactory)
单例设计模式:保证一个类仅有一个实例,并提供一个访问它的全局访问
// 饿汉式单例
public class Singleton1 {
// 私有构造
private Singleton1() {}
private static Singleton1 single = new Singleton1();
// 静态工厂方法
public static Singleton1 getInstance() {
return single;
}
}
// 懒汉式单例
public class Singleton2 {
// 私有构造
private Singleton2() {}
private static Singleton2 single = null;
public static Singleton2 getInstance() {
if(single == null){
single = new Singleton2();
}
return single;
}
}
AOP代理模式
/**
* 接口
*/
public interface IUserDao {
void save();
}
/**
* 接口实现
* 目标对象
*/
public class UserDao implements IUserDao {
public void save() {
System.out.println("----已经保存数据!----");
}
}
静态代理:静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类.
优点:可以做到在不修改目标对象的功能前提下,对目标功能扩展.
缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.
/**
* 代理对象,静态代理
*/
public class UserDaoProxy implements IUserDao{
//接收保存目标对象
private IUserDao target;
public UserDaoProxy(IUserDao target){
this.target=target;
}
public void save() {
System.out.println("开始事务...");
target.save();//执行目标对象的方法
System.out.println("提交事务...");
}
}
动态代理:有以下特点:
1.代理对象,不需要实现接口
2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
3.动态代理也叫做:JDK代理,接口代理
/**
* 创建动态代理对象
* 动态代理不需要实现接口,但是需要指定接口类型
*/
public class ProxyFactory {
//维护一个目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//给目标对象生成代理对象
public Object getProxyInstance() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始事务2");
//运用反射执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务2");
return returnValue;
}
}
);
}
}
Spring Boot听过吗?它是什么?SpringMVC?
Spring是一个IOC容器,用来管理Bean,使用依赖注入实现控制反转,可以很方便的整合各种框架,提供AOP机制弥补oop代码重复问题,更方便将不同类不同方法中的共同处理抽取成切面,自动注入方法去执行。
SpringMVC是spring对web框架的一个解决方案,提供了一套前端控制器servlet,用来接收请求。然后定义了一套路由策略(url到handle的映射),及shibeizhixinghandle,将handle结果使用视图技术来展示给前端。
Springboot是spring提供的一个快速开发工具包。让程序员能更方便更快速的开发spring+springmvc应用,简化了配置(约定了默认配置),整合了一系列的解决方案。
2 mysql
面试被问 如果一个数据是树形结构,那么这个数据库表应该如何设计?
一开始没懂树形结构的数据是什么意思,后来理解了一些,但是还是没回答太好。
查资料,链接:
程序设计过程中,我们常常用树形结构来表征某些数据的关联关系,如企业上下级部门、栏目结构、商品分类等等,通常而言,这些树状结构需要借助于数据库完成持久化。然而目前的各种基于关系的数据库,都是以二维表的形式记录存储数据信息,因此是不能直接将Tree存入DBMS,设计合适的Schema及其对应的CRUD算法是实现关系型数据库中存储树形结构的关键。
举个例子,食品族谱如下:
对树形结构最直观的分析莫过于节点之间的继承关系上,通过显示地描述某一节点的父节点,从而能够建立二维的关系表,则这种方案的Tree表结构通常设计为:{Node_id,Parent_id},上述数据可以描述为如下图所示:
这种方案的优点很明显:设计和实现自然而然,非常直观和方便。缺点当然也是非 常的突出:由于直接地记录了节点之间的继承关系,因此对Tree的任何CRUD操作都将是低效的,这主要归根于频繁的“递归”操作,递归过程不断地访问数据库,每次数据库IO都会有时间开销。当然,这种方案并非没有用武之地,在Tree规模相对较小的情况下,我们可以借助于缓存机制来做优化,将Tree的信息载入内存进行处理,避免直接对数据库IO操作的性能开销。
基于左右值编码的设计
第一次看见这种表结构,相信大部分人都不清楚左值(Lft)和右值(Rgt)是如何计算出来的,而且这种表设计似乎并没有保存父子节点的继承关系。但当你用手指指着表中的数字从1数到18,你应该会发现点什么吧。对,你手指移动的顺序就是对这棵树进行前序遍历的顺序,如下图所示。当我们从根节点Food左侧开始,标记为1,并沿前序遍历的方向,依次在遍历的路径上标注数字,最后我们回到了根节点Food,并在右边写上了18。
树形结构CRUD算法
1)获取某节点的子孙节点
只需要一条SQL语句,即可返回该节点子孙节点的前序遍历列表,以Fruit为例:
SELECT * FROM tree WHERE lft BETWEEN 2 AND 11 ORDER BY lft ASC
查询结果如下所示:
等等,此处就不在赘述…
mybatis优缺点
优点:
- 基于sql语句编程,sql写在xml里,解除了sql与程序代码的耦合,便于统一管理。支持编写动态sql,并可重用。
- 与JDBC相比,减少了大量冗余代码,不需要手动开关连接。
- 能够与各种数据库兼容,mybatis使用jdbc来连接数据库,支持jdbc就支持mybatis
- 能与Spring很好的集成
缺点: - sql语句编写工作量交大,尤其是字段多、关联表多时。
- sql语句依赖数据库(不同数据库函数不识别),导致数据库移植性差,不能随意更换数据库。
#{} ${}区别
#{}是预编译处理,是占位符,mybatis在处理时会将sql中#{}替换成?号,再去调用preparedstatement来赋值。预编译呢就会提前对sql进行编译,在替换后就不再对sql进行编译了,就能预防sql注入的问题。
{}替换成变量的值,没有预编译。
mysql函数
函数 | 描述 |
CHAR_LENGTH(s) | 返回字符串 s 的字符数 |
CONCAT(s1,s2…sn) | 字符串 s1,s2 等多个字符串合并为一个字符串 |
FORMAT(x,n) | 函数可以将数字 x 进行格式化 “#,###.##”, 将 x 保留到小数点后 n 位,最后一位四舍五入。 |
INSERT(s1,x,len,s2) | 字符串 s2 替换 s1 的 x 位置开始长度为 len 的字符串 |
LEFT(s,n) | 返回字符串 s 的前 n 个字符 |
LOWER(s) | 将字符串 s 的所有字母变成小写字母 |
mysql 事务
在MySQL中,默认情况下,事务是自动提交的,也就是说,只要执行一条DML语句就开启了事物,并且提交了事务
mysql锁的类型
基于锁的属性分为:共享锁,排他锁;
- 共享锁:又称之为读锁,S锁。当一个事务为数据加上读锁之后,其他事务只能对该数据加读锁,不能加写锁。其意义在于读取数据时支持并发,此时不支持修改,避免出现重复读的问题。
- 排他锁:写锁,x锁。当一个事务为数据加上写锁时,其他的请求不能再加任何锁。不允许他人修改,也不允许他人读取。避免出现脏读和脏数据的问题。
- 表锁:锁住整个表,粒度大,加锁简单,但是容易冲突。
- 行锁:锁住某一行或多行,粒度小,加锁麻烦,不容易冲突,支持的并发要高于表锁。
- 记录锁:也属于行锁的一种,范围是一行一条记录,命中的条件字段是唯一索引。避免数据在查询时重复读的问题/脏读问题。
- 页锁:
- 间隙锁:查1,3,4,会锁住234,遵循左开右闭。
- 临建锁:查1,3,4,会锁住1234。
- 意向共享锁:
索引的基本原理
索引的原理:无序变有序。
创建索引的原则
- 较频繁作为查询条件的字段才去创建索引
- 适合索引的列是出现在where子句中的列,或者连接子句中指定的列
- 基数较小的类,索引效果较差,没有必要在此列建立索引
- 使用短索引,如果对长字符串列进行索引,应该指定一个前缀长度,这样能够节省大量索引空间。
- 不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进行更新甚至重构,索引列越多,这个时间就会越长。所以只保持需要的索引有利于查询即可。
- 更新频繁字段不适合创建索引
- 定义有外键的数据列一定要建立索引。
- 若是不能有效区分数据的列不适合做索引列
- 尽量的扩展索引,不要新建索引。
- 对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。
- 对于定义为text、image和bit的数据类型的列不要建立索引。
索引的类型
- UNIQUE(唯一索引):不可以出现相同的值,可以有NULL值
- INDEX(普通索引):允许出现相同的索引内容
- PROMARY KEY(主键索引):不允许出现相同的值
- fulltext index(全文索引):可以针对值中的某个单词,但效率确实不敢恭维
- 组合索引:实质上是将多个字段建到一个索引里,列值的组合必须唯一
创建索引的三种方式
创建索引
-- 第一种方式:在执行CREATE TABLE时创建索引
CREATE TABLE tb_name(id INT,
name VARCHAR(20),
sex BOOLEAN,
INDEX(id)
);
-- 第二种方式:使用CREATE INDEX语句对表增加索引
CREATE INDEX index_name ON table_name(username(length));
-- 第三种方式:使用ALTER TABLE语句创建索引:
ALTER TABLE 表名 ADD 索引类型 (unique,primary key,fulltext,index)[索引名](字段名)
删除索引
删除索引可以使用ALTER TABLE或DROP INDEX语句来实现。
drop index index_name on table_name ;
alter table table_name drop index index_name ;
alter table table_name drop primary key ;
其中,在前面的两条语句中,都删除了table_name中的索引index_name。而在最后一条语句中,只在删除PRIMARY KEY索引中使用,因为一个表只可能有一个PRIMARY KEY索引,因此不需要指定索引名。如果没有创建PRIMARY KEY索引,但表具有一个或多个UNIQUE索引,则MySQL将删除第一个UNIQUE索引。
如果从表中删除某列,则索引会受影响。对于多列组合的索引,如果删除其中的某列,则该列也会从索引中删除。如果删除组成索引的所有列,则整个索引将被删除。
mysql索引的数据结构
索引的数据结构与具体存储引擎的实现有关。
-- 查看索引
show index on tb;
对于哈希索引,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询时,就会选择哈希索引。根据哈希算法一次就可以找到数据的地址链。等值查询,哈希有绝对优势,一次哈希便可找到。但是如果是范围检索,哈希后原先有序的键值可能变得无序了,这就没意义了。
CREATE INDEX hash_id using hash ON table_name (column_list);
其余大部分场景都会使用B+数索引。
-- 没指定默认使用B树索引
CREATE INDEX index_name ON table_name (column_list)
B+数索引原理
树的遍历方式 前序 后续 中序
(1)二叉树简介:
二叉查找树也称为有序二叉查找树,满足二叉查找树的一般性质,是指一棵空树具有如下性质:
1、任意节点左子树不为空,则左子树的值均小于根节点的值;
2、任意节点右子树不为空,则右子树的值均大于于根节点的值;
3、任意节点的左右子树也分别是二叉查找树;
4、没有键值相等的节点;
上图为一个普通的二叉查找树,按照中序遍历的方式可以从小到大的顺序排序输出:2、3、5、6、7、8。
时间复杂度logn。一亿个树 大概28次就能查到 log2 1亿 。
(2)局限性及应用
一个二叉查找树是由n个节点随机构成,所以,对于某些情况,二叉查找树会退化成一个有n个节点的线性链。时间复杂度o(n)如下图
(3)AVL树
AVL树是带有平衡条件的二叉查找树,一般是用平衡因子差值判断是否平衡并通过旋转来实现平衡,左右子树树高不超过1,和红黑树相比,它是严格的平衡二叉树,平衡条件必须满足(所有节点的左右子树高度差不超过1).不管我们是执行插入还是删除操作,只要不满足上面的条件,就要通过旋转来保持平衡,而旋转是非常耗时的,由此我们可以知道AVL树适合用于插入删除次数比较少,但查找多的情况。
(4)红黑树
一种二叉查找树,但在每个节点增加一个存储位表示节点的颜色,可以是red或black. 通过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其它路径长出两倍.它是一种弱平衡二叉树(由于是弱平衡,可以推出,相同的节点情况下,AVL树的高度低于红黑树),相对于要求严格的AVL树来说,它的旋转次数变少,所以对于搜索,插入,删除操作多的情况下,我们就用红黑树.
性质:
- 每个节点非红即黑.
- 根节点是黑的。
- 不可能有连在一起的两个红色节点。
- 每个叶节点(叶节点即树尾端NUL指针或NULL节点)都是黑的.
- 如果一个节点是红的,那么它的两儿子都是黑的
- 对于任意节点而言,其到叶子点树NIL指针的每条路径都包含相同数目的黑节点.
- 所有插入的点默认为红色
红黑树为什么不能用于mysql索引呢? - 树高度很高,
- 红黑树的节点在磁盘上不是紧挨着的,当磁盘io读取数据时,一页存了一个点的数据。非常消耗磁盘IO。
如何解决呢? 内存一页存多个数据。就变成多叉树了。
(5)B树的性质
BTree树即B-Tree,基于红黑树进化来的。
重要特性:
- 节点最多含有M棵子树,m-1个关键字(关键字就是存储的数据)m>=2
- 除了根节点和叶子节点之外,其他节点至少有ceil(m/2)个子树。
- 若根节点不是叶子节点,则至少有两棵子树
Btree的非常重要的操作–分裂–来保持上述特性
每一个节点对应一个硬盘地址,来存数据库中的记录。
那么这样的结构存在着问题:
索引空间(带有mysql数据地址)占用空间,此时是索引+数据。
范围查照时还是会遍历。
B+Tree出现了:
- 叶子节点连起来了(双向链表),用来解决范围查找。排好序的双向链表。
- 非叶子节点不存数据。
- 数据和节点一样多。当前节点中最大的数字,会存放到下一节点
多个索引组合时,B+Tree是怎么样的呢?
此时遵循最左原则。id+name+age会只有一颗B树。会根据id来进行查找,所以如果不带最左边索引时,树中的索引就是失效了,只会走叶结点的链表索引(使用explain时 index=all 扫描所有的索引,不会扫描全表)。 比如条件只用 name+age。
阶数如何算出来的?
是根据页数算出来的,索引列字段大小
索引的使用及注意事项
执行计划:EXPLAIN可以帮助开发人员分析SQL问题,explain显示了mysql如何使用索引来处理select语句以及连接表,可以帮助选择更好的索引和写出更优化的查询语句。
Explain select * from user where id=1;
-- 尽量避免这些不走索引的sql:
SELECT `sname` FROM `stu` WHERE `age`+10=30;-- 不会使用索引,因为所有索引列参与了计算
SELECT `sname` FROM `stu` WHERE LEFT(`date`,4) <1990; -- 不会使用索引,因为使用了函数运算,原理与上面相同
SELECT * FROM `houdunwang` WHERE `uname` LIKE'后盾%' -- 走索引
SELECT * FROM `houdunwang` WHERE `uname` LIKE "%后盾%" -- 不走索引
执行计划的参数:
- id:有顺序的编号,有几个select就有几个id,id值越大执行优先级越高越先执行。相同则从上往下执行,null最后执行。
- selecttype 表示select字句的类型。
- table 表
- type 优化sql重要的字段:
const:通过索引一次命中,匹配一行数据。(id=1,只传入了一行数据)
system:表中只有一行记录。
eq_ref:使用了唯一性索引,对于每个索引键,表中只有一条记录与之匹配。
ref:非唯一性的索引扫描,多个值返回。
range:(通常出现在between时候,<,>),多个索引。
index:遍历索引书;效率极低
ALL:全表扫描。 - possible_keys:可能是用的索引名字
- key:使用的索引的名字。是possible_keys的子集。
- key_len:表示优化器使用索引的字节数,评估索引是否被完全使用。
- rows:扫描读取的行数,扫描读取的行数越多。说明索引设置不对。
- filtered:百分比,读取行数和返回行数的百分比。越高越好。效率高。
- Extra:
using filesort:外部排序,没有使用内部索引排序。建议优化。
using index:覆盖索引扫描,表示在索引树中就能查找到所需的数据。说明性能不错。
using temporary:查询时有临时表,分组,多表join,查询效率不高。
using where:sql使用where过滤,效率高。
使用索引查询一定能提高查询的性能吗?为什么
通常,通过索引查询数据比全表扫描要快。但是我们也必须注意到它的代价。
索引需要空间来存储,也需要定期维护, 每当有记录在表中增减或索引列被修改时,索引本身也会被修改。 这意味着每条记录的INSERT,DELETE,UPDATE将为此多付出4,5 次的磁盘I/O。 因为索引需要额外的存储空间和处理,那些不必要的索引反而会使查询反应时间变慢。
使用索引查询不一定能提高查询性能,索引范围查询(INDEX RANGE SCAN)适用于两种情况:
- 基于一个范围的检索,一般查询返回结果集小于表中记录数的30%
- 基于非唯一性索引的检索
事物的基本特性和隔离级别
基本特性,ACID:
原子性:一个事务中的操作要么全部成功,要么全部失败。
一致性:数据库总是从一个一致性的状态转换到另一个状态。事务的最终的目的。
隔离性:一个事物的修改在最终提交前,对其他事务是不可见的。
持久性:事务一旦被提交,所做的修改就是永久保存在数据库中。
靠什么来保证ACID呢?
隔离性有四个隔离级别:
- read uncommit 读未提交,可能会读到其他事务未提交的数据。脏读。
- read commit 读已提交,两次读取结果不一致,叫做不可重复读。先读了一个数,这时这个数被改了,再读的时候发生了变化。Orical采用的级别。
- repeatable read 可重复读,mysql默认级别。但是可能产生幻读:一个事务(同一个read view)在前后两次查询同一范围的时候,后一次查询看到了前一次查询没有看到的行。(行锁只能锁住行,即使把所有的行记录都上锁,也阻止不了新插入的记录)。此时可以加入间隙锁来组织新纪录的插入。
- serializable 串行化,一般不使用,给每行读取的数据枷锁。
关心过sql耗时嘛?统计过慢查询嘛?怎么优化慢查询?
MVCC
MVCC:多版本并发控制,读取数据时通过一种类似快照的方式将数据保存下来。这样读锁和写锁就不冲突了。只能看到自己版本的数据。
mysql主从同步
- 主节点binlog日志,该日志是从数据库启动那一刻开始,保存所有修改数据库结构或内容的文件。
- 主线程log dump线程,
- 从节点IO县城接受bin’log内容,并将其写入到relaylog文件中。
- 从节点sql县城读取relaylog文件内容对数据进行更新,最终保持h数据库一致性。
- 每次都是增量同步。
3 Tomcat/ngix/
Tomcat的缺省端口是多少,怎么修改?
修改tomcat目录下conf文件夹下server.xml文件,修改<Connector connectionTimeout=“20000” port=“8080” protocol=“HTTP/1.1”。其中port="8080"便是端口号。
Tomcat有几种部署方式?
1)直接把Web项目放在webapps下,Tomcat会自动将其部署
2)在server.xml文件上配置节点,设置相关的属性即可
3)通过Catalina来进行配置:进入到conf\Catalina\localhost文件下,创建一个xml文件,该文件的名字就是站点的名字。
tomcat容器是如何创建servlet类实例?用到了什么原理?
当容器启动时,会读取在webapps目录下所有的web应用中的web.xml文件,然后对xml文件进行解析,并读取servlet注册信息。然后,将每个应用中注册的servlet类都进行加载,并通过反射的方式实例化。
(有时候也是在第一次请求时实例化)在servlet注册时加上如果为正数,则在一开始就实例化,如果不写或为负数,则第一次请求实例化。
内存调优
内存方式的设置是在catalina.sh中,调整一下JAVA_OPTS变量即可,因为后面的启动参数会把JAVA_OPTS作为JVM的启动参数来处理。
具体设置如下:
JAVA_OPTS="$JAVA_OPTS -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4"
其各项参数如下:
-Xmx3550m:设置JVM最大可用内存为3550M。
-Xms3550m:设置JVM促使内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g:设置年轻代大小为2G。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:MaxPermSize=16m:设置持久代大小为16m。
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
Nginx负载均衡
轮询、轮询是默认的,每一个请求按顺序逐一分配到不同的后端服务器,如果后端服务器down掉了,则能自动剔除。
ip_hash、个请求按访问IP的hash结果分配,这样来自同一个IP的访客固定访问一个后端服务器,有效解决了动态网页存在的session共享问题。
weight、weight是设置权重,用于后端服务器性能不均的情况,访问比率约等于权重之比。
fair(第三方)、这是比上面两个更加智能的负载均衡算法。此种算法可以依据页面大小和加载时间长短智能地进行负载均衡,也就是根据后端服务器的响应时间来分配请求,响应时间短的优先分配。Nginx本身是不支持fair的,如果需要使用这种调度算法,必须下载Nginx的upstream_fair模块。
url_hash(第三方)此方法按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,可以进一步提高后端缓存服务器的效率。Nginx本身是不支持url_hash的,如果需要使用这种调度算法,必须安装Nginx 的hash软件包。
4 Redis
redis持久化
- RDB
redis database,指在指定的时间间隔内将内存的数据集快速写入磁盘,实际操作过程是fork一个子进程,先将书局街写入临时文件,写入成功后再替换之前的文件,采用二进制压缩。
优点:
整个redis数据库只包含一个文件,dumo.rdb方便持久化。
方便备份。
性能最大化,fork子进程来完成操作,让主进程继续处理数据。
缺点:
数据安全性低,间隔时间段的数据就会丢失。 - AOF
Append Only File
以日志的形式记录服务器所处理的每一个写、删除操作。查询操作不会记录,以文本的方式记录,可以打开文件查看。
优点:
数据安全,提供同步策略:每秒/每修改/不同步。每修改绝对能保证数据的一致性。
通过append模式写文件。
aof的rewrite模式,会定期将aof文件进行重写,对文件命令进行合并。
缺点:
aof文件比rdb大,恢复速度慢。
运行效率没有rdb高。
如果rdb和aof都配置了,优先加载aof。
过期建的删除策略
**惰性过期:**只有当访问一个key,才会判断该key是否已经过期。过期则清楚。该策略可以最大化节省cpu的资源,对内存不友好。极端情况可能出现大量的过期key没有被访问,从而不会被清除,占用大量内存。
**定期过期:**每隔一定时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已经过期的kkey,该策略是前两者的一个折中方案。
**定时过期:**设定过期定时器,去监控每一个对象。过期就会被删除,对内存友好,对cpu不友好。
redis事务
WATCH命令是一个乐观锁,为redis事务提供cas行为。可以监控一个或多个键,一旦有其中一个键被修改(或删除),之后的事务就不会执行,监控一直持续到exec。
MULTI开启事务,他总是返回ok。
exec执行所有事务块内的命令。返回事务快内所有命令的返回值。当操作被打断时,返回nil值。
DISCARD可以使客户端清空事务队列,并放弃执行事务。
UnWATCH解除对事物的监控。
- 事务的开启
MULTI命令的执行,标识着事务的开始,将客户端flag属性中REDIS_MULTI打开。 - 命令入队
当一个客户端切换到事务状态后,服务器会根据客户端发送的命令来执行不同的操作。如果发送的是MULTI/EXEC/WATCH/DISCARD中的一个,则立即执行这个命令。否则就会放入队列,此时只检查语法有没有问题。如果执行中发生错误,则会全部执行完。
事务队列是按照先进先出的方式来入队。 - 事务执行
客户端发送EXEC,服务器执行EXEC命令。
集群方案
- 哨兵模式
- 集群监控:负责监控master和slave进程是否正常工作。
- 消息通知:如果某个redis实例有故障,那么哨兵负责发送消息给管理员。
- 故障转移:如果master node挂掉了,会自动转移到slave node上
- 配置中心:如果故障转移发生了,通知client客户端新的master地址。
- Redis Cluster
- 通过hash方法将数据分片,16384个哈希槽。
- 每份数据分片会存储在互为主从的多个节点上。
- 数据写入先写入主节点,在同步到从节点。
每个节点都开放两个端口,一个是6379,一个是+1w(16379)用来节点通信。
只能使用0号数据库,不支持批量操作。
主从复制
全量复制:
主节点通过bgsave,fork出一个子进程,生成一个rdb快照。该过程非常消耗cpu,硬盘io。
主节点通过网络将rdb发送给从节点。
从节点清空老数据,载入新的rdb文件,此过程是阻塞的,无法响应客户端命令。
增量复制:
复制偏移量:主从节点分别维护了一个复制偏移量offset。
复制积压缓冲区:主节点内部维护了一个固定长度的、先进先出的复制积压缓冲区。偏移量后的数据会先写入缓冲区。当主从节点的偏移量大于缓冲区长度时,会进行全量复制。
服务器运行id:
缓存/特点/解决了什么问题
缓存即使计算机内存中的一段数据,因为在内存中,具有读写快,断电立即丢失的特点。
缓存解决了:
- 1.提高网站吞吐量提高网站运行效率
- 2.核心解决:减轻了数据库的访问压力。
既然缓存可以提效,所有数据都加入缓存不是更好?
使用缓存时,一定是数据库中的数据极少发生修改,更多用于查询情况。若缓存经常变化的数据,一旦数据发生变化,需要对缓存清空或者更新处理,这样会极大延误缓存的效率。
本地缓存、分布式缓存的区别
本地缓存:存在应用服务器内存中数据称之为本地缓存 local cache
分布式缓存:存储在当前服务器内存之外的数据称之为分布式缓存 distribute cache
java中实现序列化接口的意义
什么是java序列化?
序列化是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据。
如何实现java序列化?
需要被序列化的类必须实现Serializable接口。一个接口里面什么内容都没有,我们可以将它理解成一个标识接口。在Java中的这个Serializable接口其实是给jvm看的,通知jvm,我不对这个类做序列化了,你(jvm)帮我序列化就好了。
目的意义
序列化是一种用来处理对象流的机制 ,所谓对象流就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来,这是java中的提供的保存对象状态的机制—序列化。
实现序列化接口的数据,在http传输时才可以被以流的方式传输
如何利用mybatis自身本地缓存结合redis实现分布式缓存
首先在mapper.xml中加入标签,此时开启二级缓存(sqlsessionFactory),所有会话共享。查询对象必须实现序列化接口,否则直接会报错。此时本地缓存占用了服务器的资源,且当前jvm结束后缓存就消失了,下一次重新查询后才能生成缓存。
集群分布式缓存的实现
标签,cache接口中,mybatis默认使用perpetualCache类来实现cache接口。底层是一个Map<obj,obj>来实现。所以当使用集群来实现分布式缓存时,需要改写其中的put/get方法使用redis来实现。
- 新建cache包,Rediscache类(必须有带有id参数的构造方法(id为当前缓存的mapper的namespace)/getId方法的返回值不能为空"name 不能为空”)
- 重新对cache标签赋值:
<cache type="RedisCache“> - 重写put/get方法,put至redis。此时rediscache类并不是由工厂实例化的,就不能直接注入RedisTemplate,那么此时需要获取工厂(基于工具类)。如下:
此时就可通过工具类来获取RedisTemplate,put方法如下:
get方法如下:
此时数据放入redis中,即使jvm关闭,缓存中数据也存在。
增删改如何修改缓存呢,只要发生增删改的任意一个方法,默认都是调用clean,清空缓存。
最后重写getsize方法,用来计算缓存数量。返回值为long,需要转换为int。
Zookeeper
分布式锁的解决方案
- 数据库来控制
- zookeeper分布式锁
zab协议
zab协议是分布式协调服务zookeeper专门设计的一种支持崩溃恢复的原子广播协议。所有客户端请求都是写入Leader进程中,然后由Leader同步到其他节点,称之为foolower。在集群中,如果出现leader崩溃和follower崩溃,都会通过zab协议来保持数据一致性。
zab协议包括两种基本模式:崩溃恢复和消息广播。
消息广播:
集群中所有请求都由Leader来处理,Leader将客户端的事务请求转换为事务proposal,并且将proposal分发到集群中其他所有follower。
完成广播后,leader等待follower反馈。收到半数反馈后,Leader就会再广播一个commit信息,将之前的事务进行提交。
崩溃恢复:
Leader崩溃,失去半数机器支持,与集群中超过一半的节点断开连接。
此时开始新一轮Leader选举:选举产生的Leader会与过半的follower进行同步。使数据一致,与半数机器同步完成后,退出恢复模式,进行消息广播模式。
此时是不可用。放弃了高可用。
**zxid:**是zab的一个事务编号,单调递增计数器。客户端每一个事物请求,计数器加1.
当选出一个新Leader时,从leader服务器上取出本地日志中最大事务idzxid,然后+1。