springboot对redis的操作封装了两个StringRedisTemplate和RedisTemplate类,StringRedisTemplate是RedisTemplate的子类,StringRedisTemplate它只能存储字符串类型,无法存储对象类型。要想用StringRedisTemplate存储对象必须把对象转为json字符串。
1.1.StringRedisTemplate(1) 引入相关的依赖
org.springframework.boot spring-boot-starter-data-redis
(2)注入StringRedisTemplate该类对象
@Autowired private StringRedisTemplate redisTemplate;
(3)使用StringRedisTemplate
该类把对每种数据类型的操作,单独封了相应的内部类。
对String类型的数据操作
//对String类型的操作 @Test void test01() { ValueOperationsforValue = stringRedisTemplate.opsForValue(); //时间存储 时间结束后销毁 forValue.set("k1","李四",30l, TimeUnit.SECONDS); //获取k1的value值 String s = forValue.get("k1"); System.out.println(s); //若存在,不存入,不存在则存入 返回布尔值 Boolean aBoolean = forValue.setIfAbsent("k1", "张三", 30l, TimeUnit.SECONDS); System.out.println(aBoolean); //追加 Integer i = forValue.append("k1", "是个人"); System.out.println(i); }
对Hash类型的数据操作
//对Hash类型的操作 @Test void test02(){ HashOperationsforHash = stringRedisTemplate.opsForHash(); forHash.put("k1","name","张三"); //必须都是字符串类型,虽然上面泛型是Object 但使用的是spring的序列化 Integer 无法转为String forHash.put("k1","age","18"); Map map=new HashMap<>(); map.put("name","李四"); map.put("age","25"); forHash.putAll("k2",map); Object o = forHash.get("k1", "name"); System.out.println(o); Set
注意:
1.2.RedisTemplate使用RedisTemplate 必须要指定序列化方式,默认使用jdk序列化方式。但会引起乱码,而且占用内存大。
@Autowired private RedisTemplate redisTemplate; @Test void test01(){ //指定key的序列化方式 redisTemplate.setKeySerializer(new StringRedisSerializer()); //指定value的序列化方式 GenericJackson2JsonRedisSerializer()/Jackson2JsonRedisSerializer(Object.class) redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer (Object.class)); ValueOperations forValue = redisTemplate.opsForValue(); forValue.set("k1","张三",60l,TimeUnit.SECONDS); //value默认采用jdk,类必须实现序列化接口 forValue.set("k2",new User(1,"李四","123456")); }
上面的RedisTemplate需要每次都指定key value以及field的序列化方式,可以创建一个配置类,为RedisTemplate指定好序列化。以后再用就无需指定。
@Configuration public class RedisConfig { @Bean public RedisTemplate2.redis的使用场景 2.1.作为缓存redisTemplate(RedisConnectionFactory factory) { RedisTemplate template = new RedisTemplate<>(); RedisSerializer 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)数据存储在内存中,数据查询速度快。可以分摊数据库压力。
(2)什么样的数据适合放入缓存
查询频率比较高,修改频率比较低。
安全系数低的数据
(3)使用redis作为缓存
@Autowired private UserMapper userMapper; @Autowired private RedisTemplate redisTemplate; //根据id查询 public User findById(Integer id){ ValueOperations forValue = redisTemplate.opsForValue(); //查询缓存 Object o = forValue.get("user::" + id); //缓存命中 if(o!=null){ return (User) o; } User user = userMapper.selectById(id); if(user!=null){ //存入缓存 forValue.set("user::"+id,user,2, TimeUnit.HOURS); } return user; } //根据id删除 public int delete(Integer id){ //先删除缓存再删除数据库中的数据 redisTemplate.delete("user::"+id); int i = userMapper.deleteById(id); return i; } //添加 public User insert(User user){ int i = userMapper.insert(user); return user; } //根据id修改 //先删掉缓存,再修改数据库 public User update(User user){ Integer id = user.getId(); redisTemplate.delete("user::"+id); int i = userMapper.updateById(user); return user; }
查看的缓存: 前部分代码相同@before通知,后部分代码也相同后置通知。 我们可以AOP完成缓存代码和业务代码分离。
spring框架它应该也能想到。--使用注解即可完成。解析该注解。
(1)把缓存的配置类加入
@Bean public CacheManager cacheManager(RedisConnectionFactory factory) { RedisSerializer2.2.分布式锁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; }
使用压测工具测试高并发下带来线程安全问题
同一个库存被使用了n次。以及数据库中库存为负数。 线程安全问题导致。
1. 解决方案: 使用 synchronized 或者lock锁
2.使用redis作为锁
nginx(windows系统下)
配置nginx文件 nginx.conf
开启nginx
注意nginx包的目录必须没有中文,否则无法开启
准备数据库文件
开启idea集群
测试代码:
controller层
@RestController @RequestMapping("productStock") public class ProductStockController { @Autowired private ProductStockService productStockService; //减库存 @RequestMapping("decreaseStock/{productId}") public String decreaseStock(@PathVariable("productId") Integer productId){ return productStockService.decreaseStock(productId); } }
service层
@Service public class ProductStockServiceImpl2 implements ProductStockService { @Autowired private ProductStockDao productStockDao; @Autowired private StringRedisTemplate stringRedisTemplate; @Override public String decreaseStock(Integer productId) { ValueOperationsforValue = stringRedisTemplate.opsForValue(); Boolean flag = forValue.setIfAbsent("dis::" + productId, "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); 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 { stringRedisTemplate.delete("dis::" + productId); } } return "服务忙,请稍后.........."; } }
sql语句
3.解决redis分布式锁的bugupdate tbl_stock set num=num-1 where productId=#{productId}
可以使用:redission依赖,redission解决redis超时问题的原理。
为持有锁的线程开启一个守护线程,守护线程会每隔10秒检查当前线程是否还持有锁,如果持有则延迟生存时间。
使用redis破解版(可以在windows中使用的),开启redis
org.redisson redisson3.13.4
//获取redisson对象并交于spring容器管理 @Bean public Redisson redisson(){ Config config =new Config(); config.useSingleServer(). setAddress("redis://192.168.226.234:6379"). //redis默认有16个数据库 setDatabase(0); return (Redisson) Redisson.create(config); }
测试代码
@Service public class ProductStockServiceImpl2 implements ProductStockService { @Autowired private ProductStockDao productStockDao; @Autowired private Redisson redisson; @Override public String decreaseStock(Integer productId) { //获取锁对象 RLock rlock = redisson.getLock("dis::"+productId); try{ rlock.lock(30, TimeUnit.SECONDS); //查看该商品的库存数量 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 { rlock.unlock(); } } }