最近做的项目是和多机房有些关系,不过不是做多活,而是将数据从一个机房拆到多个机房,业务上又允许用户异地访问,即数据分业务数据和用户数据,业务数据跟业务发生的机房绑定,用户数据只有一份,按需要跨区访问:
上图中用户A注册时选择的国家最终保存在A机房,用户A可以到B机房下单,按照隐私合规的要求,用户A的数据只能保存一份,因此D机房下单时必须从A机房获取用户信息,就有跨机房调用的场景了;
还有原来的场景,在没做多机房改造之前,下订单也要查询用户信息,不过这里是同机房调用了;
上面就是RPC调用的2种典型场景,RPC调用系统上要支持同机房调用也支持跨机房调用,什么时候是同机房,什么时候是跨机房,跟用户输入的参数有关,有可能是根据用户名,有可能是根据邮箱等其它信息。
以下是我们的实践的一些想法,我们的Rpc框架使用的是Dubbo。
一、下沉路由表服务
根据业务参数做路由,需要一个单独的服务来保存、查询路由信息,以减少业务的接入复杂性和收拢路由逻辑及降低业务复杂性,因为刚开始我们还对存储没太大的把握,只要接口兼容,后续可以将存储迁移到其它更快的介质上。
路由表是典型的写少读多的场景,这里不具体讨论路由表的设计,注意数据的规模和一些一致性要求细节即可。
二、Provider分组
我们将所有机房的服务注册到一个全局的注册中心,不同机房的服务通过Group来区别。
三、客户端路由
我们的RPC是基于DUBBO做的,是在Consumer还是Provider做呢,我们的结论是在Consumer方做,理由如下:
1、使用简单
调用没有太强的一致性要求,只要路由表算出来的结果是准确的,最终选择的机房就是正确的,如果在Provider做,则所有Provider要感知其它机房的Provider,增加系统的复杂性;
2、性能下降不大
如果在Provider方做,以前是两层调用关系,即Consumer直接调用Provider,现在要变成Consumer——》Provider——》最终Provider三层调用关系,性能下降的厉害;
3、系统改造成本低
为了支持Consumer透明,简单的接入,我们在支持路由能力的方法上加了注解,然后拦截调用,接入成本非常低;
假设原来有一个服务UserService,有一个方法查询用户的:
public interface UserService{
public List<UserDto> queryUser(UserDto userDto);
}
改造后只要加上少量注解就可以:
@EnableRoute
public interface UserService{
@RouteField(name="email")
public List<UserDto> queryUser(UserDto userDto);
}
四、其它
其它还涉及到Dubbo的改造了,加了一个新的Filter,根据attachments里传入的参数动态的选择相应Group里的服务,细节就不讨论了。
这里再说下通过这个项目对中间件的理解,中间件应该要解决大部分常见问题,对于一些特殊的问题应该预留接口让接入方来实现以保持良好的扩展性,而不是把所有的实现细节都抛给接入方;
我们在做这个多机房调用的时候刚开始的时候想着路由的过程其实就是根据输入参数算出一个机房的Group过程,我们把方法名,参数和其它一些上下文信息传递给业务方,让业务方自己算出来就可以了,最后和大家一起讨论,其实整个路由过程就是根据输入参数某个字段调用路由表的过程,这是一个模板方法,应该可以统一封装起来,而对于一些不规范的,如入参不是一个DTO或对象,而是多种原生类型的直接让接入方自己实现我们预留的接口就行了,这样大部分场景只需要加些注解就可以完成接入了。