JAVA学习

annotation

元注解:

1
2
3
4
1. @Target
2. @Document
3. @Retention
4. @Inherited

@Target,注解的修饰的范围

1
2
3
4
5
6
1. ElementType.CONSTRUCTOR 构造器
2. ElementType.METHOD 方法
3. ElementType.FIELD 属性
4. ElementType.TYPE 接口或者类
5. ElementType.PARAMETER 参数
6. ElementType.PACKAGE 包

Retention 注释修饰的时间

1
2
3
1. RetentionPolicy.RESOURCE 源文件保留
2. RetentionPolicy.RUNTIME 运行时保留
3. RetentionPolicy.CLASS class文件保留

@Inherited 元注解的一个标记注解,被标注的类型是被继承的。

cdn

CDN简介:

CDN就是内容分发网络 Content Delivery NetWork,构建在Internet上一种先进的流量分配网络。在现有的Internet
中添加一层新的网络架构,将网络内容分发到离用户最近的网络边缘,使用户可以就近取得所需的内容。

CDN缓存内容:

目前CDN都以缓存网站中静态数据为主,如CSS. JS. 图片和静态页面等数据。用户在主站服务器请求到动态内容后,再从CDN上下载
这些静态数据,从而加速网站数据内容的下载速度。

CDN要达到的目标:

  1. 可扩展性. 2. 安全性. 3. 可靠性. 响应和执行

负载均衡:

  1. 链路负载均衡:DNS解析成不同IP
  2. 集群负载均衡:由硬件和软件负载均衡
  3. 系统负载均衡:利用操作系统级别的软中断和硬中断来达到负载均衡,例如可以设置多列网卡。

CDN动态加速

  1. CDN动态加速原理;在CDN的DNS解析中,通过动态的链路探测来寻找回源最好的一条路径,然后将DNS调度将所有请求调度到选定
    的这条路径上回源,从而加速用户的访问效率。
  2. 如何选择源站链路最好的路径让用户走,一个简单的原则就是在每个CDN节点上,从源站下载一个一定大小的文件,看哪个路径耗时最短,
    可以构成一个链路列表,绑定到DNS解析上。

class

简介

1:任何一个Class文件都对应唯一一个类或者接口的定义信息。Class文件是一组8位字节码为基础单位的二进制流,各个项目岩哥按照顺序紧凑的排列在Class文件中,中间没有添加任何的分隔符,Class文件存储的几乎是全部的必要数据。
2:Class文件格式采用二中类型数据:无符号数和表。无符号数:无符号数属于基本数据类型,用来描述数字. 索引引用. 数量值或者按照UTF-8编码组成的字符串。表:是由多个无符号数或者其他表作为数据项构成的符合数据类型,所有的表都习惯性的以”_info”结尾。
3:每个Class文件的头4个字节成为”魔数”,它的唯一作用就是确定这个文件是否为一个能够被虚拟机接受的Class文件,很多文件存储标准中都使用魔数来进行身份识别。
4:Class文件的魔数值为oxCAFEBABE

类的加载时机

  1. 类从加载到虚拟机内存开始,到卸载出内存位置。它的生命周期包括:加载. 连接. 初始化. 使用. 卸载。连接:验证. 准备. 解析。

类的初始化

  1. 遇到new. getstatic. putstatic或invokestatic这4条字节码指令的时候,如果类没有初始化,则需要初始化。new创建一个对象,获取或者设置静态属性. 调用静态方法时,需要初始化。
  2. 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。
  3. 当初始化一个类的时候,发现其父类没有初始化,则需要先触发其父类的初始化。
  4. 当使用jdk7动态语言支持时,有些方法句柄对应的类,没有进行初始化,需要触发其初始化。

类的加载过程

  1. 在加载阶段,虚拟机需要完成一下3件事情,1:通过一个类的权限名来获取定义此类的二进制字节流。
    2:将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构。3:在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

类加载过程:

  1. 加载:查找和导入class文件
  2. 连接:
    2.1:验证:检查载入Class文件是否符合JVM规范。
    2.2:准备:将静态变量分配存储空间。
    2.3:解析:将符号引用,转换成直接引用。
  3. 初始化:对类的静态变量,静态代码块初始化操作。

类的加载过程:

  1. 类的主动引用(一定会发生类的初始化)
    1.1 new一个类的对象
    1.2 调用一个类的静态成员或者静态方法
    1.3 使用反射方法对类进行反射调用
    1.4 启动main方法所在的类
    1.5 当初始化一个类时,其父类如果没有被初始化,则先初始化父类
  2. 类的被动引用(不会发生类的初始化)
    2.1 当访问一个静态域时,只有真正声明的这个类才会被初始化
    通过子类引用父类的静态变量,子类不会被初始化
    2.2 通过数组定义引用类,不会触发类的初始化
    2.3 引用常量不会触发此类的初始化

类加载器的代理模式:

  1. 代理模式:交给其他加载器来加载指定的类
  2. 双亲委托机制:在加载类时,首先将加载任务交给父类加载器加载。双亲委托机制是为了保证java核心类库的类型安全。

类加载器的层次结构:

  1. 引导类加载器(根加载器)
  2. 扩展类加载器
  3. 应用程序加载器
  4. 自定义加载器

线程上下文加载器:

  1. 当需要动态加载资源的时候,你至少有三个classloader可以选择:
    1.1系统类加载器叫做应用加载器
    1.2当前类加载器
    1.3当前线程加载器
    1.4线程类加载器是为了抛弃双亲委派加载链模式
    每个线程都有一个关联的上下文类加载器。如果使用new Thread方式生成新的线程,新线程将继承父线程的上下文加载器

concurrent

  • jdk1.5以后,推出了java.util.concurrent包
  • Executor:具体的Runnable的执行者
  • ExecutorService:一个线程池的管理者,其实现类有很多,可以把Runnable. Callable提交到线程池中调用。
  • Semaphore:信号量。
  • ReentrantLock:可重入的互斥锁。
  • Future:与Runnable. Callable接口进行交互。
  • BlockingQueue:阻塞队列。
  • CompletionService:ExecutorService的扩展,可以活得最先执行完线程的结果,底层实现是通过阻塞队列。
  • CountDownLatch:一个同步辅助类,完成一组正在其他线程中执行的操作之前,允许一个或者多个线程一直等待。
  • CyclicBarrier:一个同步辅助类,它允许一组线程互相等待,直到达到某个屏障点。
  • CopyOnWriteArrayList:
  1. 在CopyOnWriteArrayList在处理写(包括add. set. remove)操作的时候,先将原始数据通过Array.copyOf来生成一个新的数组,
    在新的数据对象上写,写完后再将原来的引用指向当前的数据对象,并加锁。
  2. 读操作在引用的当前引用的对象上进行读,不存在加锁和阻塞。
  3. 因为每次使用CopyOnWriteArrayList.add都要引起数组拷贝,所以应该避免在循环中使用。可以在初始化完成之后设置到CopyeOnWriteArrayList中,或者使用CopyOnWriteArrayList.addAll方法。
  4. CopyOnWriteArrayList采用“写入时复制”策略,对容器的写操作将导致的容器中基本数据的复制,性能开销较大。所以在有写操作的情况下,CopyOnWriteArayList性能不佳,而且如果容器容量较大的话容易造成溢出。
  • 线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
    说明:Executors 返回的线程池对象的弊端如下:
    1)FixedThreadPool 和 SingleThreadPool:
    允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
    2)CachedThreadPool 和 ScheduledThreadPool:
    允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
  • SimpleDateFormat 是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用 DateUtils 工具类。

正例:注意线程安全,使用 DateUtils。亦推荐如下处理:

1
2
3
4
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() { @Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
};}
  • 高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能 锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
  • 对多个资源. 数据库表. 对象同时加锁时,需要保持一致的加锁顺序,否则可能会造 成死锁。
    说明:线程一需要对表 A. B. C 依次全部加锁后才可以进行更新操作,那么线程二的加锁顺序 也必须是 A. B. C,否则可能出现死锁。
  • 并发修改同一记录时,避免更新丢失,要么在应用层加锁,要么在缓存加锁,要么在 数据库层使用乐观锁,使用 version 作为更新依据。
    说明:如果每次访问冲突概率小于20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于3次。
  • 多线程并行处理定时任务时,Timer 运行多个 TimeTask 时,只要其中之一没有捕获 抛出的异常,其它任务便会自动终止运行,使用 ScheduledExecutorService 则没有这个问题。
  • 使用 CountDownLatch 进行异步转同步操作,每个线程退出前必须调用 countDown方法,线程执行代码注意 catch 异常,
    确保 countDown 方法可以执行,避免主线程无法执行 至 countDown 方法,直到超时才返回结果。 说明:注意,子线程抛出异常堆栈,不能在主线程 try-catch 到。
  • 避免 Random 实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一 seed 导致的性能下降。
    说明:Random 实例包括 java.util.Random 的实例或者 Math.random()实例。
    正例:在 JDK7 之后,可以直接使用 API ThreadLocalRandom,在 JDK7 之前,可以做到每个 线程一个实例。
  • volatile 解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题, 但是如果多写,同样无法解决线程安全问题。如果是 count++操作,使用如下类实现:
    AtomicInteger count = new AtomicInteger(); count.addAndGet(1);如果是 JDK8,推 荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)。
  • HashMap 在容量不够进行 resize 时由于高并发可能出现死链,导致 CPU 飙升,在 开发过程中注意规避此风险
  • ThreadLocal 无法解决共享对象的更新问题,ThreadLocal 对象建议使用 static 修饰。
    这个变量是针对一个线程内所有操作共有的,所以设置为静态变量,所有此类实例共享 此静态变量 ,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只 要是这个线程内定义的)都可以操控这个变量。

core

volatile

  1. volatile修饰的变量,可以保证从主内存加载到线程内存的值是最新的。
  2. volatile可以防止指令重排序。

单例模式失效

双重锁检测,可能会失效。原因在于:初始化Singleton和将地址写入到instance字段的顺序是不确定的。在某个线程new Singleton()时,在构造方法之前,就为
该对象分配了内存空间并将对象的字段设置成默认值。此时就可以将分配的内存地址赋值给instance字段了,然而改对象可能还没有初始化;此时若另外一个线程来调用
getInstance,渠道的状态就是不正确的对象。

一致性Hash算法

  1. 一个分布式系统,要将数据存储到具体的节点。如果普通的hash方法,将数据映射到具体的节点上,如key%N,key是数据的key,N是机器节点,如果有一个机器加入
    或者退出这个集群,则所有的数据映射都无效,如果是持久化存储则要做数据迁移,如果是分布式存储,则其他存储就失效了。
  2. 把数据用户hash函数(如MD5),映射到一个很大的空间。数据的存储时,先得到一个hash值,对应到这个环上的每个位置。数据沿着顺时针找到一个机器节点,将数据
    存储到这个机器上。
  3. 当其中一个机器节点挂掉后,另外一个节点会承担挂掉节点的数据,另外一个节点很容易宕机。
  4. 引入”虚拟节点”的概念:即把想象在这个环上有很多“虚拟节点”,数据的存储是沿着环的顺时针方向找一个虚拟节点,每个虚拟节点都会关联到一个真实节点。由于这些
    虚拟节点数量很多,均匀分布,因此不会造成“雪崩”现象。

equals 和 hashcode

在Java中任何一个对象都具备equals(Object obj)和hashcode()这两个方法,因为他们是在Object类中定义的。
equals(Object obj)方法用来判断两个对象是否“相同”,如果“相同”则返回true,否则返回false。
hashcode()方法返回一个int数,在Object类中的默认实现是“将该对象的内部地址转换成一个整数返回”。
若重写equals(Object obj)方法,有必要重写hashcode()方法,确保通过equals(Object obj)方法判断结果为true的两个对象具备相等的hashcode()返回值
Oject类中的equals方法用来比较两个引用值,hashCode用来返回引用的内地址的十六进制数值。在Set以及Map集合中,判断两个元素是否重复时,往往需要使用这两个方法。这两个方法往往被子类覆盖

  1. 如果两个对象equals,Java运行时环境会认为他们的hashcode一定相等。
  2. 如果两个对象不equals,他们的hashcode有可能相等。
  3. 如果两个对象hashcode相等,他们不一定equals。
  4. 如果两个对象hashcode不相等,他们一定不equals。
    for testsdsdsd
  5. 如果两个对象hashcode相等,他们不一定equals。
  6. 如果两个对象hashcode不相等,他们一定不equals。

design

设计模式的六大原则:

  1. 开闭原则:对扩展开放,对修改关闭。为了程序可扩展. 易于维护和升级,需要使用接口和抽象类。
  2. 里氏替换原则:任何基类出现的地方,子类一定可以出现。
  3. 依赖反转原则:针对接口编程,依赖抽象,不依赖具体。
  4. 接口隔离原则:使用多个隔离的接口,比使用单个接口要好。
  5. 迪米特法则(最少知道原则):一个实体应该尽量少的和其他实体之间发生交互。
  6. 合成复用原则:尽量使用聚合,合成方式,而不是使用继承。

