JAVA常见应用题总结

 面试题总结 


前段时间在网上看到了一个高级java面试题这样的合集,也有网络图片传言这些是阿里的面试题,也受到了一些质疑,这边对这些面试题进行一些整理和作答,去除了含糊不清的问题和重复的概念问题,希望对面试的人有所帮助。

Part1、BASEJAVA


1.hashcode相等的两个类一定相等吗?equals呢?相反呢?


equals在Object的方法里的实现是== 也就是比较两个对象的地址是否相等,而equals的概念为,两个对象确确实实相等,在基本数据类型中,大多重写了equals方法,让其比较其内容是否相等。 
hashcode时对对象内容经过散列算法的出来的一个int类型的值,这就说明,在这个程序里,如果两个不同的对象,其hashcode相同,则非常大的概率他们相同(99.9x%)。其散列出来的hashcode算法可能使用了程序的内存空间的基址一样的东西,他不保证在两台机器或者是在两次运行程序时,内容相同的对象其hashcode相同。但是如果我们想要让两个对象内容相同则认为它相同,就需要重写的equal()里一般比较的比较全面比较复杂,这样效率就比较低,而利用hashCode()进行对比,则只要生成一个hash值进行比较就可以了,效率很高。 
总结: 
1.equal()相等的两个对象他们的hashCode()肯定相等,也就是用equal()对比是绝对可靠的。 
2.hashCode()相等的两个对象他们的equal()不一定相等,也就是hashCode()不是绝对可靠的。 
对于需要大量并且快速的对比的话如果都用equal()去做显然效率太低,所以解决方式是,每当需要对比的时候,首先用hashCode()去对比,如果hashCode()不一样,则表示这两个对象肯定不相等(也就是不必再用equal()去再对比了),如果hashCode()相同,此时再对比他们的equal(),如果equal()也相同,则表示这两个对象是真的相同了,这样既能大大提高了效率也保证了对比的绝对正确性。

2.介绍一下集合框架


分为三块:List列表、Set集合、Map映射 
List在数据结构上可以被看作为线性表,常用的有ArryList与LinkList,其底层存储结构有所不同,一个是数组,一个是链表。除了这两个常见的线性表外,还有古董级别的Vector(类比ArrayList)。但是这两种是注重数据存储结构的区分,众所周知数据结构数据操作方法层面上的区分,也就是栈和队列,在java集合框架里自然也有 就是Stack和Queue,但是Stack是一个继承了Vector的类(使用数组,最起码这个类限定死了)里面扩展和限定了线性表操作方法,可以拿来直接使用,而Queue是一个继承于Collection的接口(因为队列可以分多种),LinkList实现了Queue接口,常用的有ArrayBlockingQueue(基于数组),LinkedBlockingQueue(基于链表),PriorityBlockingQueue(实现优先级排序)等。 
Map一种映射,用于储存关系型数据,保存着两种值,一组用于保存key,另一组用来保存value。并且key不允许重复。 
HashMap底层就是一个数组,数组中根据存入的Key的HashCode决定存放位置,其Entry单元中有四个属性,分别为HashCode,Key,Vaule,和下一个Entry,这样就形成了一个链表,当HashMap中的另一个拥有相同的HashCode值的不同的Key存入时,会将原来的Entry赋到新Entry的属性中,然后形成Entry链,查询的时候先比较HashCode,如果相同且Key值相同则直接取出,如果HashCode相同Key值不同则继续顺着链表寻找直到寻找到相同的Key值。 
TreeMap与HashMap的不同:表象上时TreeMap可以对Key进行排序,原因时TreeMap使用的时“红黑树”的二叉树结构储存Entry,也就是排序二叉树,左边恒放比此值小的数右边恒放比此值大的树,按照当前节点值与传入查询值的比较进行判断决定其存放位置/查询其数值; 
Set集合:Set与Map可以手动的互相转换 Set转换Map只需要新建一个对象,对象中又key和value两个属性,新建一个类继承Set存储新建的对象即可实现。Map转换为Set只需要将Map的Value固定,只使用Key存储数据即可实现; 
Table:Map的线程安全型号;

3.hashmap,hashtable底层实现的区别,hashtable和concurrenthashtable呢?
hashtable简单的理解就是hashmap的线程安全类 其方法大部分都相同只不过家了synchronize关键字保证其线程安全。其他的区别也有继承的接口不同这点。 
concurrenthashtable则是改进了hashtable的效率,hashtable虽然安全但是不能多线程同时操作,concurrenthashtable使用了分块的模式支持多线程操作,且使用了lock替换synchronize来提高了效率。

4.hashmap与treemap的区别,底层数据结构是什么样的?


HashMap底层就是一个数组,数组中根据存入的Key的HashCode决定存放位置,其Entry单元中有四个属性,分别为HashCode,Key,Vaule,和下一个Entry,这样就形成了一个链表,当HashMap中的另一个拥有相同的HashCode值的不同的Key存入时,会将原来的Entry赋到新Entry的属性中,然后形成Entry链,查询的时候先比较HashCode,如果相同且Key值相同则直接取出,如果HashCode相同Key值不同则继续顺着链表寻找直到寻找到相同的Key值。 
TreeMap与HashMap的不同:表象上时TreeMap可以对Key进行排序,原因时TreeMap使用的时“红黑树”的二叉树结构储存Entry,也就是排序二叉树,左边恒放比此值小的数右边恒放比此值大的树,按照当前节点值与传入查询值的比较进行判断决定其存放位置/查询其数值;

5.线程池用过么,都有什么参数?底层如何实现的?


线程池: 
1、线程是稀缺资源,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以重复使用。 
2、可以根据系统的承受能力,调整线程池中工作线程的数量,防止因为消耗过多内存导致服务器崩溃。 
参数: 
corePoolSize:线程池核心线程数量 
maximumPoolSize:线程池最大线程数量 
keepAliverTime:当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间 
unit:存活时间的单位 
workQueue:存放任务的队列 
handler:超出线程范围和队列容量的任务的处理程序 
底层核心实现为封装一层线程类work,在运行的时候再执行完自己的线程后主动去队列中拿取下一条线程去执行。

6.sychnized与lock有什么区别?sychnized什么时候是对象锁什么时候是全局锁?


synchronized是java中的一个关键字,用于线程同步。 
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象; 
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象; 
3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象; 
4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象。

synchronized修饰不加static的方法,锁是加在单个对象上,不同的对象没有竞争关系;修饰加了static的方法,锁是加载类上,这个类所有的对象竞争一把锁。

Lock是一个java接口 里面有一些实现类,也用于实现线程同步,但是相比较于synchronized,无论功能还是性能都有很大提升,但是要注意需要手动释放。 
性能上由于synchronized在编译时会对代码进行修正,最终由cpu通过调度线程的方式处理线程同步问题,开销很大。而lock使用了Java的unsafe类中的CAS方法实现对线程同步的控制,减小了消耗。 
功能上:synchronized由线程控制,只有等待执行结束和异常释放,本身是可重入的,非公平锁,书写时无需手动释放。lock实现多样,除了最基础的可重入锁ReentrantLock,还有使用读写锁(ReadWriteLock)来实现读写分离,进一步提高效率,ReentrantLock默认使用的是非公平锁(竞争锁,可以设置)。

7.ThreadLocal是什么?底层如何实现的?写一个例子。


是一个解决线程并发问题的一个类,底层实现主要是存有一个map,以线程作为key,范型作为value。可以理解为线程级别的缓存。 
使用起来比较简单 假设我们要实现一个线程级别的缓存。

    private static final ThreadLocal<Map> testInt=new ThreadLocal<Map>();
        public static Map getMap(){
        Map s = (Map) testInt.get();
                if(s==null){
                    s=new HashMap();
                    testInt.set(s);
                }
                return s;
        }


这样我们就能实现在调用这个方法的时候 获得的map是线程级别的。每一个线程都会获得一个单独的map。

8. volatile的工作原理。


定义: 
java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁更加方便。如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。 
在多个线程之间能够被共享的变量被称为共享变量。共享变量包括所有的实例变量,静态变量和数组元素。他们都被存放在堆内存中,volatile只作用于共享变量。 
工作原理: 
在汇编时 有volatile修饰符的变量将会被lock,这个lock做两件事:

1.将当前处理器缓存行的数据会写回到系统内存。
2.这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效。

你也可以理解为:volatile修饰的变量在修改时直接修改内存(越过缓存)

9.cas知道么?如何实现的?


CAS全称Compare and swap 意思为比较和互换,使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值(比如 锁模式)。需要注意的一点是:CAS必须保证自己操作的原子性(可以使用synchronized确保原子性)。

class MyLock {
    private boolean locked = false;
        public synchronized boolean lock() {
            if(!locked) {
                locked = true;
                return true;
            }
            return false;
        }
}



现在CPU内部已经执行原子的CAS操作。Java5以来,你可以使用java.util.concurrent.atomic包中的一些原子类来使用CPU中的这些功能

public static class MyLock {
    private AtomicBoolean locked = new AtomicBoolean(false);
    public boolean lock() {
        return locked.compareAndSet(false, true);
    }
}


10.用4个写法写一个单例模式。


