1. 引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
application.properties增加以下配置
## redis配置
spring.redis.host=127.0.0.1
spring.redis.database=0
spring.redis.port=6379
spring.redis.password=slo
2.加入配置類
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
/**
* <p>redis緩存配置</p>
*/
@Configuration
@EnableCaching
public class RedisCachedConfig extends CachingConfigurerSupport {
//過期時間1天
// private Duration timeToLive = Duration.ofMinutes(1);
private Duration timeToLive = Duration.ofHours(1);
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
//默認1
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(this.timeToLive)
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))
.disableCachingNullValues();
RedisCacheManager redisCacheManager = RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.transactionAware()
.build();
return redisCacheManager;
}
@Bean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(keySerializer());
redisTemplate.setHashKeySerializer(keySerializer());
redisTemplate.setValueSerializer(valueSerializer());
redisTemplate.setHashValueSerializer(valueSerializer());
return redisTemplate;
}
private RedisSerializer<String> keySerializer() {
return new StringRedisSerializer();
}
private RedisSerializer<Object> valueSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
}
3.編寫Lock工具類
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import org.springframework.data.redis.core.RedisTemplate;
import lombok.Data;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Data
@Component
public class RedisLock {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* 重試時間
*/
private static final int DEFAULT_ACQUIRY_RETRY_MILLIS = 100;
/**
* 鎖的後綴
*/
private static final String LOCK_SUFFIX = "_redis_lock";
/**
* 鎖的key
*/
private String lockKey;
/**
* 鎖超時時間,防止線程在入鎖以後,防止阻塞後面的線程無法獲取鎖
*/
private int expireMsecs = 2 * 60 * 1000;
/**
* 線程獲取鎖的等待時間
*/
private int timeoutMsecs = 10 * 1000;
/**
* 是否鎖定標誌
*/
private volatile boolean locked = false;
/**
* 封裝和jedis方法
*
* @param key
* @return
*/
private String get(final String key) {
Object obj = redisTemplate.opsForValue().get(key);
return obj != null ? obj.toString() : null;
}
/**
* 封裝和jedis方法
*
* @param key
* @param value
* @return
*/
private boolean setNX(final String key, final String value) {
return redisTemplate.opsForValue().setIfAbsent(key, value);
}
/**
* 封裝和jedis方法
*
* @param key
* @param value
* @return
*/
private String getSet(final String key, final String value) {
Object obj = redisTemplate.opsForValue().getAndSet(key, value);
return obj != null ? (String) obj : null;
}
//=================================公共方法區============================//
/**
* 初始化參數
* 默認鎖超時時間 2分鐘
* 默認獲取鎖等待時間 10秒
*
* @param lockKey 鎖的key
*/
public void init(String lockKey) {
this.lockKey = lockKey + LOCK_SUFFIX;
}
/**
* 初始化參數
*
* @param lockKey 鎖的key
* @param timeoutMsecs 獲取鎖的超時時間 單位毫秒 eg:2 * 60 * 1000
*/
public void init(String lockKey, int timeoutMsecs) {
this.lockKey = lockKey + LOCK_SUFFIX;
this.timeoutMsecs = timeoutMsecs;
}
/**
* 初始化參數
*
* @param lockKey 鎖的key
* @param timeoutMsecs 獲取鎖的超時時間
* @param expireMsecs 鎖的有效期 單位毫秒 eg: 10 * 1000
*/
public void init(String lockKey, int timeoutMsecs, int expireMsecs) {
this.lockKey = lockKey + LOCK_SUFFIX;
this.timeoutMsecs = timeoutMsecs;
this.expireMsecs = expireMsecs;
}
public String getLockKey() {
return lockKey;
}
/**
* 獲取鎖
*
* @return 獲取鎖成功返回ture,超時返回false
* @throws InterruptedException
*/
public synchronized boolean lock() throws InterruptedException {
int timeout = timeoutMsecs;
logger.info("準備獲取鎖{},獲取鎖超時時間爲{}",this.getLockKey(),timeout);
while (timeout >= 0) {
long expires = System.currentTimeMillis() + expireMsecs + 1;
String expiresStr = String.valueOf(expires); // 鎖到期時間
if (this.setNX(lockKey, expiresStr)) {
locked = true;
logger.info("成功獲取鎖{},鎖超時時間爲{}",this.getLockKey(),expires);
return true;
}
// redis裏key的時間
String currentValue = this.get(lockKey);
// 判斷鎖是否已經過期,過期則重新設置並獲取
if (currentValue != null && Long.parseLong(currentValue) < System.currentTimeMillis()) {
// 設置鎖並返回舊值
String oldValue = this.getSet(lockKey, expiresStr);
// 比較鎖的時間,如果不一致則可能是其他鎖已經修改了值並獲取
if (oldValue != null && oldValue.equals(currentValue)) {
logger.info("成功獲取鎖{},鎖超時時間爲{}",this.getLockKey(),expires);
locked = true;
return true;
}
}
timeout -= DEFAULT_ACQUIRY_RETRY_MILLIS;
// 延時
Thread.sleep(DEFAULT_ACQUIRY_RETRY_MILLIS);
}
logger.warn("獲取鎖{}失敗了!",this.getLockKey());
return false;
}
/**
* 釋放獲取到的鎖
*/
public void unlock() {
logger.info("準備釋放鎖{}",this.getLockKey());
if (locked) {
redisTemplate.delete(lockKey);
logger.info("成功釋放鎖{}",this.getLockKey());
locked = false;
}
}
}
4.redis緩存數據測試
具體類無需關心,只要看註解如何使用即可。
//REDIS緩存調用示例
@Deprecated
@Service
@CacheConfig(cacheNames = "user") //指定cache的名字,這裏指定了 caheNames,下面的方法的註解裏就可以不用指定 value 屬性了
public class UserServiceImpl {
private Map<Long, User> userMap = new HashMap<Long, User>();
public UserServiceImpl() {
User u1=new User();
u1.setId(1L);
u1.setName("1111");
u1.setPassword("11223434");
User u2=new User();
u2.setId(2L);
u2.setName("1111");
u2.setPassword("11223434");
User u3=new User();
u3.setId(3L);
u3.setName("1111");
u3.setPassword("11223434");
userMap.put(1L,u1);
userMap.put(2L,u2);
userMap.put(3L,u3);
}
@Cacheable()
@Override
public List<User> list() {
System.out.println("querying list.....");
User[] users = new User[userMap.size()];
this.userMap.values().toArray(users);
return Arrays.asList(users);
}
@Cacheable(key = "'user:'.concat(#id.toString())")
@Override
public User findUserById(Long id) {
System.out.println("沒有調用緩存");
return userMap.get(id);
}
@Cacheable(key = "'user:'.concat(#user.id)")
@Override
public void update(User user) {
System.out.println("沒有調用緩存");
userMap.put(user.getId(), user);
}
// 清空cache
@CacheEvict(key = "'user:'.concat(#id.toString())")
@Override
public void remove(Long id) {
System.out.println("沒有調用緩存");
userMap.remove(id);
}
@CacheEvict(key = "'user:'.concat(#id.toString())")
@Override
public User upuser(Long id) {
System.out.println("沒有調用緩存");
User d = userMap.get(id);
d.setName("0000000000000000000000000000000000000000");
return d;
}
@CachePut(key = "'user:'.concat(#user.id)")
@Override
public User saveUser(User user) {
System.out.println("沒有調用緩存");
userMap.put(user.getId(), user);
return user;
}
}
5.Lock嵌入添加示例以及測試
以下無需關注具體業務,只需看lock如何嵌入使用
@Transactional
@Service
public class TestSrvImpl implements TestSrv {
Logger log = LoggerFactory.getLogger(this.getClass());
@Resource
private RedisLock redisLock; //這裏自動注入redisLock類
@Resource
private UserInfoRepository userInfoRepository;
@LogAnnotation(moduleName = "測試服務", option = "測試方法")
public ResponseVO testService(RequestVO requestVO) {
//獲取支付配置相關信息
ResponseVO response = new ResponseVO(requestVO);
try {
log.info("準備獲取鎖...");
redisLock.init("Test",20*1000);
if (redisLock.lock()) {
log.info("成功獲取鎖,入參:{}", JSON.toJSONString(requestVO));
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("no", "123456");
resultMap.put("name", "test");
HandlerStateDTO handlerStateDTO = new HandlerStateDTO();
response.setBizObj(resultMap);
if (!HandlerType.isSuccessful(handlerStateDTO)) {
response.setRetInfo(handlerStateDTO);
}
log.info("開始進入睡眠模式");
Thread.sleep(5000);
log.info("睡眠模式解除");
redisLock.unlock();
log.info("釋放鎖");
}
} catch (Exception e) {
e.printStackTrace();
//異常釋放鎖
redisLock.unlock();
response.setRetInfo(HandlerType.UNKNOWN);
log.error("失敗了,{}" + e.toString());
}
log.info("結束返回");
return response;
}
本樣例SDK模擬多線程測試用例如下,自己根據自己的服務調用方式模擬調用即可。本項目採用sdk請求http接口進行調用:
/**
* 測試
*/
@Test
public void test() {
logger.info("開始請求");
SdkClient sdkClient = new SdkClient(web_pay_url, appId, appSecret, signType, encryptType);
try {
Map<String,Object> param = new HashMap<>();
param.put("name","testUser");
param.put("id","A000001");
// // 發起交易
ResponseVO responseParams = sdkClient.unifyCall("test.service.forfun",version,param);
logger.info("返回的結果爲:"+JSON.toJSONString(responseParams));
logger.info("其中業務返回爲:"+JSON.toJSONString(responseParams.getBizObj()));
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void test3() throws InterruptedException {
// 同一時刻最大的併發線程的個數 即併發數
final int concurrentThreadNum = 2;
//總共多少線程
final int countThreadNum = 2;
ExecutorService executorService = Executors.newCachedThreadPool();
CountDownLatch countDownLatch = new CountDownLatch(countThreadNum);
Semaphore semaphore = new Semaphore(concurrentThreadNum);
for (int i = 0; i< countThreadNum; i++) {
executorService.execute(()->{
try {
semaphore.acquire();
test();
semaphore.release();
} catch (InterruptedException e) {
logger.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
logger.info("請求完成");
}
6.基礎框架項目
內置spring boot 和接口加解密sdk,redis鎖等,下載即可開發具體業務