前言
在当今的互联网时代,抢单活动已经成为了电商平台、外卖平台等各种电子商务平台中常见的营销手段。通过抢单活动,商家可以吸引大量用户参与,从而提高销量和知名度。然而,抢单活动所带来的高并发请求往往会给系统带来巨大的压力,如何在抢单活动开始前进行预热,以确保系统能够稳定运行,成为了技术人员需要解决的重要问题。
在这篇博客中,我们将深入探讨如何利用Redis技术来进行抢单预热,以应对抢单活动带来的高并发访问压力。我们将介绍Redis的基本概念和特点,以及如何利用Redis来进行缓存预热、数据预加载等操作,从而提高系统的并发处理能力和稳定性。同时,我们也将分享一些实际案例和经验,帮助读者更好地理解和应用Redis技术解决抢单预热的挑战。
通过本文的学习,读者将能够深入了解抢单预热的必要性和原理,掌握利用Redis进行抢单预热的具体方法和技巧,从而为自己的系统应对抢单活动带来的高并发访问压力提供有效的解决方案。让我们一起深入探讨Redis在抢单预热中的应用吧!
一、前期准备
1、新建项目,结构如下
2、添加依赖
<dependencies>
<!-- 放在最前面依赖,依据依赖的最短路径原则,
将不在使用spring-data中的slf4j,否则
会引发冲突-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.3.8</version>
</dependency>
<!-- spring data框架,提供了对redis的整合支持,
内部支持lettuce以及Jedis客户端-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.5.6</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.29</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.29</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.29</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
</dependency>
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
</dependencies>
这个Maven项目中包含了多个依赖,以下是每个依赖的作用:
logback-classic: 这是logback日志框架的经典模块,用于在应用程序中进行日志记录和管理。
spring-data-redis: 提供了Spring Data框架对Redis的整合支持,包括对lettuce和Jedis客户端的支持,可以方便地使用Redis进行数据操作。
javax.servlet-api: 这是Java Servlet API的依赖,提供了对Servlet的支持,通常在Java Web应用中使用。
spring-webmvc: Spring框架的Web MVC模块,提供了基于MVC架构的Web应用程序开发支持。
spring-jdbc: Spring框架的JDBC模块,提供了对JDBC的封装和支持,用于在Spring应用中进行数据库操作。
spring-tx: Spring框架的事务管理模块,提供了声明式事务管理的支持。
druid: 阿里巴巴开源的数据库连接池,在应用中用于管理数据库连接。
mysql-connector-java: MySQL数据库的JDBC驱动,用于连接MySQL数据库。
lettuce-core: Lettuce是一个高性能的开源Java Redis客户端,用于与Redis进行交互。
lombok: Lombok是一个Java库,可以通过注解的方式来简化Java代码的编写,提高开发效率。
junit: JUnit是一个Java单元测试框架,用于编写和运行自动化的单元测试。
jackson-databind: Jackson是一个流行的Java JSON处理库,jackson-databind模块提供了数据绑定功能,用于将Java对象和JSON数据进行相互转换。
mybatis: MyBatis是一个持久层框架,用于在Java应用中进行数据库操作。
mybatis-spring: MyBatis与Spring框架的整合模块,提供了MyBatis和Spring框架的无缝集成支持。
这些依赖项涵盖了日志记录、Web开发、数据库操作、缓存操作、测试等多个方面,可以满足一个典型的Java应用程序的开发需求。
二、编写 dao
由于代码量太多了,就不一一讲解了,本次案例只是讲重要的怎么预热和减库存。
1、GoodsDao
public interface GoodsDao {
/**
* 查询参与活动的商品
* @return
*/
List<Goods> listProduct();
/**
* 减库存
* @param id
* @return
*/
void decrStock(int id);
}
首先要有一个查询库存的方法和一个删减库存的方法。
三、编写 service
1、OrderService
public interface OrderService {
/**
* 下单
* @param id
*/
void placeOrder(int id);
}
当库存放生改变时,我们需要为这写下单的用户添加订单记录。
2、GoodsService
public interface GoodsService {
/**
* 扣减库存
* @param id
*/
void decrStock(int id);
}
当下单成功后,需要扣减数据库的库存数量。
3、GoodsServiceImpl
@Service
@Slf4j
@RequiredArgsConstructor
@Transactional(rollbackFor = RuntimeException.class)
public class GoodsServiceImpl implements GoodsService {
private final GoodsDao goodsDao;
private final RedisTemplate<String, Object> redisTemplate;
/**
* 缓存预热
*/
@PostConstruct
public void initProductCache() {
goodsDao.listProduct().forEach(goods -> {
//将商品数量加入redis缓存
String key = GoodsEnum.PREFIX.value() + goods.getId();
redisTemplate.opsForValue().set(key, goods.getStock(), Duration.ofMinutes(60));
});
}
@Override
public void decrStock(int id) {
goodsDao.decrStock(id);
}
}
这段代码是一个名为GoodsServiceImpl的服务类,使用了Lombok的@RequiredArgsConstructor注解来自动生成构造函数,并且使用了Slf4j来实现日志记录。同时,@Service注解表明这是一个Spring的服务类,@Transactional注解表明这个类中的方法将进行事务管理,并且在遇到RuntimeException时进行回滚。
这个类中有两个成员变量:GoodsDao和RedisTemplate。GoodsDao是一个数据访问对象,用于对商品数据进行持久化操作;而RedisTemplate是Spring提供的用于操作Redis的模板类。
在这个类中,有一个@PostConstruct注解的方法initProductCache(),它在类实例化后会被自动调用。这个方法通过goodsDao.listProduct()获取所有商品,并将它们的库存数量加入到Redis缓存中,以实现商品的缓存预热。对于每个商品,它会将商品的id作为key,库存数量作为value存入Redis,并设置了缓存的有效期为60分钟。
另外,这个类还实现了GoodsService接口,其中包含了decrStock(int id)方法,用于减少商品库存。在这个方法中,它调用了goodsDao.decrStock(id)来实现对商品库存的减少操作。
总的来说,这个类主要负责商品库存的管理,通过缓存预热来提高系统性能,并且在减少商品库存时进行事务管理。
4、OrderServiceImpl
@Service
@Slf4j
@RequiredArgsConstructor
@Transactional
public class OrderServiceImpl implements OrderService {
private final RedisTemplate<String, Object> redisTemplate;
private final OrderDao orderDao;
private final GoodsDao goodsDao;
/**
* 下单
* @param id
*/
@Override
public void placeOrder(int id) {
//扣减库存
decrCacheStock(id);
//生成订单
createOrder(id);
//同步数据库的库存
goodsDao.decrStock(id);
}
/**
* 在缓存中扣减库存
* @param id
*/
private void decrCacheStock(int id) {
//扣减库存(原子减),并返回剩余库存量
long stock = redisTemplate.opsForValue().decrement(GoodsEnum.PREFIX.value() + id);
//如果redis中库存为0,则抛出异常告诉用户已经售罄
if(stock < 0) {
//在并发时redis扣减后的库存为负数,因此要将redis自增回来
redisTemplate.opsForValue().increment(GoodsEnum.PREFIX.value() + id);
throw new OrderException(ErrorMessageEnum.SELL_OUT);
}
}
/**
* 生成订单
* @param gid
*/
private Order createOrder(int gid) {
try {
Order order = new Order();
//用户ID
order.setUserId(1);
//商品ID
order.setGoodsId(gid);
//0表示未支付
order.setStatus(0);
orderDao.save(order);
return order;
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}
}
这段代码是一个名为OrderServiceImpl的服务类,同样使用了Lombok的@RequiredArgsConstructor注解来自动生成构造函数,并且使用了@Slf4j来实现日志记录。同时,@Service注解表明这是一个Spring的服务类,@Transactional注解表明这个类中的方法将进行事务管理。
这个类中有三个成员变量:RedisTemplate用于操作Redis缓存,OrderDao用于对订单数据进行持久化操作,GoodsDao用于对商品数据进行持久化操作。
在这个类中,有一个placeOrder(int id)方法,用于处理下单操作。在这个方法中,首先调用了decrCacheStock(int id)方法来扣减商品的库存,然后调用了createOrder(int id)方法来生成订单,最后调用了goodsDao.decrStock(id)方法来同步数据库中的库存信息。
在decrCacheStock(int id)方法中,它使用了RedisTemplate来实现对Redis缓存中商品库存的扣减操作,并且通过判断库存是否小于0来判断商品是否售罄,如果售罄则抛出OrderException异常。
在createOrder(int gid)方法中,它创建了一个订单对象,并将订单信息存入数据库中。如果在存入数据库时出现异常,它会记录错误日志并抛出RuntimeException异常。
总的来说,这个类主要负责处理订单的生成和库存的扣减操作,通过调用RedisTemplate来实现对Redis缓存的操作,并且在数据库操作时进行事务管理。
四、编写controller
@RestController
@RequiredArgsConstructor
public class OrderController extends BaseController{
private final OrderService orderService;
@PostMapping("/seckill")
public ResultVO placeOrder(Integer gid) {
orderService.placeOrder(2);
return success();
}
}
这段代码是一个名为OrderController的控制器类,使用了Lombok的@RequiredArgsConstructor注解来自动生成构造函数,并且继承了BaseController。同时,@RestController注解表明这是一个Spring的RESTful控制器类。
在这个类中,有一个成员变量OrderService,用于处理订单相关的业务逻辑。在控制器中,有一个@PostMapping注解的方法placeOrder(Integer gid),用于处理秒杀下单的请求。在这个方法中,它调用了orderService.placeOrder(2)来处理下单操作,并且返回了一个ResultVO对象,通过success()方法来表示操作成功。
总的来说,这个控制器类主要用于处理秒杀下单的请求,通过调用OrderService来实现下单操作,并返回相应的结果。
五、使用jmeter测试
官网网址:Apache JMeter - Apache JMeter™
去官网下载下来,我们用 jmeter 来测试我们的controller。
1、jmeter有什么用
JMeter是一个用于进行性能测试的开源工具,它最初是为测试Web应用程序而设计的,但后来扩展到其他测试领域。JMeter的主要用途包括:
-
性能测试:JMeter可以模拟多个并发用户对目标系统(如Web服务器、数据库、FTP服务器等)发起请求,以评估系统的性能和稳定性。它可以测量系统在不同负载下的响应时间、吞吐量和并发用户数等指标,帮助开发人员和测试人员发现系统性能方面的问题。
-
负载测试:通过模拟大量用户请求,JMeter可以测试系统在高负载情况下的表现,评估系统的承载能力和性能瓶颈,以便确定系统是否能够满足预期的用户需求。负载测试也可以用于验证系统的可伸缩性和稳定性。
-
压力测试:JMeter可以模拟系统在正常或异常负载下的表现,以便评估系统在不同压力下的稳定性和可靠性。通过压力测试,可以发现系统在极端情况下可能出现的问题,如内存泄漏、资源竞争等。
-
功能测试:除了性能测试,JMeter也可以用于进行功能测试,例如测试网站的登录、注册、搜索等功能,以及测试API的响应等。
总的来说,JMeter是一个功能强大的测试工具,可以帮助开发人员和测试人员进行性能、负载、压力和功能测试,以确保系统能够稳定、高效地运行。
2、测试
1)打开 jmeter ,bin目录下双击ApacheJMeter.jar 运行
运行:
2)添加线程组
3)添加HTTP请求
4)添加汇总报告
5)填写信息
添加循环数量,我们的库存中有100个库存,我们执行150次,看会不会出现超卖的情况。还是售完了直接就抛异常。
填写 HTTP 协议
请求路径不要写错了,还有就是请求的方式是什么就选择什么。
6)测试结果
当运行测试后,售完100个数量之后并没有出现超卖的现象,证明我们的代码就没有写错,并且在售完之后直接提示用户商品已售完。
六、gitee 案例
地址:ch02 · qiuqiu/Redis-study - 码云 - 开源中国 (gitee.com)