目录

  • 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的一各更高级的容器,提供了更多的有用的功能:

  1. 国际化(继承MessageSource)
  2. 可同时加载多个配置文件
  3. 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
  4. 提供在监听器中注册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的作用域

Java 通过当前用户id递归寻找下级用户_面试

<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的隔离级别就是数据库的隔离级别。

Java 通过当前用户id递归寻找下级用户_目标对象_02

Spring 事务在哪些场景下会失效

Spring事务的原理是AOP,进行切面增强。所以失效的原理也是AOP不起作用了。
常见的情况有:

  1. 发生自调用,类里面使用this调用本类的方法,此时调用方法时对象本身而不是代理对象,就不会走代理逻辑。(解决:所有的调用都用Autowared注入去调用)
  2. 方法不是public的 @Transactional只能用于public的方法。只适合于外部调用
  3. 数据库不支持事务,spring事务是基于数据库事务的。
  4. 没有被Spring管理,方法加了事务注解,但是类没有放入IOC容器中(没有加注解)。
  5. 异常被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算法是实现关系型数据库中存储树形结构的关键。

举个例子,食品族谱如下:

Java 通过当前用户id递归寻找下级用户_目标对象_03


对树形结构最直观的分析莫过于节点之间的继承关系上,通过显示地描述某一节点的父节点,从而能够建立二维的关系表,则这种方案的Tree表结构通常设计为:{Node_id,Parent_id},上述数据可以描述为如下图所示:

Java 通过当前用户id递归寻找下级用户_数据库_04


这种方案的优点很明显:设计和实现自然而然,非常直观和方便。缺点当然也是非 常的突出:由于直接地记录了节点之间的继承关系,因此对Tree的任何CRUD操作都将是低效的,这主要归根于频繁的“递归”操作,递归过程不断地访问数据库,每次数据库IO都会有时间开销。当然,这种方案并非没有用武之地,在Tree规模相对较小的情况下,我们可以借助于缓存机制来做优化,将Tree的信息载入内存进行处理,避免直接对数据库IO操作的性能开销。

基于左右值编码的设计

Java 通过当前用户id递归寻找下级用户_目标对象_05


第一次看见这种表结构,相信大部分人都不清楚左值(Lft)和右值(Rgt)是如何计算出来的,而且这种表设计似乎并没有保存父子节点的继承关系。但当你用手指指着表中的数字从1数到18,你应该会发现点什么吧。对,你手指移动的顺序就是对这棵树进行前序遍历的顺序,如下图所示。当我们从根节点Food左侧开始,标记为1,并沿前序遍历的方向,依次在遍历的路径上标注数字,最后我们回到了根节点Food,并在右边写上了18。

Java 通过当前用户id递归寻找下级用户_实例化_06


树形结构CRUD算法

1)获取某节点的子孙节点

只需要一条SQL语句,即可返回该节点子孙节点的前序遍历列表,以Fruit为例:

SELECT * FROM tree WHERE lft BETWEEN 2 AND 11 ORDER BY lft ASC

查询结果如下所示:

Java 通过当前用户id递归寻找下级用户_实例化_07


等等,此处就不在赘述…

mybatis优缺点

优点:

  1. 基于sql语句编程,sql写在xml里,解除了sql与程序代码的耦合,便于统一管理。支持编写动态sql,并可重用。
  2. 与JDBC相比,减少了大量冗余代码,不需要手动开关连接。
  3. 能够与各种数据库兼容,mybatis使用jdbc来连接数据库,支持jdbc就支持mybatis
  4. 能与Spring很好的集成
    缺点:
  5. sql语句编写工作量交大,尤其是字段多、关联表多时。
  6. sql语句依赖数据库(不同数据库函数不识别),导致数据库移植性差,不能随意更换数据库。

#{} ${}区别

#{}是预编译处理,是占位符,mybatis在处理时会将sql中#{}替换成?号,再去调用preparedstatement来赋值。预编译呢就会提前对sql进行编译,在替换后就不再对sql进行编译了,就能预防sql注入的问题。
Java 通过当前用户id递归寻找下级用户_数据库_08{}替换成变量的值,没有预编译。

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。
  • 意向共享锁:

索引的基本原理

索引的原理:无序变有序。

创建索引的原则

  1. 较频繁作为查询条件的字段才去创建索引
  2. 适合索引的列是出现在where子句中的列,或者连接子句中指定的列
  3. 基数较小的类,索引效果较差,没有必要在此列建立索引
  4. 使用短索引,如果对长字符串列进行索引,应该指定一个前缀长度,这样能够节省大量索引空间。
  5. 不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进行更新甚至重构,索引列越多,这个时间就会越长。所以只保持需要的索引有利于查询即可。
  6. 更新频繁字段不适合创建索引
  7. 定义有外键的数据列一定要建立索引。
  8. 若是不能有效区分数据的列不适合做索引列
  9. 尽量的扩展索引,不要新建索引。
  10. 对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。
  11. 对于定义为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、没有键值相等的节点;

Java 通过当前用户id递归寻找下级用户_面试_09


上图为一个普通的二叉查找树,按照中序遍历的方式可以从小到大的顺序排序输出:2、3、5、6、7、8。

时间复杂度logn。一亿个树 大概28次就能查到 log2 1亿 。

(2)局限性及应用

一个二叉查找树是由n个节点随机构成,所以,对于某些情况,二叉查找树会退化成一个有n个节点的线性链。时间复杂度o(n)如下图

Java 通过当前用户id递归寻找下级用户_数据库_10


