版权声明:个人收集,转载注明,谢谢支持 https://blog.csdn.net/qq_42629110/article/details/84840129
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注解