需求背景
最近接到一个项目,需要改造一个老的系统。该老系统以Oracle为存储,巨量的PL/SQL代码实现业务代码,C实现Socket Server,作为Client和PL/SQL的桥梁。不出所料,该老系统最大的问题是PL/SQL代码量巨大(上万的Procedure好几个),且毫无组织可言,实在改不动了,其次是性能有问题。改动的方向是,把PL/SQL从Oracle中踢出,用Java改写相关业务逻辑,放到Web Server中,不过Oracle中的Schema不动。
到目前位置,改造老系统和笔者要分享的主题没啥关系。问题来了,老系统有三套,A,B,C,就是说有三个Oracle数据库,Schema以及PL/SQL完全相同,但是数据没有啥关系,完全独立的三个数据库。历史的问题,不好评说,但是只是为了解决性能问题,搞了三套,客户被分配到不同的套系统,和传统的游戏开个服务器一个思路。
我们有3个方案:
1. 部署三套WebServer,对应三个Oracle数据库,客户端连接到不同的Web Server,和原来架构相同。数据库和Server都继续分。
2. 把三个数据库整合为一个数据库,部署一套Web Server。数据和,Server和。
3. 保持三套Oracle数据库不变,一个Web Server,但是Server需要把三个Oracle都管理起来。
方案1最简单,最容易,但是当性能已经不再是问题的时候还是部署三套Web Server,实在是有些说不过去,运维工作增加,客户端维护不同的版本(服务器地址),不太愿意选择,暂时备选。
方案2很难,非常的难。原来的三个Oracle数据库,完全独立,没有全局的主键,基本上无法区分开数据。放弃!
方案3是个折中的方案,中国人讲究中庸之道。最终我们选择了方案3。
设计思路
方案3要解决的问题是同一个Server如何集成3个数据库,具体来说,就是Spring里面如何管理3个Shema完全相同的Datasouce。我们的系统Server的技术选型是常见的Spring+MyBatis。管理多个Shema不同的Datasouce,网上有很多例子,Schema相同这叫分库吗,貌似很少?那进一步在Spring环境下呢?没有找到。强调Spring只想知道一个Mapper,而非3个。是因为,Spring只想知道一个Mapper,而非3个。
其中的关键技术难点如下:
- 如何识别什么样的数据应该存到哪一个Oracle数据库?
我们的解法是根据用户所在的位置来判断,用户在A库,那么后续的操作针对A库,在B库就操作B库。最开始登录的时候先探测用户到底存在于哪一个库。我们认为用户名+密码应该是跨三个数据库唯一的。 - 如何动态切换数据库?
代理,用代理来实现。Spring容器内注册的就是一个代理而已,代理被调用的时候,我们截获调用,根据登录时候获得的环境(哪一个库),来动态切换,委托给背后的MyBatis Mapper来执行。
下图是我画的切换的示意图。偷懒的原因,我只画了A,B两套,实战中是A,B,C。
实例代码
识别应该存于哪一个数据库
稍微改造一下Shiro访问用户信息的地方,增加环境的属性。注意两点,一是遍历数据源,探测用户所在的数据库,而是直接用SqlSessionFactory的bean name作为环境名,够简单直白!
Shiro Realm代码如下:
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private SecureService secureService;
@Autowired
private AuthorizationService authorizationService;
@PostConstruct
public void initAlgorithms() {
AllowAllCredentialsMatcher credentialsMatcher = new AllowAllCredentialsMatcher();
setCredentialsMatcher(credentialsMatcher);
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken pairToken = (UsernamePasswordToken) token;
String userName = pairToken.getUsername();
String password = new String(pairToken.getPassword());
// determine env
Map<String, Object>