(3)AVL树

AVL树是带有平衡条件的二叉查找树,一般是用平衡因子差值判断是否平衡并通过旋转来实现平衡,左右子树树高不超过1,和红黑树相比,它是严格的平衡二叉树,平衡条件必须满足(所有节点的左右子树高度差不超过1).不管我们是执行插入还是删除操作,只要不满足上面的条件,就要通过旋转来保持平衡,而旋转是非常耗时的,由此我们可以知道AVL树适合用于插入删除次数比较少,但查找多的情况。

Java 通过当前用户id递归寻找下级用户_面试_11


(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的非常重要的操作–分裂–来保持上述特性

每一个节点对应一个硬盘地址,来存数据库中的记录。

Java 通过当前用户id递归寻找下级用户_数据库_12


那么这样的结构存在着问题:

索引空间(带有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 "%后盾%" -- 不走索引

执行计划的参数:

Java 通过当前用户id递归寻找下级用户_面试_13

  • 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呢?

Java 通过当前用户id递归寻找下级用户_实例化_14


隔离性有四个隔离级别:

  • read uncommit 读未提交,可能会读到其他事务未提交的数据。脏读。
  • read commit 读已提交,两次读取结果不一致,叫做不可重复读。先读了一个数,这时这个数被改了,再读的时候发生了变化。Orical采用的级别。
  • repeatable read 可重复读,mysql默认级别。但是可能产生幻读:一个事务(同一个read view)在前后两次查询同一范围的时候,后一次查询看到了前一次查询没有看到的行。(行锁只能锁住行,即使把所有的行记录都上锁,也阻止不了新插入的记录)。此时可以加入间隙锁来组织新纪录的插入。
  • serializable 串行化,一般不使用,给每行读取的数据枷锁。

关心过sql耗时嘛?统计过慢查询嘛?怎么优化慢查询?

Java 通过当前用户id递归寻找下级用户_实例化_15

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持久化

  1. RDB
    redis database,指在指定的时间间隔内将内存的数据集快速写入磁盘,实际操作过程是fork一个子进程,先将书局街写入临时文件,写入成功后再替换之前的文件,采用二进制压缩。
    优点:
    整个redis数据库只包含一个文件,dumo.rdb方便持久化。
    方便备份。
    性能最大化,fork子进程来完成操作,让主进程继续处理数据。
    缺点:
    数据安全性低,间隔时间段的数据就会丢失。
  2. 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解除对事物的监控。

  1. 事务的开启
    MULTI命令的执行,标识着事务的开始,将客户端flag属性中REDIS_MULTI打开。
  2. 命令入队
    当一个客户端切换到事务状态后,服务器会根据客户端发送的命令来执行不同的操作。如果发送的是MULTI/EXEC/WATCH/DISCARD中的一个,则立即执行这个命令。否则就会放入队列,此时只检查语法有没有问题。如果执行中发生错误,则会全部执行完。
    事务队列是按照先进先出的方式来入队。
  3. 事务执行
    客户端发送EXEC,服务器执行EXEC命令。

集群方案

  1. 哨兵模式
  • 集群监控:负责监控master和slave进程是否正常工作。
  • 消息通知:如果某个redis实例有故障,那么哨兵负责发送消息给管理员。
  • 故障转移:如果master node挂掉了,会自动转移到slave node上
  • 配置中心:如果故障转移发生了,通知client客户端新的master地址。
  1. Redis Cluster
  • 通过hash方法将数据分片,16384个哈希槽。
  • 每份数据分片会存储在互为主从的多个节点上。
  • 数据写入先写入主节点,在同步到从节点。

每个节点都开放两个端口,一个是6379,一个是+1w(16379)用来节点通信。
只能使用0号数据库,不支持批量操作。

主从复制

全量复制:

主节点通过bgsave,fork出一个子进程,生成一个rdb快照。该过程非常消耗cpu,硬盘io。

主节点通过网络将rdb发送给从节点。

从节点清空老数据,载入新的rdb文件,此过程是阻塞的,无法响应客户端命令。

增量复制:

复制偏移量:主从节点分别维护了一个复制偏移量offset。

复制积压缓冲区:主节点内部维护了一个固定长度的、先进先出的复制积压缓冲区。偏移量后的数据会先写入缓冲区。当主从节点的偏移量大于缓冲区长度时,会进行全量复制。

服务器运行id:

Java 通过当前用户id递归寻找下级用户_实例化_16

缓存/特点/解决了什么问题

缓存即使计算机内存中的一段数据,因为在内存中,具有读写快,断电立即丢失的特点。
缓存解决了:

  • 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来实现。

  1. 新建cache包,Rediscache类(必须有带有id参数的构造方法(id为当前缓存的mapper的namespace)/getId方法的返回值不能为空"name 不能为空”)
  2. 重新对cache标签赋值:
    <cache type="RedisCache“>
  3. 重写put/get方法,put至redis。此时rediscache类并不是由工厂实例化的,就不能直接注入RedisTemplate,那么此时需要获取工厂(基于工具类)。如下:

    此时就可通过工具类来获取RedisTemplate,put方法如下:

    get方法如下:

    此时数据放入redis中,即使jvm关闭,缓存中数据也存在。
    增删改如何修改缓存呢,只要发生增删改的任意一个方法,默认都是调用clean,清空缓存。

    最后重写getsize方法,用来计算缓存数量。返回值为long,需要转换为int。

Zookeeper

分布式锁的解决方案

  1. 数据库来控制
  2. 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。