java-技能提升、性能优化相关面试题

多线程

创建线程是几种方式

方式一:继承Thread类,覆写run方法,创建实例对象,调用该对象的start方法启动线程
方式二:创建Runnable接口的实现类,类中覆写run方法,再将实例作为此参数传递给Thread类有参构造创建线程对象,调用start方法启动

方式三:创建Callable接口的实现类,类中覆写call方法,创建实例对象,将其作为参数传递给FutureTask类有参构造创建FutureTask对象,再将FutureTask对象传递给Thread类的有参构造创建线程对象,调用start方法启动

Thread有单继承的局限性,Runnable和Callable三避免了单继承的局限,使用更广泛。Runnable适用于无需返回值的场景,Callable使用于有返回值的场景

Thread的start和run的区别

start是开启新线程, 而调用run方法是一个普通方法调用,还是在主线程里执行。没人会直接调用run方法

sleep 和 wait的区别

第一,sleep方法是Thread类的静态方法,wait方法是Object类的方法

第二:sleep方法不会释放对象锁,wait方法会释放对象锁

第三:sleep方法必须捕获异常,wait方法不需要捕获异常

线程的几种状态

新建状态:线程刚创建,还没有调用start方法之前

就绪状态:也叫临时阻塞状态,当调用了start方法后,具备cpu的执行资格,等待cpu调度器轮询的状态

运行状态:就绪状态的线程,获得了cpu的时间片,真正运行的状态

冻结状态:也叫阻塞状态,指的是该线程因某种原因放弃了cpu的执行资格,暂时停止运行的状态,比如调用了wait,sleep方法

死亡状态:线程执行结束了,比如调用了stop方法

Synchronized 和 lock的区别

他们都是用来解决并发编程中的线程安全问题的,不同的是

  • synchronized是一个关键字,依靠Jvm内置语言实现,底层是依靠指令码来实现;Lock是一个接口,它基于CAS乐观锁来实现的
  • synchronized在线程发生异常时,会自动释放锁,不会发生异常死锁,Lock在异常时不会自动释放锁,我们需要在finally中释放锁
  • synchronized是可重入,不可判断,非公平锁,Lock是可重入,可判断的,可手动指定公平锁或者非公平锁

你知道AQS吗

AQS:AbstractQuenedSynchronizer抽象的队列式同步器。是除了java自带的synchronized关键字之外的锁机制,它维护了一个volatile修饰的 int 类型的,state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。

工作思想是如果被请求的资源空闲,也就是还没有线程获取锁,将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果请求的资源被占用,就将获取不到锁的线程加入队列。

悲观锁和乐观锁

悲观锁和乐观锁,指的是看待并发同步问题的角度

  • 悲观锁认为,对同一个数据的并发操作,一定是会被其他线程同时修改的。所以在每次操作数据的时候,都会上锁,这样别人就拿不到这个数据。如果不加锁,并发操作一定会出问题。用阳间的话说,就是总有刁民想害朕

  • 乐观锁认为,对同一个数据的并发操作,是不会有其他线程同时修改的。它不会使用加锁的形式来操作数据,而是在提交更新数据的时候,判断一下在操作期间有没有其他线程修改了这个数据

悲观锁一般用于并发小,对数据安全要求高的场景,乐观锁一般用于高并发,多读少写的场景,通常使用版本号控制,或者时间戳来解决.

你知道什么是CAS嘛

CAS,compare and swap的缩写,中文翻译成比较并交换。它是乐观锁的一种体现,CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。

Synchronized 加非静态和静态方法上的区别

实例方法上的锁,锁住的是这个对象实例,它不会被实例共享,也叫做对象锁

静态方法上的锁,锁住的是这个类的字节码对象,它会被所有实例共享,也叫做类锁

Synchronized(this) 和 Synchronized (User.class)的区别

Synchronized(this) 中,this代表的是该对象实例,不会被所有实例共享

Synchronized (User.class),代表的是对类加锁,会被所有实例共享

Synchronized 和 volatitle 关键字的区别

这两个关键字都是用来解决并发编程中的线程安全问题的,不同点主要有以下几点

第一:volatile的实现原理,是在每次使用变量时都必须重主存中加载,修改变量后都必须立马同步到主存;synchronized的实现原理,则是锁定当前变量,让其他线程处于阻塞状态

第二:volatile只能修饰变量,synchronized用在修饰方法和同步代码块中

第三:volatile修饰的变量,不会被编译器进行指令重排序,synchronized不会限制指令重排序

第四:volatile不会造成线程阻塞,高并发时性能更高,synchronized会造成线程阻塞,高并发效率低

第五:volatile不能保证操作的原子性,因此它不能保证线程的安全,synchronized能保证操作的原子性,保证线程的安全

synchronized 锁的原理

synchronized是基于JVM内置锁实现,通过内部对象Monitor(监视器锁)实 现,基于进入与退出Monitor对象实现方法与代码块同步,监视器锁的实现依赖 底层操作系统的Mutex lock(互斥锁)实现,它是一个重量级锁性能较低,涉及到用户态到内核态的切换,会让整个程序性能变得很差。

因此在JDK1.6及以后的版本中,增加了锁升级的过程,依次为无锁,偏向锁,轻量级锁,重量级锁。而且还增加了锁粗化,锁消除等策略,这就节省了锁操作的开销,提高了性能

synchronized 锁升级原理

每个对象都拥有对象头,对象头由Mark World ,指向类的指针,以及数组长度三部分组成,锁升级主要依赖Mark Word中的锁标志位和释放偏向锁标识位。

  • 偏向锁(无锁)

大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得。偏向锁的目的是在某个线程 获得锁之后(线程的id会记录在对象的Mark Word锁标志位中),消除这个线程锁重入(CAS)的开销,看起来让这个线程得到了偏护。(第二次还是这个线程进来就不需要重复加锁,基本无开销),如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外开销,性能极高。

  • 轻量级锁(CAS):

轻量级锁是由偏向锁升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁自旋锁);没有抢到锁的线程将自旋,获取锁的操作。轻量级锁的意图是在没有多线程竞争的情况下,通过CAS操作尝试将MarkWord锁标志位更新为指向LockRecord的指针,减少了使用重量级锁的系统互斥量产生的性能消耗。

长时间的自旋操作是非常消耗资源的,一个线程持有锁,其他线程就只能在原地空耗CPU,执行不了任何有效的任务,这种现象叫做忙等(busy-waiting)

  • 重量级锁:

如果锁竞争情况严重,某个达到最大自旋次数(10次默认)的线程,会将轻量级锁升级为重量级锁,重量级锁则直接将自己挂起,在JDK1.6之前,synchronized直接加重量级锁,很明显现在得到了很好的优化。

虚拟机使用CAS操作尝试将MarkWord更新为指向LockRecord的指针,如果更新成功表示线程就拥有该对象的锁;如果失败,会检查MarkWord是否指向当前线程的栈帧,如果是,表示当前线程已经拥有这个锁;如果不是,说明这个锁被其他线程抢占,此时膨胀为重量级锁。

乐观锁的使用场景(数据库,ES)

场景一:ES中对version的控制并发写。

场景二:数据库中使用version版本号控制来防止更新覆盖问题。

场景三:原子类中的CompareAndSwap操作

AtomicInterger怎么保证并发安全性的

通过CAS操作原理来实现的,就可见性和原子性两个方面来说

它的value值使用了volatile关键字修饰,也就保证了多线程操作时内存的可见性

Unsafe这个类是一个很神奇的类,而compareAndSwapInt这个方法可以直接操作内存,依靠的是C++来实现的,它调用的是Atomic类的cmpxchg函数。而这个函数的实现是跟操作系统有关的,比如在X86的实现就利用汇编语言的CPU指令lock cmpxchg,它在执行后面的指令时,会锁定一个北桥信号,最终来保证操作的原子性

什么是重入锁,什么是自旋锁,什么是阻塞

可重入锁是指允许同一个线程多次获取同一把锁,比如一个递归函数里有加锁操作

自旋锁不是锁,而是一种状态,当一个线程尝试获取一把锁的时候,如果这个锁已经被占用了,该线程就处于等待状态,并间隔一段时间后再次尝试获取的状态,就叫自旋

阻塞,指的是当一个线程尝试获取锁失败了,线程就就进行阻塞,这是需要操作系统切换CPU状态的

你用过JUC中的类吗,说几个

Lock锁体系 ,ConcurrentHashMap ,Atomic原子类,如:AtomicInteger ;ThreadLoal ; ExecutorService

ThreadLocal的作用和原理

