1.QPS
Web系统开发中,会有一种常见的高并发系统,对系统吞吐量要求很高,一般的管理系统用户访问量不大对高并发要求并不够,如果对用户访问量很大的系统,如电商,搜索引擎等API接口,要求会特别的高。其中QPS(Quest per seconds)会有一个计算公式:
QPS(TPS)= 并发数/响应时间(单位秒);
并发数: 可以理解为单位时间内请求接口的用户数量。 系统能同时处理的request/(事务数),可以理解为网络能同时打开的通道,最直接的是Linux系统,每个进程能开启的网络通道个数,比如默认可以打开1024个网络文件描述符。
响应时间: 一般取平均响应时间,是每个http请求从请求开始到结束花销的时间,一般取系统各个接口的平均时间。
举个例子,我们假设处理一个业务请求平均响应时间为100ms,同时,系统内有20台Web服务器,配置MaxClients为500个(表示服务器的最大连接数目)。那么,我们的Web系统的理论峰值QPS为(理想化的计算方式):20*500/0.1 = 100000 (10万QPS)。
2.高并发系统设计
现在的web系统基本都是基于关系型数据库的,比如互联网最喜欢的MySql系统,当高并发系统中,最简单的是使用悲观锁的方式,不管是数据库还是代码当中,但是这并不是比较好的解决方式。锁的性能是低效的。
例子:
当用户请求涉及数据库值修改时,多个用户同时修改值,可能会造成错误,当使用悲观锁时,每次只允许一个用户修改值,比如商品出货这个操作。
a.查询商品剩余的数量 : select numbers from goods where good_id=${good_id}
b.生成订单: insert into orders(user_id,good_id) values('12345',${good_id})
c.减少商品剩余数量: update goods set numbers=numbers-1 where good_id=${good_id}
一个商品订单操作可以分为这几步,但是如果在多线程情况下,当商品数量剩余数量为1的时候,多个用户同时下单,同时发现商品用户数量为1,都生成了一条订单,实际上商品只有一个了,造成数据不一致错误。
(1) 队列方案
将用户的所有请求都放入放入队列中,然后消费者线程从队列中取数据,一个一个的处理,这种方式可以一定程度减少并发度,但是如果用户数量暴增,队列入队数量会远高于出队数量,最后导致系统出错,消费堵塞。
(2) 悲观锁方案
要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交。
我们可以使用命令设置MySQL为非autocommit模式:
set autocommit=0;
// 0.开始事务
begin;
// 1.查询出商品信息
select numbers from goods where good_id=${good_id} for update;
// 2.根据商品信息生成订单
insert into orders(user_id,good_id) values('12345',${good_id})
// 3.修改商品剩余数量减少一个
update goods set numbers=numbers-1 where good_id=${good_id}
// 4.提交事务
commit;
上面的begin/commit为事务的开始和结束,因为在前一步我们关闭了mysql的autocommit,所以需要手动控制事务的提交,在这里就不细表了。
FOR UPDATE
MySQL select…for update的Row Lock与Table Lock
使用select…for update会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB默认Row-Level Lock,所以只有明确地指定主键或者索引,MySQL 才会执行Row lock 只锁住被选取的数据),否则MySQL 将会执行Table Lock 将整个数据表单给锁住。
但是悲观锁并不是适用于任何场景,它也有它存在的一些不足,因为悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。如果加锁的时间过长,其他用户长时间无法访问,影响了程序的并发访问性,同时这样对数据库性能开销影响也很大,特别是对长事务而言,这样的开销往往无法承受。
(3)乐观锁方案
为了减少锁竞争,我们可以采用悲观锁的方式来实现高并发操作,为需要高并发访问的数据表建立一个version字段,然后操作。
a.查询商品剩余的数量 : select numbers,version from goods where good_id=#{good_id};
b.生成订单: insert into orders(user_id,good_id) values('12345',#{good_id})
c.减少商品剩余数量: update goods set numbers=numbers-1 where good_id=#{good_id} and version=#{version}
(4)CAS高并发系统
Compare And Swap 为了降低锁竞争带来的低效率,我们在高并发系统中应该尽量减少锁的使用,而使用CAS无锁操作.
独占锁是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。而另一个更加有效的锁就是乐观锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁用到的机制就是CAS,Compare and Swap。
在Java并发包中有这样一个包,java.util.concurrent.atomic,该包是对Java部分数据类型的原子封装,在原有数据类型的基础上,提供了原子性的操作方法,保证了线程安全。下面以AtomicInteger为例,来看一下是如何实现的。
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
public final int decrementAndGet() {
for (;;) {
int current = get();
int next = current - 1;
if (compareAndSet(current, next))
return next;
}
}
GCC4.1+版本中支持CAS的原子操作(完整的原子操作可参看 GCC Atomic Builtins)
bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...)
type __sync_val_compare_and_swap (type *ptr, type oldval type newval, ...)
高并发队列推荐使用Disruptor,CAS无锁队列