Java中的代理 (静态代理+动态代理)
Java注解机制
注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。
Java SPI机制
SPI(Service Provider Interface),是JDK内置的一种 服务提供发现机制,主要作用是解耦,提高可扩展性。
(1) SPI是什么
SPI(Service Provider Interface),是JDK内置的一种 服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用。
比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL、PostgreSQL、Oracle都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。
Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是 解耦。
SPI整体机制图
Java多态
面向对象的三大特性:封装、继承、多态。
那么多态是什么,有什么优点,又是怎么实现的呢?
(1) 什么是多态
多态是同一个行为具有多个不同表现形式或形态的能力。
比如:猫、狗 都是动物,但是在叫
这个行为上,表现不一样,猫叫起来是喵
,狗叫起来是汪
。
class Animal {
public void say() {
System.out.println("叫");
}
}
class Dog extends Animal {
public void say() {
System.out.println("汪汪");
}
}
class Cat extends Animal {
public void say() {
System.out.println("喵喵");
}
}
public static void main(String[] args) {
Animal animal = new Dog();
animal.say();
Animal animal2 = new Cat();
animal2.say();
}
分布式事务-TCC
TCC是什么
TCC 是一种补偿型事务,该模型要求应用的每个服务提供 try、confirm、cancel 三个接口,它的核心思想是通过对资源的预留(提供中间态,如账户状态、冻结金额等),尽早释放对资源的加锁,如果事务可以提交,则完成对预留资源的确认,如果事务要回滚,则释放预留的资源。
组成
TCC模型完全交由业务实现,每个子业务都需要实现Try-Confirm-Cancel三个接口,对业务侵入大,资源锁定交由业务方。
1、Try:尝试执行业务,完成所有业务检查(一致性),预留必要的业务资源(准隔离性)。
2、Confirm:确认执行业务,不再做业务检查。只使用Try阶段预留的业务资源,Confirm操作满足幂等性。
3、Cancel:取消执行业务释放Try阶段预留业务资源。
一个完整的业务活动由一个主业务服务与若干子业务服务组成:
1、主业务服务负责发起并完成整个业务活动
2、业务服务提供TCC型业务操作。
3、业务活动管理器控制业务活动的一致性,它登记业务活动中的操作,并在业务活动提交时确认所有的TCC型操作的Confirm操作,在业务活动取消时调用所有TCC型操作的Cancel操作。
成本
1、 实现TCC操作的成本
2、业务活动结束时Confirm或Cancel操作的执行成本。在Confirm和Cancel范围内的操作成功性需要框架来保证,只能一直重试保证资源被消耗或者释放。
3、业务活动日志成本
适用范围
1、强隔离性、严格一致性要求的业务活动
2、适用于执行时间较短的业务
应用案例
下单扣减资源
下单扣减库存
转账
A用户给B用户转账
TCC与2PC对比
TCC将事务提交划分成两个阶段,Try即为一阶段,Confirm 和 Cancel 是二阶段并行的两个分支,二选一。从阶段划分上非常像2PC,我们是否可以说TCC是一种2PC或者2PC变种呢?其实不可以,原因如下:
1、2PC的操作对象在于资源层,对于开发人员无感知;而TCC的操作在于业务层,具有较高开发成本。
2、2PC是一个整体的长事务,也是刚性事务;而TCC是一组的本地短事务,是柔性事务。
3、2PC的Prepare(表决阶段)进行了操作表决;而TCC的try并没有表决准备,直接兼备资源操作与准备能力
4、2PC是全局锁定资源,所有参与者阻塞 交互等待TM通知;而TCC的资源锁定在于Try操作,业务方可以灵活选择业务资源的锁定粒度。
TCC注意事项
TCC为了解决网络不可靠引起的异常情况,要求业务方在设计上要遵循三个策略:
1、允许空回滚:原因是异常发生在阶段一时,部分参与方没有收到 Try 请求从而触发整个事务的Cancel 操作;Try 失败或者没有执行 Try 操作的参与方收到 Cancel 请求时,要进行空回滚操作。
2、保持幂等性:原因是异常发生在阶段二时,比如网络超时,则会重复调用参与方的 Confirm/Cancel 方法,因此Confirm/Cancel方法必须保证幂等性。
3、防止资源悬挂:原因网络异常导致两个阶段无法保证严格的顺序执行,出现参与方侧 Try 请求比 Cancel 请求更晚到达的情况,Cancel 会执行空回滚而确保事务的正确性,但是此时 Try 方法也不可以再被执行。
TCC对业务的强侵入性,使用成本非常昂贵,虽然提供了更灵活的资源锁粒度,对标2PC拥有更高的吞吐量。
参考
[1] 我说分布式事务之TCC
[2] 分布式柔性事务的TCC方案
DDD笔记
(1) DDD是什么
领域驱动设计(Domain-Driven Design,简称DDD)
(2) 为什么使用DDD
防腐层,该层负责与外部服务提供方打交道,还负责将外部概念翻译成自己的核心领域能够理解的概念。
(3) 怎么做
(3.1) DDD战略战术
DDD的战略设计主要包括领域/子域、通用语言、限界上下文和架构风格等概念。
(3.1.1) 战略
DDD的战略设计主要包括领域/子域、通用语言、限界上下文和架构风格等概念。
领域
在日常开发中,我们通常会将一个大型的软件系统拆分成若干个子系统。
在DDD中,我们对系统的划分是基于领域的,也即是基于业务的。
统一语言
DDD引入了统一语言,把业务名词含义事先确定好,减少不必要的翻译过程,车同轨,书同文,行同伦
这也消除了业务与技术之间的重复,共同使用业务原语对话,代码就是文档,代码就是领域知识
userService.love(Jack, Rose) => Jack.love(Rose)
界限上下文
引入限界上下文的目的,不在于如何划分边界,而在于如何控制边界
限界上下文是“分而治之”架构原则的体现,我们引入它的目的其实为了控制(应对)软件的复杂度
是对领域模型、团队合作以及技术风险的控制
限界上下文和上下文映射图
限界上下文和领域具有一对一的关系。
当限界上下文作为领域模型的边界时,一方面它限制了跨限界上下文之间领域模型的关系,另一方面它作为知识语境,分离了同一个领域概念的不同视角。我将限界上下文称为战略设计的基本架构单元。
架构风格
DDD并不要求采用特定的架构风格,因为它是对架构中立的。
你可以采用传统的三层式架构,也可以采用REST架构和事件驱动架构等。
在《实现领域驱动设计》中,作者比较推崇事件驱动架构和六边形(Hexagonal)架构。
(3.1.2) 战术
战略设计为我们提供一种高层视野来审视我们的软件系统,而战术设计则将战略设计进行具体化和细节化,它主要关注的是技术层面的实施,也是对我们程序员来得最实在的地方。
对于开发人员而,战术是最实用的,比如聚合、实体、值对象、工厂、仓储、领域事件等等。
自上而下的结构化分解 + 自下而上的面向对象建模,过程化分析更好地清理了模型之间的关系,而对象模型提升代码复用性和业务语义表达能力。
行为饱满的领域对象
要创建行为饱满的领域对象并不难,我们需要转变一下思维,将领域对象当做是服务的提供方,而不是数据容器,多思考一个领域对象能够提供哪些行为,而不是数据。
实体vs值对象
实体表示那些具有生命周期并且会在其生命周期中发生改变的东西;
而值对象则表示起描述性作用的并且可以相互替换的概念。
聚合(Aggregate)
资源库(Repository)
资源库用于保存和获取聚合对象,在这一点上,资源库与DAO多少有些相似之处
DAO只是对数据库的一层很薄的封装,而资源库则更加具有领域特征。
所有的实体都可以有相应的DAO,但并不是所有的实体都有资源库,只有聚合才有相应的资源库。
参考资料
[1] 美团技术沙龙:基于领域驱动设计(DDD)的架构演进和实践
[2] 阿里技术专家详解DDD系列 第五讲:聊聊如何避免写流水账代码
[3] 会前答疑 | 来自听众的16个DDD问题,美团技术团队是这样回答的
[1] DDD之战略战术设计
[2] DDD 领域驱动设计落地实践系列:战略设计和战术设计
[3] 领域驱动设计在马蜂窝优惠中心重构中的实践
[4] 如何分辨应用服务与领域服务
[5] 领域驱动建模与面向对象建模的差异
[6] 端口和适配器架构——DDD好帮手
[7] 有了MVC,为什么还要DDD?
交易流程编排
1.代码逻辑 以及 层次做有效拆分
2.交易流程相互隔离;
3.新人对代码理解更容易;
原有代码结构逻辑基本为瀑布式编程,service从头写到尾,没有做很好的抽象;
很多if else,改一个业务场景,容易对另一种业务场景产生影响。
Redis使用lua脚本
使用Lua脚本的好处:
减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络时延。
原子操作。redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。因此在编写脚本的过程中无需担心会出现竞态条件,无需使用事务。
复用。客户端发送的脚本会永久存在redis中,这样,其他客户端可以复用这一脚本而不需要使用代码完成相同的逻辑。
EVAL script numkeys key [key ...] arg [arg ...]
说明:script
是第一个参数,为Lua 5.1脚本。该脚本不需要定义Lua函数(也不应该)。numkeys
指定后续参数有几个key。key [key ...]
,是要操作的键,可以指定多个,在lua脚本中通过KEYS[1]
, KEYS[2]
获取arg [arg ...]
,参数,在lua脚本中通过ARGV[1]
, ARGV[2]
获取。
127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 value1 value2
1) "key1"
2) "key2"
3) "value1"
4) "value2"
127.0.0.1:6379>
127.0.0.1:6379> eval "redis.call('SET', KEYS[1], ARGV[1]);redis.call('EXPIRE', KEYS[1], ARGV[2]); return 1;" 1 key1 value1 60
(integer) 1
127.0.0.1:6379> get key1
"value1"
127.0.0.1:6379> ttl key1
(integer) 53
127.0.0.1:6379>
这个脚本相当于执行了2条命令 SET key1 value1
EXPIRE key1 60
参考资料
[1] Redis使用lua脚本
[2] EVAL script numkeys key arg
[3] EVAL
[4] Scripting with Lua