ThreadLocal,翻译成中国话,叫做线程本地变量,它是为了解决线程安全问题的,它通过为每个线程提供一个独立的变量副本,来解决并发访问冲突问题 - 简单理解它可以把一个变量绑定到当前线程中,达到线程间数据隔离目的。

原理:ThredLocal是和当前线程有关系的,每个线程内部都有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,它用来存储每个线程中的变量副本,key就是ThreadLocal变量,value就是变量副本。

当我们调用get方法是,就会在当前线程里的threadLocals中查找,它会以当前ThreadLocal变量为key获取当前线程的变量副本

它的使用场景比如在spring security中,我们使用SecurityContextHolder来获取SecurityContext,比如在springMVC中,我们通过RequestContextHolder来获取当前请求,比如在 zuul中,我们通过ContextHolder来获取当前请求

线程池的作用

请求并发高的时候,如果没有线程池会出现线程频繁创建和销毁而浪费性能的情况,同时没办法控制请求数量,所以使用了线程池后有如下好处

  • 主要作用是控制并发数量,线程池的队列可以缓冲请求
  • 线程池可以实现线程的复用效果
  • 使用线程池能管理线程的生命周期

Executors创建四种线程池

  • CachedThreadPool:可缓存的线程池,它在创建的时候,没有核心线程,线程最大数量是Integer最大值,最大空闲时间是60S

  • FixedThreadPool:固定长度的线程池,它的最大线程数等于核心线程数,此时没有最大空闲时长为0

  • SingleThreadPool:单个线程的线程池,它的核心线程和最大线程数都是1,也就是说所有任务都串行的执行

  • ScheduledThreadPool:可调度的线程池,它的最大线程数是Integer的最大值,默认最长等待时间是10S,它是一个由延迟执行和周期执行的线程池

线程池的执行流程

corePoolSize,maximumPoolSize,workQueue之间关系。

  1. 当线程池中线程数小于corePoolSize时,新提交任务将创建一个新线程(使用核心)执行任务,即使此时线程池中存在空闲线程。
  2. 当线程池中线程数达到corePoolSize时(核心用完),新提交任务将被放入workQueue中,等待线程池中任务调度执行 。
  3. 当workQueue已满,且maximumPoolSize > corePoolSize时,新提交任务会创建新线程(非核心)执行任务。
  4. 当workQueue已满,且提交任务数超过maximumPoolSize(线程用完,队列已满),任务由RejectedExecutionHandler处理。
  5. 当线程池中线程数超过corePoolSize,且超过这部分的空闲时间达到keepAliveTime时,回收这些线程。
  6. 当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize范围内的线程空闲时间达到keepAliveTime也将回收。

线程池执行流程 : 核心线程 => 等待队列 => 非核心线程 => 拒绝策略

线程池构造器的7个参数

  • CorePoolSize:核心线程数,它是不会被销毁的

  • MaximumPoolSize :最大线程数,核心线程数+非核心线程数的总和

  • KeepAliveTime:非核心线程的最大空闲时间,到了这个空闲时间没被使用,非核心线程销毁

  • Unit:空闲时间单位

  • WorkQueue:是一个BlockingQueue阻塞队列,超过核心线程数的任务会进入队列排队

  • ThreadFactory:它是一个创建新线程的工厂

  • Handler:拒绝策略,任务超过最大线程数+队列排队数 ,多出来的任务该如何处理取决于Handler

线程池拒绝策略有几种

拒绝策略,当线程池任务超过 最大线程数+队列排队数 ,多出来的任务该如何处理取决于Handler

  1. AbortPolicy丢弃任务并抛出RejectedExecutionException异常;
  2. DiscardPolicy丢弃任务,但是不抛出异常;
  3. DiscardOldestPolicy丢弃队列最前面的任务,然后重新尝试执行任务;
  4. CallerRunsPolicy由调用线程处理该任务

可以定义和使用其他种类的RejectedExecutionHandler类来定义拒绝策略。

你知道ScheduledThreadPool使用场景吗

这是带定时任务的线程池,EurekaClient拉取注册表&心跳续约就是使用的这个线程池。

索引部分

什么是索引

索引是用来高效获取数据的存储结构如同字典的目录一样,数据库的索引通常使用b+tree来实现,索引树的节点和数据地址相关联,查询的时候在索引树种进行高效搜索,然后根据数据地址获取数据。索引提高了搜索的效率同时增加了索引维护的成本,滥用索引也会降低insert,update,delete的性能。

Mysql索引有哪些类型

普通索引:允许重复的值

唯一索引:不允许有重复的值

主键索引:数据库自动为我们的主键创建索引,如果我们没有指定主键,它会根据没有null的唯一索引创建主键索引,否则会默认根据一个隐藏的rowId作为主键索引

全文索引,用来对文本域进行索引,比如text,varchar,只针对MyISAM有效

索引方式有哪些

B+树和hash,Myisam和innodb都不支持hash

Mysql的索引结构原理

采用了B+树的数据结构,采用B+树的原因,B+树是多叉树,适合存储大量数据,B+树的数据存储在叶子节点,内部节点只存键值,因此B+树每次查询都要走到叶子节点, 查询性能更稳定,同时它的非叶子节点只存储key,因此每个节点能存储更多的key,树的高度变的更低,查询性能更快,而且它的叶子节点能够形成一个链表,支持范围查询,排序 。

InnoDB的索引结构和MyIsam的索引结构有什么区别

他们都是用的B+树,不同的是

  • innodb的叶子节点存放的是数据,myisam的叶子节点存放的是数据的地址

  • innodb中辅助索引的叶子节点存放的是主键索引的键值,myisam中辅助索引的叶子节点存放的也是数据的地址

  • innodb的索引和数据都存放到一个文件中,myisam的索引和数据分别存放到不同的文件中

哪些列不适合创建索引

不经常查询的列不适合创建索引

不出现在where中的字段不适合创建索引

离散度太低的字段不适合创建索引,比如性别

更新非常频繁的字段不适合创建索引

哪些因素会造成索引失效

模糊查询时,通配符放到左边的时候,会导致索引失效 比如 like ''%keyword%''

列是字符串类型,查询条件没有用引号,会导致索引失效

使用了or,in,not in,not exist, !=等,会导致索引失效

查询null值,会导致索引失效

还有mySQL认为全表扫描会比索引查找快,就不会使用索引,比如表里只有一条记录

什么是辅助索引&什么是覆盖索引

除了主键索引之外的其他索引都叫辅助索引,也叫二级检索。辅助索引的叶子节点存储的是主键索引的键值,因此辅助索引扫描完之后还会扫描主键索引,这也叫回表

但是如果查询的列恰好包含在辅助索引的键值中,就不会再回表了,这也叫覆盖索引

InnoDB辅助索引的叶子节点也存数据吗

InnoDB辅助索引的叶子节点存放的是,主键索引的键值

因此辅助索引扫描完还会扫描主键索引,也叫回表

但是如果查询的列恰好包含在辅助索引的键值中,就不会再回表了,这也叫覆盖索引

组合索引的匹配原则

组合索引向左匹配,我们应该优先选择组合索引,因为对覆盖索引命中率更高,查询性能更高,但是应该考虑列的顺序,因为组合索引会向左匹配

Like一定会让索引失效吗

不一定,比如:like "值%" 一样可以使用索引,向左匹配,而 like "%值"或 "_值"就不能命中索引。

索引创建的原则有哪些

查询较频繁的列应该考虑创建索引

不经常查询的列不适合创建索引

不出现在where中的字段不适合创建索引

离散度太低的字段不适合创建索引,比如性别

更新非常频繁的字段不适合创建索引

数据库优化

哪些因素可能会造成数据库性能问题

不合理的商业需求,比如实时更新总注册人数,总交易额等等,应该考虑不要实时

对于热点数据的查询并发太高,应该考虑用缓存

数据库结构设计不合理,比如几十个字段集中在一张表,应该考虑分表

SQL语句有问题,比如太多JOIN,很多不需要的字段也要全部查询出来,应该考虑优化SQL

硬件和网络方面的影响

Mysql的执行流程是怎么样的

客户端发起SQL查询,首先通过连接器,它会检查用户的身份,包括校验账户密码,权限

然后会查询缓存,如果缓存命中直接返回,如果没有命中再执行后续操作,但是MySQL8.0之后已经删除了缓存功能

接下来到达分析器,主要检查语法词法,比如SQL有没有写错,总共有多少关键字,要查询哪些东西

然后到达优化器,他会以自己的方式优化我们的SQL

