六大设计原则是设计模式的理论,设计模式是设计原则的实践。
一个类只负责一个职责,术语叫仅有一个引起变化的原因。一个类应该是一组相关性很高的函数及数据的封装。
一个软件实体应该对扩展开放,对修改关闭。
提倡一个类一旦开发完成,后序增加新的功能,不应该通过修改这个类实现,而是通过继承或者接口实现增加新的类。
抽象不应该依赖于细节,细节应该依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。
也就是说两个模块之间的通信应该通过接口来实现。
使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。即让调用者依赖的接口尽可能小,接口分离类似于单一职责原则。
一个软件实体应该尽可能地减少与其他实体发生相互作用。或者说一个类,对自己需要调用的类知道得最少,类的内部应该与被调用者无关,也称迪米特隔离
。
例如使用一个Thread类下边的run方法,按照迪米特原则,可以把run单独抽离出来,构建一个Runnable接口供userClass使用,这样调用者userClass与Thread之间的交互是最少的。
所有引用基类(父类)的地方,必须能够透明的使用其子类的对象。即一个软件系统中,把所有用到某个类的地方都替换为其子类,系统应该仍然可以正常工作。这个原则依赖面向对象的继承特性和多态特性。
单一职责原则
构建一个类或者接口开闭原则
继承类或者实现接口,来构建新类。里式替换
原则,所有使用父类的地方可以使用子类来替换。依赖倒置
原则,使用接口通信。接口隔离
原则,应该设计多个专门实现某种功能的接口迪米特原则
,老师类要实现对同学的点名,应该通过一个用来跟同学交互的班长,分层次实现。饿汉单例:初始化时直接创建一个静态的实例对象,这种方式天生就是线程安全的。
懒汉单例:实际需要使用时才创建,需要利用线程同步机制,有下边三种写法:
class SingleInstance {
private SingleInstance() {
}
private static SingleInstance singleInstance;
public static SingleInstance getInstance() {
if (singleInstance == null) {
synchronized (SingleInstance.class) {
if (singleInstance == null) {
singleInstance = new SingleInstance();
}
}
}
return singleInstance;
}
}
class Single {
private Single(){
}
private static Single single;
public static synchronized Single GetInstance() {
if (single == null) {
single = new Single();
}
return single;
}
}
静态内部类的成员只在初次调用时被初始化,非常符合懒汉单例的方式。
class SingleByInner{
private SingleByInner() {
}
static class Inner {
private static final SingleByInner INSTANCE = new SingleByInner();
}
public static SingleByInner getInstance() {
return Inner.INSTANCE;
}
}
class SingleByInner{
private SingleByInner() {
}
static class Inner {
private static final SingleByInner INSTANCE = new SingleByInner();
}
public static SingleByInner getInstance() {
return Inner.INSTANCE;
}
}
定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化操作推迟到了子类
监听者用来监听自已感兴趣的事件,当收到自已感兴趣的事件时执行自定义的操作。
监听者模式在Android中有大量的运用,相信大家都不会感到陌生。在Android开发中,Button控件的点击事件就是监听者模式最常见的例子。
当Button被点击,执行了 OnClickListener.onClick。 Activity中给这个Button设置了自己实现的OnClickListener,并复写了onClick方法,就能执行自定义操作了。
用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地互相作用, 从而达到松耦合的目的。
由以下几个部分组成
每个同事角色都知道中介者角色,但不与其他同事进行交互,而是通过中介者来调度。
为其他对象提供一种代理以控制对这个对象的访问。也叫委托模式。
主要有三个角色:
优点:
责任链模式是一种行为型设计模式,它用于将多个请求处理器对象连接成一条链,可以让请求沿着这条链不断地传递,直到有一个请求处理器处理完成为止,实现高效灵活的请求处理与分发。
主要涉及三个角色:
优点:
缺点:
将一个类的接口变换成客户端锁期待的另一种接口,从而使原本因接口不匹配而无法工作在一起的两个类能够在一起工作。
优点:
CAP 也就是 Consistency(一致性)、Availability(可用性)、Partition Tolerance(分区容错性) 这三个单词首字母组合。
CAP 定理(CAP theorem)指出对于一个分布式系统来说,当设计读写操作时,只能同时满足以下三点中的两个:
什么是网络分区?
分布式系统中,多个节点之间的网络本来是连通的,但是因为某些故障(比如部分节点网络出了问题)某些节点之间不连通了,整个网络就分成了几块区域,这就叫 网络分区。
大部分人解释这一定律时,常常简单的表述为:“一致性、可用性、分区容忍性三者你只能同时达到其中两个,不可能同时达到”。实际上这是一个非常具有误导性质的说法,而且在 CAP 理论诞生 12 年之后,CAP 之父也在 2012 年重写了之前的论文。
当发生网络分区的时候,如果我们要继续服务,那么强一致性和可用性只能 2 选 1。也就是说当网络分区之后 P 是前提,决定了 P 之后才有 C 和 A 的选择。也就是说分区容错性(Partition tolerance)我们是必须要实现的。
简而言之就是:CAP 理论中分区容错性 P 是一定要满足的,在此基础上,只能满足可用性 A 或者一致性 C。
因此,分布式系统理论上不可能选择 CA 架构,只能选择 CP 或者 AP 架构。 比如 ZooKeeper、HBase 就是 CP 架构,Cassandra、Eureka 就是 AP 架构,Nacos 不仅支持 CP 架构也支持 AP 架构。
为啥不可能选择 CA 架构呢?
举个例子:若系统出现“分区”,系统中的某个节点在进行写操作。为了保证 C, 必须要禁止其他节点的读写操作,这就和 A 发生冲突了。如果为了保证 A,其他节点的读写操作正常的话,那就和 C 发生冲突了。
选择 CP 还是 AP 的关键在于当前的业务场景,没有定论,比如对于需要确保强一致性的场景如银行一般会选择保证 CP 。
另外,需要补充说明的一点是:如果网络分区正常的话(系统在绝大部分时候所处的状态),也就说不需要保证 P 的时候,C 和 A 能够同时保证。
负载均衡 指的是将用户请求分摊到不同的服务器上处理,以提高系统整体的并发处理能力以及可靠性。负载均衡服务可以有由专门的软件或者硬件来完成,一般情况下,硬件的性能更好,软件的价格更便宜。
七层负载均衡比四层负载均衡会消耗更多的性能,不过,也相对更加灵活,能够更加智能地路由网络请求,比如说你可以根据请求的内容进行优化如缓存、压缩、加密。
简单来说,四层负载均衡性能更强,七层负载均衡功能更强! 不过,对于绝大部分业务场景来说,四层负载均衡和七层负载均衡的性能差异基本可以忽略不计的。
在工作中,我们通常会使用 Nginx 来做七层负载均衡,LVS(Linux Virtual Server 虚拟服务器, Linux 内核的 4 层负载均衡)来做四层负载均衡。
使用相同的哈希映射输入服务器的唯一标识码将机器也映射到这个环上:
对象与机器处于同一哈希空间中,这样按顺时针转动,:m1存储到了t3中,m3和m4存储到了t2中,m2存储到了t1中。
当需要增加一台机器t4时:仅需要修改m4-> t2 为
m4 -> t4
数据的移动仅发生在t2和t4之间,其他机器上的数据并未受到影响。
当删除一台机器t1时,需修改m2->t1 为 m2->t3,数据迁移仅发生在t1和t3之间。
问题1 机器节点数量少时,数据归属域不均衡
当集群中的节点数量较少时,可能会出现节点在哈希空间中分布不平衡的问题。如下图所示,图中节点A、B、C分布较为集中,造成hash环的倾斜。数据1、2、3、4、6全部被存储到了节点A上,节点B上只存储了数据5,而节点C上什么数据都没有存储。A、B、C三台机器的负载极其不均衡。
问题2 数据迁移时导致负载不均衡
在极端情况下,假如A节点出现故障,存储在A上的数据要全部转移到B上,大量的数据导可能会导致节点B的崩溃,之后A和B上所有的数据向节点C迁移,导致节点C也崩溃,由此导致整个集群宕机。这种情况被称为雪崩效应。
这两个问题都可以通过虚拟节点来解决。
每台实际机器(节点),分配大量的虚拟节点,虚拟节点量大,保证了在哈希输入环域上的均衡,只要记录好实际节点与虚拟节点的映射即可。
当增加或者减少机器时,同样增加或减少等多的大量虚拟节点,在虚拟节点上完成数据迁移即可。
同时还可根据实际机器内存的大小,分配不同数量的虚拟节点(假定每个虚拟节点的负载能力一致),以此用于负载的管理。