赞
踩
根据请求切换租户,修改redis前缀,不修改原来的代码,以最小改动实现,支持并发。
ThreadLocal 保存当前请求的数据以及租户相关信息,利用 Filter 拦截请求,判断配置当前请求的租户类型,保存设置到RequestContext中。
1.RequestContext 是当前线程文本类,存储当前线程中的一些变量以及租户信息,
TenantType是枚举类,定义租户信息,读者自行定义,这里不提供了。
public class RequestContext { private static final String TENANT_TYPE = "TENANT_TYPE"; private Map<String, Object> values = new HashMap<String, Object>(); private static final ThreadLocal<RequestContext> LOCAL = new ThreadLocal<RequestContext>() { @Override protected RequestContext initialValue() { return new RequestContext(); } }; public static RequestContext getContext() { return LOCAL.get(); } public static void clearContext() { LOCAL.remove(); LOCAL.set(new RequestContext()); } public Object get(String key) { return values.get(key); } public void remove(String key) { values.remove(key); } public RequestContext set(String key, Object value) { if (value == null) { values.remove(key); } else { values.put(key, value); } return this; } /** * 设置数据源 * @param value * @return */ public RequestContext setDataSource(String value) { if (value == null) { values.remove(TENANT_TYPE); } else { values.put(TENANT_TYPE, value); } return this; } /** * 设置数据源 * @param value * @return */ public RequestContext setTenantType(String value) { if (value == null) { values.remove(TENANT_TYPE); } else { values.put(TENANT_TYPE, value); } return this; } /** * Get current DataSource * * @return data source name */ public String getTenantType() { return (String) values.get(TENANT_TYPE); } /** * Get current DataSource * * @return data source name */ public String getRedisPre() { if(StringUtils.isBlank(this.getTenantType())){ throw new IllegalArgumentException("当前请求未找到租户类型"); } return TenantType.getRedisPre(this.getTenantType()); } /** * Get current DataSource * * @return data source name */ public String getDataSource() { return (String) values.get(TENANT_TYPE); } /** * Clear current DataSource * * @return data source name */ public void clearDataSource() { remove(TENANT_TYPE); } }
2.利用 Filter 过滤器在请求进入后,根据请求域名来判断租户(每个租户的域名请求是不同的),并且把租户保存到 RequestContext 中,供当前后续代码获取。
public class SwitchTenantFilter implements Filter { /** 日志 */ private static Logger logger = LoggerFactory.getLogger(SwitchTenantFilter.class); @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; String host = request.getHeader("host"); // 租户1访问 if(host.contains("test1")){ RequestContext.getContext().setDataSource("test1"); } // 租户2访问 if(host.contains("test2")){ RequestContext.getContext().setDataSource("test2"); } String dataSource = RequestContext.getContext().getDataSource(); if(StringUtils.isBlank(dataSource)){ logger.error("访问host:{},切换数据源失败!!!", host); filterChain.doFilter(request, response); return; } logger.info("访问host:{},切换数据源成功,数据源key:{}", host, RequestContext.getContext().getDataSource()); filterChain.doFilter(request, response); // 清除 ThreadLocal 变量 } @Override public void init(FilterConfig filterConfig) throws ServletException { // TODO Auto-generated method stub } @Override public void destroy() { // TODO Auto-generated method stub } }
3.在Configuration类中实例化这个Filter Bean,配置拦截
// ===SwitchTenantFilter 切换租户,在WebAccessLogFilter之前加载=== @Bean public FilterRegistrationBean switchDataSourceFilterRegistrationBean() { FilterRegistrationBean registration = new FilterRegistrationBean(); SwitchTenantFilter switchTenantFilter = new SwitchTenantFilter(); registration.setFilter(switchTenantFilter); registration.addUrlPatterns("/*"); registration.setName("switchDataSourceFilter"); registration.setOrder(12); registration.setDispatcherTypes(DispatcherType.REQUEST); return registration; } // ===SwitchTenantFilter===
--------------------------------------------------------------------------------------------------------------------
以下是简单的源码跟踪,不需要的可以跳过:
springboot 使用 RedisTemplate 操作redis
- redisTemplate.opsForValue().set(key, val); // 设置数据
- redisTemplate.opsForValue().get(key); // 获取数据
- redisTemplate.delete(key); //删除数据
- 等等....
跟踪源码可以发现,RedisTemplate 在操作key和value时,会进行序列化。
AbstractOperations:
这里扩展点就出来了,我们可以重写RedisTemplate 的 getKeySerializer 方法,来根据当前租户切换 keySerializer ,来达到修改前缀的目的。
-----------------------------------------------------------------------------------------------------------------------
以下是重写的RedisTemplate:
- public class DynamicRedisTemplate<K, V> extends RedisTemplate<K, V> {
-
- public static final String SEPARATE=":";
-
- private RedisSerializer<String> stringSerializer = RedisSerializer.string();
-
- @Override
- public RedisSerializer getKeySerializer() {
- RedisSerializer<?> redisSerializer = super.getKeySerializer();
- if(StringUtils.isNotBlank(RequestContext.getContext().getRedisPre())){
- String cachePrefix = RequestContext.getContext().getRedisPre() + DynamicRedisTemplate.SEPARATE;
- redisSerializer = new KeyStringRedisSerializer(cachePrefix);
- }
- return redisSerializer;
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.data.redis.core.RedisOperations#delete(java.lang.Object)
- */
- @Override
- public Boolean delete(K key) {
-
- byte[] rawKey = rawKey(key);
-
- Long result = execute(connection -> connection.del(rawKey), true);
-
- return result != null && result.intValue() == 1;
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.data.redis.core.RedisOperations#delete(java.util.Collection)
- */
- @Override
- public Long delete(Collection<K> keys) {
-
- if (CollectionUtils.isEmpty(keys)) {
- return 0L;
- }
-
- byte[][] rawKeys = rawKeys(keys);
-
- return execute(connection -> connection.del(rawKeys), true);
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.data.redis.core.RedisOperations#unlink(java.lang.Object)
- */
- @Override
- public Boolean unlink(K key) {
-
- byte[] rawKey = rawKey(key);
-
- Long result = execute(connection -> connection.unlink(rawKey), true);
-
- return result != null && result.intValue() == 1;
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.data.redis.core.RedisOperations#unlink(java.util.Collection)
- */
- @Override
- public Long unlink(Collection<K> keys) {
-
- if (CollectionUtils.isEmpty(keys)) {
- return 0L;
- }
-
- byte[][] rawKeys = rawKeys(keys);
-
- return execute(connection -> connection.unlink(rawKeys), true);
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.data.redis.core.RedisOperations#countExistingKeys(java.util.Collection)
- */
- @Override
- public Long countExistingKeys(Collection<K> keys) {
-
- Assert.notNull(keys, "Keys must not be null!");
-
- byte[][] rawKeys = rawKeys(keys);
- return execute(connection -> connection.exists(rawKeys), true);
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.data.redis.core.RedisOperations#hasKey(java.lang.Object)
- */
- @Override
- public Boolean hasKey(K key) {
-
- byte[] rawKey = rawKey(key);
-
- return execute(connection -> connection.exists(rawKey), true);
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.data.redis.core.RedisOperations#expire(java.lang.Object, long, java.util.concurrent.TimeUnit)
- */
- @Override
- public Boolean expire(K key, final long timeout, final TimeUnit unit) {
-
- byte[] rawKey = rawKey(key);
- long rawTimeout = TimeoutUtils.toMillis(timeout, unit);
-
- return execute(connection -> {
- try {
- return connection.pExpire(rawKey, rawTimeout);
- } catch (Exception e) {
- // Driver may not support pExpire or we may be running on Redis 2.4
- return connection.expire(rawKey, TimeoutUtils.toSeconds(timeout, unit));
- }
- }, true);
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.data.redis.core.RedisOperations#expireAt(java.lang.Object, java.util.Date)
- */
- @Override
- public Boolean expireAt(K key, final Date date) {
-
- byte[] rawKey = rawKey(key);
-
- return execute(connection -> {
- try {
- return connection.pExpireAt(rawKey, date.getTime());
- } catch (Exception e) {
- return connection.expireAt(rawKey, date.getTime() / 1000);
- }
- }, true);
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.data.redis.core.RedisOperations#getExpire(java.lang.Object)
- */
- @Override
- public Long getExpire(K key) {
-
- byte[] rawKey = rawKey(key);
- return execute(connection -> connection.ttl(rawKey), true);
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.data.redis.core.RedisOperations#getExpire(java.lang.Object, java.util.concurrent.TimeUnit)
- */
- @Override
- public Long getExpire(K key, final TimeUnit timeUnit) {
-
- byte[] rawKey = rawKey(key);
- return execute(connection -> {
- try {
- return connection.pTtl(rawKey, timeUnit);
- } catch (Exception e) {
- // Driver may not support pTtl or we may be running on Redis 2.4
- return connection.ttl(rawKey, timeUnit);
- }
- }, true);
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.data.redis.core.RedisOperations#keys(java.lang.Object)
- */
- @Override
- @SuppressWarnings("unchecked")
- public Set<K> keys(K pattern) {
-
- byte[] rawKey = rawKey(pattern);
- Set<byte[]> rawKeys = execute(connection -> connection.keys(rawKey), true);
-
- return getKeySerializer() != null ? SerializationUtils.deserialize(rawKeys, getKeySerializer()) : (Set<K>) rawKeys;
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.data.redis.core.RedisOperations#persist(java.lang.Object)
- */
- @Override
- public Boolean persist(K key) {
-
- byte[] rawKey = rawKey(key);
- return execute(connection -> connection.persist(rawKey), true);
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.data.redis.core.RedisOperations#move(java.lang.Object, int)
- */
- @Override
- public Boolean move(K key, final int dbIndex) {
-
- byte[] rawKey = rawKey(key);
- return execute(connection -> connection.move(rawKey, dbIndex), true);
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.data.redis.core.RedisOperations#randomKey()
- */
- @Override
- public K randomKey() {
-
- byte[] rawKey = execute(RedisKeyCommands::randomKey, true);
- return deserializeKey(rawKey);
- }
-
- @SuppressWarnings("unchecked")
- private K deserializeKey(byte[] value) {
- return getKeySerializer() != null ? (K) getKeySerializer().deserialize(value) : (K) value;
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.data.redis.core.RedisOperations#rename(java.lang.Object, java.lang.Object)
- */
- @Override
- public void rename(K oldKey, K newKey) {
-
- byte[] rawOldKey = rawKey(oldKey);
- byte[] rawNewKey = rawKey(newKey);
-
- execute(connection -> {
- connection.rename(rawOldKey, rawNewKey);
- return null;
- }, true);
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.data.redis.core.RedisOperations#renameIfAbsent(java.lang.Object, java.lang.Object)
- */
- @Override
- public Boolean renameIfAbsent(K oldKey, K newKey) {
-
- byte[] rawOldKey = rawKey(oldKey);
- byte[] rawNewKey = rawKey(newKey);
- return execute(connection -> connection.renameNX(rawOldKey, rawNewKey), true);
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.data.redis.core.RedisOperations#type(java.lang.Object)
- */
- @Override
- public DataType type(K key) {
-
- byte[] rawKey = rawKey(key);
- return execute(connection -> connection.type(rawKey), true);
- }
-
- /**
- * Executes the Redis dump command and returns the results. Redis uses a non-standard serialization mechanism and
- * includes checksum information, thus the raw bytes are returned as opposed to deserializing with valueSerializer.
- * Use the return value of dump as the value argument to restore
- *
- * @param key The key to dump
- * @return results The results of the dump operation
- */
- @Override
- public byte[] dump(K key) {
-
- byte[] rawKey = rawKey(key);
- return execute(connection -> connection.dump(rawKey), true);
- }
-
- /**
- * Executes the Redis restore command. The value passed in should be the exact serialized data returned from
- * {@link #dump(Object)}, since Redis uses a non-standard serialization mechanism.
- *
- * @param key The key to restore
- * @param value The value to restore, as returned by {@link #dump(Object)}
- * @param timeToLive An expiration for the restored key, or 0 for no expiration
- * @param unit The time unit for timeToLive
- * @param replace use {@literal true} to replace a potentially existing value instead of erroring.
- * @throws RedisSystemException if the key you are attempting to restore already exists and {@code replace} is set to
- * {@literal false}.
- */
- @Override
- public void restore(K key, final byte[] value, long timeToLive, TimeUnit unit, boolean replace) {
-
- byte[] rawKey = rawKey(key);
- long rawTimeout = TimeoutUtils.toMillis(timeToLive, unit);
-
- execute(connection -> {
- connection.restore(rawKey, rawTimeout, value, replace);
- return null;
- }, true);
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.data.redis.core.RedisOperations#watch(java.lang.Object)
- */
- @Override
- public void watch(K key) {
-
- byte[] rawKey = rawKey(key);
-
- execute(connection -> {
- connection.watch(rawKey);
- return null;
- }, true);
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.data.redis.core.RedisOperations#watch(java.util.Collection)
- */
- @Override
- public void watch(Collection<K> keys) {
-
- byte[][] rawKeys = rawKeys(keys);
-
- execute(connection -> {
- connection.watch(rawKeys);
- return null;
- }, true);
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.data.redis.core.RedisOperations#sort(org.springframework.data.redis.core.query.SortQuery, org.springframework.data.redis.serializer.RedisSerializer)
- */
- @Override
- public <T> List<T> sort(SortQuery<K> query, @Nullable RedisSerializer<T> resultSerializer) {
-
- byte[] rawKey = rawKey(query.getKey());
- SortParameters params = QueryUtils.convertQuery(query, stringSerializer);
-
- List<byte[]> vals = execute(connection -> connection.sort(rawKey, params), true);
-
- return SerializationUtils.deserialize(vals, resultSerializer);
- }
-
- /*
- * (non-Javadoc)
- * @see org.springframework.data.redis.core.RedisOperations#sort(org.springframework.data.redis.core.query.SortQuery, java.lang.Object)
- */
- @Override
- public Long sort(SortQuery<K> query, K storeKey) {
-
- byte[] rawStoreKey = rawKey(storeKey);
- byte[] rawKey = rawKey(query.getKey());
- SortParameters params = QueryUtils.convertQuery(query, stringSerializer);
-
- return execute(connection -> connection.sort(rawKey, params, rawStoreKey), true);
- }
-
- @SuppressWarnings("unchecked")
- private byte[] rawKey(Object key) {
- Assert.notNull(key, "non null key required");
- if (getKeySerializer() == null && key instanceof byte[]) {
- return (byte[]) key;
- }
- return getKeySerializer().serialize(key);
- }
-
- private byte[][] rawKeys(Collection<K> keys) {
- final byte[][] rawKeys = new byte[keys.size()][];
-
- int i = 0;
- for (K key : keys) {
- rawKeys[i++] = rawKey(key);
- }
-
- return rawKeys;
- }
- }
发现我们重写了 RedisTemplate里面的挺多方法没这事什么原因?
原因:这里RedisTemplate里面有个“坑”,不能只重写getKeySerializer方法。
因为调用 redisTemplate.delete 等RedisTemplate本身的方法时,获取keySerializer的方法 rawKey
是私有的,并且不是通过 getKeySerializer 方法获取的 keySerializer,而是直接使用RedisTemplate本身的变量,这就不得以把 RedisTemplate 一些操作方法都进行重写。
RedisTemplate:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。