最后到达执行器,调用存储引擎执行SQL并返回结果

优化SQL你从哪些方面着手

不需要的字段就不要查询出来

小结果集驱动大结果集,将能过率更多数据的条件写到前面

in和not in尽量不要用,会导致索引失效

避免在where中使用or链接条件,这会导致索引失效

给经常要查询的字段建立索引

考虑如果不需要事务,并且主要查询的化,可以考虑使用MyISAM存储引擎

如果表数据量实在太庞大了,考虑分表

如何去定位慢SQL

通过druid连接池的内置监控来定位慢SQL

通过MySQL的慢查询日志查看慢SQL

通过show processlist,查看当前数据库SQL执行情况来定位慢SQL

页面上发起的一个查询很慢,你怎么去优化

首先看一下硬件和网络层面,有没有什么异常

然后分析代码有没有什么问题,算法有没有什么缺陷,比如多层嵌套循环

最后我们再定位到慢SQL,比如

  • 通过druid连接池的内置监控来定位慢SQL
  • 通过MySQL的慢查询日志查看慢SQL
  • 通过show processlist,查看当前数据库SQL执行情况来定位慢SQL

定位到慢SQL再考虑优化该SQL,比如说

  • 不需要的字段就不要查询出来
  • 小结果集驱动大结果集,将能过率更多数据的条件写到前面
  • in和not in尽量不要用,会导致索引失效
  • 避免在where中使用or链接条件,这会导致索引失效
  • 考虑如果不需要事务,并且主要查询的化,可以考虑使用MyISAM存储引擎

如果优化SQL后还是很慢,可以考虑给查询字段建索引来提升效率

如果建立索引了还是慢,看一下是不是数据量太庞大了,应该考虑分表了

你如何看SQL有没有命中索引

在SQL语句前加上explain,结果中的key就是实际用到的索引

mysql存储引擎有哪些,有什么区别,如何选择

主要有innodb,memory,myisam

innodb支持事务,速度相对较慢,支持外键,不支持全文索引

myisam 速度相对较快,支持全文索引,不支持外键,不支持事务,

memory不支持事务,基于内存读写,速度快,支持全文索引

如果对事务要求不高,而且是查询为主,考虑用myisam

如果对事务要求高,保存的都是重要的数据,建议使用innodb,它也是默认的存储引擎

如果数据频繁变化的,不需要持久化,可以使用memory

下面SQL如何优化

一个sql : select sum(amount) from recharge ,来查询总充值,recharge 表数据量达到了上千万,怎么优化

可以考虑建个汇总表来统计总充值,总订单数,总人数等等等

或者采用日报表,月报表,年报表,使用定时任务进行结算的方式来统计

或者看数据能不能使用ES搜索引擎来优化,如果非得要在这个上千万的表中来查询,那就采用分表

事务相关

什么是事务

一组对数据库的操作,把这一组看成一个再给你,要么全部成功,要么全部失败。

举个栗子,比如A向B转账,A账户的钱少了,B账户的钱就应该对应增加,这就转账成功了,如果A账户的钱少了,由于网络波动等因素转账失败了,B账户的钱没有增加,那么A账户就应该恢复成原先的状态

事务的四大特性

原子性:指的是一个事务应该是一个最小的无法分割的单元,不允许部分成功部分失败,只能同时成功,或者同时失败

持久性:一旦提交事务,那么数据就应该持久化,保证数据不会丢失

隔离性:两个事务修改同一个数据,必须按顺序执行,并且前一个事务如果未完成,那么中间状态对另一个事务不可见

一致性:要求任何写到数据库的数据都必须满足预先定义的规则,它基于其他三个特性实现的

InnoDB如何保证原子性和持久性的

通过undo log 保证事务的原子性,redo log保证事务的持久性

undo log是回滚日志,记录的是回滚需要的信息,redo log记录的是新数据的备份

当事务开始时,会先保存一个undo log,再执行修改,并保存一个redo log,最后再提交事务。如果系统崩溃数据保存失败了,可以根据redo log中的内容,从新恢复到最新状态,如果事务需要回滚,就根据undo log 回滚到之前的状态

事务并发问题有哪些

脏读:事务A读到了事务B修改还未提交的数据

幻读,也叫虚读:事务A两次读取相同条件的数据,两次查询到的数据条数不一致,是由于事务B再这两次查询中插入或删除了数据造成的

不可重复读:事务A两次读取相同条件的数据,结果读取出不同的结果,是由于事务B再这两次查询中修改了数据造成的

第一类丢失更新:也叫回滚丢失,事务A和事务B更新同一条数据,事务B先完成了修改,此时事务A异常终止,回滚后造成事务B的更新也丢失了

第二类丢失更新:也叫覆盖丢失,事务A和事务B更新同一条数据,事务B先完成了修改,事务A再次修改并提交,把事务B提交的数据给覆盖了

事务隔离级别有哪些,分别能解决什么问题

读未提交:事务读不阻塞其他事务的读和写,事务写阻塞其他事务的写但不阻塞读,能解决第一类丢失更新的问题,

读已提交:事务读不会阻塞其他事务读和写,事务写会阻塞其他事务的读和写,能解决第一类丢失更新,脏读的问题

可重复读:事务读会阻塞其他事务的写但不阻塞读,事务写会阻塞其他事务读和写,能解决第一类丢失更新,脏读,不可重复读,第二类丢失更新问题

串行化:使用表级锁,让事务一个一个的按顺序执行,能解决以上所有并发安全问题

MySql的InnoDB是如何保证原子性的

利用了undo log实现的

undo log记录了这些回滚需要的信息,当事务执行失败或调用了rollback,导致事务需要回滚,就可以利用undo log中的信息将数据回滚到修改之前的样子

MySql的InnoDB是如何保证持久性的

利用了redo log实现的

redo log记录的是新数据的备份,在事务提交前,需要将Redo Log持久化,当系统崩溃时,可以根据redo Log的内容,将所有数据恢复到最新的状态

说一下事务的执行流程(Undolog+Redolog)

假设有A=1,B=2,两个数据,现在有个事务把A修改为3,B修改为4,那么事务的执行流程:

当事务开始时,会首先记录A=1到undo log,记录A=3到redo log,和记录B=2到undo log,记录B=4到redo log,然后再将redo log写入磁盘,最终事务提交

解释一下事务并发丢失更新问题,·如何解决

第一类丢失更新:也叫回滚丢失,事务A和事务B更新同一条数据,事务B先完成了修改,此时事务A异常终止,回滚后造成事务B的更新也丢失了

第二类丢失更新:也叫覆盖丢失,事务A和事务B更新同一条数据,事务B先完成了修改,事务A再次修改并提交,把事务B提交的数据给覆盖了

SQL标准中的四种隔离级别,读未提交,读已提交,可重复读,串行化,都能解决第一类数据更新丢失问题

对于第二类丢失更新问题,可以使用悲观锁也就是串行化来解决,也可以使用乐观锁的方式,比如加一个版本号管理来解决

InnoDB事务隔离的实现原理是什么

隔离的实现主要利用了读写锁和MVCC机制

读写锁,要求在每次读操作时需要获取一个共享锁,写操作时需要获取一个写锁。共享锁之间不会产生互斥,共享锁和写锁,写锁与写锁之间会产生互斥。当产生锁竞争时,需要等一个操作的锁释放,另一个操作才能获得锁

MVCC,多版本并发控制,它是在读取数据时通过一种类似快照的方式将数据保存下来,不同的事务看到的快照版本是不一样的,即使其他事务修改了数据,但是对本事务仍然是不可见的,它只会看到第一次查询到的数据

可重复读是只在事务开始的时候生成一个当前事务全局性的快照,而读提交则是每次执行语句的时候都重新生成一次快照

数据库集群

Mysql主从解决什么问题,不能解决什么问题?

MySQL主从同步,主负责写,从负责读,使用一主多从,能减轻读的压力

但是这不能解决写的压力和主库的单点故障,如果主库的写并发高,可以做成多个主库

MySql主从复制原理?

主要依靠binlog来实现的,它记录的是所有的DDL,DML,TCL操作

当主库的数据发生改变时,会将改变记录保存到binlog中

从库新开一个线程将binlog内容发送到从库

从库会发起一个I/O线程请求主库的binlog,并保存到中继日志中

从库新开一个SQL线程,读取中继日志并解析成具体操作,从而将主库更新的内容写到了从库中

MySql主从配置步骤?

安装mySQL主从客户端,并配置my.ini

