diff --git a/.gitignore b/.gitignore
index 32858aa..fa3193d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,5 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
+*.iml
+.idea/spring-data-redis-tools.iml
diff --git a/README.md b/README.md
index b78706b..634eda5 100644
--- a/README.md
+++ b/README.md
@@ -1,34 +1,109 @@
# spring-data-redis-tools
-spring data redis 封装工具类
+- RedisTemplate封装工具类 `redisTools`
+- 可视化分布式ID生成器 `distributedId` (eg:JD202501010001)
+- 可靠分布式锁工具类 `distributedLock`(lua脚本实现原子性解决断电问题、valueId避免错误释放问题)
+- [Redis技术总结思维导图](#user-content-redis技术总结)
***
## Maven
-```html
-
- redis.clients
- jedis
- 2.6.2
-
-
- org.springframework.data
- spring-data-redis
- 1.5.1.RELEASE
-
+```xml
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
```
-## applicationContext.xml
-```html
-
-
-
-
-
-
-
-
-
-
-
+## RedisConfiguration
+```java
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+import redis.clients.jedis.JedisPoolConfig;
+
+/**
+ * @author wellJay
+ */
+@Configuration
+@EnableCaching
+public class RedisConfiguration {
+ //过期时间一天
+ private static final int DEFAULT_EXPIRE_TIME = 3600 * 24;
+
+ //从配置文件读取redis参数
+ @Autowired
+ private CloudConfigProperties cloudConfigProperties;
+
+ /**
+ * jedisPoolConfig config
+ */
+ @Bean
+ public JedisPoolConfig jedisPoolConfig() {
+ JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
+ jedisPoolConfig.setMaxIdle(cloudConfigProperties.getRedis().getMaxIdle());
+ jedisPoolConfig.setMinIdle(cloudConfigProperties.getRedis().getMinIdle());
+ jedisPoolConfig.setTestOnBorrow(cloudConfigProperties.getRedis().getTestOnBorrow());
+ jedisPoolConfig.setTestOnReturn(cloudConfigProperties.getRedis().getTestOnReturn());
+ return jedisPoolConfig;
+ }
+
+ /**
+ * JedisConnectionFactory
+ */
+ @Bean
+ public JedisConnectionFactory jedisConnectionFactory(JedisPoolConfig jedisPoolConfig) {
+ JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
+ jedisConnectionFactory.setHostName(cloudConfigProperties.getRedis().getHost());
+ jedisConnectionFactory.setPort(cloudConfigProperties.getRedis().getPort());
+ jedisConnectionFactory.setPassword(cloudConfigProperties.getRedis().getPassword());
+ jedisConnectionFactory.setTimeout(cloudConfigProperties.getRedis().getTimeout());
+ jedisConnectionFactory.setUsePool(true);
+ jedisConnectionFactory.setPoolConfig(jedisPoolConfig);
+ return jedisConnectionFactory;
+ }
+
+ /**
+ * RedisTemplate
+ * 从执行时间上来看,JdkSerializationRedisSerializer是最高效的(毕竟是JDK原生的),但是是序列化的结果字符串是最长的。
+ * JSON由于其数据格式的紧凑性,序列化的长度是最小的,时间比前者要多一些。
+ * 所以个人的选择是倾向使用JacksonJsonRedisSerializer作为POJO的序列器。
+ */
+ @Bean
+ public RedisTemplate redisTemplate(JedisConnectionFactory jedisConnectionFactory) {
+ RedisTemplate, ?> redisTemplate = new RedisTemplate();
+ redisTemplate.setConnectionFactory(jedisConnectionFactory);
+ redisTemplate.setDefaultSerializer(new StringRedisSerializer());
+ //设置普通value序列化方式
+ redisTemplate.setValueSerializer(new StringRedisSerializer());
+ redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
+ return redisTemplate;
+ }
+
+ @Bean
+ public CacheManager cacheManager(RedisTemplate redisTemplate) {
+ RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate);
+ redisCacheManager.setDefaultExpiration(DEFAULT_EXPIRE_TIME);
+ return redisCacheManager;
+ }
+}
```
+
+## How To Use
+1、注入util方式,适用于复杂的业务处理
+```java
+@Autowired
+private RedisCacheUtil redisCacheUtil;
+```
+2、Spring注解方式适用于简单的数据缓存
+```java
+@Cacheable(value = Constants.RedisKey.XXX_KEY)
+```
+## Redis技术总结
+
+
+
+
diff --git "a/Redis\346\212\200\346\234\257\346\200\273\347\273\223.png" "b/Redis\346\212\200\346\234\257\346\200\273\347\273\223.png"
new file mode 100644
index 0000000..114d8d9
Binary files /dev/null and "b/Redis\346\212\200\346\234\257\346\200\273\347\273\223.png" differ
diff --git a/distributedId/OrderNumberGenerate.java b/distributedId/OrderNumberGenerate.java
new file mode 100644
index 0000000..c2df8fb
--- /dev/null
+++ b/distributedId/OrderNumberGenerate.java
@@ -0,0 +1,50 @@
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Component;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * Created by WenJie on 2015/11/3.
+ * 可视化分布式唯一订单号生成器 20151103001
+ */
+@Component
+public class OrderNumberGenerate {
+
+ public static final String FROMAT_STR = "yyyyMMddHHmm";
+ private static StringRedisTemplate staticTemplate;
+
+ @Autowired
+ public static void setStaticTemplate(StringRedisTemplate staticTemplate) {
+ OrderNumberGenerate.staticTemplate = staticTemplate;
+ }
+
+ private OrderNumberGenerate() {
+ }
+
+ /**
+ * 生成订单编号
+ * date存redis,只要date变化则直接increment=1,否则就是并发冲突直接increment++保证分布式唯一id
+ *
+ * public static final String FLOWER_ORDER_NUMBER_INCR = "order_number_incr";
+ * public static final String FLOWER_ORDER_NUMBER_DATE = "order_number_date";
+ *
+ * @return
+ */
+ public static synchronized String getOrderNo() {
+ String str = new SimpleDateFormat(FROMAT_STR).format(new Date());
+ String date = staticTemplate.opsForValue().get(CacheKey.FLOWER_ORDER_NUMBER_DATE);
+ if (date == null || !date.equals(str)) {
+ //保存时间
+ staticTemplate.opsForValue().set(CacheKey.FLOWER_ORDER_NUMBER_DATE, str);
+ //缓存清除
+ staticTemplate.delete(CacheKey.FLOWER_ORDER_NUMBER_INCR);
+ }
+ //缓存++
+ Long incrementValue = staticTemplate.opsForValue().increment(CacheKey.FLOWER_ORDER_NUMBER_INCR, 1);
+ long orderNo = Long.parseLong(date) * 10000;
+ orderNo += incrementValue;
+ return orderNo + "";
+ }
+}
diff --git a/distributedLock/README.md b/distributedLock/README.md
new file mode 100644
index 0000000..4005d8d
--- /dev/null
+++ b/distributedLock/README.md
@@ -0,0 +1,6 @@
+- `RedisLockTool.tryGetDistributedLock();` GET LOCK key = caller className&method value = Current Thread Id
+- `RedisLockTool.tryGetDistributedLock(String lockKey, String requestId);` GET LOCK
+- `RedisLockTool.tryGetDistributedLock(String lockKey, String requestId, Integer expireSecond);`
+- `RedisLockTool.tryGetDistributedLock(String lockKey, String requestId, Integer expireSecond, Long loopTimes, Long sleepInterval);`
+
+redis集群情况下推荐[RedLock](https://github.com/redisson/redisson)
diff --git a/distributedLock/RedisLockTool.java b/distributedLock/RedisLockTool.java
new file mode 100644
index 0000000..4bf8f47
--- /dev/null
+++ b/distributedLock/RedisLockTool.java
@@ -0,0 +1,137 @@
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.stereotype.Component;
+
+import java.util.Collections;
+import java.util.concurrent.TimeUnit;
+/**
+ * @author wenjie
+ * @date 2018/5/7 0007 16:44
+ */
+@Component
+public final class RedisLockTool {
+
+ private static final Long SUCCESS = 1L;
+ public static final String LOCK_SCRIPT_STR = "if redis.call('set',KEYS[1],ARGV[1],'EX',ARGV[2],'NX') then return 1 else return 0 end";
+ public static final String UNLOCK_SCRIPT_STR = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
+
+ //default value
+ public static final Integer DEFAULT_EXPIRE_SECOND = 60;
+ public static final Long DEFAULT_LOOP_TIMES = 10L;
+ public static final Long DEFAULT_SLEEP_INTERVAL = 500L;
+ public static final String PACKAGE_NAME_SPLIT_STR = "\\.";
+ public static final String CLASS_AND_METHOD_CONCAT_STR = "->";
+
+ private static RedisTemplate redisTemplate;
+
+ @Autowired
+ public void init(RedisTemplate redisTemplate) {
+ this.redisTemplate = redisTemplate;
+ }
+
+ /**
+ * 得到分布式锁
+ * 默认key:调用者类名
+ *
+ * @return
+ * @throws InterruptedException
+ */
+ public static boolean tryGetDistributedLock() {
+ String callerKey = getCurrentThreadCaller();
+ String requestId = String.valueOf(Thread.currentThread().getId());
+ return tryGetDistributedLock(callerKey, requestId);
+ }
+
+ /**
+ * @param lockKey 锁名称
+ * @param requestId 随机请求id
+ * @return
+ * @throws InterruptedException
+ */
+ public static boolean tryGetDistributedLock(String lockKey, String requestId) {
+ return tryGetDistributedLock(lockKey, requestId, DEFAULT_EXPIRE_SECOND);
+ }
+
+ /**
+ * @param lockKey key
+ * @param requestId 随机请求id
+ * @param expireSecond 超时秒
+ * @return
+ * @throws InterruptedException
+ */
+ public static boolean tryGetDistributedLock(String lockKey, String requestId, Integer expireSecond) {
+ return tryGetDistributedLock(lockKey, requestId, expireSecond, DEFAULT_LOOP_TIMES, DEFAULT_SLEEP_INTERVAL);
+ }
+
+
+ /**
+ * 加锁
+ *
+ * @param lockKey key
+ * @param requestId 随机请求id
+ * @param expireSecond 超时秒
+ * @param loopTimes 循环次数
+ * @param sleepInterval 等待间隔(毫秒)
+ * @return
+ */
+ public static boolean tryGetDistributedLock(String lockKey, String requestId, Integer expireSecond, Long loopTimes, Long sleepInterval) {
+ DefaultRedisScript redisScript = new DefaultRedisScript<>(LOCK_SCRIPT_STR, Long.class);
+ while (loopTimes-- >= 0) {
+ Object result = redisTemplate.execute(redisScript, Lists.newArrayList(lockKey), requestId, String.valueOf(expireSecond));
+ if (SUCCESS.equals(result)) {
+ return true;
+ }
+ try {
+ TimeUnit.MILLISECONDS.sleep(sleepInterval);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ continue;
+ }
+ return false;
+ }
+
+
+ /**
+ * 释放锁
+ *
+ * @return
+ */
+ public static boolean releaseDistributedLock() {
+ String callerKey = getCurrentThreadCaller();
+ String requestId = String.valueOf(Thread.currentThread().getId());
+ return releaseDistributedLock(callerKey, requestId);
+ }
+
+ /**
+ * 释放锁
+ *
+ * @param lockKey key
+ * @param requestId 加锁的请求id
+ * @return
+ */
+ public static boolean releaseDistributedLock(String lockKey, String requestId) {
+ DefaultRedisScript redisScript = new DefaultRedisScript<>(UNLOCK_SCRIPT_STR, Long.class);
+ Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId);
+ if (SUCCESS.equals(result)) {
+ return true;
+ }
+ return false;
+ }
+
+ private static String getSimpleClassName(String className) {
+ String[] splits = className.split(PACKAGE_NAME_SPLIT_STR);
+ return splits[splits.length - 1];
+ }
+
+ /**
+ * Get caller
+ *
+ * @return
+ */
+ private static String getCurrentThreadCaller() {
+ StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[3];
+ return getSimpleClassName(stackTraceElement.getClassName()) + CLASS_AND_METHOD_CONCAT_STR + stackTraceElement.getMethodName();
+ }
+}
diff --git a/distributedLock/Test.java b/distributedLock/Test.java
new file mode 100644
index 0000000..2fe2c3c
--- /dev/null
+++ b/distributedLock/Test.java
@@ -0,0 +1,60 @@
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.util.Random;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = Application.class)
+public class ApplicationTests {
+
+ @Test
+ public void distributedLock() throws InterruptedException {
+ CountDownLatch countDownLatch = new CountDownLatch(10);
+
+ for (int i = 0; i < 10; i++) {
+ Thread thread = new Thread(new Tester(), "Thread-" + i);
+ countDownLatch.countDown();
+ thread.start();
+ }
+
+ countDownLatch.await();
+
+ System.out.println("down");
+
+ Thread.sleep(Integer.MAX_VALUE);
+ }
+
+ class Tester implements Runnable {
+
+ public static final int RANDOM_NUMBER_RANGE = 3;
+
+ @Override
+ public void run() {
+ //获取锁, key:caller className&method value:Current Thread Id
+ //也可以传参 key value 可选[expireSecond loopTimes sleepInterval]
+ boolean result = RedisLockTool.tryGetDistributedLock();
+ if (result) {
+ System.out.println(Thread.currentThread().getName() + ": get lock");
+ } else {
+ System.err.println(Thread.currentThread().getName() + ": get lock timeout");
+ }
+
+ //if get the lock
+ if (result) {
+ try {
+ TimeUnit.SECONDS.sleep(new Random().nextInt(RANDOM_NUMBER_RANGE));
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ RedisLockTool.releaseDistributedLock();
+ System.out.println(Thread.currentThread().getName() + ": release lock");
+ }
+ }
+ }
+}
+
diff --git a/impl/RedisDaoTool.java b/impl/RedisDaoTool.java
deleted file mode 100644
index a7c7fea..0000000
--- a/impl/RedisDaoTool.java
+++ /dev/null
@@ -1,386 +0,0 @@
-package com.brandbigdata.rep.redis.impl;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.springframework.dao.DataAccessException;
-import org.springframework.data.redis.connection.RedisConnection;
-import org.springframework.data.redis.core.RedisCallback;
-import org.springframework.stereotype.Repository;
-
-import com.brandbigdata.rep.redis.inter.AbstractBaseRedisDao;
-
-/**
- * @author Wen Jie
- *
- */
-@Repository
-public class StringRedisDao extends AbstractBaseRedisDao {
-
- // ----------------------------------------Object----------------------------------------
- /**
- * 设置对象
- *
- * @param key
- * @param object
- * @param timeout
- * @param 对象class
- * @return
- * @throws Exception
- */
- public boolean addObject(final String key, final Object object, final Long timeout, Class> clazz) throws BbdException {
- redisTemplate.opsForValue().set(key, clazz.cast(object));
- return true;
- }
-
- /**
- * 获得对象
- *
- * @param key
- * @return
- */
- public Object getObject(final String key) {
- return redisTemplate.opsForValue().get(key);
- }
-
- // ---------------------------------------String-----------------------------------------
-
- /**
- * 新增String ----setNX 不存在则增加 ------------------------------
- *
- * @param key
- * 键
- * @param value
- * 值
- * @param timout
- * 超时(秒)
- * @return true 操作成功,false 已存在值
- */
- public boolean addString(final String key, final String value, final Long timeout) {
- boolean result = redisTemplate.execute(new RedisCallback() {
- public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
- Boolean result = connection.setNX(key.getBytes(), value.getBytes());
- if (result == false)
- return result;
- if (timeout != null && timeout > 0)
- connection.expire(key.getBytes(), timeout);
- return result;
- }
- });
- return result;
- }
-
- /**
- * 批量新增String---setNx 不存在则增加
- *
- * @param keyValueList
- * 键值对的map
- * @param timeout
- * 超时处理
- * @return
- */
- public boolean addString(final Map keyValueList, final Long timeout) {
- boolean result = redisTemplate.execute(new RedisCallback() {
- public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
- for (String key : keyValueList.keySet()) {
- connection.setNX(key.getBytes(), keyValueList.get(key).getBytes());
- if (timeout != null && timeout > 0)
- connection.expire(key.getBytes(), timeout);
- }
- return true;
- }
- }, false, true);
- return result;
- }
-
- /**
- * 通过key获取单个
- *
- * @param key
- * @return
- */
- public String getString(final String key) {
- String value = redisTemplate.execute(new RedisCallback() {
- public String doInRedis(RedisConnection connection) throws DataAccessException {
- byte[] result = connection.get(key.getBytes());
- if(result != null && result.length > 0)
- return new String(result);
- return null;
- }
- });
- return value;
- }
-
- /**
- * 修改 String
- *
- * @param key
- * @param value
- * @return
- */
- /*
- 重新Set等于Update
- public boolean updateString(final String key, final String value) {
- if (getString(key) == null) {
- throw new NullPointerException("数据行不存在, key = " + key);
- }
- boolean result = redisTemplate.execute(new RedisCallback() {
- public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
- connection.set(key.getBytes(), value.getBytes());
- return true;
- }
- });
- return result;
- }
- /
-
- // ---------------------------------------List-----------------------------------------
- /**
- * 新增Hash ----setNX 不存在则增加 ------------------------------
- *
- * @param key
- * 键
- * @param value
- * 值
- * @param timout
- * 超时(秒)
- * @return true 操作成功,false 已存在值
- */
- public boolean addHash(final String key, final String field, final String value, final Long timeout) {
- boolean result = redisTemplate.execute(new RedisCallback() {
- public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
- Boolean result = connection.hSetNX(key.getBytes(), field.getBytes(), value.getBytes());
- if (result == false)
- return result;
- if (timeout != null && timeout > 0)
- connection.expire(key.getBytes(), timeout);
- return result;
- }
- });
- return result;
- }
-
- /**
- * 批量新增Hash ----setNX 不存在则增加 ------------------------------
- *
- * @param key
- * 键
- * @param value
- * 值
- * @param timout
- * 超时(秒)
- * @return true 操作成功,false 已存在值
- */
- public boolean addHash(final String key, final Map fieldValueList, final Long timeout) {
- boolean result = redisTemplate.execute(new RedisCallback() {
- public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
- for (String hashKey : fieldValueList.keySet()) {
- connection.hSetNX(key.getBytes(), hashKey.getBytes(), fieldValueList.get(hashKey).getBytes());
- if (timeout != null && timeout > 0)
- connection.expire(key.getBytes(), timeout);
- }
- return true;
- }
- });
- return result;
- }
-
- /**
- * 通过key获取单个
- *
- * @param key
- * @return
- */
- public Object getHashField(final String key, final String field) {
- String value = redisTemplate.execute(new RedisCallback() {
- public String doInRedis(RedisConnection connection) throws DataAccessException {
- return new String(connection.hGet(key.getBytes(), field.getBytes()));
- }
- });
- return value;
- }
-
- /**
- * 通过key获取整个Hash
- *
- * @param key
- * @return
- */
- public Map getHashAll(final String key, final String field) {
- Map value = redisTemplate.execute(new RedisCallback