并发解决方案-基于redis的消息队列

上一篇教大家用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次请求

并发解决方案-基于redis的消息队列_第1张图片

配置http请求

并发解决方案-基于redis的消息队列_第2张图片

测试结果

TPS

并发解决方案-基于redis的消息队列_第3张图片

响应时间

并发解决方案-基于redis的消息队列_第4张图片

并发解决方案-基于redis的消息队列_第5张图片

没有发生错误,平均响应时间是0.3秒,因为是同步处理,所以TPS=throughput= 1400,还不错。

并发解决方案-基于redis的消息队列_第6张图片数据库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");
    }
}

好,再来测试一遍!

响应时间

并发解决方案-基于redis的消息队列_第7张图片

TPS,这一块可能有点问题。因为我实际测试的时候比上个快多了。

并发解决方案-基于redis的消息队列_第8张图片

 

总览

并发解决方案-基于redis的消息队列_第9张图片

用了队列的好处很明显,平均响应时间0.09秒,快了3倍多,而且吞吐量也上去了,接近5000!,也就是10W请求4秒就响应完了。这是很恐怖的,实际应用因为业务比较多,一般达到1000也就不错了。

当然这是并不代表处理完成了,只能说服务器已经接受你的请求正在处理,受限于数据库的瓶颈,TPS还是1400,我测试报告生成了数据库还在慢慢减库存。

并发解决方案-基于redis的消息队列_第10张图片

但是起码用户体验好了,服务器也可用,没有宕机的风险。(redis的发布订阅并没有持久化机制,只适用于数据安全性不高或者有容错机制的场景。如果对数据安全高的场景应该使用kafka和rabbitmq)

 

你可能感兴趣的:(java,redis,并发)