主库需要配置授权从库使用的账号和权限,启动后可以通过show 主库名 status查看状态,我们需要记录File和Position的值,File是对应的binlog文件名,position是当前同步数据的最新行

从库需要配置主库链接信息,包括账号密码和binlog文件名和最新行,然后启动。通过show 从库名 status 检查同步状态,Slave_IO_Running 和 Slave_SQL_Running 的值都为YES,说明大功告成了

什么是垂直分表,垂直分库,水平分表,水平分库

垂直分表,可以理解为按列分表,如果一个表的字段太多了,可以按照使用频率分成不同的表,优化查询性能。比如商品表可以分为商品类型表,商品详情表,商品促销表等等

垂直分库,为了减轻单个数据库压力,我们可以按照业务类型,拆分成多个数据库,比如分布式架构,不同的模块可以有不同的数据库

水平分表,可以理解为按行分表,如果一个表的数据有千万行,查询性能太低,可以拆分成10张小表,每张表保存一百万行数据

水平分库,我们做了水平分表后,表数量太多了也会影响数据库查询效率,我们可以将这些表分到多个数据库中

分库分表后会出现哪些问题?怎么解决

会产生分布式事务,以前本地事务就能结局的问题现在要用上Seata分布式事务

垂直分库后跨库查询会导致一个查询结果来源于两个库,可能要用到多线程调用多个库查询

水平分库后一个分页查询的某一页可能来自两个库,可以将两个库的数据合并之后再执行SQL

水平分表后不同的表出现主键重复,可以通过雪花算法来解决

两个库都用到同一个表,那这个公共表的维护可能要用到MySQL主从同步

你们公司使用的是什么技术来水平分表?还可以有什么技术?有什么区别?

使用的是sharding-jdbc来实现的,它是由java开发的关系型数据库中间件,读写分离,分库分表操作简单

TDDL,淘宝业务框架,复杂而且分库分表的部分还没有开源

Mycat,要安装额外的环境,不稳定用起来复杂

MySQL官方提供的中间件,不支持大数据量的分不分表,性能较差

你们使用什么规则来分库分表的?还有哪些规则?

垂直分库,按照业务进行垂直分库,比如课程表和用户表放到不同数据库

垂直分表,把多字段表拆分少量字段表,比如将课程表分为课程类型表,课程详情表,课程促销表等

水平分表,把海量数据表拆分为多个小表

把商品业务进行水平分库,可以对水平分库后每一个数据库服务器进行集群

你从哪些方面去优化你的数据库?

如果是并发高,可以考虑缓存,如果是数据量大可以考虑分库分表,具体如下:

首先应该考虑垂直分库,不同的业务使用不同的数据库

然后进行垂直分表,按照使用频率把字段多的表拆分成若干个表

对经常查询的列建立索引,提高查询效率

设计冗余字段,减少join表的次数

SQL优化,比如尽量使用索引查询

对热点数据应该考虑做缓存,比如首页展示汇总数据

从海量数据中查询数据应该考虑用全文检索

如果查询并发高,可以对mySQL做集群

如果数据量实在太大了,可以考虑水平分表,

水平分表后,表数量还是太多了,可以考虑水平分库

Mysql的集群有哪些模式?

一主一从;一主多从;双主;环形多主;级联同步

单机优化到极致了,可以怎么优化?

可以考虑做集群,比如一主多从模式,然后对应用做读写分离

多机优化有哪些方式?

分表,分库,主从同步

解释一下分库分表的含义?

垂直分表,可以理解为按列分表,如果一个表的字段太多了,可以按照使用频率分成不同的表,优化查询性能。比如商品表可以分为商品类型表,商品详情表,商品促销表等等

垂直分库,为了减轻单个数据库压力,我们可以按照业务类型,拆分成多个数据库,比如分布式架构,不同的模块可以有不同的数据库

水平分表,可以理解为按行分表,如果一个表的数据有千万行,查询性能太低,可以拆分成10张小表,每张表保存一百万行数据

水平分库,我们做了水平分表后,表数量太多了也会影响数据库查询效率,我们可以将这些表分到多个数据库中

水平分表有哪些分表规则?

按照区间范围分表,比如把用户按照年龄分为新生代表,青年代表,老年代表

按照时间分表,比如按照年来分表,比如登录日志,分成今年的表,去年的表。。

hash分表,通过将某一列的值比如id,通过一定的hash算法来算出对应那张表

雪花算法,通过雪花算法生成id,根据id来算出对应那张表

能简单说一下你怎么使用shardingjdbc做读写分离的嘛

首先导入相关的依赖

然后在配置文件中配置datasource,包括主从数据库的名字,主从数据库的连接信息,配置负载均衡

项目中就可以正常使用datasource了,自动做读写分离

能简单说一下你怎么使用shardingjdbc做读分库分表的嘛

首先,要改造数据库,比如水平分表,水平分库

在配置文件中,需要做如下配置

  • datasource名字,多个数据源就配多个datasource

  • 分库策略,比如按照哪一列分库,分库规则

  • 分表策略,比如哪些库下面的哪些表,按照那一列分表,分表规则

  • 配置公共的表

然后项目中就可以正常使用了

JVM篇

你们用什么工具监控JVM

jconsule, jvisualvm

JVM类加载流程

loading加载:class文件从磁盘加载到内存中

verification验证:校验class文件,包括字节码验证,元数据验证,符号引用验证等等

preparation准备:静态变量赋默认值,只有final会赋初始值

resolution解析:常量池中符号引用,转换成直接访问的地址

initializing初始化:静态变量赋初始值

JVM类加载器有几种类型,分别加载什么东西,用到什么设计模式?

  1. BootStrap ClassLoader 启动类加载器,加载<JAVA_HOME>\lib下的类

  2. Extenstion ClassLoader 扩展类加载器,加载<JAVA_HOME>\lib\ext下的类

  3. Application ClassLoader 应用程序类加载器,加载Classpath下的类

  4. 自定义类加载器

这里是用到了双亲委派模式,从上往下加载类,在这过程中只要上一级加载到了,下一级就不会加载了,这麽做的目的

  • 不让我们轻易覆盖系统提供功能
  • 也要让我们扩展我们功能。

JVM组成,以及他们的作用

运行时数据区:

  • 堆:存放对象的区域,所有线程共享

  • 虚拟机栈:对应一个方法,线程私有的,存放局部变量表,操作数栈,动态链接等等

  • 本地方法栈:对应的是本地方法,在hotspot中虚拟机栈和本地方法栈是合为一体的

  • 程序计数器:确定指令的执行顺序

  • 方法区:存放虚拟机加载的类的信息,常量,静态变量等等,JDK1.8后,改为元空间

执行引擎:

  • 即时编译器,用来将热点代码编译成机器码(编译执行)

  • 垃圾收集,将没用的对象清理掉

本地方法库:融合不同的编程语言为java所用

在JVM层面,一个线程是如何执行的

线程执行,每个方法都会形成一个栈帧进行压榨保存到虚拟机栈中,方法调用结束就回出栈。调用过程中创建的变量在虚拟机栈,对象实例存放在堆内存中,栈中的变量指向了对中的内存。当方法执行完成就出栈,创建的变量会被销毁,堆中的对象等待GC。

程序内存溢出了,如何定位问题出在哪儿?

增加启动参数-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:\ 可以把内存溢出的日志输出到文件,然后通过JVM监视工具VisualVM来分析日志,定位错误所在。在linux服务器也可以使用命令: jmap -dump 来下载堆快照。

垃圾标记算法

垃圾标记算法有:引用计数和可达性算法

  • 引用计数 : 给每一个对象添加一个引用计数器,每当有一个地方引用它时,计数器值加1;每当有一个地方不再引用它时,计数器值减1,这样只要计数器的值不为0,就说明还有地方引用它,它就不是无用的对象. 这种算法的问题是当某些对象之间互相引用时,无法判断出这些对象是否已死
  • GC Roots :找到一个对象作为 CG Root , 当一个对象到GC Roots没有任何引用链相连(GC Roots到这个对象不可达)时,就说明此对象是不可用的

垃圾回收算法

  • 标记清除算法 :分为标记和清除两个阶段,首先标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象 ;缺点:标记和清除两个过程效率都不高;标记清除之后会产生大量不连续的内存碎片。
  • 复制算法 :把内存分为大小相等的两块,每次存储只用其中一块,当这一块用完了,就把存活的对象全部复制到另一块上,同时把使用过的这块内存空间全部清理掉,往复循环 ,缺点:实际可使用的内存空间缩小为原来的一半,比较适合

  • 标记整理算法 :先对可用的对象进行标记,然后所有被标记的对象向一段移动,最后清除可用对象边界以外的内存

  • 分代收集算法 :把堆内存分为新生代和老年代,新生代又分为Eden区、From Survivor和To Survivor。一般新生代中的对象基本上都是朝生夕灭的,每次只有少量对象存活,因此新生代采用复制算法,只需要复制那些少量存活的对象就可以完成垃圾收集;老年代中的对象存活率较高,就采用标记-清除和标记-整理算法来进行回收。

