GitHub地址:https://github.com/hsowan/rabbitmq-demo
This demo will involve RabbitMQ and SpringAMQP.
Application
- SecKill simulation
- Reliable delivery
SecKill
Now, there is only one goods which is MacBook Pro
and one hundred users want to get it, ok,
the only one MacBook Pro
will be free after n minutes and all users can SecKill
it. Let`s
realize it.
Using ThreadPoolExecutor to fake 100 users
/**
* int corePoolSize,
* int maximumPoolSize,
* long keepAliveTime,
* java.util.concurrent.TimeUnit unit,
* java.util.concurrent.BlockingQueue<Runnable> workQueue
*/
private ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(100, 1000, 200, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(10));
Expose problem in SecKill situation
// Create 100 thread waiting for the SecKill
for (int i = 1; i <= 100; i++) {
threadPoolExecutor.execute(() -> {
while (true) {
if (System.currentTimeMillis() >= startTime) {
Timestamp createAndUpdateTime = new Timestamp(System.currentTimeMillis());
String messageId = UUID.randomUUID().toString();
// Create an order
orderService.saveOrUpdate(new Order("order-macbookpro", OrderConstants.CREATING, createAndUpdateTime, createAndUpdateTime, messageId));
// If count > 0, switch the status of an order to DONE
Goods goods = goodsService.getById((long) 1);
if (goods.getCount() > 0){
Order order = orderService.getByMessageid(messageId);
if (order != null){
order.setStatus(OrderConstants.DONE);
order.setUpdateTime(new Timestamp(System.currentTimeMillis()));
orderService.saveOrUpdate(order);
}
goods.setCount(goods.getCount() - 1);
goodsService.saveOrUpdate(goods);
}
break;
}
}
});
}
What will happen?
mysql> select count(1) from test_order where status = 1;
+----------+
| count(1) |
+----------+
| 100 |
+----------+
1 row in set (0.00 sec)
All users got the only one MacBook Pro
!!!
Solve it with using RabbitMQ
// Create 100 thread waiting for the SecKill
for (int i = 1; i <= 100; i++) {
threadPoolExecutor.execute(() -> {
while (true) {
if (System.currentTimeMillis() >= startTime) {
Timestamp createAndUpdateTime = new Timestamp(System.currentTimeMillis());
String messageId = UUID.randomUUID().toString();
Order order = new Order("order-macbookpro", OrderConstants.CREATING, createAndUpdateTime, createAndUpdateTime, messageId);
// Create an order
orderService.saveOrUpdate(order);
orderProducer.sendMessage(order);
break;
}
}
});
}
This is the OrderProducer that sends messages.
public void sendMessage(Order order){
Long time = System.currentTimeMillis();
Timestamp createAndUpdateTime = new Timestamp(time);
Timestamp nextRetry = new Timestamp(time + 60000 * MessageConstants.INTERVAL);
Message message = new Message(JSON.toJSONString(order), MessageConstants.SENDING, 0, nextRetry, createAndUpdateTime, createAndUpdateTime);
rabbitTemplate.setConfirmCallback((correlationData, b, s) -> {
if (b){
System.out.println("ack");
}else {
System.out.println("nack");
}
});
CorrelationData correlationData = new CorrelationData();
rabbitTemplate.convertAndSend("order-exchange", "order.secKill", JSON.toJSONString(message), correlationData);
}
This is the OrderConsumer that mostly waits to receive messages.
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "order-queue", durable = "true"),
exchange = @Exchange(name = "order-exchange", type = "topic"),
key = "order.*"
))
@RabbitHandler
public void onMessage(@Payload String message, @Headers Map<String, Object> headers, Channel channel) throws IOException {
Message m = JSON.parseObject(message, Message.class);
System.out.println(m.toString());
String messageId = JSON.parseObject(m.getBody(), Order.class).getMessageId();
Goods goods = goodsService.getById((long) 1);
Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
int count = goods.getCount();
if (count > 0){
goods.setCount(count - 1);
goodsService.saveOrUpdate(goods);
Order order = orderService.getByMessageid(messageId);
order.setStatus(OrderConstants.DONE);
order.setUpdateTime(new Timestamp(System.currentTimeMillis()));
orderService.saveOrUpdate(order);
}
channel.basicAck(deliveryTag, false);
}
Make sure that there is a single RabbitMQ server running background.
Then run the consumer (run Application.java
in client module).
Finally, run the producer (run the testUsingRabbitMQ
test of the ApplicationTests.java
in server module).
mysql> select count(1) from test_order where status = 1;
+----------+
| count(1) |
+----------+
| 1 |
+----------+
1 row in set (0.00 sec)
Cool!
AMQP
Core Concepts
- Server
- Connection
- Channel(like session in jdbc)
- Message(properties & body)
- Virtual host
- Exchange
- Binding
- Routing key
- Queue
AMQ Model
Message Flow
Tutorial
- message broker
- producer
- consumer
- queue
- multiple workers
- round-robin dispatching
- message acknowledgment (ack)
- message durability
- fair dispatch
- prefetch
- qos
The core idea in the messaging model in RabbitMQ is that the producer never sends any messages directly to a queue.
Actually, quite often the producer doesn’t even know if a message will be delivered to any queue at all.
- exchange
- exchange type (direct, topic, headers, fanout)
- broadcast
- temporary queue (non-durable, exclusive, autodelete, random queue name)
- binding
- routingKey (its value is ignored for fanout exchanges)
Routing (Direct)
A binding is a relationship between an exchange and a queue.
This can be simply read as: the queue is interested in messages from this exchange.
The routing algorithm behind a direct exchange is simple - a message goes to the queues whose binding key exactly matches the routing key of the message.
- binding key (routingKey)
Messages sent to a topic exchange can’t have an arbitrary routing_key - it must be a list of words, delimited by dots
.
The words can be anything, but usually they specify some features connected to the message.
A few valid routing key examples: “stock.usd.nyse”, “nyse.vmw”, “quick.orange.rabbit”.
There can be as many words in the routing key as you like, up to the limit of 255 bytes
.
* (star)
can substitute for exactly one word.# (hash)
can substitute for zero or more words.
- Callback queue
- Correlation Id
Docker
$ docker run -d \
--name first-rabbit \
--hostname rabbitmq-demo \
-p 5672:5672 \
-p 8080:15672 \
-e RABBITMQ_DEFAULT_USER=user \
-e RABBITMQ_DEFAULT_PASS=password \
rabbitmq:3-management
You can then go to http://localhost:8080 or http://host-ip:8080 in a browser
and use user
/ password
to gain access to the management console.
Documents
- RabbitMQ Docker Official Images
- AMQP 0-9-1 Model Explained
- AMQP Specification
- Spring AMQP 2.1.3 Reference Doc
- Java SE API Documentation
- RabbitMQ Tutorials
- JdbcType in MyBatis
Blog
来源:CSDN
作者:NCUCoder
链接:https://blog.csdn.net/ken1583096683/article/details/86558956