设计模式分类

  1. 创建型模式:工厂模式. 抽象工厂模式. 单例模式. 建造者模式,原型模式。
  2. 结构型模式:适配器. 装饰器. 代理. 外观. 桥接. 组合. 享元模式
  3. 行为型模式:策略模式. 模板方法. 观察者模式. 迭代子模式. 责任链模式. 命令模式. 备忘录模式. 状态模式. 访问者模式. 中介模式. 解析器模式。
  4. 其他2种:并发型模式. 线程池模式。

常见设计模式:

  1. 策略模式:定义了一系列算法,并将每个算法封装起来,使他们可以互相替换,且算法的变化不会影响到使用算法的客户。需要设计一个接口,为
    一系列实现类提供方法,多个实现类实现该接口。
  2. 模板方法:准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式展现,然后声明一些抽象方法来迫使子类实现剩余逻辑。不同的子类可以以不同
    的逻辑来实现。
  3. 工厂方法:建立一个工厂类,对实现了同一个接口的一些类做实例化。
  4. 适配器模式:适配器模式将某个类的接口转换成希望的另外一个接口表示,目的是为了消除由于接口不匹配造成的兼容性问题。主要包括三类,类的适配器.
    对象的适配模式. 接口的适配模式。
    类的适配器:有一个Source类,拥有一个方法,待适配,目标接口是Targetable,通过Adapter类,将Source的功能扩展到Targetable里。
    对象的适配器:基本思路和类的适配器模式相同,只是将Adapter类作修改,这次不继承Source类,而是持有Source类的实例,以达到解决兼容性的问题。
    接口的适配器:有时我们写的一个接口中有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显有时比较浪费,因为并不是所有的方法都是我们需要的,
    有时只需要某一些,此处为了解决这个问题,我们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法,而我们不和原始的接口打交道,
    只和该抽象类取得联系,所以我们写一个类,继承该抽象类,重写我们需要的方法就行
  5. 装饰模式:装饰模式就是给一个对象增加一些新的功能,而且是动态的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例
  6. 代理模式:代理模式就是多一个代理类出来,替原对象进行一些操作

面向对象的五大基本原则(solid)

  1. S单一职责SRP:Single-Responsibility Principle 一个类,最好只做一件事,只有一个引起它的变化。单一职责原则可以看做是低耦合,高内聚在面向对象原则的引申,将职责定义为引起变化的原因,以提高内聚性减少引起变化的原因。
  2. O开放封闭原则OCP:Open-Closed Principle 软件实体应该是可扩展的,而不是可修改的。对扩展开放,对修改封闭
  3. L里氏替换原则LSP:Liskov-Substitution Principle 子类必须能够替换其基类。这一思想表现为对继承机制的约束规范,只有子类能够替换其基类时,才能够保证系统在运行期内识别子类,这是保证继承复用的基础。
  4. I接口隔离原则ISP:Interface-Segregation Principle 使用多个小的接口,而不是一个大的总接口
  5. D依赖倒置原则DIP:Dependency-Inversion Principle 依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者共同依赖于抽象。抽象不依赖于具体,具体依赖于抽象。

EasyCoding

Cpu与内存

  1. Cpu是一块超大规模集成电路板,是计算机的核心部件。CPU包括:控制器. 运算器. 寄存器;
  2. 控制器组成:控制单元. 指令译码器. 指令寄存器组成;控制单元是CPU的大脑,由时序控制器和指令控制器组成;指令译码器是在控制单元的协调下完成指令读取,
    分析并交由运算器执行;指令寄存器是存成指令集,当前流行的指令集包括:X86,SSE,MMX等。
  3. 运算器:核心是算术逻辑运算单元,ALU,能执行算术运算或逻辑运算等各种指令。运算单元会从寄存器中提取或者存储数据。
  4. 寄存器:最著名的寄存器是CPU的高速缓存L1. L2,缓存容量是在组装计算机逼问的CPU性能问题之一;CPU缓存部分指令和数据,以提升性能;
  5. L0寄存器. L1一级缓存. L2二级缓存. 内存. 本地磁盘. 分布式存储. 云端存储;

TCP. IP协议

  1. 传输控制协议/因特网互联协议。是当前流行的网络传输协议框架。
  2. OSI模型:物理层. 数据链路层. 网络层. 传输层. 会话层. 表示层. 应用层;

影响RPC性能的因素如下:

  1. 序列化。常用的RPC序列化协议包括:Thrift. Protobuf. Avro. Kryo. MsgPack. Hessian. Jackson。
  2. 传输协议。常用的传输协议包括:HTTP. Socket. TCP. UDP等;
  3. 连接。连接包括:长连接. 短链接;
  4. IO模型。常用的网络IO模型:同步阻塞IO. 同步非阻塞IO. IO多路复用. 异步IO;
    注释:stub一般翻译成存根,即在本地存在一个和远程一样的方法;

Protobuf

protobuf是一个高性能. 易扩展的序列化框架,通常是rpc调用追求高性能的首选。结合Netty可以非常便捷的实现RPC调用。Protobuf比JSON. XML更快. 更轻. 更小
,并且可以跨平台。Protobuf首先要编写proto文件,即IDL文件,后缀为“.proto”的文件。然后通过客户端生成Java相关类进行序列化. 反序列化。

Rest

Rest是Representaitional State Transfer的缩写,通常翻译成“表现层状态转换”;

  1. 协议:API基于HTTP协议;
  2. 域名:需要一个域名例如:http://api.xx.com;
  3. 版本:需要版本信息,例如http://api.xx.com/v1/
  4. 路径:rest开发又被称作是面向资源的开发。例如http://api.xx.com/v1/user/
  5. 方法:一般包含的方法包括:get(获取资源,一个或者多个);post(创建资源);put(修改资源,客户端提供修改后的完整资源);
    patch(对已知资源进行全局更新,客户端需要提供改变的属性);delete(删除,回收资源);head(获取资源的元数据);option(读取对资源的访问权限);
    注释:SLA:需要提供相应时间. 吞吐量. 可用性等关键指标;

HTTP/2协议

HTTP/2对比HTTP/1.x进行了大量简化,使得性能大幅提升;HTTP/2是基于二进制协议的。
HTTP/1.x的语义只支持客户端发起请求,服务端响应数据。HTTP/2改变了这种模式,只需要客户端发送一次请求,服务端便把所有的资源都推送到客户端。

Cloud Native属性总结

分布式. 弹性. 多租户. 自服务. 按需计量和计费. 增量部署和测试;

gRPC

  1. gRPC默认使用Protobuf进行序列化和反序列化。
  2. gRPC默认采用HTTP/2进行传输。HTTP/2支持流(streaming),在批量发送数据的场景下使用流可以显著提升性能;
  3. gRPC流可以分为三类:客户端流式发送. 服务端流式返回. 客户端/服务端同时流式处理;
  4. gRPC并非完美,相比非IDL描述的RPC(Hession. Kyro)方式,定义proto文件是一个比较麻烦的事情。另外HTTP/2相比于基于TCP的通信协议,心梗也有显著的差异;

微服务框架

服务治理. 容量规划. 高效通信(需要微服务框架实现高效的序列化. 反序列化. 支持并行. 异步. 非阻塞转换以及多语言支持). 负载均衡(微服务框架需要支持常用
负载均衡. 故障转移. 支持自由的流量切换);

Dubbo

  1. Dubbot中的角色:Provider提供者,Consumer消费者. Registry注册中心. Montiro监控中心。
  2. 消费者启动后,会查询注册中心,注册中心返回提供者地址列表给消费者。如果有变更,注册中心将以长连接推送变更数据给消费者。

Etcd

Etcd是一个高可用的键值存储系统,主要用于共享配置和服务发现。它使用Go语言编写,并通过Raft一致性算法处理日志复制以保障强一致性。Etcd并不是强一致的。

微服务部署策略

服务独享数据库. 服务独享虚拟机/容器;

容器VS虚拟机

虚拟机是在硬件的基础上进行虚拟化,隔离性更高,而容器是在操作系统上进行的虚拟化。容器更像软件中的集装箱,能够把环境. 配置. 依赖. 软件等封装起来。
Docker可以让开发者大宝应用及依赖包到一个轻量级. 可移植的容器中,然后发布到任何安装了Docker的物理机或者虚拟机上,而不必担心是否安装了依赖项,
不必考虑编译器或其他任何需要支持的基础设施;

分布式消息中间件

通过分布式消息中间件解耦。系统之间可以进行可靠的异步通信,从而降低系统之间的耦合度,系统能够得到更好的扩展性和可用性。通过分布式消息中间件降低响应
时间。通过分布式消息中间件提升吞吐率。

Kafka的设计原理

在Kafka中,消息被持久化到磁盘,Kafka需要依赖于ZooKeeper管理元数据。
Borker:Kafka的服务端,负责接收数据,并持久化数据,Broker可以有多个,每隔Broker可以包含多个Topic,Broker并不保存Offset数据,由Consumer自己
负责保存,默认保存在Zookeeper中。
Producer:生产者,生成数据发送到Broker存储数据,Producer将会和Topic下所有Partition Leader保持连接。
Consumer:消费者,每个Partition只能被一个消费者订阅,一个消费者可以订阅多个Partition,消费者挂掉后会从新进行负载均衡。
Topic:主题,每个Topic包含多个Partition,所有的元数据都存储在ZooKeeper中。
Partition:分区,Kafka为了扩展性,可以将一个Topic拆分为多个分区,每个分区可以独立放到一个Broker上。

generic

概念:

  1. 泛型是参数化类型,使用广泛的类型。
  2. 起因:数据类型不明确,装入的类型被当做Object对待,从而”丢失”自己的实际类型。获取数据的时候往往需要转型,效率低,容易产生错误。
  3. 作用:3.1安全,在编译的时候检查类型安全。3.2省心,所有强制类型转换都是自动和隐式的,提高代码的重用率。

定义:

class 类名 <字母列表> {
修饰符 字母 属性
修饰符 构造器{
}
修饰符 返回类型 方法(){
}
}
泛型常见的字母列表:
T type 表示类型
K V 分别表示键中的key 和 value的值。
E 代表Element
? 代表不确定的类型

泛型使用需注意问题:

  1. 不能用在静态属性. 静态方法上
  2. 泛型使用不能指定基本类型
  3. 接口中泛型只能使用在接口方法中,不能使用在全局变量中

http

HTTP协议特点:

  1. 支持客户端和服务端
  2. 简单:客户向服务器发送请求时,只需要传送请求方法和路径。
  3. 灵活:HTTP允许传输任意类型的数据对象
  4. 无连接:无连接限制每次连接只处理一次请求。服务器处理完客户的请求后,并收到客户的应答后,立即断开连接,采用这种方式节省时间
  5. 无状态:HTTP协议时无状态的,对于事务处理没有记忆能力,缺少状态意味着如果后续处理需要前面的信息,则必须重传

Cookie和Session

  1. Cookie数据存放到客户端的浏览器上,sessions数据存放在服务器上。
  2. cookie不是很安全,别人可以分析本地的cookie并进行cookie欺骗。
  3. 单个cookie保存的数据不能超过4k,很多浏览器限制一个站点最多保存20个cookie
  4. Cookie 客户端需要每次将Cookie的值传送给服务端,如果Cookie的值很大,无形中增加了客户端与服务端数据传输的数量
  5. Session 同一个客户端和服务端交互,不需要每次都回传所有的cookie值,而是只需要传递一个JssionId

HTTP介绍

  1. 大多数传统的c/s互联网应用采用长连接方式
  2. http采用无状态的端连接,采用这种方式的目的是未来同时服务更多的用户。
  3. 互联网所有的资源都需要用一个URL标识,URL是统一资源定位符
  4. 发起一个HTTP请求就是建立一个Socket的过程。
  5. Http Header是控制着互联网用户数据的传输,控制着浏览器的渲染行为和服务器的执行逻辑。
  6. http请求头,包括accept-charset 指定客户端接受的字符集。Accept-Encoding 用于接受可接受的内容编码。Accept-Language,
    用于指定一种自然语言。

DNS

  1. 将域名解析成IP
  2. DNS解析过程
    2.1 检查缓存中是否有域名对应的IP,如果有直接使用,缓存大小,缓存时间都有限制。缓存时间太长一旦域名对应的IP有变化,这段时间会有部分用户无法访问。
    时间太短,每次访问都需要重新解析。
    2.2 如果浏览器没有缓存,操作系统也可以存储。windows在host中存储,linux在etc/host中存储。
  3. 几种域名解析方式
    3.1:A记录,A代表Address,用来指定域名对应的IP地址。A记录可以将多个域名解析到一个IP地址。但是不能将多个域名解析到多个IP地址。
    3.2:CNAME记录,全称(别名解析),所谓的别名解析就是将一个域名设置一个或者多个别名。