四种写法:懒汉 饿汉 sychnized方法饿汉 sychnized代码块饿汉 双层判断sychnized饿汉 枚举 
前两种线程不安全 懒汉直接写在定义变量中 饿汉在第一次使用时初始化 
后三种线程安全 sychnized饿汉不够效率 sychnized代码块饿汉存在同时通过null判断的极端情况 双层判断sychnized饿汉基本认为是比较完美的解决方案 枚举就是单例 不常用

Part2、jvm相关


1.介绍一下jvm内存模型,垃圾回收器用法。


jvm内存模型 
JVM结构模型如下 
 
1. 类加载器(ClassLoader):在JVM启动时或者在类运行时将需要的class加载到JVM中。 
2. 执行引擎:负责执行class文件中包含的字节码指令; 
3. 内存区(也叫运行时数据区):是在JVM运行的时候操作所分配的内存区。运行时内存区主要可以划分为5个区域: 
1. 方法区(Method Area):用于存储类结构信息的地方,包括常量池、静态变量、构造函数等。虽然JVM规范把方法区描述为堆的一个逻辑部分, 但它却有个别名non-heap(非堆),所以大家不要搞混淆了。方法区还包含一个运行时常量池。 
2. java堆(Heap):存储java实例或者对象的地方。这块是GC的主要区域(后面解释)。从存储的内容我们可以很容易知道,方法区和堆是被所有java线程共享的。 
3. java栈(Stack):java栈总是和线程关联在一起,每当创建一个线程时,JVM就会为这个线程创建一个对应的java栈。在这个java栈中又会包含多个栈帧,每运行一个方法就创建一个栈帧,用于存储局部变量表、操作栈、方法返回值等。每一个方法从调用直至执行完成的过程,就对应一个栈帧在java栈中入栈到出栈的过程。所以java栈是现成私有的。 
4. 程序计数器(PC Register):用于保存当前线程执行的内存地址。由于JVM程序是多线程执行的(线程轮流切换),所以为了保证线程切换回来后,还能恢复到原先状态,就需要一个独立的计数器,记录之前中断的地方,可见程序计数器也是线程私有的。 
5. 本地方法栈(Native Method Stack):和java栈的作用差不多,只不过是为JVM使用到的native方法服务的。 
4. 本地方法接口:主要是调用C或C++实现的本地方法及返回结果。 
垃圾回收器 
就功能上来言,垃圾回收存在以下几个维度的划分:

1. 按线程数分,可以分为串行垃圾回收器和并行垃圾回收器。串行垃圾回收器一次只使用一个线程进行垃圾回收;并行垃圾回收器一次将开启多个线程同时进行垃圾回收。在并行能力较强的 CPU 上,使用并行垃圾回收器可以缩短 GC 的停顿时间。

2. 按照工作模式分,可以分为并发式垃圾回收器和独占式垃圾回收器。并发式垃圾回收器与应用程序线程交替工作,以尽可能减少应用程序的停顿时间;独占式垃圾回收器 (Stop the world) 一旦运行,就停止应用程序中的其他所有线程,直到垃圾回收过程完全结束。

3. 按碎片处理方式可分为压缩式垃圾回收器和非压缩式垃圾回收器。压缩式垃圾回收器会在回收完成后,对存活对象进行压缩整理,消除回收后的碎片;非压缩式的垃圾回收器不进行这步操作。

4. 按工作的内存区间,又可分为新生代垃圾回收器和老年代垃圾回收器。
常用的垃圾回收器有以下四种: 
1.串行回收器 
2.并行/吞吐量回收器 
3.CMS回收器 
4.G1回收器 
目前来说大多数公司选择CMS回收器(标记压缩) 
但java8以来提供的G1回收器可能在以后取代这种方式:其特征是吧堆中的string字符串所占用的大量对象空间整合通过不同字符串中的字符可能指向同一地址的方式。但是目前表现弱于CMS回收器。在堆内存空间分配到4g以上的服务中可以考虑使用。 
垃圾回收器Java中提供了System.gc()的方法激活垃圾回收线程回收。

2.线上频繁的full gc应该如何处理?cpu使用率过高该怎么办?


最初解决:dump文件分析,程序是否出错。 
最终方案:增大空间,无论是调节比例还是整体扩展。 
1.System.gc()方法的调用,如果是串行回收器不建议在程序中使用System.gc(),频繁的System.gc()会导致系统暂停频率变高。 
2.老年代空间不足:可能是程序中创建为大对象、大数组导致对象不能及时在Minor GC阶段被回收,程序优化。 
3.永生区空间不足:系统过大,加载类过多,或者是频繁调用反射。推荐转为使用CMS GC。 
4.CMS中出现full gc 没救了 最终方案吧! 
cpu使用率过高最初解决方案审查代码中是否存在死循环这样 
cpu使用率过高最终解决方案加核提配置 
一般流程为获取java应用进程的pid 查找线程及占用cpu情况 找到最高的几个线程拿出来进行堆栈打印,定位代码进行审查。 
还有一种可能是gc问题 频繁被占满导致无法回收资源引起的cpu使用率过高。

3.字节码获取方式有哪些?Integer x=5,int y=5和x==y都经过哪些步骤?


字节码也就是class文件。 
在程序中获取类对象的方法就是获取字节码的方式。

1.类名.class,例如System.class;
2.对象.getClass(),例如New Date().getClass()
3.Class.forName("类名"),例如Class.forName("Java.Util.Date");
字节码角度讲int y=5单纯压入int类型变量,赋值为5,而Integer x=5则会调用Integer.valueOf()的方法。x==y会使用到if_icmp方法比较。

4类加载机制,都有哪些类加载器,这些加载器加载哪些文件?手写一下类加载器demo。


