Redis專案實戰入門
阿新 • • 發佈:2018-12-09
Redis專案實戰Demo
資料快取伺服器
分散式鎖setNX
搭建一個簡單的SpringBoot-MyBatis
資料庫依賴引入
<!--資料庫依賴--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> </dependency>
mybatis逆向生成外掛
<dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.6</version> </dependency> <!-- mybatis 逆向工程maven工具 --> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.2</version> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> </dependencies> <configuration> <!--配置檔案的路徑 --> <configurationFile> ${basedir}/src/main/resources/generatorConfig.xml </configurationFile> <overwrite>true</overwrite> </configuration> </plugin>
設定Mapper檔案過濾
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>
com/qf/redis_demo_lock/dao/mapper/*.xml
</include>
</includes>
</resource>
MapperScan
@MapperScan("com.qf.redis_demo_lock.dao") public class RedisDemoLockApplication {}
application.yml核心配置檔案
server:
port: 8080
spring:
datasource:
url: jdbc:mysql:///1806_shop
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
type: com.mchange.v2.c3p0.ComboPooledDataSource
redis:
host: 192.168.184.130
mybatis:
mapper-locations: classpath*:com/qf/redis_demo_lock/dao/mapper/*.xml
type-aliases-package: com.qf.redis_demo_lock.entity
SpringBoot整合redis使用分散式鎖並抽取工具類
@Service
public class UserServiceImpl implements IUserService {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private UserMapper userMapper;
//
// private String lockLua = "local c = redis.call(\"setNX\",KEYS[1],ARGV[1])\n" +
// "if c == 1 then\n" +
// " redis.call(\"expire\",KEYS[1],ARGV[2])\n" +
// " return 1\n" +
// "end\n" +
// "return 0";
// private String token = UUID.randomUUID().toString();
//
// private String unlockLua = "local token = redis.call(\"get\",KEYS[1])\n"+
// "local token2 = ARGV[1]\n"+
// "if token == token2 then\n"+
// " redis.call(\"del\",KEYS[1])\n"+
// " return 1\n"+
// "end\n"+
// "return 0";
@Autowired
private LockUtil lockUtil;
/**
*
* 快取失效的問題
* 對於一個快取來說,通常都需要給一個超時時間,因為這樣可以有效的過濾掉快取伺服器中的冷門資料。
* 加入某個時刻,一個熱門資料達到了快取失效時間,這個時候就會有一個快取重建的問題。
* 假設快取失效的一瞬間,大量請求請求這個資料,
* 那麼有可能很多請求同時去進行快取重建,這個時候資料庫的壓力瞬間暴漲,就可能導致資料庫崩潰
*
* 解決:通過分散式鎖,保證只有一個執行緒在進行快取重建
*
* @return
*/
@Override
public List<User> queryAll() {
//簡單的實現了redis快取
List<User> users = (List<User>) redisTemplate.opsForValue().get("users");
//通過分散式鎖來實現快取
//RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
//設定分散式鎖
//Boolean aBoolean = connection.setNX("lock".getBytes(), token.getBytes());
//通過指令碼設定鎖
if(users == null){
//設定失效時間 一分鐘
//問題:如果該鎖在設定之後,就宕機了也會導致死鎖,所有隻有通過lua指令碼保證一個原子性的操作
//connection.expire("lock".getBytes(),60);
//上鎖
boolean flag = lockUtil.lock("lock",60000);
if(flag){
//當前執行緒已經設定了分散式鎖
System.out.println("查詢了資料庫");
users = userMapper.queryAll();
redisTemplate.opsForValue().set("users",users);
//設定快取超時時間
redisTemplate.expire("users",5, TimeUnit.MINUTES);
//釋放鎖
lockUtil.unlock("lock");
}else{
//沒有獲得分散式鎖
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
return queryAll();
}
//釋放鎖
//問題,如果在設定鎖和釋放鎖之間,程式宕機,會導致該死鎖,所以我們一般會通過設定失效時間解決
//釋放鎖的時候,需要保證是同一條執行緒,不然所有的人都可以釋放該鎖,鎖機制將無意義,通過value值uuid控制
//問題,這個時候lock失效了,然後被其他的請求修改了值,該鎖也會死鎖.所以也需要通過指令碼進行控制
// byte[] bytes = connection.get("lock".getBytes());
// if(bytes == token.getBytes()){
// connection.del("lock".getBytes());
// }
}
return users;
}
@Override
public User queryOne(Integer id) {
//簡單的實現了redis快取
User user = (User) redisTemplate.opsForValue().get("user" + id);
//通過分散式鎖來實現快取
//RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
//設定分散式鎖
//Boolean aBoolean = connection.setNX("lock".getBytes(), token.getBytes());
//通過指令碼設定鎖
if(user == null){
//設定失效時間 一分鐘
//問題:如果該鎖在設定之後,就宕機了也會導致死鎖,所有隻有通過lua指令碼保證一個原子性的操作
//connection.expire("lock".getBytes(),60);
//上鎖
boolean flag = lockUtil.lock("userLock" + id,60000);
if(flag){
//當前執行緒已經設定了分散式鎖
System.out.println("查詢了資料庫");
user = userMapper.selectByPrimaryKey(id);
redisTemplate.opsForValue().set("user" + id,user);
//設定快取超時時間
redisTemplate.expire("user" + id,5, TimeUnit.MINUTES);
//釋放鎖
lockUtil.unlock("userLock" + id);
}else{
//沒有獲得分散式鎖
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
return queryOne(id);
}
//釋放鎖
//問題,如果在設定鎖和釋放鎖之間,程式宕機,會導致該死鎖,所以我們一般會通過設定失效時間解決
//釋放鎖的時候,需要保證是同一條執行緒,不然所有的人都可以釋放該鎖,鎖機制將無意義,通過value值uuid控制
//問題,這個時候lock失效了,然後被其他的請求修改了值,該鎖也會死鎖.所以也需要通過指令碼進行控制
// byte[] bytes = connection.get("lock".getBytes());
// if(bytes == token.getBytes()){
// connection.del("lock".getBytes());
// }
}
return user;
}
@Override
public int deleteUser(Integer id) {
redisTemplate.delete("users");
return userMapper.deleteByPrimaryKey(id);
}
@Override
public int insertUser(User user) {
redisTemplate.delete("users");
User user1 = new User();
user.setUsername("username");
user.setPassword("password");
return userMapper.insertSelective(user);
}
}
LockUtil工具類
@Component
public class LockUtil {
@Autowired
private RedisTemplate redisTemplate;
private RedisConnection connection;
private String lockLua = "local c = redis.call(\"setNX\",KEYS[1],ARGV[1])\n" +
"if c == 1 then\n" +
" redis.call(\"expire\",KEYS[1],ARGV[2])\n" +
" return 1\n" +
"end\n" +
"\n" +
"return 0";
//通過令牌與執行緒繫結實現鎖只有當前執行緒能釋放
private ThreadLocal<String> threadLocal = new ThreadLocal<>();
//private String token = UUID.randomUUID().toString();
private String unlockLua = "local token = redis.call(\"get\",KEYS[1])\n"+
"local token2 = ARGV[1]\n"+
"if token == token2 then\n"+
" redis.call(\"del\",KEYS[1])\n"+
" return 1\n"+
"end\n"+
"\n" +
"return 0";
//將兩個指令碼存到redis上返回的字串唯一標識
private String lockId;
private String unlockId;
@PostConstruct
public void init() {
connection = redisTemplate.getConnectionFactory().getConnection();
//快取加鎖的lua指令碼
lockId = connection.scriptLoad(lockLua.getBytes());
//快取解鎖的lua指令碼
unlockId = connection.scriptLoad(unlockLua.getBytes());
}
public boolean lock(String key,Integer time){
System.out.println("新增分散式鎖");
String token = UUID.randomUUID().toString();
//把令牌放到當前執行緒中
threadLocal.set(token);
long result = connection.evalSha(lockId,
ReturnType.INTEGER,
1,
key.getBytes(),
token.getBytes(),
"60000".getBytes());
return result == 1;
}
public boolean unlock(String key){
System.out.println("釋放分散式鎖");
String token = threadLocal.get();
long result = connection.evalSha(unlockId,
ReturnType.INTEGER, 1,
key.getBytes(), token.getBytes());
return result == 1;
}
}
SpringBoot整合redis使用快取註解
預設快取註解自帶分散式鎖,springBoot幫我們封裝好了,和之前我們自己手動封裝的效果其實是一致的
引入快取依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
配置快取
spring:
cache:
type: redis
redis:
time-to-live: 60000
使用快取
@EnableCaching//開啟自動配置快取
public class RedisDemoLockApplication {}
#####第一個註解@Cacheable
@Override
@Cacheable(cacheNames = "cache",key = "'users'")
public List<User> queryAll() {
System.out.println("查詢了資料庫");
return userMapper.queryAll();
}
redis中:
keys * ---> "cache::users"
作用:進入目標方法之前,先查詢快取伺服器
如果快取伺服器中有結果直接返回,不再呼叫目標方法
如果快取伺服器中沒有結果,就將目標方法的返回值重建進快取伺服器中
屬性有:
cacheNames:快取key的字首
key:快取的key值
condition:只有滿足condition中表達式的結果才會去快取中查詢資料
unless:與condition相反,滿足unless中的表示式就不會去快取中查詢資料
第二個註解@CacheEvict
@Override
@CacheEvict(cacheNames = "cache",key = "'user' + #id")
public int deleteUser(Integer id) {
return userMapper.deleteByPrimaryKey(id);
}
作用:進入目標方法之後,刪除掉指定快取
使用場景:
寫操作的時候需要更新快取中的中的資料,將快取中queryAll的key刪除
第三個註解@CachePut
@Override
@CacheEvict(cacheNames = "cache", key = "'stus'")
@CachePut(cacheNames = "cache",key = "'user' + #user.id")//此處需要主鍵回填
public int insertUser(User user) {
User user1 = new User();
user.setUsername("usernameCache");
return userMapper.insertSelective(user);
}
作用:
和@Cacheable大概意思差不多,唯一的區別在於@CachePut標記的方法一定會被執行,同時方法的返回值也被記錄到快取中。
使用場景:
當需要根據請求改變值的時候,利用@CachePut將值改變並寫入到快取中,而@Cacheable標籤除了第一次之外,一直是取的快取的值。
第四個註解@Caching(多個其他註解)
@Override
@Caching(evict = {
@CacheEvict(cacheNames = "cache",key = "'users'"),
@CacheEvict(cacheNames = "cache",key = "'user' + #id")
})
public int deleteUser(Integer id) {
return userMapper.deleteByPrimaryKey(id);
}
作用:可以用來巢狀其他@Cachexx註解