Http和Https的区别

  1. Https即Secure Hypertext Transfer Protocol,即安全超文本传输协议,它是一个安全通信信道,基于Http开发,用于在客户机和服务器间交换信息。它使用安全套接字层SSL进行信息交换,是Http的安全版。
  2. Https协议需要到CA申请证书,一般免费证书很少,需要交费。
  3. Http是超文本传输协议,信息是明文传输,https则是具有安全性的tls/ssl加密传输协议。
  4. http是80端口,https是443端口

浏览器输入一个URL的过程

  1. 浏览器向DNS服务器请求解析该URL中的域名所对应的IP地址
  2. 解析出IP地址后,根据IP地址和默认端口80和服务器建立TCP连接
  3. 浏览器发出Http请求,该请求报文作为TCP三次握手的第三个报文的数据发送给服务器
  4. 服务器做出响应,把对应的请求资源发送给浏览器
  5. 释放TCP连接
  6. 浏览器解析并显示内容

LinuxIO模型

  1. 阻塞IO模型 以socket为例,在进程空间调用recvfrom,其系统调用知道数据包到达且被复制到应用进程的缓冲区或者发生错误才返回,在此期间一直等待,进程从调用recvfrom开始到它返回的整段时间内都是被阻塞的,因此称为阻塞IO
  2. 非阻塞IO模型 应用进程调用recvfrom,如果缓冲区没有数据直接返回EWOULDBLOCK错误。一般对非阻塞IO进行轮询,以确定是否有数据到来。
  3. IO多路复用模型
    Linux提供select/poll,通过将一个或多个fd传递给select或poll系统调用,阻塞在select上。select/poll顺序扫描fd是否就绪。
  4. 信号驱动IO
    开启套接字接口信号驱动IO功能,并通过系统调用sigaction执行信号处理函数。当数据准备就绪时,为该进程生成SIGIO信号,通过信号回调通知应用程序调用recvfrom来读取数据,并通知主函数处理数据。
  5. 异步IO
    告知内核启动某个操作,并让内核在整个操作完成后通知我们。它与信号驱动IO的区别在于信号驱动IO由内核通知我们何时可以开始IO操作。而异步IO模型由内核通知我们IO操作已经完成。

instrumentation

Instrumentation

Instrumentation类为JVM上运行时的程序提供测量手段。很多工具通过Instrumentation修改方法字节码,实现数据收集的目的。
这些通过Instrumentation搜集数据的工具不会改变程序的状态和行为。这些良好的工作包括,monitoring agents, profilers, coverage analyzers, 和event loggers。

获取Instrumentation接口的方式

  1. 启动JVM时指定agent类,这种方式,instrumentation的实例通过agent class的premain方法被传入。
  2. JVM提供了一种当JVM启动完成后启动agent机制,这种情况下,Instrumentation通过agent代码中的agentmain方式传入。
    java agent 在JDK package specification中解释:一个agent 是被作为Jar 文件形式来部署的。在Jar文件中manifest中指定哪个类作为agent类。

Interview

导致服务器频繁fullGC的原因有那些?

限流算法

  1. 在高并发的系统中,缓存. 降级. 限流是三把利器。
  2. 限流就是限制流量,很好的控制系统的qps,从而达到保护系统的作用。
  3. 限流的方法
    1)计数器:计数器是最简单最容易实现的算法。设置一个计数器counter,没当一个请求过来的时候,counter加1,如果counter的值大于100,并且
    请求与第一个请求的间隔时间在1分钟以内,那么说明请求数过多。算法简单,最容易出现的是临界值问题。
    2)滑动窗口:滑动窗口为了解决临界值问题,很多个矩形框组成时间窗口,每个时间窗口是1分钟,将滑动窗口划分为6个格子,每个格代表10秒钟,没过
    10秒钟,格子向右滑动一格,每个各自有自己的counter。滑动窗口划分的格子越多,滑动窗口的滚动就越平滑,限流的统计就越精确。

滑动窗口指收发两端分别维护一个发送窗口和接收窗口,发送窗口有一个窗口值Wt,窗口值Wt代表在没有收到对方确认的情况下最多可以发送的帧的数目。
当发送的帧的序号被接收窗口正确收下后,接收端向前滑动并向发送端发去确认,发送端收到确认后,发送窗口向前滑动。收发两端按规律向前推进.
滑动窗口指接收和发送两端的窗口按规律不断向前推进,是一种流量控制的策略.

3)漏桶算法:有个固定容量的桶,有水流进来,也有水流出去,对于进来的水,无法估量有多少,水流的速度也无法估计,但是对于流出去的水来说,可以固定流出的频率。而且
当桶满后,多余的水会流出去。
我们将算法中的水换成实际应用中的请求,我们可以看到漏桶算法天生就限制了请求的速度。当使用了漏桶算法,我们可以保证接口会以一个常速速率来处理请求。所以漏桶算法天生不会出现临界问题。
4)令牌桶算法:令牌桶算法比漏桶算法稍显复杂。首先,我们有一个固定容量的桶,桶里存放着令牌(token)。桶一开始是空的,token以 一个固定的速率r往桶里填充,直到达到桶的容量,多余的令牌将会被丢弃。
每当一个请求过来时,就会尝试从桶里移除一个令牌,如果没有令牌的话,请求无法通过。
我们会发现我们默认从桶里移除令牌是不需要耗费时间的。如果给移除令牌设置一个延时时间,那么实际上又采用了漏桶算法的思路。Google的guava库下的SmoothWarmingUp类就采用了这个思路。

Java实现
我们可以使用Guava 的 RateLimiter 来实现基于令牌桶的流控,RateLimiter 令牌桶算法是单桶实现。RateLimiter 对简单的令牌桶算法做了一些工程上的优化,具体的实现是 SmoothBursty。
需要注意的是,RateLimiter 的另一个实现SmoothWarmingUp,就不是令牌桶了,而是漏桶算法。也许是出于简单起见,RateLimiter 中的时间窗口能且仅能为 1s。
SmoothBursty 有一个可以放 N 个时间窗口产生的令牌的桶,系统空闲的时候令牌就一直攒着,最好情况下可以扛 N 倍于限流值的高峰而不影响后续请求。
RateLimite允许某次请求拿走超出剩余令牌数的令牌,但是下一次请求将为此付出代价,一直等到令牌亏空补上,并且桶中有足够本次请求使用的令牌为止。
当某次请求不能得到所需要的令牌时,这时涉及到一个权衡,是让前一次请求干等到令牌够用才走掉呢,还是让它先走掉后面的请求等一等呢?Guava 的设计者选择的是后者,先把眼前的活干了,后面的事后面再说。

线程池排队策略

  1. 直接提交:工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。
    当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
  2. 无界队列:使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。
    (因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;
  3. 有界队列:有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:
    使用大型队列和小型池可以最大限度地降低 CPU 使用率. 操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。
    四种预定义的处理程序策略:
    在默认的 ThreadPoolExecutor.AbortPolicy 中,处理程序遭到拒绝将抛出运行时RejectedExecutionException。
    在 ThreadPoolExecutor.CallerRunsPolicy 中,线程调用运行该任务的execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
    在 ThreadPoolExecutor.DiscardPolicy 中,不能执行的任务将被删除。
    在 ThreadPoolExecutor.DiscardOldestPolicy 中,如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)。
  • 除了kv类型的数据存储外,redis适合的业务场景
  • 如果解决写操作跨库的数据一致性问题
  • spring aop如何实现,两种的区别,描述一下java动态代理的实现
  • 什么是乐观锁,乐观锁的机制是什么,
    乐观锁实现的机制就是CAS操作

线程通信

线程间通信和同步的方式主要有锁. 信号. 信号量
进程间的通信:通信机制主要有:管道. 有名管道. 消息队列. 信号量. 共享空间. 信号. 套接字(socket)
java线程通信 wait. notify. notifyAll. concurrent包lock下的condition里的await. signal. signalALl方法

io

字节流:

  1. 字节流可以处理一切(文本. 音频. 视频)
  2. 输入流 InputStream FileInputStream ByteArrayInputStream ObjectInputStream BufferedInputStream FilterInputStream DataInputStream
  3. 操作read字节数组
    中间容器 byte[] flush = new byte[长度],接收长度 int len = 0,循环读取while(-1!=(len=流.read(flush)))。输出拷贝
  4. 输出流 OutputStream FileOutputStream ByteArrayOutputStream ObjectOutputStream BufferedOutputStream DataOutputStream
  5. 操作write(字节数组,0,长度)

字符流

  1. 字符流,只能处理纯文本
  2. 输入流 Reader FileReader
  3. 操作read(字符数组)
  4. 中间容器 byte[] flush = new byte[长度],接收长度 int len = 0,循环读取while(-1!=(len=流.read(flush)))。输出拷贝
  5. 输出流:Writer FileWriter
  6. 操作write(字节数组,0,长度)

字节流和字符流转换

  1. 输入流 InputStreamReader 解码
  2. 输出流 OutputStreamWriter 编码

缓冲流

  1. 输入流:BufferedInputStream BufferedReader
  2. 输出流:BufferedOutputStream BufferedWriter

处理数据+类型

  1. 输入流:DataInputStream readXxx
  2. 输出流:DataOutputStream writeXxx
  3. 引用类型
    3.1 反序列化 ObjectInputStream readObject
    3.2 序列化 ObjectOutputStream wirteObject

打印流:PrintStream

IO流分类

  1. 基于字节操作的IO接口:InputStream. OutputStream
  2. 基于字符操作的IO接口:Reader. Writer
  3. 基于磁盘操作的IO接口:File
  4. 基于网络操作的IO接口:Socket

字节和字符转换

  1. InputStreamReader:从字节流转换成字符流,转换是需要指定编码格式,否则很容易出现乱码,StreamDecoder正是完成字节到字符的解码实现类。
  2. OutputStreamWriter:从字符流转换成字节流,由StreamEncoder完成编码过程。

磁盘IO工作机制:

9.1几种访问文件的方式:

  1. 标准访问文件方式:
    read接口,首先读取用户地址空间的缓存是否存在,如果存在直接返回,如果不存在,读取内核空间的高速页缓存,如果没有则读取磁盘空间。
    write接口,首先将数据从用户地址空间复制到内核地址空间的缓存中,这时对用户来说操作已经完成,至于什么时候写入磁盘由操作系统决定,也可以调用sync同步完成。
  2. 直接IO访问方式:就是应用程序直接访问磁盘,不经过操作系统内核数据缓存区,目的为了减少一次从内核缓冲区到用户缓冲区数据的复制。例如数据库管理系统。操作系统很难知道哪些是热点数据,操作系统
    只是简单的缓存最近一次从磁盘读取的数据。直接IO访问数据,如果不在应用程序缓存中,那么每次直接从磁盘加载,会非常慢。
    read接口:首先读取用户地址空间的缓存,如果有直接返回,没有则直接访问磁盘空间。
    write接口:首先写入应用地址空间的缓存,然后再写入磁盘空间。
  3. 同步访问文件方式:同步访问文件的方式就是数据的读取和写入都是同步操作的,与标准访问文件方式不同的是,只有当数据被成功写入到磁盘时,才返回给应用程序成功的标识。
    这种访问方式性能比较差,只有在一些对数据安全要求比较高的场景才使用。
  4. 异步访问文件方式:当访问数据的线程发出请求后,线程会继续处理其他事情,而不是阻塞等待,当请求的数据返回后继续处理下面的操作。这种访问方式明显提高应用程序的效率,但是不会改变访问文件的效率。
  5. 内存映射的方式:内存映射是操作系统将内存中的某一个区域与磁盘中的文件关联起来,当要访问内存中的一段数据时,转换成访问文件的某一段数据,这种方式的目的是同样减少数据从内核空间缓存到用户空间的数据
    复制操作,因为这两个空间的数据是共享的。
    9.2java访问磁盘文件:
    java中通常的File并不代表是一个真是存在的文件对象,当你指定一个路径描述符时,它就会返回一个代表这个路径的虚拟对象,可能是一个真是存在的文件或者是一个包含多个文件的目录。FileDescriptor对象,就是
    真正代表一个存在文件对象的描述。
    9.3 JAVA序列化:
  6. 概念:将一个对象转换成一串二进制表示的字节数组,通过保存或者转意这些字节数据来达到持久化的目的,需要持久化就必须实现java.io.Serializable接口。反序列化时,必须有原始类作为模板,才能将对象还原。
  7. java序列化的一些总结
    2.1:当父类序列化时,所有子类都可以被序列化。
    2.2:子类实现Serializable接口,父类没有,父类中的属性不会序列化(不报错,数据会丢失)
    2.3:如果序列化的属性是对象,则这个对象必须实现Serializable接口,否则会报错。
    2.4:在反序列化是,如果对象的属性有修改或者删除,则修改的部分属性会丢失,但是不会报错。
    2.5:在反序列化时,如果serialVersionUID被修改,那么反序列化时会失败。