核心库的类:加载是由原生代码实现的 不是继承ClassLoader的类。如rt.jar 
扩展库的类:加载是由ExtClassLoader实现,目录为jre/ext/**jar 
应用程序类:加载由AppClassLoader实现。程序中的类由此加载。 
自定义类加载器:继承ClassLoader自己定义的。 
类加载器用来加载 Java 类到 Java 虚拟机中。也就是把编译后的.class文件生成一个Class类的实例的作用。 
手写一下就是继承classLoad 重写其findLoadedClass,若希望打破原有的双亲委派模型则重写loadClass方法 加载class文件为字节流,使用父类的defineClass将其转化为Class类型返回

5.osgi是如何实现的?


osgi是模块之间的解耦和分离,是java的一种动态模型系统。 
osgi让应用程序无需重新引导可以被远程安装、启动、升级和卸载。 
osgi首先依赖于热部署的jetty,然后本身分为三层,模块层,生命周期层和服务层 
模块可以直接的理解成jar包,能够动态的添加卸载。 
生命周期负责控制动态安装、开启、关闭、更新和卸载模块。 
服务层负责将功能暴露和隐藏,以及发现绑定服务

6.jvm优化方案,使用什么方法达到什么效果?


jvm调优主要也就是内存空间的分配 
最终策略:提高系统性能 
主要策略有 
1.增加eden空间,让更多的对象留在年轻代。 
2.大对象直接放到老年代,以免扰乱年轻代高频率的gc。(XX:PetenureSizeThreshold设置大对象直接进入年老代的阈值) 
并且尽量避免使用短时间存在的大对象。 
3.合理调整进入老年代的年龄 
4.稳定的堆大小对垃圾回收是有利的。获得一个稳定的堆大小的方法是使-Xms 和-Xmx 的大小一致,即最大堆和最小堆 (初始堆) 一样。如果这样设置,系统在运行时堆大小理论上是恒定的,稳定的堆空间可以减少 GC 的次数。 
5.调整内存分页(真实内存虚拟内存) 
6.选用合适的gc

7.class.forName(“java.lang.String”)与String.class.getClassLoader().loadClass(“java.lang.String”)有什么区别?


一个是获得应用程序当前的Class类类型,一个是加载一个新的类。注意这两个类型比较并不一样。

Part3、Spring


1.spring机制,AOP和IOC的底层实现。


简而言之IOC依赖注入,就是单例模式。底层就是一个spring的注册中心一样的东西,无论是xml配置还是注解的方式都会将其读取生成beandefinition,获取时有两种方式,一种是在注册时就创建单例,还有一种是第一次调用时。(当然也可以选择多例模式) 
AOP是面向切面编程,可以自定义一些规则在执行方法前后进行相应的处理。所使用的方法是动态代理。

2.cglib和jdk动态代理的区别,写一个动态代理。


jdk的动态代理需要基于接口,他是在程序运行时通过继承原有类的接口封装原有类的方法和切入逻辑生成一个代理类进行处理。必须要有接口。 
而cglib则不需要,它使用动态字节码生成技术实现AOP原理是在运行期间目标字节码加载后,生成目标类的子类,将切面逻辑加入到子类中。

public class LogInvocationHandler implements InvocationHandler {

    private Object target;//目标对象

    public LogInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //执行织入的日志,你可以控制哪些方法执行切入逻辑
        if (method.getName().equals("doSomeThing2")) {
            System.out.println("记录日志");
        }
        //执行原有逻辑
        Object recv = method.invoke(target, args);
        return recv;
    }
}


Part4、数据库


1.使用mysql索引有哪些原则?索引什么数据结构?B+tree和B Tree什么区别?


索引使用原则 
** 为值唯一的建立唯一索引,可以直接通过值查询到对应记录。 
** 为经常需要使用该字段进行排序、分组和联合操作的字段建立索引 
** 为常作为查询条件的字段建立索引 
** 限制索引的数目: 
索引的数目不是越多越好。每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间就越大。修改表时,对索引的重构和更新很麻烦。 
尽量使用数据量少的索引: 
如果索引的值很长,那么查询的速度会受到影响。例如,对一个CHAR(100)类型的字段进行全文检索需要的时间肯定要比对CHAR(10)类型的字段需要的时间要多。 
** 尽量使用前缀来索引: 
如果索引字段的值很长,最好使用值的前缀来索引。例如,TEXT和BLOG类型的字段,进行全文检索会很浪费时间。如果只检索字段的前面的若干个字符,这样可以提高检索速度。 
** 尽量选择区分度高的列作为索引: 
区分度的公式是count(distinct col)/count(**),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0,一般要求索引的字段区分度最少保证在0.1以上。 
索引列不能参与计算,保持列“干净”: 
比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本 太大。所以语句应该写成create_time = unix_timestamp(’2014-05-29’); 
** 尽量的扩展索引,不要新建索引: 
比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可
索引的数据结构 
常见的有b tree b+tree 散列 通讯R-树
B+tree和B Tree的区别 
** B Tree索引结构: 
一个根节点 可以有数据可以没有 且根节点只能储存一个数据 
设定一个宽度 作为每个节点存储的数据 设为d 
每个子节点有d/2到d-1个数据 有对应数据数+1的指针(孩子节点) 
每个个节点储存的数据都是从左到右按大小排列的 
 
查找时比较大小进入对应的子节点使用二分法继续查找 递归向下直到查找到对应的数据,或者查询出null(失败) 
由于插入删除新的数据记录会破坏B-Tree的性质,因此在插入删除时,需要对树进行一个分裂、合并、转移等操作以保持B-Tree性质。 
** b+tree 
b tree的变种 
变化如下: 
每个节点的指针上限为2d而不是2d+1 
内节点不存储data,只存储key 
叶子节点不存储指针 
 
由于并不是所有节点都具有相同的域,因此B+Tree中叶节点和内节点一般大小不同。这点与B-Tree不同,虽然B-Tree中不同节点存放的key和指针可能数量不一致,但是每个节点的域和上限是一致的,所以在实现中B-Tree往往对每个节点申请同等大小的空间。一般来说,B+Tree比B-Tree更适合实现外存储索引结构 
带有顺序访问指针的B+Tree:一般在数据库系统或文件系统中使用的B+Tree结构都在经典B+Tree的基础上进行了优化,增加了顺序访问指针。 
 
在B+Tree的每个叶子节点增加一个指向相邻叶子节点的指针,就形成了带有顺序访问指针的B+Tree。做这个优化的目的是为了提高区间访问的性能。
区别: 
B+树中只有叶子节点会带有指向记录的指针(ROWID),而B树则所有节点都带有,在内部节点出现的索引项不会再出现在叶子节点中。 
B+树中所有叶子节点都是通过指针连接在一起,而B树不会。

B+树的优点:

1.非叶子节点不会带上指针,这样,一个块中可以容纳更多的索引项,一是可以降低树的高度。二是一个内部节点可以定位更多的叶子节点。
2.叶子节点之间通过指针来连接,范围扫描将十分简单,而对于B树来说,则需要在叶子节点和内部节点不停的往返移动。
B树的优点:

对于在内部节点的数据,可直接得到,不必根据叶子节点来定位。


2.mysql有哪些储存引擎?都有什么区别?


MyISAM 
这种引擎是mysql最早提供的。这种引擎又可以分为静态MyISAM、动态MyISAM 和压缩MyISAM三种: 
** 静态MyISAM:如果数据表中的各数据列的长度都是预先固定好的,服务器将自动选择这种表类型。因为数据表中每一条记录所占用的空间都是一样的,所以这种表存取和更新的效率非常高。当数据受损时,恢复工作也比较容易做。 
** 动态MyISAM:如果数据表中出现varchar、xxxtext或xxxBLOB字段时,服务器将自动选择这种表类型。相对于静态MyISAM,这种表存储空间比较小,但由于每条记录的长度不一,所以多次修改数据后,数据表中的数据就可能离散的存储在内存中,进而导致执行效率下降。同时,内存中也可能会出现很多碎片。因此,这种类型的表要经常用optimize table 命令或优化工具来进行碎片整理。 
** 压缩MyISAM:以上说到的两种类型的表都可以用myisamchk工具压缩。这种类型的表进一步减小了占用的存储,但是这种表压缩之后不能再被修改。另外,因为是压缩数据,所以这种表在读取的时候要先时行解压缩。
MyISAM Merge引擎:这种类型是MyISAM类型的一种变种。合并表是将几个相同的MyISAM表合并为一个虚表。常应用于日志和数据仓库。
InnoDB:InnoDB表类型可以看作是对MyISAM的进一步更新产品,它提供了事务、行级锁机制和外键约束的功能。
memory(heap):这种类型的数据表只存在于内存中。它使用散列索引,所以数据的存取速度非常快。因为是存在于内存中,所以这种类型常应用于临时表中。
archive:这种类型只支持select 和 insert语句,而且不支持索引。常应用于日志记录和聚合分析方面。


3.设计高并发系统数据库层面应该怎么设计?数据库锁有哪些类型?如何实现?


高并发设计 
数据库层面随着并发量和访问量的增加,会经历一系列转型 
1. WEB应用和数据库部署在同一台服务器上:用户量、数据量、并发访问量都比较小 
2. WEB应用和数据库部署在各自独立的服务器上:在系统访问量增加的时候可以分别升级应用服务器和数据库服务器,这种部署方式是一般小规模网站的典型部署方式。 
3. 数据库服务器采用集群方式部署:数据库集群方式能承担的负载是比较大的,数据库物理介质为一个磁盘阵列,多个数据库实例以虚拟IP方式向外部应用服务器提供数据库连接服务。这种部署方式基本上可以满足绝大多数的常见WEB应用,但是还是不能满足大用户量、高负载、数据库读写访问非常频繁的应用。 
4. 数据库采用主从部署方式:在面向大众用户的博客、论谈、交友、CMS等系统中,有上百万的用户,有上千万的数据量,存在众多的数据库查询操作,也有较多的数据库写操作,并且在多数情况下都是读操作远大于写操作的。于是就有了主从不熟。 
** 主从复制: 
几乎所有的主流数据库都支持复制,这是进行数据库简单扩展的基本手段。下面以Mysql为例来说明,它支持主从复制,配置也并不复杂,只需要开启主服务器上的二进制日志以及在主服务器和从服务器上分别进行简单的配置和授权。Mysql的主从复制是一句主服务器的二进制日志文件进行的,主服务器日志中记录的操作会在从服务器上重放,从而实现复制,所以主服务器必须开启二进制日志,自动记录所有对于主数据库的更新操作,从服务器再定时到主服务器取得二进制日志文件进行重放则完成了数据的复制。主从复制也用于自动备份。 
** 读写分离: 
为保证数据库数据的一致性,我们要求所有对于数据库的更新操作都是针对主数据库的,但是读操作是可以针对从数据库来进行。大多数站点的数据库读操作比写操作更加密集,而且查询条件相对复杂,数据库的大部分性能消耗在查询操作上了。 
主从复制数据是异步完成的,这就导致主从数据库中的数据有一定的延迟,在读写分离的设计中必须要考虑这一点。以博客为例,用户登录后发表了一篇文章,他需要马上看到自己的文章,但是对于其它用户来讲可以允许延迟一段时间(1分钟/5分钟/30分钟),不会造成什么问题。这时对于当前用户就需要读主数据库,对于其他访问量更大的外部用户就可以读从数据库。 
** 数据库反向代理: 
在读写分离的方式使用主从部署方式的数据库的时候,会遇到一个问题,一个主数据库对应多台从服务器,对于写操作是针对主数据库的,数据库个数是唯一的,但是对于从服务器的读操作就需要使用适当的算法来分配请求啦,尤其对于多个从服务器的配置不一样的时候甚至需要读操作按照权重来分配。 
对于上述问题可以使用数据库反向代理来实现。就像WEB反向代理服务器一样,MYsql Proxy同样可以在SQL语句转发到后端的Mysql服务器之前对它进行修改。 
5. 数据库垂直分割:主从部署数据库中,当写操作占了主数据库的CPU消耗的50%以上的时候,我们再增加从服务器的意义就不是很大了,因为所有的从服务器的写操作也将占到CPU消耗的50%以上,一台从服务器提供出来查询的资源非常有限。数据库就需要重新架构了,我们需要采用数据库垂直分区技术啦。 
最简单的垂直分区方式是将原来的数据库中独立的业务进行分拆(被分拆出来的部分与其它部分不需要进行Join连接查询操作),比如WEB站点的BLOG和论坛,是相对独立的,与其它的数据的关联性不是很强,这时可以将原来的的数据库拆分为一个BLog库,一个论坛库,以及剩余的表所组成的库。这三个库再各自进行主从数据库方式部署,这样整个数据库的压力就分担啦。 
另外查询扩展性也是采用数据库分区最主要的原因之一。将一个大的数据库分成多个小的数据库可以提高查询的性能,因为每个数据库分区拥有自己的一小部分数据。假设您想扫描1亿条记录,对一个单一分区的数据库来讲,该扫描操作需要数据库管理器独立扫描一亿条记录,如果您将数据库系统做成50个分区,并将这1亿条记录平均分配到这50个分区上,那么每个数据库分区的数据库管理器将只扫描200万记录。 
6. 数据库水平分割:在数据库的垂直分区之后,假如我们的BLOG库又再次无法承担写操作的时候,我们又该怎么办呢?数据库垂直分区这种扩展方式又无能为力了,我们需要的是水平分区。 
水平分区意味着我们可以将同一个数据库表中的记录通过特定的算法进行分离,分别保存在不同的数据库表中,从而可以部署在不同的数据库服务器上。很多的大规模的站点基本上都是主从复制+垂直分区+水平分区这样的架构。水平分区并不依赖什么特定的技术,完全是逻辑层面的规划,需要的是经验和业务的细分。 
如何分区呢?对于大型的WEB站点来说,必须分区,并且对于分区我们没有选择的余地,对于那些频繁访问导致站点接近崩溃的热点数据,我们必须分区。 
在对数据分区的时候,我们必须要存在一个分区索引字段,比如USER_ID,它必须和所有的记录都存在关系,是分区数据库中的核心表的主键,在其它表中作为外键,并且在使用主键的时候,该主键不能是自增长的,必须是业务主键才可以。 
数据库锁 
共享(S)锁:多个事务可封锁一个共享页;任何事务都不能修改该页; 通常是该页被读取完毕,S锁立即被释放。 
排它(X)锁:仅允许一个事务封锁此页;其他任何事务必须等到X锁被释放才能对该页进行访问;X锁一直到事务结束才能被释放。 
更新(U)锁:用来预定要对此页施加X锁,它允许其他事务读,但不允许再施加U锁或X锁;当被读取的页将要被更新时,则升级为X锁;U锁一直到事务结束时才能被释放。

4.数据库事务有哪些?


数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 
1. 事务是作为一个逻辑单元执行的一系列操作,一个逻辑工作单元必须有四个属性,称为 ACID(原子性、一致性、隔离性和持久性)属性,只有这样才能成为一个事务: 
原子性 
2. 事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。 
一致性 
3. 事务在完成时,必须使所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。事务结束时,所有的内部数据结构(如 B 树索引或双向链表)都必须是正确的。 
隔离性 
4. 由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它 之后的状态,事务不会查看中间状态的数据。这称为可串行性,因为它能够重新装载起始数据,并且重播一系列事务,以使数据结束时的状态与原始事务执行的状态 相同。 
持久性 
5. 事务完成之后,它对于系统的影响是永久性的。该修改即使出现系统故障也将一直保持。

开启事务start transaction 
回滚事务rollback 
提交事务commit

多线程事务问题 
1. 脏读:A事务读取B事务尚未提交的更改数据 
2. 不可重复读:不可重复读是指A事务读取了B事务已经提交的更改数据 
3. 幻读:A事务读取B事务提交的新增数据 
4. 第一类丢失更新:A事务撤销时,把已经提交的B事务的更新数据覆盖了。 
5. 第二类丢失更新:A事务覆盖B事务已经提交的数据,造成B事务所做的操作丢失 
事务隔离级别 


Part5、分库分表


1.如何设计可以动态扩容的分库分表方案?


建立查询主键与数据库的映射关系 中间建立虚拟库关系。 
1. 需要存储数据前,先根据ID(数据中总是带有ID),切换数据源 
2. 数据源查找的过程,基本上是对上述两张元数据表进行查找,先找到虚拟库ID,进而找到实际的库连接信息。 
3. 需要提取数据时,也按照上述方法找到实际的库连接信息,进行数据源切换。

2.用过哪些分库分表的中间件,有啥优点缺点?


Cobar 
提供关系型数据库(MySQL)分布式服务的中间件,它可以让传统的数据库得到良好的线性扩展,并看上去还是一个数据库,对应用保持透明。 
Cobar以Proxy的形式位于前台应用和实际数据库之间,对前台的开放的接口是MySQL通信协议,将前台SQL语句变更并按照数据分布规则发到合适的后台数据分库,再合并返回结果,模拟单库下的数据库行为。 
Cobar属于中间层方案,在应用程序和MySQL之间搭建一层Proxy。中间层介于应用程序与数据库间,需要做一次转发,而基于JDBC协议并无额外转发,直接由应用程序连接数据库,性能上有些许优势。这里并非说明中间层一定不如客户端直连,除了性能,需要考虑的因素还有很多,中间层更便于实现监控、数据迁移、连接管理等功能。 
现在Cobar已经停止维护
MyCAT 
社区爱好者在阿里cobar基础上进行二次开发,解决了cobar当时存 在的一些问题,并且加入了许多新的功能在其中。目前MyCAT社区活 跃度很高,目前已经有一些公司在使用MyCAT。总体来说支持度比较高,也会一直维护下去,发展到目前的版本,已经不是一个单纯的MySQL代理了,它的后端可以支持MySQL, SQL Server, Oracle, DB2, PostgreSQL等主流数据库,也支持MongoDB这种新型NoSQL方式的存储,未来还会支持更多类型的存储。 
MyCAT是一个强大的数据库中间件,不仅仅可以用作读写分离,以及分表分库、容灾管理,而且可以用于多租户应用开发、云平台基础设施,让你的架构具备很强的适应性和灵活性,借助于即将发布的MyCAT只能优化模块,系统的数据访问瓶颈和热点一目了然,根据这些统计分析数据,你可以自动或手工调整后端存储,将不同的表隐射到不同存储引擎上,而整个应用的代码一行也不用改变。 
MyCAT是在Cobar基础上发展的版本,两个显著提高:后端由BIO改为NIO,并发量有大幅提高; 增加了对Order By, Group By, Limit等聚合功能
TDDL 
是Tabao根据自己的业务特点开发的 
主要解决了分库分表对应用的透明化以及异构数据库之间的数据复制,它是一个基于集中式配置的jdbc datasourcce实现,具有主备,读写分离,动态数据库配置等功能。 
TDDL源码:https://github.com/alibaba/tb_tddl
DRDS:是阿里巴巴自主研发的分布式数据库服务(此项目不开源)
Atlas 
Atlas是一个位于应用程序与MySQL之间的基于MySQL协议的数据中间层项目,它是在mysql-proxy 0.8.2版本上对其进行优化,360团队基于mysql proxy 把lua用C改写,它实现了MySQL的客户端和服务端协议,作为服务端与应用程序通讯,同时作为客户端与MySQL通讯。它对应用程序屏蔽了DB的细节。 
Altas不能实现分布式分表,所有的字表必须在同一台DB的同一个DataBase里且所有的字表必须实现建好,Altas没有自动建表的功能。 
原有版本是不支持分库分表, 目前已经放出了分库分表版本。
DBProxy 
美团点评DBA团队针对公司内部需求,在奇虎360公司开源的Atlas做了很多改进工作,形成了新的高可靠、高可用企业级数据库中间件 
其特性主要有:读写分离、负载均衡、支持分表、IP过滤、sql语句黑名单、DBA平滑下线DB、从库流量配置、动态加载配置项。 
项目的Github地址是https://github.com/Meituan-Dianping/DBProxy
sharding-JDBC 
当当应用框架ddframe中,从关系型数据库模块dd-rdb中分离出来的数据库水平分片框架,实现透明化数据库分库分表访问。 
Sharding-JDBC是继dubbox和elastic-job之后,ddframe系列开源的第3个项目。 
Sharding-JDBC直接封装JDBC API,可以理解为增强版的JDBC驱动,旧代码迁移成本几乎为零: 
可适用于任何基于Java的ORM框架,如JPA、Hibernate、Mybatis、Spring JDBC Template或直接使用JDBC。 
可基于任何第三方的数据库连接池,如DBCP、C3P0、 BoneCP、Druid等。 
理论上可支持任意实现JDBC规范的数据库。虽然目前仅支持MySQL,但已有支持Oracle、SQLServer等数据库的计划。
Sharding-JDBC定位为轻量Java框架,使用客户端直连数据库,以jar包形式提供服务,无proxy代理层,无需额外部署,无其他依赖,DBA也无需改变原有的运维方式。 
Sharding-JDBC分片策略灵活,可支持等号、between、in等多维度分片,也可支持多分片键。 
SQL解析功能完善,支持聚合、分组、排序、limit、or等查询,并支持Binding Table以及笛卡尔积表查询。

3.现有一个没有分库分表的系统,以后系统需要分库分表,应如何设计让未分库分表的系统切换到分库分表的系统上?


主要是切换数据源 
需要注意的问题有: 
1. 事务问题 
使用分布式事务,交由数据库管理 
由应用程序和数据库共同控制,将一个跨多个数据库的分布式事务分拆成多个仅处 于单个数据库上面的小事务,并通过应用程序来总控各个小事务。 
2. 跨节点Join的问题 
只要是进行切分,跨节点Join的问题是不可避免的。但是良好的设计和切分却可以减少此类情况的发生。解决这一问题的普遍做法是分两次查询实现。在第一次查询的结果集中找出关联数据的id,根据这些id发起第二次请求得到关联数据。 
3. 跨节点的count,order by,group by以及聚合函数问题 
与解决跨节点join问题的类似,分别在各个节点上得到结果后在应用程序端进行合并。 
4. 数据迁移,容量规划,扩容等问题 
5. ID问题 
插入数据需要经过ID生成器 
6. 跨分片的排序分页 
一般来讲,分页时需要按照指定字段进行排序。当排序字段就是分片字段的时候,我们通过分片规则可以比较容易定位到指定的分片,而当排序字段非分片字段的时候,情况就会变得比较复杂了。为了最终结果的准确性,我们需要在不同的分片节点中将数据进行排序并返回,并将不同分片返回的结果集进行汇总和再次排序,最后再返回给用户。 
7. 分库策略 
分库维度确定后,如何把记录分到各个库里,一般有两种策略: 
** 根据数值范围,比如用户Id为1-9999的记录分到第一个库,10000-20000的分到第二个库,以此类推。 
** 根据数值取模,比如用户Id mod n,余数为0的记录放到第一个库,余数为1的放到第二个库,以此类推。 
8. 分库数量 
分库数量首先和单库能处理的记录数有关,一般来说,Mysql 单库超过5000万条记录,Oracle单库超过1亿条记录,DB压力就很大(当然处理能力和字段数量/访问模式/记录长度有进一步关系)。 
在满足上述前提下,如果分库数量少,达不到分散存储和减轻DB性能压力的目的;如果分库的数量多,好处是每个库记录少,单库访问性能好,但对于跨多个库的访问,应用程序需要访问多个库,如果是并发模式,要消耗宝贵的线程资源;如果是串行模式,执行时间会急剧增加。 
最后分库数量还直接影响硬件的投入,一般每个分库跑在单独物理机上,多一个库意味多一台设备。所以具体分多少个库,要综合评估,一般初次分库建议分4-8个库。 
9. 路由透明 
分库从某种意义上来说,意味着DB schema改变了,必然影响应用,但这种改变和业务无关,所以要尽量保证分库对应用代码透明,分库逻辑尽量在数据访问层处理。 
10. 中间件选用 
参照上面问题。

4.分布式事务知道么?如何解决?TCC?如果出现网络原因连不通怎么办?


分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务节点上,分布式事务需要保证这些小操作要么全部成功,要么全部失败;本质上来说,分布式事务就是为了保证不同数据库的数据一致性。 
分布式系统事务要素 
** 一致性:分布式环境下多个节点的数据是否强一致。 
** 可用性:分布式服务能一直保证可用状态。当用户发出一个请求后,服务能在有限时间内返回结果。 
** 分区容忍性:特指对网络分区的容忍性。 
三者只能取其二,而且分区容忍性是不可或缺的 
BASE 理论 
核心思想: 
** 基本可用(BasicallyAvailable):指分布式系统在出现故障时,允许损失部分的可用性来保证核心可用。 
** 软状态(SoftState):指允许分布式系统存在中间状态,该中间状态不会影响到系统的整体可用性。 
** 最终一致性(EventualConsistency):指分布式系统中的所有副本数据经过一定时间后,最终能够达到一致的状态。

一致性分类 
** 强一致性:数据更新成功后,任意时刻所有副本中的数据都是一致的,一般采用同步的方式实现。 
** 弱一致性:数据更新成功后,系统不承诺立即可以读到最新写入的值,也不承诺具体多久之后可以读到。 
** 最终一致性:弱一致性的一种形式,数据更新成功后,系统不承诺立即可以返回最新写入的值,但是保证最终会返回上一次更新操作的值。 
分布式事务解决方案:一般有以下几种 
** 两阶段提交(2PC, Two-phase Commit)方案 
强一致性 
2PC的核心原理是通过提交分阶段和记日志的方式,记录下事务提交所处的阶段状态,在组件宕机重启后,可通过日志恢复事务提交的阶段状态,并在这个状态节点重试,如Coordinator重启后,通过日志可以确定提交处于Prepare还是PrepareAll状态,若是前者,说明有节点可能没有Prepare成功,或所有节点Prepare成功但还没有下发Commit,状态恢复后给所有节点下发RollBack;若是PrepareAll状态,需要给所有节点下发Commit,数据库节点需要保证Commit幂等。 
** eBay 事件队列方案 
最终一致性 
它的核心思想是将需要分布式处理的任务通过消息或者日志的方式来异步执行,消息或日志可以存到本地文件、数据库或消息队列,再通过业务规则进行失败重试,它要求各服务的接口是幂等的。 
** TCC 补偿模式 
最终一致性 
某业务模型由服务 A、服务B、服务C、服务D 共同组成的一个微服务架构系统。服务A 需要依次调用服务B、服务C 和服务D 共同完成一个操作。当服务A 调用服务D 失败时,若要保证整个系统数据的一致性,就要对服务B 和服务C 的invoke 操作进行回滚,执行反向的revert 操作。回滚成功后,整个微服务系统是数据一致的。 
要求 
** 服务调用链必须被记录下来。 
** 每个服务提供者都需要提供一组业务逻辑相反的操作,互为补偿,同时回滚操作要保证幂等。 
** 必须按失败原因执行不同的回滚策略。 
** 缓存数据最终一致性 
在我们的业务系统中,缓存(Redis 或者Memcached)通常被用在数据库前面,作为数据读取的缓冲,使得I/O 操作不至于直接落在数据库上。以商品详情页为例,假如卖家修改了商品信息,并写回到数据库,但是这时候用户从商品详情页看到的信息还是从缓存中拿到的过时数据,这就出现了缓存系统和数据库系统中的数据不一致的现象。 
解决方案: 
为缓存数据设置过期时间。当缓存中数据过期后,业务系统会从数据库中获取数据,并将新值放入缓存。这个过期时间就是系统可以达到最终一致的容忍时间。更新数据库数据后同时清除缓存数据。数据库数据更新后,同步删除缓存中数据,使得下次对商品详情的获取直接从数据库中获取,并同步到缓存。

5.为什么要分库分表?


数据处理能力遭遇瓶颈。

6.分布式寻址方式有哪些算法?什么是一致性hash?


分布式哈希(DHT):每个节点只维护一部分路由;每个节点只存储一部分数据。从而实现整个网络中的寻址和存储。 
普通的后端节点取模算法 
一致性哈希算法: 
DHT的一种实现。本质还是一个哈希算法。回想平时我们做负载均衡,按querystring签名对后端节点取模是最简单也是最常用的算法,但节点的增删后所造成的问题显而易见,原有的请求几乎都落不到同一台机器上。优化一点的是carp算法(用机器ip和querystring一起做hash,选取hash值最小的一台),只让1/n的数据受到影响。 
一致性哈希,似乎最早提出是在分布式cache里面的,让节点震荡的时候,影响最小,以提高分布式cache的命中率。不过现在更多的应用在分布式存储和p2p系统里面。 
一致性哈希也只是提出四个概念和原则,并没有提及具体实现: 
  1、balance:哈希结果尽可能的平均分散到各个节点上,使得每个节点都能得到充分利用。 
  2、Monotonicity:上面也说了,如果是用签名取模算法,节点变更会使得整个网络的映射关系更改。如果是carp,会使得1/n的映射关系更改。一致性哈希的目标是节点变更,不会改变网络的映射关系。 
  3、spread:同一份数据,存储到不同的节点上,换言之就是系统冗余。一致性哈希致力于降低系统冗度。 
  4、load:负载分散,和balance其实是差不多的意思,不过这里更多是指数据存储的均衡,balance是指访的均衡。

7.如何解决分库分表主键问题?有什么实现方案?


UUID 
维护一个Sequence表 
维护多个Sequence表 但是起始值和步长错开

Part6、分布式缓存


1.redis和memcache有什么区别?为何单线程的redis比多线程的memcache效率更高?


网络IO模型 
memcache:是多线程非阻塞IO复用网络模型,分为Master线程和worker子线程,Master线程负责监听网络连接,接受请求后,传递给worker线程, 在worker线程中进行命令接受处理和返回,memcache启动时可以通过 memcached -t num 设置worker线程的数目,网络层使用libevent封装的事件库,多线程模型可以发挥多核作用,但是也引入了缓存一致性和锁的问题。 
Redis:使用单线程IO复用模型,自己封装了一个aeEvent事件处理框架,主要实现了epoll 、kqueue、select,对于单纯只有IO操作来说,单线程可以将速度发挥到最大,但是redis也提供了一些简单的计算功能,排序和聚合,对于这些操作,单线程模型实际会严重影响整体吞吐量,CPU计算过程中,整个IO调度室被阻塞的。
内存管理 
Memcache:使用了Slab Allocation的方式,memcache将内存割分成固定大小的slab,每个slab又可以分为大小相同的许多个page,每个Page中分为大小相同的chunk,chunk是保存数据的单位,每个slab中chunk大小是不相同的,使用slab和大小不同的chunk来管理内存,item根据大小不同选择合适的chunk存储,内存池可以省去申请、释放内存的开销,并且可以较少内存外部碎片的产生,但是依然会产生内部碎片。 并且在内存仍然有很大使用空间的时候,新的数据也有可能恢复剔除,因为根据大小分配到不同的slab class,如果这个slab class已经满了,就会剔除数据,即使其他的slab class还是有空余的。只是因为memcache使用的替换算法LRU算法是针对slab的不是针对全局的。 
Redis:使用现场申请内存的方式来存储数据,会在一定程度上存在内存外部碎片。 并且redis会把带过期时间的数据单独存放在一起,并把它们叫做临时数据,非临时数据永远不会剔除的,即使物理内存不够,导致swap也不会剔除任何非临时数据,这点上redis更适合作为存储而不是cache。而且redis是一种惰性的过期键删除策略,当key过期后仍然会存在在reids中,知道有客户端调用get命令,才会检查key的过期时间,那时再把过期键删除。
数据一致性问题 
memcache是多线程的,所以当并发操作时会存在数据不一致问题,memcache提供了一个cas机制 
redis提供了事务功能,可以保证一连串命令的事务性
存储方式 
memcache只支持简单的key-value存储,不支持持久化和复制,不支持枚举所有key。 
redis除了key-value还支持list set sorted hset hash等众多数据结构,提供了keys进行枚举,redis同时提供了持久化(RDB持久化和AOF持久化)和复制 
redis提供了事务功能,可以保证一连串命令的事务性
分布式存储 
redis支持master-slave复制模式 
memcache可以使用一致性hash做分布式
储存大小 
redis最大可以达到1GB,而memcache只有1MB
为何单线程的redis比多线程的memcache效率更高: 
线程不是影响吞吐量的重要因素。Redis是一个内存数据库,所有数据都在内存中,我们知道内存的读写速度是非常快的,而网络I/O速度则要慢得多,因此数据的读写在整个请求的耗时占比是较小的,使用多线程反而会在线程上下文切换上浪费时间,同时还会带来并发问题,需要对操作的对象加锁,并不能提高性能。 
在网络I/O上,Redis采用了I/O多路复用(Epoll)模型,由于每个请求的处理都非常快,因此单线程即可处理大量的请求,实现了高性能高并发。单纯的网络IO来说,量大到一定程度之后,多线程的确有优势, 但并不是单纯的多线程,而是每个线程自己有自己的epoll模型,也就是多线程和multiplexing混合。但是,考虑Redis操作的是内存中的数据结构,如果在多线程中操作,那就需要为这些对象加锁,所以使用多线程可以提高并发度,但是每个线程的效率严重下降了,而且程序的逻辑严重复杂化。Redis的数据结构并不全是简单的Key-Value,还有list,hash等复杂的结构,这些结构有可能会进行很细粒度的操作,比如在很长的列表后面添加一个元素,在hash当中添加或者删除一个对象,这些操作还可以合成MULTI/EXEC的组。这样一个操作中可能就需要加非常多的锁,导致的结果是同步开销大大增加。Redis在权衡之后的选择是用单线程,突出自己功能的灵活性。在单线程基础上任何原子操作都可以几乎无代价地实现,多么复杂的数据结构都可以轻松运用,甚至可以使用Lua脚本这样的功能。 
Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。

2.redis有什么数据类型?都在什么场景下使用?


String(字符串):二进制安全,可以包含任何数据,比如jpg图片或者序列化的对象,一个键最大能存储512M。 
Hash(字典):适合存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值。键值对集合,即编程语言中的Map,类型适合存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值(Memcached中需要取出整个字符串反序列化成对象修改完再序列化存回去)。 
场景:存储、读取、修改用户属性。 
List(列表):链表(双向链表)。增删快,提供了操作某一段元素的API 
场景:1,最新消息排行等功能(比如朋友圈的时间线) 2,消息队列 
Set(集合):哈希表实现,元素不重复。1,添加、删除,查找的复杂度都是O(1) 2,为集合提供了求交集、并集、差集等操作 
场景:1,共同好友 2,利用唯一性,统计访问网站的所有独立ip 3,好用推荐时,根据tag求交集,大于某个阈值就可以推荐 
Sorted Set(有序集合):将Set中的元素增加一个权重参数score,元素按score有序排列。数据插入集合时,已经进行天然排序 
场景:1,排行榜 2,带权重的消息队列

3.redis主从复制是怎么实现的?redis集群是如何实现的?redis的key是如何寻址的?


主从同步流程 
1、保存主节点信息;从节点只保存主节点的地址信息变直接返回(复制流程还没有开始) 
2、主从建立socket连接:无限重试到链接成功或者执行slaveofnoone取消复制; 
3、发送ping命令(1、检测主从指尖socke是否可用;2、检测主节点当前是否接受处理命令) 
4、权限验证 
5、同步数据集(全量同步和部分同步) 
6、命令持续复制:主节点持续把写命令发送给从节点; 
从节点执行psync,发送psync给主,主根据psync参数和自身数据情况决定响应结果,决定全量复制还是部分复制。全量复制发送RDB文件。部分复制是主从节点间网络出现中断时,如果超过repl-timeout时间,主会认为从故障并中断复制。中断期间,主的命令写入复制缓冲区;然后在从节点连上主之后如果复制缓冲区中存在偏移量的数据,那么表示可以复制,进行复制步骤。 
redis集群是如何实现的 
主从模式: 


只有1个Master,可以有N个slaver,而且Slaver也可以有自己的Slaver,由于这种主从的关系决定他们是在配置阶段就要指定他们的上下级关系 
读写分离:
Master只负责写和同步数据给Slaver,Slaver承担了被读的任务,所以Slaver的扩容只能提高读效率不能提高写效率。
Slaver先将Master那边获取到的信息压入磁盘,再load进内存,client端是从内存中读取信息的,所以Redis是内存数据库。 
哈希Slot: 

其实是redis层面上的分库分表 
对象保存到Redis之前先经过CRC16哈希到一个指定的Node上,例如Object4最终Hash到了Node1上。 
每个Node被平均分配了一个Slot段,对应着0-16384,Slot不能重复也不能缺失,否则会导致对象重复存储或无法存储。 
ode之间也互相监听,一旦有Node退出或者加入,会按照Slot为单位做数据的迁移。例如Node1如果掉线了,0-5640这些Slot将会平均分摊到Node2和Node3上,由于Node2和Node3本身维护的Slot还会在自己身上不会被重新分配,所以迁移过程中不会影响到5641-16384Slot段的使用。 
主从模式和哈希Slot一起使用: 

想扩展并发读就添加Slaver,想扩展并发写就添加Master,想扩容也就是添加Master,任何一个Slaver或者几个Master挂了都不会是灾难性的故障。 
redis的key是如何寻址的 
如上 根据哈希slot进行寻址。


4.使用redis如何设计分布式锁?使用zk可以么?如何实现?这两种那种效率更高?


使用redis中的setnx命令进行设计分布式锁 
关于redis的操作命令,我们一般会使用set,get等一系列操作,数据结构也有很多,这里我们使用最简单的string来存储锁。 
redis下提供一个setnx命令用来将key值设为value,类似于set功能,但是它和set是有区别的,在于后面的nx,setnx是SET if Not eXists。就是:当且仅当key值不存在的时候,将该key值设置为value。 
也就是说使用setnx加入元素的时候,当key值存在的时候,是没有办法加入内容的。 
使用zk大同小异 
是通过创建zk的znode节点进行控制。检查zookeeper集群下的这个节点是否存在存在证明已经有锁了,不存在就没有。 
优劣 
Redis分布式锁,必须使用者自己间隔时间轮询去尝试加锁,当锁被释放后,存在多线程去争抢锁,并且可能每次间隔时间去尝试锁的时候,都不成功,对性能浪费很大。 
Zookeeper分布锁,首先创建加锁标志文件,如果需要等待其他锁,则添加监听后等待通知或者超时,当有锁释放,无须争抢,按照节点顺序,依次通知使用者。

5.redis持久化有什么优缺点?具体底层实现呢?


RDB的优缺点: 
优点:RDB是一个紧凑的二进制文件,代表redis在某个时间点上的数据快照。非常适用于备份,全量复制等场景用于灾难恢复。加载Rdb恢复数据远快于AOF方式。 
缺点:1、无法做到实时持久化/秒级持久化(因为每次都要fork,属于重量级操作) 
2、redis演进过程中有多个格式rdb版本,存在老版本redis无法兼容新版rdb格式的问题 
AOF的优缺点 
优点: 
1. 该机制可以带来更高的数据安全性,即数据持久性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。 
2. 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。 
如果日志过大,Redis可以自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证数据安全性。 
3. AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。 
缺点: 
1. 对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。 
2. 根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。

6.redis过期策略都有哪些?LRU?写一下java版本的代码。


redis过期策略 
redis使用的回收策略是惰性删除和定期删除 
1. 惰性删除 
在进行get或setnx等操作时,先检查key是否过期, 
若过期,删除key,然后执行相应操作; 
若没过期,直接执行相应操作 
2. 定期删除 
遍历每个数据库(就是redis.conf中配置的”database”数量,默认为16) 
检查当前库中的指定个数个key(默认是每个库检查20个key,注意相当于该循环执行20次,循环体时下边的描述) 
如果当前库中没有一个key设置了过期时间,直接执行下一个库的遍历 
随机获取一个设置了过期时间的key,检查该key是否过期,如果过期,删除key。 
判断定期删除操作是否已经达到指定时长,若已经达到,直接退出定期删除。 
redis内存回收策略 
引用计数算法以及LRU算法 
1. 引用计数算法 
引用计数算法作为垃圾收集器最早的算法,现在的JVM都不再采用引用计数算法进行垃圾回收。 
对于创建的每一个对象都有一个与之关联的计数器,这个计数器记录着该对象被使用的次数,垃圾收集器在进行垃圾回收时,对扫描到的每一个对象判断一下计数器是否等于0,若等于0,就会释放该对象占用的内存空间,同时将该对象引用的其他对象的计数器进行减一操作。 
2. LRU算法 
又称为 最近最久未使用算法 
算法演示 
 
虽然可以使用队列实现,但是LRU算法的最经典的实现是HashMap+Double LinkedList(哈希表+双向链表) 
时间复杂度为O(1) 
个人实现

/**思路 
    1.维护一个hashMap和一个双向链表(可以使用LinkList,此处自己实现)
    2.get时通过hashMap查询,更新得到的key到链表头部
    3.set时存入hashMap,更新set的key到链表头部,检查链表大小若超过则把链表尾部去除
**/
public class LRUCache {
    private int capacity = 0;
    private Map<Integer, LRCNode> map = new HashMap<Integer, LRCNode>();
    private int len = 0;
    private LRCNode head = null;
    private LRCNode tail = null;

