上一篇教大家用spring-data-redis来实现redis的消息队列:
https://blog.csdn.net/u011870280/article/details/80012732
现在接着来做一个测试,试试redis队列在并发场景下的性能。
首先来一个没队列的场景,比如团购秒杀大家来抢一双鞋子
在上篇项目的基础上引入数据库相关依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
然后配置mysql
spring.jpa.hibernate.ddl-auto=update spring.datasource.url=jdbc:mysql://192.168.1.22:3306/test?characterEncoding=utf8&useSSL=false spring.datasource.username=root spring.datasource.password=123456
创建一个实体-库存类
package com.xzg.redis.entity; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class Stock { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; private String name; private Long count; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Long getCount() { return count; } public void setCount(Long count) { this.count = count; } }
然后来个操作接口,实现买商品减库存操作
package com.xzg.redis.repository; import com.xzg.redis.entity.Stock; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; public interface StockRepository extends CrudRepository<Stock, Long>{ @Transactional @Modifying(clearAutomatically = true) @Query("update Stock stock set stock.count =stock.count-1 where stock.id =:stockId") void buy(@Param("stockId") Long stockId); }
最后编写Controller,暴露入口,完成,运行项目。
package com.xzg.redis.controller; import com.xzg.redis.repository.StockRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; @Controller public class ShopController { @Autowired private StockRepository stockRepository; @GetMapping(path="/buy/{stockId}") public @ResponseBody String buy (@PathVariable("stockId") Long stockId) { stockRepository.buy(stockId); return "OK"; } }
可以测试了,这时候我们先来插入一条库存信息
insert into stock(id,name,count) values(1,'shoes',100000);
打开Jmeter,模拟100000次请求
配置http请求
测试结果
TPS
响应时间
没有发生错误,平均响应时间是0.3秒,因为是同步处理,所以TPS=throughput= 1400,还不错。
数据库100000的库存也清空了
可是这十万的请求花了一分钟才处理完,也就是说如果有十万个连接同时过来抢购,有些人可能要1分钟后才收到结果,很可能超过30秒连接就超时断开了,更有可能的是服务器处理不过来,响应更慢,照成服务不可用甚至宕机。
接下来试试常规的处理方法,把请求塞入队列,然后马上返回客户端已提交or排队中。
就用上一篇的redis队列,修改下Controller代码:
package com.xzg.redis.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; @Controller public class ShopController { @Autowired StringRedisTemplate template; @GetMapping(path="/buy/{stockId}") public @ResponseBody String buy (@PathVariable("stockId") Long stockId) { template.convertAndSend("shop", stockId.toString()); return "OK"; } }
Reciver代码
package com.xzg.redis.message; import com.xzg.redis.repository.StockRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class Receiver { private static final Logger LOGGER = LoggerFactory.getLogger(Receiver.class); @Autowired StockRepository stockRepository; public void receiveMessage(String message) { stockRepository.buy(Long.valueOf(message)); } }
Application代码:
package com.xzg.redis; import com.xzg.redis.message.Receiver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.listener.PatternTopic; import org.springframework.data.redis.listener.RedisMessageListenerContainer; import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; @SpringBootApplication public class Application { private static final Logger LOGGER = LoggerFactory.getLogger(Application.class); @Bean RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(connectionFactory); container.addMessageListener(listenerAdapter, new PatternTopic("shop")); return container; } @Bean MessageListenerAdapter listenerAdapter(Receiver receiver) { return new MessageListenerAdapter(receiver, "receiveMessage"); } @Bean StringRedisTemplate template(RedisConnectionFactory connectionFactory) { return new StringRedisTemplate(connectionFactory); } public static void main(String[] args) throws InterruptedException { ApplicationContext ctx = SpringApplication.run(Application.class, args); LOGGER.info("app started"); } }
好,再来测试一遍!
响应时间
TPS,这一块可能有点问题。因为我实际测试的时候比上个快多了。
总览
用了队列的好处很明显,平均响应时间0.09秒,快了3倍多,而且吞吐量也上去了,接近5000!,也就是10W请求4秒就响应完了。这是很恐怖的,实际应用因为业务比较多,一般达到1000也就不错了。
当然这是并不代表处理完成了,只能说服务器已经接受你的请求正在处理,受限于数据库的瓶颈,TPS还是1400,我测试报告生成了数据库还在慢慢减库存。
但是起码用户体验好了,服务器也可用,没有宕机的风险。