影响网络传输的因素:

  1. 网络带宽:一条物理链路上在1s内能够传输的最大比特数。
  2. 传输距离:也就是数据在光纤中要走的距离,数据在光纤中移动并不是直线,所以有一个折射率,大概是光的2/3,这段时间也就是我们常说的网络延迟。
  3. TCP拥塞控制:

BIO. NIO. AIO

  1. IO的方式通常分为几种,同步阻塞的BIO. 同步非阻塞的NIO. 异步非阻塞的AIO
  2. 同步阻塞IO(JAVA BIO):
    同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,
    当然可以通过线程池机制改善。
  3. 同步非阻塞IO(Java NIO) :
    同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
  4. 异步阻塞IO(Java NIO):
    此种方式下是指应用发起一个IO操作以后,不等待内核IO操作的完成,等内核完成IO操作以后会通知应用程序,这其实就是同步和异步最关键的区别,同步必须等待或者主动的去询问IO是否完成,
    那么为什么说是阻塞的呢?因为此时是通过select系统调用来完成的,而select函数本身的实现方式是阻塞的,而采用select函数有个好处就是它可以同时监听多个文件句柄(如果从UNP的角度看,select属于同步操作。
    因为select之后,进程还需要读写数据),从而提高系统的并发性!
  5. Java AIO(NIO.2))异步非阻塞IO:
    在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,
    因为真正的IO读取或者写入操作已经由内核完成了。

BIO. NIO. AIO适用场景分析:

  1. BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
  2. NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
  3. AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

jvm

判断对象是否已死?

  1. 引用计数法:给对象添加一个引用计数器,当对象被引用,加1,引用失效,减1,计数器的值为0,那么可以回收。缺陷:当2个对象相互引用时,导致2个计数器的值都不为0。
  2. 可达性分析:Gc roots。Gc roots包括的对象有 1 虚拟机栈中引用的对象。方法区中静态属性引用的对象,方法区中常量方法引用的对象。本地方法栈中,引用的对象。
  3. 引用包括,强引用. 软引用. 弱引用. 虚引用。强引用:对象被引用不能被回收。软引用:用来描述一些还有用但是并非必须的对象。当系统将要发生内存溢出的时候,将会把这些对象进行第二次回收。弱引用:弱引用用来描述非必须对系那个,但是它的强度比软引用更弱一些,被弱引用的对象只能生存到下一次垃圾搜集发生之前。无论内存是否足够都会被回收。虚引用:虚引用也被称为幽灵引用或者幻影引用,它是最弱的一种引用关系。为对象设置一个虚引用唯一的目的就是能在这个对象被回收时收到一个系统通知。

java的类被回收的条件:

  1. 该类所有的实例都已经被回收,java堆中不存在该类的任何实例。
  2. 加载该类的classloader已经被回收。
  3. 该类对应的java.lang.class对象没有任何地方被引用。

基本回收算法

    1:引用计数:比较古老的回收算法。原理是此对象有一个引用,即增加一个技术,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象。此算法时致命的是无法处理循环引用的问题。
    2:标记-清除:此算法执行分为两个阶段。第一个阶段从引用根节点开始标记所有被引用的对象;第二个阶段便利整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。
    3:复制:此算法把内存空间划分为两个相同的区域,每次只是用其中的一个区域。垃圾回收时,便利当前使用区域,把正在使用中的对象复制到另外一个区域。此算法每次只处理正在使用中的对象,因此复制成本比较小,同事复制过去以后还能进行相应的内存整理,不过会出现碎片问题。当然,此算法的缺点也是很明显的,就是需要两倍的内存。
    4:标记-整理:此算法结合了“标记-清除”和”复制”两个算法的有点。也是分两个阶段,第一个阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象”压缩”到堆的其中一块,按顺序排放。此算法避免了”标记-清除”碎片问题,同时也避免了复制算法的空间问题。
    5:增量收集:实施垃圾回收算法,即应用进行的同时进行垃圾回收。
    6:分代算法:基于对象生命周期分析后得出的垃圾回收算法。把对象分为年轻代. 年老带. 持久代,对不同生命周期的对象使用不同的算法进行回收。

SavePoint安全点和安全区

  1. 程序执行时并非所有的地方都能停顿下来开始GC,只有到达安全点才能停顿。SafePoint的选定既不讷讷个太少导致让GC等待时间太长,也不能过于频繁已至过分增加运行的负荷。安全点的选定基本上是已”是否具有让程序长时间执行的特征”为标准进行选定的。
  2. 让所有的线程到达安全的方案有2中:抢先式终端,和主动式中断。抢先式中断:不需要线程的执行代码主动去配合,在GC发生时,首先把所有线程全部中断,如果发现有的线程不在安全点上,就恢复线程,让它跑到安全点。主动式中断:当GC需要中断时,仅仅简单地这只一个标识,各个线程执行时主动去轮训这个标志。发现终端标志为真时,就将自己挂起。
  3. 安全区:在一段代码片段中,引用关系不会发生变化,在这个区域的任意地方开始GC都是安全的。例如,有些时候是程序不执行的时候,当线程处于Sleep状态或者Blocked状态,无法响应jvm的中段请求。当线程进入安全区以后,会标识自己已经进入安全区,当jvm要发起gc时,就不用管标识自己为safe region状态的线程了。

垃圾收集器

  1. Serial收集器
    Serial收集器是一个单线程收集器,它在进行垃圾回收时,需要stop the world。
  2. ParNew收集器
    ParNew收集器是Serial的多线程版本。是Server模式下新生代默认的收集器,目前除了Serial收集器外只有它能与CMS收集器配合使用。
  3. Parallel Scavenge收集器
    是一款新生代收集器,它采用赋值算法,又是并行的多线程收集器。主要关注的是吞吐量。吞吐量=用户代码执行时间/用户代码执行时间+垃圾回收时间。Parallel Scavenge收集器提供了两个参数用于青雀控制吞吐量,分别是控制最大垃圾手机停顿时间和直接设置吞吐量大小的参数。
  4. Serial Old收集器
    Serial Old是Serial的老年代版本,同样是单线程收集器。主要作用是作为CMS收集器的备用方案。
  5. Parallel Old收集器
    Parallel Old收集器是 Parallel Scavenge的老年代版本,使用的是多线程和标记-整理算法。
  6. CMS收集器
    CMS:Concurrent Mark Sweep 收集器是一种以获取最短回收停顿时间为目的的收集器。CMS收集器是给予标记-清除算法实现的整个过程包括4个步骤:1初始标记2. 并发标记. 3. 重新标记. 4并发清除。CMS的有点是并发收集. 低停顿。
  7. G1收集器
    分代概念在G1中仍然得已保留。G1从整体来看是给予标记整理算法实现的收集器. 从局部上来看是给予复制算法实现的。G1在运作期间不会出现内存空间碎片,收集后能提供规整的可用内存。有利于程序长时间运行,分配大对象。
    可预测的停顿,这是G1与CMS的另一大优势。降低停顿时间是G1和CMS共同关注点。G1除了追求低停顿外,还能建立可预测的停顿时间模型。能够让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾回收的时间不能超过N秒。G1跟踪各个Region里面的垃圾堆积的价值大小,在后台维护一个优先级列表。根据每次允许的收集时间,优先回收价值最大的Resion。G1收集器运作的步骤包括:初始标记. 并发标记. 最终标记. 筛选回收。

JVM中并行与并发的区别

  1. 并行:指多条垃圾收集线程并行狗牯脑做,但此时用户线程仍然处于等待状态。
  2. 并发:指用户线程与垃圾搜集线程同事执行,单不一定是并行,可能是交替执行。

基本回收算法

    1:引用计数:比较古老的回收算法。原理是此对象有一个引用,即增加一个技术,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象。此算法时致命的是无法处理循环引用的问题。
    2:标记-清除:此算法执行分为两个阶段。第一个阶段从引用根节点开始标记所有被引用的对象;第二个阶段便利整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。
    3:复制:此算法把内存空间划分为两个相同的区域,每次只是用其中的一个区域。垃圾回收时,便利当前使用区域,把正在使用中的对象复制到另外一个区域。此算法每次只处理正在使用中的对象,因此复制成本比较小,同事复制过去以后还能进行相应的内存整理,不过会出现碎片问题。当然,此算法的缺点也是很明显的,就是需要两倍的内存。
    4:标记-整理:此算法结合了“标记-清除”和”复制”两个算法的有点。也是分两个阶段,第一个阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象”压缩”到堆的其中一块,按顺序排放。此算法避免了”标记-清除”碎片问题,同时也避免了复制算法的空间问题。
    5:增量收集:实施垃圾回收算法,即应用进行的同时进行垃圾回收。
    6:分代算法:基于对象生命周期分析后得出的垃圾回收算法。把对象分为年轻代. 年老带. 持久代,对不同生命周期的对象使用不同的算法进行回收。

基于分代算法

    1. 年轻代:年轻代分为三个区。一个Eden区,两个Survivor区。大部分对象都是在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象被复制到另外一个Survivor区,当这个Survivor区也满了时,会从第一个Survivor区复制过来的并且此时还存活的对象,将被复制到老年代。需要注意的是Survivor区是对称的,没有先后关系,所以同一个区中可能同时存在从Eden复制过来的对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor过来的对象。而且Survivor总有一个是空的。
    2. 年老代:年老代存放从年轻带存货的对象。一般来说年老代都是存放生命周期较长的对象。
    3. 持久代:用于存放静态文件,例如java类. 方法等。持久代对垃圾回收没有显著的影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程总新增的类。持久代大小通过-XX:MAXPermSize进行设置。

java运行时数据区域:

  1. 程序计数器:当前线程所执行的字节码的行号指示器。
  2. 虚拟机栈:是线程私有的,它的生命周期和线程相同,每个方法在执行的时候会创建一个栈帧,用于存储变量表. 操作数栈. 方法出口. 动态连接等。
  3. 本地方法区:本地方法栈是为虚拟机使用到的native方法服务。
  4. 堆:是java虚拟机所管理的内存中最大的一块。java堆是所有线程共享的一块内存区域,目的是为了存放对象实例。
  5. 方法区:方法区也是各个线程共享的一块区域,用于存储虚拟机加载的类信息. 常量. 静态变量. 即时编译后的代码等。

kafka

  • Kafka是一个高性能. 高可用. 可持久化的,为分布式设计的消息中间件。Kafka主要包括,consumer. broker. producer。
  • 每一种消息的分类叫做topic,一个消息中间可以包括多个topic。为了分布式设计,每个主题又可以分成多个分区。每个分区都是一个顺序写入
    ,且不可变的文件。每个新的消息总是追加到文件末尾。每个主题可以多个分区,每个分区又可以分布到不同的机器上,所以实现了分布式系统的功能。
    Kafka通过zk实现注册中心,将消息生产者,消费者,消息中介元数据存储到zk上,kafka也会将分区分配到不同机器。
  • 对于消息发送到哪个分区,默认是轮训调度的方式,也可以根据自己的业务场景,进行定制实现消息分区的逻辑。
  • 每个分区的消费都是顺序的,但是跨分区的消息不保证顺序性。kafka的消费者提供了GroupId的功能,同时只能拥有同样的GroupId的消费者,
    消费一个分区。消息中间件分为queue和topic两种模式,如果每个消费者拥有相同的groupId,那么这个主题就被当做为queue方式消费,因为不会
    有消费者消费相同的消息。如果每个消费者拥有不同的GroupId,那么这个主题会被当做Topic模式消费,每个消费者都会消费一遍这个消息。
  • 消息的消费者使用标准的推送push模型,将消息推送到消息中介,但是消费者不同,消费者使用拉取pull模型,主动的将消息从消息中介拉取
    到客户端,这样消息中介不需要维护消息的状态。为了提升吞吐量可以批量拉取。
  • kafka的消息中介不会存储消息状态,而是把消费到哪里的偏移量存储到客户端,并且同步到zk,可以选择批量同步,这样可以提升吞吐量,风险
    是如果消费者崩溃,未同步的消息偏移量将导致重复消费。系统提供了3种可能消费的传递保障方式,至多一次,至少一次,仅仅一次。至多一次,无论
    消费者是否成功,消息不会在重新推送。至少一次,消息被消费时,如果由于网络原因导致重复消费,需要业务实现幂等性,无论消费多少次都是相同
    的结果。仅仅一次,如果消息已经被消费,再次消费消息的时候会回滚。Kafka由于设计的理念不同,只支持至少一次。
  • kafka的存储方式,kafka直接使用磁盘进行存储,没有使用缓存,由于操作系统本身就有缓存,先将数据放入缓存,经过一定时间再刷盘。
    Kafka最大限度的利用了操作系统的缓存,所以如果想提升kafka的性能,最好使用固态硬盘。Kafka是追加数据顺序读,不会随机读写,所以及时
    直接使用磁盘效率也很高。如果不想丢弃消息,可以修改kafka的配置参数,每发送一条消息就同步一次磁盘,用降低性能的方式提高安全性。kafka
    会自动清理过期的消息,默认保留1个星期。
  • kafka收到消息后会向主节点和从节点发送消息。主节点用于接收和消费消息,从节点用于同步消息,kafka的主从节点不是以服务器为粒度,而
    是以topic主体为粒度,这样每台机器都会有主从分区,最大限度的利用了机器资源。

