Java实战:实现高并发秒杀

本文将详细介绍如何实现高并发秒杀功能,我们将深入探讨七种常见的秒杀系统实现方式,包括使用缓存、数据库乐观锁、数据库悲观锁、分布式锁、队列限流、令牌桶算法和限流器。

1. 引言

在现代的互联网应用中,秒杀活动是一种常见的需求,允许用户在短时间内抢购有限的商品。然而,这种类型的活动可能会导致高并发的请求,对系统造成巨大的压力。为了应对这种高并发的场景,我们需要实现一种能够处理大量并发请求的秒杀系统。
秒杀系统通常需要处理以下挑战:

  • 高并发:在短时间内,大量的用户可能会同时访问秒杀活动页面,导致系统压力巨大。
  • 数据一致性:在秒杀过程中,需要保证数据的准确性和一致性。
  • 系统稳定性:在秒杀过程中,系统需要能够承受高并发的请求,保证系统的稳定运行。
    为了应对这些挑战,我们可以使用多种技术来实现高并发的秒杀系统。以下是一些常见的实现方式:

2. 使用缓存

使用缓存是一种常见的秒杀系统实现方式。通过使用缓存,我们可以减少数据库的访问次数,提高系统的响应速度。在秒杀活动中,我们可以将商品的数量缓存到 Redis 中,并在用户请求时直接从缓存中获取商品数量。

import redis.clients.jedis.Jedis;
public class CacheBasedSecKill {
    private Jedis jedis;
    public CacheBasedSecKill(Jedis jedis) {
        this.jedis = jedis;
    }
    public boolean isProductAvailable(String productId) {
        String productKey = "product:" + productId;
        return jedis.exists(productKey) && jedis.decr(productKey) > 0;
    }
}

在这个示例中,我们创建了一个名为 CacheBasedSecKill 的类,它接受一个 Jedis 实例作为参数。我们定义了一个名为 isProductAvailable 的方法,它接受一个名为 productId 的字符串参数。我们使用 Redis 的 exists 命令检查缓存中是否存在该商品的键,并使用 decr 命令减少商品的数量。如果商品的数量大于 0,我们返回 true,表示商品可用;否则返回 false

3. 使用数据库乐观锁

数据库乐观锁是一种基于数据版本号或时间戳的锁机制,用于处理并发更新操作。在秒杀活动中,我们可以使用乐观锁来处理商品数量的更新。

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class OptimisticLockBasedSecKill {
    private Connection connection;
    public OptimisticLockBasedSecKill(Connection connection) {
        this.connection = connection;
    }
    public boolean isProductAvailable(String productId) throws SQLException {
        String sql = "SELECT product_id, quantity, version FROM product WHERE product_id = ? FOR UPDATE";
        try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
            preparedStatement.setString(1, productId);
            ResultSet resultSet = preparedStatement.executeQuery();
            if (resultSet.next()) {
                int quantity = resultSet.getInt("quantity");
                int version = resultSet.getInt("version");
                if (quantity > 0) {
                    sql = "UPDATE product SET quantity = quantity - 1, version = version + 1 WHERE product_id = ? AND version = ?";
                    try (PreparedStatement updateStatement = connection.prepareStatement(sql)) {
                        updateStatement.setString(1, productId);
                        updateStatement.setInt(2, version);
                        int affectedRows = updateStatement.executeUpdate();
                        return affectedRows > 0;
                    }
                }
            }
        }
        return false;
    }
}

在这个示例中,我们创建了一个名为 OptimisticLockBasedSecKill 的类,它接受一个 Connection 实例作为参数。我们定义了一个名为 isProductAvailable 的方法,它接受一个名为 productId 的字符串参数。我们首先执行一个 SELECT 查询,获取商品的 ID、数量和版本号。然后,我们检查商品的数量是否大于 0。如果商品的数量大于 0,我们执行一个 UPDATE 查询,减少商品的数量并增加版本号。如果 UPDATE 查询影响的行数大于 0,我们返回 true,表示商品可用;否则返回 false

