域名分发(Java高并发常见应对之策吐血整理)

Java高并发常见应对之策吐血整理一、高并发涉及到的知识点线程安全,线程封闭,线程调度,同步容器,并发容器,AQS,J.U.C。二、同步与异步所谓同步,可以理解为在执行完一个函数或方法之后,一直等待系统返回值或消息,这时程序是出于阻塞的,只有接收到返回的值或消息后才往下执行其它的命令。异步,执行完函数或方法后,不必阻塞性地等待返回值或消息,只需要向系统委托一个异步过程,那么当系统接收到返回值或消息时,系统会自动触发委托的异步过程,从而完成一个完整的流程。同步在一定程度上可以看做是单线程,这个线程请求一个方法后就待这个方法给他回复,否则他不往下执行。异步在一定程度上可以看做是多线程的,请求一个方法后,就不管了,继续执行其他的方法。2.1举例同步:吃饭和说话,只能一件事一件事的来,因为只有一张嘴。异步:但吃饭和听音乐是异步的,因为,听音乐并不引响我们吃饭。2.2脏数据脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据(Dirty Data),依据脏数据所做的操作可能是不正确的。2.3不可重复读不可重复读是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。三、如何处理高并发问题3.1硬件上扩容:水平扩容、垂直扩容3.2中间件上缓存:Redis、Memcache、GuavaCache等队列:Kafka、RabitMQ、RocketMQ等3.3分布式拆分应用拆分:服务化Dubbo与微服务Spring Cloud限流:Guava RateLimiter使用、常用限流算法、自己实现分布式限流等服务降级与服务熔断:服务降级的多重选择、Hystrix3.4数据库层面数据库切库,分库分表:切库、分表、多数据源高可用的一些手段:任务调度分布式elastic-job、主备curator的实现、监控报警机制3.5使用微服务使用微服务拆分,划分模块、网关,熔断、限流、降级四、并发的优势与风险4.1并发的优势(1)速度上可以同时处理多个请求,响应更快;复杂的操作可以分成多个进程同时进行。(2)设计上程序设计在某些情况下更简单,也可以更多的选择。(3)资源利用上CPU能够在等待IO的时候做一些其他的事情。4.2并发的风险(1)安全性:多个线程共享数据时可能会产生于期望不相符的结果。(2)活跃性:某个操作无法继续进行下去时,就会发生活跃性问题。比如死锁、饥饿等问题。(3)性能:线程过多时会使得CPU频繁切换,调度时间增多;同步机制;消耗过多内存。4.3如何处理并发和同步4.3.1Java代码层面java中的同步锁,典型的就是同步关键字synchronized。还有一个Atomic包,里面是基于乐观锁版本号CAS自旋实现并发控制。4.3.2数据库层面悲观锁(Pessimistic Locking):悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自 外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能 真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系 统不会修改数据)。一个典型的倚赖数据库的悲观锁调用:select * from account where name=”javagongfu” for update这条 sql 语句锁定了 account 表中所有符合检索条件( name=”javagongfu” )的记录。本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。乐观锁:大多是基于数据版本 Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来 实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提 交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据 版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。对于上面修改用户帐户信息的例子而言,假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 $50( $100-$50 )。 在操作员 A 操作的过程中,操作员 B 也读入此用户信息( version=1 ),并 从其帐户余额中扣除 $20 ( $100-$20 )。 操作员 A 完成了修改工作,将数据版本号加一( version=2 ),连同帐户扣 除后余额( balance=$50 ),提交至数据库更新,此时由于提交数据版本大 于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。操作员 B 完成了操作,也将版本号加一( version=2 )试图向数据库提交数 据( balance=$80 ),但此时比对数据库记录版本时发现,操作员 B 提交的 数据版本号为 2 ,数据库记录当前版本也为 2 ,不满足 “ 提交版本必须大于记 录当前版本才能执行更新 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。这样,就避免了操作员 B 用基于version=1 的旧数据修改的结果覆盖操作 员 A 的操作结果的可能。 从上面的例子可以看出,乐观锁机制避免了长事务中的数据库加锁开销(操作员 A和操作员 B 操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系 统整体性能表现。 需要注意的是,乐观锁机制往往基于系统中的数据存储逻辑,因此也具备一定的局 限性,如在上例中,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户 余额更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。在 系统设计阶段,我们应该充分考虑到这些情况出现的可能性,并进行相应调整(如 将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途 径,而不是将数据库表直接对外公开)。五、常见并发问题列举案例一:订票系统案例,某航班只有一张机票,假定有1w个人打开你的网站来订票,问你如何解决并发问题。问题描述:1w个人同时点击购买,到底谁能成交?总共只有一张票。解决方案:锁同步同步更多指的是应用程序的层面,多个线程进来,只能一个一个的访问,java中指的是syncrinized关键字。锁也有2个层面,一个是java中谈到的对象锁,用于线程同步;另外一个层面是数据库的锁;如果是分布式的系统,显然只能利用数据库端的锁来实现。假定我们采用了同步机制或者数据库物理锁机制,如何保证1w个人还能同时看到有票,显然会牺牲性能,在高并发网站中是不可取的。采用乐观锁即可解决此问题。乐观锁意思是不锁定表的情况下,利用业务的控制来解决并发问题,这样即保证数据的并发可读性又保证保存数据的排他性,保证性能的同时解决了并发带来的脏数据问题。在现有表当中增加一个冗余字段,version版本号, long类型原理:1)只有当前版本号》=数据库表版本号,才能提交2)提交成功后,版本号version ++5.1解决此问题还需考虑的因素(1)可以考虑增加缓存,分布式缓存,实现读写分离,采用Redis作为缓存端,基础数据、常用业务数据直接从缓存取。(2)增加网络带宽,DNS域名解析分发多台服务器。(3)负载均衡,配置前置代理服务器nginx、apache。(4)数据库查询优化,读写分离,分表等等.(5)优化数据库结构,多做索引,提高查询效率。六、线程安全基于数组结构:ArrayList -> 线程安全:Vector, StackVector中的方法使用synchronized修饰过,线程安全Stack继承Vector非线程安全:HashMap -> 线程安全:HashTable(key、value不能为null)HashTable使用synchronized修饰方法Collections.synchronizedXXX(List、Set、Map)ConcurrentHashMap、ConcurrentLinkedList都是线程安全的。6.1并发容器ArrayList -> CopyOnWriteArrayList:相比ArrayList,CopyOnWriteArrayList是线程安全的,写操作时复制,即当有新元素添加到CopyOnWriteArrayList时,先从原有的数组里拷贝一份出来,然后在新的数组上写操作,写完之后再将原来的数组指向新的数组,CopyOnWriteArrayList整个操作都是在锁(ReentrantLock锁)的保护下进行的,这么做主要是避免在多线程并发做add操作时复制出多个副本出来,把数据搞乱了。第一个缺点是做写操作时,需要拷贝数组,就会消耗内存,如果元素内容比较多会导致youngGC或者是fullGc;第二个缺点是不能用于实时读的场景,比如拷贝数组、新增元素都需要时间,所以调用一个set操作后,读取到的数据可能还是旧的,虽然CopyOnWriteArrayList能够做到最终的一致性,但是没法满足实时性要求,因此CopyOnWriteArrayList更适合读多写少的场景。CopyOnWriteArrayList设计思想:1读写分离 2最终一致性 3使用时另外开辟空间解决并发冲突。HashSet -> CopyOnWriteArraySetTreeSet -> ConcurrentSkipListSetCopyOnWriteArraySet:底层实现是CopyOnWriteArrayList。ConcurrentSkipListSet:和TreeSet 一样支持自然排序,基于map集合,但是批量操作不是线程安全的。HashMap -> ConcurrentHashMap :不允许空值,针对读操作做了大量的优化,具有特别高的并发性。TreeMap -> ConcurrentSkipListMap :内部使用SkipList跳表结构实现的,key是有序的,支持更高的并发。6.2AQS同步组件1 CountDownLatch:闭锁,通过计数来保证线程是否需要一直阻塞2 Semaphore:控制同一时间并发线程的数目3 CyclicBarrier:和CountDownLatch相似,都能阻阻塞线程4 ReentrantLock5 Condition6 FutureTask七、使用线程池new Thread弊端:1 每次new Thread新建对象,性能差2 线程缺乏统一的管理,肯无限制的新建线程,相互竞争,有可能占用过多系统资源导致死机或者OOM3 缺少更多功能,如更多执行、定期执行、线程中断线程池的好处:1 重用存在的线程,减少对象创建、消亡的开销,性能佳2 可有效控制最大并发的线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞3 提供定时执行、定期执行、单线程、并发数控制等功能7.1ThreadPoolExecutorThreadPoolExecutor参数:1 corePoolSize:核心线程数2 maximumPoolSize:最大线程数3 workQueue:阻塞队列,存储等待执行的任务,很重要,会对线程池运行过程产生重大影响如果当前系统运行的线程数量小于corePoolSize,直接新建线程执行处理任务,即使线程池中的其他线程是空闲的。如果当前系统运行的线程数量大于或等于corePoolSize,且小于maximumPoolSize,只有当workQueue满的时候才创建新的线程去处理任务,如果设置corePoolSize和maximumPoolSize相同的话,那么创建的线程池大小是固定的,这时如果有新任务提交,当workQueue没满时,把请求放进workQueue中,等待有空闲的线程从workQueue中取出任务去处理。如果运行的线程数量大于maximumPoolSize,这时如果workQueue满,根据拒绝策略去处理。4 keepAliveTime:线程没有任务执行时最多保持多久的时间终止5 unit:keepAliveTime的时间单位6 threadFactory:线程工厂,用来创建线程7 rejectHandler:当拒绝处理任务时的策略线程池方法:八、分布式缓存8.1Redis8.2memcache8.3缓存一致性8.4缓存并发九、应用拆分采用MyCat分库分表,读写分离。十、使用微服务使用Spring Cloud Alibaba,网关组件,Sentinel隔离、熔断、降级、限流。具体微服务本博客有很多文章介绍。

本文出自快速备案,转载时请注明出处及相应链接。

本文永久链接: https://kuaisubeian.cc/34363.html

kuaisubeian