从零开始打造简易秒杀系统
- 理论分析
- 代码实现
- 压测验证
1. 防止超卖
使用数据库乐观锁的方式解决超卖问题,作者代码实现,并使用JMeter压测
乐观锁 VS 悲观锁
乐观锁: 适合数据修改较少,读取较频繁的场景。(即使出现了少量的冲突,可以省去在量的锁的开销,能提高系统吞吐量) 悲观锁: 适合写数据比较多的情况,(由于经常发生冲突,上层应用不断的retry,反而会降低性能)
2. 令牌桶限流 + 再谈超卖
使用Guava的RateLimiter实现令牌桶限流:
- 阻塞式: 如果桶内没有足够令牌,阻塞,直到令牌发放
- 非阻塞式: 如果桶内没有足够令牌,尝试等待设置好的时间,如果超过了时间还不能拿到,直接返回失败。等待时间设置为0,则等于阻塞式获取令牌。
两种算法的比较
-
令牌桶算法 能够限制数据平均传输速率,并且允许某种程度的突发流量
-
漏桶算法 能够强行限制数据的传输速率,多用于流量整型
Spring事务传播
3. 抢购接口隐藏 + 单用户限制频率
接口隐藏与单位用户限制频率
获取验证值,携带验证值下单 代码实现
4. 缓存与数据库双写问题的争议
缓存一致性问题产生的原因分析以及改进方案
-
删除缓存,更新数据库
-
延时双删(写数据库前删除一次缓存,写数据库后再删除一次缓存)
在删除缓存,更新数库之上做的改进,删除缓存,更新数据库,停顿一段时间,再删除缓存
- 更新数据库,删除缓存
5. 阿里开源MySQL中间件Canal快速入门
使用Canel更新缓存
bin log 开启,日志格式设置为row MySQL的数据
- 同步,返回slave都写成功
- 异步,写入bin log,就算成功
- 半同步,只有一个slave收到relay log,并且回复
6. 如何优雅的实现订单异步处理
使用RabbitMQ 异步处理代码实现
秒杀系统
逻辑清晰,代码清晰,提供了不同的问题角度,开阔一下视野
1. RedisLimitRateWithLUA.java 使用Redis做分布式限流
public static boolean accquire() throws IOException, URISyntaxException {
Jedis jedis = new Jedis("39.107.245.253");
String lua =
"local key = KEYS[1] " +
" local limit = tonumber(ARGV[1]) " +
" local current = tonumber(redis.call('get', key) or '0')" +
" if current + 1 > limit " +
" then return 0 " +
" else " +
" redis.call('INCRBY', key,'1')" +
" redis.call('expire', key,'2') " +
" end return 1 ";
String key = "ip:" + System.currentTimeMillis() / 1000; // 当前秒
String limit = "3"; // 最大限制
List<String> keys = new ArrayList<String>();
keys.add(key);
List<String> args = new ArrayList<String>();
args.add(limit);
jedis.auth("youxin11");
String luaScript = jedis.scriptLoad(lua);
Long result = (Long) jedis.evalsha(luaScript, keys, args);
return result == 1;
}
2. 分布式锁的三种实现方式
- 数据库
- redis
- zookeeper (临时顺序节点)