4. 使用数据库悲观锁

数据库悲观锁是一种基于排他锁的机制,用于处理并发更新操作。在秒杀活动中,我们可以使用悲观锁来处理商品数量的更新。

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class PessimisticLockBasedSecKill {
    private Connection connection;
    public PessimisticLockBasedSecKill(Connection connection) {
        this.connection = connection;
    }
    public boolean isProductAvailable(String productId) throws SQLException {
        String sql = "SELECT quantity FROM product WHERE product_id = ? FOR UPDATE";
        try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
            preparedStatement.setString(1, productId);
            ResultSet resultSet = preparedStatement.executeQuery();
            if (resultSet.next()) {
                int quantity = resultSet.getInt("quantity");
                if (quantity > 0) {
                    sql = "UPDATE product SET quantity = quantity - 1 WHERE product_id = ?";
                    try (PreparedStatement updateStatement = connection.prepareStatement(sql)) {
                        updateStatement.setString(1, productId);
                        int affectedRows = updateStatement.executeUpdate();
                        return affectedRows > 0;
                    }
                }
            }
        }
        return false;
    }
}

在这个示例中,我们创建了一个名为 PessimisticLockBasedSecKill 的类,它接受一个 Connection 实例作为参数。我们定义了一个名为 isProductAvailable 的方法,它接受一个名为 productId 的字符串参数。我们首先执行一个 SELECT 查询,并使用 FOR UPDATE 子句获取商品的排他锁。然后,我们检查商品的数量是否大于 0。如果商品的数量大于 0,我们执行一个 UPDATE 查询,减少商品的数量。如果 UPDATE 查询影响的行数大于 0,我们返回 true,表示商品可用;否则返回 false

5. 使用分布式锁

分布式锁是一种用于在分布式系统中控制对共享资源访问的锁机制。在秒杀活动中,我们可以使用分布式锁来保证对商品数量的一致性访问。