    public class LRCNode {
        int val;
        int key;
        LRCNode pre;
        LRCNode next;
        public LRCNode(int key, int val) {
            this.val = val;
            this.key = key;
            this.pre = null;
            this.next = null;
        }
    }

    public LRUCache(int capacity) {
        this.capacity = capacity;
    }

    public int get(int key) {
        if(map.containsKey(key)) {
            LRCNode lastest = map.get(key);
            remove(lastest);
            setHead(lastest);
            return lastest.val;
        } else {
            return -1;
        }
    }

    public void set(int key, int value) {
        if(map.containsKey(key)) {
            LRCNode temp = map.get(key);
            remove(temp);
            temp.val = value;
            setHead(temp);
        } else {
            LRCNode node = new LRCNode(key,value);
            if(len < capacity) { 
                map.put(key,node);
                setHead(node);
                len++;
            } else {
                map.remove(tail.key);
                remove(tail);
                map.put(key,node);
                setHead(node);
            }
        }
    }

    private void setHead(LRCNode node) {
        node.pre = null;
        node.next = head;
        if(head != null)
            head.pre = node;
        head = node;
        if(tail == null) 
            tail = node;
    }

    private void remove(LRCNode node) {
        LRCNode pre = node.pre;
        LRCNode cur = node;
        LRCNode next = node.next;
        if(pre != null) 
            pre.next = next;
        else {
            head = next;
        }
        if(next != null)
            next.pre = pre;
        else {
            tail = pre;
        }
    }
}


