一个典型的微服务架构中,服务应该是没有状态的,但是对于一个多租户的SAAS类系统来说,每个租户都有自己的配置和业务数据,并且不同租户的之间的数据应该要满足一定程度的隔离性。
隔离方案一般有以下三种:
描述 | 优点 | 缺点 | |
独立数据库 | 一个租户一个数据库 | 隔离级别最高,安全性最好 | 成本较高 |
共享数据库,隔离数据架构 | 多个或所有租户共享Database,但是每个租户一个Schema | 为安全性要求较高的租户提供了一定程度的逻辑数据隔离,并不是完全隔离;每个数据库可支持更多的租户数量 | 现故障,数据恢复比较困难,因为恢复数据库将牵涉到其他租户的数据 |
共享数据库,共享数据架构 | 租户共享同一个Database、同一个Schema,但在表中增加TenantID多租户的数据字段 | 成本最低,允许每个数据库支持的租户数量最多 | 隔离级别最低,安全性最低,需要在设计开发时加大对安全的开发量; 数据备份和恢复最困难,需要逐表逐条备份和还原 |
处于安全性和经济性的综合考虑,第二种“共享数据库,隔离数据架构”是大多数SAAS类系统采用的方案。
由于服务是没有状态的且各租户共享的,即一个服务实例可以处理来自不同租户中的用户发起的请求,那么服务必须要支持动态数据源,即当不同租户的用户访问服务时,服务可以动态路由到访问这个租户对应的数据源。
为了模拟这种应用场景,我在2个mysql数据库服务器上建立了几个租户数据库,如图所示:
其中jg6(代表机构6)在192.168.2.135上,jg3、jg4、jg5在192.168.2.143上,每个数据库里都有一个叫userinfo的表,
jg6上数据为:
jg3上的数据为:
jg4上的数据为:
jg5上的数据为:
接下来就是重点了,如何在Spring Cloud体系中优雅的实现动态数据源,思路如下:
- 开发一个支持动态配置的Spring Boot Starter 来支持动态数据源,我把它命名为 dynamicDS-spring-boot-starter,
这个starter中包含一个@EnableDynamicDS注解。 - 需要动态数据源支持的微服务需要在pom.xml添加dynamicDS-spring-boot-starter依赖,并在应用启动类上添加
@EnableDynamicDS注解。
dynamicDS-spring-boot-starter的组成,如图所示:
- Application.java,自动生成的类,没啥意义,仅仅为了支持mvn clean install 编译而已,不然会不成功,提示没有Main Class.
- DynamicDatasourceConfigProperties,这个类是为了接受微服务中动态数据源的配置(application.yml),具体怎么定义一个类接受application.yml的配置,请查阅相关文档,简单讲就是定义Map<String, String>和嵌套的Map--Map<String, Map<String,String>>,就能解决绝大多数问题了。
微服务中application.yml中对应的动态数据源配置部分为:package com.tay.dynamicds; import java.util.Map; import org.springframework.boot.context.properties.ConfigurationProperties; import lombok.Data; @ConfigurationProperties(prefix = "dynamicds") @Data public class DynamicDatasourceConfigProperties { private String orgCodeHeader; private Map<String, String> general; private Map<String, Map<String, String>> tenants; }
你仔细对照阅读配置读取类和配置,就会明白他们之间的对应关系。dynamicds: orgCodeHeader: orgCode general: maxPoolSize: 10 minIdle: 3 defaultTenant : jg3 tenants: jg3: url: jdbc:mysql://192.168.2.143:3306/jg3 userName: root password: password jg4: url: jdbc:mysql://192.168.2.143:3306/jg4 userName: root password: password jg5: url: jdbc:mysql://192.168.2.143:3306/jg5 userName: root password: password maxPoolSize: 20 minIdle: 6 jg6: url: jdbc:mysql://192.168.2.135:3306/jg6 userName: root password: password
- DynamicDSAutoConfiguration.java,动态数据源自动配置类,在里面定义了一些必要的Bean。
package com.tay.dynamicds; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties