Redis的使用场景

1.热点数据的缓存

        把经常被访问的数据存储到redis中,以后再查找该数据时,优先从redis中查询,如果redis没有被命中,则才会查询数据。并把查询的结果放入redis中以便下次能从redis中获取。

        什么样的数据适合放入缓存中?

                1. 查询频率高的数据
                2. 修改频率低的数据
                3. 数据安全性要求不高的

        缓存的作用:

                1. 提高查询效率
                2. 降低数据库的访问频率,减少数据库的压力

        如何使用redis作为缓存

                1.依赖

<dependencies>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-data-redis</artifactId>
     </dependency>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
     </dependency>

     <dependency>
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
     </dependency>
     <dependency>
         <groupId>com.baomidou</groupId>
         <artifactId>mybatis-plus-boot-starter</artifactId>
         <version>3.5.1</version>
     </dependency>
     <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
         <optional>true</optional>
     </dependency>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
     </dependency>
 </dependencies>

                2.配置 

#数据源
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/qy168?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root

#redis单机版
spring.redis.host=192.168.223.158
spring.redis.port=6379

#mybatis-plus
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

                 3.Dao层

public interface DeptDao extends BaseMapper<Dept> {
}

                4.业务层

@Service
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptDao deptDao;
    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public Dept findById(Integer id) {
        ValueOperations valueOperations = redisTemplate.opsForValue();
        //1.查询缓存
        Object o = valueOperations.get("dept::" + id);
        //2.判断是否缓存命中
        if(o!=null&&o instanceof Dept){//o instanceof Dept: 判断对象是否属于Dept类型
            return (Dept) o;
        }
        //3. 缓存没有命中则查询数据库
        Dept dept = deptDao.selectById(id);
        if(dept!=null){
            //4. 查询的数据存入缓存中以便下次能从缓存中获取
            valueOperations.set("dept::" + id,dept);
        }
        return dept;
    }

    @Override
    @Transactional
    public int delete(Integer id) {
        int row = deptDao.deleteById(id);
        redisTemplate.delete("dept::"+id);
        return row;
    }

    @Override
    public Dept insert(Dept dept) {
        int insert = deptDao.insert(dept);
        return dept;
    }

    @Override
    public Dept update(Dept dept) {
        redisTemplate.opsForValue().set("dept::"+dept.getDid(),dept);
        int i = deptDao.updateById(dept);
        return dept;
    }


}

               4.控制层

@RestController
@RequestMapping("/dept")
public class DeptController {
    @Autowired
    private DeptService deptService;

    @GetMapping("/getById/{id}")
    public Dept getById(@PathVariable Integer id ){
        Dept dept = deptService.findById(id);
        return dept;
    }

    @GetMapping("/delete/{id}")
    public int delete(@PathVariable Integer id ){
        return deptService.delete(id);
    }

    @GetMapping("/insert")
    public Dept insert(Dept dept){ //接受表单参数和查询参数。
        return deptService.insert(dept);
    }

    @GetMapping("/update")
    public Dept update(Dept dept ){
        return deptService.update(dept);
    }
}

        然后启动项目访问查询,第一次会查询数据库,可以在控制台打印查询数据库结果,之后便不会再查询数据库。

        我们再使用redis作为缓存时,每次都需要自己添加缓存代码【非业务代码】。未来维护时还要维护redis非业务代码。

        spring框架也会想到使用AOP解决业务代码和缓存的非业务代码的重合。--使用了缓存的注解。

        如何使用spring缓存注解的方式实现redis缓存功能。

 @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        //解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // 配置序列化(解决乱码的问题),过期时间600秒
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(600)) //缓存过期10分钟 ---- 业务需求。
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))//设置key的序列化方式
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) //设置value的序列化
                .disableCachingNullValues();
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }

        然后修改业务层代码

package com.ykq.service;