Part7、分布式服务框架


1.dubbo的实现过程?注册中心挂了可以继续通信么?


服务容器负责启动,加载,运行服务提供者。
服务提供者在启动时,向注册中心注册自己提供的服务。
服务消费者在启动时,向注册中心订阅自己所需的服务。
注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外
注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者
注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表

2.zk的原理知道么?zk都可以干什么?paxos算法知道么?说一下原理和实现。

zk的原理: 
主要提供了分布式锁 
分布式锁原理:依赖zk的原子性特性进行临时节点的创建(会话的创建) 
使用zk集群+选举算法,使得半数之内的zk服务挂掉服务都可以继续运行,使用复制的形式来进行数据同步使得zk本地端数据与leader一定程度上的同步 
服务可以连接任意一个zk客户端进行注册。在读数据时从客户端读取,写时通过客户端找通知leader去实施。 
zk都可以干什么 
zk提供的主要功能是提供分布式锁服务 
在此基础上发展出了其他的使用方法:配置维护、组服务、分布式消息队列、分布式通知/协调等。 
paxos算法 
paxos算法是基于消息传递且具有高度容错特性的一致性算法。 
Paxos算法 - CSDN博客

3.dubbo支持哪些序列化协议?hessian?说一下hession的数据结构。PB知道么?为啥PB效率是最高的?

