互联网高并发问题

互联网高并发问题

 

 

       一般的互联网应用,访问量非常大,最常见的是高并发问题。这是很多程序员都头疼的问题,但是问题终究要解决,所以我们一起看看吧。

 

 

 

 

高并发要解决的问题

 

  • 同步
  • 服务器能够接受请求能力
  • 响应时间
  • 防止单点故障和扩展

 

 

 

同步

 

说起同步,就是要加锁,而锁又分为先三种

 

  • 代码层次上的,如java中的同步锁,典型的就是同步关键字synchronized(这里不讲,因为不适合分布式系统)
  • 数据库层次上的,这些是分布式锁,如数据库的悲观锁、乐观锁;redis的分布式锁机制
  • 第三方框架层次上的,如zookeeper

 

 

悲观锁

 

/*
本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。 
*/
select * from account where name=”Erica” for update

 

        悲观锁的实现,往往依靠数据库提供的锁机制,如果是InnoDB,就会是行锁(也只有数据库层提供的锁机制才能 真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系 统不会修改数据)。

 

优点:实现分布式锁。

 

缺点:在事务执行过程中,锁住对应记录,如果事务执行时间比较长,这样锁住资源太久,太浪费了。

 

 

 

乐观锁

 

       乐观锁主要是为了解决悲观锁的问题,不会在事务中锁住数据,只要加一个version,在update数据的时候version +1,然后保存到数据库。这样程序执行第二次update的方法时会返回false,因为version变了。

 

例如:

 

  1. 假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。
  2. 操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 $50( $100-$50 )。
  3. 在操作员 A 操作的过程中,操作员 B 也读入此用户信息( version=1 ),并 从其帐户余额中扣除 $20 ( $100-$20 )。
  4. 操作员 A 完成了修改工作,将数据版本号加一( version=2 ),连同帐户扣除后余额( balance=$50 ),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。
  5. 操作员 B 完成了操作,也将版本号加一( version=2 )试图向数据库提交数据( balance=$80 ),但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 2 ,数据库记录当前版本也为 2 ,要求现在保存数据的版本必须大于数据库现在版本,因此,操作员 B 的提交被驳回。
  6. 这样,就避免了操作员 B 用基于version=1 的旧数据修改的结果覆盖操作 员 A 的操作结果的可能。

 

