java提高连接redis的方式jedis. 我们需要遵循jedis协议。
引入依赖
redis.clients jedis 4.3.1
public static void main(String[] args) { Set nodes=new HashSet<>(); nodes.add(new HostAndPort("192.168.111.188",7001)); nodes.add(new HostAndPort("192.168.111.188",7002)); nodes.add(new HostAndPort("192.168.111.188",7003)); nodes.add(new HostAndPort("192.168.111.188",7004)); nodes.add(new HostAndPort("192.168.111.188",7005)); nodes.add(new HostAndPort("192.168.111.188",7006)); JedisCluster jedisCluster=new JedisCluster(nodes); jedisCluster.set("k5","666"); System.out.println(jedisCluster.get("k5")); }
适合ssm项目。
starter启动依赖。---包含自动装配类---完成相应的装配功能。
引入依赖
org.springframework.boot
spring-boot-starter-data-redis
修改配置文件
#redis 配置 spring.redis.host=192.168.100.104 spring.redis.port=6379 spring.redis.database=1 spring.redis.jedis.pool.max-active=100 spring.redis.jedis.pool.max-idle=10 spring.redis.jedis.pool.max-wait=10000ms
使用
springboot整合redis时封装了两个工具类:StringRedisTemplate和RedisTemplate.
StringRedisTemplate它是RedisTemplate的子类。StringRedisTemplate里面只能存放字符串的内容。
package com.zql; 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.StringRedisTemplate; import org.springframework.data.redis.core.ValueOperations; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; /** * Spring Boot Redis 应用的测试类,用于验证 Redis 的集成和操作。 */ @SpringBootTest class SpringBootRedisApplicationTests { // 自动注入 StringRedisTemplate,用于进行 Redis 的字符串操作 @Autowired private StringRedisTemplate stringRedisTemplate; /** * 测试 Redis Hash 的操作,包括添加、获取和删除数据。 */ //关于Hash的操作 @Test void testHash(){ // 获取 Hash 操作实例 HashOperations ops = stringRedisTemplate.opsForHash(); // 向 Hash 中添加单个字段值 //存放数据 hset(k,f,v) ops.put("user","name","zql"); // 向 Hash 中添加多个字段值 Map map=new HashMap<>(); map.put("name","zql"); map.put("age","18"); map.put("sex","男"); ops.putAll("user",map); // 获取 Hash 中的单个字段值 //获取指定的元素 Object name = ops.get("user", "name"); // 获取 Hash 中的所有字段值 Map user = ops.entries("user"); // 获取 Hash 中的所有字段名 Set keys = ops.keys("user"); // 获取 Hash 中的所有字段值 List
它属于StringRedisTemplate的父类,它的泛型默认都是Object。它可以直接存储任意类型的key和value.
package com.zql; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer; import com.zql.entity.User; 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.ValueOperations; import org.springframework.data.redis.serializer.StringRedisSerializer; @SpringBootTest class SpringBootRedisApplicationTest02 { @Autowired private RedisTemplate redisTemplate; @Test public void test01() { //指定key的序列化方式 redisTemplate.setKeySerializer(new StringRedisSerializer()); //指定value的序列化方式 redisTemplate.setValueSerializer(new FastJsonRedisSerializer<>(Object.class)); ValueOperations valueOperations = redisTemplate.opsForValue(); valueOperations.set("name","zhangsan"); System.out.println(valueOperations.get("name")); valueOperations.set ("k6",new User("zql",15)); JSONObject k6=(JSONObject) valueOperations.get("k6"); HashOperations forHash = redisTemplate.opsForHash(); forHash.put("u","n","zhangsan"); forHash.put("u","张三","张三杀回"); } }
我们可以自己写个工具类
package com.zql.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; /** * @program: spring-boot01 * @description: * @author: * @create: 2024-07-23 19:47 **/ @Configuration public class RedisConfig { /** * 配置Redis模板 * * @param factory Redis连接工厂,用于创建Redis连接。 * @return RedisTemplate实例,配置了键值的序列化方式。 */ @Bean public RedisTemplate redisTemplate(RedisConnectionFactory factory){ RedisTemplate template=new RedisTemplate<>();//创建redisTemplate对象 RedisSerializer redisSerializer=new StringRedisSerializer();//字符串序列化器 Jackson2JsonRedisSerializer jackson2JsonRedisSerializer=new Jackson2JsonRedisSerializer<>(Object.class);//json序列化器 // 配置ObjectMapper,用于序列化和反序列化Java对象为JSON。 ObjectMapper om=new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); // 配置Redis模板的序列化方式。 template.setConnectionFactory(factory); //key序列化方式 template.setKeySerializer(redisSerializer); //value序列化方式 template.setValueSerializer(jackson2JsonRedisSerializer); //hash的key序列化方式 template.setHashKeySerializer(jackson2JsonRedisSerializer); template.setHashValueSerializer(redisSerializer); return template; } }
在配置文件里面配置集群的配置
#集群模式
spring.redis.cluster.nodes=192.168.111.188:7006,192.168.111.188:7001,192.168.111.188:7002,192.168.111.188:7003,192.168.111.188:7004,192.168.111.188:7005
项目结构
controller
package com.zql.controller; import com.zql.SendMsgUtil; import com.zql.vo.LoginVo; import com.zql.vo.R; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import java.util.concurrent.TimeUnit; /** * @program: spring-boot01 * @description: * @author: * @create: 2024-07-24 18:35 **/ @RestController @RequestMapping("/msg") public class MsgController { @Autowired private StringRedisTemplate redisTemplate; @GetMapping("send") public R send(String phone) throws Exception { //1.校验手机号是否存在 --连接数据库 if (phone.equals("13938300924")) { if (redisTemplate.hasKey("code::" + phone)) { return new R(500, "验证码已发送", null); } //2. 发送验证码 String code = SendMsgUtil.sendCode(phone); //3.将验证码存入redis redisTemplate.opsForValue().set("code::" + phone, code, 5, TimeUnit.MINUTES); return new R(200, "发送成功", null); } return new R(500, "手机号未注册", null); } @PostMapping("login") public R login(@RequestBody LoginVo loginVo) { //1.校验验证码 String code = redisTemplate.opsForValue().get("code::" + loginVo.getPhone()); String phone = loginVo.getPhone(); if (StringUtils.hasText(loginVo.getCode()) && loginVo.getCode().equals(code)) { //2.登录成功 if (phone.equals("13938300924")) { // redisTemplate.delete("code::" + phone); return new R(200, "登录成功", null); } else { return new R(500, "手机号未注册", null); } } return new R(500, "验证码错误", null); } }
entity
package com.zql.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; /** * @program: spring-boot01 * @description: * @author: * @create: 2024-07-23 19:18 **/ @Data @AllArgsConstructor @NoArgsConstructor public class User implements Serializable { private String name; private int age; }
vo
package com.zql.vo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * @program: spring-boot01 * @description: * @author: * @create: 2024-07-24 18:46 **/ @Data @AllArgsConstructor @NoArgsConstructor public class LoginVo { private String phone; private String code; }
package com.zql.vo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * @program: spring-boot01 * @description: * @author: * @create: 2024-07-24 18:40 **/ @Data @AllArgsConstructor @NoArgsConstructor public class R { private Integer code; private String msg; private Object data; }
发送短信的配置类 里面带****的使用自己的配置就行
package com.zql; import cn.hutool.captcha.generator.RandomGenerator; import com.aliyun.teaopenapi.Client; import org.springframework.beans.factory.annotation.Value; /** * @Author: * @Description: * @Date: Create in 17:30 2024/7/23 */ public class SendMsgUtil { /** * 使用AK&SK初始化账号Client * @return Client * @throws Exception */ @Value("${aliyun.msg.accessKeyId}") private static String accessKeyId; public static Client createClient() throws Exception { com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config() // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。 .setAccessKeyId("**************") // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。 .setAccessKeySecret("****************"); // Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi config.endpoint = "dysmsapi.aliyuncs.com"; return new com.aliyun.teaopenapi.Client(config); } /** * API 相关 * @return OpenApi.Params */ public static com.aliyun.teaopenapi.models.Params createApiInfo() throws Exception { com.aliyun.teaopenapi.models.Params params = new com.aliyun.teaopenapi.models.Params() // 接口名称 .setAction("SendSms") // 接口版本 .setVersion("2017-05-25") // 接口协议 .setProtocol("HTTPS") // 接口 HTTP 方法 .setMethod("POST") .setAuthType("AK") .setStyle("RPC") // 接口 PATH .setPathname("/") // 接口请求体内容格式 .setReqBodyType("json") // 接口响应体内容格式 .setBodyType("json"); return params; } public static String sendCode(String phone) throws Exception { com.aliyun.teaopenapi.Client client = createClient(); com.aliyun.teaopenapi.models.Params params = createApiInfo(); // query params java.util.Map queries = new java.util.HashMap<>(); queries.put("PhoneNumbers", phone); queries.put("SignName", "智友"); queries.put("TemplateCode", "SMS_173342144"); //您正在申请手机注册,验证码为:${code},5分钟内有效! RandomGenerator randomGenerator=new RandomGenerator("0123456789",6); String code= randomGenerator.generate(); queries.put("TemplateParam", "{\"code\":\""+code+"\"}"); // runtime options com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); com.aliyun.teaopenapi.models.OpenApiRequest request = new com.aliyun.teaopenapi.models.OpenApiRequest() .setQuery(com.aliyun.openapiutil.Client.query(queries)); // 复制代码运行请自行打印 API 的返回值 // 返回值为 Map 类型,可从 Map 中获得三类数据:响应体 body、响应头 headers、HTTP 返回的状态码 statusCode。 client.callApi(params, request, runtime); return code; } }
测试
package com.zql; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; /** * @program: spring-boot01 * @description: * @author: * @create: 2024-07-23 20:40 **/ @SpringBootTest class Test01 { @Test public void test01()throws Exception{ System.out.println(SendMsgUtil.sendCode("*********")); } }
为了把一些经常访问的数据,放入缓存中以减少对数据库的访问频率。从而减少数据库的压力,提高程序的性能。【内存中存储】
1. 查询频率高且修改频率低 2. 数据安全性低
redis组件
memory组件
ehcache组件
等
配置文件
# 应用服务 WEB 访问端口 server.port=8080 #数据库 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/zql?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=123456 #映射文件路径 mybatis-plus.mapper-locations=classpath*:mapper/*.xml #配置日志--sql日志 mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl #redis 配置 spring.redis.host=192.168.100.104 spring.redis.port=6379 spring.redis.database=1 spring.redis.jedis.pool.max-active=100 spring.redis.jedis.pool.max-idle=10 spring.redis.jedis.pool.max-wait=10000ms
项目结构
config
package com.zql.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.cache.CacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; 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.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.time.Duration; /** * @program: spring-boot01 * @description: * @author: 赵庆龙 * @create: 2024-07-23 19:47 **/ @Configuration public class RedisConfig { @Bean//把该方法返回的类对象交与spring容器管理 public RedisTemplate redisTemplate(RedisConnectionFactory factory){ RedisTemplate template=new RedisTemplate<>();//创建redisTemplate对象 RedisSerializer redisSerializer=new StringRedisSerializer();//字符串序列化器 Jackson2JsonRedisSerializer jackson2JsonRedisSerializer=new Jackson2JsonRedisSerializer<>(Object.class);//json序列化器 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); //hash的key序列化方式 template.setHashKeySerializer(jackson2JsonRedisSerializer); template.setHashValueSerializer(redisSerializer); return template; } @Bean public CacheManager cacheManager(RedisConnectionFactory factory) { 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); // 配置序列化(解决乱码的问题),过期时间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; } }
controller
package com.zql.controller; import com.zql.entity.Dept; import com.zql.service.DeptService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; /** * @program: spring-boot01 * @description: * @author: * @create: 2024-07-25 09:16 **/ @RestController @RequestMapping("/dept") public class DeptController { @Autowired private DeptService deptService; @GetMapping("/getById/{id}") public Dept getById(@PathVariable Integer id) { Dept dept = deptService.getById(id); return dept; } @PostMapping("/insert") public Dept insert(@RequestBody Dept dept) { return deptService.insert(dept); } @PutMapping("/update") public Dept update(@RequestBody Dept dept) { return deptService.update(dept); } @DeleteMapping("/delete/{id}") public int delete(@PathVariable Integer id) { return deptService.delete(id); } }
dao
package com.zql.dao; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.zql.entity.Dept; /** * @program: spring-boot01 * @description: * @author: * @create: 2024-07-24 20:00 **/ public interface DeptDao extends BaseMapper { }
entity
package com.zql.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * @program: spring-boot01 * @description: * @author: * @create: 2024-07-24 19:58 **/ @Data @NoArgsConstructor @AllArgsConstructor @TableName("tb_dept") public class Dept { @TableId(type = IdType.AUTO) private Integer did; private String dname; private String loc; }
service
package com.zql.service; import com.zql.entity.Dept; /** * @program: spring-boot01 * @description: * @author: * @create: 2024-07-24 20:02 **/ public interface DeptService { public Dept getById(Integer id); public Dept insert(Dept dept); public Dept update(Dept dept); public int delete(Integer id); }
package com.zql.service.impl; import com.zql.dao.DeptDao; import com.zql.entity.Dept; import com.zql.service.DeptService; 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; /** * @program: spring-boot01 * @description: * @author: * @create: 2024-07-24 20:07 **/ @Service public class DeptServiceImpl01 implements DeptService { @Autowired private DeptDao deptDao; @Autowired private RedisTemplate redisTemplate; @Override public Dept getById(Integer id) { //1.查询redis缓存是否命中 ValueOperations ops = redisTemplate.opsForValue(); Object o=ops.get("dept::"+id); //表示缓存命中 if (o!=null){ return (Dept) o; } //查询数据库 Dept dept = deptDao.selectById(id); if (dept!=null){ ops.set("dept::"+id,dept); } return dept; } @Override public Dept insert(Dept dept) { int insert = deptDao.insert(dept); return dept; } @Override public Dept update(Dept dept) { // 更新数据库 int i = deptDao.updateById(dept); if (i>0){ //删除缓存 redisTemplate.opsForValue().set("dept::"+dept.getDid(),dept); } return dept; } @Override public int delete(Integer id) { int i = deptDao.deleteById(id); if (i>0){ //删除缓存 redisTemplate.delete("dept::"+id); } return i; } }
发现: 业务层代码除了要维护核心业务功能外,额外还要维护缓存的代码。
如何解决: 使用AOP面向切面编程。
spring框架也能想到。---aop切面来解决。
package com.zql.service.impl; import com.zql.dao.DeptDao; import com.zql.entity.Dept; import com.zql.service.DeptService; 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.stereotype.Service; /** * @program: spring-boot01 * @description: * @author: * @create: 2024-07-24 20:07 **/ @Service public class DeptServiceImpl implements DeptService { @Autowired private DeptDao deptDao; @Autowired private RedisTemplate redisTemplate; //Cacheable:表示查询时使用的注解。 cacheNames:缓存的名称 key:缓存的唯一表示值 // 1. 查询缓存中是否存在名称为cacheNames::key的值 //2.如果存在则方法不会执行 // 3. 如果不存在则执行方法体并把方法的返回结果放入缓存中cacheNames::key @Cacheable(cacheNames = "dept",key = "#id") @Override public Dept getById(Integer id) { //1.查询redis缓存是否命中 //查询数据库 Dept dept = deptDao.selectById(id); return dept; } @Override public Dept insert(Dept dept) { int insert = deptDao.insert(dept); return dept; } //CachePut:表示修改时使用的注解. // 1. 先执行方法体 // 2. 把方法的返回结果放入缓存中 @CachePut(cacheNames = "dept",key = "#dept.did") @Override public Dept update(Dept dept) { // 更新数据库 int i = deptDao.updateById(dept); return dept; } //CacheEvict:表示删除时使用的注解 // 1. 先执行方法体 // 2. 把缓存中名称为cacheNames::key的值删除 @CacheEvict(cacheNames = "dept",key = "#id") @Override public int delete(Integer id) { int i = deptDao.deleteById(id); return i; } }
开启缓存注解
模拟高并发:---jmeter压测工具
通过压测发现库存超卖和重卖了。---解决办法使用锁
syn和lock锁。
package com.ykq.service; import com.ykq.dao.StockDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @program: distinct-lock * @description: * @author: * @create: 2023-08-31 16:49 **/ @Service public class StockService01 { @Autowired private StockDao stockDao; //如果在单线程下---该功能没有任何问题。 //如果在多线程的情况---jmeter压测工具---发现出现线程安全问题了。==使用锁synchronized ()或lock锁。 //发现使用锁之前没有问题了。但是如果该项目是一个集群。--发现在集群的情况下本地锁【只对当前工程有效】无效了。 //解决方案就是集群工程共用一把锁就行。---可以使用redis来解决问题。 public String decrement(Integer productid) { //根据id查询商品的库存 synchronized (this) { 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 + "的商品库存不足。"; } } } }
上面使用syn和lock虽然解决了并发问题,但是我们未来项目部署时可能要部署集群模式。
nginx代理集群
通过压测发现本地锁 无效了。使用redis解决分布式锁文件
package com.ykq.service; import com.ykq.dao.StockDao; 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; /** * @program: distinct-lock * @description: * @author: * @create: 2023-08-31 16:49 **/ @Service public class StockService { @Autowired private StockDao stockDao; @Autowired private StringRedisTemplate redisTemplate; // public String decrement(Integer productid) { ValueOperations opsForValue = redisTemplate.opsForValue(); //1.获取共享锁资源 Boolean flag = opsForValue.setIfAbsent("product::" + productid, "1111", 30, TimeUnit.SECONDS); //表示获取锁成功 if(flag) { try { //根据id查询商品的库存 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 { //释放锁资源 redisTemplate.delete("product::"+productid); } }else{ //休眠100毫秒 在继续抢锁 try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } return decrement(productid); } } }
redis超时问题[业务代码执行时间超过了上锁时间]. 第三方redisson
引入redisson依赖
@Configuration public class RedissonConfig { @Bean public RedissonClient redisson(){ Config config = new Config(); // //连接的为redis集群 // config.useClusterServers() // // use "rediss://" for SSL connection // .addNodeAddress("redis://127.0.0.1:7181","","","") // ; //连接单机 config.useSingleServer().setAddress("redis://192.168.111.188:6379"); RedissonClient redisson = Redisson.create(config); return redisson; } }
package com.ykq.service; import com.ykq.dao.StockDao; import org.redisson.Redisson; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; 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 StockService { @Autowired private StockDao stockDao; @Autowired private RedissonClient redisson; // public String decrement(Integer productid) { RLock lock = redisson.getLock("product::" + productid); lock.lock(); try { //根据id查询商品的库存: 提前预热到redis缓存中 int num = stockDao.findById(productid); if (num > 0) { //修改库存---incr---定时器[redis 数据库同步] stockDao.update(productid); System.out.println("商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个"); return "商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个"; } else { System.out.println("商品编号为:" + productid + "的商品库存不足。"); return "商品编号为:" + productid + "的商品库存不足。"; } }finally { lock.unlock(); } } }