核心篇
数据结构与算法
网路:TCP/IP, HTTP
操作系统, 文件, shell, CPU, IO, epoll, 非阻塞IO,
进程/线程/协程,锁
HashMap, ConcurrentHashMap实现原理, 链表, 红黑树
git
maven
缓存:各种缓存, redis zset与跳跃表
高并发,高可用,降级,限流,容灾,弱依赖
分布式框架
架构设计, clean code, DDD, API 设计
数据库, 索引与B+树,SQL调优, WAL, 2阶段提交, CAP与BASE
class字节码,序列化, JVM, 内存模型, GC
多线程与并发编程
Java, Kotlin, JavaScript, 编程语言
Spring, SpringBoot, Spring MVC, MyBatis
OOP , FP, 响应式
大数据, 分布式文件存储,MapReduce, Hadoop, Spark, Storm, Flink, AI
.....
首先我们需要明白一个事实,招聘的一个很关键的因素是在给自己找未来的同事,同级别下要找比自己优秀的人,面试是一个双向选择的过程,也是一个将心比心去沟通的过程。
就像我们有的人感觉自己很牛逼,但是拿不到offer,而其他的人菜的一笔,却可以拿到offer,我们称之为玄学offer,遇到这种情况大家也不要感觉到有什么不可描述的心情,一切随缘即可!
必备Java 面试题
小编这里可是有Java面试题参考答案的哟,需要各位小伙伴下来逐一学习!
一、开场白
简单的介绍一下自己的工作经历与职责,在校或者工作中主要的工作内容,主要负责的内容;(你的信息一清二白的写在简历上,能答出来的最好写在上面,模棱两可不是很清楚的最好不要写,否则会被问的很尴尬)
介绍下自己最满意的,有技术亮点的项目或平台,重点介绍下自己负责那部分的技术细节;(主要对自己做过的事情是否有清晰的描述)
二、Java基础
什么是字符串常量池?
String为什么是不可变的?
String s = new String("xyz");究竟产生了几个对象,从JVM角度谈谈?
String拼接字符串效率低,你知道原因吗?
你真的了解String的常见API吗?
Java中的subString()真的会引起内存泄露么?
浅析Java中的final关键字?
浅析Java中的static关键字?
你对Java中的volatile关键字了解多少?
i++是线程安全的吗?如何解决线程安全性?
从字节码角度深度解析 i++ 和 ++i 线程安全性原理?
请谈谈什么是CAS?
从源码角度看看ArrayList的实现原理?
手写LinkedList的实现,彻底搞清楚什么是链表?
Java中方法参数的传递规则?
Java中throw和throws的区别是什么?
重载和重写的区别?
手写ArrayList的实现,在笔试中如何过关斩将?
finally语句块你踩过哪些坑?
为什么重写equals方法需同时重写hashCode方法?
equals() 与 == 的区别?
StringBuffer和StringBuilder的区别,从源码角度分析?
你知道HashMap的数据结构吗?
为何HashMap的数组长度一定是2的次幂?
HashMap何时扩容以及它的扩容机制?
HashMap的key一般用字符串,能用其他对象吗?
HashMap的key和value都能为null么?如果key能为null,那么它是怎么样查找值的?
HashMap是线程安全的吗?如何实现线程安全?
从源码角度分析HashSet实现原理?
HashTable与HashMap的实现原理有什么不同?
String方法intern() 你真的会用吗?
什么是自动拆装箱?
String.valueOf和Integer.toString的区别?
三、Java多线程
线程的生命周期包括哪几个阶段?
多线程有几种实现方式?
请谈谈什么是进程,什么是线程?
启动线程是用start()方法还是run()方法?
说说线程安全问题,什么实现线程安全,如何实现线程安全?
sychronized和Lock的区别?
sleep()和wait()的区别?
深入分析ThreadLocal的实现原理?
你看过AbstractQueuedSynchronizer源码阅读吗,请说说实现原理?
谈谈对synchronized的偏向锁、轻量级锁、重量级锁的理解?
通过三种方式实现生产者消费者模式?
JVM层面分析sychronized如何保证线程安全的?
JDK层面分析sychronized如何保证线程安全的?
如何写一个线程安全的单例?
通过AQS实现一个自定义的Lock?
ThreadLocal什么时候会出现OOM的情况?为什么?
为什么wait, notify 和 notifyAll这些方法不在thread类里面?
你真的理解CountDownLatch与CyclicBarrier使用场景吗?
出现死锁,如何排查定位问题?
notify和notifyAll的区别?
线程池启动线程submit和execute有什么不同?
SimpleDateFormat是线程安全的吗?如何解决?
请谈谈ConcurrentHashmap底层实现原理?
使用synchronized修饰静态方法和非静态方法有什么区别?
当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其方法?
线程池的原理,为什么要创建线程池?创建线程池的方式?
创建线程池有哪几个核心参数? 如何合理配置线程池的大小?
synchronized修饰的静态方法和非静态方法有什么区别?
四、Java Web
什么是Servlet,Servlet生命周期方法?
什么Session和Cookie,它们之间有什么联系?
JSP的八个隐含对象有哪些?
JSP的四个域对象的作用范围?
Post和Get请求的区别?
转发和重定向有什么区别?
JSP自定义标签,如何实现循环打印功能?
Http1.0和Http1.1的区别是什么?
拦截器与过滤器的区别?
五、JVM面试题
JVM内存区域如何划分?
JVM堆中对象是如何创建的?
JVM对象的结构?
JVM垃圾回收-如何判断对象是否是垃圾对象?
JVM垃圾回收算法有哪些?
JVM垃圾收集器有哪些?
JVM内存是如何分配的?
从一道面试题分析类的加载过程?
JVM双亲委派机制?
JVM可以作为GC Root的对象有哪些?
请写出几段可以导致内存溢出、内存泄漏、栈溢出的代码?
哪些情况会导致Full GC?
频繁GC问题或内存溢出问题,如何定位?
六、SQL性能优化
数据库三范式是什么?
数据库的事务、ACID及隔离级别?
不考虑事务的隔离性,容易产生哪三种情况?
数据库连接池原理?
什么是B-Tree?
什么是B+Tree?
MySQL数据库索引结构?
什么是索引?什么条件适合建立索引?什么条件不适合建立索引?
索引失效的原因有哪些?如何优化避免索引失效?
MySQL如何启动慢查询日志?
MySQL如何使用show Profile进行SQL分析?
一条执行慢的SQL如何进行优化,如何通过Explain+SQL分析性能?
什么是行锁、表锁、读锁、写锁,说说它们各自的特性?
什么情况下行锁变表锁?
什么情况下会出现间隙锁?
谈谈你对MySQL的in和exists用法的理解?
MySQL的数据库引擎有哪些,如何确定在项目中要是用的存储引擎?
count(*)、count(列名)和count(1)的区别?
union和union all的区别?
七、Spring框架
Spring的IOC和AOP机制?
Spring中Autowired和Resource关键字的区别?
依赖注入的方式有几种,各是什么?
Spring容器对Bean组件是如何管理的?
Spring容器如何创建?
Spring事务分类?
Spring事务的传播特性?
Spring事务的隔离级别?
Spring的通知类型有哪些?
八、SpringMVC框架
SpringMVC完整工作流程,熟读源码流程?
SpringMVC如何处理JSON数据?
SpringMVC拦截器原理,如何自定义拦截器?
SpringMVC如何将请求映射定位到方法上面?结合源码阐述?
SpringMVC常见注解有哪些?
SpringMVC容器和Spring容器的区别?
SpringMVC的控制器是不是单例模式,如果是,有什么问题,怎么解决?
九、MyBatis框架
MyBatis中#和$的区别?
MyBatis一级缓存原理以及失效情况?
MyBatis二级缓存的使用?
MyBatis拦截器原理?
看过MyBatis源码吗,请说说它的工作流程?
十、Java高级部分
Dubbo负载均衡策略?
Dubbo中Zookeeper做注册中心,如果注册中心集群都挂掉,发布者和订阅者之间还能通信么?
Dubbo完整的一次调用链路介绍?
请说说SpringBoot自动装配原理?
有用过SpringCloud吗,请说说SpringCloud和Dubbo有什么不一样?
什么是WebService,如何基于WebService开发接口?
谈谈项目中分布式事务应用场景?
使用Redis如何实现分布式锁?
请谈谈单点登录原理?
Tomcat如何优化?
后台系统怎么防止请求重复提交?
Linux常见命令有哪些?
请说说什么是Maven的依赖、继承以及聚合?
Git暂存区和工作区的区别?
Git如何创建、回退以及撤销版本?
常见的设计模式有哪些?
十一、其他
看过哪些源代码?然后会根据你说的源码问一些细节的问题?(这里主要考察面试者是否对技术有钻研的精神,还是只停留在表面,还是背了几道面经,这个对于很多有强迫症的面试官,如果你连源码都没看过,基本上是会pass掉的!)
项目中遇到了哪些比较有挑战性的问题,是如何解决的;(这个很有争议,一方面是你连一个复杂的问题都解决不了,要你过来干什么,还有就是,我的能力牛逼啊,但是公司没有业务场景让我展示啊!这个就看你遇到的面试官了,祝你好运!)
java基础以及多个“比较”
1.Collections.sort排序内部原理
在Java 6中Arrays.sort()和Collections.sort()使用的是MergeSort,而在Java 7中,内部实现换成了TimSort,其对对象间比较的实现要求更加严格
2.hashMap原理,java8做的改变
从结构实现来讲,HashMap是数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的。HashMap最多只允许一条记录的键为null,允许多条记录的值为null。HashMap非线程安全。ConcurrentHashMap线程安全。解决碰撞:当出现冲突时,运用拉链法,将关键词为同义词的结点链接在一个单链表中,散列表长m,则定义一个由m个头指针组成的指针数组T,地址为i的结点插入以T(i)为头指针的单链表中。Java8中,冲突的元素超过限制(8),用红黑树替换链表。
3.String 和 StringBuilder 的区别
1)可变与不可变:String不可变,每一次执行“+”都会新生成一个新对象,所以频繁改变字符串的情况中不用String,以节省内存。
2)是否多线程安全:StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。StringBuffer和String均线程安全。
4.Vector 与 Array 的区别
1)ArrayList在内存不够时默认是扩展50% + 1个,Vector是默认扩展1倍。
2)Vector属于线程安全级别的,但是大多数情况下不使用Vector,因为线程安全需要更大的系统开销。
5.HashMap 与 Hashtable 的区别
1) 历史原因: Hashtable继承Dictonary类, HashMap继承自abstractMap
2) HashMap允许空的键值对, 但最多只有一个空对象,而HashTable不允许。
3) HashTable同步,而HashMap非同步,效率上比HashTable要高
6.ConncurrentHashMap和hashtable比较(两个线程并发访问map中同一条链,一个线程在尾部删除,一个线程在前面遍历查找,问为什么前面的线程还能正确的查找到后面被另一个线程删除的节点)
ConcurrentHashMap融合了hashtable和hashmap二者的优势。hashtable是做了同步的,即线程安全,hashmap未考虑同步。所以hashmap在单线程情况下效率较高。hashtable在的多线程情况下,同步操作能保证程序执行的正确性。但是hashtable是阻塞的,每次同步执行的时候都要锁住整个结构,ConcurrentHashMap正是为了解决这个问题而诞生的,
ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术(一个Array保存多个Object,使用这些对象的锁作为分离锁,get/put时随机使用任意一个)。它使用了多个锁来控制对hash表的不同部分进行的修改。在JDK 1.6中,有HashEntry结构存在,每次插入将新添加节点作为链的头节点(同HashMap实现),而且每次删除一个节点时,会将删除节点之前的所有节点拷贝一份组成一个新的链,而将当前节点的上一个节点的next指向当前节点的下一个节点,从而在删除以后有两条链存 在,因而可以保证即使在同一条链中,有一个线程在删除,而另一个线程在遍历,它们都能工作良好,因为遍历的线程能继续使用原有的链。
Java8中,采用volatile HashEntry保存数据,table元素作为锁;从table数组+单向链表加上了红黑树。红黑树是一种特别的二叉查找树,特性为:1.节点为红或者黑 2.根节点为黑 3.叶节点为黑 4.一节点为红,则叶节点为黑 5.一节点到其子孙节点所有路径上的黑节点数目相同。
7.ArrayList与 LinkedList 的区别?
最明显的区别是
ArrrayList 底层的数据结构是数组,支持随机访问,而 LinkedList 的底层数据结构书链表,不支持随机访问。使用下标访问一个元素,ArrayList 的时间复杂度是 O(1),而 LinkedList 是 O(n)。LinkedList是双向链表
8.Java 中,Comparator 与Comparable 有什么不同?
Comparable 接口用于定义对象的自然顺序,是排序接口,而 comparator 通常用于定义用户定制的顺序,是比较接口。我们如果需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口),那么我们就可以建立一个“该类的比较器”来进行排序。Comparable 总是只有一个,但是可以有多个 comparator 来定义对象的顺序。
9.抽象类是什么?它与接口有什么区别?你为什么要使用过抽象类?
抽象类是指不允许被实例化的类;一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。
abstract class和interface所反映出的设计理念不同。其实abstract class表示的是"is-a"关系,interface表示的是"like-a"关系
实现抽象类和接口的类必须实现其中的所有方法。抽象类中可以有非抽象方法。接口中则不能有实现方法。但在Java8中允许接口中有静态默认的方法。
接口中定义的变量默认是public static final 型,且必须给其初值,所以实现类中不能重新定义,也不能改变其值。抽象类中的变量默认是 friendly 型,其值可以在子类中重新定义,也可以重新赋值。
子类中实现父类中的抽象方法时,可见性可以大于等于父类中的;而接口实现类中的接口 方法的可见性只能与接口中相同(public)。
用抽象类是为了重用。减少编码量,降低耦合性。
10.描述 Java 中的重载和重写?
重载和重写都允许你用相同的名称来实现不同的功能,但是重载是编译时活动,而重写是运行时活动。你可以在同一个类中重载方法,但是只能在子类中重写方法。重写必须要有继承
重写:1、在子类中可以根据需要对从基类中继承来的方法进行重写。2、重写的方法和被重写的方法必须具有相同方法名称、参数列表和返回类型。3、重写方法不能使用比被重写的方法更严格的访问权限。
重载的时候,方法名要一样,但是参数类型和个数不一样,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准。
11.Collection与Collections的区别是什么?
Collection是Java集合框架中的基本接口;
Collections是Java集合框架提供的一个工具类,其中包含了大量用于操作或返回集合的静态方法。
12.Java中多态的实现原理
所谓多态,指的就是父类引用指向子类对象,调用方法时会调用子类的实现而不是父类的实现。多态的实现的关键在于“动态绑定”。
13.object中定义了哪些方法?
clone(), equals(), hashCode(), toString(), notify(), notifyAll(),
wait(), finalize(), getClass()
14.Java泛型和类型擦除?
泛型即参数化类型,在创建集合时,指定集合元素的类型,此集合只能传入该类型的参数。类型擦除:java编译器生成的字节码不包含泛型信息,所以在编译时擦除:1.泛型用最顶级父类替换;2.移除。
15.说出 5 个 JDK 1.8 引入的新特性?
Java 8 在 Java 历史上是一个开创新的版本,下面 JDK 8 中 5 个主要的特性:
Lambda 表达式;允许像对象一样传递匿名函数 Stream API,充分利用现代多核 CPU,可以写出很简洁的代码 ;Date 与 Time API,最终,有一个稳定、简单的日期和时间库可供你使用 扩展方法,现在,接口中可以有静态、默认方法; 重复注解,现在你可以将相同的注解在同一类型上使用多次。
16.java中public,private,protected以及默认关键字的访问范围:
Protected可在包内及包外子类访问,default只能同一包内访问,prvate只能同一类
17. 常用数据结构:
集合,线性结构(数组,队列,链表和栈),树形结构,图状结构
18.Java 中的 TreeMap 是采用什么树实现的?(答案)
Java 中的 TreeMap 是使用红黑树实现的。
19. 匿名内部类是什么?如何访问在其外面定义的变量?
匿名内部类也就是没有名字的内部类,匿名内部类只能使用一次,它通常用来简化代码编写。
匿名内部类只能访问外部类的Final变量. Java 8更加智能:如果局部变量被匿名内部类访问,那么该局部变量相当于自动使用了final修饰。
20. 如何创建单例模式?说了双重检查,他说不是线程安全的。如何高效的创建一个线程安全的单例?
一种是通过枚举,一种是通过静态内部类。
21.poll() 方法和 remove() 方法的区别?
poll() 和
remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常。
22.写一段代码在遍历 ArrayList 时移除一个元素
使用迭代器。
Iterator itr = list.iterator();
while(itr.hasNext()) {if(…) { itr.remove();} }
JVM
1.JVM如何加载一个类的过程,双亲委派模型中有哪些方法
类加载过程:加载、验证(验证阶段作用是保证Class文件的字节流包含的信息符合JVM规范,不会给JVM造成危害)、准备(准备阶段为变量分配内存并设置类变量的初始化)、解析(解析过程是将常量池内的符号引用替换成直接引用)、初始化。
双亲委派模型中方法:双亲委派是指如果一个类收到了类加载的请求,不会自己先尝试加载,先找父类加载器去完成。当顶层启动类加载器表示无法加载这个类的时候,子类才会尝试自己去加载。当回到最开的发起者加载器还无法加载时,并不会向下找,而是抛出ClassNotFound异常。
方法:启动(Bootstrap)类加载器,标准扩展(Extension)类加载器,应用程序类加载器(Application ),上下文(Custom)类加载器。意义是防止内存中出现多份同样的字节码 。
2.GC算法(什么样的对象算是可回收对象,可达性分析),CMS收集器
jvm是如何判断一个对象已经变成了可回收的“垃圾”,一般是两个方法:引用记数法和根搜索算法。引用记数法没办法解决循环引用的问题,所以用根搜索。从一系列的”GC Roots“对象开始向下搜索,搜索走过的路径称为引用链。当一个对象到”GC Roots“之间没有引用链时,被称为引用不可达。引用不可到的对象被认为是可回收的对象。
几种垃圾收集器:1,Serial New/Serial Old(串行),2,Parrallel New (并行),3,Parrallel Scavenge,4,Parrallel Old,5,CMS(CMS收集器是一个以获得最短回收停顿时间为目标的收集器,它是一种并发收集器,采用的是Mark-sweep算法。),6,G1(是一款并行与并发收集器,并且可建立可预测的停顿时间模型,整体上是基于标记清理,局部采用复制)
3.JVM分为哪些区,每一个区干吗的?
1)方法区(method):被所有的线程共享。方法区包含所有的类信息和静态变量。
2)堆(heap):被所有的线程共享,存放对象实例以及数组,Java堆是GC的主要区域。
3)栈(stack):每个线程包含一个栈区,栈中保存一些局部变量等。
4)程序计数器:是当前线程执行的字节码的行指示器。
4.JVM新生代,老年代,持久代,都存储哪些东西?
持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大。所有新生成的对象首先都是放在年轻代的,年老代中存放的都是一些生命周期较长的对象。
5.内存溢出和内存泄漏:
内存溢出:程序申请内存时,没有足够的内存,out of memory;内存泄漏值垃圾对象无法回收,可以使用memory analyzer工具查看泄漏。
6.进程与线程:
进程值运行中的程序(独立性,动态性,并发性),线程指进程中的顺序执行流。区别是:1.进程间不共享内存 2.创建进程进行资源分配的代价要大得多,所以多线程在高并发环境中效率高。
7.序列化与反序列化:
序列化指将java对象转化为字节序列,反序列化相反。主要是为了java线程间通讯,实现对象传递。只有实现了Serializable或Externalizable接口类对象才可被序列化。
8.64 位 JVM 中,int 的长度是多数?
Java 中,int 类型变量的长度是一个固定值,与平台无关,都是 32 位。意思就是说,在 32 位 和 64 位 的Java 虚拟机中,int 类型的长度是相同的。
9.Java 中 WeakReference 与 SoftReference的区别?
Java中一共有四种类型的引用。StrongReference、 SoftReference、 WeakReference 以及 PhantomReference。
StrongReference 是 Java 的默认引用实现, 它会尽可能长时间的存活于 JVM 内,当没有任何对象指向它时将会被GC回收
WeakReference,顾名思义, 是一个弱引用, 当所引用的对象在
JVM 内不再有强引用时, 将被GC回收
虽然 WeakReference 与 SoftReference 都有利于提高 GC 和 内存的效率,但是 WeakReference ,一旦失去最后一个强引用,就会被 GC 回收,而 SoftReference 会尽可能长的保留引用直到 JVM 内存不足时才会被回收(虚拟机保证), 这一特性使得
SoftReference 非常适合缓存应用
10.解释 Java 堆空间及 GC?
当通过 Java 命令启动
Java 进程的时候,会为它分配内存。内存的一部分用于创建堆空间,当程序中创建对象的时候,就从对空间中分配内存。GC 是 JVM 内部的一个进程,回收无效对象的内存用于将来的分配。
11.Java 中堆和栈有什么区别?
JVM 中堆和栈属于不同的内存区域,使用目的也不同。栈常用于保存方法帧和局部变量,而对象总是在堆上分配。栈通常都比堆小,也不会在多个线程之间共享,而堆被整个 JVM 的所有线程共享。
并发,锁
1.volatile关键字, Lock
并发编程中:原子性问题,可见性问题,有序性问题。
volatile关键字能保证可见性,字能禁止指令重排序,但是不能保证原子性。可见性只能保证每次读取的是最新的值,但是volatile没办法保证对变量的操作的原子性。在生成的会变语句中加入Lock关键字和内存屏障。
Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题。用sychronized修饰的方法或者语句块在代码执行完之后锁自动释放,而用Lock需要我们手动释放锁
2.MYSQL常用优化(sql优化,表结构优化等)
SQL优化、表机构优化、索引优化、缓存参数优化
3.java每改一点都需要重新编译打包部署,有没有更好的方法
可以使用热加载
4.进程间通信有哪几种方式?
1)管道(Pipe),2)命名管道(named pipe),3)信号(Signal),4)消息(Message)队列,5)共享内存,6)内存映射(mapped memory),7)信号量(semaphore),8)套接口(Socket)
5.Sychronized修饰静态方法,锁定类本身而不是实例,非静态方法锁定实例。
6. 操作系统什么情况下会死锁?
所谓死锁:是指多个进程在运行过程中因争夺资源而造成的一种僵局。产生的原因:竞争资源:当系统中多个进程使用共享资源,并且资源不足以满足需要,会引起进程对资源的竞争而产生死锁。进程间推进的顺序非法:请求和释放资源的顺序不当,也同样会导致产生进程死锁
7.产生死锁的四个条件:
1.互斥条件(进程独占资源)2.请求与保持(进程因请求资源而阻塞时,对已获得的资源保持不放) 3.不剥夺条件(进程已获得的资源,在末使用完之前,不能强行剥夺) 4.循环等待(若干进程之间形成一种头尾相接的循环等待资源关系)
8. 如何理解分布式锁?
由于在平时的工作中,线上服务器是分布式多台部署的,经常会面临解决分布式场景下数据一致性的问题,那么就要利用分布式锁来解决这些问题。
9. 线程同步与阻塞的关系?同步一定阻塞吗?阻塞一定同步吗?
线程同步与否 跟 阻塞非阻塞没关系,同步是个过程,阻塞是线程的一种状态。多个线程操作共享变量时可能会出现竞争。这时需要同步来防止两个以上的线程同时进入临界区内,在这个过程中后进入临界区的线程将阻塞,等待先进入的线程走出临界区。
10. 同步和异步有什么区别?
同步和异步最大的区别就在于。一个需要等待,一个不需要等待。同步可以避免出现死锁,读脏数据的发生,一般共享某一资源的时候用,如果每个人都有修改权限,同时修改一个文件,有可能使一个人读取另一个人已经删除的内容,就会出错,同步就会按顺序来修改。
11. 线程池
根据系统自身的环境情况,有效的限制执行线程的数量,使得运行效果达到最佳。线程主要是通过控制执行的线程的数量,超出数量的线程排队等候,等待有任务执行完毕,再从队列最前面取出任务执行
12. 如何调用 wait()方法?使用 if 块还是循环?为什么?
wait() 方法应该在循环调用,因为当线程获取到 CPU 开始执行的时候,其他条件可能还没有满足,所以在处理前,循环检测条件是否满足会更好。
wait(),notify()和notifyall()方法是java.lang.Object类为线程提供的用于实现线程间通信的同步控制方法。等待或者唤醒
13. 实现线程的几种方法
(1)继承Thread类,重写run函数
(2)实现Runnable接口,重写run函数
(3)实现Callable接口,重写call函数
14. 什么是多线程环境下的伪共享(false sharing)?
伪共享是多线程系统(每个处理器有自己的局部缓存)中一个众所周知的性能问题。缓存系统中是以缓存行(cache line)为单位存储的。缓存行是2的整数幂个连续字节,一般为32-256个字节。最常见的缓存行大小是64个字节。当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。
网络、数据库
1.TCP如何保证可靠传输?三次握手过程?
在TCP的连接中,数据流必须以正确的顺序送达对方。TCP的可靠性是通过顺序编号和确认(ACK)来实现的。TCP 连接是通过三次握手进行初始化的。三次握手的目的是同步连接双方的序列号和确认号并交换 TCP 窗口大小信息。第一次是客户端发起连接;第二次表示服务器收到了客户端的请求;第三次表示客户端收到了服务器的反馈。
2. Linux下你常用的命令有哪些?
1. cd命令用来改变所在目录。cd / 转到根目录中cd ~ 转到用户目录下
2. ls命令用来查看目录的内容。
3. cp命令用来拷贝文件cp
4.mv命令 mv t.txt Document 把文件t.txt 移动到目录Document中。
3. 常用的hash算法有哪些?
1.加法hash:所谓的加法Hash就是把输入元素一个一个的加起来构成最后的结果。
2.位运算hash:这类型Hash函数通过利用各种位运算(常见的是移位和异或)来充分的混合输入元素
3.乘法hash:33*hash + key.charAt(i)
4. 什么是一致性哈希?
设计目标是为了解决因特网中的热点(Hot spot)问题,一致性hash算法提出了在动态变化的Cache环境中,判定哈希算法好坏的四个定义:1、平衡性(Balance) 2、单调性(Monotonicity) 3、分散性(Spread) 4、负载(Load)
5. 数据库中的范式有哪些?
第一范式----数据库中的表(所有字段值)都是不可分割的原子数据项。
第二范式----数据库表中的每一列都和主键相关,而不能只和主键的某一部分相关。
第三范式----数据库表中每一列数据都和主键直接相关,不能间接相关。范式是为了减小数据冗余。
6. 数据库中的索引的结构?什么情况下适合建索引?
数据库中索引的结构是一种排序的数据结构,数据库索引是通过B树和变形的B+树实现的。什么情况下不适合建立索引:1.对于在查询过程中很少使用或参考的列;对于那些只有很少数据值的列;对于那些定义为image,text和bit数据类型的列;当修改性能远大于检索性能。
根据系统自身的环境情况,有效的限制执行线程的数量,使得运行效果达到最佳。线程主要是通过控制执行的线程的数量,超出数量的线程排队等候,等待有任务执行完毕,再从队列最前面取出任务执行
7. concurrent包下面,都用过什么?
java.util.concurrent、java.util.concurrent.atomic和java.util.concurrent.lock
8. 常用的数据库有哪些?redis用过吗?
…
9. 你知道的开源协议有哪些?
GPL (GNU General Public License) :GNU通用公共许可协议
LGPL (GNU Lesser General Public License) :GNU宽通用公共许可协议
BSD
(Berkeley Software Distribution) :伯克利软件分发许可协议
MIT (Massachusetts Institute of Technology):MIT之名源自麻省理工学院
Apache (Apache License) :Apache许可协议
MPL (Mozilla Public License) :Mozilla公共许可协议
10.表单提交中,get和post区别
1.get从服务器获取信息,post向服务器传信息
2.get传送数据量比较小,post可以比较大
3.get安全性比较低
11. TCP 协议与 UDP 协议有什么区别?(answer答案)
TCP(Tranfer Control Protocol)的缩写,是一种面向连接的保证传输的协议,在传输数据流前,双方会先建立一条虚拟的通信道。可以很少差错传输数据。
UDP(User DataGram Protocol)的缩写,是一种无连接的协议,使用UDP传输数据时,每个数据段都是一个独立的信息,包括完整的源地址和目的地,在网络上以任何可能的 路径传到目的地,因此,能否到达目的地,以及到达目的地的时间和内容的完整性都不能保证。
所以TCP必UDP多了建立连接的时间。相对UDP而言,TCP具有更高的安全性和可靠性。
TCP协议传输的大小不限制,一旦连接被建立,双方可以按照一定的格式传输大量的数据,而UDP是一个不可靠的协议,大小有限制,每次不能超过64K。
参考:https://www.jianshu.com/p/ce1fb8497883
来看 208 道面试题,具体的内容。
一、Java 基础
1.JDK 和 JRE 有什么区别?
2.== 和 equals 的区别是什么?
3.两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?
4.final 在 java 中有什么作用?
5.java 中的 Math.round(-1.5) 等于多少?
6.String 属于基础的数据类型吗?
7.java 中操作字符串都有哪些类?它们之间有什么区别?
8.String str="i"与 String str=new String(“i”)一样吗?
9.如何将字符串反转?
10.String 类的常用方法都有那些?
11.抽象类必须要有抽象方法吗?
12.普通类和抽象类有哪些区别?
13.抽象类能使用 final 修饰吗?
14.接口和抽象类有什么区别?
15.java 中 IO 流分为几种?
16.BIO、NIO、AIO 有什么区别?
17.Files的常用方法都有哪些?
二、容器
18.java 容器都有哪些?
19.Collection 和 Collections 有什么区别?
20.List、Set、Map 之间的区别是什么?
21.HashMap 和 Hashtable 有什么区别?
22.如何决定使用 HashMap 还是 TreeMap?
23.说一下 HashMap 的实现原理?
24.说一下 HashSet 的实现原理?
25.ArrayList 和 LinkedList 的区别是什么?
26.如何实现数组和 List 之间的转换?
27.ArrayList 和 Vector 的区别是什么?
28.Array 和 ArrayList 有何区别?
29.在 Queue 中 poll()和 remove()有什么区别?
30.哪些集合类是线程安全的?
31.迭代器 Iterator 是什么?
32.Iterator 怎么使用?有什么特点?
33.Iterator 和 ListIterator 有什么区别?
34.怎么确保一个集合不能被修改?
三、多线程
35.并行和并发有什么区别?
36.线程和进程的区别?
37.守护线程是什么?
38.创建线程有哪几种方式?
39.说一下 runnable 和 callable 有什么区别?
40.线程有哪些状态?
41.sleep() 和 wait() 有什么区别?
42.notify()和 notifyAll()有什么区别?
43.线程的 run()和 start()有什么区别?
44.创建线程池有哪几种方式?
45.线程池都有哪些状态?
46.线程池中 submit()和 execute()方法有什么区别?
47.在 java 程序中怎么保证多线程的运行安全?
48.多线程锁的升级原理是什么?
49.什么是死锁?
50.怎么防止死锁?
51.ThreadLocal 是什么?有哪些使用场景?
52.说一下 synchronized 底层实现原理?
53.synchronized 和 volatile 的区别是什么?
54.synchronized 和 Lock 有什么区别?
55.synchronized 和 ReentrantLock 区别是什么?
56.说一下 atomic 的原理?
四、反射
57.什么是反射?
58.什么是 java 序列化?什么情况下需要序列化?
59.动态代理是什么?有哪些应用?
60.怎么实现动态代理?
五、对象拷贝
61.为什么要使用克隆?
62.如何实现对象克隆?
63.深拷贝和浅拷贝区别是什么?
六、Java Web
64.jsp 和 servlet 有什么区别?
65.jsp 有哪些内置对象?作用分别是什么?
66.说一下 jsp 的 4 种作用域?
67.session 和 cookie 有什么区别?
68.说一下 session 的工作原理?
69.如果客户端禁止 cookie 能实现 session 还能用吗?
70.spring mvc 和 struts 的区别是什么?
71.如何避免 sql 注入?
72.什么是 XSS 攻击,如何避免?
73.什么是 CSRF 攻击,如何避免?
七、异常
74.throw 和 throws 的区别?
75.final、finally、finalize 有什么区别?
76.try-catch-finally 中哪个部分可以省略?
77.try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
78.常见的异常类有哪些?
八、网络
79.http 响应码 301 和 302 代表的是什么?有什么区别?
80.forward 和 redirect 的区别?
81.简述 tcp 和 udp的区别?
82.tcp 为什么要三次握手,两次不行吗?为什么?
83.说一下 tcp 粘包是怎么产生的?
84.OSI 的七层模型都有哪些?
85.get 和 post 请求有哪些区别?
86.如何实现跨域?
87.说一下 JSONP 实现原理?
九、设计模式
88.说一下你熟悉的设计模式?
89.简单工厂和抽象工厂有什么区别?
十、Spring/Spring MVC
90.为什么要使用 spring?
91.解释一下什么是 aop?
92.解释一下什么是 ioc?
93.spring 有哪些主要模块?
94.spring 常用的注入方式有哪些?
95.spring 中的 bean 是线程安全的吗?
96.spring 支持几种 bean 的作用域?
97.spring 自动装配 bean 有哪些方式?
98.spring 事务实现方式有哪些?
99.说一下 spring 的事务隔离?
100.说一下 spring mvc 运行流程?
101.spring mvc 有哪些组件?
102.@RequestMapping 的作用是什么?
103.@Autowired 的作用是什么?
十一、Spring Boot/Spring Cloud
104.什么是 spring boot?
105.为什么要用 spring boot?
106.spring boot 核心配置文件是什么?
107.spring boot 配置文件有哪几种类型?它们有什么区别?
108.spring boot 有哪些方式可以实现热部署?
109.jpa 和 hibernate 有什么区别?
110.什么是 spring cloud?
111.spring cloud 断路器的作用是什么?
112.spring cloud 的核心组件有哪些?
十二、Hibernate
113.为什么要使用 hibernate?
114.什么是 ORM 框架?
115.hibernate 中如何在控制台查看打印的 sql 语句?
116.hibernate 有几种查询方式?
117.hibernate 实体类可以被定义为 final 吗?
118.在 hibernate 中使用 Integer 和 int 做映射有什么区别?
119.hibernate 是如何工作的?
120.get()和 load()的区别?
121.说一下 hibernate 的缓存机制?
122.hibernate 对象有哪些状态?
123.在 hibernate 中 getCurrentSession 和 openSession 的区别是什么?
124.hibernate 实体类必须要有无参构造函数吗?为什么?
十三、Mybatis
125.mybatis 中 #{}和 ${}的区别是什么?
126.mybatis 有几种分页方式?
127.RowBounds 是一次性查询全部结果吗?为什么?
128.mybatis 逻辑分页和物理分页的区别是什么?
129.mybatis 是否支持延迟加载?延迟加载的原理是什么?
130.说一下 mybatis 的一级缓存和二级缓存?
131.mybatis 和 hibernate 的区别有哪些?
132.mybatis 有哪些执行器(Executor)?
133.mybatis 分页插件的实现原理是什么?
134.mybatis 如何编写一个自定义插件?
十四、RabbitMQ
135.rabbitmq 的使用场景有哪些?
136.rabbitmq 有哪些重要的角色?
137.rabbitmq 有哪些重要的组件?
138.rabbitmq 中 vhost 的作用是什么?
139.rabbitmq 的消息是怎么发送的?
140.rabbitmq 怎么保证消息的稳定性?
141.rabbitmq 怎么避免消息丢失?
142.要保证消息持久化成功的条件有哪些?
143.rabbitmq 持久化有什么缺点?
144.rabbitmq 有几种广播类型?
145.rabbitmq 怎么实现延迟消息队列?
146.rabbitmq 集群有什么用?
147.rabbitmq 节点的类型有哪些?
148.rabbitmq 集群搭建需要注意哪些问题?
149.rabbitmq 每个节点是其他节点的完整拷贝吗?为什么?
150.rabbitmq 集群中唯一一个磁盘节点崩溃了会发生什么情况?
151.rabbitmq 对集群节点停止顺序有要求吗?
十五、Kafka
152.kafka 可以脱离 zookeeper 单独使用吗?为什么?
153.kafka 有几种数据保留的策略?
154.kafka 同时设置了 7 天和 10G 清除数据,到第五天的时候消息达到了 10G,这个时候 kafka 将如何处理?
155.什么情况会导致 kafka 运行变慢?
156.使用 kafka 集群需要注意什么?
十六、Zookeeper
157.zookeeper 是什么?
158.zookeeper 都有哪些功能?
159.zookeeper 有几种部署模式?
160.zookeeper 怎么保证主从节点的状态同步?
161.集群中为什么要有主节点?
162.集群中有 3 台服务器,其中一个节点宕机,这个时候 zookeeper 还可以使用吗?
163.说一下 zookeeper 的通知机制?
十七、MySql
164.数据库的三范式是什么?
165.一张自增表里面总共有 7 条数据,删除了最后 2 条数据,重启 mysql 数据库,又插入了一条数据,此时 id 是几?
166.如何获取当前数据库版本?
167.说一下 ACID 是什么?
168.char 和 varchar 的区别是什么?
169.float 和 double 的区别是什么?
170.mysql 的内连接、左连接、右连接有什么区别?
171.mysql 索引是怎么实现的?
172.怎么验证 mysql 的索引是否满足需求?
173.说一下数据库的事务隔离?
174.说一下 mysql 常用的引擎?
175.说一下 mysql 的行锁和表锁?
176.说一下乐观锁和悲观锁?
177.mysql 问题排查都有哪些手段?
178.如何做 mysql 的性能优化?
十八、Redis
179.redis 是什么?都有哪些使用场景?
180.redis 有哪些功能?
181.redis 和 memecache 有什么区别?
182.redis 为什么是单线程的?
183.什么是缓存穿透?怎么解决?
184.redis 支持的数据类型有哪些?
185.redis 支持的 java 客户端都有哪些?
186.jedis 和 redisson 有哪些区别?
187.怎么保证缓存和数据库数据的一致性?
188.redis 持久化有几种方式?
189.redis 怎么实现分布式锁?
190.redis 分布式锁有什么缺陷?
191.redis 如何做内存优化?
192.redis 淘汰策略有哪些?
193.redis 常见的性能问题有哪些?该如何解决?
十九、JVM
194.说一下 jvm 的主要组成部分?及其作用?
195.说一下 jvm 运行时数据区?
196.说一下堆栈的区别?
197.队列和栈是什么?有什么区别?
198.什么是双亲委派模型?
199.说一下类加载的执行过程?
200.怎么判断对象是否可以被回收?
201.java 中都有哪些引用类型?
202.说一下 jvm 有哪些垃圾回收算法?
203.说一下 jvm 有哪些垃圾回收器?
204.详细介绍一下 CMS 垃圾回收器?
205.新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别?
206.简述分代垃圾回收器是怎么工作的?
207.说一下 jvm 调优的工具?
208.常用的 jvm 调优的参数都有哪些?
原文:https://blog.csdn.net/sufu1065/article/details/88051083
必备Spring Boot高级面试题
自Spring Boot诞生以来,就引起了业界轰动,目前越来越多的公司技术选型选择拥抱Spring Boot。所以Spring Boot也成为面试必问的问题之一。下面的问题是小胖哥面试了很多候选人后总结出来的,希望对你有所帮助
问
Spring和Spring Boot有什么区别?
答
Spring Framework提供了多种功能,使Web应用程序的开发更加容易。这些功能包括依赖注入,数据绑定,面向方面的编程,数据访问等等。
随着Spring社区的壮大,Spring慢慢变得越来越复杂,不再像开始宣称的那么轻量级。开发应用程序的配置量越来越大令开发者头疼。这时Spring Boot就派上用场了 - 它采用“约定大于配置”的思想简化了配置,对Spring提供的功能和配置而且将一些功能抽象成为“Starter”开箱即用、按需引用。极大地简化了开发。
问
我们如何使用Maven设置Spring Boot应用程序?
答
我们可以像在任何其他库中一样在Maven项目中包含Spring Boot。但是,最好的方法是从spring-boot-starter-parent项目继承并声明依赖于Spring Boot启动器。这样做可以让我们的项目重用Spring Boot的默认设置。
继承spring-boot-starter-parent项目非常简单 - 我们只需要在pom.xml中指定一个parent元素:
我们可以在Maven 中央仓库找到最新版本的 spring-boot-starter-parent。
上面的方式很方便但是并不一定符合实际需要。例如公司要求所有项目依赖构建从一个标准BOM开始,我们就不能按上面的方式进行。
在这种情况下,我们可以进行如下引用:
然后在 dependencies 标签下引用Spring Boot 的starters 就行了。
问
Spring boot 中的starter是什么?
答
依赖管理对于项目至关重要。当项目足够复杂时,管理依赖项可能会变成一场噩梦,因为涉及的组件太多了。
这就是Spring Boot 的starter就派上用场了。每个starter都可以为我们提供所需要的Spring技术的一站式服务。并且以一致的方式传递和管理其他所需的依赖关系。
所有官方starter都在org.springframework.boot组下,其名称以spring-boot-starter-开头 。非官方的starter的名称在前,如mybatis-spring-boot-starter。这种命名模式使得查找启动器变得很容易,尤其是在使用支持按名称搜索依赖关系的IDE时。但是这个不是绝对的,有些开发者可能不遵从这种契约。
目前大概有超过50种官方starter。最常用的是:
spring-boot-starter: 核心启动器,包括自动配置支持,日志记录和YAML
spring-boot-starter-aop: 使用Spring AOP和AspectJ进行面向方面编程的初学者
spring-boot-starter-data-jpa: 使用Spring Data JPA和Hibernate的启动器
spring-boot-starter-jdbc: 用于将JDBC与HikariCP连接池一起使用的启动器
spring-boot-starter-security: 使用Spring Security的启动器
spring-boot-starter-test: 用于测试Spring Boot应用程序的启动器
spring-boot-starter-web: 使用Spring MVC构建Web的启动器,包括RESTful应用程序
其他starter 可去spring.io查询
问
Spring Boot 如何禁用特定的自动配置?
答
如果我们需要禁用特定Spring Boot的自动配置,我们可以使用@EnableAutoConfiguration注解的exclude属性来指示它。如下禁用了
DataSourceAutoConfiguration:
如果我们使用@SpringBootApplication注解。 它具有@EnableAutoConfiguration作为元注解 - 我们同样可以配置exclude属性来禁用自动配置:
我们还可以使用spring.autoconfigure.exclude环境属性禁用自动配置。在application.properties配置文件设置如下也可以达到同样的目的:
问
Spring Boot 如何注册自定义自动配置?
答
要注册自动配置类,我们必须在META-INF /
spring.factories文件的EnableAutoConfiguration 键
下列出其完全限定名称,如果是多个按照以下风格配置:
如果我们使用Maven构建一个项目,那么该文件应放在resources / META-INF目录中。
问
Spring Boot如何根据不同的条件来加载bean?
答
你可在配置中使用@Conditional 系列注解。例如@ConditionalOnMissingBean。此注释的最显着属性是:
value:要检查的bean类型
name:要检查的bean的名称
放置在使用@Bean装饰的方法上时,目标类型默认为方法的返回类型:
表示的意思是如果不存在CustomService类型的bean则初始化并注入该bean。
问
如何将Spring Boot Web应用程序部署为JAR和 WAR文件?
答
传统上,我们将Web应用程序打包为WAR文件,然后将其部署到外部服务器中。这样做可以让我们在同一台服务器上安排多个应用程序。在CPU和内存稀缺的时候,这是节省资源的好方法。
但事情发生了变化。现在计算机硬件相当便宜,并且注意力转向服务器配置。在部署期间配置服务器的一个小错误可能会导致灾难性后果。
Spring通过提供一个插件即spring-boot-maven-plugin来解决这个问题,将Web应用程序打包为可执行的JAR。要包含此插件,只需向pom.xml添加一个插件元素:
有了这个插件,我们将在执行包阶段后得到一个fat JAR 。此JAR包含所有必需的依赖项,包括嵌入式服务器。因此,我们不再需要担心配置外部服务器。
然后我们可以像运行普通的可执行JAR一样运行应用程序。
请注意,必须将pom.xml文件中的packaging元素设置为 jar 才能构建JAR文件:
如果我们不包含这个元素,它也默认为jar。
如果我们想要构建WAR文件,请将包装 元素更改为war:
并将容器依赖关系从打包文件中删除:
执行Maven 包阶段后,我们将拥有一个可部署的WAR文件。
问
如何在Spring Boot启动的时候运行一些逻辑?
答
可以实现Spring Boot 提供的接口 ApplicationRunner 也可以实现接口CommandLineRunner,这两个接口实现方式一样,它们都只提供了一个 run 方法。
问
Spring boot支持哪些外部配置?
答
Spring Boot支持外部配置,允许我们在各种环境中运行相同的应用程序。我们可以使用properties文件,YAML文件,环境变量,系统属性和命令行选项参数来指定配置属性。
然后,我们可以访问使用这些属性@Value注释,经由绑定对象 的@ConfigurationProperties注释,或Environment 环境抽象类注入。
以下是最常见的外部配置来源:
命令行属性:命令行选项参数是以双连字符开头的程序参数,例如-server.port = 8080。Spring Boot将所有参数转换为属性,并将它们添加到环境属性集中。
应用程序属性:应用程序属性是从application.properties文件或其YAML对应文件加载的属性。默认情况下,Spring Boot会在当前目录,类路径根或其config子目录中搜索此文件。
特定于配置文件的属性:特定于配置文件的属性从application- {profile} .properties文件或其YAML对应文件加载。{profile}占位符是指活性轮廓。这些文件与非特定属性文件位于相同位置,并且优先于非特定属性文件。
问
Spring和Spring Boot有什么区别?
答
在为Spring应用程序运行集成测试时,我们必须有一个ApplicationContext。
为了简化测试,Spring Boot为测试提供了一个特殊的注释 @SpringBootTest。此批注从其classes属性指示的配置类创建ApplicationContext。
如果未设置classes属性,Spring Boot将搜索主配置类。搜索从包含测试的包开始,直到找到使用@SpringBootApplication或@SpringBootConfiguration注释的类。
请注意,如果我们使用JUnit 4,我们必须用@RunWith(SpringRunner.class)装饰测试类。可以查阅我前面的关于Spring Boot Mock测试的文章来学习更多的测试方式。
问
Spring Boot Actuator有什么用?
答
Spring Boot Actuator可以帮助你监控和管理Spring Boot应用,比如健康检查、审计、统计和HTTP追踪等。所有的这些特性可以通过JMX或者HTTP endpoints来获得。
Actuator同时还可以与外部应用监控系统整合,比如 Prometheus, Graphite, DataDog, Influx, Wavefront, New Relic等。这些系统提供了非常好的仪表盘、图标、分析和告警等功能,使得你可以通过统一的接口轻松的监控和管理你的应用。
Actuator使用Micrometer来整合上面提到的外部应用监控系统。这使得只要通过非常小的配置就可以集成任何应用监控系统。
将Spring Boot Actuator集成到一个项目中非常简单。我们需要做的就是在pom.xml文件中包含 spring-boot-starter-actuator启动器:
Spring Boot Actuator可以使用HTTP或JMX端点公开操作信息。但是,大多数应用程序都使用HTTP,其中端点的标识和/执行器前缀形成URL路径。
以下是Actuator提供的一些最常见的内置端点:
auditevents: 公开审计事件信息
env: 公开环境属性
health: 显示应用程序运行状况信息
httptrace: 显示HTTP跟踪信息
info: 显示任意应用程序信息
metric: 显示指标信息
mapping: 显示所有@RequestMapping路径的列表
scheduledtasks: 显示应用程序中的计划任务
threaddump: 执行线程转储
beans :所有加载的spring bean
生产使用Actuator务必保护好这些端点,避免未授权的访问请求。
Java 面试 Spring MVC 必问题
Spring MVC是Spring构建在Servlet API上的Web框架。目前大部分的Java Web 开发已经使用Spring MVC 来做。它提供了模型 - 视图 - 控制器架构,可用于开发灵活的Web应用程序。在本教程中,我们将重点关注与之相关的问题,因为它通常是Spring开发人员面试的热点问题。强烈建议收藏!
Q
为什么选择Spring MVC ?
A
Spring MVC 实现了一些明确而且相对低耦合的概念,可以让开发者很容易开发和测试他们的Web应用。这些概念有:
Dispatcher Servlet ——核心Servlet前置控制器,配置在web.xml文件中的。
拦截匹配的请求,Servlet拦截匹配规则要自己定义,把拦截下来的请求,依据相应的规则分发到目标Controller来处理
Controllers ——具体的业务控制器,处理具体请求的业务并响应
View Resolvers ——视图解析器,用于将响应的逻辑视图解析为真正的视图View对象
Views, Models ——Views的主要作用是用于处理响应视图,然后返回给客户端,Models主要用于传递控制方法处理数据到响应视图页面
ModelAndView ——Model 和 View 的复合体
Model and Session Attributes ——对模型属性和会话属性的处理
这些概念都是完全独立而且职责单一。因此Spring MVC给了我们很大的灵活性。它基于接口(提供的实现类),我们可以使用自定义接口配置框架的每个部分。另一个重要的事情是我们不再依赖于特定的视图技术(例如,JSP),可以选择我们最复合业务的视图技术。此外,我们不仅仅在Web应用程序开发中使用Spring MVC,也可以用它创建RESTful Web服务。
Q
SpringMVC的流程是什么?
A
1. 用户发送请求至前端控制器DispatcherServlet;
2. DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handle;
3. 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有)一并返回给DispatcherServlet;
4. DispatcherServlet 调用 HandlerAdapter处理器适配器;
5. HandlerAdapter 经过适配调用 具体处理器(Handler,也叫后端控制器);
6. Handler执行完成返回ModelAndView;
7. HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet;
8. DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析;
9. ViewResolver解析后返回具体View;
10. DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)
11. DispatcherServlet响应用户。
流程图:
Q
@Autowired 注解的规则是什么?
A
@Autowired注解可以使用在成员属性上或方法上,按类型注入Spring bean。这个注解允许Spring解析协作的bean并注入到你业务需要的bean中。
Q
简述一下注解@ModelAttribute 。
A
@ModelAttribute注解是Spring MVC中最重要的注解之一。它将方法参数或方法返回值绑定到命名中的Model属性中,然后将其公开给Web视图。如果我们在方法级别使用它,则表明该方法的目的是添加一个或多个模型属性。另一方面,当用作方法参数时,它表示应从模型中检索参数。如果不存在,我们应该首先实例化它,然后将其添加到Model中。一旦出现在模型中,我们应该填充所有具有匹配名称的请求参数的参数字段。
Q
@Controller和@RestController之间有什么区别?
A
@Controller和@RestController注释之间的主要区别在于@ResponseBody注解功能已经自动包含在@RestController中。这意味着我们不需要使用@ResponseBody来注释我们的处理程序方法。
Q
描述一下 @PathVariable注解 。
A
我们可以使用@PathVariable注解来从请求URI中提取一个特定模版变量的值来作为我们的请求参数。例如 从/user/123提取值123给/user/{id}控制器来获取一个id=123的数据映射关系。需要特别指出的是通过此注解获取的值不会被编码。具体可通过我的文章来获取原因。
Q
如何在Spring MVC中校验参数?
A
Spring MVC 默认支持JSR-303校验规范 。并在Spring-Boot-starter-web中提供了JSR-303规范实现Hibernate Validator。我们可以使用它来进行参数校验。详情可查看我的相关文章。
Q
@RequestBody 和 @ResponseBody是干嘛用的?
A
1. @RequestBody注解用于控制器方法参数上,目的是将Http 请求体转换为领域对象(请求参数)。Spring 通过`HttpMessageConverter`将请求体反序列化到Java对象中,默认使用jackson类库反序列化。
2.@ResponseBody注解使用于Spring MVC控制器中的处理程序方法上,它表明我们将把方法的返回类型直接写入HTTP响应主体而不会将它放在Model中,同样不会将其解释为视图名称。
Q
Spring MVC 拦截器有什么用怎么用?
A
Spring MVC拦截器允许我们拦截客户端请求并在三个地方处理它 - 在处理之前,处理之后或完成之后(在呈现视图时)。拦截器切面处理一些公共逻辑而避免重复处理程序代码(如日志记录),也可以用来更改Spring模型中全局使用的参数。通过以下方式:
org.springframework.web.servlet.handler.HandlerInterceptorAdapter —继承该类
org.springframework.web.servlet.HandlerInterceptor—实现该接口
Q
如何全局处理控制器异常?
A
通过@ControllerAdvice 或者@RestControllerAdvice 和@ExceptionHandler注解组合,通过在方法入参中捕获异常进行处理,举例如下:
@Slf4j
@RestControllerAdvice("cn.felord.manage.api")
public class GlobalExceptionControllerAdvice {
@ExceptionHandler(NullPointerException.class)
public Rest nullPointHandler(HttpServletRequest request, NullPointerException e) {
log.error("空指针啦,赶紧关注公众号:Felordcn", e);
return RestBody.failure(-1, "null point exception");
}
}
Q
如何处理Spring MVC 中的跨域问题?
A
Spring MVC 解决跨域问题主要有以下几种办法:
通过Spring MVC 拦截器来处理,同理servlet中的filter也可以处理。
通过在控制层方法使用@CrossOrigin注解。 请注意该方案需要在Spring MVC 4.x 以上。
通过在Spring MVC xml配置文件中的<mvc:cors>标签中配置。
通过`WebMvcConfigurer#addCorsMappings(CorsRegistry)`来配置。
如果想具体深入可通过公众号:Felordcn 来获取具体的教程。
Q
如何格式化Spring MVC如参参数?
A
一般可通过两种方式:
实现org.springframework.core.convert.
converter.Converter<S,T> ,并将实现注入Spring容器中。
实现org.springframework.format.
Formatter<T> ,并将实现注入Spring 容器中。
透彻理解并发编程的三个核心知识点
远看并发,并发编程可以抽象成三个核心问题: 分工、同步/协作、互斥
如果你已经工作了,那么你一定听说过或者正在应用敏捷开发模式来交付日常的工作任务,我们就用你熟悉的流程来解释这三个核心问题
分工
将当前 Sprint 的 Story 拆分成「合适」大小的 Task,并且安排给「合适」的 Team Member 去完成
这里面用了两个「合适」,将 Story 拆分成大小适中,可完成的 Task 是非常重要的。拆分的粒度太粗,导致这个任务完成难度变高,耗时长,不易与其他人配合;拆分的粒度太细,又导致任务太多,不好管理与追踪,浪费精力和资源。(合适的线程才能更好的完成整块工作,当然一个线程可以轻松搞定的就没必要多线程);安排给合适的人员去完成同样重要,UX-UE 问题交给后端人员处理,很显然是有问题的 (主线程应该做的事交给子线程显然是解决不了问题的,每个线程做正确的事才能发挥作用)
关于分工,常见的 Executor,生产者-消费者模式,Fork/Join 等,这都是分工思想的体现
同步/协作
任务拆分完毕,我要等张三的任务,张三要等李四的任务,也就是说任务之间存在依赖关系,前面的任务执行完毕,后面的任务才可以执行,人高级在可以通过沟通反复确认,确保自己的任务可以开始执行。但面对程序,我们需要了解程序的沟通方式,一个线程执行完任务,如何通知后续线程执行
所有的同步/协作关系我们都可以用你最熟悉的 If-then-else 来表示:
if(前序任务完成){
execute();
}else{
wait();
}
上面的代码就是说:当某个条件不满足时,线程需要等待;当某个条件满足时,线程需要被唤醒执行,线程之间的协作可能是主线程与子线程的协作,可能是子线程与子线程的合作, Java SDK 中 CountDownLatch 和 CyclicBarrier 就是用来解决线程协作问题的
互斥
分工和同步强调的是性能,但是互斥是强调正确性,就是我们常常提到的「线程安全」,当多个线程同时访问一个共享变量/成员变量时,就可能发生不确定性,造成不确定性主要是有可见性
、原子性
、有序性
这三大问题,而解决这些问题的核心就是互斥
互斥
同一时刻,只允许一个线程访问共享变量
来看下图,主干路就是共享变量,进入主干路一次只能有一辆车,这样你是否理解了呢?「天下大事,分久必合」
同样 Java SDK 也有很多互斥的解决方案,比如你马上就能想到 synchronized 关键字,Lock,ThreadLocal 等就是互斥的解决方案
总结
资本家疯狂榨取劳动工人的剩余价值,获得最大收益。当你面对 CPU,内存,IO 这些劳动工人时,你就是那个资本家,你要思考如何充分榨取
它们的价值
当一个工人能干的活,绝不让两个人来干(单线程能满足就没必要为了多线程)当多个工人干活时,就要让他们分工明确,合作顺畅,没矛盾
当任务很大时,由于 IO 干活慢,CPU 干活快,就没必要让 CPU 死等当前的 IO,转而去执行其他指令,这就是榨取剩余价值
,如何最大限度的榨取其价值,这就涉及到后续的调优问题,比如多少线程合适等
分工是设计,同步和互斥是实现,没有好的设计也就没有好的实现,所以在分工阶段,强烈建议大家勾划草图,了解瓶颈所在,这样才会有更好的实现,后续章节的内容,我也会带领大家画草图,分析问题,逐步养成这个习惯
本章内容可以用下面的图来简单概括,叶子结点的内容我们会逐步点亮,现阶段不用过分关注(如果你上来就啃 JDK 源码,也许你会痛苦的迷失,并最终放弃你的进阶之路的)
理解三大核心问题,你要充分结合生活中的实际,程序中的并发问题,基本上都能在实际生活中找得到原型
下一篇文章的内容,我们就要聊聊,引起线程安全的三个问题:「可见性,原子性,有序性」,这涉及到 JMM 的一点内容,可以提前了解一下的,这样我们才能更好的碰撞
灵魂追问
工作中多线程编程的场景多吗?
想到多线程,只会想到 synchronized 吗?
Java 并发包各个类,你有了解底层实现和设计理念吗?
缓存使用
Redis 有哪些类型
在Redis中有五种数据类型
String----------字符串
Hash------------字典
List-------------列表
Set--------------集合
Sorted Set------有序集合
Redis 内部结构
Redis 内部使用一个 redisObject 对象来表示所有的 key 和 value。type :代表一个 value 对象具体是何种数据类型。
encoding :是不同数据类型在 redis 内部的存储方式,比如:type=string 代表 value 存储的是一个普通字符串,那么对应的 encoding 可以是 raw 或者是 int,如果是 int 则代表实际 redis 内部是按数值型类存储和表示这个字符串的,当然前提是这个字符串本身可以用数值表示,比如:"123" "456"这样的字符串。
vm 字段:只有打开了 Redis 的虚拟内存功能,此字段才会真正的分配内存,该功能默认是关闭状态的。Redis 使用 redisObject 来表示所有的 key/value 数据是比较浪费内存的,当然这些内存管理成本的付出主要也是为了给 Redis 不同数据类型提供一个统一的管理接口,实际作者也提供了多种方法帮助我们尽量节省内存使用。
作者:zhanglbjames
链接:https://www.jianshu.com/p/f09480c05e42
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
聊聊 Redis 使用场景
缓存
会话缓存
时效性
访问频率
计数器
社交列表
记录用户判定信息
交集、并集和差集
热门列表与排行榜
最新动态
消息队列
摘抄自:http://blog.720ui.com/2017/redis_core_use/
Redis 持久化机制
redis有两种持久化机制RDB与AOF。
摘抄自:http://shanks.leanote.com/post/Untitled-55ca439338f41148cd000759-22
Redis 如何实现持久化
RDB持久化方式会在一个特定的间隔保存那个时间点的一个数据快照。
AOF持久化方式则会记录每一个服务器收到的写操作。在服务启动时,这些记录的操作会逐条执行从而重建出原来的数据。写操作命令记录的格式跟Redis协议一致,以追加的方式进行保存。
Redis的持久化是可以禁用的,就是说你可以让数据的生命周期只存在于服务器的运行时间里。
两种方式的持久化是可以同时存在的,但是当Redis重启时,AOF文件会被优先用于重建数据。
Redis 集群方案与实现
客户端分片
基于代理的分片
路由查询
客户端分片
由客户端决定key写入或者读取的节点。
包括jedis在内的一些客户端,实现了客户端分片机制。路由查询
将请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上执行。
开源方案
Redis 为什么是单线程的
因为CPU不是Redis的瓶颈。Redis的瓶颈最有可能是机器内存或者网络带宽。(以上主要来自官方FAQ)既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。
缓存奔溃
碰到这种情况,一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。
加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。假设在高并发下,缓存重建期间key是锁着的,这是过来1000个请求999个都在阻塞的。同样会导致用户等待超时,这是个治标不治本的方法。
缓存降级
页面降级:在大促或者某些特殊情况下,某些页面占用了一些稀缺服务资源,在紧急情况下可以对其整个降级,以达到丢卒保帅;
页面片段降级:比如商品详情页中的商家部分因为数据错误了,此时需要对其进行降级;
页面异步请求降级:比如商品详情页上有推荐信息/配送至等异步加载的请求,如果这些信息响应慢或者后端服务有问题,可以进行降级;
服务功能降级:比如渲染商品详情页时需要调用一些不太重要的服务:相关分类、热销榜等,而这些服务在异常情况下直接不获取,即降级即可;
读降级:比如多级缓存模式,如果后端服务有问题,可以降级为只读缓存,这种方式适用于对读一致性要求不高的场景;
写降级:比如秒杀抢购,我们可以只进行Cache的更新,然后异步同步扣减库存到DB,保证最终一致性即可,此时可以将DB降级为Cache。
爬虫降级:在大促活动时,可以将爬虫流量导向静态页或者返回空数据,从而保护后端稀缺资源。
自动开关降级
自动降级是根据系统负载、资源使用情况、SLA等指标进行降级。
超时降级
当访问的数据库/http服务/远程调用响应慢或者长时间响应慢,且该服务不是核心服务的话可以在超时后自动降级;比如商品详情页上有推荐内容/评价,但是推荐内容/评价暂时不展示对用户购物流程不会产生很大的影响;对于这种服务是可以超时降级的。如果是调用别人的远程服务,和对方定义一个服务响应最大时间,如果超时了则自动降级。
摘抄自:http://jinnianshilongnian.iteye.com/blog/2306477
使用缓存的合理性问题
热点数据,缓存才有价值
频繁修改的数据,看情况考虑使用缓存
数据不一致性
缓存更新机制
缓存可用性
缓存服务降级
缓存预热
缓存穿透
摘抄自:http://blog.720ui.com/2016/redis_action_01_use_core/
消息队列
消息队列的使用场景
校验用户名等信息,如果没问题会在数据库中添加一个用户记录
如果是用邮箱注册会给你发送一封注册成功的邮件,手机注册则会发送一条短信
分析用户的个人信息,以便将来向他推荐一些志同道合的人,或向那些人推荐他
发送给用户一个包含操作指南的系统通知
消息的重发补偿解决思路
可靠消息服务定时查询状态为已发送并超时的消息
可靠消息将消息重新投递到 MQ 组件中
下游应用监听消息,在满足幂等性的条件下,重新执行业务。
下游应用通知可靠消息服务该消息已经成功消费。
通过消息状态确认和消息重发两个功能,可以确保上游应用、可靠消息服务和下游应用数据的最终一致性。
消息的幂等性解决思路
查询操作
查询一次和查询多次,在数据不变的情况下,查询结果是一样的。select是天然的幂等操作删除操作
删除操作也是幂等的,删除一次和多次删除都是把数据删除。(注意可能返回结果不一样,删除的数据不存在,返回0,删除的数据多条,返回结果多个)
3.唯一索引,防止新增脏数据
比如:支付宝的资金账户,支付宝也有用户账户,每个用户只能有一个资金账户,怎么防止给用户创建资金账户多个,那么给资金账户表中的用户ID加唯一索引,所以一个用户新增成功一个资金账户记录token机制,防止页面重复提交
悲观锁
获取数据的时候加锁获取
select * from table_xxx where id='xxx' for update;
注意:id字段一定是主键或者唯一索引,不然是锁表,会死人的
悲观锁使用时一般伴随事务一起使用,数据锁定时间可能会很长,根据实际情况选用乐观锁
乐观锁只是在更新数据那一刻锁表,其他时间不锁表,所以相对于悲观锁,效率更高。分布式锁
还是拿插入数据的例子,如果是分布是系统,构建全局唯一索引比较困难,例如唯一性的字段没法确定,这时候可以引入分布式锁,通过第三方的系统(redis或zookeeper),在业务系统插入数据或者更新数据,获取分布式锁,然后做操作,之后释放锁,这样其实是把多线程并发的锁的思路,引入多多个系统,也就是分布式系统中得解决思路。select + insert
并发不高的后台系统,或者一些任务JOB,为了支持幂等,支持重复执行,简单的处理方法是,先查询下一些关键数据,判断是否已经执行过,在进行业务处理,就可以了
注意:核心高并发流程不要用这种方法状态机幂等
在设计单据相关的业务,或者是任务相关的业务,肯定会涉及到状态机(状态变更图),就是业务单据上面有个状态,状态在不同的情况下会发生变更,一般情况下存在有限状态机,这时候,如果状态机已经处于下一个状态,这时候来了一个上一个状态的变更,理论上是不能够变更的,这样的话,保证了有限状态机的幂等。对外提供接口的api如何保证幂等
如银联提供的付款接口:需要接入商户提交付款请求时附带:source来源,seq序列号
source+seq在数据库里面做唯一索引,防止多次付款,(并发时,只能处理一个请求)
摘抄自:http://825635381.iteye.com/blog/2276077
消息的堆积解决思路
如果还没开始投入使用kafka,那应该在设计分区数的时候,尽量设置的多点(当然也不要太大,太大影响延迟,具体可以参考我前面提到的文章),从而提升生产和消费的并行度,避免消费太慢导致消费堆积。
增大批次
瓶颈在消费吞吐量的时候,增加批次也可以改善性能
增加线程数
如果一些消费者组中的消费者线程还是有1个消费者线程消费多个分区的情况,建议增加消费者线程。尽量1个消费者线程对应1个分区,从而发挥现有分区数下的最大并行度。
摘抄自:https://kaimingwan.com/post/framworks/kafka/kafkaxiao-xi-dui-ji-chu-li
自己如何实现消息队列
大体上的设计是由一条线程1执行从等待列表中获取任务插入任务队列再由线程池中的线程从任务队列中取出任务去执行.
添加一条线程1主要是防止在执行耗时的任务时阻塞主线程.当执行耗时任务时,添加的任务的操作快于取出任务的操作,
当任务队列长度达到最大值时,线程1将被阻塞,等待线程2,3…从任务队列取出任务执行。
作者:DrJasonZhang
链接:https://www.jianshu.com/p/2d2271ecc64d
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
如何保证消息的有序性
通过轮询所有队列的方式来确定消息被发送到哪一个队列(负载均衡策略)。订单号相同的消息会被先后发送到同一个队列中,
在获取到路由信息以后,会根据算法来选择一个队列,同一个OrderId获取到的肯定是同一个队列。
框架篇
Spring
BeanFactory 和 ApplicationContext 有什么区别
BeanFactory 可以理解为含有bean集合的工厂类。BeanFactory 包含了种bean的定义,以便在接收到客户端请求时将对应的bean实例化。
BeanFactory还能在实例化对象的时生成协作类之间的关系。此举将bean自身与bean客户端的配置中解放出来。BeanFactory还包含了bean生命周期的控制,调用客户端的初始化方法(initialization methods)和销毁方法(destruction methods)。
从表面上看,application context如同bean factory一样具有bean定义、bean关联关系的设置,根据请求分发bean的功能。但application context在此基础上还提供了其他的功能。
提供了支持国际化的文本消息
统一的资源文件读取方式
已在监听器中注册的bean的事件
摘抄自:http://www.importnew.com/15851.html
Spring Bean 的生命周期
Spring Bean的生命周期简单易懂。在一个bean实例被初始化时,需要执行一系列的初始化操作以达到可用的状态。同样的,当一个bean不在被调用时需要进行相关的析构操作,并从bean容器中移除。
Spring bean factory 负责管理在spring容器中被创建的bean的生命周期。Bean的生命周期由两组回调(call back)方法组成。
初始化之后调用的回调方法。
销毁之前调用的回调方法。
Spring框架提供了以下四种方式来管理bean的生命周期事件:
InitializingBean和DisposableBean回调接口
针对特殊行为的其他Aware接口
Bean配置文件中的Custom init()方法和destroy()方法
@PostConstruct和@PreDestroy注解方式
摘抄自:http://www.importnew.com/15851.html
Spring IOC 如何实现
Spring中的 org.springframework.beans 包和 org.springframework.context包构成了Spring框架IoC容器的基础。
BeanFactory 接口提供了一个先进的配置机制,使得任何类型的对象的配置成为可能。ApplicationContex接口对BeanFactory(是一个子接口)进行了扩展,在BeanFactory的基础上添加了其他功能,比如与Spring的AOP更容易集成,也提供了处理message resource的机制(用于国际化)、事件传播以及应用层的特别配置,比如针对Web应用的WebApplicationContext。
org.springframework.beans.factory.BeanFactory 是Spring IoC容器的具体实现,用来包装和管理前面提到的各种bean。BeanFactory接口是Spring IoC 容器的核心接口。
说说 Spring AOP
面向切面编程,在我们的应用中,经常需要做一些事情,但是这些事情与核心业务无关,比如,要记录所有update方法的执行时间时间,操作人等等信息,记录到日志,通过spring的AOP技术,就可以在不修改update的代码的情况下完成该需求。
Spring AOP 实现原理
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
动态代理(cglib 与 JDK)
JDK 动态代理类和委托类需要都实现同一个接口。也就是说只有实现了某个接口的类可以使用Java动态代理机制。但是,事实上使用中并不是遇到的所有类都会给你实现一个接口。因此,对于没有实现接口的类,就不能使用该机制。而CGLIB则可以实现对类的动态代理。
摘抄自:http://www.importnew.com/22015.html
Spring 事务实现方式
1、编码方式
所谓编程式事务指的是通过编码方式实现事务,即类似于JDBC编程实现事务管理。
2、声明式事务管理方式
声明式事务管理又有两种实现方式:基于xml配置文件的方式;另一个是在业务方法上进行@Transaction注解,将事务规则应用到业务逻辑中
Spring 事务底层原理
a、划分处理单元——IOC
由于spring解决的问题是对单个数据库进行局部事务处理的,具体的实现首先用spring中的IOC划分了事务处理单元。并且将对事务的各种配置放到了ioc容器中(设置事务管理器,设置事务的传播特性及隔离机制)。
b、AOP拦截需要进行事务处理的类
Spring事务处理模块是通过AOP功能来实现声明式事务处理的,具体操作(比如事务实行的配置和读取,事务对象的抽象),用TransactionProxyFactoryBean接口来使用AOP功能,生成proxy代理对象,通过TransactionInterceptor完成对代理方法的拦截,将事务处理的功能编织到拦截的方法中。读取ioc容器事务配置属性,转化为spring事务处理需要的内部数据结构(TransactionAttributeSourceAdvisor),转化为TransactionAttribute表示的数据对象。
c、对事物处理实现(事务的生成、提交、回滚、挂起)
spring委托给具体的事务处理器实现。实现了一个抽象和适配。适配的具体事务处理器:DataSource数据源支持、hibernate数据源事务处理支持、JDO数据源事务处理支持,JPA、JTA数据源事务处理支持。这些支持都是通过设计PlatformTransactionManager、AbstractPlatforTransaction一系列事务处理的支持。为常用数据源支持提供了一系列的TransactionManager。
d、结合
PlatformTransactionManager实现了TransactionInterception接口,让其与TransactionProxyFactoryBean结合起来,形成一个Spring声明式事务处理的设计体系。
如何自定义注解实现功能
创建自定义注解和创建一个接口相似,但是注解的interface关键字需要以@符号开头。
注解方法不能带有参数;
注解方法返回值类型限定为:基本类型、String、Enums、Annotation或者是这些类型的数组;
注解方法可以有默认值;
注解本身能够包含元注解,元注解被用来注解其它注解。
摘抄自:http://www.importnew.com/20286.html
Spring MVC 运行流程
1.spring mvc将所有的请求都提交给DispatcherServlet,它会委托应用系统的其他模块负责对请求 进行真正的处理工作。
2.DispatcherServlet查询一个或多个HandlerMapping,找到处理请求的Controller.
3.DispatcherServlet请请求提交到目标Controller
4.Controller进行业务逻辑处理后,会返回一个ModelAndView
5.Dispathcher查询一个或多个ViewResolver视图解析器,找到ModelAndView对象指定的视图对象
6.视图对象负责渲染返回给客户端。
摘抄自:http://blog.csdn.net/liangzi_lucky/article/details/52459378
Spring MVC 启动流程
在 web.xml 文件中给 Spring MVC 的 Servlet 配置了 load-on-startup,所以程序启动的
时候会初始化 Spring MVC,在 HttpServletBean 中将配置的 contextConfigLocation
属性设置到 Servlet 中,然后在 FrameworkServlet 中创建了 WebApplicationContext,
DispatcherServlet 根据 contextConfigLocation 配置的 classpath 下的 xml 文件初始化了
Spring MVC 总的组件。
摘自:《SpringMVC 源代码分析与实践》
Spring 的单例实现原理
Spring 对 Bean 实例的创建是采用单例注册表的方式进行实现的,而这个注册表的缓存是 ConcurrentHashMap 对象。
摘抄自:http://blog.720ui.com/2017/design_pattern_singleton_reg/
Spring 框架中用到了哪些设计模式
代理模式—在AOP和remoting中被用的比较多。
单例模式—在spring配置文件中定义的bean默认为单例模式。
模板方法—用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
前端控制器—Spring提供了DispatcherServlet来对请求进行分发。
视图帮助(View Helper )—Spring提供了一系列的JSP标签,高效宏来辅助将分散的代码整合在视图里。
依赖注入—贯穿于BeanFactory / ApplicationContext接口的核心理念。
工厂模式—BeanFactory用来创建对象的实例。
摘抄自:http://www.importnew.com/15851.html#design_patterns_used_in_spring
Spring 其他产品(Srping Boot、Spring Cloud、Spring Secuirity、Spring Data、Spring AMQP 等)
Netty
为什么选择 Netty
1) API使用简单,开发门槛低;
2) 功能强大,预置了多种编解码功能,支持多种主流协议;
3) 定制能力强,可以通过 ChannelHandler 对通信框架进行灵活的扩展;
4) 性能高,通过与其它业界主流的NIO框架对比,Netty的综合性能最优;
5) 成熟、稳定,Netty修复了已经发现的所有JDK NIO BUG,业务开发人员不需要再为NIO的BUG而烦恼;
6) 社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时,更多的新功能会被加入;
7) 经历了大规模的商业应用考验,质量已经得到验证。在互联网、大数据、网络游戏、企业应用、电信软件等众多行业得到成功商用,证明了它可以完全满足不同行业的商业应用。
正是因为这些优点,Netty逐渐成为Java NIO编程的首选框架。
摘抄自:http://ifeve.com/netty-2-6/
说说业务中,Netty 的使用场景
构建高性能、低时延的各种Java中间件,例如MQ、分布式服务框架、ESB消息总线等,Netty主要作为基础通信框架提供高性能、低时延的通信服务;
公有或者私有协议栈的基础通信框架,例如可以基于Netty构建异步、高性能的WebSocket协议栈;
各领域应用,例如大数据、游戏等,Netty作为高性能的通信框架用于内部各模块的数据分发、传输和汇总等,实现模块之间高性能通信。
摘抄自:http://www.voidcn.com/article/p-xydqhgxk-uk.html
原生的 NIO 在 JDK 1.7 版本存在 epoll bug
它会导致Selector空轮询,最终导致CPU 100%。官方声称在JDK 1.6版本的update18修复了该问题,但是直到JDK 1.7版本该问题仍旧存在,只不过该BUG发生概率降低了一些而已,它并没有得到根本性解决。
摘抄自:http://blog.csdn.net/broadview2006/article/details/46041995
什么是TCP 粘包/拆包
1、要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。
2、待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。
3、要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。
4、接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。
摘抄自:https://blog.insanecoder.top/tcp-packet-splice-and-split-issue/
TCP粘包/拆包的解决办法
1、发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
2、发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
3、可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。
摘抄自:https://blog.insanecoder.top/tcp-packet-splice-and-split-issue/
Netty 线程模型
首先,Netty使用EventLoop来处理连接上的读写事件,而一个连接上的所有请求都保证在一个EventLoop中被处理,一个EventLoop中只有一个Thread,所以也就实现了一个连接上的所有事件只会在一个线程中被执行。一个EventLoopGroup包含多个EventLoop,可以把一个EventLoop当做是Reactor线程模型中的一个线程,而一个EventLoopGroup类似于一个ExecutorService
作者:一字马胡
链接:https://www.jianshu.com/p/128ddc36e713
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
说说 Netty 的零拷贝
“零拷贝”是指计算机操作的过程中,CPU不需要为数据在内存之间的拷贝消耗资源。而它通常是指计算机在网络上发送文件时,不需要将文件内容拷贝到用户空间(User Space)而直接在内核空间(Kernel Space)中传输到网络的方式。
Netty 内部执行流程
Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。
Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象,用户可以像操作一个Buffer那样方便的对组合Buffer进行操作,避免了传统通过内存拷贝的方式将几个小Buffer合并成一个大的Buffer。
Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题。
摘抄自:http://blog.onlycatch.com/post/Netty%E4%B8%AD%E7%9A%84%E9%9B%B6%E6%8B%B7%E8%B4%9D
Netty 重连实现
1.心跳机制检测连接存活
2.启动时连接重试
3.运行中连接断开时重试
摘抄自:http://www.spring4all.com/article/889
简述设计模式原则
1、为什么需要设计模式
其实没有设计模式我们也能完成开发工作。但是为什么需要设计模式呢?让你看起来很牛,没错这个算一个。让你的代码层次感分明,可读性强而且容易维护。让你像我一样有更多的摸鱼划水时间。
可能有人说我一个类或者方法就干完的东西,你搞了七八个。当然使用设计模式也是要斟酌的。一些简单稳定的业务也不推荐使用设计模式。设计模式多用于复杂多变的业务或者要求适配性、扩展性更强的场景中。不要为了设计模式而设计模式。
接下来我们结合实际开探讨一下设计模式的一些原则。
2、开闭原则
public class Seller {
public BigDecimal sellCar(Car car) {
return car.getPrice();
}
}
上面模拟4S店一个销售在卖车。突然老板搞了一个促销:在双十一要开展打折活动。在sellCar方法内增加一个计算可行吗?这势必影响整个业务,导致所有车都打折。不行不行!那么在Car里面操作?然后你改啊改!结果各种逻辑流程判断。才实现了业务要求。如果后续打折活动结束了或者升级了,你还要再进行各种改动。你发现一个打折让你的代码面目全非、臃肿不堪。上面说了对于复杂而多变的业务使用设计模式就可以解决。
那么设计模式最重要的一个原则就是开闭原则。也就是说一个软件模型实体如类、模块和函数应该对扩展开放,对修改关闭。也就是需要我们将业务行为抽象出来,使用抽象来构建。具体的业务通过抽象的实现来解决。那么我们就搞一个DiscountCar来extends Car.这样sellCar是什么具体的实现就执行什么具体的逻辑。不会影响以前的逻辑,而且不会因为改动原来的代码影响其他逻辑。保证接口可靠性和稳定性。如下:
public class DiscountCar extends Car{
private BigDecimal price;
private BigDecimal discount;
@Override
public BigDecimal getPrice() {
return price.multiply(discount);
}
}
3、依赖倒置原则
还拿上面的例子来说。经过一系列的打折活动4S店的生意蒸蒸日上。老板突然想扩展一下周边,同时压榨一下销售。让他们卖车的同时卖点玻璃水、防冻液之类的。这个需求当然又抛给了苦逼的程序员。sellCar太具体了不能满足需要了。很多情况下你会增加一个卖玻璃水、卖防冻液的方法。如果以后增加了卖大米,甚至买起了鸡蛋饼呢?总不能一直增加方法吧。我们需要考虑这种问题。我们可以抽象所有卖东西的场景。然后我们把卖的物品抽象成了一个抽象化的概念(java对应的是接口,把卖的行为抽象成了sell方法:
public interface Any {
String getName();
BigDecimal getPrice();
}
public class Seller {
public BigDecimal sell(Any any) {
return any.getPrice();
}
}
这样随便老板以后卖什么你都可以通过该方法进行处理了,只需要关注于Any的实现。
4、职责单一原则
4S店销售卖了一段东西后,发现对客户的吸引力度不大。突然脑子比较灵活的老板又想起了电影中的一句台词:少林功夫加唱歌跳舞有没有搞头?对啊你们销售能不能搞搞什么唱、跳、Rap,当然打篮球就不要了别砸坏了车玻璃。但是人与人是不一样的,有的人只会唱,有的人只会跳,有的人可能唱跳Rap都会甚至篮球都很溜。所以为了适配这么多情况,我们必须把每种技能独立出来,根据不同的人来组合这些技能。
public class Seller implements Sing, Jump, Rap {
public BigDecimal sell(Any any) {
return any.doSell();
}
@Override
public void sing() {
System.out.println("seller sing ");
}
@Override
public void jump() {
System.out.println("seller jumping ");
}
@Override
public void rap() {
System.out.println("seller raping ");
}
}
但是注意一定要适度,根据业务来细分。否则会导致接口过多反而增大开发难度以及代码的复杂度。
5、迪米特法则
新的销售方法搞了一段时间后,老板想看看检验一下他这个馊主意的效果。于是就叫了一个销售让他提供一份报表出来看看。那么程序员该如何实现老板查看报表功能呢,很可能有人会这么写:
public class Boss {
private Seller seller;
private Report report;
public void read() {
seller.apply(report);
}
}
乍看功能实现了,细看会发现逻辑不对。哪里不对呢?老板已经持有了报表,如果老板已经知道了你的业绩还叫你干什么?这种逻辑肯定是不对的!也就是说Boss直接依赖了Report;而这个Report不应该直接由Boss处理,而应由Seller控制。
public class Boss {
private Seller seller;
public void read(){
seller.apply();
}
}
public class Seller {
private Report report;
public void apply(){
report.show();
}
}
这种最大化隔离了类与类之间的关系。降低了类之间的耦合。迪米特法则因此又得名最少知道原则。
6、接口隔离原则
用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口。一个类对一个类的依赖应该建立在最小的接口上。
建立单一接口,不要建立庞大臃肿的接口尽量细化接口,接口中的方法尽量少,尽量细化接口。注意适度原则,一定要适度。不能滥用
就像上面的唱跳 rap,分离是最好的。
7、里氏代换原则
这里主要针对类的继承关系而言。比较正式的定义:如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2 时,程序P的行为没有发生变化,那么类型 S 是类型 T 的子类型。
在4S店老板眼里,只要新来的能在销售岗位上像销售老手一样卖出汽车,他就是一名合格的销售。感觉这种定义就像一句名言:不管你黑猫白猫,能抓老鼠的都是好猫。
从某种含义上里氏代换有着以下的契约:
1. 子类必须完全实现父类的方法。父类出现的地方子类都可以代替父类。
2. 子类可以有自己的个性定义。里氏替换原则 可以正着用,但是不能反过来用。在子类出现的地方,父类未必就可以胜任。子类一般比父类有个性。
3. 覆盖或实现父类的方法时输入参数可以被放大。如果4S店老板规定基础车谈价的折扣最多九折,销售打个九五折没有问题,打八折老板肯定要跟你说道说道了。
4. 覆写或实现父类的方法时输出结果可以被缩小。同样是15W本来只能卖出给客户一个乞丐版,结果换了个销售结果给出了一辆旗舰版。怕不是过不了试用期哦。
8、合成/复用原则
它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
如果要使用继承关系,则必须严格遵循里氏替换原则。合成复用原则同里氏替换原则相辅相成的,两者都是开闭原则的具体实现规范。
9、总结
这七种设计原则是软件设计模式必须尽量遵循的原则,各种原则要求的侧重点不同。其中,开闭原则是总纲,它告诉我们要对扩展开放,对修改关闭;里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;单一职责原则告诉我们实现类要职责单一;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合度;合成复用原则告诉我们要优先使用组合或者聚合关系复用,少用继承关系复用。在实际开发中我们可以根据业务来进行设计模式的使用,但是很重要的一点千万不要被这些条条框框束缚了你的手脚。
数据存储
MySQL 索引使用的注意事项
1.索引不会包含有NULL的列
只要列中包含有NULL值,都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此符合索引就是无效的。
2.使用短索引
对串列进行索引,如果可以就应该指定一个前缀长度。例如,如果有一个char(255)的列,如果在前10个或20个字符内,多数值是唯一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。
3.索引列排序
mysql查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作,尽量不要包含多个列的排序,如果需要最好给这些列建复合索引。
4.like语句操作
一般情况下不鼓励使用like操作,如果非使用不可,注意正确的使用方式。like ‘%aaa%’不会使用索引,而like ‘aaa%’可以使用索引。
5.不要在列上进行运算
6.不使用NOT IN 、<>、!=操作,但<,<=,=,>,>=,BETWEEN,IN是可以用到索引的
7.索引要建立在经常进行select操作的字段上。
这是因为,如果这些列很少用到,那么有无索引并不能明显改变查询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求。
8.索引要建立在值比较唯一的字段上。
9.对于那些定义为text、image和bit数据类型的列不应该增加索引。因为这些列的数据量要么相当大,要么取值很少。
10.在where和join中出现的列需要建立索引。
11.where的查询条件里有不等号(where column != …),mysql将无法使用索引。
12.如果where字句的查询条件里使用了函数(如:where DAY(column)=…),mysql将无法使用索引。
13.在join操作中(需要从多个数据表提取数据时),mysql只有在主键和外键的数据类型相同时才能使用索引,否则及时建立了索引也不会使用。
说说分库与分表设计
垂直分表在日常开发和设计中比较常见,通俗的说法叫做“大表拆小表”,拆分是基于关系型数据库中的“列”(字段)进行的。通常情况,某个表中的字段比较多,可以新建立一张“扩展表”,将不经常使用或者长度较大的字段拆分出去放到“扩展表”中。在字段很多的情况下,拆分开确实更便于开发和维护(笔者曾见过某个遗留系统中,一个大表中包含100多列的)。某种意义上也能避免“跨页”的问题(MySQL、MSSQL底层都是通过“数据页”来存储的,“跨页”问题可能会造成额外的性能开销,拆分字段的操作建议在数据库设计阶段就做好。如果是在发展过程中拆分,则需要改写以前的查询语句,会额外带来一定的成本和风险,建议谨慎。
垂直分库在“微服务”盛行的今天已经非常普及了。基本的思路就是按照业务模块来划分出不同的数据库,而不是像早期一样将所有的数据表都放到同一个数据库中。系统层面的“服务化”拆分操作,能够解决业务系统层面的耦合和性能瓶颈,有利于系统的扩展维护。而数据库层面的拆分,道理也是相通的。与服务的“治理”和“降级”机制类似,我们也能对不同业务类型的数据进行“分级”管理、维护、监控、扩展等。
众所周知,数据库往往最容易成为应用系统的瓶颈,而数据库本身属于“有状态”的,相对于Web和应用服务器来讲,是比较难实现“横向扩展”的。数据库的连接资源比较宝贵且单机处理能力也有限,在高并发场景下,垂直分库一定程度上能够突破IO、连接数及单机硬件资源的瓶颈,是大型分布式系统中优化数据库架构的重要手段。
然后,很多人并没有从根本上搞清楚为什么要拆分,也没有掌握拆分的原则和技巧,只是一味的模仿大厂的做法。导致拆分后遇到很多问题(例如:跨库join,分布式事务等)。
水平分表也称为横向分表,比较容易理解,就是将表中不同的数据行按照一定规律分布到不同的数据库表中(这些表保存在同一个数据库中),这样来降低单表数据量,优化查询性能。最常见的方式就是通过主键或者时间等字段进行Hash和取模后拆分。水平分表,能够降低单表的数据量,一定程度上可以缓解查询性能瓶颈。但本质上这些表还保存在同一个库中,所以库级别还是会有IO瓶颈。所以,一般不建议采用这种做法。
水平分库分表与上面讲到的水平分表的思想相同,唯一不同的就是将这些拆分出来的表保存在不同的数据中。这也是很多大型互联网公司所选择的做法。某种意义上来讲,有些系统中使用的“冷热数据分离”(将一些使用较少的历史数据迁移到其他的数据库中。而在业务功能上,通常默认只提供热点数据的查询),也是类似的实践。在高并发和海量数据的场景下,分库分表能够有效缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源的瓶颈。当然,投入的硬件成本也会更高。同时,这也会带来一些复杂的技术问题和挑战(例如:跨分片的复杂查询,跨分片事务等)。
以上摘抄自:http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-split-table
分库与分表带来的分布式困境与应对之策
数据迁移与扩容问题
前面介绍到水平分表策略归纳总结为随机分表和连续分表两种情况。连续分表有可能存在数据热点的问题,有些表可能会被频繁地查询从而造成较大压力,热数据的表就成为了整个库的瓶颈,而有些表可能存的是历史数据,很少需要被查询到。连续分表的另外一个好处在于比较容易,不需要考虑迁移旧的数据,只需要添加分表就可以自动扩容。随机分表的数据相对比较均匀,不容易出现热点和并发访问的瓶颈。但是,分表扩展需要迁移旧的数据。
针对于水平分表的设计至关重要,需要评估中短期内业务的增长速度,对当前的数据量进行容量规划,综合成本因素,推算出大概需要多少分片。对于数据迁移的问题,一般做法是通过程序先读出数据,然后按照指定的分表策略再将数据写入到各个分表中。
表关联问题
在单库单表的情况下,联合查询是非常容易的。但是,随着分库与分表的演变,联合查询就遇到跨库关联和跨表关系问题。在设计之初就应该尽量避免联合查询,可以通过程序中进行拼装,或者通过反范式化设计进行规避。
分页与排序问题
一般情况下,列表分页时需要按照指定字段进行排序。在单库单表的情况下,分页和排序也是非常容易的。但是,随着分库与分表的演变,也会遇到跨库排序和跨表排序问题。为了最终结果的准确性,需要在不同的分表中将数据进行排序并返回,并将不同分表返回的结果集进行汇总和再次排序,最后再返回给用户。
分布式事务问题
随着分库与分表的演变,一定会遇到分布式事务问题,那么如何保证数据的一致性就成为一个必须面对的问题。目前,分布式事务并没有很好的解决方案,难以满足数据强一致性,一般情况下,使存储数据尽可能达到用户一致,保证系统经过一段较短的时间的自我恢复和修正,数据最终达到一致。
分布式全局唯一ID
在单库单表的情况下,直接使用数据库自增特性来生成主键ID,这样确实比较简单。在分库分表的环境中,数据分布在不同的分表上,不能再借助数据库自增长特性。需要使用全局唯一 ID,例如 UUID、GUID等。关于如何选择合适的全局唯一 ID,我会在后面的章节中进行介绍。
摘抄自:http://blog.csdn.net/jiangpingjiangping/article/details/78069480
说说 SQL 优化之道
一、一些常见的SQL实践
(1)负向条件查询不能使用索引
select * from order where status!=0 and stauts!=1
not in/not exists都不是好习惯
可以优化为in查询:
select * from order where status in(2,3)
(2)前导模糊查询不能使用索引
select * from order where desc like '%XX'
而非前导模糊查询则可以:
select * from order where desc like 'XX%'
(3)数据区分度不大的字段不宜使用索引
select * from user where sex=1
原因:性别只有男,女,每次过滤掉的数据很少,不宜使用索引。
经验上,能过滤80%数据时就可以使用索引。对于订单状态,如果状态值很少,不宜使用索引,如果状态值很多,能够过滤大量数据,则应该建立索引。
(4)在属性上进行计算不能命中索引
select * from order where YEAR(date) < = '2017' 即使date上建立了索引,也会全表扫描,可优化为值计算:select * from order where date < = CURDATE() 或者:select * from order where date < = '2017-01-01' 二、并非周知的SQL实践 (5)如果业务大部分是单条查询,使用Hash索引性能更好,例如用户中心 select * from user where uid=? select * from user where login_name=? 原因:B-Tree索引的时间复杂度是O(log(n));Hash索引的时间复杂度是O(1) (6)允许为null的列,查询有潜在大坑 单列索引不存null值,复合索引不存全为null的值,如果列允许为null,可能会得到“不符合预期”的结果集 select * from user where name != 'shenjian' 如果name允许为null,索引不存储null值,结果集中不会包含这些记录。所以,请使用not null约束以及默认值。(7)复合索引最左前缀,并不是值SQL语句的where顺序要和复合索引一致 用户中心建立了(login_name, passwd)的复合索引 select * from user where login_name=? and passwd=? select * from user where passwd=? and login_name=? 都能够命中索引 select * from user where login_name=? 也能命中索引,满足复合索引最左前缀 select * from user where passwd=? 不能命中索引,不满足复合索引最左前缀 (8)使用ENUM而不是字符串 ENUM保存的是TINYINT,别在枚举中搞一些“中国”“北京”“技术部”这样的字符串,字符串空间又大,效率又低。三、小众但有用的SQL实践 (9)如果明确知道只有一条结果返回,limit 1能够提高效率 select * from user where login_name=? 可以优化为:select * from user where login_name=? limit 1 原因:你知道只有一条结果,但数据库并不知道,明确告诉它,让它主动停止游标移动 (10)把计算放到业务层而不是数据库层,除了节省数据的CPU,还有意想不到的查询缓存优化效果 select * from order where date < = CURDATE() 这不是一个好的SQL实践,应该优化为: res = mysql_query(
'select * from order where date < = $curDate');
原因:
释放了数据库的CPU
多次调用,传入的SQL相同,才可以利用查询缓存
(11)强制类型转换会全表扫描
select * from user where phone=13800001234
你以为会命中phone索引么?大错特错了,这个语句究竟要怎么改?
末了,再加一条,不要使用select *(潜台词,文章的SQL都不合格 =_=),只返回需要的列,能够大大的节省数据传输量,与数据库的内存使用量哟。
整理自:https://cloud.tencent.com/developer/article/1054203
MySQL 遇到的死锁问题
产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
下列方法有助于最大限度地降低死锁:
(1)按同一顺序访问对象。
(2)避免事务中的用户交互。
(3)保持事务简短并在一个批处理中。
(4)使用低隔离级别。
(5)使用绑定连接。
整理自:http://onwise.xyz/2017/04/20/mysql-%E6%AD%BB%E9%94%81%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3/
存储引擎的 InnoDB 与 MyISAM
◆1.InnoDB不支持FULLTEXT类型的索引。
◆2.InnoDB 中不保存表的具体行数,也就是说,执行select count() from table时,InnoDB要扫描一遍整个表来计算有多少行,但是MyISAM只要简单的读出保存好的行数即可。注意的是,当count()语句包含 where条件时,两种表的操作是一样的。
◆3.对于AUTO_INCREMENT类型的字段,InnoDB中必须包含只有该字段的索引,但是在MyISAM表中,可以和其他字段一起建立联合索引。
◆4.DELETE FROM table时,InnoDB不会重新建立表,而是一行一行的删除。
◆5.LOAD TABLE FROM MASTER操作对InnoDB是不起作用的,解决方法是首先把InnoDB表改成MyISAM表,导入数据后再改成InnoDB表,但是对于使用的额外的InnoDB特性(例如外键)的表不适用。
另外,InnoDB表的行锁也不是绝对的,假如在执行一个SQL语句时MySQL不能确定要扫描的范围,InnoDB表同样会锁全表,例如update table set num=1 where name like “%aaa%”
数据库索引的原理
数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。
为什么要用 B-tree
一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗,相对于内存存取,I/O存取的消耗要高几个数量级,所以评价一个数据结构作为索引的优劣最重要的指标就是在查找过程中磁盘I/O操作次数的渐进复杂度。换句话说,索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数。
聚集索引与非聚集索引的区别
1).聚集索引一个表只能有一个,而非聚集索引一个表可以存在多个
2).聚集索引存储记录是物理上连续存在,而非聚集索引是逻辑上的连续,物理存储并不连续
3).聚集索引:物理存储按照索引排序;聚集索引是一种索引组织形式,索引的键值逻辑顺序决定了表数据行的物理存储顺序
非聚集索引:物理存储不按照索引排序;非聚集索引则就是普通索引了,仅仅只是对数据列创建相应的索引,不影响整个表的物理存储顺序.
4).索引是通过二叉树的数据结构来描述的,我们可以这么理解聚簇索引:索引的叶节点就是数据节点。而非聚簇索引的叶节点仍然是索引节点,只不过有一个指针指向对应的数据块。
limit 20000 加载很慢怎么解决
mysql的性能低是因为数据库要去扫描N+M条记录,然后又要放弃之前N条记录,开销很大
解决思略:
1、前端加缓存,或者其他方式,减少落到库的查询操作,例如某些系统中数据在搜索引擎中有备份的,可以用es等进行搜索
2、使用延迟关联,即先通用limit得到需要数据的索引字段,然后再通过原表和索引字段关联获得需要数据
select a.* from a,(select id from table_1 where is_deleted='N' limit 100000,20) b where a.id = b.id
3、从业务上实现,不分页如此多,例如只能分页前100页,后面的不允许再查了
4、不使用limit N,M,而是使用limit N,即将offset转化为where条件。
选择合适的分布式主键方案
数据库自增长序列或字段
UUID
使用UUID to Int64的方法
Redis生成ID
Twitter的snowflake算法
利用zookeeper生成唯一ID
MongoDB的ObjectId
选择合适的数据存储方案
关系型数据库 MySQL
MySQL 是一个最流行的关系型数据库,在互联网产品中应用比较广泛。一般情况下,MySQL 数据库是选择的第一方案,基本上有 80% ~ 90% 的场景都是基于 MySQL 数据库的。因为,需要关系型数据库进行管理,此外,业务存在许多事务性的操作,需要保证事务的强一致性。同时,可能还存在一些复杂的 SQL 的查询。值得注意的是,前期尽量减少表的联合查询,便于后期数据量增大的情况下,做数据库的分库分表。
内存数据库 Redis
随着数据量的增长,MySQL 已经满足不了大型互联网类应用的需求。因此,Redis 基于内存存储数据,可以极大的提高查询性能,对产品在架构上很好的补充。例如,为了提高服务端接口的访问速度,尽可能将读频率高的热点数据存放在 Redis 中。这个是非常典型的以空间换时间的策略,使用更多的内存换取 CPU 资源,通过增加系统的内存消耗,来加快程序的运行速度。
在某些场景下,可以充分的利用 Redis 的特性,大大提高效率。这些场景包括缓存,会话缓存,时效性,访问频率,计数器,社交列表,记录用户判定信息,交集、并集和差集,热门列表与排行榜,最新动态等。
使用 Redis 做缓存的时候,需要考虑数据不一致与脏读、缓存更新机制、缓存可用性、缓存服务降级、缓存穿透、缓存预热等缓存使用问题。
文档数据库 MongoDB
MongoDB 是对传统关系型数据库的补充,它非常适合高伸缩性的场景,它是可扩展性的表结构。基于这点,可以将预期范围内,表结构可能会不断扩展的 MySQL 表结构,通过 MongoDB 来存储,这就可以保证表结构的扩展性。
此外,日志系统数据量特别大,如果用 MongoDB 数据库存储这些数据,利用分片集群支持海量数据,同时使用聚集分析和 MapReduce 的能力,是个很好的选择。
MongoDB 还适合存储大尺寸的数据,GridFS 存储方案就是基于 MongoDB 的分布式文件存储系统。
列族数据库 HBase
HBase 适合海量数据的存储与高性能实时查询,它是运行于 HDFS 文件系统之上,并且作为 MapReduce 分布式处理的目标数据库,以支撑离线分析型应用。在数据仓库、数据集市、商业智能等领域发挥了越来越多的作用,在数以千计的企业中支撑着大量的大数据分析场景的应用。
全文搜索引擎 ElasticSearch
在一般情况下,关系型数据库的模糊查询,都是通过 like 的方式进行查询。其中,like “value%” 可以使用索引,但是对于 like “%value%” 这样的方式,执行全表查询,这在数据量小的表,不存在性能问题,但是对于海量数据,全表扫描是非常可怕的事情。ElasticSearch 作为一个建立在全文搜索引擎 Apache Lucene 基础上的实时的分布式搜索和分析引擎,适用于处理实时搜索应用场景。此外,使用 ElasticSearch 全文搜索引擎,还可以支持多词条查询、匹配度与权重、自动联想、拼写纠错等高级功能。因此,可以使用 ElasticSearch 作为关系型数据库全文搜索的功能补充,将要进行全文搜索的数据缓存一份到 ElasticSearch 上,达到处理复杂的业务与提高查询速度的目的。
ElasticSearch 不仅仅适用于搜索场景,还非常适合日志处理与分析的场景。著名的 ELK 日志处理方案,由 ElasticSearch、Logstash 和 Kibana 三个组件组成,包括了日志收集、聚合、多维度查询、可视化显示等。
摘抄自:http://blog.720ui.com/2017/db_better_db_use/
ObjectId 规则
[0,1,2,3] [4,5,6] [7,8] [9,10,11]
时间戳 |机器码 |PID |计数器
前四位是时间戳,可以提供秒级别的唯一性。
接下来三位是所在主机的唯一标识符,通常是机器主机名的散列值。
接下来两位是产生ObjectId的PID,确保同一台机器上并发产生的ObjectId是唯一的。
前九位保证了同一秒钟不同机器的不同进程产生的ObjectId时唯一的。
最后三位是自增计数器,确保相同进程同一秒钟产生的ObjectId是唯一的。
https://github.com/qianjiahao/MongoDB/wiki/MongoDB%E4%B9%8B_id%E7%94%9F%E6%88%90%E8%A7%84%E5%88%99
聊聊 MongoDB 使用场景
高伸缩性的场景
MongoDB 非常适合高伸缩性的场景,它是可扩展性的表结构。基于这点,可以将预期范围内,表结构可能会不断扩展的 MySQL 表结构,通过 MongoDB 来存储,这就可以保证表结构的扩展性。
日志系统的场景
日志系统数据量特别大,如果用 MongoDB 数据库存储这些数据,利用分片集群支持海量数据,同时使用聚集分析和 MapReduce 的能力,是个很好的选择。
分布式文件存储
MongoDB 还适合存储大尺寸的数据,之前介绍的 GridFS 存储方案,就是基于 MongoDB 的分布式文件存储系统。
摘抄自:http://blog.720ui.com/2017/mongodb_core_use/
倒排索引
倒排索引(英语:Inverted index),也常被称为反向索引、置入档案或反向档案,是一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。它是文档检索系统中最常用的数据结构。
有两种不同的反向索引形式:
一条记录的水平反向索引(或者反向档案索引)包含每个引用单词的文档的列表。
一个单词的水平反向索引(或者完全反向索引)又包含每个单词在一个文档中的位置。
聊聊 ElasticSearch 使用场景
全文搜索,这个是用的最多的。加上分词插件、拼音插件什么的可以做成强大的全文搜索引擎。
数据库,挺奇葩的用法,因为ES存数相同数据,更费空间,不过确实不错,因为他的强大统计分析汇总能力,再加上分布式P2P扩展能力,现在硬件又那么便宜,所以就有人拿来当数据库了。
在线统计分析引擎,日志系统。logstash,不用解释了吧。可以实时动态分析数据,很是爽。
CAP与BASE理论
分布式事务
分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于分布式系统的不同节点之上。通常一个分布式事务中会涉及对多个数据源或业务系统的操作。
例如在下单场景下,库存和订单如果不在同一个节点上,就涉及分布式事务。
一个分布式事务可以看作是由多个分布式的操作序列组成的,通常可以把这一系列分布式的操作序列称为子事务。因此分布式事务也可以被定义为一种嵌套型的事务,同时也就具有了 ACID 事务特性。但由于在分布式事务中,各个子事务的执行是分布式的,因此要实现一种能够保证 ACID 特性的分布式事务处理系统就显得格外复杂。
CAP和BASE理论的引出
对于本地事务处理或者是集中式的事务处理系统,依照传统的ACID特性即可保证数据的严格一致性。
可是对于分布式事务,传统的单机事务模型已经无法胜任。如果我们期望实现一套严格满足ACID特性的分布式事务,很可能出现的情况就是在系统的可用性和严格一致性之间出现冲突。
因此,在可用性和一致性之间永远无法存在一个两全其美的方案,于是如何构建一个兼顾可用性和一致性的分布式系统成为了无数工程师探讨的难题,出现了诸如CAP和BASE这样的分布式系统经典理论。
CAP定理
CAP定理告诉我们,分布式系统不可能同时满足一致性(C:Consistency)、可用性(A:Availability)和分区容错性(P:Partition Tolerance),最多只能同时满足其中两项。
一、一致性(C:Consistency)
一致性指的是多个数据副本是否能保持一致的特性,在一致性的条件下,系统在执行数据更新操作之后能够从一致性状态转移到另一个一致性状态。
对系统的一个数据更新成功之后,如果所有用户都能够读取到最新的值,该系统就被认为具有强一致性。
二、可用性(A:Availability)
可用性指分布式系统在面对各种异常时可以提供正常服务的能力,可以用系统可用时间占总时间的比值来衡量,4 个 9 的可用性表示系统 99.99% 的时间是可用的。
在可用性条件下,要求系统提供的服务一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
三、分区容错性(P:Partition Tolerance)
网络分区指分布式系统中的节点被划分为多个区域,每个区域内部可以通信,但是区域之间无法通信。
在分区容错性条件下,分布式系统在遇到任何网络分区故障的时候,仍然需要能对外提供一致性和可用性的服务,除非是整个网络环境都发生了故障。
四、权衡
由上面我们可以看出,在分布式系统中,分区容错性必不可少,因为需要总是假设网络是不可靠的。因此,CAP 理论实际上是要在可用性和一致性之间做权衡。
可用性和一致性往往是冲突的,很难使它们同时满足。在多个节点之间进行数据同步时:
为了保证一致性(CP),不能访问未同步完成的节点,也就失去了部分可用性;
为了保证可用性(AP),允许读取所有节点的数据,但是数据可能不一致。
BASE理论
BASE 是基本可用(Basically Available)、软状态(Soft State)和最终一致性(Eventually Consistent)三个短语的缩写。
BASE 理论是对 CAP 中一致性和可用性权衡的结果,它的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
基本可用
指分布式系统在出现故障的时候,保证核心可用,允许损失部分可用性。
例如,电商在做促销时,为了保证购物系统的稳定性,部分消费者可能会被引导到一个降级的页面。
软状态
指允许系统中的数据存在中间状态,并认为该中间状态不会影响系统整体可用性,即允许系统不同节点的数据副本之间进行同步的过程存在时延。
最终一致性
最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能达到一致的状态。
ACID 要求强一致性,通常运用在传统的数据库系统上。而 BASE 要求最终一致性,通过牺牲强一致性来达到可用性,通常运用在大型分布式系统中。
在实际的分布式场景中,不同业务单元和组件对一致性的要求是不同的,因此 ACID 和 BASE 往往会结合在一起使用。
参考内容:《从Paxos到ZooKeeper 分布式一致性原理与实践》
说说反模式设计
简单的来说,反模式是指在对经常面对的问题经常使用的低效,不良,或者有待优化的设计模式/方法。甚至,反模式也可以是一种错误的开发思想/理念。在这里我举一个最简单的例子:在面向对象设计/编程中,有一条很重要的原则, 单一责任原则(Single responsibility principle)。其中心思想就是对于一个模块,或者一个类来说,这个模块或者这个类应该只对系统/软件的一个功能负责,而且该责任应该被该类完全封装起来。当开发人员需要修改系统的某个功能,这个模块/类是最主要的修改地方。相对应的一个反模式就是上帝类(God Class),通常来说,这个类里面控制了很多其他的类,同时也依赖其他很多类。整个类不光负责自己的主要单一功能,而且还负责了其他很多功能,包括一些辅助功能。很多维护老程序的开发人员们可能都遇过这种类,一个类里有几千行的代码,有很多功能,但是责任不明确单一。单元测试程序也变复杂无比。维护/修改这个类的时间要远远超出其他类的时间。很多时候,形成这种情况并不是开发人员故意的。很多情况下主要是由于随着系统的年限,需求的变化,项目的资源压力,项目组人员流动,系统结构的变化而导致某些原先小型的,符合单一原则类慢慢的变的臃肿起来。最后当这个类变成了维护的噩梦(特别是原先熟悉的开发人员离职后),重构该类就变成了一个不容易的工程。