天天看点

阿里通信基础技术框架介绍

在阿里通信必零(计费重构)项目中,在完成项目建设的同时,我们把日常常用的一些工具类进行了抽取沉淀,形成了基础库。我觉得有些东西挺好用的,写篇文章介绍一下。 二方库:

<code>&lt;dependency&gt; &lt;groupId&gt;com.alicom&lt;/groupId&gt; &lt;artifactId&gt;alicom-frame&lt;/artifactId&gt; &lt;version&gt;1.0.7&lt;/version&gt; &lt;/dependency&gt;</code>

主要内容包括:

1、 DAO处理,灵活方便的分库分表策略,可以支持自由自定义分库分表规则

2、 通过注解,实现方法级缓存,通过dimaond配置控制缓存更新

3、 利用tair实现分布式锁

4、 jmx支持,通过注解方便的jmx支持

5、 通信客户端,包括:http/https,ftp/ftps,sftp,oss

6、 其它一些工具类,主要包括:

a、FrameDiamondUtil:Diamond配置读取,自动监听diamond的配置变更,保存在内部的CACHE,不需要调用者写Listener

b、FrameBeanFactory:便捷的获取spring bean

c、FrameTimeUtil:便捷的时间处理函数,线程安全,基于joda封装的时间处理类,性能快

d、JacksonUtil:使用jackson进行json处理;EncryptUtil:3des加减密;Md5Util:md5摘要生成

接下去就其中几块内容展开说明一下。

a、首先是datasource配置,这个是必须得,也少不了。我们一般都是用TDDL的Group层,在这配置对应数据源的appName和dbGroupKey。有几个数据源就配置几个bean

阿里通信基础技术框架介绍

b、编写我们的sqlmap文件,xxxxx1_sqlmap.xml,xxxxx2_sqlmap.xml,一个表一个sqlmap,在sqlMapConfig中,对sqlmap文件进行引用

阿里通信基础技术框架介绍

c、配置sqlMap操作类sqlMapClient或sqlMapClientTemplate,需要配置sqlMapConfig和DataSources属性,一个DataSource需要配一个sqlMapClient

阿里通信基础技术框架介绍

d、编写DAO类,根据不同的操作,sqlMapClient或sqlMapClientTemplate执行对应的sqlmap语句;

如果涉及分库分表,就需要做些加工。分表意味着sqlmap中对应的表名是由外部参数传递构建的,我们一般会写TableRoute之类的东西,根据规则拼接表名;分库本质上就使用不同DataSource的sqlMapClient.,我们一般会写DbRoute之类的东西,根据规则定位sqlMapClient。

当然涉及事务,还需要增加一些事务相关的配置处理。传统的方式,问题主要有:

• 配置多而繁琐 

• 分库分表规则的支持上不灵活,分库分表对DAO代码的侵入很大

a、配置文件

•persistence.xml:数据源datasource相关的配置还是少不了的,有关sequence,事务的配置原来该怎么样还是怎么样。

但不再需要手动配置sqlMapClient或SqlMapClientTemplate,只需要设置DAO路径和数据源映射关系,对于分库只要加通配DBNAME就可以了。

阿里通信基础技术框架介绍

•sql-map-config.xml和sql-map-null.xml文件(基本不用改,所有项目都一样)

一个是 sqlMapConfig全局配置,一个是只有schema的文件,用于内部构造作为模板。

b、命名和目录约定

所有DAO文件在dao目录下,所有DO文件在dataobject目录下,目录平行;

DAO实现类以xxxx DAOImpl命名,对应的DO类是xxxxDO。

为什么有这个约定,后续会有解释。

c、DAO实现类继承BaseDAO,通过注解指定sqlMap文件,自动注册为bean。

阿里通信基础技术框架介绍

d、DO实现继承BaseDO,通过@DbDefine 指定分表规则,通过@TableDefine指定分表规则。

我们一般使用代码生成工具构建DAO/DO、SQLMAP代码,这些都全自动化了。

e、对于分库分表特别介绍一下

•sqlmap文件简单调整,需要将表名变更为_TABLE_NAME_ ,如:

阿里通信基础技术框架介绍

•在DO类上加注解@TableDefine

阿里通信基础技术框架介绍

[]里的内容可以约定哪个字段作为分表key,如何进行key转换。如上面就是 以gmt_create作为分表键,分表转换规则见monthTableConvert类

另外可以用多个[]号,指定更为复杂的分表规则,如: @TableDefine("zw_add_month_[monthTableConvertByBillMonth]_[acctIdTableConvert]")

•如果不涉及分库,通过前面说的DAO路径和DataSource映射就可以获取最终DS了。

•如果涉及分库,根据映射规则拿到的是带通配符DBNAME的DS名。需要在DO类上加注解@DbDefine,用法同@ TableDefine,通过注解指定分库key和分库转换方法类,最终替换通配符,获取真正DS.

•DAO实现方法,可以通过DO、Query、Map对象向最终的sqlmap传递参数,如果涉及分库分表,对象中一定要包含分表key字段。

•AbstractConvert

所有分库分表规则的抽象类,具体分库分表规则可以通过实现这个类来提供。

如:按ID取模分64个表、按ID取模分4个库、按时间戳按月分表、按时间戳按日分表、按时间戳按年分表等等。