import com.ykq.dao.DeptDao;
import com.ykq.entity.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * @program: qy168-springboot-redis-02
 * @description:
 * @author: 闫克起2
 * @create: 2023-08-31 14:34
 **/
@Service
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptDao deptDao;
    @Autowired
    private RedisTemplate redisTemplate;

    //该注解用于查询功能方法上。 先查询缓存中是否存在cacheNames+"::"+key是否存在,
    // 如果存在则不执行该方法,如果不存在则执行该方法并把该方法的返回结果存入缓存中。以cacheName+"::"+key作为缓存的key
    @Cacheable(cacheNames = "dept",key = "#id")
    public Dept findById(Integer id) {
        //3. 缓存没有命中则查询数据库
        Dept dept = deptDao.selectById(id);
        return dept;
    }

    //把缓存中的数据进行移除,以cacheName+"::"+key作为缓存的key移除
    @CacheEvict(cacheNames = "dept",key = "#id")
    @Transactional
    public int delete(Integer id) {
        int row = deptDao.deleteById(id);
        return row;
    }

    @Override
    public Dept insert(Dept dept) {
        int insert = deptDao.insert(dept);
        return dept;
    }

    //把该方法的返回结果重新写入到缓存中
    @CachePut(cacheNames = "dept",key="#dept.did")
    @Transactional
    public Dept update(Dept dept) {
        int i = deptDao.updateById(dept);
        int c=10/0;
        return dept;
    }
}

        并在主启动类开启注解驱动

@EnableCaching //开启缓存注解驱动
@EnableTransactionManagement //开启事务注解驱动

2.Redis实现分布式锁

         如果用户过多并且同时发起请求的话,通过代理服务器将这些请求分发给服务器,多台服务器的线程同时进行,会导致数据的准确性丢失。列如一个买票的程序,100个用户同时进行了下单,有两条线程同时拿到了最后一张票,此时,票应该归谁。为避免这种事情发生,可以使用锁来控制线程,但是普通的自动锁只针对当前服务器的jvm,如果是一台服务器确实可以通过自动锁来解决,但当前是集群,也就是说大于1台的服务器,服务器之间没有办法共享这个锁资源,还是会导致数据的准确性丢失。

        如何通过Redis实现分布式锁?

        首先,所需:一台Nginx代理服务器,两台服务器,jmeter测压,测压就相当于高并发。

        先创建一个买票的Springboot项目,然后编辑服务器配置,复制一份该配置,修改一个端口号即可代替两台的服务器,将这两个服务都跑起来。然后前往nginx的配置文件中。

upstream redis {
    		server localhost:8088;    #修改为服务的端口
    		server localhost:8089;    #修改为另一个服务的端口
    	}
    
    server {
    	listen		8087;    #需要监听的端口
    	server_name	localhost;
    	location / {
    		proxy_pass   http://redis;
    	}
    }

        通过访问这个需要监听的端口来访问你的服务。现在打开jmeter进行测压

        测试可得会出现负数。使用Redis解决。

public String decrement(Integer productid) {
        ValueOperations<String, String> forValue = redisTemplate.opsForValue();
        //调用setIfAbsent方法判断key是否存在,如果存在表示有线程正在执行,没有则继续
        Boolean ifAbsent = forValue.setIfAbsent("productid::" + productid, "0", 30, TimeUnit.SECONDS);
        if(ifAbsent){
            try{
                int num = stockDao.findById(productid);
                if (num > 0) {
                    stockDao.update(productid);
                    System.out.println("商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个");
                    return "商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个";
                } else {
                    System.out.println("商品编号为:" + productid + "的商品库存不足。");
                    return "商品编号为:" + productid + "的商品库存不足。";
                }
            }finally {
                //线程执行完移除key,表示该线程执行完了,解锁
                redisTemplate.delete("productid::" + productid);
            }
        }else {
            //如果有线程再执行,其他线程会进入这里
            return "服务器繁忙,请稍后再试";
        }
    }

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值