系统发展到一定程度就会面临分库分表的问题,我们要考虑的是怎么让系统平滑的从老的表迁移到新的表。通常新表的数据可以通过binlog同步的方式同步老表的数据,在某一个时间点通过开关的方式让读写老表的操作切换到新表。这里就涉及到如何方便的切换新老操作。
加入了新表,必须要有新表的DAO,现在的问题是如何根据开关调用新老DAO来执行操作。先说下目标:
能够方便的实现开关切换。
对现有代码侵入尽量的少。
擦屁股方便,就是稳定迁移之后要能顺利的清理之前的代码。
方案一:在每个调用到的地方利用if判断来决定调用哪个DAO的方法。这个肯定不现实,涉及的代码太多了。而且这样遗留问题比较多,迁移之后需要清理很多垃圾代码。
方案二:写一个代理类,实现DAO接口,根据开关分别调用新DAO或老DAO的相应方法。基本代码量可以接受,对系统侵入性也比较小,原来的代码可以不用动,spring bean里将代理DAO的id设置成原来的DAO id就可以接入系统。迁移之后只要把老DAO和代理DAO的配置删除,将新DAO的id设置为原来的DAO id就可以了。只有一个问题就是代理DAO写的有点恶心,如果接口里的方法不多的话还好,方法很多的话还是需要写很多东西的。而且代码重复比较多各个方法其实就是判断下开关然后调用相应DAO的方法。
这个时候想到了动态代理,这样的话,DAO代理类就可以写的比较简单,基本没有重复的代码。接下来的问题就是怎么让动态代理接入spring 的context。这个可以通过FactoryBean来封装。
首先,实现动态代理的接口InvocationHandler
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//如果开关打开那么调用新的DAO
if(diamondSwitchConfig.isNewTableSwitch()){
log.warn("bid_proxy user new table..........................................");
return method.invoke(bidProxyDAONew, args);
}
return method.invoke(proxyBidRepo, args);
}
这里根据开关让method选择不同的 object来invoke。达到调用不同DAO的目的。然后实现FactoryBean 接口
@Override
public Object getObject() throws Exception {
Object proxyObj=Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{BidProxyDAO.class}, this);
return (BidProxyDAO) proxyObj;
}
@Override
public Class getObjectType() {
return BidProxyDAO.class;
}
就是返回代理类对象和对象类型,BidProxyDAO 就是DAO接口。附上完整的类
public class ProxyBidDAOProxyFactoryBean implements InvocationHandler, FactoryBean {
private static Logger log = LoggerFactory.getLogger(ProxyBidDAOProxyFactoryBean.class);
/**
* 新的dao
*/
@Resource
private BidProxyDAO bidProxyDAONew;
/**
* 老的dao
*/
@Resource
private BidProxyDAO proxyBidRepo;
@Resource
private DiamondSwitchConfig diamondSwitchConfig;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//如果开关打开那么调用新的DAO
if(diamondSwitchConfig.isNewTableSwitch()){
log.warn("bid_proxy user new table..........................................");
return method.invoke(bidProxyDAONew, args);
}
return method.invoke(proxyBidRepo, args);
}
@Override
public Object getObject() throws Exception {
Object proxyObj=Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{BidProxyDAO.class}, this);
return (BidProxyDAO) proxyObj;
}
@Override
public Class getObjectType() {
return BidProxyDAO.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
然后spring context的配置
<bean id="proxyBidRepo" class="com.taobao.misc.auction.repository.ProxyBidRepo"/>
<bean id="bidProxyDAONew" class="com.taobao.misc.auction.dao.impl.BidProxyDAOImpl"/>
<bean id="bidProxyDAO" class="com.taobao.misc.auction.dao.impl.ProxyBidDAOProxyFactoryBean"/>
这样系统原来引用bidProxyDAO 的代码都不需要动就可以实现根据开关来切换新老DAO的实现。等系统上线稳定之后可以将bidProxyDAONew替换成bidProxyDAO,然后删除剩下2个DAO的配置,系统就完全使用新的DAO,而且不用改一行代码。