dubbo序列化:阿里尚未开发成熟的高效java序列化实现,阿里不建议在生产环境使用它
hessian2序列化:hessian是一种跨语言的高效二进制序列化方式。但这里实际不是原生的hessian2序列化,而是阿里修改过的hessian lite,它是dubbo RPC默认启用的序列化方式
json序列化:目前有两种实现,一种是采用的阿里的fastjson库,另一种是采用dubbo中自己实现的简单json库,但其实现都不是特别成熟,而且json这种文本序列化性能一般不如上面两种二进制序列化。
java序列化:主要是采用JDK自带的Java序列化实现,性能很不理想。
Kryo:Kryo序列化机制比默认的Java序列化机制速度要快,序列化后的数据要更小,大概是Java序列化机制的1/10。所以Kryo序列化优化以后,可以让网络传输的数据变少,在集群中耗费的内存资源大大减少。需要注意的是其基于类的field。性能上优于古老的hessian,虽然类第一次序列化时优于Kryo要去加载该类导致速度上不及hessian,但是无论在大小速度上都和特性支持上都强于hessian
FST:fst是完全兼容JDK序列化协议的系列化框架,序列化速度大概是JDK的4-10倍,大小是JDK大小的1/3左右。
ProtoBuf:google开发的开源的序列化方案,独立于语言,独立于平台,效率相当高,用protobuf序列化后的大小是json的10分之一,xml格式的20分之一,是二进制序列化的10分之一。
ProtoStuff:ProtoBuf的一个优化版本,不需要我们去写.proto文件了。 
hession的数据结构 

