SpringBoot 支持 redis 多数据库或redis多服务 自由切换路由,saas多租户支持.

公司产品XXX使用SpringBoot+SpringCloud实现微服务架构下的SaaS多租户实现,每个租户的创建都会:

1. 创建一个独享的database schema

2. 使用独享redis database

3. 使用独立的文件存储空间

----------

与多租户共享单个数据库的相比,这样可以不侵犯各业务类微服务的代码及数据库表结构,租户的控制有统一的微服务进行管控。下面列举了需要解决的几个技术点及解决方案:

  • 微服务之间的租户信息传递

技术方案:

租户信息(id, db_schema, redis_db_index)保存在并设置到threadlocal上,微服务在feign请求其他微服务时,断路器拦截器将租户信息包装到token中传递到其他微服务;微服务被feign调用时,断路器拦截器获取http header中的token,并获取到租户信息,并设置到threadlocal。threadlocal中的租户信息将用到数据库切换和redis数据库切换。

  • 租户数据库切换

技术方案:

实现springboot的AbstractRoutingDataSource, 实现方法determineCurrentLookupKey(), 方法中获取到threadlocal中的db_schema,并根据需要创建对应的datasource。(这个网上比较多,就不重复了。关于activiti的数据库多租户切换实现不太一样,但是大致原理相同)

  • 使用springboot的cache注解前提下,实现租户redis动态切换(此次分享的重点,网上没啥材料)

失败的技术方案:

使用一个redistemplate,通过aop在有cache注解的方法执行前,修改template的connectionfactory。这个方案有多线程问题,单线程调用没问题,多线程会出现数据乱窜,具体原因就不多废话了。

技术方案:

效仿springboot的AbstractRoutingDataSource创建一个AbstractRoutingRedisTemplate, 然后通过abstract determineCurrentLookupKey由下游决定于redistemplate的选取。

package james.wu;

import java.io.Closeable;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.BoundGeoOperations;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.BoundZSetOperations;
import org.springframework.data.redis.core.BulkMapper;
import org.springframework.data.redis.core.ClusterOperations;
import org.springframework.data.redis.core.GeoOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.HyperLogLogOperations;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.query.SortQuery;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.data.redis.serializer.RedisSerializer;

/**
 * 
 * @author James Wu 2018-3-22
 *
 *	Abstract {@link org.springframework.data.redis.core.RedisTemplate} implementation that routes {@link #getConnectionFactory()}
 * calls to one of various target RedisTemplate based on a lookup key. The latter is usually
 * (but not necessarily) determined through some thread-bound transaction context.
 * 
 */

public abstract class AbstractRoutingRedisTemplate<K,V> extends RedisTemplate<K, V>{

	private Map<Integer, RedisTemplate<K, V>> redisTemplates = new HashMap<>();
	
	@Override
	public RedisConnectionFactory getConnectionFactory() {
		return this.determineTargetRedisTemplate().getConnectionFactory();
	}
	
	@Override
	public <T> T execute(RedisCallback<T> action) {
		return this.determineTargetRedisTemplate().execute(action);
	}

	@Override
	public <T> T execute(SessionCallback<T> session) {
		return this.determineTargetRedisTemplate().execute(session);
	}

	@Override
	public List<Object> executePipelined(RedisCallback<?> action) {
		return this.determineTargetRedisTemplate().executePipelined(action);
	}

	@Override
	public List<Object> executePipelined(RedisCallback<?> action, RedisSerializer<?> resultSerializer) {
		return this.determineTargetRedisTemplate().executePipelined(action, resultSerializer);
	}

	@Override
	public List<Object> executePipelined(SessionCallback<?> session) {
		return this.determineTargetRedisTemplate().executePipelined(session);
	}

	@Override
	public List<Object> executePipelined(SessionCallback<?> session, RedisSerializer<?> resultSerial
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值