Dubbo

Dubbo

高性能Java RPC框架.Apache Dubbo |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

远程过程调用(英语:Remote Procedure Call,缩写为 RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。

HTTP/TCP 都属于RPC

特性一览

面向接口代理的高性能RPC调用

提供高性能的基于代理的远程调用能力,服务以接口为粒度,为开发者屏蔽远程调用底层细节。

智能负载均衡

内置多种负载均衡策略,智能感知下游节点健康状况,显著减少调用延迟,提高系统吞吐量。

服务自动注册与发现

支持多种注册中心服务,服务实例上下线实时感知。

高度可扩展能力

遵循微内核+插件的设计原则,所有核心能力如Protocol、Transport、Serialization被设计为扩展点,平等对待内置实现和第三方实现。

运行期流量调度

内置条件、脚本等路由策略,通过配置不同的路由规则,轻松实现灰度发布,同机房优先等功能。

可视化的服务治理与运维

提供丰富服务治理、运维工具:随时查询服务元数据、服务健康状态及调用统计,实时下发路由策略、调整配置参数。

Dubbo核心角色

Dubbo核心角色

核心角色:

  • 服务提供者者,
  • 服务消费者,
  • 注册中心,
  • 监控中心

服务提供者:

  1. 提供服务的接口 (API)
  2. 实现服务(实现类)
  3. 注册服务(远程服务,本地注册)
  4. 暴露服务(比如启动tomcat)

注册中心:

  1. 保存服务名与服务器地址映射的关系,
  2. 服务地址变更主动通知服务消费者

服务消费者:

  1. 启动时从服务中心获取服务地址并缓存
  2. 根据负载均衡策略选出一个服务地址进行服务调用。

监控中心:
统计服务的调用次数和调用时间的监控中心

Nginx

Nginx

nginx是一个轻量级的Web服务器/反向代理服务器以及电子邮件(IMAP/POP3/SMTP)代理服务器,特点:占内存少,并发能力强。是俄罗斯人编写的十分轻量级的 HTTP 服务器。

基本特点:

1.处理静态文件,索引文件以及自动索引;打开文件描述符缓冲。
2.无缓存的反向代理加速,简单的负载均衡和容错。
3.FastCGI,简单的负载均衡和容错。
4.模块化的结构。包括 gzipping, byte ranges, chunked responses,以及 SSI-filter 等 filter。如果由 FastCGI 或其它代理服务器处理单页中存在的多个SSI,则这项处理可以并行运行,而不需要相互等待。
5.支持 SSL 和 TLSSNI。

优势:

Nginx 专为性能优化而开发,性能是其最重要的考量,实现上非常注重效率 。它支持内核 Poll 模型,能经受高负载的考验,有报告表明能支持高达 50,000 个并发连接数。

Nginx 具有很高的稳定性。其它 HTTP 服务器,当遇到访问的峰值,或者有人恶意发起慢速连接时,也很可能会导致服务器物理内存耗尽频繁交换,失去响应,只能重启服务器。例如当前 apache 一旦上到 200 个以上进程,web响应速度就明显非常缓慢了。而 Nginx 采取了分阶段资源分配技术,使得它的 CPU 与内存占用率非常低。Nginx 官方表示保持 10,000 个没有活动的连接,它只占 2.5M 内存,所以类似 DOS 这样的攻击对 Nginx 来说基本上是毫无用处的。就稳定性而言,Nginx 比 lighthttpd 更胜一筹。

Nginx 支持热部署。它的启动特别容易, 并且几乎可以做到 7*24 不间断运行,即使运行数个月也不需要重新启动。你还能够在不间断服务的情况下,对软件版本进行进行升级。

Nginx 采用 master-slave 模型,能够充分利用 SMP 的优势,且能够减少工作进程在磁盘 I/O 的阻塞延迟。当采用 select()/poll() 调用时,还可以限制每个进程的连接数。

Nginx 代码质量非常高,代码很规范,手法成熟,模块扩展也很容易。特别值得一提的是强大的 Upstream 与 Filter 链。Upstream 为诸如 reverse proxy,与其他服务器通信模块的编写奠定了很好的基础。而 Filter 链最酷的部分就是各个 filter 不必等待前一个 filter 执行完毕。它可以把前一个 filter 的输出做为当前 filter 的输入,这有点像 Unix 的管线。这意味着,一个模块可以开始压缩从后端服务器发送过来的请求,且可以在模块接收完后端服务器的整个请求之前把压缩流转向客户端。

Nginx 采用了一些 os 提供的最新特性如对 sendfile (Linux2.2+),accept-filter (FreeBSD4.1+),TCP_DEFER_ACCEPT (Linux 2.4+)的支持,从而大大提高了性能。

并发编程

并发编程

JVM内存模型(JMM)
1.Java当中的线程通讯和消息传递
2.什么是重排序和顺序一致性?Happens-Before?As-If-Serial?

Synchronized的概念和分析
1.同步,重量级锁以及Sychronized的原理分析
2.自旋锁,偏向锁,轻量级锁,重量级锁的概念、使用以及如何优化

Volatile和DCL的知识
1.Volatile的使用场景和Volatile实现机制、内存语义、内存模型
2.DCL的单例模式,什么是DCL?如何来解决DCL的问题

并发基础之AQS的深度分析
1.AnstractAueuedSynchronizer同步器的概念、CLH同步队列是什么?
2.同步状态的获取和释放、线程阻塞和唤醒

Lock和并发常用工具类
1.Java当中的Lock、ReenrantLock、ReentrantReadWriteLock、Condition
2.Java当中的并发工具类CyclicBarrier、CountdownLatch、Semphore
3.Java当中的并发集合类ConcurrentHashMap、ConcurrentLinkedQueue…

原子操作常用知识讲解
1.基本类型的原子操作比如经典的AtomicBoolean、AtomicIngter、AtomicLong
2.数组类型的原子操作代表的几个类AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
3.引用类型的原子操作的典型AtomicReference、AtomicReferenceFieldUpdater…
4.CAS概念知识、COmpare And Swap以及缺陷

线程池和并发并行
1.Excutor、ThreadPoolExcutor、Callable & Future、ScheduledExcutorService
2.ThreadLocal、Fork & Join?什么是并行?线程池如何保证核心线程不被销毁?

引用类型

引用类型

对象引用类型分为强引用、软引用、弱引用和虚引用。

强引用: 就是我们一般声明对象是时虚拟机生成的引用,强引用环境下,垃圾回收时需要严格判断当前对象是否被强引用,如果被强引用,则不会被垃圾回收

软引用: 软引用一般被做为缓存来使用。与强引用的区别是,软引用在垃圾回收时,虚拟机会根据当前系统的剩余内存来决定是否对软引用进行回收。如果剩余内存比较紧张,则虚拟机会回收软引用所引用的空间;如果剩余内存相对富裕,则不会进行回收。换句话说,虚拟机在发生OutOfMemory时,肯定是没有软引用存在的。

弱引用: 弱引用与软引用类似,都是作为缓存来使用。但与软引用不同,弱引用在进行垃圾回收时,是一定会被回收掉的,因此其生命周期只存在于一个垃圾回收周期内。

强引用不用说,我们系统一般在使用时都是用的强引用。而“软引用”和“弱引用”比较少见。他们一般被作为缓存使用,而且一般是在内存大小比较受限的情况下做为缓存。因为如果内存足够大的话,可以直接使用强引用作为缓存即可,同时可控性更高。因而,他们常见的是被使用在桌面应用系统的缓存。

对象实例与对象引用有何不同

对象实例与对象引用有何不同

new运算符,new创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。
一个对象引用可以指向0个或1个对象(一根绳子可以不系气球,也可以系一个气球);
一个对象可以有n个引用指向它(可以用n条绳子系住一个气球)。

1)分配地址
2)创建对象实例(堆内存)
3)对象引用指向对象实例 (栈内存)

