redis的操作

1.Springboot操作redis

1.1首先创建user实体类

package com.hmq;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User  {
    private String name;
    private Integer age;
}

 接口对应自动序列化

 1.2使用RedisTemplate时需要为key和value设置序列化

package com.hmq.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
    @Bean //把该方法返回的类对象交于spring的IOC容器来管理
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        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);
        template.setConnectionFactory(factory);
        //key序列化方式
        template.setKeySerializer(redisSerializer);
        //value序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //value hashmap序列化  filed value
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.setHashKeySerializer(redisSerializer); //
        return template;
    }
}

1.3测试

package com.hmq;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.util.HashMap;
import java.util.Map;
@SpringBootTest
class HmqRedisApplicationTests02 {
    //springboot创建好该类对象 并交于IOC容器管理
    @Autowired
    private RedisTemplate redisTemplate;//在使用时需要指定序列化方式,如果没有指定,则采用JDK序列化方式,而jdk序列化方式要求类对象必须实现序列化接口
    @Test
    public void test02(){
        ValueOperations valueOperations = redisTemplate.opsForValue();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());//手动序列化,不需要给user实体类添加接口如果这里指定序列化方式,对象类可以不用实现序列化接口
        valueOperations.set("k1","v1");//存储都是字符串类型,看到存放的数据乱码,对yey进行序列化采用的是默认的jdk序列化方式
        System.out.println(valueOperations.get("k1"));
        valueOperations.set("k3",new User("张三丰",108));
        Object user = valueOperations.get("k3");
        System.out.println(user);
    }
    @Test
    public void test03(){
        ValueOperations valueOperations = redisTemplate.opsForValue();
        valueOperations.set("k11","v11");
        valueOperations.set("k12",new User("刘文强",24));
    }
}

2.springboot能否使用redis集群模式

#redis的配置信息--单机
#spring.redis.host=192.168.245.223
#spring.redis.port=6379
# nginx代理redis集群
spring.redis.cluster.nodes=192.168.245.223:7001,192.168.245.223:7002,192.168.245.223:7003,192.168.245.223:7004,192.168.245.223:7005,192.168.245.223:7006

3.使用redis作为缓存

缓存:---存在内存中。

作用: ---提高查询性能,减少数据库访问频率。

什么样的数据适合放入缓存: ---安全系数低的。---访问频率高 ---修改频率低 。

原理:

 

 3.1.创建springboot项目,连接数据库

 3.2.导入pom依赖

<!--导入阿里云依赖-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!--数据库依赖-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.0</version>
</dependency>

 3.3.配置文件

pring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql:///qy165?serverTimezone=Asia/Shanghai
spring.redis.host=192.168.245.223
spring.redis.port=6379
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

3.4创建dept实体类

package com.ehmq.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@TableName("tb_dept")
@AllArgsConstructor
@NoArgsConstructor
public class Dept {
    private Integer id;
    private String name;
    private String loc;
}

3.5创建dao接口的接口类deptdao

package com.ehmq.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ehmq.entity.Dept;
public interface DeptDao extends BaseMapper<Dept> {
}

3.6创建service层

package com.ehmq.service;
import com.ehmq.dao.DeptDao;
import com.ehmq.entity.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DeptService {
    @Autowired
    private DeptDao deptDao;
    public Dept findById(Integer id){
        Dept dept = deptDao.selectById(id);
        return dept;
    }
}

3.7给RedisSpeingbootCacheApp类添加路径

package com.ehmq;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(basePackages = "com.ehmq.dao")
public class RedisSpringbootCacheApplication {
    public static void main(String[] args) {
        SpringApplication.run(RedisSpringbootCacheApplication.class, args);
    }
}

3.8测试

package com.ehmq;
import com.ehmq.entity.Dept;
import com.ehmq.service.DeptService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class RedisSpringbootCacheApplicationTests {
    @Autowired
    private DeptService deptService;
    @Test
    void contextLoads() {
        Dept dept = deptService.findById(2);
        System.out.println(dept);
    }
}

实现缓存:

package com.ehmq.service;
import com.ehmq.dao.DeptDao;
import com.ehmq.entity.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Service
public class DeptService {
    @Autowired
    private DeptDao deptDao;
    @Autowired
    private RedisTemplate redisTemplate;
    public Dept findById(Integer id){
        ValueOperations valueOperations = redisTemplate.opsForValue();
        //1.查询缓存是否存在该数据
        Object o = valueOperations.get("dept:" + id);
        if(o!=null && o instanceof Dept){
            return (Dept) o;
        }
        //2.查询数据库
        Dept dept = deptDao.selectById(id);
        if(dept!=null) {
            //把该数据放入缓冲中
            valueOperations.set("dept:" + id, dept, 24, TimeUnit.HOURS);
        }
        return dept;
    }
    public Dept update(Dept dept){
        ValueOperations valueOperations = redisTemplate.opsForValue();
        //1.先该数据库还是先删缓冲。
        deptDao.updateById(dept);
        valueOperations.set("dept:"+dept.getId(),dept);
        return dept;
    }
    public Dept insert(Dept dept){
        deptDao.insert(dept);//添加
        return dept;
    }
    public int delete(int id){
        redisTemplate.delete("dept:"+id);
        deptDao.deleteById(id);
        return 1;
    }
}

AOP---可以把一些非核心业务代码抽象----抽取为一个切面类@Aspect. 在结合一些注解完成相应的切面功能。 Spring框也能想到,Spring在3.0以后提高了缓存注解。可以帮助把功能抽取。

 1)配置RedisConfig缓存配置

@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;
 }

2)开启缓存注解

 3)使用缓存注解

package com.ehmq.service;
import com.ehmq.dao.DeptDao;
import com.ehmq.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.stereotype.Service;

@Service
public class DeptServiceCache {
    @Autowired
    private DeptDao deptDao;
    //使用于查询的缓存注解: 缓存的名称叫做: cacheNames::key
    //先从缓存中找名字叫:cacheNames::key 如果存在,则方法不执行。如果不存在会执行方法,并把改方法的返回值作为缓存的值.
    //必须开启缓存注解驱动
    @Cacheable(cacheNames = "dept",key = "#id")
    public Dept findById(Integer id){
        Dept dept = deptDao.selectById(id);
        return dept;
    }
    //先执行方法体,并把方法的返回结果作为缓存的值。修改缓存的值。
    @CachePut(cacheNames = "dept",key="#dept.id")
    public Dept update(Dept dept){
        //1.先该数据库还是先删缓冲。
        deptDao.updateById(dept);
        return dept;
    }
    //
//    public Dept insert(Dept dept){
//          deptDao.insert(dept);
//          return dept;
//    }
//
    //删除缓存再执行方法体
    @CacheEvict(cacheNames = "dept",key = "#id")
    public int delete(int id){
        deptDao.deleteById(id);
        return 1;
    }
}

4.redis解决分布式锁

锁:保证程序的原子性操作

 

 

 

上面由于线程安全问题:出现了重卖以及超卖现象。

如何解决上面的问题:加锁.

 synchronized或lock锁---本地JVM。

 

 如果我们的项目是一个集群模式

 

 上面的情况也会出现线程安全问题。

 

 

 

 

 配置nginx

 

 启动nginx

 

 使用jemter压测时又出现了上面的现象

 

 

如何解决:---使用分布式锁。

如何实现分布式---redis可以实现或zookeeper实现

 

 

package com.ykq.distrinctlock.service.impl;
import com.ykq.distrinctlock.dao.ProductStockDao;
import com.ykq.distrinctlock.service.ProductStockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class ProductStockServiceImpl implements ProductStockService {
    @Autowired
    private ProductStockDao productStockDao;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Override
    public  String decreaseStock(Integer productId) {
        ValueOperations<String, String> forValue = redisTemplate.opsForValue();
        Boolean flag = forValue.setIfAbsent("product:" + productId, "霍梦齐", 30, TimeUnit.SECONDS);
        if(flag) {//表示获取分布锁成功
            try {
                //查看该商品的库存数量
                Integer stock = productStockDao.findStockByProductId(productId);
                if (stock > 0) {
                    //修改库存每次-1
                    productStockDao.updateStockByProductId(productId);
                    System.out.println("扣减成功!剩余库存数:" + (stock - 1));
                    return "success";
                } else {
                    System.out.println("扣减失败!库存不足!");
                    return "fail";
                }
            }finally {
                redisTemplate.delete("product:"+productId);//释放锁资源
            }
        }else{
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return decreaseStock(productId);
        }
    }
}

缺陷: 当程序执行时间超过锁的时间。

解决: 使用watch dog机制。----第三方框架完成redisson

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值