<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.tomato</groupId>
<artifactId>super-shopping</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>super-shopping</name>
<description>super-shopping</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</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>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--<dependency>-->
<!--<groupId>org.springframework.boot</groupId>-->
<!--<artifactId>spring-boot-starter-security</artifactId>-->
<!--</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
启动类 SuperShoppingApplication .java
@SpringBootApplication
@EnableCaching
public class SuperShoppingApplication {
private static final Logger LOGGER = LoggerFactory.getLogger(SuperShoppingApplication.class);
public static void main(String[] args) throws InterruptedException {
SpringApplication.run(SuperShoppingApplication.class, args);
}
}
Rabbit配置类
RabbitMqConfig
@Configuration
public class RabbitMqConfig {
public static final String topicExchangeName = "shopping-order-exchange";
public static final String queueName = "shopping-order";
@Bean
Queue queue(){
return new Queue(queueName,false);
}
@Bean
TopicExchange topicExchange(){
return new TopicExchange(topicExchangeName);
}
@Bean
Binding binding(Queue queue,TopicExchange topicExchange){
return BindingBuilder.bind(queue).to(topicExchange).with("shopping.#");
}
@Bean
MessageListenerAdapter rabbitListenerAdapter(RabbitReceiver rabbitReceiver) {
return new MessageListenerAdapter(rabbitReceiver, "receiverMessage");
}
@Bean
SimpleMessageListenerContainer rabbitContainer(ConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter){
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
container.setMessageListener(listenerAdapter);
container.setQueueNames(queueName);
return container;
}
@Bean
RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
return new RabbitTemplate(connectionFactory);
}
}
Rabbit MQ 消息接受处理类
@Component
public class RabbitReceiver {
//private CountDownLatch countDownLatch = new CountDownLatch(1);
private static final Logger LOGGER = LoggerFactory.getLogger(RabbitReceiver.class);
@Autowired
OrderService orderService;
public void receiverMessage(ShoppingOrder order){
LOGGER.info(".......MQ receiverMessage.......");
orderService.save(order);
}
// public CountDownLatch getCountDownLatch() {
// return countDownLatch;
// }
}
Redis配置
RedisConfig
@Configuration
public class RedisConfig {
@Bean
CacheManager cacheManager(RedisConnectionFactory connectionFactory){
return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(connectionFactory).build();
}
@Bean
RedisTemplate template(RedisConnectionFactory connectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(connectionFactory);
return redisTemplate;
}
}
数据库dao层 JPA实现
OrderRepository
@Cacheable(cacheNames = "shopping-order")
public interface OrderRepository extends CrudRepository<ShoppingOrder,String> {
@Cacheable(key="#p0",condition = "#p0.indexOf('mygood')!=-1")
List<ShoppingOrder> findAllByGoods(String goods);
@Cacheable(key="#p0")
ShoppingOrder findOrderById(String id);
@CachePut(value = "shopping-order",key="#p0.id")//每次都会执行方法,并将结果存入指定的缓存中
@Override
<S extends ShoppingOrder> S save(S s);
//cacheNames 放在类上对所有方法都适用,也可以放在方法上只对某个方法作用
//key 设置主键 #p0是指第一个参数,也可以 #参数名 比如 #id
//condition 过滤条件 过滤掉不需要缓存的调用
// @CacheEvict是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。
}
service层 接口和实现类
public interface OrderService {
List<ShoppingOrder> queryAllOrder(String goodsName);
ShoppingOrder findOrderById(String id);
ShoppingOrder save(ShoppingOrder order);
}
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
OrderRepository repository;
@Override
public List<ShoppingOrder> queryAllOrder(String goodsName) {
return repository.findAllByGoods(goodsName);
}
@Override
public ShoppingOrder findOrderById(String id) {
return repository.findOrderById(id);
}
@Override
public ShoppingOrder save(ShoppingOrder order) {
return repository.save(order);
}
}
控制层controller实现
@RequestMapping("/order")
@Controller
public class OrderController {
private static final Logger LOGGER = LoggerFactory.getLogger(OrderController.class);
@Autowired
OrderService orderService;
@Autowired
RabbitTemplate rabbitTemplate;
@GetMapping("/")
public String OrderPage(){
return "order";
}
@PostMapping
public @ResponseBody String addOrder(@RequestBody ShoppingOrder shoppingOrder){
LOGGER.info("order is ",shoppingOrder);
String uuid = UUID.randomUUID().toString();
LOGGER.info("request coming.. uuid is "+uuid);
shoppingOrder.setId(uuid);
shoppingOrder.setCreateDate(new Date());
//mq 缓存订单
rabbitTemplate.convertAndSend(RabbitMqConfig.topicExchangeName, "shopping.order", shoppingOrder);
//orderService.save(shoppingOrder);
return "success";
}
}
ShoppingOrder 订单实体类
实现Serializable接口给redis缓存使用
/**
* 订单实体类
* create by tomato
* date 2020年5月30日
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class ShoppingOrder implements Serializable {
@Id
private String id;
private Date createDate;
private double price;
private String goods;
private int amount;
}
1.安装Jmeter压力测试工具,具体使用方法百度
2.创建http请求
这里我只是配置了测试10000次,可以测试多点
3.测试结果
使用MQ明显能提高吞吐量和响应速度。因为他加入队列是非常快的,后续在做集体业务处理。使用MQ我觉得需要前端提供一个响应接口来反馈业务处理的结果,这里我就没有写了。
使用MQ测试结果
关闭MQ,测试结果
关闭MQ和redis缓存测试结果
事实上,redis对于下订单接口没有提高吞吐量和响应速度的帮助,使用redis的作用是这些操作的数据都缓存在了redis当再次用到的时候就不需要读取数据库,对于高并发读取数据库是很有帮助的,但需要考虑下缓存和数据库的数据一直性的问题。
我只是简单的测试了下,还有很多细节和原理也还没弄明白。继续努力中…