1、Dubbo SPI机制
1.1、Dubbo具有良好拓展性的原因
- 1、整个框架中针对不同的场景,恰到好处地使用了各种设计模式
- 2、基于Dubbo SPI 加载机制,让整个框架的接口和具体实现完全解耦合
- Dubbo SPI 扩展
- 与Java SPI类似,需要在META-INF/dubbo/下放置对应的SPI配置文件,文件名称需要命名为接口的全路径名。配置文件的内容为 key=扩展点实现类全路径名,如果有多个实现类则需要使用换行符分割
- 在Dubbo启用时,会默认扫描这三个目录下的配置文件:META-INF/services/、META-INF/dubbo/、META-INF/dubbo/internal/
- 1、dubbo filter详解
- todo
1.2、Dubbo SPI和Java SPI的区别?
Dubbo的扩展点加载从JDK标准SPI(Service provider Interface)扩展点发现机制加强而来。
改进了JDK标准SPI的以下问题:
1、JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但是没用上也加载,会很浪费资源。(我的代码中初始化配置项也存在这样的问题)
2、如果扩展点加载失败,连扩展点的名称都拿不到了
3、增加了对扩展点IOC和AOP的支持,一个扩展点可以直接setter注入其他扩展点。
1.3、Dubbo SPI可认为是IOC实现吗?
todo
2、SPI机制在商品中心的应用
2.1、TMF校验器
详情见这个项目
2.2、在AfterImage应用中的使用
2.2.1、整体流程图如下所述
2.2.2、业务逻辑
残影系统,使用切面实现的
- 注解:Afterimage
- 包含的字段有
- captorConfig 本次使用的captor(捕获)配置
- 该类包含两字段 Captor,dubbo接口或其他 config dubbo接口详情,具体结构见下文
- expireMills 超时时间,单位是毫秒,小于1:不过期;大于1:过期;不配置:使用captor侧配置的过期时间
- 使用场景:缓存dubbo调用结果,用于优化在执行上下文重复同一dubbo调用
- 当同个线程执行链路,多次使用此注解时,只有最外层的注解生效
- 加上此注解的方法,耗时调用将被暂存起来,直到退出方法
- 注意事项:
- 必须使用在能被spring AOP代理的方法上
- 如何使用:
- 引入依赖
<dependency> <groupId>cn.gov.zcy</groupId> <artifactId>afterimage-integration-zcy</artifactId> <version>2.0.0-SNAPSHOT</version> </dependency>
- 如果是spring,请使用注解
@EnableAfterimage
开启, 然后在需要的方法(可被spring aop切面)加上注解@Afterimage开启。
- eg: 1、启动时开启Afterimage,如果是spring boot 环境,会自动配置开启,
- 如果spring-boot,不想启动时自动开启可配置afterimage.spring.bootstrap.enabled=false
@EnableAfterimage public class Application{ }
- 开启后,使用在需要优化的方法(可被spring aop切面),使被注解的方法开启afterimage
- 如果想在运行时关闭,请配置:afterimage.spring.enabled=false
public class ItemServiceImpl{ @Afterimage public Item find(){ } }
- 执行流程
- 1、afterimage-config 以扩展配置读取、监听的扩展
- 第一块:apollo配置获取,监听事件变更
- 第二块:spring配置 ,将残影实例自动配置到Spring中
- config 扩展
- 实现此cn.gov.zcy.afterimage.core.config.Config接口,可用于配置读取的扩展。 还需要实现ObjectProvider用来生成Config实例
- 接口:Config
- 作用:统计每个dubbo方法的缓存时间
- 定义的方法:default get(key,defaultValue)、get(key)、default ddListener(key,configListenr) 如果对应key发生变化,调用listener、default removeListener(configListenr) 移除监听器、default removeListener(key, configListenr) 移除监听器
- 实现类1
- ApolloConfig(apollo配置)
- 继承apollo的ConfigChangeListener接口实现了ApolloListener,实现其onChange事件
- onChange方法里面,遍历所有改变的key,如果等于当前key,监听其改变
- 补充了addListener方法和removeListener方法
- 使用了写时复制ArraySet,因为配置读取的场景读多写少
- 组合apolloConfig,并实现了Config的get方法,底层调用apolloConfig的getProperty方法
- 实现了addListener(key,configListenr)方法,先创建监听,然后添加监听,如果apolloListener监听的数量等于1,底层调用apolloConfig的addChangeListener方法
- 实现类removeListener(configListenr)方法,apolloListener移除监听,检查ApolloListener,为空则移除
- 实现类removeListener(key, configListenr)方法,获取apolloListener监听器,如果为空,返回,否则,移除监听器,检查ApolloListener,为空则移除
- 实现类2
- SpringConfig(Spring配置)
- 先实例化自身,然后获取系统参数
- 实现了get(key)方法,如果系统参数为空,抛错,否则,有key获取系统参数
- 实现类3
- CompositeConfig(组合配置)
- 实现了get(String key)方法,遍历每一个configList,查询config,如果能找到值,返回,否则,返回null
- 实现了addListener(key, ConfigListener),给configList添加监听器
- 实现了removeListener(ConfigListener)方法, 给configList移除监听器
- 实现了removeListener(key, ConfigListener)方法, 给configList移除监听器
- 实现类4
- SystemConfig (系统配置)
- 实现了get(key)方法,获取系统的值
- 读取配置的顺序
- apollo配置
- 在resources资源文件夹下的
META-INF/afterimage/ConfigProvider.def
中配置, 格式如:order,name,providerClass,选择时,order小的优先,可以以#开头在上一行添加注释
100,ApolloConfig,cn.gov.zcy.afterimage.config.apollo.ApolloConfigProvider
- Apollo环境可引入afterimage-config-apollo来支持apollo的配置读取、以及动态配置监听
<dependency> <groupId>cn.gov.zcy</groupId> <artifactId>afterimage-config-apollo</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
- Spring配置
- 在resources资源文件夹下的
META-INF/afterimage/ConfigProvider.def
中配置
0,SpringConfig,cn.gov.zcy.afterimage.config.spring.SpringConfigProvider
- 系统配置
- 在resources资源文件夹下的
META-INF/afterimage/ConfigProvider.def
中配置
1000,SystemConfig,cn.gov.zcy.afterimage.core.config.SystemConfigProvider
- 默认配置为spring优先、apollo次之、System.getProperty最低
- 配置的读取将按照spi的order配置,顺序从小到大读取,取到第一个不为null则返回
- 也可以再启动时,设置-Dafterimage.config.order=SpringConfig,ApolloConfig 指定顺序,未指定的配置扩展将按默认顺序对应追加在后面
- ObjectProvider 接口
- 提供的方法:get()
- 实现类
- 实现类1、ApolloConfigProvider
- NameSpace:afterimage.apollo.namespace
- 实现了get()方法 --》从系统参数中获取名称空间,如果名称空间非空,获取apollo中当前名称空间的配置,否则:获取apollo “application”名称空间的配置项
- 实现类2:SpringConfigProvider
- 实现了get()方法 --》从SpringConfig获取实例
- 实现类3:SystemConfigProvider
- 实现了get()方法 --》获取SystemConfig实例
- 实现类4:NopAlbumProvider
- 实现了get()方法 --》获取NopAlbum实例
- 实现类5:SoftReferenceAlbumProvider
- 实现了get()方法 --》获取软引用类型ReferenceAlbum实例
- 实现类6:StrongReferenceAlbumProvider
- 实现了get()方法 --》获取强引用类型ReferenceAlbum实例
- 实现类7:WeakReferenceAlbumProvider
- 实现了get()方法 --》获取虚引用类型ReferenceAlbum实例
- 2、afterimage-album 扩展调用结果的存储
- 目前为空
- 实现此cn.gov.zcy.afterimage.core.album.Album接口,可用于结果存储的扩展。 还需要实现ObjectProvider用来生成Album实例
- 接口:Album
- 作用:存储dubbo接口调用结果
- 定义的方法
- init() 初始化,
- destory() 销毁,
- write(contextId,key,value,超期时间)存入数据,获取残影系统上下文数据,如果值为空,返回;如果上下文为空,抛错,获取上下文中的超期时间,如果这个时间为空,取入参中传递过来的超期时间,写入;
- dowrite(contextId,key,value,超期时间)方法, 为null或小于1,将不过期
- read(ContextId contextId, String key) 读取
- clear(ContextId contextId, String key) 清理
- clear(ContextId contextId) 清空整个context
- 实现类
- 实现类1:抽象类 AbstractAlbum
- 实现了dowrite(contextId,key,value,超期时间)方法,获取-当不存在时,创建新的上下文,组装ValueWrapper(value,超期时间,系统时间)
- 实现了read(ContextId contextId, String key),获取上下文,如果为空,return,否则 --》获取valueWrapper ,如果没有超期(时间为空或者小于1ms)–》返回valueWrapper中值 --》如果有配置超期时间,获取当前时间,减去valueWrapper中的系统时间,比较valueWrapper中的超期时间,如果大于 --》移除上下文,否则返回valueWrapper中的值
- 实现了clear(ContextId contextId, String key), 获取上下文,移除对应的key
- 提供了两抽象类
- 1、getOrCreate(contextId) 获取或创建上下文
- 2、get(ContextId contextId) 获取上下文
- 实现类:ReferenceAlbum
- Map类型 字段album,使用了ValueReference(有三个实现类,后面讲解)
- 字段strength,枚举类型(STRONG强引用 SOFT软引用 -在内存不足时回收,每MB空余可保留1s,WEAK弱引用-GC就被回收 )
- 提供了一个抽象方法referenceValue
- 实现了三个静态内部类
- StrongValueReference实现ValueReference接口,实现了get方法
- SoftValueReference继承了SoftReference 实现了ValueReference
- WeakValueReference继承了WeakReference 实现了ValueReference
- 实现了方法init()
- 实现了方法destory() 调用 album的清理
- 实现了方法clear() 调用 album的remove
- 实现了父类的抽象方法getOrCreate(contextId) 由context获取album Map的value,如果为空,说明需要初始化,给album初始化一个空的map,否则,返回map
- 实现了父类的抽象方法get(contextId) 由context获取album Map的value,如果为空,返回空,否则,返回map
- 实现类2:NopAlbum
- 实现了这几个方法,都是空实现
- init()方法初始化
- destory() 销毁
- doWrite(ContextId contextId, String key, Object value, Integer expireTimeMills) 存入
- read(ContextId contextId, String key) 返回null
- clear(ContextId contextId, String key) 清除第一个
- clear(ContextId contextId) 清空
- 实现类3:ReferenceAlbum 使用了引用实现,方便帮助GC 见上文
- Album配置
- 在resources资源文件夹下的
META-INF/afterimage/AlbumProvider.def
中配置,格式如:order,name,providerClass,选择时,order小的优先,可以以#开头在上一行添加注释
0,SoftReferenceAlbum,cn.gov.zcy.afterimage.core.album.reference.SoftReferenceAlbumProvider 100,WeakReferenceAlbum,cn.gov.zcy.afterimage.core.album.reference.WeakReferenceAlbumProvider 1000,StrongReferenceAlbum,cn.gov.zcy.afterimage.core.album.reference.StrongReferenceAlbumProvider 10000,NopAlbum,cn.gov.zcy.afterimage.core.album.NopAlbumProvider
- 默认实现:
- 0,SoftReferenceAlbum,cn.gov.zcy.afterimage.core.album.reference.SoftReferenceAlbumProvider
- 软引用map实现,help gc
- 100,WeakReferenceAlbum,cn.gov.zcy.afterimage.core.album.reference.WeakReferenceAlbumProvider
- 弱引用map实现,help gc
- 1000,DefaultAlbum,cn.gov.zcy.afterimage.core.album.reference.StrongReferenceAlbumProvider
- map实现
- 10000,NopAlbum,cn.gov.zcy.afterimage.core.album.NopAlbumProvider
- 空实现
- 只能使用一个具体实现,默认取order最小的那一个
- 3、afterimage-captor 用来按需捕获调用结果进行存储
- 背景知识:dubbo SPI 见上文dubbo filter的使用
- 配置项为:afterimage.captor.dubbo.config
- 配置格式:接口名1#方法名1=过期时间毫秒&方法名2=过期时间毫秒,接口名2#方法名1=过期时间&方法名2=过期时间
- 可用*匹配所有方法名;过期时间单位为毫秒,当未配置或者小于1时,代表不过期,特别注意:当被优化方法执行完毕时,执行期间存储的调用将自动失效。
- 配置eg: com.A#a=1&b&c=2,com.B#*=1
- 目前不支持重载方法不同配置
- AfterimageCaptorFilter实现了dubbo filter,使用代理设计模式,在调用dubbo接口前,先查询album是否有返回结果,在dubbo接口调用之后,将调用id和rpc结果放入到album中
- 执行时机:消费方
- **执行过程简述:**先获取dubbo配置,查询db(album)中是否存在该dubbo接口执行结果,有结果,直接返回该数据,否则,执行远程rpc调用,然后将方法入参和调用结果缓存到db(album)中
- 类似于查询redis,查不到就查询db,并将结果放到redis中
- 添加Activate注解,标识其为被代理对象,其注解含义如下
- group :所属组:消费者,只能在消费端使用
- order: 多个filter的执行顺序(越小越早)
- 文件名:com.alibaba.dubbo.rpc.Filter 对于Filter的接口
- 执行过程详述:在resources目录下新建META-INF文件夹,然后建立子文件夹dubbo,最后新建文件com.alibaba.dubbo.rpc.Filter --》key为过滤器的实例名,value为过滤器的全类名 --》在dubbo接口执行之前,会进入拦截器里面 --》
- 在拦截器里面 --》先获取CompositeConfig配置,如果配置项不存在或者开关未开启,提前return --》否则,获取配置项,优先上下文配置,获取dubbo接口全路径,不存在的话,获取Config中的数据 --》读取配置项,入参为原始配置 + dubbo执行信息,在工具类里面获取dubboConfig --》获取超期时间 --》如果获取的配置为空,执行invoke方法调用远程rpc,否则获取上下文,然后生成调用id --》获取应用程序信息,group信息,版本信息,接口名和方法名,入参类型信息(用逗号链接),入参信息(用逗号链接) --》使用 + 将上述全部信息连接后返回 --》获取db数据(album),由上下文+dubbo信息查询album中是否存在数据,如果为空,返回null,否则 --》判断是否有设置超期时间,如果没有设置,返回album对应的值,–》如果已经超期了,从album中删除数据,否则,返回album对应的值 --》有值,说明近期有调用,AsyncRpcResult返回该值,否则,没有结果
- 执行rpc调用,拿到返回结果,如果返回结果没有异常,将结果写入到album中,入参为上下文id,dubbo组装参数后的id,rpc调用的结果,超期时间 --》album如果没有key,初始化,然后以dubbo组装参数后的id为key,rpc结果,超期时间,当前时间这三个字段组装的对象为value,存入map中
- 如果这个过程中发生了异常,分类讨论,如果执行了远程rpc,并且结果非空,返回执行结果,结果为空,抛出异常 --》没有执行远程rpc,此时调用远程rpc接口,并返回
- Action:这个兜底逻辑中,执行远程rpc后,没有把执行结构放入到album中
- 4、afterimage-boot 启动afterimage
- 实现了切面逻辑 ,源码分析如下
- 注入切面的逻辑
- 5、afterimage-core 核心类,
- 定义了spi拓展接口:
- cn.gov.zcy.afterimage.core.config.Config:可用于配置读取、监听的扩展
- cn.gov.zcy.afterimage.core.album.Album:用于存储调用结果
- 6、afterimage-boot-dubbo模块
- AfterimageBootFilter实现了dubbo filter,使用代理设计模式,在调用dubbo接口前,先查询album是否有返回结果,在dubbo接口调用之后,将调用id和rpc结果放入到album中
- 执行时机:生产方
- 执行过程简述:先获取dubbo配置,查询Config中配置是否开启,没开启,执行远程rpc,否则,查询配置项,有方法入参查询超期时间,配置超期时间,执行rpc调用,最后清理db(album) 数据
- 可以类似于redis中:缓存时间到期了,清理redis缓存
- Action:这个操作不优雅,它是在每次调用dubbo接口时,判断当前执行的方法有没有过期,可以模仿redis删除key的实现思路
- 添加Activate注解,标识其为被代理对象,其注解含义如下
- group :所属组:提供者,只能在服务提供端使用
- 前提:需要引入对应captor才会优化相应的调用
- 配置项为:afterimage.boot.dubbo.config
- 配置格式:接口名1#方法名1=过期时间毫秒&方法名2=过期时间毫秒,接口名2#方法名1&方法名2=过期时间
- 可用 * 匹配所有方法名;过期时间单位为毫秒,当未配置或者小于1时,代表不过期,特别注意:当被优化方法执行完毕时,执行期间存储的调用将自动失效。
- 配置eg: com.A#a=1&b&c=2,com.B#*=1
- 目前不支持重载方法不同配置
- 在resources目录下新建META-INF文件夹,然后建立子文件夹dubbo,最后新建文件com.alibaba.dubbo.rpc.Filter --》key为过滤器的实例名,value为过滤器的全类名
- 在服务提供端接口返回之前,会进入拦截器里面 --》首先获取CompositeConfig配置,如果配置项不存在或者开关未开启,执行dubbo接口invoke业务逻辑 --》否则,获取配置项详情,原始配置 格式如:接口名#方法名=过期时间&方法名=过期时间,接口名#方法名=过期时间&方法名=过期时间 --》获取配置详情,如果为空,跳过,否则 --》去除全部空格,接口的配置用’,'隔开,接口名和方法使用“#”分割,如果不合规范,跳过 --》如果当前的接口名与入参接口名相符,继续匹配方法的参数,使用“&”分割,然后使用 “=” 分割入参的key和value,如果key value不是一个pair,跳过,否则,获取入参的key --》如果key为通配符“*” 或者符合入参方法名,执行DubboConfig方法 --》设置超期时间,目前只有expireMills一个配置项,返回DubboConfig —》如果获取到的DubboConfig对象为null,执行dubbo业务逻辑并返回,否则 --》调用startAfterimageManage,底层为AfterimageManage的start1方法,入参为超期时间 --》然后执行dubbo invoke执行业务方法 --》最后,调用AfterimageManage的end方法清理数据
- Action:这个过滤器如果执行报错,不需要补偿吗?
- 残影系统使用的设计模式:
- 1、代理模式 在dubbo invoke之前和之后执行某些操作
- 2、filter模式:拦截器设计模式
- 源码分析:
- 入口:切面AfterimageAspectj
- 在构造器里面初始化,如果已经初始化,提前return,否则,调用AfterimageManage初始化数据 --》对ConfigLoader进行初始化 --》对AlbumLoader进行初始化 --》环切操作,先判断ConfigLoader中“afterimage.spring.enabled”配置项是否打开,如果没有打开,直接执行业务逻辑,否则 --》调用start0开始执行切面逻辑,然后执行业务逻辑,最后执行残影的清理操作(end方法)
- AfterimageManage --》初始化init,如果已经初始化,return,否则 --》初始化项目,
- ConfigLoader
- order_key:afterimage.config.order
- PATH = “META-INF/afterimage/ConfigProvider.def” 使用系统配置
- order:1000
- name:SystemConfig
- providerClassName:cn.gov.zcy.afterimage.core.config.SystemConfigProvider
- CompositeConfig CONFIG
- 初始化,如果CONFIG非空,说明已经被初始化了,提前return --》 由固定的PATH查询Spi order_key拓展信息 --》加载资源,由path获取类加载器中的urls,–》如果加载出的资源为空,return,否则 --》解析路径,如果路径长度不等于3,抛出异常,否则 --》定义SpiModel,并组装顺序、名称、类名 --》按顺序排序 --》定义组合配置CompositeConfig,将SpiModel转换为CompositeConfig --》按key为name,value为spiModel构建为map --》指定顺序排序(afterimage–config–order) --》循环遍历SpiModel,通过类名获取类实例,然后添加到配置中
- AlbumLoader
- ALBUM_KEY = “afterimage.album”
- PATH = “META-INF/afterimage/AlbumProvider.def”
- ALBUM
- List < SpiModel> SPI_MODELS
- order :0
- name:SoftReferenceAlbum
- providerClassName:cn.gov.zcy.afterimage.core.album.reference.SoftReferenceAlbumProvider
- 初始化 --》如果CONFIG非空,说明已经被初始化了,提前return --》 由固定的PATH查询Spi拓展信息 --》加载资源,由path获取类加载器中的urls,–》如果加载出的资源为空,return,否则 --》解析路径,如果路径长度不等于3,抛出异常,否则 --》定义SpiModel,并组装顺序、名称、类名 --》按顺序排序 --》按key为name,value为spiModel构建为map --》指定顺序排序(afterimage–config–order) --》循环遍历SpiModel,通过类名获取类实例,然后添加到配置中
- 加载:–》获取SPI_MODELS的第一条数据, --》如果指定了CompositeConfig,使用指定的,否则 --》调用ConfigLoader初始化接口,返回CONFIG --》从配置项中读取“afterimage.album”,使用指定的spiModel --》获取单签拓展点实例信息 --》添加钩子函数,在程序结束时自动销毁album
- 强制清除方法 forceClear --》获取残影上下文,如果为空,提前return,否则 --》由AlbumLoader获取ALBUM,做清理工作 --》然后由残影上下文中移除ThreadLocal数据
- start0
- 执行时机
- 当进入切面后
- 业务逻辑:
- 当某线程连续调用start时,只有第一次的配置生效 --》入参为残影注解,如果没有配置过期时间,返回null --》入参中的captor非空,循环遍历captorConfig --》组装captor(目前仅为dubbo接口),config(dubbo接口的全路径)–》组装到配置项AfterimageConfig中 --》然后调用start(afterimageConfig)方法执行后续逻辑 --》
- 当嵌套多次start时,计数器会加1,调用end是减1,当为0时,代表上下文安全结束 --》判断是否是新的上下文,如果通过Local能获取上下文,返回该数据,否则 --》 创建一个空的上下文到Local中,返回Local --》计数器自增,如果计数器值不为1,返回false,否则,将入参中的配置放入上下文中,然后返回true
- start1
- 执行时机
- 对于每一个执行的dubbo,都会进入拦截器里面去,然后判断有没有匹配上,有配置上,重新计算超期时间
- 业务逻辑
- 当某线程连续调用start时,只有第一次的配置生效 --》过期时间毫秒-为null或小于1,将不过期 --》获取Local里面的上下文,如果数据非空,返回上下文,否则 --》初始化Local后返回 --》计数器自增,如果计数器不为1,返回false,否则,组装配置后,返回true
- Action:计数器起的作用是啥? 返回的true,false是啥意思?
- 计数器是为空防止一次请求中,进入切面里面多次
- Action:提供的两filter方法会拦截全部的dubbo接口,这样做不会影响性能吗?
- start(expireMills, captorConfigList) 当某线程连续调用start时,只有第一次的配置生效
- end 在结束后必须调用end --》获取Local中的上下文,如果上下文为空,抛错异常,否则 --》计数器自减 --》如果数量不为0,返回false,标识在其它上下文之中,不用清理数据,否则 --》返回true,获取Album,做清理操作,清理上下文信息,return
- SystemConfigProvider 系统配置提供方
- SoftReferenceAlbumProvider
3、SPI总结
1、利用Dubbo SPI机制提供的良好拓展性
- 1、整个框架中针对不同的场景,恰到好处地使用了各种设计模式
- 2、基于Dubbo SPI 加载机制,让整个框架的接口和具体实现完全解耦合
2、缓存思想的运用
- 先获取dubbo配置,查询db(album)中是否存在该dubbo接口执行结果,有结果,直接返回该数据,否则,执行远程rpc调用,然后将方法入参和调用结果缓存到db(album)中
- 先获取dubbo配置,查询Config中配置是否开启,没开启,执行远程rpc,否则,查询配置项,有方法入参查询超期时间,配置超期时间,执行rpc调用,最后清理db(album) 数据