提供了两个方法需要实现:

String getDefaultKey() 如果对外注解没指定分库或分表key,就缺省用这个key。

String convert(String key,Object) 具体如何转换分库或分表逻辑。

•BaseDO

主要提供了set_TABLE_NAME_(),供分表使用。

•BaseDAO

所有DAO操作基类 

初始化的时候:

根据@SQLMap注解,获取sqlmap路径;

找到同级的DO(前面约定的DAO和DO命名规范),根据是否有@DbDefine注解,判断是否分库和分库规则,根据是否有@TableDefine注解,判断是否分表和分表规则;

sql操作的时候,根据getTemplate方法,获取SqlMapClientTemplate。

主要操作包括:如果非分库,直接根据包路径映射获取ds名,否则根据分库规则替换通配符,获取ds,根据ds构建数据源(第一次是构建,后续会从Map中取);如果是分表的,根据分表规则获取最终表名,最终通过操作对象传递到sqlmap。

Diamond真是个好东西,阿里内部很多动态配置的业务场景都是由Diamond实现的。有关Diamond的介绍,大家可以自己搜。

Diamond使用主要两个方法

•Diamond.getConfig(dataId,group,timeoutMs) 

•Diamond. addListener(dataId,group,new ManagerListenerAdapter()

{ public void receiveConfigInfo(String configInfo) { …}

为此我们一般都需要在使用Diamond的类里加init方法,调用getConfig获取相应配置,并且赋值给我们的业务变量;同时增加一个监听器,实现receiveConfigInfo方法,当diamond配置变更的时候替换我们得业务变量。

我们觉得这样使用,每个用到Diamond的类都需要添加类似代码,似乎不太优雅。能不能封装一个工具类,直接通过静态方法就可以获取Diamond配置值,而有关Diamond推送变更相关代码也隐式的做到,对调用者透明呢?

FrameDiamondUtil就是这样一个工具类。提供了

public static String getConfig(String dataId,String group,long timeoutMs)静态方法,外部使用者只需要调用这个静态方法简单赋值,其它都不用管。

如果希望返回的不是原生配置的String内容 ,而是自己做些转换,可以调用

public static Object getConfig(String dataId,String group,long timeoutMs,Convert convert),自己实现Convert类就可以。

其实也挺简单的,内置一个Map

调用getConfig的时候,以group^dataId作key,从Map中取。如果是第一次调用,调用Diamond.getConfig获取,以及注册监听器。

阿里通信基础技术框架介绍

在监听器代码中,对Map内容进行替换

阿里通信基础技术框架介绍

利用AOP,将缓存实现和业务逻辑分离,相信大家都有所耳闻。比如采用SpringCache框架,通过注解就能实现方法级缓存。

我们这套框架就是基于spring Cache做了些扩展,配合Diamond可以定时更新缓存而无需重启。

通过spring的注解实现方法级缓存 ;支持缓存到本地JVM或者Tair中,本地缓存遵循LRU策略;通过diamond对缓存生命周期进行管理

使用举例:

a、声明注解

阿里通信基础技术框架介绍

b、配置文件,指定需要管理的Diamond及cacheManager配置

阿里通信基础技术框架介绍

c、只要在方法上加注解,就可以实现缓存功能

阿里通信基础技术框架介绍

d、dimaond管理缓存生命周期

当时间大于配置内容,就刷新缓存(其实是替换缓存的key),但外部感觉就像缓存刷新

阿里通信基础技术框架介绍

a、整体上依赖spring Cache

b、实现了spring Cache的Cache接口

本地缓存使用google的concurrentlinkedhashmap,内存超出的时候使用LRU;

通过配置确定是否使用tair,如果配置了enableTair,本地读取不到的时候读tair,put的时候同时put本地和tair。

c、对SpringCache的KeyGenerator进行了特殊实现

一般大家用SpringCache的时候都是采用DefaultKeyGenerator,通过方法名和参数名构建Key.(当然也有通过表达式自定义key的)。

我们对KeyGenerator做了特殊实现,key的组成除了方法名和参数名外,将从Diamond相关配置中读取的时间戳作为key组成部分的前缀,如果Diamond内容发生了变更,相应的key就变化,这个时候根据改key就取不到缓存,会触发真正的方法调用,等到第二次调用的时候缓存key没有变化,那就能读到缓存。

基于tair的原子操作实现的分布式锁。

a、实现TairLockObject

举例CreditAcctIdLock,确定lock key的前缀KEY_PREFIX,确定配置项CONFIG_PREFIX;构造函数中传入acctId

阿里通信基础技术框架介绍

b、使用锁

构建对象 AcctIdLock lock = new AcctIdLock(6536182776198L) ;

获取锁 lock.acquire();

释放锁 lock.release()。

相对比较简单,依赖tair的原子操作。

TairLockObject决定了key的组成,决定了如何获取tair配置(如:数据库或配置文件),此外还包括获取锁的一些策略:如获取锁失败的重试次数和等待时间。

加锁:基于 key做 tairManager.incr

释放锁:基于key做tairManager.delete

唠唠叨叨讲了一些,该收场了。个人觉得在项目实施的过程中,除了完成特定业务目标外,还能逐步沉淀出一些技术框架,供后续工程使用,避免重复造轮子,还是挺有意义的。