栈代表了处理逻辑,而堆代表了数据。

栈是运行时的单位,而堆是存储式的单位。

栈解决程序运行的问题,解决程序如何运行的问题,如何处理数据。

堆解决数据存储问题,数据存哪,怎么存。

定时任务

定时任务

Java实现定时任务的三种方法:

  • 普通thread实现
  • TimerTask实现
  • ScheduledExecutorService实现

普通thread

这是最常见的,创建一个thread,然后让它在while循环里一直运行着,通过sleep方法来达到定时任务的效果。这样可以快速简单的实现,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ScheduleTask {
public static void main(String[] args) {
// run in a second 定时任务
final long timeInterval = 1000;
Runnable runnable = new Runnable() {
public void run() {
while (true) {
// ------- code for task to run
System.out.println("Hello !!");
// ------- ends here
try {
Thread.sleep(timeInterval);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread thread = new Thread(runnable);
thread.start();
}
}

Timer和TimerTask

上面的实现是非常快速简便的,但它也缺少一些功能。
用Timer和TimerTask的话与上述方法相比有如下好处:

  • 当启动和去取消任务时可以控制
  • 第一次执行任务时可以指定你想要的delay时间
  • 在实现时,Timer类可以调度任务,TimerTask则是通过在run()方法里实现具体任务。
  • Timer实例可以调度多任务,它是线程安全的。

当Timer的构造器被调用时,它创建了一个线程,这个线程可以用来调度任务:

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
import java.util.Timer;
import java.util.TimerTask;

public class TimerTaskTest {

public static void main(String[] args) {

TimerTask task = new TimerTask() {

@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("Hello World!");
}

};

Timer timer = new Timer();
long delay = 0;
long inteval = 1 * 1000;

timer.scheduleAtFixedRate(task, delay, inteval);
}

}

ScheduledExecutorService

是从Java SE 5的java.util.concurrent里,做为并发工具类被引进的,这是最理想的定时任务实现方式。
相比于上两个方法,它有以下好处:

  • 相比于Timer的单线程,它是通过线程池的方式来执行任务的
  • 可以很灵活的去设定第一次执行任务delay时间
  • 提供了良好的约定,以便设定执行的时间间隔
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledExecutorServiceTest {
public static void main(String[] args) {

Runnable runnable = new Runnable() {
public void run() {
// task to run goes here
System.out.println("Hello !!");
}
};

ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
service.scheduleAtFixedRate(runnable, 0, 1, TimeUnit.SECONDS);
}
}

接口和抽象类的区别

接口和抽象类的区别

接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),而抽象类可以有非抽象的方法。

接口中除了static、final变量,不能有其他变量,而抽象类中则不一定。

一个类可以实现多个接口,但只能实现一个抽象类。接口自己本身可以通过extends关键字扩展多个接口。

接口方法默认修饰符是public,抽象方法可以有public、protected和default这些修饰符(抽象方法就是为了被重写所以不能使用private关键字修饰!)。

从设计层面来说,抽象是对类的抽象,是一种模板设计,而接口是对行为的抽象,是一种行为的规范。

Java 线程属性

#线程属性

以下是关系到线程属性的几个方法:

1)getId

用来得到线程ID

2)getName和setName

用来得到或者设置线程名称。

3)getPriority和setPriority

用来获取和设置线程优先级。

4)setDaemon和isDaemon

用来设置线程是否成为守护线程和判断线程是否是守护线程。

守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程则不依赖。举个简单的例子:如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。

Thread类有一个比较常用的静态方法currentThread()用来获取当前线程。

在上面已经说到了Thread类中的大部分方法,那么Thread类中的方法调用到底会引起线程状态发生怎样的变化呢?
image

死锁问题

死锁问题

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
public class DeadLockDemo {
private static Object resource1 = new Object();//资源 1
private static Object resource2 = new Object();//资源 2

public static void main(String[] args) {
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource2");
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
}
}
}, "线程 1").start();

new Thread(() -> {
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource1");
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
}
}
}, "线程 2").start();
}
}

产生死锁必须具备以下四个条件:

  • 互斥条件:该资源任意一个时刻只由一个线程占用
  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

如何避免线程死锁?

我们只要破坏产生死锁的四个条件中的其中一个就可以了。

破坏互斥条件

这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。

破坏请求与保持条件

一次性申请所有的资源。

破坏不剥夺条件

占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。

破坏循环等待条件

靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。