优点:乐观锁机制避免了长事务中的数据库加锁开销(操作员 A和操作员 B 操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系 统整体性能表现。

 

缺点:需要注意的是,乐观锁机制往往基于系统中的数据存储逻辑,因此也具备一定的局 限性,如在上例中,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户 余额更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。在 系统设计阶段,我们应该充分考虑到这些情况出现的可能性。

 

优化方案:将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途 径,而不是将数据库表直接对外公开。

 

 

 

注意

 

Hibernate中同时实现了悲观锁和乐观锁。

 

//Hibernate悲观锁实现

String hqlStr ="from TUser as user where user.name='Erica'";

Query query = session.createQuery(hqlStr);

query.setLockMode("user",LockMode.UPGRADE); // 加锁

List userList = query.list();// 执行查询,获取数据
 
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
 
<hibernate-mapping package="com.xiaohao.test">
 
    <!--乐观锁就靠optimistic-lock这个配置,并且表中有version这个字段-->
    <class name="User"  table="user" optimistic-lock="version" >
              <id name="id">
            <generator class="native" />
        </id>
        <!--version标签必须跟在id标签后面-->
        <version column="version" name="version"  />
        <property name="userName"/>
        <property name="password"/>
                 
    </class>
     
 
</hibernate-mapping>
 

 

 

 

服务器能够承受请求的能力

 

       首先,每个服务器能够承受的请求个数是有限的,太多的时候,服务器会出来不过来,一般单台服务器,能够承受400的并发量,但是这远远满足不了需求。

 

解决方法:

 

  • 能使用静态页面的地方尽量使用
  • 负载均衡
  • 控制关键请求同时触发的量

 

 

使用静态页面代替动态页面

 

  • 尽量减少动态页面(jsp、asp)的使用:这种方法就是减少jsp的使用,减少服务器对动态页面的解析,也减少启动服务时,占用jvm的内存。
  • 用Nginx做动静分离:当请求访问的是静态数据就跳转到文件服务器(NFS),当请求访问的是动态数据就跳转到Tomcat。

 

 

负载均衡

 

       这是最常见的扩展方法,如果一台服务器能够接受的并发量是400,那提升到四台服务器,就是1600,是最直接有效的提升并发量的方式。

 

  • 硬件负载均衡:LVS
  • 软件负载均衡:Nginx
  • 第三方平台负载均衡:AWS、Azure

 

 

控制关键请求同时触发的量

 

       这是防止服务器挂掉的最后手段,一般对消耗资源比较大的API使用,思路如下:在请求API中,给API设置信号量,例如信号量只有20个,能够拿到的请求就执行,拿不到的就返回异常,那四台服务器,总共对于这个API的请求就是同时处理80个。这样虽然会出一些异常,但是最起码保证了服务器不会因为请求量过大而挂掉。

 

 

 

 

响应时间

 

    响应时间是互联网应用很重要的一点,我们应该想尽办法让服务器更快返回信息。

 

解决方法:

 

  • 数据库优化
  • 缓存的使用
  • JVM优化
  • NoSQL分布式数据引擎及MapReduce:Hadoop(不熟悉,以后学习)
  • CDN镜像(不熟悉,以后学习)
  • 带宽,网速好当然好点(不属于开发内容,这里不讲了)

 

 

数据库优化

 

  • 数据库存储引擎选择
  • 数据库集群
  • 优化数据库结构,多做索引,提高查询效率;分表;读写分离;业务分库。
  • 优化数据库查询语句,减少直接使用hibernate等工具的直接生成语句(仅耗时较长的查询做优化),重点是保证索引有效,explain一下你的SQL语句。
  • 减少数据库IO次数,不过要看具体情况。
  • 还有个更能绝的方法,用Hbase等其他数据库代替MySQL

 

 

缓存的使用

 

       尽量使用缓存(Redis),是因为缓存速度奇快,而且用途很多,为我们开发和解决方案提供很多便利。

常用用途:
  • 缓存热门数据,如:user session
  • 为业务需求,缓存用户基本信息,查看通讯录好友时候是App用户
  • 分布式锁
  • inc()方法,可以实现全局唯一ID生成
  • 常用的是String、Hash,也可以用队列、set做交集并集、sortedSet做排行榜
 
 
JVM优化
       JVM的优化主要是减少Minor GC 和 Full GC的次数和时间,从而减少系统因为垃圾回收所引起的停顿,我之前的文章有写关于JVM调优的内容。
 

 

 

防止单点故障和扩展

 

       这方面的设计,现有最好的解决方案是使用云平台,因为云平台的冗余量、扩展性、自动部署,都做的非常好,就是钱的问题。

 

解决方案:

 

  • 使用云平台,防止单点故障和实现易扩展
  • 自己做冗余和负载均衡,工作量很大

 

 

 

案例分析

 

 

案例一:订票系统,只有一张机票,1W个人抢,如何解决高并发问题(可扩展到任何高并发网站要考虑的并发读写问题)

 

 

问题关键

 

  1. 在票卖出之前,所有人都应该看到票
  2. 即使再多人进来,也要保证只有一个人能抢到票
  3. 如果1W人同时请求系统,而系统能够接受的并发了又达不到这个数,应该想办法保证系统不会挂掉

 

解决思路

 

  1. 可以把票看作是共享资源,那就要加锁;而这种订票网站,一般是分布式架构,应该用分布式锁。
  2. 订单是对数据库操作,选择使用数据库锁做分布式锁,那要用悲观锁?乐观锁?
  3. 悲观锁锁住资源时间太久,不能接受,选择了用乐观锁。
  4. 是有在提交时,数据库version = 查出来时的version,才保存数据,记住version++。
  5. 使用乐观锁就要加version,这个用什么数据类型,如何设计,设计多大,需要讨论;还有旧数据要设置什么值。
  6. 乐观锁的使用,是用hibernate?还是自己逻辑解决?这要看系统原有架构
  7. 控制服务器可进入的请求量,可以用信号量,例如一台server有20个信号量,那就时同时可以接受20个请求,每进入一个请求拿一个信号量,使用完就释放信号量。保证服务器是在一个不高负载的环境下运行。
  8. 基本写好实现后,还要进行压测,试试效果,加多些log,保证方便查询调用时情况。

 

 

案例二:多个用户同时使用短信验证码登陆

 

问题关键:

 

  1. 发出请求后,如何加快响应时间
  2. 如何防止同一时间内,多次发送和接受短信
  3. 如果不断请求验证短信,怎么防止攻击

 

解决思路:

 

  1. 想要响应时间更快,后端接收到请求后,用线程池调用发短信API,然后马上返回发送成功。
  2. 想要防止同一时间内多次发短信,首先前端点了发送后,按钮禁用,有一个60s的冷却期
  3. 然后后端用redis来标记,这个时间段内是否发过短信
  4. 大致方法,redis.setex(phonenumber+'SMSSended',60,1);//设置一个60秒的标志位;redis.exists(phonenumber+'SMSSended');//检测是否存在
  5. 防止攻击,可以在发送短信后,保存一条数据在数据库,发短信请求过来时,验证一段时间内连续发送的次数,例如在10分钟内,如果连续发了5次,并且没有使用的,就让用户进入冷却期(在半个小时内,不能再请求发送短信)。
  6. 上面的方法要注意检查两点,一是电话号码,是防止一个人多次请求,占用资源;二是deviceId,是防止有人恶意攻击。

 

 

案例三:一个商家同时收到多个人发来的钱,一个人同时向多个人转钱

 

问题关键:

 

  1. 这种同时执行,实际上就需要排序一个个执行,那是否要用阻塞队列?
  2. 怎么保证一个商家收多个人的钱是有序,一个个执行的

 

解决思路:

 

  1. 这种多个商家可以并发收钱的业务,应该不使用队列,因为有可能要建多个队列,所以使用分布式锁
  2. 在赚钱给商家之间,先用setNx(),key = 商家id,超时删除时间 = 交易超时时间 + 几秒
  3. 然后执行赚钱给商家的业务,在这里面执行的因为有锁,所以保证只能有一笔转钱给商家的交易正在进行。
  4. 交易完成后删除锁。
  5. 如果一笔交易之前拿不到锁,那么就等待几秒再继续拿锁,进行一个轮询,等请求超时就返回给前端。

 

新问题:

 

  1. 在一条交易正在进行时加锁,那如何保证加锁期间,再进来的请求后面是有序进行的?(思考中。。。)

 

 

 

 

携程的高并发应用架构

 

http://geek.csdn.net/news/detail/96708

 

其他学习资料

http://www.iqiyi.com/w_19rshylvpx.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值