lock

  1. Lock能完成几乎所有synchronized的功能,并有一些后者不具备的功能,如锁投票. 定时锁等候. 可中断锁等候等。
  2. synchronized 是Java 语言层面的,是内置的关键字;Lock 则是JDK 5中出现的一个包,在使用时,synchronized 同步的代码块可以由JVM自动释放;
    Lock 需要程序员在finally块中手工释放,如果不释放,可能会引起难以预料的后果

主内存和工作内存:

  1. Java内存模型中规定了所有的变量都存储在主内存中,每个线程还有自己的工作内存,线程的工作内存,保存了该线程使用到的主内存副本拷贝,线程对变量的所有操作必须在工作内存中,而不能直接读写主内存中的变量,
    不同线程之间无法直接访问对方工作内存中的变量,线程间值的传递均需要通过主内存来完成。

什么是自旋锁:

自旋锁jdk1.6后默认自动开启,基于之前的观察,共享数据的锁定状态只会持续很短的时间,为了这一段时间而去挂起和恢复线程有点浪费,然后面请求锁的那个线程稍等一会,但是不放弃处理器的执行时间,看看只有线程的锁
是否能很快释放。为了让线程等待,所以需要让线程执行一个忙循环自旋操作。

锁消除

虚拟机即使编译器在运行时,对于代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。如果判断一段代码,在堆上的数据都不会逃逸,被其他线程访问到,那么认为是线程私有的,同步加锁也就没有必要了。

锁粗化

原则上,在编写代码时,推荐将同步块的作用范围限制的尽量小,仅仅在共享数据的实际作用域才进行同步,这样目的是使得需要同步的操作尽可能小,如果存在锁竞争,那等待锁的线程也能尽快拿到锁。但是如果一系列的连续
操作都对同一个对象反复加锁和解锁,甚至锁出现在循环体内,及时没有线程竞争,频繁的进行互斥操作也会带来性能损耗。

偏向锁

偏向锁意思是这个锁会偏向第一个获取它的线程,如果在接下来的过程中,该锁没有被其他线程获取,则只有偏向锁的线程将永远不需要进行同步,偏向锁可以提高带有同步但是没有竞争的程序的性能。偏向锁也不一定总是对程序
有利的,如果程序中的锁大部分都是被多个不同的线程访问,那么偏向模式就是多余的。

轻量级锁

轻量级锁能提升程序同步性能的依据是”对于绝大部分的锁,在整个同步周期内都是不存在竞争”,这是一个经验值。如果没有竞争,轻量级锁使用CAS操作避免了使用互斥所的开销,但如果存在竞争,除了互斥锁的开销外,还额外
发生了CAS操作,一次在竞争的情况下,轻量级锁会比传统重量级锁更慢。

volatile变量

volatile能够实现可见性,但是不能保证原子性。

CAS

CAS包括三个操作数,内存位置,预期旧值,新值。如果内存位置的值和预期的旧值相同,那么使用新值替换旧值。

如何实现互斥同步?

java中最基本的互斥就是synchronized关键字,synchronized在经过编译后,会在同步块的前后分别形成monitorenter和moitorexit这两个字节码指令。在执行monitorenter指令时,首先要去尝试获取对象的锁,
如果这个对象没有被锁定,或者当前线程已经拥有了那个对象的锁,把锁的计数器加1,相应的,在执行monitorexit指令时会把锁计数器减1,当计数器为0时,锁就被释放了。如果获取对象的锁失败,当当前线程就要阻塞等待,
知道对象的锁被另一个线程释放为止。synchronized对于同一个线程来说是可重入的,不会出现自己把自己锁死的问题。除了synchronized指望,JUC中的Lock也能实现互斥同步,ReentrantLock,写法上更加可见,
lock和unlock配合try/finally来配合完成,ReentrantLock比synchronized有几个高级的特性。

ReentrantLock的高级特性有那几个?

  1. 等待可中断,当持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,改为处理其他事情;
  2. 可以实现公平锁,公平锁指多个线程在等待同一个锁时,必须按照申请锁的顺序依次获得锁,synchronized是非公平锁,ReentrantLock默认也是非公平的,只不过可以通过构造函数来制定实现公平锁;
  3. 锁绑定多个条件,ReentrantLock对象可以同时绑定多个Condition对象,在synchronized中,锁对象的wait/notify/notifyall方法可以实现一个隐含的条件,如果要多一个条件关联的时候,就需要额外的增加一个锁;

关于sunchronized的几个注意点?

  1. 当一个线程访问object的一个synchronized(this)同步代码块时, 另一个线程仍然可以访问该object中的非synchronized(this)同步代码块;
  2. 当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时, 一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块;
  3. 尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时, 其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞;
  4. Java中的每一个对象都可以作为锁,对于同步方法,锁是当前实例对象,对于静态同步方法,锁是当前对象的Class对象,对于同步方法块,锁是Synchonized括号里配置的对象;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
## JAVA concurrent包下的类:
Executors
Executor
ExecutorService
ScheduledExecutorService
Callable
Future
ScheduledFuture
Delayed
CompletionService
ThreadPoolExecutor
ScheduledThreadPoolExecutor
AbstractExecutorService
Executors
FutureTask
ExecutorCompletionService
Queues
BlockingQueue
ConcurrentLinkedQueue
LinkedBlockingQueue
ArrayBlockingQueue
SynchronousQueue
PriorityBlockingQueue
DelayQueue
Concurrent Collections
ConcurrentMap
ConcurrentHashMap
CopyOnWriteArray{List,Set
Synchronizers
CountDownLatch
Semaphore
Exchanger
CyclicBarrier
Timing
TimeUnit
Locks
Lock
Condition
ReadWriteLock
AbstractQueuedSynchronizer
LockSupport
ReentrantLock
ReentrantReadWriteLock
Atomics
Atomic[Type], Atomic[Type]Array
Atomic[Type], FieldUpdater
Atomic{Markable, Stampable}Reference}

原子性. 可见性. 有序性

  1. 原子性:由java内存模型来直接保证原子性变量操作包括:lock. unlock. read. load. assign. use. store和write 。
  2. 可见性:当一个线程修改了共享变量的值,其他线程能够立即知道这个修改。java内存模型是通过在变量修改后将新值同步回
    主内存,在变量读取之前需要从主内存刷新变量值这种依赖猪内存作为传递媒介的方式来实现的可见性。除了volatitle关键字外,
    还有2个synchronized. final。
  3. 有序性:java提供了volatitle和synchronized关键字来保证线程之间操作的有序性。

mongodb

MongoDB简介

  1. MongoDB是一个开源的. 面向文档的存储的NoSql数据库。
  2. 模式自由. 支持动态查询. 完全索引. 可以轻易查询文档中内嵌的对象及数组。
  3. 面向文档存储,易存储对象类型的数据。
  4. 高效的文档存储,支持二进制数据及大型对象。
  5. 支持复制和故障恢复
  6. 自动分片支持云级别的伸缩性,支持水平的数据库集群。

特点:

高性能. 易部署. 易使用,存储数据非常方便。主要功能特性有:
面向集合存储,易存储对象类型的数据。
模式自由。
支持动态查询。
支持完全索引,包含内部对象。
支持查询。
支持复制和故障恢复。
使用高效的二进制数据存储,包括大型对象(如视频等)。
自动处理碎片,以支持云计算层次的扩展性
支持Python,PHP,Ruby,Java,C,C#,Javascript,Perl及C++语言的驱动程序,社区中也提供了对Erlang及.NET等平台的驱动程序。
文件存储格式为BSON(一种JSON的扩展)。
可通过网络访问。

功能:

面向集合的存储:适合存储对象及JSON形式的数据。
动态查询:Mongo支持丰富的查询表达式。查询指令使用JSON形式的标记,可轻易查询文档中内嵌的对象及数组。
完整的索引支持:包括文档内嵌对象及数组。Mongo的查询优化器会分析查询表达式,并生成一个高效的查询计划。
查询监视:Mongo包含一个监视工具用于分析数据库操作的性能。
复制及自动故障转移:Mongo数据库支持服务器之间的数据复制,支持主-从模式及服务器之间的相互复制。复制的主要目标是提供冗余及自动故障转移。
高效的传统存储方式:支持二进制数据及大型对象(如照片或图片)
自动分片以支持云级别的伸缩性:自动分片功能支持水平的数据库集群,可动态添加额外的机器。

适用场合:

网站数据:Mongo非常适合实时的插入,更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性。
缓存:由于性能很高,Mongo也适合作为信息基础设施的缓存层。在系统重启之后,由Mongo搭建的持久化缓存层可以避免下层的数据源 过载。
大尺寸,低价值的数据:使用传统的关系型数据库存储一些数据时可能会比较昂贵,在此之前,很多时候程序员往往会选择传统的文件进行存储。
高伸缩性的场景:Mongo非常适合由数十或数百台服务器组成的数据库。Mongo的路线图中已经包含对MapReduce引擎的内置支持。
用于对象及JSON数据的存储:Mongo的BSON数据格式非常适合文档化格式的存储及查询。

与传统数据库对比

MongoDb是由数据库databse. 集合collection. 文档对象document三个层次组成。

索引

MongoDB提供了多样性的索引支持,索引信息被保存在sytem.indexes中,且默认为_id创建索引,它的索引使用基本和mysql一样。

mysql

Mysql主要存储引擎包括InnoDB. MyISAM

InnoDB和MyISAM的区别:

  1. MyISAM不支持事物,表锁,支持全文检索,不支持外键,表空间相对比较小,关注的主要是查询的性能。
  2. InnoDB支持事物,行锁,不支持全文检索,支持外检,表空间相对比较大,关注的是事务。
  3. MyISAM的读性能比InnoDB强。

数值类型:

  1. tinyint 1个字节
  2. smallint 2个字节
  3. mediumint 3个字节
  4. int 4个字节
  5. bigint 8个字节
  6. float 4个字节
  7. double 8个字节
  8. bit 1-8个字节

日期类型

  1. 如果表示年月日用date
  2. 如果标示年月日时分秒,用datetime和timestamp
  3. 如果表示十分秒,用time
  4. 如果表示年,用year

datetime和timestamp的区别:

  1. timestamp支持的时间范围比较小,从1970年到2038年的某个时间。
  2. timestamp的查询和插入受当地时区的影响。

char. varchar. binary. varbinary. blob. text. enum和set

  1. char的固定长度是0-255,且会去掉末尾的空格
  2. varchar是非固定的,会保留末尾的空格

数据库隔离级别

  1. 数据库隔离级别包括:读未提交. 读提交. 重复读. 序列化。
  2. 数据库各种隔离级别出现的问题:脏读. 不可重复度. 幻读
  3. 读未提交:脏读. 不可重复读. 幻读
  4. 读提交:不可重复读. 幻读
  5. 重复度:幻读
  6. 序列化:不会出现问题

