上一篇教大家用spring-data-redis来实现redis的消息队列:
https://blog.csdn.net/u011870280/article/details/80012732
现在接着来做一个测试,试试redis队列在并发场景下的性能。
首先来一个没队列的场景,比如团购秒杀大家来抢一双鞋子
在上篇项目的基础上引入数据库相关依赖:
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-jpa
mysql
mysql-connector-java
然后配置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{
@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,还不错。
可是这十万的请求花了一分钟才处理完,也就是说如果有十万个连接同时过来抢购,有些人可能要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,我测试报告生成了数据库还在慢慢减库存。
但是起码用户体验好了,服务器也可用,没有宕机的风险。(redis的发布订阅并没有持久化机制,只适用于数据安全性不高或者有容错机制的场景。如果对数据安全高的场景应该使用kafka和rabbitmq)