pb效率为何这么高 
一、使用三元组储存方式:TLV(Tag - Length - Value)以标识 - 长度 - 字段值表示单个数据,最终将所有数据拼接成一个字节流,从而实现数据存储的功能。
不需要分隔符 就能 分隔开字段,减少了 分隔符 的使用
各字段 存储得非常紧凑,存储空间利用率非常高
若 字段没有被设置字段值,那么该字段在序列化时的数据中是完全不存在的,即不需要编码 
二、Protocol Buffer对于不同数据类型 采用不同的 序列化方式 

三、使用Varint & Zigzag编码方式对不同大小的值以不同的大小进行表示,对数据进行进一步的压缩 四、通过.proto定义文件使用嵌套消息类型节约定义成本(类似于json字符串一样的嵌套)

4.知道netty么?netty可以干嘛啊?NIO,BIO,AIO都是什么?有什么区别?

参照博文 
架构设计:系统间通信(3)——IO通信模型和JAVA实践 上篇 - CSDN博客 
及后续的几篇来了解

5.dubbo负载均衡策略和高可用策略都有哪些?动态代理策略呢?

dubbo提供几种预置负载均衡可选策略 
1. Random:随机,按权重设置随机概率。在一个截面上碰撞的概率很高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。 
2. RoundRobin:轮循,按公约后的权重设置轮循比率。注意:轮询策略存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。 
3. LeastActive:最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差(调用前的时刻减去响应后的时刻的值 也就是请求耗时),使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。 
4. ConsistentHash:一致性 Hash。相同参数的请求总是发到同一提供者。当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。 
高可用性:见第一问dubbo的实现过程 
动态代理策略:基于Spring Bean生成的rpc服务代理。所有的Dubbo消费者Bean都是ReferenceBean类型的对象,interface属性中配置的接口只是让ReferenceBean对象知道Dubbo的服务提供方提供的方法签名而已

6.为什么要进行系统拆分?拆分不用dubbo可以么?dubbo和thrift有什么区别?

系统拆分可能是由于技术团队扩大,组织结构变化从最初的一个团队逐渐成长并拆分为几个团队,团队按照业务线不同进行划分,为了减少各个业务系统和代码间的关联和耦合,几个团队不再可能共同向一个代码库中提交代码,必须对原有系统进行拆分。 
其不仅是为了适应团队规模扩张,系统拆分也是soa架构实现的一环,并且具有代码成果的安全性、方便服务替换、解耦各个功能模块以增加交付速度等优势。 
dubbo只是一种通用的系统拆分服务治理解决方案,不用当然可以。自己引入rpc框架,监控体系,消息系统也能实现。 
thrift是rpc通讯框架,通过.thrift文件实现rpc通讯。dubbo是服务治理框架,不仅包含rpc,更多的是服务治理。比如注册中心,mq,监控等等。

Part8、分布式消息队列

1.为什么使用消息队列?消息队列有什么优点和缺点?

出现原因: 
主要原因是由于在高并发环境下,由于来不及同步处理,请求往往会发生堵塞,比如说,大量的insert,update之类的请求同时到达MySQL,直接导致无数的行锁表锁,甚至最后请求会堆积过多,从而触发too many connections错误。通过使用消息队列,我们可以异步处理请求,从而缓解系统的压力。 
解决问题: 
系统中出现“生产“和“消费“的速度或稳定性等因素不一致的时候,就需要消息队列,作为抽象层,弥合双方的差异。 
优点: 
1. 解耦:将消息写入消息队列,需要消息的系统自己从消息队列中订阅,从而系统不需要做任何修改。 
2. 异步:将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度 
3. 削峰:系统慢慢的按照数据库能处理的并发量,从消息队列中慢慢拉取消息。在生产中,这个短暂的高峰期积压是允许的。 
缺点: 
通讯依赖中间件:与其他系统交互依赖了第三方实现。可用性降低(中间件挂掉会出现问题) 
系统复杂性增加:需要额外考虑一致性问题,重复消费问题等诸多由于中间件带来的问题。 
非及时:无法确保消息被消费的时间。不能作为单次请求链条的依赖。

