Internet high concurrency problem

Internet high concurrency problem

 

 

       In general Internet applications, the amount of access is very large, and the most common problem is high concurrency. This is a headache for many programmers, but the problem will eventually be solved, so let's take a look.

 

 

 

 

Problems to be solved by high concurrency

 

  • Synchronize
  • The server is capable of accepting requests
  • Response time
  • Protection against single points of failure and scaling

 

 

 

Synchronize

 

Speaking of synchronization, it is to add locks, and locks are divided into three types.

 

  • At the code level, such as synchronization locks in java, the synchronization keyword is typically synchronized (not mentioned here because it is not suitable for distributed systems)
  • At the database level, these are distributed locks, such as pessimistic locks and optimistic locks of databases; distributed lock mechanism of redis
  • At the third-party framework level, such as zookeeper

 

 

pessimistic lock

 

/*
Before the transaction is committed (the locks in the transaction process will be released when the transaction is committed), the outside world cannot modify these records. 
*/
select * from account where name=”Erica” for update

 

        The implementation of pessimistic locks often relies on the locking mechanism provided by the database. If it is InnoDB, it will be a row lock (only the locking mechanism provided by the database layer can truly guarantee the exclusivity of data access, otherwise, even if locking is implemented in this system) mechanism, and there is no guarantee that external systems will not modify the data).

 

Advantages : Implement distributed locks.

 

Disadvantages : During the execution of the transaction, the corresponding records are locked. If the transaction execution time is relatively long, it is too wasteful to lock the resources for too long.

 

 

 

optimistic locking

 

       Optimistic locking is mainly to solve the problem of pessimistic locking. It will not lock data in the transaction. Just add a version, when updating data, version +1, and then save it to the database. In this way, when the program executes the second update method, it will return false because the version has changed.

 

E.g:

 

  1. Suppose there is a version field in the account information table in the database, the current value is 1; and the current account balance field (balance) is $100.
  2. Operator A now reads it out ( version=1 ) and deducts $50 from its account balance ( $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

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326305815&siteId=291194637