各种隔离级别出现的问题

  1. 脏读:一个事务读取了另外一个事务未提交的数据,而这个数据有可能回滚。
  2. 不可重复读:在访问数据库时,一个事务范围内的两次查询,返回了不同的结果。这是由于查询时系统中其他事务修改的提交而引起的。
  3. 幻读:是指事务不是独立执行时发生的一种现象。,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样.
  • 小数类型为 decimal,禁止使用 float 和 double。
    说明:float 和 double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不 正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。
  • 如果存储的字符串长度几乎相等,使用 char 定长字符串类型。
    varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长 度大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索 引效率。
  • 字段允许适当冗余,以提高性能,但是必须考虑数据同步的情况。冗余字段应遵循:
    1)不是频繁修改的字段。
    2)不是 varchar 超长字段,更不能是 text 字段。
  • 单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。 说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。
  • 合适的字符存储长度,不但节约数据库表空间. 节约索引存储,更重要的是提升检 索速度。
  • 业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。
    说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明 显的;另外,即使在应用层做了非常完善的校验和控制,只要没有唯一索引,根据墨菲定律, 必然有脏数据产生。
  • 超过三个表禁止 join。需要 join 的字段,数据类型保持绝对一致;多表关联查询 时,保证被关联的字段需要有索引。
    说明:即使双表 join 也要注意表索引. SQL 性能。
  • 在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据 实际文本区分度决定索引长度。
    说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分 度会高达 90%以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度 来确定。
  • 页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。 说明:索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索 引。
  • 如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合 索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。
    正例:where a=? and b=? order by c; 索引:a_b_c 反例:索引中有范围查找,那么索引有序性无法利用,如:WHERE a>10 ORDER BY b; 索引 a_b 无法排序。
  • 利用覆盖索引来进行查询操作,来避免回表操作。
    说明:如果一本书需要知道第 11 章是什么标题,会翻开第 11 章对应的那一页吗?目录浏览 一下就好,这个目录就是起到覆盖索引的作用。
    正例:能够建立索引的种类:主键索引. 唯一索引. 普通索引,而覆盖索引是一种查询的一种 效果,用explain的结果,extra列会出现:using index
  • 利用延迟关联或者子查询优化超多分页场景。
    说明:MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过 特定阈值的页数进行 SQL 改写。
    正例:先快速定位需要获取的 id 段,然后再关联:
    SELECT a.* FROM 表 1 a, (select id from 表 1 where 条件 LIMIT 100000,20 ) b where a.id=b.id
  • SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好。
    说明:
    1)consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。 2)ref 指的是使用普通的索引(normal index)。
    3)range 对索引进行范围检索。
    反例:explain 表的结果,type=index,索引物理文件全扫描,速度非常慢,这个 index 级 别比较 range 还低,与全表扫描是小巫见大巫。
  • 建组合索引的时候,区分度最高的在最左边。
    正例:如果 where a=? and b=? ,a 列的几乎接近于唯一值,那么只需要单建 idx_a 索引即 可。
    说明:存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。如:where a>? and b=? 那么即使 a 的区分度更高,也必须把 b 放在索引的最前列。
  • 创建索引时避免有如下极端误解:
    1)误认为一个查询就需要建一个索引。 2)误认为索引会消耗空间. 严重拖慢更新和新增速度。 3)误认为唯一索引一律需要在应用层通过“先查后插”方式解决。
  • count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行
  • 当某一列的值全是 NULL 时,count(col)的返回结果为 0,但 sum(col)的返回结果为 NULL,因此使用 sum()时需注意 NPE 问题
    正例:可以使用如下方式来避免sum的NPE问题:SELECT IF(ISNULL(SUM(g)),0,SUM(g)) FROM table;
  • 使用 ISNULL()来判断是否为 NULL 值。注意:NULL 与任何值的直接比较都为 NULL。 说明:
  1. NULL<>NULL的返回结果是NULL,而不是false。 2) NULL=NULL的返回结果是NULL,而不是true。 3) NULL<>1的返回结果是NULL,而不是true。

netty

为什么使用Netty?

  1. Netty提供了这样的一个间接的解决方法。Netty提供了高层次的抽象来简化TCP和UDP服务器的编程,但是你仍然可以使用底层地API。
  2. Netty成功的提供了易于开发,高性能和高稳定性,以及较强的扩展性

Design(设计)

各种传输类型,阻塞和非阻塞套接字统一的API
使用灵活
简单但功能强大的线程模型
无连接的DatagramSocket支持
链逻辑,易于重用

Ease of Use(易于使 用)

提供大量的文档和例子
除了依赖jdk1.6+,没有额外的依赖关系。某些功能依赖jdk1.7+,其他特性可能有相 关依赖,但都是可选的。

Performance(性能)

比Java APIS更好的吞吐量和更低的延迟
因为线程池和重用所有消耗较少的资源
尽量减少不必要的内存拷贝

Robustness(鲁棒性)

链接快或慢或超载不会导致更多的OutOfMemoryError
在高速的网络程序中不会有不公平的read/write

Security(安全性)

完整的SSL/TLS和StartTLS支持
可以在如Applet或OSGI这些受限制的环境中运行

Community(社区)

版本发布频繁
社区活跃

network

Netty 解决TCP粘包/拆包问题

业务上一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。
对于这类问题,netty提供了LineBasedFrameDecoder和StringDecoder进行完美解决。如下是相关代码。

  1. LineBasedFrameDecoder
    工作原理是依次遍历ByteBuf中的可读字节,判断是否有换行符,如果有,就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。
  2. StringDecoder
    功能就是将接受到的对象转换为字符串,然后继续调用后面的handler。
    LineBasedFrameDecoder+StringDecoder组合就是按行切换的文本解码器。
  3. 其他解码器
    DelimiterBasedFrameDecoder可以自定义分隔符
    FixedLengthFrameDecoder定长解码器。

nginx

Nginx能做什么?

  1. 反向代理
  2. 负载均衡
  3. HTTP服务器(包含动静分离)
  4. 正向代理

反向代理

反向代理是指代理服务器接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器得到的结果返回给internet上请求连接的客户端,此时代理服务器
对外表现为一个反向代理服务器。简单的说就是真是的服务器不能直接被外部网络访问,需要一台代理服务器,而代理服务器能被外部网络访问的同时又跟真是服务器在同一个网络环境。

1
2
3
4
5
6
7
8
9
10
server {  
listen 80;
server_name localhost;
client_max_body_size 1024M;

location / {
proxy_pass http://localhost:8080;
proxy_set_header Host :;

}}

保存配置文件后启动Nginx,这样当我们访问localhost的时候,就相当于访问localhost:8080了

负载均衡

  1. RR 默认
    每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
upstream test {
server localhost:8080;
server localhost:8081;

server {
listen 81;
server_name localhost;
client_max_body_size 1024M;

location / {
proxy_pass http://test;
proxy_set_header Host :;
}
}}

负载均衡的核心代码为

1
2
3
4
upstream test {
server localhost:8080;
server localhost:8081;
}

这里我配置了2台服务器,当然实际上是一台,只是端口不一样而已,而8081的服务器是不存在的,也就是说访问不到,但是我们访问http://localhost 的时候,也不会有问题,会默认跳转到http://localhost:8080
具体是因为Nginx会自动判断服务器的状态,如果服务器处于不能访问(服务器挂了),就不会跳转到这台服务器,所以也避免了一台服务器挂了影响使用的情况,由于Nginx默认是RR策略,所以我们不需要其他更多的设置。

  1. 权重
    指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。 例如
1
2
3
4
5

upstream test {
server localhost:8080 weight=9;
server localhost:8081 weight=1;
}

那么10次一般只会有1次会访问到8081,而有9次会访问到8080

  1. ip_hash
    上面的2种方式都有一个问题,那就是下一个请求来的时候请求可能分发到另外一个服务器,当我们的程序不是无状态的时候(采用了session保存数据),这时候就有一个很大的很问题了,比如把登录信息保存到了session中,那么跳转到另外一台服务器的时候就需要重新登录了,所以很多时候我们需要一个客户只访问一个服务器,那么就需要用iphash了,iphash的每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。
1
2
3
4
5
upstream test {
ip_hash;
server localhost:8080;
server localhost:8081;
}
  1. fair(第三方)
    按后端服务器的响应时间来分配请求,响应时间短的优先分配。
1
2
3
4
5
upstream backend {
fair;
server localhost:8080;
server localhost:8081;
}
  1. url_hash(第三方)
    按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。 在upstream中加入hash语句,server语句中不能写入weight等其他的参数,hash_method是使用的hash算法
1
2
3
4
5
6
7

upstream backend {
hash ;
hash_method crc32;
server localhost:8080;
server localhost:8081;
}

以上5种负载均衡各自适用不同情况下使用,所以可以根据实际情况选择使用哪种策略模式,不过fair和url_hash需要安装第三方模块才能使用,由于本文主要介绍Nginx能做的事情,所以Nginx安装第三方模块不会再本文介绍

HTTP服务器

Nginx本身也是一个静态资源的服务器,当只有静态资源的时候,就可以使用Nginx来做服务器,同时现在也很流行动静分离,就可以通过Nginx来实现,
首先看看Nginx做静态资源服务器

1
2
3
4
5
6
7
8
9
10
11
server {
listen 80;
server_name localhost;
client_max_body_size 1024M;


location / {
root e:wwwroot;
index index.html;

}}

这样如果访问http://localhost 就会默认访问到E盘wwwroot目录下面的index.html,如果一个网站只是静态页面的话,那么就可以通过这种方式来实现部署。

动静分离

动静分离是让动态网站里的动态网页根据一定规则把不变的资源和经常变的资源区分开来,动静资源做好了拆分以后,我们就可以根据静态资源的特点将其做缓存操作,这就是网站静态化处理的核心思路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
upstream test{  
server localhost:8080;
server localhost:8081;


server {
listen 80;
server_name localhost;

location / {
root e:wwwroot;
index index.html;
}

# 所有静态请求都由nginx处理,存放目录为html
location ~ .(gif|jpg|jpeg|png|bmp|swf|css|js)$ {
root e:wwwroot;
}

# 所有动态请求都转发给tomcat处理
location ~ .(jsp|do)$ {
proxy_pass http://test;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root e:wwwroot;
}
} }

这样我们就可以吧HTML以及图片和css以及js放到wwwroot目录下,而tomcat只负责处理jsp和请求,例如当我们后缀为gif的时候,
Nginx默认会从wwwroot获取到当前请求的动态图文件返回,当然这里的静态文件跟Nginx是同一台服务器,我们也可以在另外一台服务器,然后通过反向代理和负载均衡配置过去就好了,只要搞清楚了最基本的流程,很多配置就很简单了,另外localtion后面其实是一个正则表达式,所以非常灵活

正向代理

正向代理,意思是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。当你需要把你的服务器作为代理服务器的时候,可以用Nginx来实现正向代理,但是目前Nginx有一个问题,那么就是不支持HTTPS,虽然我百度到过配置HTTPS的正向代理,但是到最后发现还是代理不了,当然可能是我配置的不对,所以也希望有知道正确方法的同志们留言说明一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
resolver 114.114.114.114 8.8.8.8;
server {

resolver_timeout 5s;

listen 81;

access_log e:wwwrootproxy.access.log;
error_log e:wwwrootproxy.error.log;

location / {
proxy_pass http://;

}}

resolver是配置正向代理的DNS服务器,listen 是正向代理的端口,配置好了就可以在ie上面或者其他代理插件上面使用服务器ip+端口号进行代理了。

Nginx热部署

Nginx是支持热启动的,也就是说当我们修改配置文件后,不用关闭Nginx,就可以实现让配置生效,当然我并不知道多少人知道这个,反正我一开始并不知道,导致经常杀死了Nginx线程再来启动。。。Nginx从新读取配置的命令是
nginx -s reload
windows下面就是
nginx.exe -s reload

redis

redis数据类型

  1. string. list. set. zset. hash
  2. String是最简单的类型,一个key对应一个value
  3. list是一个链表,主要包括push. pop,获取一定范围内的所有值,操作中key为链表的名称
  4. set是集合,对集合的操作包括,新增. 删除. 合并求交集等,操作中的key为集合的名称。
  5. zSet是set的一个升级版本,在set的基础上增加了顺序的功能,这个属性在添加删除的时候可以指定,每次指定后,zSet会自动重新按照
    新的值调整顺序。
  6. Hash数据类型允许用户使用redis对象类型,当你存储的数据对象只有很少几个key值时,数据存储的消耗会很小。

Redis的持久化方式:

  1. RDB方式:默认redis会以快照的形式存储数据持久化到硬盘中,在配置文件中的格式是 save N M,标识在N秒的时间内,redis至少发生
    M次修改,则将redis抓去快照到磁盘。工作原理:当redis需要做持久化时,只需要fork一个子进程,子进程将数据写入到磁盘上的一个临时
    文件RDB中,当子进程完成后,将原来的RDB文件替换掉,这样的好处是copy-on-write。
  2. AOF方式:append only file,文件日志追加,当开启后,redis每执行一次修改数据的命令后,都会把它添加到aof文件中,当redis
    重启时,进行”重放”,以恢复redis关闭前的最后时刻。AOF的三中方式:每提交一个修改命令都刷新到aof中,非常慢,但是很安全。每秒中
    刷盘一次,很快,但是可能会丢失一秒中的数据。第三种是依赖操作系统的缓存进行刷新,最快,但是安全性最差。

