上一章節講了基本的整合和各類Exchange的使用,這章主要來實現一個單機的簡單的搶票系統,麻雀雖小但五臟俱全,爲何用它作搶票系統你們應該也懂,爲了削峯和異步處理。java
在這個項目裏我用的是springboot
的2版本,ORM選用JPA
快速開發,JSON工具使用阿里的fastjson
,固然,mq用的是rabbitMQ
。導入的是springboot
集成的依賴。mysql
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>複製代碼
server.port=10000
spring.datasource.url=jdbc:mysql://xxxxx/xxxxx?characterEncoding=utf-8
spring.datasource.username=xxx
spring.datasource.password=xxxx
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.show-sql=true
spring.rabbitmq.host=localhost
spring.rabbitmq.username=root
spring.rabbitmq.password=root
spring.rabbitmq.port=5672複製代碼
我只是頗有針對性的對mq
和datasource
進行了配置。web
create table if not result
(
id int auto_increment primary key,
ticket_id int null,
user_id int null
);
create table if not exists ticket
(
id int auto_increment primary key,
name varchar(255) null,
content varchar(255) null,
user_name varchar(20) null,
count int default '6666' not null
);複製代碼
根據數據表能夠Generate出JavaBean,不貼JavaBean了。面試
1.4 項目架構spring
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── fantj
│ │ │ └── springbootjpa
│ │ │ ├── AMQP.java
│ │ │ ├── controller
│ │ │ │ └── TicketController.java
│ │ │ ├── mq
│ │ │ │ ├── Message.java
│ │ │ │ ├── MQConstants.java
│ │ │ │ ├── MQReceiver.java
│ │ │ │ └── MQSender.java
│ │ │ ├── pojo
│ │ │ │ ├── Result.java
│ │ │ │ └── Ticket.java
│ │ │ ├── repostory
│ │ │ │ ├── ResultRepository.java
│ │ │ │ └── TicketRepository.java
│ │ │ └── service
│ │ │ ├── ResultServiceImpl.java
│ │ │ ├── ResultService.java
│ │ │ ├── TicketServiceImpl.java
│ │ │ └── TicketService.java
│ │ └── resources
│ │ ├── application.properties
│ │ └── rebel.xml複製代碼
@SpringBootApplication
@EntityScan("com.fantj.springbootjpa.pojo")
@EnableRabbit
public class AMQP {
public static void main(String[] args) {
SpringApplication.run(AMQP.class, args);
}
}複製代碼
注意這個@EnableRabbit
註解,它會開啓對rabbit註解的支持。sql
很簡單的一個controller類,實現查詢和搶票功能。數據庫
@RestController
@RequestMapping("/ticket")
public class TicketController {
@Autowired
private TicketService ticketService;
@Autowired
private MQSender mqSender;
@RequestMapping("/get/{id}")
public Ticket getByid(@PathVariable Integer id){
return ticketService.findById(id);
}
@RequestMapping("/reduce/{id}/{userId}")
public String reduceCount(@PathVariable Integer id,
@PathVariable Integer userId){
Message message = new Message(id,userId);
ticketService.reduceCount(id);
mqSender.sendMessage(new Message(message.getTicketId(),message.getUserId()));
return "搶票成功!";
}
}複製代碼
注意private MQSender mqSender;
這是個人rabbit
發送消息的類。json
接口我就再也不這裏貼出,直接貼實現類。設計模式
4.1 ResultServiceImpl.java
@Service
public class ResultServiceImpl implements ResultService{
@Autowired
private ResultRepository resultRepository;
@Override
public void add(Result result) {
resultRepository.add(result.getTicketId(), result.getUserId());
}
@Override
public Result findOneByUserId(Integer userId) {
return resultRepository.findByUserId(userId);
}
}複製代碼
@Service
public class TicketServiceImpl implements TicketService{
@Autowired
private TicketRepository repository;
@Override
public Ticket findById(Integer id) {
return repository.findTicketById(id);
}
@Override
public Ticket reduceCount(Integer id) {
repository.reduceCount(id);
return findById(id);
}
}複製代碼
這兩個都是很普通的service實現類,沒有新加入的東西。安全
@Repository
public interface ResultRepository extends JpaRepository<Result,Integer> {
@Transactional
@Modifying
@Query(value = "insert into result(ticket_id,user_id) values(?1,?2) ",nativeQuery = true)
void add(@Param("ticketId") Integer ticketId,@Param("userId") Integer userId);
Result findByUserId(Integer userId);
}複製代碼
@Repository
public interface TicketRepository extends JpaRepository<Ticket,Integer>{
/**
* 減小庫存
*/
@Transactional
@Modifying
@Query(value = "update ticket t set t.count=t.count+(-1) where id=?1",nativeQuery = true)
int reduceCount(Integer id);
/**
* 查詢信息
*/
Ticket findTicketById(Integer id);
}複製代碼
到了這裏,你會發現,md哪裏有用mq的痕跡…
剩下的全是mq的處理。
6.1 Message.java
這個類用來封裝mq傳輸的消息對象,咱們使用它來對傳輸的byte進行編解碼,獲得咱們想要的數據。
@Data
public class Message implements Serializable {
private Integer ticketId;
private Integer userId;
public Message() {
}
public Message(Integer ticketId, Integer userId) {
this.ticketId = ticketId;
this.userId = userId;
}
}複製代碼
這是一個常量類,用來定義和保存
queue
的名字,雖然裏面只有一個常量,好習慣要從小事作起。
public class MQConstants {
public static final String QUEUE= "qiangpiao";
}複製代碼
這是消息發送類,用來給queue發送數據。
@Service
@Slf4j
public class MQSender {
@Autowired
private AmqpTemplate amqpTemplate;
public void sendMessage(Message message){
String msg = JSONObject.toJSONString(message);
log.info("send message : "+msg);
amqpTemplate.convertAndSend(MQConstants.QUEUE,msg);
}
}複製代碼
AmqpTemplate
是springboot框架提供給咱們使用的amqp操做模板,利用它咱們能更方便的調用和處理業務。
咱們在Controller層調用它,來完成消息入隊的操做,完成削峯和異步處理,大大增長了系統併發和強健性。
這是消息接收類,用來從queue裏獲取數據。
@Service
@Slf4j
public class MQReceiver {
@Autowired
private TicketService ticketService;
@Autowired
private ResultService resultService;
@RabbitListener(queues = MQConstants.QUEUE)
public void receive(String message){
log.info("receive msg : "+message);
JSONObject jsonObject = JSONObject.parseObject(message);
System.out.println(jsonObject);
Message msg = JSONObject.toJavaObject(jsonObject, Message.class);
Integer ticketId = msg.getTicketId();
Integer userId = msg.getUserId();
// 減庫存
Ticket ticket = ticketService.reduceCount(ticketId);
if (ticket.getCount() <= 0){
return;
}
// 判斷是否已經搶過
Result oneByUserId = resultService.findOneByUserId(userId);
if (oneByUserId != null){
return;
}
resultService.add(new Result(ticketId,userId));
}
}複製代碼
在這個類中,@RabbitListener(queues = MQConstants.QUEUE)
標記的是監聽方法,該方法會從queue中獲取到String數據。
以後咱們須要將其復原爲JavaBean,取出咱們該要的屬性,繼續處理業務: 查詢票剩餘量
->判斷是否已搶到過
-> 減庫存
-> 增長搶票數據
。 (我這裏寫的有點草率,應該先查餘量…,不過不重要,本章重點在過一遍springboot與rabbitmq的整合)。
我對該搶票功能作了一個9999請求,我原本作3k併發,電腦沒那麼多句柄,實現不了,最後作了1k併發的壓測。
這是rabbitMQ 自帶Managerment模板上的截圖:
壓測報告:
Server Software:
Server Hostname: 127.0.0.1
Server Port: 10000
Document Path: /ticket/reduce/1/10
Document Length: 13 bytes
Concurrency Level: 1000
Time taken for tests: 423.101 seconds
Complete requests: 9999
Failed requests: 0
Total transferred: 1459854 bytes
HTML transferred: 129987 bytes
Requests per second: 23.63 [#/sec] (mean)
Time per request: 42314.334 [ms] (mean)
Time per request: 42.314 [ms] (mean, across all concurrent requests)
Transfer rate: 3.37 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 2 6.8 0 29
Processing: 217 40197 7390.7 41984 58488
Waiting: 217 40197 7390.8 41984 58488
Total: 246 40199 7384.8 41985 58488
Percentage of the requests served within a certain time (ms)
50% 41984
66% 42670
75% 42744
80% 42758
90% 42801
95% 42828
98% 42850
99% 42868
100% 58488 (longest request)複製代碼
2. 面試題內容聚合
3. 設計模式內容聚合
4. Mybatis內容聚合
5. 多線程內容聚合