假设有一个库存表为 table, 结构如下:
列名 | 描述 |
ID | 主键 |
number | 数量 |
这里暂时不考虑服务器数量、页面 CDN 缓存、带宽等问题。我们常需要的操作是在 A 上同时增加 B、C,要的结果是 ABC 或者 ACB,而不希望是 AB 或 AC。再其次之,如果结果是 AB,则需要给 C 返回一个错误。
数据库乐观锁
数据库表中增加一个 version
字段,默认值为 0。在需要修改前,先查询 version,在修改时验证 version 且令 version = version + 1。在大多数的场合中,用来控制并发时的更新丢失是行之有效的手段。
所以我们可以用类似 update table set number = number - 1 where version = ? and number > 0
来操作数据库,基于判断 update 的条件来确定是否成功并保证 number 一直大于零。
memcached cas 锁 或者其他锁
Memecached 支持的多线程同步的方法有 Add 命令、incr/decr 命令、gets/cas 操作。我们使用 gets/cas 操作来进行同步锁。
Memcached 1.2.5 以及更高版本,提供了 gets 和 cas 命令。如果您使用 gets 命令查询某个 key 的 cache 会给您返回该 item 当前值的唯一标识。如果您覆写了这个 item 并想把它写回到 Memcached 中,您可以通过 cas 命令把那唯一标识送给 Memcached。如果该 item 存放在 Memcached 中的唯一标识与您提供的一致,您的写操作将会成功。如果另一个进程在这期间也修改了这个 item,那么该 item 存放在 Memcached 中的唯一标识将会改变,您的写操作就会失败。
示例代码如下:
MemcachedClient cache = null;
AuthDescriptor ad = new AuthDescriptor(new String[]{"PLAIN"}, new PlainCallbackHandler(username, password));
try {
cache = new MemcachedClient(
new ConnectionFactoryBuilder().setProtocol(ConnectionFactoryBuilder.Protocol.BINARY)
.setAuthDescriptor(ad)
.build(),
AddrUtil.getAddresses(host + ":" + port));
} catch (IOException e) {
e.printStackTrace();
}
int y = 0;
CASValue<Object> uniqueValue = cache.gets("testobject-1");
if (uniqueValue == null || uniqueValue.getValue() == null) {
cache.add("testobject-1", 300, 0);
uniqueValue = cache.gets("testobject-1");
} else {
y = (Integer) uniqueValue.getValue();
}
CASResponse response = cache.cas("testobject-1", uniqueValue.getCas(), (Integer) uniqueValue.getValue() + 1);
if (response.toString().equals("OK")) {
System.out.println("ok," + y);
return "ok," + y;
} else {
System.out.println("fail," + y);
return "fail," + y;
}
消息队列
业界已经有很多 MQ 产品,我们选适合自己的使用便可。如 RabbitMQ、ActiveMQ、ZeroMQ、Jafka/Kafka 等等。
对于短时间的突然大量并发来说,利用队列来缓存请求并依次进行处理也是个很好的方案。
题外:消息队列一般用于其他场景,如应用间的异步通讯、消息通知、日志记录等等。
其他
有很多时候,配合具体的业务逻辑来操作才是真正的好系统。 当预期人数并没有太多时,直接用数据库乐观锁来实现既节约开发成本也节约维护成本,并不是什么东西重要就是好。
业务层面也可以帮助系统更好的运作。比如用户量过大时且有很多为了刷单而注册的用户时,可以使用会员等级、用户积分等方式预先筛选出优质用户来参与。投机取巧类的,比如可以在前台只取 1/10 的用户请求进入后台进行处理,其他直接返回繁忙。如果只是秒杀少量商品,也可以使用预先生成订单,再填入用户来实现商品不会超卖。
过早或者没必要地让软件复杂化,而往软件系统中添加组件就是严重增加复杂性是一种很差的做法,我认同网络上一作者的想法,链接见参考链接。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于