Redis各种特征的试用场景

  1. Strings
    Strings 数据结构是简单的key-value类型,value其实不仅是String,也可以是数字。
    setnx 设置可以为对应的值为String类型的value,如果key存在返回0不覆盖,不存在返回1
    常规key-value缓存应用。
    常规计数: 微博数, 粉丝数
  2. Hashs
    在Memcached中,我们经常将一些结构化的信息打包成hashmap,在客户端序列化后存储为一个字符串的值,比如用户的昵称. 年龄. 性别. 积分等,这时候在需要修改其中某一项时,
    通常需要将所有值取出反序列化后,修改某一项的值,再序列化存储回去。这样不仅增大了开销,也不适用于一些可能并发操作的场合(比如两个并发的操作都需要修改积分)。
    而Redis的Hash结构可以使你像在数据库中Update一个属性一样只修改某一项属性值。
    它是一个String类型的field和value的映射表,它的添加和删除都是平均的,hash特别适合用于存储对象,对于将对象存储成字符串而言,hash会占用更少的内存,
    并且可以更方便的存取整个对象. 它和java的HashMap完全类似
    使用场景
    存储部分变更数据
    如用户信息等。
  3. Lists
    Lists 就是链表,略有数据结构知识的人都应该能理解其结构。使用Lists结构,我们可以轻松地实现最新消息排行等功能。Lists的另一个应用就是消息队列,可以利用Lists的PUSH操作,将任务存在Lists中,
    然后工作线程再用POP操作将任务取出进行执行。Redis还提供了操作Lists中某一段的api,你可以直接查询,删除Lists中某一段的元素。
    Redis的list是每个子元素都是String类型的双向链表,可以通过push和pop操作从列表的头部或者尾部添加或者删除元素,这样List即可以作为栈,也可以作为队列。
    消息队列系统
    使用list可以构建队列系统,使用sorted set甚至可以构建有优先级的队列系统。
    比如:将Redis用作日志收集器
    实际上还是一个队列,多个端点将日志信息写入Redis,然后一个worker统一将所有日志写到磁盘。
    取最新N个数据的操作
    记录前N个最新登陆的用户Id列表,超出的范围可以从数据库中获得。
    //把当前登录人添加到链表里
    ret = r.lpush(“login:last_login_times”, uid)
    //保持链表只有N位
    ret = redis.ltrim(“login:last_login_times”, 0, N-1)
    //获得前N个最新登陆的用户Id列表
    last_login_list = r.lrange(“login:last_login_times”, 0, N-1)
    比如sina微博:
    在Redis中我们的最新微博ID使用了常驻缓存,这是一直更新的。但是我们做了限制不能超过5000个ID,因此我们的获取ID函数会一直询问Redis。只有在start/count参数超出了这个范围的时候,才需要去访问数据库。
    我们的系统不会像传统方式那样“刷新”缓存,Redis实例中的信息永远是一致的。SQL数据库(或是硬盘上的其他类型数据库)只是在用户需要获取“很远”的数据时才会被触发,而主页或第一个评论页是不会麻烦到硬盘上的数据库了。
    Sets
    Sets 就是一个集合,集合的概念就是一堆不重复值的组合。利用Redis提供的Sets数据结构,可以存储一些集合性的数据。
    案例:
    在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为集合提供了求交集. 并集. 差集等操作,可以非常方便的实现如共同关注. 共同喜好. 二度好友等功能,对上面的所有集合操作,
    你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。
    Set是集合,是String类型的无序集合,set是通过hashtable实现的,概念和数学中个的集合基本类似,可以交集,并集,差集等等,set中的元素是没有顺序的。

Sorted Sets
和Sets相比,Sorted Sets增加了一个权重参数score,使得集合中的元素能够按score进行有序排列,比如一个存储全班同学成绩的Sorted Sets,其集合value可以是同学的学号,而score就可以是其考试得分,
这样在数据插入集合的时候,就已经进行了天然的排序。
可以用Sorted Sets来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。
比如在线游戏的排行榜,根据得分你通常想要:

  • 列出前100名高分选手
  • 列出某用户当前的全球排名

seckill

典型的秒杀系统:

  1. 典型的秒杀系统由接入层. 逻辑服务层. 存储层与缓存构成。Proxy处理请求接入,Server承载主要的业务逻辑,Cache用于缓存库存数量. DB则用于数据持久化。
  2. 一个秒杀系统对应DB中一条库存记录,当用户秒杀商品时,系统主要逻辑在于DB中库存的操作,一般对DB操作流程主要有以下三方面,1.锁库存. 2.插入秒杀记录. 3.更新库存,锁
    库存避免超卖情况,同时要求这三步操作需要在一个事务中完成,作为单个逻辑工作单元执行,要么全部成功,要么全部失败。
  3. 秒杀系统设计难点,就在这个事务的操作上,商品库存在DB中记为一行,大量用户同时秒杀同一商品,第一个到达DB的请求锁住了这个库存记录,在第一个事物完成之前,这个锁一直被
    第一个请求占用,后面的所有请求需要排队等待。并发请求的用户越多,DB请求就越多,排队越严重。

秒杀系统解决高并发问题常用方案

  1. 方案一,使用内存操作替代实时DB事务操作。将实时扣库存的行为上移到内存Cache中操作,内存Cache操作成功直接给Server返回成功,然后异步落DB持久化。
    优点:用内存操作替换磁盘操作,提高了并发性能。
    缺点:在内存操作成功,但是DB持久化失败,或者内存Cache故障的情况下,DB持久化会丢数据。
  2. 方案二 使用乐观锁代替悲观锁:
    悲观锁:关系数据库管理系统中一种并发控制的方法。
    乐观锁:它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,会检查该事务读取的数据后,有没有其他事务对数据
    进行了修改,如果有修改,正在提交的事务会回滚。
    商品秒杀系统中,乐观锁的具体应用方法,是在DB的库存记录中维护一个版本号,在更新库存之前,先去DB获取当前版本号,在更新库存的事务提交时,检查版本号是否已经被其他事务修改,如果没有修改,
    则提交事务,且版本号+1,如果版本号已经被其他事务修改,那么回滚事务,并报错。
    优点:提高了DB并发处理的能力
    缺点:采用乐观锁的方式,会带来大数据量的无效更新,事务回滚,给DB造成不必要的压力。用户体验也十分不好。

秒杀系统的挑战

  1. 对现有网站业务造成冲击
  2. 高并发下的应用. 数据库压力
  3. 突然增加的网络及服务器宽带
  4. 直接下单,下单页面的URL,需要动态化

秒杀系统的应对策略

  1. 秒杀系统独立部署
  2. 秒杀系统页面静态化,用户请求不需要经过应用服务器的业务逻辑,也不需要访问数据库。
  3. 租赁秒杀网络宽带,需要将秒杀商品网页缓存到CDN,同样需要和CDN服务商临时租赁新增的出口宽带。
  4. 动态生成下单页面的URL,为了避免用户直接访问下单页面的URL,需要将URL动态化。在下单页面的URL加入由服务端生成的随机数作为参数,在秒杀开始的时候才能得到。

秒杀系统架构设计

  1. 秒杀页面尽量设计的简单
  2. 下单页也一样,尽量简单。只有第一个提交的订单会发送到网站的订单系统,其他用户提交订单后,只能看到秒杀结束页面。

如何控制秒杀商品页面购买按钮的点亮

使用javascript脚本控制,在秒杀商品静态页面加入一个javaScript文件引用,该javaScript文件中加入秒杀是否开始的标志和下单页面URL的随机参数,当秒杀开始的时候,生成一个新的javaScript文件并被用户浏览器加载,控制秒杀商品页面的展示,这个javaScript文件使用随机版本号,并且不做浏览器. CDN和反向代理服务器缓存。javaScript文件非常小,及时每次浏览器刷新都访问javaScript文件服务器也不会对服务器集群和网络宽带造成太大压力。

如何只允许第一个提交的订单被发送到订单子系统。

  1. 在用户提交订单时,需要检查是否已经有订单提交了。可以控制进入下单页面的入口,只有少数用户能进入到下单页,其他用户直接进入秒杀结束页面。

service

微服务故障

  1. 分布式调用故障
    序列化. 反序列化故障
    分布式路由故障
    网络通信故障
  2. 第三方服务故障,微服务会依赖第三方服务,包括数据库服务. 文件存储服务. 缓存服务. 消息队列服务等
    网络通信类故障
    雪崩效应,导致的级联故障,例如服务处理缓慢导致客户端线程被阻塞
    第三方不可用,导致微服务处理失败
  3. 微服务之间的故障
    处理较慢的微服务会阻塞其他服务
    某个微服务故障蔓延,导致整个进程不可用OOM
    低优先级的微服务,抢占高优先级微服务的资源

服务故障隔离

  1. IO操作故障隔离:网络IO. 磁盘IO. 数据库
  2. 资源故障隔离
    通信链路隔离
    调度资源隔离:微服务之间隔离. 第三方依赖隔离
    进程级隔离:VM隔离. 进程级隔离
  3. 容错能力
    路由容错:失败重试. 失败回调. 快速失败
    服务降级:强制降级. 容错降级
    熔断:全部拒绝. 部分拒绝
  4. 流量控制

故障隔离技术

同步I/O主要弊端:
I/O操作同步阻塞, 受制于网络和第三方处理速度  I/O线程效率低,容易发生 线程数量膨胀. 通信队列积压 等问题
优化策略:
TCP私有协议:建议直接基于 Netty开发
HTTP/Restful/SOAP等:选择支 持非阻塞I/O的Web框架。可以选 择基于Netty构建的开源应用层 协议栈框架

RPC通信链路隔离

隔离策略:

  1. 微服务节点之间支持配置多链路
  2. 微服务链路支持不同的隔离策略: 例如根据消息码流大小. 根据微服务的优先级等策略,实现链路级的隔离

微服务调度隔离

关键技术点:

  1. 微服务发布时支持指定线程池/线程组
  2. 微服务线程池支持独享和共享两种模式  微服务和线程池监控,识别故障微服务, 动态调整到故障隔离线程池中
  3. 支持按照微服务优先级调度微服务, 即微服务线程支持微服务优先级调度

第三方服务依赖隔离

关键技术点:

  1. 第三方依赖隔离可以采用线程池 + 响应式编程(例如RxJava)的方式实现
  2. 对第三方依赖进行分类,每种依赖对应一个独立的线程/线程池
  3. 微服务不直接调用第三方依赖的API,而是使用异步封装之后的API接口
  4. 异步调用第三方依赖API之后,获取Future对象。利用响应式编程框架, 可以订阅后续的事件,接收响应,针对响应进行编程

微服务进程隔离(Docker容器)

关键技术点:

  1. 微服务独立开发. 打包和部署
  2. 基于Docker部署微服务,可以实现细粒度的资源隔离,实现微服务的高密度部署。
    优势:
  3. 高效:微服务的启动和销毁速度非常快, 可以实现秒级弹性伸缩
  4. 高性能:Docker容器的性能接近裸的物理机, 综合性能损耗 < 5%
  5. 可移植性:“一次编写,到处运行”

分布式路由容错

  1. 失败自动重试:微服务调用失败自动重试
  2. 失败自动切换:当发生服务调用异常时,重新选路,查找下一个可用的微服务提供者
  3. 快速失败:对于一些非核心的服务,希望只调用一次,失败也不再重试
  4. 失败回调:提供异常回调接口,执行微服务消费者自定义的失败处理逻辑

服务降级

  1. 强制降级:不发起远程服务调用,执行本地降级策略,例如本地Mock方法
  2. 容错降级:当非核心服务不可用时,可以对故障服务做业务逻辑放通,以保障核心服务的运行,降级策略包括异常转换. 本地放通方法调用

熔断机制

  1. 熔断判断:微服务调用时, 对熔断开关状态进行判断,当熔断器开关关闭时, 请求被允许通过熔断器
  2. 熔断执行:当熔断器开关打 开时,微服务调用请求被禁止通过,执行失败回调接口
  3. 自动恢复:熔断之后,周期T 之后允许一条消息通过,如果成功,则取消熔断状态,否则继续处于熔断状态

spark

什么是Spark

  1. Apache Spark是一个围绕速度. 易用性和复杂分析构建的大数据处理框架。
    最初在2009年由加州大学伯克利分校的AMPLab开发,并于2010年成为Apache的开源项目之一。
  2. Spark为我们提供了一个全面. 统一的框架用于管理各种有着不同性质(文本数据. 图表数据等)的数据集和数据源(批量数据或实时的流数据)的大数据处理的需求。
  3. Spark可以将Hadoop集群中的应用在内存中的运行速度提升100倍,甚至能够将应用在磁盘上的运行速度提升10倍。
  4. 除了Map和Reduce操作之外,它还支持SQL查询,流数据,机器学习和图表数据处理。
    开发者可以在一个数据管道用例中单独使用某一能力或者将这些能力结合在一起使用。

Spark特性

  1. Spark通过在数据处理过程中成本更低的洗牌(Shuffle)方式,将MapReduce提升到一个更高的层次。利用内存数据存储和接近实时的处理能力,Spark比其他的大数据处理技术的性能要快很多倍。
  2. Spark还支持大数据查询的延迟计算,这可以帮助优化大数据处理流程中的处理步骤。Spark还提供高级的API以提升开发者的生产力,除此之外还为大数据解决方案提供一致的体系架构模型。
  3. Spark将中间结果保存在内存中而不是将其写入磁盘,当需要多次处理同一数据集时,这一点特别实用。Spark的设计初衷就是既可以在内存中又可以在磁盘上工作的执行引擎。当内存中的数据不适用时,Spark操作符就会执行外部操作。Spark可以用于处理大于集群内存容量总和的数据集。
  4. Spark会尝试在内存中存储尽可能多的数据然后将其写入磁盘。它可以将某个数据集的一部分存入内存而剩余部分存入磁盘。开发者需要根据数据和用例评估对内存的需求。Spark的性能优势得益于这种内存中的数据存储

