当前位置:   article > 正文

springboot redis多租户切换统一切换key前缀

springboot redis多租户

根据请求切换租户,修改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

  1. redisTemplate.opsForValue().set(key, val); // 设置数据
  2. redisTemplate.opsForValue().get(key); // 获取数据
  3. redisTemplate.delete(key); //删除数据
  4. 等等....

 跟踪源码可以发现,RedisTemplate 在操作key和value时,会进行序列化。

AbstractOperations:

 这里扩展点就出来了,我们可以重写RedisTemplate  的 getKeySerializer 方法,来根据当前租户切换 keySerializer ,来达到修改前缀的目的。

-----------------------------------------------------------------------------------------------------------------------

以下是重写的RedisTemplate:

  1. public class DynamicRedisTemplate<K, V> extends RedisTemplate<K, V> {
  2. public static final String SEPARATE=":";
  3. private RedisSerializer<String> stringSerializer = RedisSerializer.string();
  4. @Override
  5. public RedisSerializer getKeySerializer() {
  6. RedisSerializer<?> redisSerializer = super.getKeySerializer();
  7. if(StringUtils.isNotBlank(RequestContext.getContext().getRedisPre())){
  8. String cachePrefix = RequestContext.getContext().getRedisPre() + DynamicRedisTemplate.SEPARATE;
  9. redisSerializer = new KeyStringRedisSerializer(cachePrefix);
  10. }
  11. return redisSerializer;
  12. }
  13. /*
  14. * (non-Javadoc)
  15. * @see org.springframework.data.redis.core.RedisOperations#delete(java.lang.Object)
  16. */
  17. @Override
  18. public Boolean delete(K key) {
  19. byte[] rawKey = rawKey(key);
  20. Long result = execute(connection -> connection.del(rawKey), true);
  21. return result != null && result.intValue() == 1;
  22. }
  23. /*
  24. * (non-Javadoc)
  25. * @see org.springframework.data.redis.core.RedisOperations#delete(java.util.Collection)
  26. */
  27. @Override
  28. public Long delete(Collection<K> keys) {
  29. if (CollectionUtils.isEmpty(keys)) {
  30. return 0L;
  31. }
  32. byte[][] rawKeys = rawKeys(keys);
  33. return execute(connection -> connection.del(rawKeys), true);
  34. }
  35. /*
  36. * (non-Javadoc)
  37. * @see org.springframework.data.redis.core.RedisOperations#unlink(java.lang.Object)
  38. */
  39. @Override
  40. public Boolean unlink(K key) {
  41. byte[] rawKey = rawKey(key);
  42. Long result = execute(connection -> connection.unlink(rawKey), true);
  43. return result != null && result.intValue() == 1;
  44. }
  45. /*
  46. * (non-Javadoc)
  47. * @see org.springframework.data.redis.core.RedisOperations#unlink(java.util.Collection)
  48. */
  49. @Override
  50. public Long unlink(Collection<K> keys) {
  51. if (CollectionUtils.isEmpty(keys)) {
  52. return 0L;
  53. }
  54. byte[][] rawKeys = rawKeys(keys);
  55. return execute(connection -> connection.unlink(rawKeys), true);
  56. }
  57. /*
  58. * (non-Javadoc)
  59. * @see org.springframework.data.redis.core.RedisOperations#countExistingKeys(java.util.Collection)
  60. */
  61. @Override
  62. public Long countExistingKeys(Collection<K> keys) {
  63. Assert.notNull(keys, "Keys must not be null!");
  64. byte[][] rawKeys = rawKeys(keys);
  65. return execute(connection -> connection.exists(rawKeys), true);
  66. }
  67. /*
  68. * (non-Javadoc)
  69. * @see org.springframework.data.redis.core.RedisOperations#hasKey(java.lang.Object)
  70. */
  71. @Override
  72. public Boolean hasKey(K key) {
  73. byte[] rawKey = rawKey(key);
  74. return execute(connection -> connection.exists(rawKey), true);
  75. }
  76. /*
  77. * (non-Javadoc)
  78. * @see org.springframework.data.redis.core.RedisOperations#expire(java.lang.Object, long, java.util.concurrent.TimeUnit)
  79. */
  80. @Override
  81. public Boolean expire(K key, final long timeout, final TimeUnit unit) {
  82. byte[] rawKey = rawKey(key);
  83. long rawTimeout = TimeoutUtils.toMillis(timeout, unit);
  84. return execute(connection -> {
  85. try {
  86. return connection.pExpire(rawKey, rawTimeout);
  87. } catch (Exception e) {
  88. // Driver may not support pExpire or we may be running on Redis 2.4
  89. return connection.expire(rawKey, TimeoutUtils.toSeconds(timeout, unit));
  90. }
  91. }, true);
  92. }
  93. /*
  94. * (non-Javadoc)
  95. * @see org.springframework.data.redis.core.RedisOperations#expireAt(java.lang.Object, java.util.Date)
  96. */
  97. @Override
  98. public Boolean expireAt(K key, final Date date) {
  99. byte[] rawKey = rawKey(key);
  100. return execute(connection -> {
  101. try {
  102. return connection.pExpireAt(rawKey, date.getTime());
  103. } catch (Exception e) {
  104. return connection.expireAt(rawKey, date.getTime() / 1000);
  105. }
  106. }, true);
  107. }
  108. /*
  109. * (non-Javadoc)
  110. * @see org.springframework.data.redis.core.RedisOperations#getExpire(java.lang.Object)
  111. */
  112. @Override
  113. public Long getExpire(K key) {
  114. byte[] rawKey = rawKey(key);
  115. return execute(connection -> connection.ttl(rawKey), true);
  116. }
  117. /*
  118. * (non-Javadoc)
  119. * @see org.springframework.data.redis.core.RedisOperations#getExpire(java.lang.Object, java.util.concurrent.TimeUnit)
  120. */
  121. @Override
  122. public Long getExpire(K key, final TimeUnit timeUnit) {
  123. byte[] rawKey = rawKey(key);
  124. return execute(connection -> {
  125. try {
  126. return connection.pTtl(rawKey, timeUnit);
  127. } catch (Exception e) {
  128. // Driver may not support pTtl or we may be running on Redis 2.4
  129. return connection.ttl(rawKey, timeUnit);
  130. }
  131. }, true);
  132. }
  133. /*
  134. * (non-Javadoc)
  135. * @see org.springframework.data.redis.core.RedisOperations#keys(java.lang.Object)
  136. */
  137. @Override
  138. @SuppressWarnings("unchecked")
  139. public Set<K> keys(K pattern) {
  140. byte[] rawKey = rawKey(pattern);
  141. Set<byte[]> rawKeys = execute(connection -> connection.keys(rawKey), true);
  142. return getKeySerializer() != null ? SerializationUtils.deserialize(rawKeys, getKeySerializer()) : (Set<K>) rawKeys;
  143. }
  144. /*
  145. * (non-Javadoc)
  146. * @see org.springframework.data.redis.core.RedisOperations#persist(java.lang.Object)
  147. */
  148. @Override
  149. public Boolean persist(K key) {
  150. byte[] rawKey = rawKey(key);
  151. return execute(connection -> connection.persist(rawKey), true);
  152. }
  153. /*
  154. * (non-Javadoc)
  155. * @see org.springframework.data.redis.core.RedisOperations#move(java.lang.Object, int)
  156. */
  157. @Override
  158. public Boolean move(K key, final int dbIndex) {
  159. byte[] rawKey = rawKey(key);
  160. return execute(connection -> connection.move(rawKey, dbIndex), true);
  161. }
  162. /*
  163. * (non-Javadoc)
  164. * @see org.springframework.data.redis.core.RedisOperations#randomKey()
  165. */
  166. @Override
  167. public K randomKey() {
  168. byte[] rawKey = execute(RedisKeyCommands::randomKey, true);
  169. return deserializeKey(rawKey);
  170. }
  171. @SuppressWarnings("unchecked")
  172. private K deserializeKey(byte[] value) {
  173. return getKeySerializer() != null ? (K) getKeySerializer().deserialize(value) : (K) value;
  174. }
  175. /*
  176. * (non-Javadoc)
  177. * @see org.springframework.data.redis.core.RedisOperations#rename(java.lang.Object, java.lang.Object)
  178. */
  179. @Override
  180. public void rename(K oldKey, K newKey) {
  181. byte[] rawOldKey = rawKey(oldKey);
  182. byte[] rawNewKey = rawKey(newKey);
  183. execute(connection -> {
  184. connection.rename(rawOldKey, rawNewKey);
  185. return null;
  186. }, true);
  187. }
  188. /*
  189. * (non-Javadoc)
  190. * @see org.springframework.data.redis.core.RedisOperations#renameIfAbsent(java.lang.Object, java.lang.Object)
  191. */
  192. @Override
  193. public Boolean renameIfAbsent(K oldKey, K newKey) {
  194. byte[] rawOldKey = rawKey(oldKey);
  195. byte[] rawNewKey = rawKey(newKey);
  196. return execute(connection -> connection.renameNX(rawOldKey, rawNewKey), true);
  197. }
  198. /*
  199. * (non-Javadoc)
  200. * @see org.springframework.data.redis.core.RedisOperations#type(java.lang.Object)
  201. */
  202. @Override
  203. public DataType type(K key) {
  204. byte[] rawKey = rawKey(key);
  205. return execute(connection -> connection.type(rawKey), true);
  206. }
  207. /**
  208. * Executes the Redis dump command and returns the results. Redis uses a non-standard serialization mechanism and
  209. * includes checksum information, thus the raw bytes are returned as opposed to deserializing with valueSerializer.
  210. * Use the return value of dump as the value argument to restore
  211. *
  212. * @param key The key to dump
  213. * @return results The results of the dump operation
  214. */
  215. @Override
  216. public byte[] dump(K key) {
  217. byte[] rawKey = rawKey(key);
  218. return execute(connection -> connection.dump(rawKey), true);
  219. }
  220. /**
  221. * Executes the Redis restore command. The value passed in should be the exact serialized data returned from
  222. * {@link #dump(Object)}, since Redis uses a non-standard serialization mechanism.
  223. *
  224. * @param key The key to restore
  225. * @param value The value to restore, as returned by {@link #dump(Object)}
  226. * @param timeToLive An expiration for the restored key, or 0 for no expiration
  227. * @param unit The time unit for timeToLive
  228. * @param replace use {@literal true} to replace a potentially existing value instead of erroring.
  229. * @throws RedisSystemException if the key you are attempting to restore already exists and {@code replace} is set to
  230. * {@literal false}.
  231. */
  232. @Override
  233. public void restore(K key, final byte[] value, long timeToLive, TimeUnit unit, boolean replace) {
  234. byte[] rawKey = rawKey(key);
  235. long rawTimeout = TimeoutUtils.toMillis(timeToLive, unit);
  236. execute(connection -> {
  237. connection.restore(rawKey, rawTimeout, value, replace);
  238. return null;
  239. }, true);
  240. }
  241. /*
  242. * (non-Javadoc)
  243. * @see org.springframework.data.redis.core.RedisOperations#watch(java.lang.Object)
  244. */
  245. @Override
  246. public void watch(K key) {
  247. byte[] rawKey = rawKey(key);
  248. execute(connection -> {
  249. connection.watch(rawKey);
  250. return null;
  251. }, true);
  252. }
  253. /*
  254. * (non-Javadoc)
  255. * @see org.springframework.data.redis.core.RedisOperations#watch(java.util.Collection)
  256. */
  257. @Override
  258. public void watch(Collection<K> keys) {
  259. byte[][] rawKeys = rawKeys(keys);
  260. execute(connection -> {
  261. connection.watch(rawKeys);
  262. return null;
  263. }, true);
  264. }
  265. /*
  266. * (non-Javadoc)
  267. * @see org.springframework.data.redis.core.RedisOperations#sort(org.springframework.data.redis.core.query.SortQuery, org.springframework.data.redis.serializer.RedisSerializer)
  268. */
  269. @Override
  270. public <T> List<T> sort(SortQuery<K> query, @Nullable RedisSerializer<T> resultSerializer) {
  271. byte[] rawKey = rawKey(query.getKey());
  272. SortParameters params = QueryUtils.convertQuery(query, stringSerializer);
  273. List<byte[]> vals = execute(connection -> connection.sort(rawKey, params), true);
  274. return SerializationUtils.deserialize(vals, resultSerializer);
  275. }
  276. /*
  277. * (non-Javadoc)
  278. * @see org.springframework.data.redis.core.RedisOperations#sort(org.springframework.data.redis.core.query.SortQuery, java.lang.Object)
  279. */
  280. @Override
  281. public Long sort(SortQuery<K> query, K storeKey) {
  282. byte[] rawStoreKey = rawKey(storeKey);
  283. byte[] rawKey = rawKey(query.getKey());
  284. SortParameters params = QueryUtils.convertQuery(query, stringSerializer);
  285. return execute(connection -> connection.sort(rawKey, params, rawStoreKey), true);
  286. }
  287. @SuppressWarnings("unchecked")
  288. private byte[] rawKey(Object key) {
  289. Assert.notNull(key, "non null key required");
  290. if (getKeySerializer() == null && key instanceof byte[]) {
  291. return (byte[]) key;
  292. }
  293. return getKeySerializer().serialize(key);
  294. }
  295. private byte[][] rawKeys(Collection<K> keys) {
  296. final byte[][] rawKeys = new byte[keys.size()][];
  297. int i = 0;
  298. for (K key : keys) {
  299. rawKeys[i++] = rawKey(key);
  300. }
  301. return rawKeys;
  302. }
  303. }

发现我们重写了 RedisTemplate里面的挺多方法没这事什么原因?

原因:这里RedisTemplate里面有个“坑”,不能只重写getKeySerializer方法。

因为调用 redisTemplate.delete 等RedisTemplate本身的方法时,获取keySerializer的方法 rawKey

是私有的,并且不是通过 getKeySerializer 方法获取的 keySerializer,而是直接使用RedisTemplate本身的变量,这就不得以把 RedisTemplate 一些操作方法都进行重写。

RedisTemplate:

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/389681
推荐阅读
相关标签
  

闽ICP备14008679号