垃圾回收器有哪些

  • 新生代:Serial :一款用于新生代的单线程收集器,采用复制算法进行垃圾收集。Serial进行垃圾收集时,不仅只用一条线程执行垃圾收集工作,它在收集的同时,所有的用户线程必须暂停(Stop The World

  • 新生代:ParNew : ParNew就是一个Serial的多线程版本`,其它与Serial并无区别。ParNew在单核CPU环境并不会比Serial收集器达到更好的效果,它默认开启的收集线程数和CPU数量一致,可以通过-XX:ParallelGCThreads来设置垃圾收集的线程数。

  • 新生代:Parallel Scavenge(掌握) Parallel Scavenge也是一款用于新生代的多线程收集器,与ParNew的不同之处是,ParNew的目标是尽可能缩短垃圾收集时用户线程的停顿时间,Parallel Scavenge的目标是达到一个可控制的吞吐量.Parallel Old收集器以多线程,采用标记整理算法进行垃圾收集工作。

  • 老年代:Serial Old ,Serial Old收集器是Serial的老年代版本,同样是一个单线程收集器,采用标记-整理算法。

  • 老年代CMS收集器是一种以最短回收停顿时间为目标的收集器,以“最短用户线程停顿时间”著称。整个垃圾收集过程分为4个步骤

    • 初始标记:标记一下GC Roots能直接关联到的对象,速度较快
    • 并发标记:进行GC Roots Tracing,标记出全部的垃圾对象,耗时较长
    • 重新标记:修正并发标记阶段引用户程序继续运行而导致变化的对象的标记记录,耗时较短
    • 并发清除:用标记-清除算法清除垃圾对象,耗时较长

    整个过程耗时最长的并发标记和并发清除都是和用户线程一起工作,所以从总体上来说,CMS收集器垃圾收集可以看做是和用户线程并发执行的。

  • 老年代:Parallel Old ,Parallel Old收集器是Parallel Scavenge的老年代版本,是一个多线程收集器,采用标记-整理算法。可以与Parallel Scavenge收集器搭配,可以充分利用多核CPU的计算能力

  • 堆收集:G1 收集器, G1 收集器是jdk1.7才正式引用的商用收集器,现在已经成为jdk1.9默认的收集器。前面几款收集器收集的范围都是新生代或者老年代,G1进行垃圾收集的范围是整个堆内存,它采用“化整为零”的思路,把整个堆内存划分为多个大小相等的独立区域(Region)在每个Region中,都有一个Remembered Set来实时记录该区域内的引用类型数据与其他区域数据的引用关系(在前面的几款分代收集中,新生代、老年代中也有一个Remembered Set来实时记录与其他区域的引用关系),在标记时直接参考这些引用关系就可以知道这些对象是否应该被清除,而不用扫描全堆的数据

Jdk1.7.18新生代使用Parallel Scavenge,老年代使用Parallel Old

Minor GC和Full GC

新生代的回收称为Minor GC,新生代的回收一般回收很快,采用复制算法,造成的暂停时间很短 ,而Full GC一般是老年代的回收,并伴随至少一次的Minor GC,新生代和老年代都回收,而老年代采用标记-整理算法这种GC每次都比较慢造成的暂停时间比较长`,通常是Minor GC时间的10倍以上。尽量减少 Full GC

JVM优化的目的是什么?

优化程序的内存使用大小,以及减少CG来减少程序的停顿来提升程序的性能。

堆怎么调,栈怎么调

-Xms : 初始堆,1/64 物理内存

-Xmx : 最大堆,1/4物理内存

-Xmn :新生代大小

-Xss : 栈大小

设计模式

什么是单例,如何实现

一个类只能有一个实例,主要用于需要频繁使用的对象避免频繁初始化和销毁来提高性能,或者资源需要相互通信的环境

主要实现方式有,饿汉模式,懒汉模式,枚举,静态内部类

饿汉模式,是在类加载过程中就将这个单例对象实例化,需要将构造方法私有化,定义一个成员变量并new一个该类的实例作为初始值,提供一个公共的静态方法获取这个实例

懒汉模式,是在使用时才创建这个单例对象,需要将构造方法私有化,定义一个该类的成员变量不赋初始值,提供一个获取实例的公共静态方法。特别注意这个方法需要保证多线程环境下的并发安全性,可以通过DCL加volatile关键字来解决

枚举,直接在枚举中定义字段,它就是单例并且线程安全的

静态内部类,在类中搞一个静态内部类,在静态内部类中搞一个目标类的静态成员变量并且new一个实例作为初始值。然后在目标类中定义一个获取实例的静态方法,方法返回的就是静态内部类中的成员变量。这种方式能保证线程安全,也能实现延迟加载。缺点是这种方式传参不太方便

模板模式的作用

定义一个算法骨架,而将某个或多个具体的实现延迟到子类中,使得子类可以在不修改当前算法的结构情况下,重新定义当前算法的某些特定步骤

比如考试中所有考生的试卷都一样,答案由每个考生自己完成

什么是适配器模式

将不兼容的接口转换为可兼容的接口的中间类

比如HandlerInterceptorAdapter ,我们定义拦截器时不需要覆写HandlerInterceptor中的所有方法,因为适配器类帮我们做了空实现。但JDK1.8之后,给接口中增加了默认方法,可以有方法体,因此这些适配器类已经失去作用了

什么是代理模式?有几种代理?

不直接使用实际对象,通过调用代理对象间接调用实际对象,主要用作对实际对象的增强,分为静态代理,JDK动态代理,CGLIB动态代理

JDK动态代理和CGLIB动态代理的区别?

JDK动态代理是jdk提供的,我们可以直接使用,而CGLIB需要导入第三方库

JDK动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用目标方法前调用InvokeHandler来处理

CGLIB动态代理是先加载目标类的class文件,然后修改其字节码生成子类来实现的

常见的设计模式说一下

单例模式:一个类只能有一个实例,分为饿汉模式(迫切加载)和懒汉模式(延迟加载)和枚举。

工厂模式:隐藏了产品的复杂创建过程,实现生产功能的复用,让产品生产更加高效。分为简单工厂(需要来回切换生产线),工厂方法(开设新的生产线),抽象工厂(制定创建产品的接口,让子工厂选择创建哪种产品)

在Spring中各种的BeanFactory创建bean都用到了

模板模式:定义一个算法骨架或者算法的流程,而不同的实例实现方式不同,将某个或多个具体的实现延迟到子类中,比如RedisTemplate实现了RedisOperations,ElasticSearchTemplate实现了ElasticsearchOperations

代理模式:不直接使用实际对象,通过调用代理对象间接调用实际对象,主要用作对实际对象的增强,分为静态代理,JDK动态代理,CGLIB动态代理比如Spring的AOP原理就是动态代理,当目标对象实现了接口会使用JDK动态代理,没有实现接口会使用CGLIB动态代理

适配器模式:将不兼容的接口转换为可兼容的接口的中间类,比如HandlerInterceptorAdapter ,我们定义拦截器时不需要覆写HandlerInterceptor中的所有方法,因为适配器类帮我们做了空实现。但JDK1.8之后,给接口中增加了默认方法,可以有方法体,因此这些适配器类已经失去作用了

观察者模式:当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新,比如Spring中的ApplicationListener

数据结构

数据结构有哪几种分类

按照逻辑结构分

  • 集合:没有相互关系的一堆数据
  • 线性结构:元素存在一对一的相互关系
  • 树形结构:元素存在一对多的相互关系
  • 图形结构:元素存在多对多的相互关系

按照物理结构分

  • 顺序存储结构:用一组地址连续的存储空间依次存储线性表的数据元素,也叫顺序存储结构,比如数组

  • 链接存储结构:用一组任意的存储空间来存储线性表中的数据元素,不要求相邻元素在物理位置上也相邻,比如链表

  • 数据索引存储结构:建立附加的索引来标识节点的地址,通过索引,可以很快检索数据

  • 数据散列存储结构:将数据元素的存储位置与关键字之间建立确定的对应关系,加快查找的速度,又叫hash存储

数组和链表在内存中的存储结构有什么区别

数组在内存中是一组连续的存储空间,它随机存取元素性能很高,但是插入和删除操作,需要移动其他元素,因此性能很低

链表在内存中的存储空间可以是不连续的,而在每一个元素中都保存相邻节点的指针,因此它的存储密度相对较小,查找的性能低,因为需要从第一个元素依次遍历,但是它的插入和删除操作性能很高,因为它不需要移动节点,只需要改变相邻节点指针就行了,同时它更容易造成内存的碎片化

说一下散列存储(Hash存储) , 什么是Hash冲突 , 有什么解决方案

散列存储,它通过把关键码的值映射到表中的一个位置,来提高查询的速度。而这个映射函数叫做散列函数。

哈希冲突,也叫哈希碰撞,指的是两个不同的值,计算出了相同的hash,也就是两个不同的数据计算出同一个下标,通常解决方案有:

  • 拉链法,把哈希碰撞的元素指向一个链表

  • 开放寻址法,把产生冲突的哈希值作为值,再进行哈希运算,直到不冲突

  • 再散列法,就是换一种哈希算法重来一次

  • 建立公共溢出区,把哈希表分为基本表和溢出表,将产生哈希冲突的元素移到溢出表

说说 数组,链表,循环,嵌套循环的时间复杂度

时间复杂度是用来度量算法执行的时间长短,通常我们用O(f(n))渐进时间复杂度来衡量,比如说

  • 要在 hash 表中找到一个元素就是 O(1)
  • 要在无序数组中找到一个元素就是 O(n)
  • 访问数组的第 n 个元素是 O(1)
  • 二分搜索的时间复杂度最好的情况是 O(1),最坏情况(平均情况)下 O(log n)
  • 访问链表的第 n 个元素是 O(n)
  • 一个For循环是O(n)
  • 两个For循环嵌套是O(n2)
  • 三个Foreach嵌套是O(n3)

JDK中线性结构的集合有哪些

数组:按照顺序物理结构存储,ArrayList

链表:按照链式物理结构存储,LinkedList

栈:LIFO后进先出的线性存储结构,分为用数组实现的顺序栈,用链表实现的链栈

队列:FIFO先进先出的线性存储结构,分为顺序队列和链式队列

串:特殊的线性存储结构,String,StringBuffer,StringBuilder

你说一下树形结构对比线性结构的优势

线性结构,对于大量的输入数据,访问时间很长,效率很低,树形结构的优势在于它查找数据性能很高

说一下树的分类,以及你对它们的理解

树有二叉树,多叉树,他们特点如下

  • 二叉树:树中任意节点最多只有两个分叉的树,它又分为二叉排序树,平衡二叉树,赫夫曼树,红黑树

  • 二叉排序树,它是一个有序的二叉树,优势在于查找插入数据的性能很高,但是可能会出现倾斜而变成数组

  • 平衡二叉树,二叉排序树进化形态,要求任何节点的两颗字数高度差不大于1。它的查询性能很高,但是每次增删元素,会重排序导致性能低

  • 红黑树,自平衡二叉树,要求根节点和叶子节点是黑色,其他节点红黑交替,在任何一个子树中,从根节点向下走到空姐点的路径经过的黑节点数相同。从而保证了平衡。它的查询性能比平衡二叉树稍低,插入和删除元素的性能大幅提高。

多叉树:解决二叉树存储大规模数据时,深度过大而导致IO性能低,查询效率低的问题,常见有B树和B+树,字典树,后缀树等等

  • B树,自平衡的树,一个节点可以存储多个key,和拥有key数量+1个分叉,适用于读写相对大的数据块,比如文件系统,数据库索引。因为相对二叉树来说,节点存储key越多,分叉越多,需要的节点越少,树高越矮,IO次数少,查询效率越高。

  • B+树,B树升级版,它的内部节点只存储key,不存储具体数据,叶子节点存放key和具体数据。这就使得每个节点可以存更多的key,树的高度更低,查询更快,同时它每次查询都会到叶子节点,查询速度更稳定。并且所有的叶子节点会组成一个有序链表,方便区间查询

有是二叉树为什么要出现多叉树

因为二叉树在大规模的数据存储中,树会高的没谱,这会导致IO读写过于频繁,查询效率低下

多叉树可以解决这个问题,它每层可以存放更多的数据,因此能大幅度降低树的深度,提高查询性能

B-tree和b+tree的区别

一是节点存储内容上的区别:B树每个节点都可以存放key,存放数据,而B+树所有内部节点只存放key,叶子节点存放key和数据,因此它的节点能存放更多数据,降低树高,查询性能更快

二是B+树所有的叶子节点会构成一个链表结构,方便区间查找和排序

说一下ES用到了什么数据结构

ES是使用了数据索引存储结构,它是通过为关键字建立索引,通过索引找到对应的数据,这种索引也叫倒排索引,可以实现快速检索

五.项目部分

浏览器输入一个域名,它是怎么去执行的?

  1. 首先带着域名去hosts文件中看有没有配置对应的本地域名,如果有就以配置的ip进行访问
  2. 如果hosts没有配置,就会请求DNS服务器解析域名得到对应的IP然后发起访问
  3. 这时候请求就会打到服务器上可能是Nginx也有可能直接打到Tomcat.

请求在你的项目中是怎么执行的?

后端使用zuul网关,请求先到达zuul网关,zuul做登录检查,zuul网关底层整合ribbon把请求路由到下游微服务,服务之间使用OpenFeign进行通信。执行成功后原路返回结果。

如果zuul网关挂了怎么办?

可以做zuul集群,使用Nginx做负载均衡到zuul集群,然后Nginx可以采用双机主备,或者双机互备做集群防止单点故障。如果并发非常高可以加上LVS做负载。

如果有人用脚本刷你们的短信接口怎么办

首先,可以设置图形验证码,流量错峰

其次,可以获取请求的ip地址,手机号,发送时间,并保存到发送短信记录的日志中,对于短时间多次请求的ip地址,手机号,可以拦截不执行发送手机验证码

再次,可以设置单位时间内发送短信的总数量,比如设定1秒最多只发送10条验证码。但这种方式会降低并发性

非对称加密,什么是数字签名

非对称加密是一种算法,指的是加密和解密时使用不同的密钥,其中私钥不可公开,公钥可以公开。

数字签名就是在非对称加密的基础上,使用私钥加密,公钥解密,主要用来防止数据被篡改,实现安全传输的目的

Oauth2的四种授权模式

oauth协议是一个安全的开放授权标准,与传统的授权方式相比,它不会使第三方触及到用户的账号信息,比如用户名,密码。Oauth2有四种授权模式

一、授权码模式,它是功能最完整,流程最严密的授权模式

二、简化模式,直接从前端渠道获取token,容易受安全攻击

三、用户名密码模式,使用用户名和密码登录的应用,比如桌面APP

四、客户端凭证模式,用户直接向客户端认证,客户端以自己的名义向第三方索取服务

要求每天早上 1点统计前一天的平台注册人数,怎么做

使用定时任务每日结算即可。把结算的数据保存到一个统计表中

使用Quzrtz定时任务做订单超时关单有什么问题

数据量大的时候,定时任务扫描表性能会很差,而且多数都是空扫描,还有延迟问题,

对于我们的小型项目,可以使用quartz定时器,使用起来也很简单方便,但如果是高并发,比如秒杀等业务,可以使用RabbitMQ的延迟队列来实现,也可以使用Redis来做延迟队列。

讲一下你做过的比较复杂的业务

省略...

什么是RBAC , 相关表怎么设计的?

RBAC:Role-Based Access Control首字母缩写,意为基于角色的访问控制。基本思想是对系统操作的各种权限不是直接授予具体的用户,而是在用户集合与权限集合之间建立一个角色集合。

将权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。

实现RBAC,需要将用户对权限的多对多关系,转化为用户对角色,角色对权限的多对多关系,因此在数据库中,需要在用户,角色,权限中分别加入中间表,即用户表,用户和角色关系表,角色表,角色和权限关系表,权限表

在VUE中,什么是MVVM

MVVM,Model–View–ViewModel首字母缩写,是一种软件架构模式。

其中Model指的是模型,包括数据和一些基本操作

View指的是视图,页面渲染结果

ViewModel指的是模型与视图间的双向操作

MVVM的思想就是数据模型和视图的双向绑定,只要数据变化,视图会跟着变化,只要视图被修改,数据也会跟者变化

讲几个VUE的指令

v-text:给元素填充纯文本内容

v-html:给元素填充内容,与v-text的区别是它会把内容的html符号进行渲染

v-for:遍历数字,字符串,数组,对象

v-bind:将data中的数据绑定到标签上,作为标签的属性

v-model:创建双向绑定,表单的值被修改时会自动修改data中的数据,data中的值变化时页面也会被修改

v-show:根据表达式的真假值,切换元素的css属性

v-if:根据表达式的真假值,销毁或重建元素

v-on:绑定事件

webpack的作用

VUE项目需要打包后才能部署

首先,它可以将ES6等高级语法,编译成各个浏览器都认识的语法

其次,它可以将相互依赖的许多散碎文件搞成一个整体,提高网页访问的效率

再次,它可以将代码压缩,减小代码体积

Vue中定义组件分为几种,有什么区别

组件是一种自定义的元素标签,可以对功能封装,提高代码复用性,分为全局组件和局部组件两种

  • 全局组件,是在所有vue挂载的标签中都有效,

  • 局部组件,只在当前vue所挂载的标签中有效

讲一下你用过ElementUI的哪些组件

基础组件,比如按钮Button,图标Icon

表单组件:比如表单Form,单选框Radio,多选框Checkbox,输入框Input,选择器Select,级联选择器Cascader

其他组件:比如Dialog对话框,消息提示Message

你们Redis做登录是怎么处理登录信息过期的?

给保存在Redis中的token设置过期时间来处理登录过期的,为了防止已登录用户在访问后台时突然遭遇登录过期的情况,我们在后台接收到用户访问时,重新设置token的过期时间写入Redis,则用户访问期间就不会突然过期了

讲一下你们的登录实现方案

当用户第一次发起登录请求,后台生成一个token保存到Redis中

将生成的token返回给用户端

用户端使用用浏览器中的localStorage保存token

通过axios的拦截器,给每次请求的请求头都加上token

服务端收到token,就能在Redis中找到对应的数据

三方登录流程讲一下

1.用户发起微信登录请求

2.后端获取请求二维码的连接,重定向到扫码界面

3.用户使用微信扫一扫并同意授权

4.后端回调获取授权码,并将授权码作为参数,重定向到前端跳转页面

5.前端将授权码返回后端,后端根据授权码获取token

6.后端根据token获取openId

7.根据openId查询微信用户表

  • 如果查到有用户信息,且已关联本地账户,就默认登录

  • 如果有查到用户信息,但没有关联本地账户,就跳转本地账户绑定页面,

  • 如果没有查到用户信息,就向微信平台发起请求查询用户基本信息,添加到微信用户信息表,再跳转本地账户绑定页面

8.执行绑定逻辑时,根据手机号判断是否有本地账户,如果有就直接绑定,如果没有就自动注册再绑定,绑定成功后就默认登录

讲一下什么是非对称加密,什么是数字签名,数字签名的作用是什么?

非对称加密是一种算法,指的是加密和解密时使用不同的密钥,其中私钥不可公开,公钥可以公开。

数字签名就是在非对称加密的基础上,使用私钥加密,公钥解密,主要用来防止数据被篡改,实现安全传输的目的

京东的首页的商品分类,让你设计表,你怎么设计

首先可以看出表的结构是自关联,三层的树状结构,分类表的字段可以有主键id,商品名,创建时间,修改时间,上架时间,下架时间,商品数量,排序,图标,父级id

如何查询出树状结构的课程分类数据

首先,在entity中加入子分类字段children

查询方式有四种

  • 第一,使用嵌套for循环,循环体内查询每一层级的数据,并关联到children。当然这也可以使用递归函数来实现

  • 第二,使用mybatis的嵌套查询,也就是主查询加额外子sql查询的方式

  • 第三,使用mybatis的嵌套结果,也就是join连表查询的方式

  • 第四,只使用一次查询,将所有数据查询出来,通过一种算法来实现:除了第一级,其他所有数据都关联到自己的父级分类,结果返回第一级数据就可以

第一,第二种方式,当层级多的时候查询性能极低,第三种方式一般只能查询两层结构,第四种方式性能最高,适用于数据量本身并不大但层级很多的场景

所有课程的数据本身体量小,层级多,因此采用了第四种方式。

你们系统使用Redis缓存了哪些东西?用Redis的什么结构去存储的?

登录信息login,使用的是String结构存储

手机验证码code,使用的是String结构

课程分类course_type ,使用的是String结构

购物车保存,使用的是Hash结构

课程发布流程讲一下

发布课程两大步

第一步,将课程的状态改为上线并保存到数据库中,

第二步,将课程信息保存到ES中,方便门户网站展示

你们课程相关的表是怎么设计的?主要的字段说一下

我们按照字段的使用频次,垂直分表来设计,分为课程主表,课程详情表,课程类型表,课程市场详情表。

课程主表,包括主键id,课程名称,课程类型id,课程上下线状态,适用人群,课程等级,课程所属机构等,并且冗余了课程类型名,课程价格字段来提高前台的查询性能

课程详情表,包括课程简介,课程详情

课程市场详情表,包括课程价格,促销活动,活动过期时间

课程类型表,包括主键id,类型名,创建修改时间,课程数量,父级id

其中课程主表和课程详情表、课程主表和课程市场详情表,都是一对一的关系,他们采用相同的主键id来相互关联。课程主表和课程类型表是多对一的关系,在课程主表添加类型id来相互关联

讲一下你们这个项目的主线业务

我们项目分为两大版图,

入驻我们平台的培训机构,可以发布相关课程,入驻平台的企业,可以发布相关的就业招聘信息

门户网站的大众用户,可以选择培训机构发布的课程来进行学习,可以选择企业发布的招聘信息来就业

你们项目最大并发是多少

俺们项目是按照最高2000 QPS设计的,实际并发数运维在统计,俺也不太清楚

你们项目最大表数量是多少

俺们项目都有分库分表,按服务拆分多个数据库,对于有些数据量大的表,我们也是按照字段的使用频率,拆分成多个表,比如课程表拆分成课程主表,课程详情表,课程分类表等等。

但是有些表比如日志,流水相关的表,数据量还是很大的

.说一下你们课程搜索的那个业务方法的大致逻辑

首先,课程在发布的时候,就同时将课程信息存放到ES中,信息中包括了需要查询的字段,如课程标题,课程分类,课程等级,机构名,销量,浏览量,上线时间,价格等等

接下来,根据用户在前台发送的查询条件,在ES中搜索对应的课程,并作关键字高亮处理,排序和分页处理,然后返回前台

项目并发高处理过不过来怎么办

前端优化:

  • 使用页面静态化技术由Nginx实现动静分离、
  • CDN加速加快响应速度、
  • 使用验证码使流量错峰等手段最大限度的降低并发

后端优化:

  • Nginx+LVS负载,也可以多机房部署,分流

  • 从架构上使用分布式、集群分散并发量,

  • 从数据结构上使用缓存如Redis减少数据读写时间,

  • 从处理方式上采用如RabitMQ队列实现异步响应,

  • 资源隔离比如使用Hystrix的信号量隔离来限流,同时做好备用方案比如Hystrix的熔断降级策略等等

讲一下你们的微服务授权方案 你还知道有哪些方案吗?

我们使用的是SpringSecurity+Oauth2+JWT,认证服务器负责颁发token,资源服务器负责认证和授权

或者也可以将认证工作交给网关zuul,资源服务器只负责授权工作。

另外常见的授权方案还有,单点登录,用户只用在某个服务上登录,访问其他服务时就不需要登录了,这就要求每个面向用户的服务都必须于认证服务交互,会产生大量重复的工作

分布式会话,它是将用户认证信息存储在共享容器比如redis中,通常会以会话作为key,当用户访问微服务时,就从redis中获取认证信息。这对安全存储有较高的要求,复杂度高

讲一下你们微服务认证授权的整体流程

客户端访问认证服务器,认证服务器验证用户名密码,然后颁发token

客户端保存token,并且每次访问服务时都携带token

资源服务器接收到客户端请求,会验证token信息,认证通过后返回资源

你们为啥要用JWT

一个字,安全

我们做了认证授权后,每次客户端访问资源服务器,都需要远程调用认证服务器进行token的校验和授权,才能访问到资源。这是很好性能的,因此我们考虑将签名信息直接保存到客户端,那就不需要每次都向认证服务器认证授权了。

但是这有有一个新的问题,这些敏感数据赤裸裸的存到客户端不安全!而JWT就能解决这个问题。它支持非对称加密算法对信息加密,保证了信息安全

另外,JWT以json对象的形式传递信息,解析更方便

可以再令牌中定义内容,方便扩展

Oauth2的授权模式有哪些,分别使用在什么场景?

授权码模式:它是功能最完整、流程最严密的授权模式

简化模式:跳过授权码,直接再浏览器端申请令牌

用户名密码模式:客户向客户端提供用户名密码,建立在用户对客户端高度信赖的基础上

客户端模式:客户端以自己的名义,要求服务提供商提供服务

Oauth2认证,如果Token过期了你们是怎么处理的

首先,我们会在前端设置axios后置拦截,检查是否是token过期,判断一下如果返回401,就代表token过期了

然后从localStorage中获取刷新refresh_token,并发送请求获取新的token

后台接收到前台的刷新token请求,拼接完整的刷新token的url,发送http请求获取到新的token并返回客户端

客户端收到新的token就把旧的token覆盖掉,最后把之前的请求再重新发送一次

Oauth2认证,如果Token被盗了怎么办?

首先,我们需要对token设置过期时间,这个时间可以根据需要设置短一点

然后,可以在token中加入客户身份标识,比如客户的ip地址,如果短时间内ip地址频繁变动,就标记为异常状态,并给用户发送信息,提示账户有风险

秒杀的整体流程详细说一下

秒杀的商品和库存是缓存到Redis的,库存使用信号量,做的是秒杀预减库存方案。用户发起秒杀,直接走Redis秒杀商品,满足资格就预减库存,然后预创订单写入Redis。整个秒杀流程是不做数据罗库的。

此时把订单号返回给客户端,用户带着订单号进入订单确认页面进行下单,用户确认下单,再把Redis中的预创订单写入订单数据,同时做库存同步。紧接着就是调用支付接口做支付。

如果流量更高,比如:每秒10W请求,应该怎么处理

Lvs+Nginx集群+下游服务集群。如果流量再高,就使用CDN分流。

说一下支付超时处理方案?延迟队列和死信队列是什么意思?

支付超时使用MQ延迟队列来处理,把消息投递到一个设置了过期时间的队列中,达到过期时间消息会被转发给另外一个“死信队列”

设置了过期时间的队列就是延迟队列,过期的消息叫着死信消息,存放死信消息的队列叫死信队列。

整个秒杀流程你用到了哪些队列

下单业务中用到了一个低劣,订单超时用到一个队列,支付结果处理用到一个队列。

秒杀成功,返回给用户的数据是什么?

预创订单号,前台通过这个订单号来进行下单。

你们怎么处理超卖

Redisson分布式锁,信号量来保证库存不超卖

如何提高接口的qps

一方面:提高并发数

1.多线程,尽量用线程池 (线程个数:CPU核数 / (1 - 阻塞系数(IO密集型接近1,计算密集型接近0)))

2.适当调整连接数(Tomcat,Redis,Mysql等连接数)

3.集群

二方面:提高接口响应速度

1.减少和数据库交互,使用Redis代替

2.使用异步方案,比如MQ

3.使用并发编程,多个线程同时工作

4.减少服务的调用链

5.实在要连数据库,考虑数据库优化

你们这个前后端分离项目是怎么部署的

前后端分开部署,前端使用Nginx部署,

后端使用Springboot内嵌的tomcat部署,

分开部署后,通过代理解决前后端域名不一致的跨域问题

前后端分离的好处

第一,专人干专事,前后端同时开发,效率更高

第二,责任分离,避免了前后端相互踢皮球的现象

第三,前后端解耦合,一套后端可以处理不同的前端,包括app端,浏览器端

第四,分开部署,减轻了服务器压力

第五,页面显示东西再多也不怕,数据都是异步加载,就算后端服务器挂了,前端页面也能访问,虽然没有数据

第六,前端分离出去,后端写一套接口就可以适用于web,app端

你们用什么做项目代码管理的

使用主流的Git管理项目

讲讲Git相对于SVN的区别

第一。Git是每个攻城狮都有自己的版本库,可以在自己的库上任意操作提交代码

第二。Git在每个工程只产生一个.git目录,而SVN会在每个目录下都生成.svn目录

第三。Git能快速切换分支,且合并文件的速度比SVN快

第四。Git采用分布式版本库,内容完整性更好

你们微服务项目怎么部署

docker 容器 ,使用Jnekins做持续集成。

讲几个Git的命令

git clone:从远程仓库克隆项目到本地

git add:添加代码到本地仓库管理

git commit:提交add后的代码到本地仓库

git push:推送本地仓库文件到远程仓库

git pull:拉取远程仓库中的代码到本地仓库

六.运维篇

linux

有使用过linux吗 , 讲几个命令

  • 查看目录 : ls
  • 切换目录: cd
  • 拷贝:cp
  • 远程拷贝 :scp
  • 移动 : mv
  • 删除:rm
  • 查看文本内容:cat
  • 编辑文本: vi
  • 查找:find
  • 远程拷贝:scp
  • 创建目录 : mkdir
  • 创建文件:touch

Linux根目录下的几个核心目录

  • /bin : 二进制文件
  • /dev : 设备文件
  • /etc : 配置文件
  • /home : 用户的主目录,在 Linux 中,每个用户都有一个自己的目录,一般该目录名是以用户的账号命名的
  • /root: 该目录为系统管理员,也称作超级权限者的用户主目录。
  • /sbin : s 就是 Super User 的意思,是 Superuser Binaries (超级用户的二进制文件) 的缩写,这里存放的是系统管理员使用的系统管理程序。

周日凌晨零点零分定期备份 /user/backup到 /tmp 目录下,如何做?

使用crontab即可做到,如下配置:

crontab -e
0 0 * * 7 /bin/cp /user/backup /tmp

Linux中你怎么排查项目问题?查看项目日志你一般怎么做?

查看tomcat日志,使用tail命令tail -n N filename.txt 。n是查看行数

怎么查看进程

ps -ef | grep 软件名

常用的压缩命令

使用 : tar -zcvf压缩 , tar -zxvf 解压缩

或者使用: zip 压缩成zip, unzip解压

部署过项目么?大概讲一讲如何部署的

单体应用的部署是比较简单的,前端打包上传使用Nginx,后端打包成war,可以使用Tomcat来部署,如果是SpringBoot的可以默认打包为jar,直接java -jar 启动。如果用到其他组件,比如Redis可以直接在服务器安装,然后项目指向其IP即可。

如果项目的组成部分比较多,比如:项目后端,前端,Redis,Mysql等等都涉及到,那么可以使用Docker来部署,这样更好管理应用之间的内存和资源分配。

你们这个服务器的配置是怎么样的

我们微服务有20个服务器,业务系统是8核CPU,16G内存,有些服务配置还要低一些,视频处理系统是12核CPU,24G内存。通过NFS方式共享20T硬盘。

Docker

讲讲什么是Docker

docker是一个容器技术,最大的好处是做资源的分配和管理,传统的linux部署项目不好管理内存等资源的分配,造成了应用之间资源竞争的情况,Dcoker的出现解决了这一问题。我们可以把我们的应用打包成Docker的镜像,然后启动成容器。容器和容器之间相互隔离也可以互相通信。就类似于有多个主机一样。

讲几个Docker的命令

docker images :查看本地镜像

docker search : 搜索镜像

docker pull : 下载镜像

docker push : 上传镜像到仓库

docker rmi : 删除镜像

docker run : 创建并启动一个容器

docker ps : 查看容器

docker rm :删除容器

docker stop : 停止容器

docker kill :停止容器

docker start : 启动容器

docker exec -it 容器名 /bin/bash : 进入容器

docker exit :退出容器

docker cp : 拷贝文件到容器,或者从容器中拷贝文件到linux

docker logs : 查看容器的日志

怎么把文件上传到容器中

docker cp 或者在启动容器的时候增加 -v 做目录映射

某个服务不可访问了你怎么排查

服务不可访问,那就是容器出问题了,我会去找到对应的容器是不是挂了,或者使用docker logs 查看日志根据错误日志来排错。

容器之间怎么通信

使用容器IP通信,但是容器重启IP会变动,不建议

使用端口映射也可以通信,但是内网部署的应用不需要做端口映射,所以这个不建议用

使用--link 名字进行通信

使用桥接网络通信

对于Redis和zuul网关你怎么部署

首先肯定要下载一个redis的镜像, 对于zuul的镜像可以使用docker插件对zuul进行打包。

redis是不需要暴露给外网的,所以不要做端口映射,可以使用--link或桥接网络通信 ,而zuul是服务访问入口需要做端口映射进行外网部署。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容