Spark生态系统

  1. Spark Streaming:
    Spark Streaming基于微批量方式的计算和处理,可以用于处理实时的流数据。它使用DStream,简单来说就是一个弹性分布式数据集(RDD)系列,处理实时数据。
  2. Spark SQL:
    Spark SQL可以通过JDBC API将Spark数据集暴露出去,而且还可以用传统的BI和可视化工具在Spark数据上执行类似SQL的查询。用户还可以用Spark SQL对不同格式的数据(如JSON,Parquet以及数据库等)执行ETL,将其转化,然后暴露给特定的查询。
  3. Spark MLlib:
    MLlib是一个可扩展的Spark机器学习库,由通用的学习算法和工具组成,包括二元分类. 线性回归. 聚类. 协同过滤. 梯度下降以及底层优化原语。
  4. Spark GraphX:
    GraphX是用于图计算和并行图计算的新的(alpha)Spark API。通过引入弹性分布式属性图(Resilient Distributed Property Graph),一种顶点和边都带有属性的有向多重图,扩展了Spark RDD。为了支持图计算,
    GraphX暴露了一个基础操作符集合(如subgraph,joinVertices和aggregateMessages)和一个经过优化的Pregel API变体。此外,GraphX还包括一个持续增长的用于简化图分析任务的图算法和构建器集合。
  5. BlinkDB是一个近似查询引擎,用于在海量数据上执行交互式SQL查询。BlinkDB可以通过牺牲数据精度来提升查询响应时间。通过在数据样本上执行查询并展示包含有意义的错误线注解的结果,操作大数据集合。
  6. Tachyon是一个以内存为中心的分布式文件系统,能够提供内存级别速度的跨集群框架(如Spark和MapReduce)的可信文件共享。它将工作集文件缓存在内存中,从而避免到磁盘中加载需要经常读取的数据集。通过这一机制,不同的作业/查询和框架可以以内存级的速度访问缓存的文件。
    此外,还有一些用于与其他产品集成的适配器,如Cassandra(Spark Cassandra 连接器)和R(SparkR)。Cassandra Connector可用于访问存储在Cassandra数据库中的数据并在这些数据上执行数据分析。

Spark体系架构

Spark体系架构包括如下三个主要组件:

  1. 数据存储
  2. API
  3. 管理框架
  4. 数据存储:
    Spark用HDFS文件系统存储数据。它可用于存储任何兼容于Hadoop的数据源,包括HDFS,HBase,Cassandra等。
  5. API:
    利用API,应用开发者可以用标准的API接口创建基于Spark的应用。Spark提供Scala,Java和Python三种程序设计语言的API。
    下面是三种语言Spark API
    Scala API
    Java
    Python
  6. 资源管理:
    Spark既可以部署在一个单独的服务器也可以部署在像Mesos或YARN这样的分布式计算框架之上。

Spark体系架构

  1. 弹性分布式数据集:弹性分布式数据集(基于Matei的研究论文)或RDD是Spark框架中的核心概念。可以将RDD视作数据库中的一张表。其中可以保存任何类型的数据。Spark将数据存储在不同分区上的RDD之中。
    RDD可以帮助重新安排计算并优化数据处理过程。此外,它还具有容错性,因为RDD知道如何重新创建和重新计算数据集。RDD是不可变的。你可以用变换(Transformation)修改RDD,但是这个变换所返回的是一个全新的RDD,而原有的RDD仍然保持不变。
    RDD支持两种类型的操作:
    变换(Transformation)
    行动(Action)

变换:变换的返回值是一个新的RDD集合,而不是单个值。调用一个变换方法,不会有任何求值计算,它只获取一个RDD作为参数,然后返回一个新的RDD。

变换函数包括:map,filter,flatMap,groupByKey,reduceByKey,aggregateByKey,pipe和coalesce。

行动:行动操作计算并返回一个新的值。当在一个RDD对象上调用行动函数时,会在这一时刻计算全部的数据处理查询并返回结果值。

行动操作包括:reduce,collect,count,first,take,countByKey以及foreach。

synchronized

synchronized是java的关键字,是一种同步锁

  1. 修饰代码块,被修饰的代码块成为同步代码块,其作用范围是大括号括起来的代码,作用的对象是调用这个代码块的对象。
  2. 修饰一个方法,被修饰的方法成为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象。
  3. 修改是一个静态方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象。

零长度的byte数组创建起来比任何对象都经济,生成0长度的byte[]对象只需要3条字节码. 而创建一个Object则需要7步骤。

并发访问时需要注意事项

  1. 当2个并发线程访问一个对象Object中的这个Synchronized同步代码块时,一个时间内只能有一个线程得到执行,另一个线程必须等待
    当前线程执行完,才能执行。
  2. 当一个线程访问Object的Synchronized方法时,另一个线程可以访问非synchronized的同步代码块。
  3. 当一个线程访问Object的Synchronized方法时,其他线程对Object中所有其他Synchronized同步代码块的访问将阻塞。
  4. 静态方法属于类,而不属于任何一个对象,synchronized修饰的静态方法锁定这个对象的所有类。
    总结:
  5. 无论synchronized关键字加在方法上还是对象上,如果它作用的是非静态的,则它取得的锁是对象。如果一个synchronized作用的对象
    是一个静态方法或者一个类,则它获取的锁是对该类所有对象的同一把锁。

system

高并发服务器建议调小 TCP 协议的 time_wait 超时时间

说明:操作系统默认 240 秒后,才会关闭处于 time_wait 状态的连接,在高并发访问下,服 务器端会因为处于 time_wait 的连接数太多,可能无法建立新的连接,所以需要在服务器上 调小此等待值。
正例:在 linux 服务器上请通过变更/etc/sysctl.conf 文件去修改该缺省值(秒):
net.ipv4.tcp_fin_timeout = 30

调大服务器所支持的最大文件句柄数(File Descriptor,简写为fd)

说明:主流操作系统的设计是将 TCP/UDP 连接采用与文件一样的方式去管理,即一个连接对 应于一个 fd。
主流的 linux 服务器默认所支持最大 fd 数量为 1024,当并发连接数很大时很 容易因为 fd 不足而出现“open too many files”错误,导致新的连接无法建立。
建议将 linux 服务器所支持的最大句柄数调高数倍(与服务器的内存数量相关)。

给 JVM 设置-XX:+HeapDumpOnOutOfMemoryError 参数,让 JVM 碰到 OOM 场景时输出 dump 信息

说明:OOM 的发生是有概率的,甚至有规律地相隔数月才出现一例,出现时的现场信息对查错 非常有价值。

服务器内部重定向使用 forward,外部重定向地址使用 URL 拼装工具类来生成,否则 会带来 URL 维护不一致的问题和潜在的安全风险。

threadpoolexecutor

参数介绍

  1. corePoolSize:线程池维护线程的最小数量。
  2. maxinumPoolSize:线程池维护线程的最大数量。
  3. keepAliveTime:线程池维护线程所允许的空闲时间。
  4. unit:空闲时间对应的时间单元。
  5. workQueue:线程池所使用的缓冲队列

线程池对拒绝任务的处理策略

  1. 不使用线程池线程执行。
  2. 直接丢弃当前任务。
  3. 丢弃队列中最旧的任务。
  4. 抛出异常。
    这四种策略是独立无关的,是对任务拒绝处理的四种表现形式。最简单的方式就是直接丢弃任务。
    但是却有两种方式,到底是改丢弃哪一个任务,比如可以丢弃当前将要加入队列的任务本身,或者丢弃任务队列最旧任务。
    丢弃最旧任务也不是简单的丢弃最旧的任务,而是有一些额外的处理。除了丢弃任务还可以抛出异常,这是比较简单的方式。
    抛出异常的方式比较简单,但是会中断调用者的处理过程,除了抛出异常还可以不进入线程池执行,在这种方式中,任务将由调用者去执行。

transaction

数据库事务的四个特性ACID:

  1. 原子性:事务是一个原子操作,由一系列动作组成,要么全部成功,要么全部失败。
  2. 一致性:事物开启到结束,数据库的完整性没有被破坏,一个事务执行之前和执行之后都必须处于一致性状态。
  3. 隔离性:事务之间相互隔离,互不影响。
  4. 持久性:事务一旦提交,影响将是永久性的。

weakhashmap

WeakHashMap

它的特殊之处在于 WeakHashMap 里的entry可能会被GC自动删除,即使程序员没有调用remove()或者clear()方法。
使用 WeakHashMap 时,即使没有显示的添加或删除任何元素,也可能发生如下情况:
调用两次size()方法返回不同的值;
两次调用isEmpty()方法,第一次返回false,第二次返回true;
两次调用containsKey()方法,第一次返回true,第二次返回false,尽管两次使用的是同一个key;
两次调用get()方法,第一次返回一个value,第二次返回null,尽管两次使用的是同一个对象。

WeekHashMap用途:

WeekHashMap 的这个特点特别适用于需要缓存的场景。在缓存场景下,由于内存是有限的,不能缓存所有对象;
对象缓存命中可以提高系统效率,但缓存MISS也不会造成错误,因为可以通过计算重新得到。

原理

要明白 WeekHashMap 的工作原理,还需要引入一个概念:弱引用(WeakReference)。我们都知道Java中内存是通过GC自动管理的,GC会在程序运行过程中自动判断哪些对象是可以被回收的,并在合适的时机进行内存释放。
GC判断某个对象是否可被回收的依据是,是否有有效的引用指向该对象。如果没有有效引用指向该对象(基本意味着不存在访问该对象的方式),那么该对象就是可回收的。这里的“有效引用”并不包括弱引用。
也就是说,虽然弱引用可以用来访问对象,但进行垃圾回收时弱引用并不会被考虑在内,仅有弱引用指向的对象仍然会被GC回收。
WeakHashMap 内部是通过弱引用来管理entry的,弱引用的特性对应到 WeakHashMap 上意味着什么呢?将一对key, value放入到 WeakHashMap 里并不能避免该key值被GC回收,
除非在 WeakHashMap 之外还有对该key的强引用。

web

静态化系统的特征

  1. 一个页面对应的URL通常是固定的。不同的URL标识不同的内容。
  2. 页面中不能包含与浏览者相关的因素,例如JS动态生成的部分,还有用户的姓名. 身份等。
  3. 在页面中不能包含时间因素。
  4. 在页面中不能包含地域因素。
  5. 不能包含Cookie等私有数据。

为什么要进行静态化架构设计

  1. 系统进行多次升级,包括架构升级,系统本身升级,代码优化,增加各种缓存,这些java系统中做的优化,不能满足要求。
    java本身不擅长处理大量连接的请求,每个连接消耗的内存较多。
  2. 所以需要跳出java系统,在前面的web服务层直接返回。

静态化系统的优点:

  1. 改变了缓存的方式,直接缓存HTTP连接,而不是仅仅缓存数据。web代理服务器根据请求URL直接取出对应的HTTP响应头
    和响应提直接返回。这个响应连HTTP都不用重新组装。
  2. 改变了缓存的地方。不是在java层面做缓存,而是直接在web服务层做,屏蔽了java的一些弱点。

如何动态改造系统

  1. 动静分离
  2. 组装动态内容:
    2.1 ESI,在Web代理服务器上做动态内容请求,并将请求插入到静态页面中。当用户拿到页面已经是一个完整的页面了。这种对服务器
    性能有些影响,但是用户体验是好的。
    2.2 CSI,这种方式就是发起一个异步JS请求单独向服务器获取动态内容。这种方式使服务器性能更佳,但是用户端页面有些延迟,体验
    稍差。

zk

流行的应用场景

  1. 分布式配置管理
    发布与订阅即所谓的配置管理,顾名思义就是将数据发布到zk的节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新,例如全局的配置信息,地址列表等就非常的适合使用。
  2. Name Server
    这个主要是用作分布式命名服务,通过调用zk的create node api,能够很容易创建一个全局唯一的path,这个path就可以作为一个名称。
  3. 分布式通知/协调
    Zookeeper中特有的watcher注册与异步通知机制,能够很好的实现分布式环境下不同系统之间的通知与协调,实现对数据变更的实时处理。使用方法通常是不同系统都对zk上同一个znode进行注册,监听znode的变化(包括znode本身内容以及子节点的),其中一个系统update了znode,那么另外一个系统能够收到通知,并作出对应的处理。
  4. 分布式锁
    分布式锁,这个主要得益于zookeeper为我们保证了数据的强一致性,即用户只要完全相信每时每刻,zk集群中任意的借点(一个zk server)上的相同znode的数据是一定相同的。锁服务可以氛围两类,一个是保持独占,另一个是控制时序。
  5. 集群管理
    Hbase Master 选举则是zookeeper经典的使用场景;
    Storm集群管理。
  6. 分布式队列
    队列方面一种是常规的先进先出队列,另外一种是要等到队列成员都聚齐之后才统一按序执行,对于第二种先进先出队列,增加分布式锁服务以控制时序场景。