组件设计的作用
要回答上述的问题,首先我们要知道组件存在的意思是什么?当项目多了后,很有不少的功能是可以共用的,而这些公共的部分还是在每个项目都实现一遍的话,不仅重复,而且如果修改的话,还要每个项目都改一遍,这样太太浪费效率了,所以通常的做法是把这些公共的部分给抽取出来,放到一个独立的项目中,其他的项目来依赖这个独立的项目,大家就都有公共的功能了,而这个独立的项目也就是所谓的 “组件”。
到这里就可以得出一个结论,组件的最基本的作用就是 提高效率,减少开发成本。如果是有一个项目,那自然也就没有所谓的组件了。
既然知道了组件的基本作用是为了提高效率,减少开发成本,那么在组件的设计上就要从这个角度去思考,尽可能让人使用起来 简单和高效。
以分布式举例:如果让开发人员使用简单的话,就要思考在正常使用分布式锁是怎么样的?
1.引入 Redisson 的相关依赖
2.配置连接 Redisson 的地址和密码
3.引入 Redisson 的 API
4.加锁
5.执行具体的业务逻辑
6.解锁
7.如果加锁失败执行的处理策略(可以是直接抛出异常,也可以是自定义处理)
到这里关键点来了!
既然要让别人使用起来简单,那么就要思考上述的这几个步骤,哪些是公共的部分,或者可以让人使用起来不去做的呢?
答案就是:除了第 2 和 5 点,其余都是公共的,不需要开发人员去操作
那么针对于这点,就要接着思考,通常都是在方法执行逻辑,这些逻辑需要加锁,而方法又是在 Spring 中运行的,那么有没有什么相关的操作是在方法级别的呢?
到这里,你应该就想到了,那就是 AOP。AOP的特点就是在方法执行的前和后加上额外的功能,而这不就对应上了分布式锁中的加锁和解锁了吗!
组件的设计思路:
那么这个分布式锁的大概设计思路就应该这样:
引入 Redisson 的相关依赖(组件引入 Redisson 依赖,其他项目引入这个组件后,自然就有依赖了)
配置连接 Redisson 的地址和密码(这个没有办法,只能每个项目自己配置,每个项目连接的 Redis 有可能不同)
引入 Redisson 的 API (由组件进行操作,项目不用操心)
加锁(由组件的 AOP 进行操作,项目不用操心)
执行具体的业务逻辑(由项目自己执行)
解锁(由组件的 AOP 进行操作,项目不用操心)
如果加锁失败执行的处理策略(由组件的 AOP 进行操作,项目不用操心)
组件的设计细节:
大致的设计思路确定了后,接下来就是开始思考每个步骤的细节了
第 1、2、3、5 没什么好说的
思考:
这里就用到了 AOP 的知识了,动态代理说烂的八股文了,除了这个还有另一个注意点,就是 锁和事务的执行顺序,这个也可以和面试官提一句。
然后就是解析自定义注解的参数,使用Spel表达式解析,调用 Redisson 的api执行加锁,然后解锁,如果加锁失败,执行相关策略
到这里,分布式锁的组件其实就已经设计完了,已经可以使用了。
那这就结束了吗?星哥既然费这么大篇幅给你讲,那肯定是不能啊!
组件的优化
锁的种类
还是要回归到项目的业务上去思考,大麦项目的业务为了应对高并发的问题在锁的设计上使用了很多的技巧:
为了解决修改和查询的并发问题,需要用读写锁来解决,修改用写锁,查询用读锁,这样读写之间互斥,读读之间不互斥
如果设计提前预约功能的话,为了保证高级用户在同时抢票时,优先购票,可以使用 公平锁 来执行。
那么应该设计这种多类型的锁呢?这不就和策略模式很符合了吗?每一种锁就是一种策略啊!
锁的其他方式
除了 AOP 方式的锁,还要再思考 AOP 这种方式的弊端有什么?就是方法执行的粒度,因为 AOP 是针对整个方法执行,而有时候要锁的部分是在这个方法内执行的一部分,并不是整个方法。
所以还要设计更细控制方式的锁,还是思考刚才说的那 7 个步骤,那应该如何能在方法内执行锁的功能,又可以让使用者不用操心加锁和解锁的操作呢?那就是使用 委派模式。
public void execute(LockType lockType,TaskRun taskRun,String name,String [] keys,long waitTime) {
//获取锁的名字解析处理器
LockInfoHandle lockInfoHandle = lockInfoHandleFactory.getLockInfoHandle(LockInfoType.SERVICE_LOCK);
//拼接锁的名字 LOCK:${name}:${key}
String lockName = lockInfoHandle.simpleGetLockName(name,keys);
//获得具体的锁类型
ServiceLocker lock = serviceLockFactory.getLock(lockType);
//进行加锁
boolean result = lock.tryLock(lockName, TimeUnit.SECONDS, waitTime);
//如果加锁成功
if (result) {
try {
//执行业务逻辑
taskRun.run();
}finally {
//解锁
lock.unlock(lockName);
}
}else {
//默认处理,快速失败
LockTimeOutStrategy.FAIL.handler(lockName);
}
}其实 java 的 Runnable 接口,就是典型的这种设计,实现了 Runnable 接口,运行接口的 run() 方法,就可以异步执行了,那么锁的执行也是同理,我们也自己设计个接口,叫 TaskRun,实现了这个接口,然后在执行方法执行,就自动实现加锁和解锁的操作。
总结
到这里,分布式锁的思考和设计就差不多了,相信你对组件的设计有了更加深度的理解,那么以这个分布式锁为例来总结一波,如果更好的回答组件的设计问题:
1.组件设计的最根本作用(减少使用的重复,提供开发效率)
2.组件设计时,如何减少使用人员的操作(引入 Redisson 的配置)
3.组件设计时,如果让使用人员更方便(使用 AOP 和自定义注解,使用人员不用关心加锁和解锁)
4.组件在执行时,如何更加具有灵活性和扩展性(策略模式实现读写锁、公平锁、非公平锁)
5.组件在设计时,应该注意哪些其他的问题(使用SpringBoot自动装配机制,避免不同部门包名不同可能带来的bean扫描不到问题)