context) {
41 | // 将父线程的MDC内容传给子线程
42 | if (context != null) {
43 | try {
44 | MDC.setContextMap(context);
45 | } catch (Exception e) {
46 | logger.error(e.getMessage(), e);
47 | }
48 | }
49 | try {
50 | // 执行异步操作
51 | runnable.run();
52 | } finally {
53 | // 清空MDC内容
54 | MDC.clear();
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/multi-layering-cache-core/src/main/java/org/github/roger/concurrent/RedisDistriLock.java:
--------------------------------------------------------------------------------
1 | package org.github.roger.concurrent;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.data.redis.core.RedisTemplate;
6 | import org.springframework.data.redis.core.script.RedisScript;
7 | import org.springframework.util.Assert;
8 | import org.springframework.util.StringUtils;
9 |
10 | import java.util.*;
11 | import java.util.concurrent.TimeUnit;
12 |
13 | /**
14 | * Redis分布式锁
15 | * 使用 SET resource-name anystring NX EX max-lock-time 实现
16 | *
17 | * 该方案在 Redis 官方 SET 命令页有详细介绍。
18 | * http://doc.redisfans.com/string/set.html
19 | *
20 | * 在介绍该分布式锁设计之前,我们先来看一下在从 Redis 2.6.12 开始 SET 提供的新特性,
21 | * 命令 SET key value [EX seconds] [PX milliseconds] [NX|XX],其中:
22 | *
23 | * EX seconds — 以秒为单位设置 key 的过期时间;
24 | * PX milliseconds — 以毫秒为单位设置 key 的过期时间;
25 | * NX — 将key 的值设为value ,当且仅当key 不存在,等效于 SETNX。
26 | * XX — 将key 的值设为value ,当且仅当key 存在,等效于 SETEX。
27 | *
28 | * 命令 SET resource-name anystring NX EX max-lock-time 是一种在 Redis 中实现锁的简单方法。
29 | *
30 | * 客户端执行以上的命令:
31 | *
32 | * 如果服务器返回 OK ,那么这个客户端获得锁。
33 | * 如果服务器返回 NIL ,那么客户端获取锁失败,可以在稍后再重试。
34 | *
35 | */
36 | public class RedisDistriLock {
37 |
38 | private static Logger logger = LoggerFactory.getLogger(RedisDistriLock.class);
39 |
40 | private RedisTemplate redisTemplate;
41 |
42 | /**
43 | * 调用set后的返回值
44 | */
45 | private static final String OK = "OK";
46 |
47 | /**
48 | * 默认请求锁的超时时间(ms 毫秒)
49 | */
50 | private static final long TIME_OUT = 100;
51 |
52 | /**
53 | * 默认锁的有效时间(s)
54 | */
55 | private static final int EXPIRE = 60;
56 |
57 | /**
58 | * 解锁的lua脚本
59 | */
60 | private static final String UNLOCK_LUA;
61 |
62 | static {
63 | UNLOCK_LUA = "if redis.call(\"get\",KEYS[1]) == ARGV[1] "
64 | + "then "
65 | + " return redis.call(\"del\",KEYS[1]) "
66 | + "else "
67 | + " return 0 "
68 | + "end ";
69 | }
70 |
71 | /**
72 | * 锁标志对应的key
73 | */
74 | private String lockKey;
75 |
76 | /**
77 | * 记录到日志的锁标志对应的key
78 | */
79 | private String lockKeyLog = "";
80 |
81 | /**
82 | * 锁对应的值
83 | */
84 | private String lockValue;
85 |
86 | /**
87 | * 锁的有效时间(s)
88 | */
89 | private int expireTime = EXPIRE;
90 |
91 | /**
92 | * 请求锁的超时时间(ms)
93 | */
94 | private long timeOut = TIME_OUT;
95 |
96 | /**
97 | * 锁标记
98 | */
99 | private volatile boolean locked = false;
100 |
101 | private final Random random = new Random();
102 |
103 | /**
104 | * 使用默认的锁过期时间和请求锁的超时时间
105 | *
106 | * @param redisTemplate redis客户端
107 | * @param lockKey 锁的key(Redis的Key)
108 | */
109 | public RedisDistriLock(RedisTemplate redisTemplate, String lockKey) {
110 | this.redisTemplate = redisTemplate;
111 | this.lockKey = lockKey + "_lock";
112 | }
113 |
114 | /**
115 | * 使用默认的请求锁的超时时间,指定锁的过期时间
116 | *
117 | * @param redisTemplate redis客户端
118 | * @param lockKey 锁的key(Redis的Key)
119 | * @param expireTime 锁的过期时间(单位:秒)
120 | */
121 | public RedisDistriLock(RedisTemplate redisTemplate, String lockKey, int expireTime) {
122 | this(redisTemplate, lockKey);
123 | this.expireTime = expireTime;
124 | }
125 |
126 | /**
127 | * 使用默认的锁的过期时间,指定请求锁的超时时间
128 | *
129 | * @param redisTemplate redis客户端
130 | * @param lockKey 锁的key(Redis的Key)
131 | * @param timeOut 请求锁的超时时间(单位:毫秒)
132 | */
133 | public RedisDistriLock(RedisTemplate redisTemplate, String lockKey, long timeOut) {
134 | this(redisTemplate, lockKey);
135 | this.timeOut = timeOut;
136 | }
137 |
138 | /**
139 | * 锁的过期时间和请求锁的超时时间都是用指定的值
140 | *
141 | * @param redisTemplate redis客户端
142 | * @param lockKey 锁的key(Redis的Key)
143 | * @param expireTime 锁的过期时间(单位:秒)
144 | * @param timeOut 请求锁的超时时间(单位:毫秒)
145 | */
146 | public RedisDistriLock(RedisTemplate redisTemplate, String lockKey, int expireTime, long timeOut) {
147 | this(redisTemplate, lockKey, expireTime);
148 | this.timeOut = timeOut;
149 | }
150 |
151 | /**
152 | * 尝试获取锁 超时返回
153 | *
154 | * @return boolean
155 | */
156 | public boolean tryLock() {
157 | // 生成随机key
158 | this.lockValue = UUID.randomUUID().toString();
159 | // 请求锁超时时间,纳秒
160 | long timeout = timeOut * 1000000;
161 | // 系统当前时间,纳秒
162 | long nowTime = System.nanoTime();
163 | while ((System.nanoTime() - nowTime) < timeout) {
164 | if (this.set(lockKey, lockValue, expireTime)) {
165 | locked = true;
166 | // 上锁成功结束请求
167 | return locked;
168 | }
169 |
170 | // 每次请求等待一段时间
171 | seleep(10, 50000);
172 | }
173 | return locked;
174 | }
175 |
176 | /**
177 | * 尝试获取锁 立即返回
178 | *
179 | * @return 是否成功获得锁
180 | */
181 | public boolean lock() {
182 | this.lockValue = UUID.randomUUID().toString();
183 | //不存在则添加 且设置过期时间(单位ms)
184 | locked = set(lockKey, lockValue, expireTime);
185 | return locked;
186 | }
187 |
188 | /**
189 | * 以阻塞方式的获取锁
190 | *
191 | * @return 是否成功获得锁
192 | */
193 | public boolean lockBlock() {
194 | this.lockValue = UUID.randomUUID().toString();
195 | while (true) {
196 | //不存在则添加 且设置过期时间(单位ms)
197 | locked = set(lockKey, lockValue, expireTime);
198 | if (locked) {
199 | return locked;
200 | }
201 | // 每次请求等待一段时间
202 | seleep(10, 50000);
203 | }
204 | }
205 |
206 | /**
207 | * 解锁
208 | *
209 | * 可以通过以下修改,让这个锁实现更健壮:
210 | *
211 | * 不使用固定的字符串作为键的值,而是设置一个不可猜测(non-guessable)的长随机字符串,作为口令串(token)。
212 | * 不使用 DEL 命令来释放锁,而是发送一个 Lua 脚本,这个脚本只在客户端传入的值和键的口令串相匹配时,才对键进行删除。
213 | * 这两个改动可以防止持有过期锁的客户端误删现有锁的情况出现。
214 | *
215 | * @return Boolean
216 | */
217 | public Boolean unlock() {
218 | // 只有加锁成功并且锁还有效才去释放锁
219 | // 只有加锁成功并且锁还有效才去释放锁
220 | if (locked) {
221 | try {
222 | RedisScript script = RedisScript.of(UNLOCK_LUA, Long.class);
223 | List keys = new ArrayList<>();
224 | keys.add(lockKey);
225 | Long result = redisTemplate.execute(script, keys, lockValue);
226 | if (result == 0 && !StringUtils.isEmpty(lockKeyLog)) {
227 | logger.debug("Redis分布式锁,解锁{}失败!解锁时间:{}", lockKeyLog, System.currentTimeMillis());
228 | }
229 |
230 | locked = result == 0;
231 | return result == 1;
232 | } catch (Throwable e) {
233 | logger.warn("Redis不支持EVAL命令,使用降级方式解锁:{}", e.getMessage());
234 | String value = this.get(lockKey, String.class);
235 | if (lockValue.equals(value)) {
236 | redisTemplate.delete(lockKey);
237 | return true;
238 | }
239 | return false;
240 | }
241 | }
242 |
243 | return true;
244 | }
245 |
246 | /**
247 | * 重写redisTemplate的set方法
248 | *
249 | * 命令 SET resource-name anystring NX EX max-lock-time 是一种在 Redis 中实现锁的简单方法。
250 | *
251 | * 客户端执行以上的命令:
252 | *
253 | * 如果服务器返回 OK ,那么这个客户端获得锁。
254 | * 如果服务器返回 NIL ,那么客户端获取锁失败,可以在稍后再重试。
255 | *
256 | * @param key 锁的Key
257 | * @param value 锁里面的值
258 | * @param seconds 过去时间(秒)
259 | * @return String
260 | */
261 | private boolean set(final String key, final String value, final long seconds) {
262 | Assert.isTrue(!StringUtils.isEmpty(key), "key不能为空");
263 | Boolean success = redisTemplate.opsForValue().setIfAbsent(key, value, seconds, TimeUnit.SECONDS);
264 | if (!StringUtils.isEmpty(lockKeyLog) && Objects.nonNull(success) && success) {
265 | logger.debug("获取锁{}的时间:{}", lockKeyLog, System.currentTimeMillis());
266 | }
267 | return Objects.nonNull(success) && success;
268 | }
269 |
270 | /**
271 | * 获取redis里面的值
272 | *
273 | * @param key key
274 | * @param aClass class
275 | * @return T
276 | */
277 | private T get(final String key, Class aClass) {
278 | Assert.isTrue(!StringUtils.isEmpty(key), "key不能为空");
279 | return (T) redisTemplate.opsForValue().get(key);
280 | }
281 |
282 | /**
283 | * 获取锁状态
284 | *
285 | * @return boolean
286 | * @author yuhao.wang
287 | */
288 | public boolean isLock() {
289 |
290 | return locked;
291 | }
292 |
293 | /**
294 | * @param millis 毫秒
295 | * @param nanos 纳秒
296 | * @Title: seleep
297 | * @Description: 线程等待时间
298 | * @author yuhao.wang
299 | */
300 | private void seleep(long millis, int nanos) {
301 | try {
302 | Thread.sleep(millis, random.nextInt(nanos));
303 | } catch (Exception e) {
304 | logger.debug("获取分布式锁休眠被中断:", e);
305 | }
306 | }
307 |
308 | public String getLockKeyLog() {
309 | return lockKeyLog;
310 | }
311 |
312 | public void setLockKeyLog(String lockKeyLog) {
313 | this.lockKeyLog = lockKeyLog;
314 | }
315 |
316 | public int getExpireTime() {
317 | return expireTime;
318 | }
319 |
320 | public void setExpireTime(int expireTime) {
321 | this.expireTime = expireTime;
322 | }
323 |
324 | public long getTimeOut() {
325 | return timeOut;
326 | }
327 |
328 | public void setTimeOut(long timeOut) {
329 | this.timeOut = timeOut;
330 | }
331 | }
--------------------------------------------------------------------------------
/multi-layering-cache-core/src/main/java/org/github/roger/concurrent/ThreadTaskUtils.java:
--------------------------------------------------------------------------------
1 | package org.github.roger.concurrent;
2 |
3 | import java.util.concurrent.ThreadPoolExecutor;
4 |
5 | /**
6 | * 线程池
7 | *
8 | * @author yuhao.wang3
9 | */
10 | public class ThreadTaskUtils {
11 | private static MdcThreadPoolTaskExecutor taskExecutor = null;
12 |
13 | static {
14 | taskExecutor = new MdcThreadPoolTaskExecutor();
15 | // 核心线程数
16 | taskExecutor.setCorePoolSize(8);
17 | // 最大线程数
18 | taskExecutor.setMaxPoolSize(64);
19 | // 队列最大长度
20 | taskExecutor.setQueueCapacity(1000);
21 | // 线程池维护线程所允许的空闲时间(单位秒)
22 | taskExecutor.setKeepAliveSeconds(120);
23 | /*
24 | * 线程池对拒绝任务(无限程可用)的处理策略
25 | * ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
26 | * ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
27 | * ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
28 | * ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务,如果执行器已关闭,则丢弃.
29 | */
30 | taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
31 |
32 | taskExecutor.initialize();
33 | }
34 |
35 | public static void run(Runnable runnable) {
36 | taskExecutor.execute(runnable);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/multi-layering-cache-core/src/main/java/org/github/roger/enumeration/ExpireMode.java:
--------------------------------------------------------------------------------
1 | package org.github.roger.enumeration;
2 |
3 | public enum ExpireMode {
4 |
5 | /**
6 | * 每写入一次重新计算一次缓存的有效时间
7 | */
8 | WRITE("计算到期时间的标准是:距离最后一次写入时间到期时失效"),
9 |
10 | /**
11 | * 每访问一次重新计算一次缓存的有效时间
12 | */
13 | ACCESS("计算到期时间的标准是:距离最后一次访问时间到期时失效");
14 |
15 | private String label;
16 |
17 | ExpireMode(String label) {
18 | this.label = label;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/multi-layering-cache-core/src/main/java/org/github/roger/enumeration/RedisPubSubMessageType.java:
--------------------------------------------------------------------------------
1 | package org.github.roger.enumeration;
2 |
3 | import lombok.Getter;
4 |
5 | @Getter
6 | public enum RedisPubSubMessageType {
7 |
8 | /**
9 | * 删除缓存
10 | */
11 | EVICT("删除缓存"),
12 |
13 | /**
14 | * 清空缓存
15 | */
16 | CLEAR("清空缓存");
17 |
18 | private String label;
19 |
20 | RedisPubSubMessageType(String label) {
21 | this.label = label;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/multi-layering-cache-core/src/main/java/org/github/roger/enumeration/Type.java:
--------------------------------------------------------------------------------
1 | package org.github.roger.enumeration;
2 |
3 | /**
4 | * 对象类型
5 | *
6 | */
7 | public enum Type {
8 | /**
9 | * null
10 | */
11 | NULL("null"),
12 |
13 | /**
14 | * string
15 | */
16 | STRING("string"),
17 |
18 | /**
19 | * object
20 | */
21 | OBJECT("Object 对象"),
22 |
23 | /**
24 | * List集合
25 | */
26 | LIST("List集合"),
27 |
28 | /**
29 | * Set集合
30 | */
31 | SET("Set集合"),
32 |
33 | /**
34 | * 数组
35 | */
36 | ARRAY("数组"),
37 |
38 | /**
39 | * 枚举
40 | */
41 | ENUM("枚举"),
42 |
43 | /**
44 | * 其他类型
45 | */
46 | OTHER("其他类型");
47 |
48 | private String label;
49 |
50 | Type(String label) {
51 | this.label = label;
52 | }
53 |
54 | public static Type parse(String name) {
55 | for (Type type : Type.values()) {
56 | if (type.name().equals(name)) {
57 | return type;
58 | }
59 | }
60 | return OTHER;
61 | }
62 | }
--------------------------------------------------------------------------------
/multi-layering-cache-core/src/main/java/org/github/roger/exception/NestedRuntimeException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2002-2017 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package org.github.roger.exception;
18 |
19 | import org.springframework.core.NestedCheckedException;
20 | import org.springframework.core.NestedExceptionUtils;
21 |
22 | /**
23 | * Handy class for wrapping runtime {@code Exceptions} with a root cause.
24 | *
25 | * This class is {@code abstract} to force the programmer to extend
26 | * the class. {@code getMessage} will include nested exception
27 | * information; {@code printStackTrace} and other like methods will
28 | * delegate to the wrapped exception, if any.
29 | *
30 | *
The similarity between this class and the {@link NestedCheckedException}
31 | * class is unavoidable, as Java forces these two classes to have different
32 | * superclasses (ah, the inflexibility of concrete inheritance!).
33 | *
34 | * @author Rod Johnson
35 | * @author Juergen Hoeller
36 | * @see #getMessage
37 | * @see #printStackTrace
38 | * @see NestedCheckedException
39 | */
40 | public abstract class NestedRuntimeException extends RuntimeException {
41 |
42 | /** Use serialVersionUID from Spring 1.2 for interoperability */
43 | private static final long serialVersionUID = 5439915454935047936L;
44 |
45 | static {
46 | // Eagerly load the NestedExceptionUtils class to avoid classloader deadlock
47 | // issues on OSGi when calling getMessage(). Reported by Don Brown; SPR-5607.
48 | NestedExceptionUtils.class.getName();
49 | }
50 |
51 |
52 | /**
53 | * Construct a {@code NestedRuntimeException} with the specified detail message.
54 | * @param msg the detail message
55 | */
56 | public NestedRuntimeException(String msg) {
57 | super(msg);
58 | }
59 |
60 | /**
61 | * Construct a {@code NestedRuntimeException} with the specified detail message
62 | * and nested exception.
63 | * @param msg the detail message
64 | * @param cause the nested exception
65 | */
66 | public NestedRuntimeException(String msg, Throwable cause) {
67 | super(msg, cause);
68 | }
69 |
70 |
71 | /**
72 | * Return the detail message, including the message from the nested exception
73 | * if there is one.
74 | */
75 | @Override
76 | public String getMessage() {
77 | return NestedExceptionUtils.buildMessage(super.getMessage(), getCause());
78 | }
79 |
80 |
81 | /**
82 | * Retrieve the innermost cause of this exception, if any.
83 | * @return the innermost exception, or {@code null} if none
84 | * @since 2.0
85 | */
86 | public Throwable getRootCause() {
87 | return NestedExceptionUtils.getRootCause(this);
88 | }
89 |
90 | /**
91 | * Retrieve the most specific cause of this exception, that is,
92 | * either the innermost cause (root cause) or this exception itself.
93 | *
Differs from {@link #getRootCause()} in that it falls back
94 | * to the present exception if there is no root cause.
95 | * @return the most specific cause (never {@code null})
96 | * @since 2.0.3
97 | */
98 | public Throwable getMostSpecificCause() {
99 | Throwable rootCause = getRootCause();
100 | return (rootCause != null ? rootCause : this);
101 | }
102 |
103 | /**
104 | * Check whether this exception contains an exception of the given type:
105 | * either it is of the given class itself or it contains a nested cause
106 | * of the given type.
107 | * @param exType the exception type to look for
108 | * @return whether there is a nested exception of the specified type
109 | */
110 | public boolean contains(Class> exType) {
111 | if (exType == null) {
112 | return false;
113 | }
114 | if (exType.isInstance(this)) {
115 | return true;
116 | }
117 | Throwable cause = getCause();
118 | if (cause == this) {
119 | return false;
120 | }
121 | if (cause instanceof NestedRuntimeException) {
122 | return ((NestedRuntimeException) cause).contains(exType);
123 | }
124 | else {
125 | while (cause != null) {
126 | if (exType.isInstance(cause)) {
127 | return true;
128 | }
129 | if (cause.getCause() == cause) {
130 | break;
131 | }
132 | cause = cause.getCause();
133 | }
134 | return false;
135 | }
136 | }
137 |
138 | }
139 |
--------------------------------------------------------------------------------
/multi-layering-cache-core/src/main/java/org/github/roger/exception/SerializationException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011-2013 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.github.roger.exception;
17 |
18 |
19 | /**
20 | * Generic exception indicating a serialization/deserialization error.
21 | *
22 | * @author Costin Leau
23 | */
24 | public class SerializationException extends NestedRuntimeException {
25 |
26 | /**
27 | * Constructs a new SerializationException
instance.
28 | *
29 | * @param msg msg
30 | * @param cause 原因
31 | */
32 | public SerializationException(String msg, Throwable cause) {
33 | super(msg, cause);
34 | }
35 |
36 | /**
37 | * Constructs a new SerializationException
instance.
38 | *
39 | * @param msg msg
40 | */
41 | public SerializationException(String msg) {
42 | super(msg);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/multi-layering-cache-core/src/main/java/org/github/roger/listener/RedisMessageListener.java:
--------------------------------------------------------------------------------
1 | package org.github.roger.listener;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import lombok.Setter;
5 | import lombok.extern.slf4j.Slf4j;
6 | import org.github.roger.MultiLayeringCache;
7 | import org.github.roger.cache.ICache;
8 | import org.github.roger.manager.AbstractCacheManager;
9 | import org.github.roger.message.RedisPubSubMessage;
10 | import org.springframework.data.redis.connection.Message;
11 | import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
12 |
13 | import java.util.Collection;
14 |
15 | @Setter
16 | @Slf4j
17 | public class RedisMessageListener extends MessageListenerAdapter {
18 |
19 | /**
20 | * 缓存管理器
21 | */
22 | private AbstractCacheManager cacheManager;
23 |
24 |
25 | @Override
26 | public void onMessage(Message message, byte[] pattern) {
27 | super.onMessage(message, pattern);
28 | // 解析订阅发布的信息,获取缓存的名称和缓存的key
29 | RedisPubSubMessage redisPubSubMessage = (RedisPubSubMessage) cacheManager.getRedisTemplate()
30 | .getValueSerializer().deserialize(message.getBody());
31 | log.debug("redis消息订阅者接收到频道【{}】发布的消息。消息内容:{}", new String(message.getChannel()), JSON.toJSONString(redisPubSubMessage));
32 |
33 | // 根据缓存名称获取多级缓存,可能有多个
34 | Collection caches = cacheManager.getCache(redisPubSubMessage.getCacheName());
35 | for (ICache cache : caches) {
36 | // 判断缓存是否是多级缓存
37 | if (cache != null && cache instanceof MultiLayeringCache) {
38 | switch (redisPubSubMessage.getMessageType()) {
39 | case EVICT:
40 | // 获取一级缓存,并删除一级缓存数据
41 | ((MultiLayeringCache) cache).getFirstCache().evict(redisPubSubMessage.getKey());
42 | log.info("删除一级缓存{}数据,key={}", redisPubSubMessage.getCacheName(), redisPubSubMessage.getKey());
43 | break;
44 |
45 | case CLEAR:
46 | // 获取一级缓存,并删除一级缓存数据
47 | ((MultiLayeringCache) cache).getFirstCache().clear();
48 | log.info("清除一级缓存{}数据", redisPubSubMessage.getCacheName());
49 | break;
50 |
51 | default:
52 | log.error("接收到没有定义的订阅消息频道数据");
53 | break;
54 | }
55 |
56 | }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/multi-layering-cache-core/src/main/java/org/github/roger/listener/RedisPublisher.java:
--------------------------------------------------------------------------------
1 | package org.github.roger.listener;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.springframework.data.redis.core.RedisTemplate;
5 | import org.springframework.data.redis.listener.ChannelTopic;
6 |
7 | @Slf4j
8 | public class RedisPublisher {
9 |
10 | private RedisPublisher() {
11 | }
12 |
13 | /**
14 | * 发布消息到频道(Channel)
15 | *
16 | * @param redisTemplate redis客户端
17 | * @param channelTopic 发布预订阅的频道
18 | * @param message 消息内容
19 | */
20 | public static void publisher(RedisTemplate redisTemplate, ChannelTopic channelTopic, Object message) {
21 | redisTemplate.convertAndSend(channelTopic.toString(), message);
22 | log.debug("redis消息发布者向频道【{}】发布了【{}】消息", channelTopic.toString(), message.toString());
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/multi-layering-cache-core/src/main/java/org/github/roger/manager/AbstractCacheManager.java:
--------------------------------------------------------------------------------
1 | package org.github.roger.manager;
2 |
3 | import lombok.Getter;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.github.roger.cache.ICache;
6 | import org.github.roger.listener.RedisMessageListener;
7 | import org.github.roger.settings.MultiLayeringCacheSetting;
8 | import org.springframework.beans.factory.DisposableBean;
9 | import org.springframework.beans.factory.InitializingBean;
10 | import org.springframework.context.SmartLifecycle;
11 | import org.springframework.data.redis.core.RedisTemplate;
12 | import org.springframework.data.redis.listener.ChannelTopic;
13 | import org.springframework.data.redis.listener.RedisMessageListenerContainer;
14 | import org.springframework.util.CollectionUtils;
15 |
16 | import java.util.Collection;
17 | import java.util.Collections;
18 | import java.util.LinkedHashSet;
19 | import java.util.Set;
20 | import java.util.concurrent.ConcurrentHashMap;
21 | import java.util.concurrent.ConcurrentMap;
22 |
23 | @Slf4j
24 | public abstract class AbstractCacheManager implements ICacheManager,InitializingBean,DisposableBean,SmartLifecycle {
25 |
26 | /**
27 | * redis pub/sub 容器
28 | */
29 | private final RedisMessageListenerContainer container = new RedisMessageListenerContainer();
30 |
31 | /**
32 | * redis pub/sub 监听器
33 | */
34 | private final RedisMessageListener messageListener = new RedisMessageListener();
35 |
36 |
37 | /**
38 | * 缓存容器
39 | * 外层key是cache_name
40 | * 里层key是[一级缓存有效时间-二级缓存有效时间-二级缓存自动刷新时间]
41 | */
42 | private final ConcurrentMap> cacheContainer = new ConcurrentHashMap<>(16);
43 |
44 | /**
45 | * 缓存名称容器
46 | */
47 | private volatile Set cacheNames = new LinkedHashSet<>();
48 |
49 | /**
50 | * CacheManager 容器
51 | */
52 | protected static Set cacheManagers = new LinkedHashSet<>();
53 |
54 | /**
55 | * redis 客户端
56 | */
57 | @Getter
58 | protected RedisTemplate redisTemplate;
59 |
60 | public static Set getCacheManager() {
61 | return cacheManagers;
62 | }
63 |
64 | @Override
65 | public Collection getCache(String name) {
66 | ConcurrentMap cacheMap = this.cacheContainer.get(name);
67 | if (CollectionUtils.isEmpty(cacheMap)) {
68 | return Collections.emptyList();
69 | }
70 | return cacheMap.values();
71 | }
72 |
73 | @Override
74 | public Collection getCacheNames() {
75 | return this.cacheNames;
76 | }
77 |
78 | // Lazy cache initialization on access
79 | @Override
80 | public ICache getCache(String name, MultiLayeringCacheSetting multiLayeringCacheSetting) {
81 | // 第一次获取缓存Cache,如果有直接返回,如果没有加锁往容器里里面放Cache
82 | ConcurrentMap cacheMap = this.cacheContainer.get(name);
83 | if (!CollectionUtils.isEmpty(cacheMap)) {
84 | if (cacheMap.size() > 1) {
85 | log.warn("缓存名称为 {} 的缓存,存在两个不同的过期时间配置,请一定注意保证缓存的key唯一性,否则会出现缓存过期时间错乱的情况", name);
86 | }
87 | ICache iCache = cacheMap.get(multiLayeringCacheSetting.getInternalKey());
88 | if (iCache != null) {
89 | return iCache;
90 | }
91 | }
92 |
93 | // 第二次获取缓存Cache,加锁往容器里里面放Cache
94 | synchronized (this.cacheContainer) {
95 | cacheMap = this.cacheContainer.get(name);
96 | if (!CollectionUtils.isEmpty(cacheMap)) {
97 | // 从容器中获取缓存
98 | ICache iCache = cacheMap.get(multiLayeringCacheSetting.getInternalKey());
99 | if (iCache != null) {
100 | return iCache;
101 | }
102 | } else {
103 | cacheMap = new ConcurrentHashMap<>(16);
104 | cacheContainer.put(name, cacheMap);
105 | // 更新缓存名称
106 | updateCacheNames(name);
107 | // 创建redis监听
108 | addMessageListener(name);
109 | }
110 |
111 | // 新建一个Cache对象
112 | ICache iCache = getMissingCache(name, multiLayeringCacheSetting);
113 | if (iCache != null) {
114 | // 装饰Cache对象
115 | iCache = decorateCache(iCache);
116 | // 将新的Cache对象放到容器
117 | cacheMap.put(multiLayeringCacheSetting.getInternalKey(), iCache);
118 | //同一缓存名称,缓存的过期时间设置要唯一
119 | if (cacheMap.size() > 1) {
120 | log.warn("缓存名称为 {} 的缓存,存在两个不同的过期时间配置,请一定注意保证缓存的key唯一性,否则会出现缓存过期时间错乱的情况", name);
121 | }
122 | }
123 |
124 | return iCache;
125 | }
126 | }
127 |
128 | /**
129 | * 根据缓存名称在CacheManager中没有找到对应Cache时,通过该方法新建一个对应的Cache实例
130 | *
131 | * @param name 缓存名称
132 | * @param multiLayeringCacheSetting 缓存配置
133 | * @return {@link ICache}
134 | */
135 | protected abstract ICache getMissingCache(String name, MultiLayeringCacheSetting multiLayeringCacheSetting);
136 |
137 | /**
138 | * 更新缓存名称容器
139 | *
140 | * @param name 需要添加的缓存名称
141 | */
142 | private void updateCacheNames(String name) {
143 | cacheNames.add(name);
144 | }
145 |
146 | /**
147 | * 获取Cache对象的装饰示例
148 | *
149 | * @param iCache 需要添加到CacheManager的Cache实例
150 | * @return 装饰过后的Cache实例
151 | */
152 | protected ICache decorateCache(ICache iCache) {
153 | return iCache;
154 | }
155 |
156 | /**
157 | * 添加消息监听
158 | *
159 | * @param name 缓存名称
160 | */
161 | protected void addMessageListener(String name) {
162 | container.addMessageListener(messageListener, new ChannelTopic(name));
163 | }
164 |
165 | @Override
166 | public void afterPropertiesSet() throws Exception {
167 | messageListener.setCacheManager(this);
168 | container.setConnectionFactory(getRedisTemplate().getConnectionFactory());
169 | container.afterPropertiesSet();
170 | messageListener.afterPropertiesSet();
171 | }
172 |
173 | @Override
174 | public void destroy() throws Exception {
175 | container.destroy();
176 | }
177 |
178 | @Override
179 | public boolean isAutoStartup() {
180 | return container.isAutoStartup();
181 | }
182 |
183 | @Override
184 | public void stop(Runnable callback) {
185 | container.stop(callback);
186 | }
187 |
188 | @Override
189 | public void start() {
190 | container.start();
191 | }
192 |
193 | @Override
194 | public void stop() {
195 | container.stop();
196 | }
197 |
198 | @Override
199 | public boolean isRunning() {
200 | return container.isRunning();
201 | }
202 |
203 | @Override
204 | public int getPhase() {
205 | return container.getPhase();
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/multi-layering-cache-core/src/main/java/org/github/roger/manager/ICacheManager.java:
--------------------------------------------------------------------------------
1 | package org.github.roger.manager;
2 |
3 |
4 | import org.github.roger.cache.ICache;
5 | import org.github.roger.settings.MultiLayeringCacheSetting;
6 |
7 | import java.util.Collection;
8 |
9 | /**
10 | * 缓存管理器
11 | * 允许通过缓存名称来获的对应的 {@link ICache}.
12 | *
13 | */
14 | public interface ICacheManager {
15 |
16 | /**
17 | * 根据缓存名称返回对应的{@link Collection}.
18 | *
19 | * @param name 缓存的名称 (不能为 {@code null})
20 | * @return 返回对应名称的Cache, 如果没找到返回 {@code null}
21 | */
22 | Collection getCache(String name);
23 |
24 | /**
25 | * 根据缓存名称返回对应的{@link ICache},如果没有找到就新建一个并放到容器
26 | *
27 | * @param name 缓存名称
28 | * @param multiLayeringCacheSetting 多级缓存配置
29 | * @return {@link ICache}
30 | */
31 | ICache getCache(String name, MultiLayeringCacheSetting multiLayeringCacheSetting);
32 |
33 | /**
34 | * 获取所有缓存名称的集合
35 | *
36 | * @return 所有缓存名称的集合
37 | */
38 | Collection getCacheNames();
39 | }
40 |
--------------------------------------------------------------------------------
/multi-layering-cache-core/src/main/java/org/github/roger/message/RedisPubSubMessage.java:
--------------------------------------------------------------------------------
1 | package org.github.roger.message;
2 |
3 | import lombok.Data;
4 | import org.github.roger.enumeration.RedisPubSubMessageType;
5 |
6 | import java.io.Serializable;
7 |
8 | @Data
9 | public class RedisPubSubMessage implements Serializable {
10 |
11 | /**
12 | * 缓存名称
13 | */
14 | private String cacheName;
15 |
16 | /**
17 | * 缓存key
18 | */
19 | private Object key;
20 |
21 | /**
22 | * 消息类型
23 | */
24 | private RedisPubSubMessageType messageType;
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/multi-layering-cache-core/src/main/java/org/github/roger/serializer/FastJsonRedisSerializer.java:
--------------------------------------------------------------------------------
1 | package org.github.roger.serializer;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.alibaba.fastjson.JSONArray;
5 | import com.alibaba.fastjson.parser.ParserConfig;
6 | import com.alibaba.fastjson.serializer.SerializerFeature;
7 | import org.github.roger.enumeration.Type;
8 | import org.github.roger.exception.SerializationException;
9 | import org.github.roger.support.NullValue;
10 | import org.github.roger.utils.SerializationUtils;
11 | import org.slf4j.Logger;
12 | import org.slf4j.LoggerFactory;
13 | import org.springframework.data.redis.serializer.RedisSerializer;
14 |
15 | import java.nio.charset.Charset;
16 |
17 |
18 | public class FastJsonRedisSerializer implements RedisSerializer {
19 | private Logger logger = LoggerFactory.getLogger(FastJsonRedisSerializer.class);
20 |
21 | private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
22 |
23 | private Class clazz;
24 |
25 | /**
26 | * 允许所有包的序列化和反序列化,不推荐
27 | *
28 | * @param clazz Class
29 | */
30 | @Deprecated
31 | public FastJsonRedisSerializer(Class clazz) {
32 | super();
33 | this.clazz = clazz;
34 | try {
35 | ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
36 | } catch (Throwable e) {
37 | logger.warn("fastjson 版本太低,反序列化有被攻击的风险", e);
38 | }
39 | logger.warn("fastjson 反序列化有被攻击的风险,推荐使用白名单的方式,详情参考:https://www.jianshu.com/p/a92ecc33fd0d");
40 | }
41 |
42 | /**
43 | * 指定小范围包的序列化和反序列化,具体原因可以参考:
44 | * https://www.jianshu.com/p/a92ecc33fd0d
45 | *
46 | * @param clazz clazz
47 | * @param packages 白名单包名,如:"com.xxx."
48 | */
49 | public FastJsonRedisSerializer(Class clazz, String... packages) {
50 | super();
51 | this.clazz = clazz;
52 | try {
53 | ParserConfig.getGlobalInstance().addAccept("org.github.roger.");
54 | if (packages != null && packages.length > 0) {
55 | for (String packageName : packages) {
56 | ParserConfig.getGlobalInstance().addAccept(packageName);
57 | }
58 | }
59 | } catch (Throwable e) {
60 | logger.warn("fastjson 版本太低,反序列化有被攻击的风险", e);
61 | }
62 | }
63 |
64 | @Override
65 | public byte[] serialize(T t) throws SerializationException {
66 | try {
67 | return JSON.toJSONString(new FastJsonSerializerWrapper(t), SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
68 | } catch (Exception e) {
69 | throw new SerializationException(String.format("FastJsonRedisSerializer 序列化异常: %s, 【JSON:%s】",
70 | e.getMessage(), JSON.toJSONString(t)), e);
71 |
72 | }
73 |
74 | }
75 |
76 | @Override
77 | public T deserialize(byte[] bytes) throws SerializationException {
78 | if (SerializationUtils.isEmpty(bytes)) {
79 | return null;
80 | }
81 |
82 | String str = new String(bytes, DEFAULT_CHARSET);
83 | try {
84 | FastJsonSerializerWrapper wrapper = JSON.parseObject(str, FastJsonSerializerWrapper.class);
85 | switch (Type.parse(wrapper.getType())) {
86 | case STRING:
87 | return (T) wrapper.getContent();
88 | case OBJECT:
89 | case SET:
90 |
91 | if (wrapper.getContent() instanceof NullValue) {
92 | return null;
93 | }
94 |
95 | return (T) wrapper.getContent();
96 |
97 | case LIST:
98 |
99 | return (T) ((JSONArray) wrapper.getContent()).toJavaList(clazz);
100 |
101 | case NULL:
102 |
103 | return null;
104 | default:
105 | throw new SerializationException("不支持反序列化的对象类型: " + wrapper.getType());
106 | }
107 | } catch (Exception e) {
108 | throw new SerializationException(String.format("FastJsonRedisSerializer 反序列化异常: %s, 【JSON:%s】",
109 | e.getMessage(), str), e);
110 | }
111 | }
112 | }
--------------------------------------------------------------------------------
/multi-layering-cache-core/src/main/java/org/github/roger/serializer/FastJsonSerializerWrapper.java:
--------------------------------------------------------------------------------
1 | package org.github.roger.serializer;
2 |
3 | import lombok.Data;
4 | import lombok.Getter;
5 | import lombok.NoArgsConstructor;
6 | import org.github.roger.enumeration.Type;
7 | import org.github.roger.exception.SerializationException;
8 |
9 | import java.util.List;
10 | import java.util.Set;
11 |
12 | /**
13 | * 序列化包装类
14 | *
15 | */
16 | @NoArgsConstructor
17 | @Data
18 | public class FastJsonSerializerWrapper {
19 |
20 | private Object content;
21 |
22 | private String type;
23 |
24 | public FastJsonSerializerWrapper(Object content) {
25 | this.content = content;
26 |
27 | if (content == null) {
28 | this.type = Type.NULL.name();
29 | return;
30 | }
31 |
32 | if (content instanceof String || content instanceof Integer
33 | || content instanceof Long || content instanceof Double
34 | || content instanceof Float || content instanceof Boolean
35 | || content instanceof Byte || content instanceof Character
36 | || content instanceof Short) {
37 |
38 | this.type = Type.STRING.name();
39 | return;
40 | }
41 |
42 | if (content instanceof List) {
43 | this.type = Type.LIST.name();
44 | return;
45 | }
46 |
47 | if (content instanceof Set) {
48 | this.type = Type.SET.name();
49 | return;
50 | }
51 |
52 | if (content.getClass().isArray()) {
53 | throw new SerializationException("FastJsonRedisSerializer 序列化不支持枚数组型");
54 | }
55 |
56 | if (content.getClass().isEnum()) {
57 | throw new SerializationException("FastJsonRedisSerializer 序列化不支持枚举类型");
58 | }
59 |
60 | this.type = Type.OBJECT.name();
61 | }
62 | }
--------------------------------------------------------------------------------
/multi-layering-cache-core/src/main/java/org/github/roger/serializer/KryoRedisSerializer.java:
--------------------------------------------------------------------------------
1 | package org.github.roger.serializer;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.esotericsoftware.kryo.Kryo;
5 | import com.esotericsoftware.kryo.io.Input;
6 | import com.esotericsoftware.kryo.io.Output;
7 | import org.github.roger.exception.SerializationException;
8 | import org.github.roger.support.NullValue;
9 | import org.github.roger.utils.SerializationUtils;
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 | import org.springframework.data.redis.serializer.RedisSerializer;
13 |
14 | import java.io.ByteArrayOutputStream;
15 |
16 | /**
17 | * @param T
18 | */
19 | public class KryoRedisSerializer implements RedisSerializer {
20 | Logger logger = LoggerFactory.getLogger(KryoRedisSerializer.class);
21 | private static final ThreadLocal kryos = ThreadLocal.withInitial(Kryo::new);
22 |
23 | private Class clazz;
24 |
25 | public KryoRedisSerializer(Class clazz) {
26 | super();
27 | this.clazz = clazz;
28 | }
29 |
30 | @Override
31 | public byte[] serialize(T t) throws SerializationException {
32 | if (t == null) {
33 | return SerializationUtils.EMPTY_ARRAY;
34 | }
35 |
36 | Kryo kryo = kryos.get();
37 | // 设置成false 序列化速度更快,但是遇到循环应用序列化器会报栈内存溢出
38 | kryo.setReferences(false);
39 | kryo.register(clazz);
40 |
41 | try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
42 | Output output = new Output(baos)) {
43 | kryo.writeClassAndObject(output, t);
44 | output.flush();
45 | return baos.toByteArray();
46 | } catch (Exception e) {
47 | throw new SerializationException(String.format("KryoRedisSerializer 序列化异常: %s, 【JSON:%s】",
48 | e.getMessage(), JSON.toJSONString(t)), e);
49 | } finally {
50 | kryos.remove();
51 | }
52 | }
53 |
54 | @Override
55 | public T deserialize(byte[] bytes) throws SerializationException {
56 | if (SerializationUtils.isEmpty(bytes)) {
57 | return null;
58 | }
59 |
60 | Kryo kryo = kryos.get();
61 | // 设置成false 序列化速度更快,但是遇到循环应用序列化器会报栈内存溢出
62 | kryo.setReferences(false);
63 | kryo.register(clazz);
64 |
65 | try (Input input = new Input(bytes)) {
66 |
67 | Object result = kryo.readClassAndObject(input);
68 | if (result instanceof NullValue) {
69 | return null;
70 | }
71 | return (T) result;
72 | } catch (Exception e) {
73 | throw new SerializationException(String.format("FastJsonRedisSerializer 反序列化异常: %s, 【JSON:%s】",
74 | e.getMessage(), JSON.toJSONString(bytes)), e);
75 | } finally {
76 | kryos.remove();
77 | }
78 | }
79 | }
--------------------------------------------------------------------------------
/multi-layering-cache-core/src/main/java/org/github/roger/serializer/StringRedisSerializer.java:
--------------------------------------------------------------------------------
1 | package org.github.roger.serializer;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import org.springframework.data.redis.serializer.RedisSerializer;
5 | import org.springframework.util.Assert;
6 |
7 | import java.nio.charset.Charset;
8 |
9 | /**
10 | * 必须重写序列化器,否则@Cacheable注解的key会报类型转换错误
11 | *
12 | */
13 | public class StringRedisSerializer implements RedisSerializer