import redis.clients.jedis.Jedis;
public class DistributedLockBasedSecKill {
    private Jedis jedis;
    public DistributedLockBasedSecKill(Jedis jedis) {
        this.jedis = jedis;
    }
    public boolean isProductAvailable(String productId) {
        String lockKey = "lock:" + productId;
        String requestId = "request:" + Thread.currentThread().getId();
        while (true) {
            if (jedis.setnx(lockKey, requestId) == 1) {
                String quantity = jedis.get("product:" + productId);
                if (quantity != null && Integer.parseInt(quantity) > 0) {
                    jedis.decr("product:" + productId);
                    jedis.del(lockKey);
                    return true;
                }
                jedis.del(lockKey);
                return false;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在这个示例中,我们创建了一个名为 DistributedLockBasedSecKill 的类,它接受一个 Jedis 实例作为参数。我们定义了一个名为 isProductAvailable 的方法,它接受一个名为 productId 的字符串参数。我们首先尝试使用 SETNX 命令获取分布式锁,如果成功获取锁,我们检查商品的数量是否大于 0。如果商品的数量大于 0,我们减少商品的数量,并删除锁。如果获取锁失败,我们等待一段时间后重试。

6. 使用队列限流

队列限流是一种基于队列的限流机制,用于控制并发请求的数量。在秒杀活动中,我们可以使用队列限流来限制同时处理的请求数量。

import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class QueueBasedRateLimiter {
    private ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
    private int maxQueueSize;
    private ExecutorService executorService;
    public QueueBasedRateLimiter(int maxQueueSize) {
        this.maxQueueSize = maxQueueSize;
        this.executorService = Executors.newFixedThreadPool(maxQueueSize);
    }
    public boolean isProductAvailable(String productId) {
        if (queue.size() < maxQueueSize) {
            queue.add(productId);
            executorService.execute(() -> {
                try {
                    // 执行秒杀逻辑
                } finally {
                    queue.poll();
                }
            });
            return true;
        }
        return false;
    }
}

在这个示例中,我们创建了一个名为 QueueBasedRateLimiter 的类,它接受一个名为 maxQueueSize 的整数参数。我们定义了一个名为 isProductAvailable 的方法,它接受一个名为 productId 的字符串参数。我们首先检查队列的大小是否小于最大队列大小。如果小于,我们将商品 ID 添加到队列中,并使用线程池执行秒杀逻辑。如果等于或大于,我们返回 false

7. 使用令牌桶算法和限流器

令牌桶算法是一种常见的限流算法,用于控制数据包的发送速率。在秒杀活动中,我们可以使用令牌桶算法来限制并发请求的数量。

import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TokenBucketRateLimiter {
    private ConcurrentLinkedQueue<String> bucket = new ConcurrentLinkedQueue<>();
    private int maxTokens;
    private int refillRate;
    private ExecutorService executorService;
    public TokenBucketRateLimiter(int maxTokens, int refillRate) {
        this.maxTokens = maxTokens;
        this.refillRate = refillRate;
        this.executorService = Executors.newFixedThreadPool(maxTokens);
    }
    public boolean isProductAvailable(String productId) {
        synchronized (bucket) {
            while (bucket.size() >= maxTokens) {
                try {
                    bucket.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            bucket.add(productId);
            executorService.execute(() -> {
                try {
                    // 执行秒杀逻辑
                } finally {
                    synchronized (bucket) {
                        bucket.remove(productId);
                        bucket.notifyAll();
                    }
                }
            });
            return true;
        }
    }
}

在这个示例中,我们创建了一个名为 TokenBucketRateLimiter 的类,它接受两个名为 maxTokensrefillRate 的整数参数。我们定义了一个名为 isProductAvailable 的方法,它接受一个名为 productId 的字符串参数。
我们首先检查令牌桶的大小是否达到最大令牌数。如果达到,我们等待直到有新的令牌被添加。一旦有新的令牌被添加,我们将商品 ID 添加到令牌桶中,并使用线程池执行秒杀逻辑。如果令牌桶未满,我们直接将商品 ID 添加到令牌桶中,并执行秒杀逻辑。

8. 总结

本文详细介绍了如何实现高并发秒杀功能,我们深入探讨了七种常见的秒杀系统实现方式,包括使用缓存、数据库乐观锁、数据库悲观锁、分布式锁、队列限流、令牌桶算法和限流器。每种方式都有其优缺点,适用于不同的场景。通过使用这些技术,我们可以有效地处理高并发的秒杀活动,保证系统的稳定性和性能。

  • 16
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
java实现秒杀系统@Controller @RequestMapping("seckill")//url:/模块/资源/{id}/细分 /seckill/list public class SeckillController { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private SeckillService seckillService; @RequestMapping(value="/list",method = RequestMethod.GET) public String list(Model model){ //获取列表页 List list=seckillService.getSeckillList(); model.addAttribute("list",list); //list.jsp+model = ModelAndView return "list";//WEB-INF/jsp/"list".jsp } @RequestMapping(value = "/{seckillId}/detail",method = RequestMethod.GET) public String detail(@PathVariable("seckillId") Long seckillId, Model model){ if (seckillId == null){ return "redirect:/seckill/list"; } Seckill seckill = seckillService.getById(seckillId); if (seckill == null){ return "forward:/seckill/list"; } model.addAttribute("seckill",seckill); return "detail"; } //ajax json @RequestMapping(value = "/{seckillId}/exposer", method = RequestMethod.POST, produces = {"application/json;charset=UTF-8"}) @ResponseBody public SeckillResult exposer(@PathVariable("seckillId") Long seckillId){ SeckillResult result; try { Exposer exposer =seckillService.exportSeckillUrl(seckillId); result = new SeckillResult(true,exposer); } catch (Exception e) { logger.error(e.getMessage(),e); result = new SeckillResult(false,e.getMessage()); } return result; } @RequestMapping(value = "/{seckillId}/{md5}/execution", method = RequestMethod.POST, produces = {"application/json;charset=UTF-8"} ) @ResponseBody public SeckillResult execute(@PathVariable("seckillId")Long seckillId,

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值