2.如何保证消息队列的高可用?如何保证消息不被重复消费?

如何保证消息队列的高可用:当然是集群和broker储存消息机制。 
不被重复消费:通过幂等回调方式来确保。

3.kafka,acticemq,rabbitmq,rocketmq都有什么优缺点?


4.如果让你写一个消息队列,应该如何进行架构设计?说一下思路。

个人认为mq底层需要有一套主题-生产者—消费组-消费者模型进行支持。需要一个注册模块,注册消费机器到消费组。每一个topic需要维护自己的消费组。然后储存模块,储存生产者发送消息及生产-消费轨迹,消费模块,通知到一个消费组某台机器进行消费。需要基于zk发现服务(生产者及消费者机器)。

Part9分布式搜索引擎

1.es的工作过程如何实现?如何实现分布式的?

启动过程 
当ElasticSearch的节点启动后,它会利用多播(multicast)(或者单播,如果用户更改了配置)寻找集群中的其它节点,并与之建立连接。 
在集群中,一个节点被选举成主节点(master node)。这个节点负责管理集群的状态,当群集的拓扑结构改变时把索引分片分派到相应的节点上。 
从用户的角度来看,主节点在ElasticSearch中并没有占据着重要的地位,这与其它的系统(比如数据库系统)是不同的。实际上用户并不需要知道哪个节点是主节点;所有的操作需求可以分发到任意的节点,ElasticSearch内部会完成这些让用户感到不明觉历的工作。在必要的情况下,任何节点都可以并发地把查询子句分发到其它的节点,然后合并各个节点返回的查询结果。最后返回给用户一个完整的数据集。所有的这些工作都不需要经过主节点转发(节点之间通过P2P的方式通信)。 
主节点会去读取集群状态信息;在必要的时候,会进行恢复工作。在这个阶段,主节点会去检查哪些分片可用,决定哪些分片作为主分片。处理完成后,集群就会转入到黄色状态。 
这意味着集群已经可以处理搜索请求了,但是还没有火力全开(这主要是由于所有的主索引分片(primary shard)都已经分配好了,但是索引副本还没有)。接下来需要做的事情就是找到复制好的分片,并设置成索引副本。当一个分片的副本数量少了,主节点会决定将缺少的分片放置到哪个节点中,并且依照主分片创建副本。所有工作完成后,集群就会变成绿色的状态(表示所有的主分片的索引副本都已经分配完成)。 
探测失效节点 
在正常工作时,主节点会监控所有的节点,查看各个节点是否工作正常。如果在指定的时间里面,节点无法访问,该节点就被视为出故障了,接下来错误处理程序就会启动。集群需要重新均衡——由于该节点出现故障,分配到该节点的索引分片丢失。其它节点上相应的分片就会把工作接管过来。换句话说,对于每个丢失的主分片,新的主分片将从剩余的分片副本(Replica)中选举出来。重新安置新的分片和副本的这个过程可以通过配置来满足用户需求。

2.es在数据量很大的情况下如何提高查询效率?

减小refresh时间间隔 
减少刷新频率,降低潜在的写磁盘性能损耗, 默认的刷新时间间隔是1s,对于写入量很大的场景,这样的配置会导致写入吞吐量很低,适当提高刷新间隔,可以提升写入量,代价就是让新写入的数据在60s之后可以被搜索,新数据可见的及时性有所下降。 
在bulk大量数据到ES集群的时候可以关闭刷新频率,把其值设置为-1就是关闭了刷新频率,在导入完之后设置成合理的值即可,例如30s或者60s即可。
replica数目设置 
在bulk大量数据到ES集群的可以把副本数设置为0,在数据导入完成之后再设置为1或者你集群的适合的数目。
设置合理的merge相关参数
设置合理的Translog门槛
修改配置文件调整ES的JVM内存大小
去掉mapping中_all字段 
_all字段会默认会把所有字段的内容都拷贝到这一个字段里面,这样会给查询带来方便,但是会增加索引时间和索引尺寸。
3.es查询是一个怎样的工作过程?底层的lucence介绍一下?倒排索引知道么?es和mongdb有什么区别?都在什么场景下使用?
工作过程 
通常这个过程分为两个阶段:查询分发阶段和结果汇总阶段。在查询分发阶段,会从各个分片中查询数据;在结果汇总阶段,会把从各个分片上查询到的结果进行合并、排序等其它处理过程,然后返回给用户。 
lucence 
Lucene是一款高性能的、可扩展的信息检索(IR)工具库。信息检索是指文档搜索、文档内信息搜索或者文档相关的元数据搜索等操作。 
倒排索引 
一般数据库索引是根据id去查找数据,而倒排索引是根据属性的hash值建立索引树,索引到的属性值映射的不是输出存储的地址,而是数据该属性的id,然后再根据id去获取数据。 
es和mongdb有什么区别 
Elasticsearch、MongoDB和Hadoop比较 - 简书

Part10、通信协议

1.说一下TCP/IP四层结构。

链路层(数据链路层/网络接口层):包括操作系统中的设备驱动程序、计算机中对应的网络接口卡
网络层(互联网层):处理分组在网络中的活动,比如分组的选路。
运输层:主要为两台主机上的应用提供端到端的通信。
应用层:负责处理特定的应用程序细节。

2.http工作流程?http1.0,1.1,2.0具体有哪些区别?

工作流程 
1. 地址解析:把url地址解析成解出协议名、主机名、端口、对象路径等部分 
2. 封装http请求数据包 
3. 封装成tcp包,建立tcp连接(三次握手) 
4. 客户端发送请求 
5. 服务器响应 
6. 服务器关闭tcp连接 
http1.0,1.1,2.0的区别 
1. 长连接:HTTP 1.0需要使用keep-alive参数来告知服务器端要建立一个长连接,而HTTP1.1默认支持长连接。 
2. 节约带宽:HTTP 1.1支持只发送header信息(不带任何body信息),如果服务器认为客户端有权限请求服务器,则返回100,否则返回401。客户端如果接受到100,才开始把请求body发送到服务器。 
这样当服务器返回401的时候,客户端就可以不用发送请求body了,节约了带宽。 
3. HOST 域:现在可以web server例如tomat,设置虚拟站点是非常常见的,也即是说,webserver上的多个虚拟站点可以共享同一个ip和端口。 
HTTP1.0是没有host域的,HTTP1.1才支持这个参数。

HTTP2.0使用了多路复用的技术,:做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1大了好几个数量级。
数据压缩:HTTP1.1不支持header数据的压缩,HTTP2.0使用HPACK算法对header的数据进行压缩,这样数据体积小了,在网络上传输就会更快。
服务器推送:意思是说,当我们对支持HTTP2.0的web server请求数据的时候,服务器会顺便把一些客户端需要的资源一起推送到客户端,免得客户端再次创建连接发送请求到服务器端获取。这种方式非常合适加载静态资源。

3.TCP三次握手,四层分手的工作流程?画一下流程图。为什么是四次?

 

三次握手的原因: 
如果两次握手如果客户端请求在某个节点停留较长时间 到了服务端就相当于服务端直接与客户端建立连接,但是这是一个已经失效了的报文。这种连接不可靠。 
四次挥手的原因: 
当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,”你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

4.画一下https的工作流程?具体如何实现?如何防止被抓包?

1.[Server]生成一对密钥:公钥和私钥,我们称之为“KeyPub”,“KeyPri” 
2.[Server]服务端将公钥(KeyPub)发送到客户端 
3.[Client]生成一个对称密钥(姑且称之为key2),然后用key2加密数据。 
4.[Client]使用公钥(KeyPub)加密key2.这时,key2是安全的,因为只有服务度有私钥KeyPri 
5.[Client]发送用key2加密后的信息及用KeyPub加密过的key2到服务端 
6.[Server]服务端使用KeyPri解密得到加密过的key2,得到真正的key2 
7.[Server]使用key2解密消息正文。这样,数据就被安全的传输到了服务端。 
具体实现可以参照rsa加密流程 
RSA加密使用方式及签名验证 - CSDN博客 
如何防止被抓包:https 所谓的防止被抓包并不是能够杜绝抓包的行为,而是让抓包变得无意义,例如用https进行抓包之后,抓包方无法得知里面的数据内容,也无法进行伪造。 
除了https防止抓包外,数据传输更内层也对数据进行了保护,也就是SSL(Secure Socket Layer,安全套接字层)。

Part12、算法

1.有一个文件,有45亿个阿拉伯数字,如何去重?如何找到最大的那个?

使用bitMap,映射int到byte的关系上进行去重操作 一个byte能表示8个数,这样可以把申请的内存变为原来的1/8
--------------------- 
作者:yowasa 
来源:CSDN 
原文:https://blog.csdn.net/yowasa/article/details/81320795 
版权声明:本文为博主原创文章,转载请附上博文链接!

猜你喜欢

转载自blog.csdn.net/weixin_38405770/article/details/84333732