代理模式
为其它对象提供一种代理以控制对这个对象的访问。
类结构图
Subject:接口类,定义了一些需要代理的接口方法。
RealSubject:具体的实现类。
ProxySubject:代理类,保存一个Subject引用,可以注入一个具体的子类比如RealSubject。
代理模式其实就是在操作对象时引用一定程度的间接性。这种间接性,可以增加很多附加操作。比如权限控制,参数校验等等。
12345678910public class ProxyPersonManager implements PersonManager { // 接口引用 PersonManager realPersonManager = new RealPersonManager(); @Override public double getSalary(String name, String operateName) { // 1. 增加一些的权限判断。比如操作人是否有查询某人工资的权限 // 2. 具体类的调用 return realP ...
单例模式
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
双重锁检测123456789101112131415161718public class Singleton { // 单例对象 private volatile static Singleton instance = null; // 私有构造方法 private Singleton() {} public static Singleton getInstance() { if (install == null) { // 双重检测机制 synchronized (Singleton.class) { // 同步锁 if (instance == null) { // 双重检测机制 instance = new Singleton(); ...
合成模式
合成模式属于对象的结构模式,有时又叫做“部分-整体”模式。合成模式将对象组织到树结构中,可以用来描述整体与部分的关系。合成模式可以使客户端将单纯元素与复合元素同等看待。
结构类图
Component:这是一个抽象角色,它给参与组合的对象规定一个接口。这个角色给出共有接口及其默认行为,可以用来管理所有的子对象。合成对象通常把它所包含的子对象当作类型为Component的对象。在安全模式的合成模式里,构件角色并不定义出管理子对象的方法,这一定义由树枝构件对象给出。
Leaf:代表餐价组合的树叶对象。一个树叶对象没有下级子对象,定义了参加组合的原始对象的行为。
Composite:代表参加组合的有子对象的对象,并给出树枝构件对象的行为。树枝构件给出了所有的管理子对象的方法,如add、remove以及getChild。
工厂模式
类图
模板方法模式
应用场景很多,尤其是在框架设计中,提供了一个方便的开发程序的模板,你只要实现模板中的一些接口或者方法就能完成一个复杂的任务。
结构类图
AbstractTemplate:定义一个完整的框架,方法的调用顺序已经确定,但会定义一些抽象的方法留给子类去实现。
SubTemplate:实现抽象模板中定义的抽象方法,从而形成一个完整的流程逻辑。
123456789101112131415161718192021222324252627282930313233public TradeFlowActionResult execute(TradeFlowActionParam param, Map context) throws ServiceException { try { // 业务逻辑校验 this.validateBusinessLogic(param, context); } catch (ServiceException ex) { sendGoodsLog.info("SendGoodsA ...
策略模式
策略模式通常是指完成某个操作可能会有多种方法,适用于多种场合。我们需要把每个操作方法当作一个实现策略,调用者可根据需要(特定的规则)选择合适的策略。
结构类图
Context:使用不同的策略环境,根据自身的条件选择不同的策略实现类来完成所需要的操作。它持有一个策略实例的引用。
Strategy:抽象策略,定义每个策略都要实现的方法。
Realize1,Realize2:负责实现抽象策略中定义的策略方法。
例子
123456789@Override@Enhancement({@Capability(type = CapabilityTypeEnum.INVOCATION_STATS)})public void sendGoods(SendGoodsParam param) throws ServiceException { if (param == null || param.getId() == null) { this.throwInValidError(ErrorCodeEnum.NULL_PARAM, null, pa ...
装饰模式
装饰者模式,在保持原有功能不变的情况下将一个类重新装饰,使其具有更强大的功能,用一句成语形容“锦上添花”。
类结构:
Component:抽象组件,定义了一组抽象的接口,制定了被修饰的组件都有哪些功能。
ComponentImpl:抽象组件实现类,完成了基本的功能实现。
Decorator:装饰器角色,持有Component的实例引用,有点递归的感觉。
12345678910Component c = new ComponentImpl();Decorator d1 = new Decorator();d1.setComponent(c);Decorator d2 = new Decorator();d2.setComponent(d1);Decorator d3 = new Decorator();d3.setComponent(d2);Decorator d4 = new Decorator();d4.setComponent(d3);d4.methodA();
装饰模式和适配器模式有点类似,都是包装(wrapper)了一个类,但目的却不相同。适配器模式是将一个接口转换成另一个 ...
观察者模式
观察者模式通常也叫 发布-订阅 模式,或者事件监听模式,定义一对多的依赖关系,让多个观察者对象同时监听一个主题对象,如果这个主体对象的状态发生变化时,会通知所有的观察者对象。异步消息(MQ、ActiveMQ)都是基于这种模式。
类结构
Subject:主题类,将所有的观察者对象保存在一个List集合中,并提供增、删方法,以及状态变化后的通知方法。
Observer:观察者的抽象接口,提供了一个抽象的动作方法,具体的业务由子类来实现。
ConcreteObserver:具体的观察者,负责实现自己的业务动作。
ConcreteSubject:具体的主题类,在内部状态发生变化时,给所有登记过的观察者发出通知。
优点
解耦,将耦合的双方都依赖于抽象类,而不是依赖于具体。从而使得各自的变化不会影响到另一边的变化。
Observer采用的是抽象类,这样的好处是可以将多个子类相同的代码逻辑抽取出来,放到抽象类中。
责任链模式
责任链模式就是很多对象由每个对象对其下家的引用串联起来形成一条链,请求在这条链上传递,知道最终处理完。就像一根水管一样,水从一端流入,会经过一系列的阀门,最终从另一端流出。如果有一个阀门关着,水就流不出来。
链上的节点可以控制,根据是否执行分为两种情况:
找到对应的点,执行,跳出。如:for循环的break。
所有的节点都执行一遍,上个节点的返回结果作为下个节点的入参。
业务需求:抽奖
步骤:
抽奖资格判断
判断人群
判断抽奖限制(如每天只能抽3次)
黑名单
判断中奖次数限制(如最多只能中3次) 。。。。。。。。
中奖逻辑
根据用户V等级进行概率过滤
根据抽奖概率进行过滤
发奖逻辑
取出当前奖品分布,并选出一个奖品分布来发奖
减库存
发奖
发奖成功后的逻辑处理
处理其它processor中添加的callback
代码示例 :
主流程:
1234567891011121314151617181920212223242526272829public class DefaultAwardCommanderProcedure implemen ...
适配器模式
适配器模式就是一个类的接口不能被客户端接受,需要转换成另一种接口,从而使两个不匹配的接口能在一起工作。
类结构
Adaptee:源接口,需要适配的接口。
Target:目标接口,暴露出去的接口。
Adapter:适配器,将源接口适配成目标接口。
举个例子:
Adaptee是内存卡,Target是电脑,而Adapter则是USB读卡器。
适用场景
比如查物流信息,由于物流公司的系统都是各自独立,而编程语言和交互方式上有很大差异,需要针对不同的物流公司做单独适配,同时结合不同公司的系统性能,配置不同的响应超时时间。
基础组件设计
1、ORM组件设计核心功能
根据ID获取Entity
根据QueryWrapper获取第一个Entity
根据ID集合获取Entity列表
获取所有Entity列表
根据QueryWrapper获取Entity列表
根据IPage分页查询对象获取Entity分页数据
根据IPage分页查询对象和QueryWrapper获取Entity分页数据
保存Entity(ID不存在插入记录,ID存在更新记录)
批量保存Entity
根据ID删除Entity
根据ID集合批量删除Entity
根据Entity集合批量删除Entity
根据ID获取Vo
根据QueryWrapper获取第一个Vo
根据ID集合获取Vo列表
获取所有Vo列表
根据QueryWrapper获取Vo列表
根据IPage分页查询对象获取Vo分页数据
根据IPage分页查询对象和QueryWrapper获取Vo分页数据
保存Dto(ID不存在插入记录,ID存在更新记录)
批量保存Dto
根据ID集合统计数量
根据QueryWrapper统计数量
资源访问鉴权
关键实现逻辑
逻辑删除
解决思路:增加is_deleted字段
...
系统架构探索
架构图系统架构
LB(负载均衡)负载均衡选型 NginxRPC 【待定,需参考实际使用场景】RPC框架选型 gPRCgPRC适合服务间通信的场景,无需与前端应用进行交互,适用于当前的系统架构。
gRPC文档
服务治理框架选型 etcd
服务发现
强一致性、高可用的服务存储目录。基于Raft算法的etcd本身就是一个强一致性高可用的服务存储目录。
注册服务和监控服务健康状态的机制。可以在etcd中注册服务,并且对注册的服务设置key TTL,定时保持服务的心跳以达到监控健康状态的效果。
查找和连接服务的机制。通过在etcd指定的主题下注册的服务也能在对应的主题下查找到。为了确保连接,我们可以在每个服务机器上都部署一个proxy模式的etcd,这样就可以确保能访问etcd集群的服务都能互相连接。
配置中心
应用中用到的一些配置信息放到etcd上集中进行管理。应用在启动的时候主动从etcd获取一次配置信息,同时,在etcd节点上注册一个watcher并等待,以后每次配置有更新的时候,etcd都会实时通知订阅者,以此达到获取最新配置信息的目的。
分布式通知与协调
低耦合的心 ...
位运算
位运算即是在位级别进行操作的技术,合适的位运算能够帮助我们得到更快的运算速度与更小的内存使用。
常见的位运算:
测试第k位:s & (1 << k)
设置第k位:s |= (1 << k)
第k位置零:s &= ~(1 << k)
切换第k位值:s ^= ~(1 << k)
乘以2^n:s << n
除以2^n:s >> n
交集:s & t
并集:s | t
减法:s & ~t
交换:x = x ^ y ^ (y = x)
取出最小非0位(Extract lowest set bit):s & (-s)
取出最小0位(Extract lowest unset bit):~s & (s + 1)
交换值:x ^= y; y ^= x; x ^= y;
图算法
深度优先搜索
深度优先算法是一种优先遍历子节点而不是回溯的算法。
时间复杂度: O(|V| + |E|)
广度优先搜索
广度优先搜索是优先遍历邻居节点而不是子节点的图遍历算法。
时间复杂度: O(|V| + |E|)
拓扑排序
拓扑排序是对于有向图节点的线性排序,如果存在某条从 u 到 v 的边,则认为 u 的下标先于 v。
时间复杂度: O(|V| + |E|)
Dijkstra算法
Dijkstra 算法 用于计算有向图中单源最短路径问题。
时间复杂度: O(|V|^2)
Bellman-Ford算法
Bellman-Ford 算法是在带权图中计算从单一源点出发到其他节点的最短路径的算法。
尽管算法复杂度大于 Dijkstra 算法,但是它适用于包含了负值边的图。
时间复杂度:
最优时间: O(|E|)
最坏时间: O(|V||E|)
Floyd-Warshall算法
Floyd-Warshall 算法 能够用于在无环带权图中寻找任意节点的最短路径。
时间复杂度:
最优时间: O(|V|^3)
最坏时间: O(|V|^3)
平均时间: O(|V|^3)
P ...
常见数据结构
Linked List
链表即是由节点(Node)组成的线性集合,每个节点可以利用指针指向其他节点。它是一种包含了多个节点的、能够用于表示序列的数据结构。
单向链表: 链表中的节点仅指向下一个节点,并且最后一个节点指向空。
双向链表: 其中每个节点具有两个指针p、n,使得p指向先前节点并且n指向下一个节点;最后一个节点的n指针指向null。
循环链表: 每个节点指向下一个节点并且最后一个节点指向第一个节点的链表。
时间复杂度:
索引:O(n)
搜索:O(n)
插入:O(1)
移除:O(1)
Stack
栈是元素的集合,其包含了两个基本操作:push操作可以用于将元素压入栈,pop操作可以将栈顶元素移除。
遵循后入先出(LIFO)原则。
时间复杂度:
索引:O(n)
搜索:O(n)
插入:O(1)
移除:O(1)
Queue
队列是元素的集合,其包含了两个基本操作:enqueue操作可以用于将元素插入到队列中,而dequeue操作则是将元素从队列中移除。
遵循先入先出原则(FIFO)。
时间复杂度:
索引:O(n)
搜索:O(n)
插入:O(1)
移除:O(1)
Tre ...
排序算法
插入排序直接插入排序
思想: 每次将一个待排序的数据按照其关键字的大小插入到前面已经排序好的数据中的适当位置,直到全部数据排序完成。
时间复杂度: O(n^2) O(n) O(n^2) (最坏 最好 平均)
空间复杂度: O(1)
稳定性: 稳定,每次都是在前面排好序的序列中找到适当的位置,只有小的数字会往前插入,所以原来相同的两个数字在排序后相对位置不变。
代码示例:
123456789101112//插入排序public static void insertSort(int[] array) { for (int i = 2; i < array.length; i++) { int val = array[i]; int j = i - 1; while (j >= 0 && array[j] > val) { // array[j] > val array[j + 1] = array[j]; j--; ...
查找算法
顺序查找
思想: 这是最简单的算法,从头开始遍历每个元素,并将每个元素与查找元素比较,如果一致则返回。
时间复杂度: O(n)
空间复杂度: O(1)
代码示例:
1234567891011public int search(int[] array, int num) { if (array == null || array.length == 0) { return -1; } for (int i = 0; i < array.length; i++) { if (array[i] == num) { return i; } } return -1;}
二分查找
思想: 二分查找前提是查找的数组是有序的,利用数据有序的特性提高查找性能。首先与数组中间位置的值比较,如果查找值大于中间位置值,则对数组右边以相同的思路查找,否则在左边以相同方式查找。这种方式使得每次查找范围变为原来的1/2 ...
分布式系统
分布式系统
引用自 大家都在说的分布式系统到底是什么?
随着大型网站的各种高并发访问、海量数据处理等场景越来越多,如何实现网站的高可用、易伸缩、可扩展、安全等目标就显得越来越重要。为了解决这样一系列问题,大型网站的架构也在不断发展。提高大型网站的高可用架构,不得不提的就是分布式。本文主要简单介绍了分布式系统的概念、分布式系统的特点、常用的分布式方案以及分布式和集群的区别等。
集中式系统在学习分布式之前,先了解一下与之相对应的集中式系统是什么样的。
集中式系统用一句话概括就是:一个主机带多个终端。终端没有数据处理能力,仅负责数据的录入和输出。而运算、存储等全部在主机上进行。现在的银行系统,大部分都是这种集中式的系统,此外,在大型企业、科研单位、军队、政府等也有分布。集中式系统,主要流行于上个世纪。
集中式系统的最大的特点就是部署结构非常简单,底层一般采用从IBM、HP等厂商购买到的昂贵的大型主机。因此无需考虑如何对服务进行多节点的部署,也就不用考虑各节点之间的分布式协作问题。但是,由于采用单机部署。很可能带来系统大而复杂、难于维护、发生单点故障(单个点发生故障的时候会波及到整个系统 ...
可容灾分布式系统设计
可容灾分布式系统设计
引用自 后台开发必备知识——容灾 容灾系统的衡量指标和级别有哪些?
基本概念后台开发的目标是要提供高可用的后台服务,其中很重要的一点是保证业务连续性(服务不中断,或中断时间在允许范围内)。要保证业务连续性,系统需要具备容灾能力。
所谓容灾,就是对灾难(disaster)的容忍能力,即在灾难袭来时,能够保证信息系统正常运行而采取的措施,以实现业务连续性为目标。
衡量指标衡量容灾系统的主要指标有RPO(Recovery Point Objective,灾难发生时允许丢失的数据量)、RTO(Recovery Time Objective,系统恢复的时间)、容灾半径(生产系统和容灾系统之间的距离)以及ROI(Return of Investment,容灾系统的投入产出比)。
RPO:业务系统所允许的灾难过程中的最大数据丢失量(以时间来度量),这是一个灾备系统所选用的数据复制技术有密切关系的指标,用以衡量灾备方案的数据冗余备份能力。
RTO:将信息系统从灾难造成的故障或瘫痪状态恢复到可正常运行状态,并将其支持的业务功能从灾难造成的不正常状态恢复到可接收状态所需的时间, ...
幂等操作
幂等操作
引用自 高并发的核心技术-幂等的实现方案
幂等性概念幂等(idempotent、idempotence)是一个数字与计算机学概念,常见于抽象代数中。
在编程中,一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。例如,getUsername() 和 setTrue() 函数就是幂等函数。
应用场景以下是一些常见场景:
前端重复提交选中的数据,应该后台只产生对应这个数据的一个响应结果。
我们发起一笔付款请求,应该只扣用户账户一次钱,当遇到网络重发或系统bug重发,也应该只扣一次钱。
发送消息,也应该只发一次,同样的短信发给用户,用户会哭的。
创建业务订单,一次业务请求只能创建一个,创建多个就会出大问题。
技术方案查询操作查询一次和查询多次,在数据不变的情况下,查询结果是一样的。select是天然的幂等操作。
删除操作删除操作也是幂等的,删除一次和多此删除都是把数据删除。(注意可能返回结果不一样,删除的数据不存在 ...