Redis(三)

1. java连接redis

java提高连接redis的方式jedis. 我们需要遵循jedis协议。

引入依赖

<!--引入java连接redis的驱动--><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>4.3.1</version></dependency>

2 java连接redis集群模式

 public static void main(String[] args) {Set<HostAndPort> 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项目。

3. springboot整合redis

starter启动依赖。---包含自动装配类---完成相应的装配功能。

引入依赖

  <!--引入了redis整合springboot 的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

修改配置文件

#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里面只能存放字符串的内容。

1. 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 的字符串操作@Autowiredprivate StringRedisTemplate stringRedisTemplate;/*** 测试 Redis Hash 的操作,包括添加、获取和删除数据。*///关于Hash的操作@Testvoid testHash(){// 获取 Hash 操作实例HashOperations<String, String, Object> ops = stringRedisTemplate.opsForHash();// 向 Hash 中添加单个字段值//存放数据 hset(k,f,v)ops.put("user","name","zql");// 向 Hash 中添加多个字段值Map<String, Object> 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<String, Object> user = ops.entries("user");// 获取 Hash 中的所有字段名Set<String> keys = ops.keys("user");// 获取 Hash 中的所有字段值List<Object> values = ops.values("user");}/*** 测试 Redis 字符串操作,包括设置、获取和递增操作。*///关于String字符串的操作@Testvoid testString(){// 获取字符串操作实例//得到操作字符串的类对象ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();// 设置字符串值//存放数据---set(k,v)ops.set("k1","v1");ops.set("k2","2");// 为字符串设置过期时间并设置值//存放数据--settex(k,second,v)ops.set("k3","v3",30, TimeUnit.SECONDS);// 如果键不存在,则设置键值并设置过期时间Boolean aBoolean = ops.setIfAbsent("k4", "v4", 30, TimeUnit.SECONDS);// 获取字符串值String k1 = ops.get("k1");// 对字符串进行递增操作Long k2 = ops.increment("k2", 10);}/*** 测试 Redis 关于键的操作,包括查找、删除、检查存在性和设置过期时间。*///关于key的操作@Testvoid testKeys() {// 查找所有匹配的键Set<String> keys = stringRedisTemplate.keys("*");// 删除指定的键Boolean k1 = stringRedisTemplate.delete("k1");// 检查键是否存在Boolean k11 = stringRedisTemplate.hasKey("k1");// 为键设置过期时间Boolean k12 = stringRedisTemplate.expire("k4", 30, TimeUnit.SECONDS);}}

2. RedisTemplate

它属于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;
@Testpublic 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实例,配置了键值的序列化方式。*/@Beanpublic RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){RedisTemplate<String,Object> template=new RedisTemplate<>();//创建redisTemplate对象RedisSerializer<String> 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;}
}

4. 集群模式

在配置文件里面配置集群的配置

#集群模式
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
 

5. 短信业务

