设计一个高并发的Java秒杀系统需要考虑以下几个方面:
-
数据库优化:使用高性能的数据库,如Redis或者Memcached,将秒杀商品的库存信息等数据存储在内存中,以提高读写性能。
-
缓存技术:使用缓存技术来减轻数据库的压力。可以使用缓存来存储商品信息、用户信息以及秒杀结果等数据。常用的缓存技术有Redis和Memcached。
-
消息队列:使用消息队列来削峰填谷,将秒杀请求暂存到消息队列中,再按照一定的速率进行处理,以减轻系统压力。
-
分布式部署:将系统部署在多台服务器上,使用负载均衡技术来分摊请求。可以使用Nginx、HAProxy或者LVS来进行负载均衡。
-
限流策略:使用限流算法来控制请求的并发量,防止系统被过多的请求压垮。常见的限流算法有令牌桶算法和漏桶算法。
一、下面是一个简单的Java秒杀系统的示例代码:
// 商品实体类
public class Product {
private String id;
private String name;
private int stock;
// 省略构造方法和Getter/Setter
}
// 秒杀订单实体类
public class Order {
private String id;
private String productId;
private String userId;
private Date createTime;
// 省略构造方法和Getter/Setter
}
// 秒杀服务类
@Service
public class SeckillService {
private static final int MAX_STOCK = 100; // 商品的最大库存数量
private final Map<String, Product> products = new ConcurrentHashMap<>(); // 商品信息
private final Set<String> seckillUsers = new HashSet<>(); // 已秒杀用户
@PostConstruct
public void init() {
// 初始化商品信息
Product product = new Product("1", "iPhone 12", MAX_STOCK);
products.put(product.getId(), product);
}
// 执行秒杀操作
public synchronized boolean seckill(String productId, String userId) {
// 判断商品是否存在
Product product = products.get(productId);
if (product == null) {
return false;
}
// 判断商品库存是否足够
if (product.getStock() <= 0) {
return false;
}
// 判断用户是否已经秒杀过
if (seckillUsers.contains(userId)) {
return false;
}
// 执行秒杀逻辑
product.setStock(product.getStock() - 1);
Order order = new Order(UUID.randomUUID().toString(), productId, userId, new Date());
// 保存订单信息到数据库或消息队列等
// ...
// 添加已秒杀用户
seckillUsers.add(userId);
return true;
}
}
// 秒杀控制器
@RestController
public class SeckillController {
private final SeckillService seckillService;
public SeckillController(SeckillService seckillService) {
this.seckillService = seckillService;
}
@PostMapping("/seckill")
public String seckill(@RequestParam("productId") String productId,
@RequestParam("userId") String userId) {
boolean success = seckillService.seckill(productId, userId);
if (success) {
return "秒杀成功";
} else {
return "秒杀失败";
}
}
}
这个示例中,秒杀系统使用了一个内存中的Map
来保存商品信息,使用了一个Set
来保存已经秒杀过的用户。在秒杀操作中,通过加锁来保证秒杀的原子性。
二、以下是一个使用RabbitMQ设计的Java秒杀系统的生产者和消费者的代码示例:
生产者代码示例:
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.TimeoutException;
public class SeckillProducer {
private static final String QUEUE_NAME = "seckillQueue";
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setUsername("guest");
factory.setPassword("guest");
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 模拟秒杀请求
String productId = "12345";
String userId = UUID.randomUUID().toString();
// 发送秒杀消息到队列
String message = productId + "," + userId;
channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));
System.out.println("秒杀消息发送成功:" + message);
}
}
}
消费者代码示例:
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class SeckillConsumer {
private static final String QUEUE_NAME = "seckillQueue";
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setUsername("guest");
factory.setPassword("guest");
// 创建连接
Connection connection = factory.newConnection();
// 创建通道
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 创建消费者
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("收到秒杀消息:" + message);
// 处理秒杀逻辑
processSeckillMessage(message);
// 手动确认消息已被消费
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
// 监听队列并消费消息
channel.basicConsume(QUEUE_NAME, false, consumer);
}
private static void processSeckillMessage(String message) {
// 解析消息,执行秒杀逻辑
String[] parts = message.split(",");
String productId = parts[0];
String userId = parts[1];
// 执行秒杀操作
// ...
}
}
在这个示例中,我们使用RabbitMQ作为消息队列来处理秒杀请求。生产者通过RabbitMQ的Java客户端库创建连接和通道,然后声明一个队列,并发送秒杀消息到队列中。
消费者也通过RabbitMQ的Java客户端库创建连接和通道,然后声明同样的队列,并创建一个消费者来监听队列并消费消息。当消费者接收到秒杀消息后,会调用processSeckillMessage
方法来处理秒杀逻辑,并手动确认消息已被消费。
这只是一个简单的示例,实际的秒杀系统中可能还需要考虑消息持久化、消息重试机制、并发控制等问题。