Redis项目实战入门

版权声明:个人收集,转载注明,谢谢支持 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注解

猜你喜欢

转载自blog.csdn.net/qq_42629110/article/details/84840129