案例:1.互联网秒杀 2.抢优惠卷 3.接口幂
引入pom文件
<packaging>war</packaging> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.8.RELEASE</version> </dependency> <!-- 实现分布式锁的工具类 --> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.17.7</version> </dependency> <!-- Spring操作Redis的工具类 --> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>2.3.2.RELEASE</version> </dependency> <!-- Redis客户端 --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.9.0</version> </dependency> <!-- JSON解析工具 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.4</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <configuration> <port>8001</port> <!-- /表示访问应用的路径,省略项目名 --> <path>/</path> <!-- 配置编码方式为UTF-8 --> <uriEncoding>UTF-8</uriEncoding> </configuration> <executions> <execution> <!-- 打包完成后,运行服务 --> <phase>package</phase> <goals> <goal>run</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
我们在pom文件中配置了打包后运行的jar包,所以我们要改变启动方式
在resources下创还能spring.xml的配置类
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation=" http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.leq"/>
<!-- 配置Redis连接工厂对象-->
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="192.168.40.100"></property>
<property name="port" value="6379"></property>
</bean>
<!-- Spring框架为了简化Redis操作提供了一个Redis操作的模板类:RedisTemplate-->
<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<!--ref表示该属性的取值是引用类型,将ref取值设置为外部bean的id值-->
<property name="connectionFactory" ref="connectionFactory"></property>
</bean>
</beans>
配置web.xml文件
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 配置解决中⽂乱码问题。此过滤器会进⾏:request.setCharacterEncoding("utf-8") --> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
抢购的业务:
@Autowired//简化Redis操作的模板类 private StringRedisTemplate stringRedisTemplate; @RequestMapping("seckill") public synchronized String secKill(){ String phone_count = stringRedisTemplate.opsForValue().get("phone_count"); int phoneCount = Integer.parseInt(phone_count); if(phoneCount>0){ //给redis中的数据添加锁 setnx() 在key不存在时,设置值 phoneCount--; stringRedisTemplate.opsForValue().set("phone_count",String.valueOf(phoneCount)); System.out.println("当前库存数量:"+phoneCount); }else { System.out.println("手机库存不足..."); } return "当前的手机数量:"+phoneCount; }
在一次性涌入大量数据的时候可能会出现-1或者同票的情况,这个时候就需要加锁
//RedissonClient实现分布式锁 @Autowired private RedissonClient redisson; @RequestMapping("seckill") public synchronized String secKill(){ //获取开发者自定义的锁 RLock redissonLock = redisson.getLock("phone"); //加锁,添加过期时间 1过期时间 2具体的单位 后面的代码都是受保护的 redissonLock.lock(30, TimeUnit.SECONDS); Integer phoneCount = null; try { //高并发抢购业务 String phone_count = stringRedisTemplate.opsForValue().get("phone_count"); phoneCount = Integer.parseInt(phone_count); if(phoneCount>0){ //给redis中的数据添加锁 setnx() 在key不存在时,设置值 phoneCount--; stringRedisTemplate.opsForValue().set("phone_count",String.valueOf(phoneCount)); System.out.println("当前库存数量-1:"+phoneCount); }else { System.out.println("手机库存不足..."); } }catch (Exception e){ System.out.println("有异常"); }finally { redissonLock.unlock();//释放锁 释放后代码不受保护 } return "当前的手机数量:"+phoneCount; }
模拟一次性涌入大量数据:
1.打开ApiPost测试工具
2.新建一个接口,访问刚刚写的seckil接口,点击保存
3.保存后点击自动化测试,新建一个测试用例,点击最左边的HTTP请求接口,选择刚刚建的接口,在上面可以选择次数和时间,然后点击执行即可
4.执行的结果如下,库存的数量需要我们自己手动在redis库中进行设置 :set phone_count 10
解决高并发就需要一个配置类RedissonConfig
//分布式锁的配置类 import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; //分布式锁配置类 @Configuration public class RedissonConfig { //1.Redis单机模式下 @Bean //RedissonClient 是Redisson客户端对象,使用该对象来添加锁 public RedissonClient singletonModelRedisson(){ Config config =new Config();//构建Redis结构的配置 // Redis数据库默认放在字库1中,对应的下标为0 config.useSingleServer().setAddress("redis://192.168.40.100:6379").setDatabase(0); return Redisson.create(config); } /* // 2.Redis集群 @Bean public RedissonClient clusterModelRedisson() { Config config =new Config();//构建Redis结构的配置 config.useClusterServers() .setScanInterval(2000)//集群的扫描时间 .addNodeAddress("redis://192.168.40.100:6379", "redis://192.168.40.101:6379", "redis://192.168.40.102:6379"); return Redisson.create(config); } //3.Redis集群:主从架构 @Bean public RedissonClient masterModelRedisson() { Config config =new Config();//构建Redis结构的配置 config.useMasterSlaveServers() .setMasterAddress("redis://192.168.40.100:6379") .addSlaveAddress("redis://192.168.40.101:6379","redis://192.168.40.102:6379"); return Redisson.create(config); } @Bean //4Redis集群:哨兵模式 public RedissonClient sentinelModelRedisson() { Config config =new Config();//构建Redis结构的配置 config.useSentinelServers() .addSentinelAddress("redis://192.168.40.101:6379","redis://192.168.40.102:6379","redis://192.168.40.100:6379"); return Redisson.create(config); } */ }