项目结构

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 {@Autowiredprivate 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.将验证码存入redisredisTemplate.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/Dysmsapiconfig.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 paramsjava.util.Map<String, Object> 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 optionscom.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**/
@SpringBootTestclass Test01 {@Testpublic void test01()throws Exception{System.out.println(SendMsgUtil.sendCode("*********"));}
}

5.1 注册登录功能

6. redis的使用场景-热点数据缓存

6.1 什么是缓存?

为了把一些经常访问的数据,放入缓存中以减少对数据库的访问频率。从而减少数据库的压力,提高程序的性能。【内存中存储】

6.2 缓存的原理

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

1. 查询频率高且修改频率低
2. 数据安全性低

6.4 哪个组件可以作为缓存

  1. redis组件

  2. memory组件

  3. ehcache组件

6.5 java使用redis如何实现缓存功能

配置文件

# 应用服务 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<String,Object> redisTemplate(RedisConnectionFactory factory){RedisTemplate<String,Object> template=new RedisTemplate<>();//创建redisTemplate对象RedisSerializer<String> 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;}@Beanpublic 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;}}

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 {@Autowiredprivate 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<Dept> {
}

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 {@Autowiredprivate DeptDao deptDao;@Autowiredprivate RedisTemplate<String,Object> redisTemplate;@Overridepublic Dept getById(Integer id) {//1.查询redis缓存是否命中ValueOperations<String, Object> 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;}@Overridepublic Dept insert(Dept dept) {int insert = deptDao.insert(dept);return dept;}@Overridepublic Dept update(Dept dept) {// 更新数据库int i = deptDao.updateById(dept);if (i>0){//删除缓存redisTemplate.opsForValue().set("dept::"+dept.getDid(),dept);}return dept;}@Overridepublic int delete(Integer id) {int i = deptDao.deleteById(id);if (i>0){//删除缓存redisTemplate.delete("dept::"+id);}return i;}
}

发现: 业务层代码除了要维护核心业务功能外,额外还要维护缓存的代码。

如何解决: 使用AOP面向切面编程。

spring框架也能想到。---aop切面来解决。

6.6 使用缓存注解完成缓存功能

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 {@Autowiredprivate DeptDao deptDao;@Autowiredprivate RedisTemplate<String,Object> redisTemplate;//Cacheable:表示查询时使用的注解。 cacheNames:缓存的名称  key:缓存的唯一表示值//   1. 查询缓存中是否存在名称为cacheNames::key的值//2.如果存在则方法不会执行//   3. 如果不存在则执行方法体并把方法的返回结果放入缓存中cacheNames::key@Cacheable(cacheNames = "dept",key = "#id")@Overridepublic Dept getById(Integer id) {//1.查询redis缓存是否命中//查询数据库Dept dept = deptDao.selectById(id);return dept;}@Overridepublic Dept insert(Dept dept) {int insert = deptDao.insert(dept);return dept;}//CachePut:表示修改时使用的注解.// 1. 先执行方法体// 2. 把方法的返回结果放入缓存中
@CachePut(cacheNames = "dept",key = "#dept.did")@Overridepublic Dept update(Dept dept) {// 更新数据库int i = deptDao.updateById(dept);return dept;}//CacheEvict:表示删除时使用的注解// 1. 先执行方法体// 2. 把缓存中名称为cacheNames::key的值删除@CacheEvict(cacheNames = "dept",key = "#id")@Overridepublic int delete(Integer id) {int i = deptDao.deleteById(id);return i;}
}

开启缓存注解

7. redis的使用场景--分布式锁.

模拟高并发:---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 {@Autowiredprivate 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 {@Autowiredprivate StockDao stockDao;@Autowiredprivate StringRedisTemplate redisTemplate;//public String decrement(Integer productid) {ValueOperations<String, String> 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依赖
      <!--①依赖-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.24.3</version>
        </dependency>

@Configuration
public class RedissonConfig {@Beanpublic 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 {@Autowiredprivate StockDao stockDao;@Autowiredprivate 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();}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/50534.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android Framework 之AMS

它管理了系统的四大组件:Activity、Service、ContentProvider、Broadcast。 它除了管理四大组件外&#xff0c;同时也负责管理和调度所有的进程 AMS相关目录结构 AMS代码主要在下面几个目录(AndroidQ上AMS相关部分功能移到了wm下)&#xff1a; frameworks/base/core/java/andro…

记录|LabVIEW从0开始

目录 前言一、表达式节点和公式节点二、脚本与公式2.1 公式 三、Excel表格3.1 位置3.2 案例&#xff1a;波形值存入Excel表中3.3 案例&#xff1a;行写入&#xff0c;列写入 四、时间格式化4.1 获取当前时间4.2 对当前时间进行格式化 更新时间 前言 参考视频&#xff1a; LabVI…

【STL】之 vector 使用方法及模拟实现

前言&#xff1a; 本文主要讲在C STL库中vector容器的使用方法和底层的模拟实现~ 成员变量的定义&#xff1a; 对于vector容器&#xff0c;我们首先采用三个成员变量去进行定义&#xff0c;分别是&#xff1a; private:iterator _start; // 指向数据块的开始iterator _finish…

React类组件生命周期与this关键字

类组件生命周期 参考链接 一图胜千言&#xff08;不常用的生命周期函数已隐藏&#xff09; 代码&#xff1a; //CC1.js import { Component } from "react";export default class CC1 extends Component {constructor(props) {super(props);console.log("con…

【Vue3】watchEffect

【Vue3】watchEffect 背景简介开发环境开发步骤及源码 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0c;技术出身的人总是很难放下一些执念&#xff0c;遂将这些知识整理成文&#xff0c;以纪念曾经努力学习奋斗的日子。本文…

【代码随想录第37天| 完全背包,518. 零钱兑换 II ,377. 组合总和 Ⅳ,70. 爬楼梯 (进阶)】

完全背包 完全背包中&#xff0c;每件物品都有无数件&#xff1b;这主要影响了遍历背包容量时的遍历顺序&#xff0c;应该从小到大去遍历&#xff0c;这样才能包括有多件相同物品的情况。 思路 先遍历物品&#xff0c;再遍历背包 for(int i 0; i < weight.size(); i) {…

pnpm 设置国内源

pnpm config set registry https://registry.npmmirror.com/

16、DDD系列-向微服务迈进

在本章中&#xff0c;我们将探讨如何从传统的单体架构向微服务架构演进。这个过程需要考虑许多因素&#xff0c;包括微服务的驱动力、所需条件、服务粒度的确定以及系统复杂性的治理。 1、目的&#xff1a;微服务的驱动力 微服务的主要驱动力是业务需求的快速变化和系统的可扩…

[Mysql-数据库基本知识了解]

为什么学习数据库&#xff1f; 数据的保存&#xff1a; 大量程序产生的数据在程序 运行时和程序结束运行后 数据应该怎么保存&#xff1f; 数据的完整性 &#xff1a;数据和数据之间的结构关系&#xff0c; 数据和程序之间的依赖关系&#xff0c; 如何能让这些关系持久维系…

C++初学(7)

7.1、字符串 字符串是存储在内存的连续字节中的一系列字符。C处理字符串的方式有两种&#xff0c;第一种是来自C语言&#xff0c;常被称为C风格字符串&#xff0c;另一种则是基于string类库的方法。 存储在连续字节中的一系列字符意味着可以将字符存储在char数组中&#xff0…

微信小程序开发 快速学习 这篇就够了

目录 一、配置篇 &#xff08;1&#xff09;官网链接&#xff1a; &#xff08;2&#xff09;项目分析 &#xff08;3&#xff09;调试器 &#xff08;4&#xff09;预览体验 &#xff08;5&#xff09;配置文件 &#xff08;6&#xff09;配置pages &#xff08;7&…

vue3组件通信(二)

组件通信 一.$attrs(祖>孙间接&#xff09;二、$refs()父>子&#xff0c; $parent()子>父三.provide&#xff0c;inject(祖>孙直接&#xff09;四.pinia五.slot1.默认插槽2.具名插槽3.作用域插槽 一.$attrs(祖>孙间接&#xff09; $attrs用于实现当前组件的父组…

实习心得—20240725

在实习的过程中&#xff0c;遇到了很多问题&#xff0c;对于fpga开发来说&#xff0c;我一开始值懂得一些皮毛&#xff0c;仅限于读懂代码或者写一些简单的代码&#xff0c;当接触到一个大的项目的时候&#xff0c;我发现很多事情并不是想象中的那么容易。 一个大的项目是由许…

CSRF Token 原理

CSRF 攻击 CSRF 攻击成功的关键是&#xff0c;恶意网站让浏览器自动发起一个请求&#xff0c;这个请求会自动携带 cookie &#xff0c;正常网站拿到 cookie 后会认为这是正常用户&#xff0c;就允许请求。 防范 如果在请求中加一个字段&#xff08;CSRF Token&#xff09;&am…

鸿蒙开发—黑马云音乐之Music页面

目录 1.外层容器效果 2.信息区-发光效果 3.信息区-内容布局 4.播放列表布局 5.播放列表动态化 6.模拟器运行并配置权限 效果&#xff1a; 1.外层容器效果 Entry Component export struct MuiscPage {build() {Column() {// 信息区域Column() {}.width(100%)// .backgroun…

kubernetes管理GUI工具Lens

从github上可以知道&#xff0c;lens的前端是用electron做的客户端工具&#xff0c;打开安装路径你会发现kubectl.exe,没错&#xff0c;就是你经常用的kubectl命令行的客户端工具。kubectl本来就能输出json的数据类型&#xff0c;集成前端更方便了。看到这里你是不是发现&#…

MATLAB学习日志DAY23

创建矩阵 MATLAB 提供了许多函数&#xff0c;用于创建各种类型的矩阵。 例如&#xff0c;可以使用基于帕斯卡三角形的项创建一个对称矩阵&#xff1a; A pascal(3) A 1 1 1 1 2 3 1 3 6 也可以创建一个非对称幻方矩阵&#xff0c;它的行总和与列总和相等&#xff1a; B…

操作系统(系统简介、库文件、环境变量、编译器、系统特性)

一、UNIX系统介绍 诞生于1971年美国AT&T公司的贝尔实验室&#xff0c;主要开发者是丹尼斯.里奇、肯.汤普逊。 该系统的主要特点是支持多用户、多任务&#xff0c;并支持多种处理器架构&#xff0c;同时具有高安全性、高可靠性、高稳定性&#xff0c;既可以构建大型关键业…

Linux云计算 |【第二阶段】AUTOMATION-DAY5

主要内容&#xff1a; YAML语法格式&#xff0c;层级关系、Ansible Playbook文件及语法格式、Ansible变量&#xff08;定义变量方法、优先级顺序、setup和debug查看变量&#xff09; 补充&#xff1a;Ansible ad-hoc 可以通过命令行形式远程管理其他主机&#xff0c;适合执行一…

视频逐帧播放查看神器-android闪黑闪白等分析辅助工具

背景 刚好有学员朋友在群里问道有没有什么播放软件可以实现对视频的逐帧即一帧一帧播放。在做android系统开发时候经常会偶尔遇到有时候是闪黑&#xff0c;闪白等一瞬间现象的问题。这类问题要分析的话就不得不需要对设备录屏&#xff0c;然后对录屏进行逐帧播放查看现象&…