• springboot与redis


    1.springboot整合redis

    springboot对redis的操作封装了两个StringRedisTemplate和RedisTemplate类,StringRedisTemplate是RedisTemplate的子类,StringRedisTemplate它只能存储字符串类型,无法存储对象类型。要想用StringRedisTemplate存储对象必须把对象转为json字符串。

    1.1.StringRedisTemplate

    (1) 引入相关的依赖

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-data-redisartifactId>
    4. dependency>

    (2)注入StringRedisTemplate该类对象

    1. @Autowired
    2. private StringRedisTemplate redisTemplate;

    (3)使用StringRedisTemplate

    该类把对每种数据类型的操作,单独封了相应的内部类。

    对String类型的数据操作

    1. //对String类型的操作
    2. @Test
    3. void test01() {
    4. ValueOperations forValue = stringRedisTemplate.opsForValue();
    5. //时间存储 时间结束后销毁
    6. forValue.set("k1","李四",30l, TimeUnit.SECONDS);
    7. //获取k1的value值
    8. String s = forValue.get("k1");
    9. System.out.println(s);
    10. //若存在,不存入,不存在则存入 返回布尔值
    11. Boolean aBoolean = forValue.setIfAbsent("k1", "张三", 30l, TimeUnit.SECONDS);
    12. System.out.println(aBoolean);
    13. //追加
    14. Integer i = forValue.append("k1", "是个人");
    15. System.out.println(i);
    16. }

    对Hash类型的数据操作

    1. //对Hash类型的操作
    2. @Test
    3. void test02(){
    4. HashOperations forHash = stringRedisTemplate.opsForHash();
    5. forHash.put("k1","name","张三");
    6. //必须都是字符串类型,虽然上面泛型是Object 但使用的是spring的序列化 Integer 无法转为String
    7. forHash.put("k1","age","18");
    8. Map map=new HashMap<>();
    9. map.put("name","李四");
    10. map.put("age","25");
    11. forHash.putAll("k2",map);
    12. Object o = forHash.get("k1", "name");
    13. System.out.println(o);
    14. Set s= forHash.keys("k1");
    15. System.out.println(s);
    16. List l = forHash.values("k1");
    17. System.out.println(l);
    18. //获取k1对于的所有的field和value
    19. Map k12 = forHash.entries("k1");
    20. System.out.println(k12);
    21. }
    22. 注意:

      1.2.RedisTemplate

      使用RedisTemplate 必须要指定序列化方式,默认使用jdk序列化方式。但会引起乱码,而且占用内存大。

      1. @Autowired
      2. private RedisTemplate redisTemplate;
      3. @Test
      4. void test01(){
      5. //指定key的序列化方式
      6. redisTemplate.setKeySerializer(new StringRedisSerializer());
      7. //指定value的序列化方式 GenericJackson2JsonRedisSerializer()/Jackson2JsonRedisSerializer(Object.class)
      8. redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Object.class));
      9. ValueOperations forValue = redisTemplate.opsForValue();
      10. forValue.set("k1","张三",60l,TimeUnit.SECONDS);
      11. //value默认采用jdk,类必须实现序列化接口
      12. forValue.set("k2",new User(1,"李四","123456"));
      13. }
      14. 上面的RedisTemplate需要每次都指定key value以及field的序列化方式,可以创建一个配置类,为RedisTemplate指定好序列化。以后再用就无需指定。

        1. @Configuration
        2. public class RedisConfig {
        3. @Bean
        4. public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
        5. RedisTemplate template = new RedisTemplate<>();
        6. RedisSerializer redisSerializer = new StringRedisSerializer();
        7. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        8. ObjectMapper om = new ObjectMapper();
        9. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        10. om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        11. jackson2JsonRedisSerializer.setObjectMapper(om);
        12. template.setConnectionFactory(factory);
        13. //key序列化方式
        14. template.setKeySerializer(redisSerializer);
        15. //value序列化
        16. template.setValueSerializer(jackson2JsonRedisSerializer);
        17. //value hashmap序列化 filed value
        18. template.setHashValueSerializer(jackson2JsonRedisSerializer);
        19. template.setHashKeySerializer(redisSerializer);
        20. return template;
        21. }
        22. }

        2.redis的使用场景

        2.1.作为缓存

        (1)数据存储在内存中,数据查询速度快。可以分摊数据库压力。

        (2)什么样的数据适合放入缓存

        查询频率比较高,修改频率比较低。

        安全系数低的数据

        (3)使用redis作为缓存

        1. @Autowired
        2. private UserMapper userMapper;
        3. @Autowired
        4. private RedisTemplate redisTemplate;
        5. //根据id查询
        6. public User findById(Integer id){
        7. ValueOperations forValue = redisTemplate.opsForValue();
        8. //查询缓存
        9. Object o = forValue.get("user::" + id);
        10. //缓存命中
        11. if(o!=null){
        12. return (User) o;
        13. }
        14. User user = userMapper.selectById(id);
        15. if(user!=null){
        16. //存入缓存
        17. forValue.set("user::"+id,user,2, TimeUnit.HOURS);
        18. }
        19. return user;
        20. }
        21. //根据id删除
        22. public int delete(Integer id){
        23. //先删除缓存再删除数据库中的数据
        24. redisTemplate.delete("user::"+id);
        25. int i = userMapper.deleteById(id);
        26. return i;
        27. }
        28. //添加
        29. public User insert(User user){
        30. int i = userMapper.insert(user);
        31. return user;
        32. }
        33. //根据id修改
        34. //先删掉缓存,再修改数据库
        35. public User update(User user){
        36. Integer id = user.getId();
        37. redisTemplate.delete("user::"+id);
        38. int i = userMapper.updateById(user);
        39. return user;
        40. }

        查看的缓存: 前部分代码相同@before通知,后部分代码也相同后置通知。 我们可以AOP完成缓存代码和业务代码分离。

        spring框架它应该也能想到。--使用注解即可完成。解析该注解。

        (1)把缓存的配置类加入

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

        2.2.分布式锁

        使用压测工具测试高并发下带来线程安全问题

        同一个库存被使用了n次。以及数据库中库存为负数。 线程安全问题导致。

        1. 解决方案: 使用 synchronized 或者lock锁

        2.使用redis作为锁

        nginx(windows系统下)

        配置nginx文件 nginx.conf

        开启nginx

        注意nginx包的目录必须没有中文,否则无法开启

         

         准备数据库文件

        开启idea集群

        测试代码:

        controller层

        1. @RestController
        2. @RequestMapping("productStock")
        3. public class ProductStockController {
        4. @Autowired
        5. private ProductStockService productStockService;
        6. //减库存
        7. @RequestMapping("decreaseStock/{productId}")
        8. public String decreaseStock(@PathVariable("productId") Integer productId){
        9. return productStockService.decreaseStock(productId);
        10. }
        11. }

        service层 

        1. @Service
        2. public class ProductStockServiceImpl2 implements ProductStockService {
        3. @Autowired
        4. private ProductStockDao productStockDao;
        5. @Autowired
        6. private StringRedisTemplate stringRedisTemplate;
        7. @Override
        8. public String decreaseStock(Integer productId) {
        9. ValueOperations forValue = stringRedisTemplate.opsForValue();
        10. Boolean flag = forValue.setIfAbsent("dis::" + productId, "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
        11. if(flag) {
        12. try{
        13. //查看该商品的库存数量
        14. Integer stock = productStockDao.findStockByProductId(productId);
        15. if (stock > 0) {
        16. //修改库存每次-1
        17. productStockDao.updateStockByProductId(productId);
        18. System.out.println("扣减成功!剩余库存数:" + (stock - 1));
        19. return "success";
        20. } else {
        21. System.out.println("扣减失败!库存不足!");
        22. return "fail";
        23. }
        24. }finally {
        25. stringRedisTemplate.delete("dis::" + productId);
        26. }
        27. }
        28. return "服务忙,请稍后..........";
        29. }
        30. }

         sql语句

        1. <select id="findStockByProductId" resultType="integer">
        2. select num from tbl_stock where productId=#{productId}
        3. </select>
        4. <update id="updateStockByProductId">
        5. update tbl_stock set num=num-1 where productId=#{productId}
        6. </update>

        3.解决redis分布式锁的bug

         可以使用:redission依赖,redission解决redis超时问题的原理。

        为持有锁的线程开启一个守护线程,守护线程会每隔10秒检查当前线程是否还持有锁,如果持有则延迟生存时间。

        使用redis破解版(可以在windows中使用的),开启redis

        1. <dependency>
        2. <groupId>org.redissongroupId>
        3. <artifactId>redissonartifactId>
        4. <version>3.13.4version>
        5. dependency>
        1. //获取redisson对象并交于spring容器管理
        2. @Bean
        3. public Redisson redisson(){
        4. Config config =new Config();
        5. config.useSingleServer().
        6. setAddress("redis://192.168.226.234:6379").
        7. //redis默认有16个数据库
        8. setDatabase(0);
        9. return (Redisson) Redisson.create(config);
        10. }

        测试代码 

        1. @Service
        2. public class ProductStockServiceImpl2 implements ProductStockService {
        3. @Autowired
        4. private ProductStockDao productStockDao;
        5. @Autowired
        6. private Redisson redisson;
        7. @Override
        8. public String decreaseStock(Integer productId) {
        9. //获取锁对象
        10. RLock rlock = redisson.getLock("dis::"+productId);
        11. try{
        12. rlock.lock(30, TimeUnit.SECONDS);
        13. //查看该商品的库存数量
        14. Integer stock = productStockDao.findStockByProductId(productId);
        15. if (stock > 0) {
        16. //修改库存每次-1
        17. productStockDao.updateStockByProductId(productId);
        18. System.out.println("扣减成功!剩余库存数:" + (stock - 1));
        19. return "success";
        20. } else {
        21. System.out.println("扣减失败!库存不足!");
        22. return "fail";
        23. }
        24. }finally {
        25. rlock.unlock();
        26. }
        27. }
        28. }

      15. 相关阅读:
        为什么近期越来越多人青睐国际期货?
        React(二):Redux基本使用方法
        vue3 使用axios
        STC89C52+DHT20设计的环境温湿度检测仪
        发UPS国际快递到墨西哥的收费标准
        黑马点评--优惠卷秒杀
        LeetCode 0290. 单词规律
        关于dubbo快速开发和服务提供者无法注册上注意点
        卷王杯 easy unserialize
        SpringBoot 04 多环境配置和配置文件小技巧
      16. 原文地址:https://blog.csdn.net/qq_44189274/article/details/126110598