java 并发编程学习笔记(九)多线程并发拓展

                                         多线程并发拓展

(1)死锁

public class DeadLockTest implements  Runnable {

    private int flag ;

    private static Object o1=new Object();
    private static Object o2=new Object();

    public DeadLockTest(int flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if(flag == 0){
            synchronized (o2){

                try {
                    Thread.sleep(500);
                    System.out.println("Thread1开始");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1){
                    System.out.println("Thread1结束!");
                }
            }
        }
        if(flag ==1){
            synchronized (o1){
                try {
                    Thread.sleep(500);
                    System.out.println("Thread2开始");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o2){
                    System.out.println("Thread2结束!");
                }
            }
        }


    }
    public static void  main(String[] args){

        new Thread(new DeadLockTest(0)).start();
        new Thread(new DeadLockTest(1)).start();
    }

}

死锁问题是多线程特有的问题,它可以被认为是线程间切换消耗系统性能的一种极端情况。在死锁时,线程间相互等待资源,而又不释放自身的资源,导致无穷无尽的等待,其结果是系统任务永远无法执行完成。死锁问题是在多线程开发中应该坚决避免和杜绝的问题。

一般来说,要出现死锁问题需要满足以下条件:

1. 互斥条件:一个资源每次只能被一个线程使用。

2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。

4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

只要破坏死锁 4 个必要条件之一中的任何一个,死锁问题就能被解决。

死锁解决方案

死锁是由四个必要条件导致的,所以一般来说,只要破坏这四个必要条件中的一个条件,死锁情况就应该不会发生。

  1. 如果想要打破互斥条件,我们需要允许进程同时访问某些资源,这种方法受制于实际场景,不太容易实现条件;
  2. 打破不可抢占条件,这样需要允许进程强行从占有者那里夺取某些资源,或者简单一点理解,占有资源的进程不能再申请占有其他资源,必须释放手上的资源之后才能发起申请,这个其实也很难找到适用场景;
  3. 进程在运行前申请得到所有的资源,否则该进程不能进入准备执行状态。这个方法看似有点用处,但是它的缺点是可能导致资源利用率和进程并发性降低;
  4. 避免出现资源申请环路,即对资源事先分类编号,按号分配。这种方式可以有效提高资源的利用率和系统吞吐量,但是增加了系统开销,增大了进程对资源的占用时间。

如果我们在死锁检查时发现了死锁情况,那么就要努力消除死锁,使系统从死锁状态中恢复过来。消除死锁的几种方式:

1. 最简单、最常用的方法就是进行系统的重新启动,不过这种方法代价很大,它意味着在这之前所有的进程已经完成的计算工作都将付之东流,包括参与死锁的那些进程,以及未参与死锁的进程;

2. 撤消进程,剥夺资源。终止参与死锁的进程,收回它们占有的资源,从而解除死锁。这时又分两种情况:一次性撤消参与死锁的全部进程,剥夺全部资源;或者逐步撤消参与死锁的进程,逐步收回死锁进程占有的资源。一般来说,选择逐步撤消的进程时要按照一定的原则进行,目的是撤消那些代价最小的进程,比如按进程的优先级确定进程的代价;考虑进程运行时的代价和与此进程相关的外部作业的代价等因素;

3. 进程回退策略,即让参与死锁的进程回退到没有发生死锁前某一点处,并由此点处继续执行,以求再次执行时不再发生死锁。虽然这是个较理想的办法,但是操作起来系统开销极大,要有堆栈这样的机构记录进程的每一步变化,以便今后的回退,有时这是无法做到的。

其实即便是商业产品,依然会有很多死锁情况的发生,例如 MySQL 数据库,它也经常容易出现死锁案例。

(2)多线程并发最佳实践

  • 使用本地变量
  • 使用不可变类
  • 最小化锁的作用域范围s=1/(1-a+a/n)  (a 并行计算部分所占比例,n 并行处理节点个数,S 加速比)
  • 使用线程池Executor ,而不是直接new Thread执行
  • 宁可使用同步也不要使用线程的wait和notiyf
  • 使用BlockingQueue 实现生产-消费模式
  • 使用并发集合而不是加了锁的同步集合
  • 使用Semaphore创建有界的访问
  • 宁可使用同步代码块,也不适用同步的方法
  • 避免使用静态变量

(3)高并发处理思路

扩容 

垂直 扩容 :(纵向扩展)提高系统的 部件能力

水平扩容:(横向扩容):增加更多系统成员来实现

数据库扩容:

读操作扩展:memcache,redis,cdn等缓存

写操作扩展:Cassandra,Hbase等

缓存:

java 并发编程学习笔记(九)多线程并发拓展_第1张图片

缓存命中率的影响因素: 业务场景合业务需求,缓存的设计(粒度和策略),缓存容量和基础设施

本地缓存:编程实现(成员变量、局部变量、静态变量)Guava Cache

分布式缓存:  Memcache 、Redis

高并发场景下缓存常见问题:

1、缓存一致性问题

当数据时效性要求很高时,需要保证缓存中的数据与数据库中的保持一致,而且需要保证缓存节点和副本中的数据也保持一致,不能出现差异现象。这就比较依赖缓存的过期和更新策略。一般会在数据发生更改的时,主动更新缓存中的数据或者移除对应的缓存。

java 并发编程学习笔记(九)多线程并发拓展_第2张图片

2、缓存并发问题

缓存过期后将尝试从后端数据库获取数据,这是一个看似合理的流程。但是,在高并发场景下,有可能多个请求并发的去从数据库获取数据,对后端数据库造成极大的冲击,甚至导致 “雪崩”现象。此外,当某个缓存key在被更新时,同时也可能被大量请求在获取,这也会导致一致性的问题。那如何避免类似问题呢?我们会想到类似“锁”的机制,在缓存更新或者过期的情况下,先尝试获取到锁,当更新或者从数据库获取完成后再释放锁,其他的请求只需要牺牲一定的等待时间,即可直接从缓存中继续获取数据。

java 并发编程学习笔记(九)多线程并发拓展_第3张图片

3、缓存穿透问题

缓存穿透在有些地方也称为“击穿”。很多朋友对缓存穿透的理解是:由于缓存故障或者缓存过期导致大量请求穿透到后端数据库服务器,从而对数据库造成巨大冲击。

这其实是一种误解。真正的缓存穿透应该是这样的

在高并发场景下,如果某一个key被高并发访问,没有被命中,出于对容错性考虑,会尝试去从后端数据库中获取,从而导致了大量请求达到数据库,而当该key对应的数据本身就是空的情况下,这就导致数据库中并发的去执行了很多不必要的查询操作,从而导致巨大冲击和压力。

可以通过下面的几种常用方式来避免缓存传统问题:

缓存空对象

对查询结果为空的对象也进行缓存,如果是集合,可以缓存一个空的集合(非null),如果是缓存单个对象,可以通过字段标识来区分。这样避免请求穿透到后端数据库。同时,也需要保证缓存数据的时效性。这种方式实现起来成本较低,比较适合命中不高,但可能被频繁更新的数据。

单独过滤处理

对所有可能对应数据为空的key进行统一的存放,并在请求前做拦截,这样避免请求穿透到后端数据库。这种方式实现起来相对复杂,比较适合命中不高,但是更新不频繁的数据。

java 并发编程学习笔记(九)多线程并发拓展_第4张图片

3、缓存颠簸问题

缓存的颠簸问题,有些地方可能被成为“缓存抖动”,可以看做是一种比“雪崩”更轻微的故障,但是也会在一段时间内对系统造成冲击和性能影响。一般是由于缓存节点故障导致。业内推荐的做法是通过一致性Hash算法来解决。

4、缓存的雪崩现象

缓存雪崩就是指由于缓存的原因,导致大量请求到达后端数据库,从而导致数据库崩溃,整个系统崩溃,发生灾难。导致这种现象的原因有很多种,上面提到的“缓存并发”,“缓存穿透”,“缓存颠簸”等问题,其实都可能会导致缓存雪崩现象发生。这些问题也可能会被恶意攻击者所利用。还有一种情况,例如某个时间点内,系统预加载的缓存周期性集中失效了,也可能会导致雪崩。为了避免这种周期性失效,可以通过设置不同的过期时间,来错开缓存过期,从而避免缓存集中失效。

从应用架构角度,我们可以通过限流、降级、熔断等手段来降低影响,也可以通过多级缓存来避免这种灾难。

此外,从整个研发体系流程的角度,应该加强压力测试,尽量模拟真实场景,尽早的暴露问题从而防范。

java 并发编程学习笔记(九)多线程并发拓展_第5张图片

5、缓存无底洞现象

该问题由 facebook 的工作人员提出的, facebook 在 2010 年左右,memcached 节点就已经达3000 个,缓存数千 G 内容。

他们发现了一个问题---memcached 连接频率,效率下降了,于是加 memcached 节点,

添加了后,发现因为连接频率导致的问题,仍然存在,并没有好转,称之为”无底洞现象”。

java 并发编程学习笔记(九)多线程并发拓展_第6张图片

目前主流的数据库、缓存、Nosql、搜索中间件等技术栈中,都支持“分片”技术,来满足“高性能、高并发、高可用、可扩展”等要求。有些是在client端通过Hash取模(或一致性Hash)将值映射到不同的实例上,有些是在client端通过范围取值的方式映射的。当然,也有些是在服务端进行的。但是,每一次操作都可能需要和不同节点进行网络通信来完成,实例节点越多,则开销会越大,对性能影响就越大。 

主要可以从如下几个方面避免和优化:

数据分布方式

有些业务数据可能适合Hash分布,而有些业务适合采用范围分布,这样能够从一定程度避免网络IO的开销。

IO优化

可以充分利用连接池,NIO等技术来尽可能降低连接开销,增强并发连接能力。

数据访问方式

一次性获取大的数据集,会比分多次去获取小数据集的网络IO开销更小。 

当然,缓存无底洞现象并不常见。在绝大多数的公司里可能根本不会遇到。

消息队列

java 并发编程学习笔记(九)多线程并发拓展_第7张图片

流程A在处理时没有在当前线程同步的处理完而是直接发送了一条消息A1到队列里,然后消息队列过了一段时间(可能是几毫秒 几秒 几分钟)这个消息开始被处理,消息处理的过程就相当于流程A被处理;当然这只是一个简单的模型下面我们套用实际的场景来看一下,比如下单成功后发送短信提醒;如果没有消息队列我们会选择同步调用发短信的接口并等待短信发送成功,正常情况下这么做是没有问题的但是如果发短信的时候短信接口出问题了或者说调用超时了等意外情况,这个时候我们就需要设计对应的方案来解决前提是这些方案的设计会比较复杂;
但是当我们使用消息队列以后这个事情就会变得非常简单,使用消息队列以后有如下好处:
① 实现了异步解耦
② 设计变的更加简单了
③ 保证了数据的最终一致性;
④ 提高效率;

消息队列特性:

1 与业务无关 : 只做消息分发

2 FIFO : 先投递先到达

3 容灾 : 节点的动态增删和消息的持久化

4 性能 : 吞吐量提升,系统内部通信效率提高

为什么需要消息队列?

生产和消费的速度或稳定性等因素不一致;

消息队列的好处

1 业务解耦

2 最终一致性 : 用记录和补偿的方式来处理,在做所有的不确定事情之前先记录然后再去做,它的结果通常分为三种成功失败或者不确定(比如说超时等);如果是成功我们就可以清楚掉记录,如果是失败或者不确定我们可以通过定时任务将所有事情重新做一遍直到成功为止;

3 广播 : 如果没有消息队列每一个新的业务方介入都需要联调一次接口,使用消息队列只需要关心消息是否送达到消息队列,新接入的接口订阅相关的消息自己做处理就可以了;

4 错峰与流控 : 上下游对于事情的处理是不同的,比如WEB前端每秒承受上千万的请求都是可以的但是数据库的处理却非常有限;迫于成本的压力我们不能要求数据库的机器数量与前端资源一样;这样的问题同样存在于系统与系统之间,比如短信系统的速度卡在网关上边它与前端的并发量不是一个数量级的,用户玩几秒种收到短信也是可以的;针对于这样的场景如果没有消息队列也能实现但是系统的复杂度非常的高;

常用的消息队列介绍

  • Kafka

 java 并发编程学习笔记(九)多线程并发拓展_第8张图片

Kafka是Apache下的一个子项目,是一个高性能 跨语言 分布式发布订阅消息队列系统;

1 Kafka的特性
① 快速持久化 : 它可以在o1的系统开销下实现消息的持久化;
② 高吞吐 : 在一台普通的服务器上就可以达到10w/秒的吞吐速率;
③ 天生的分布式 : 所有组件天生支持分布式且自动实现负载均衡;

2 Kafka基础定义
① Broker : Kafka集群包含一个或多个服务器,这个服务器就被称为Producer;
② Topic : 指每条发布到Kafka的消息都有一个类别,这个类别叫做Topic;物理上不同Topic的消息是分开存储的,逻辑上一个Topic消息虽然保存在一个或者多Producer上但是用户只需指定消息的Topic就可以生产或者消费数据而不用关心数据存在哪里;
③ Partition : 是物理上的概念,每个Topic包含一个或者多个Partition;
④ Producer : 负责发布消息到Kafka的Broker里边;
⑤ consumer : 消息消费者,向Kafka broker读取消息的客户端;
⑥ Consumer Group : 每个Consumer属于一个特定的Consumer Group(可为每个Consumer指定group name,若不指定group name则属于默认的group);

  • RabbitMQ

 java 并发编程学习笔记(九)多线程并发拓展_第9张图片

RabbitMQ是流行的开源消息队列系统,用erlang语言开发。RabbitMQ是AMQP(高级消息队列协议)的标准实现;

1 RabbitMQ的使用过程
(1)客户端连接到消息队列服务器,打开一个channel。
(2)客户端声明一个exchange,并设置相关属性。
(3)客户端声明一个queue,并设置相关属性。
(4)客户端使用routing key,在exchange和queue之间建立好绑定关系。
(5)客户端投递消息到exchange。

2 RabbitMQ的概念
Broker:简单来说就是消息队列服务器实体。
Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。
Queue:消息队列载体,每个消息都会被投入到一个或多个队列。
Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来。
Routing Key:路由关键字,exchange根据这个关键字进行消息投递。
vhost:虚拟主机,一个broker里可以开设多个vhost,用作不同用户的权限分离。
producer:消息生产者,就是投递消息的程序。
consumer:消息消费者,就是接受消息的程序。
channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。

(4)高并发应用拆分 

比如一个股票系统有用户信息、开户、股票行情、交易、订单等,拆分后如下图所示:

java 并发编程学习笔记(九)多线程并发拓展_第10张图片

原则

  • 业务优先

每个系统都会有多个模块,每个模块又有多个业务功能;按照业务边界进行切割,再对模块进行拆分。

  • 循序渐进

边拆分边测试,保证系统的正常运行。

  • 兼顾技术:重构、分层

不能为了分布式而分布式,拆分过程不仅是业务梳理也是代码重构的过程,根据技术进行分层来分配工作,ui对用户体验,熟悉C和C++对服务器,熟悉数据库的对数据库,做到术业有专攻,合适的人去做合适的事情。

  • 可靠测试

测试完毕后,才可进行下一步,每一步都要有足够的测试才可进行下一步,避免小错误引起蝴蝶效应。

思考

  • 应用之间通信: RPC ( dubbo等)、消息队列

消息队列通常用于传输数据包小但是数据量大,对实时性要求低的场景,比如下单后短信通知客户。而采用RPC要求实时性高,这里通常不会http或者service服务,原因是使用PRC调用service方法无感知,在配置好后和本地方法很像。

  • 应用之间数据库设计:每个应用都有独立的数据库

通常情况下,每个应用都有自己独立的数据库,如果共同使用的信息,可以考虑放在common中使用。

  • 避免事务操作跨应用

分布式事务是一个很消耗资源的问题,应用之间服务分开开发,能够保持相互独立。

框架

服务化 Dubbo

java 并发编程学习笔记(九)多线程并发拓展_第11张图片

 微服务Spring Cloud

java 并发编程学习笔记(九)多线程并发拓展_第12张图片

 微服务:独立的服务共同组成一个系统

java 并发编程学习笔记(九)多线程并发拓展_第13张图片

要实践微服务要解决4个问题:


客户端如何访问这些服务
API Gateway提供统一的服务入口,对前台透明,同时可以聚合后台的服务,提供安全过滤流控等api的管理功能
服务之间是如何通信的
异步的话使用消息队列,同步调用使用REST或者是RPC,Rest可以使用springboot,RPC通常使用Dubbo
同步调用一致性强但是出现调用问题,REST一般基于http实现,能够跨客户端,同时对客户端没有更多的要求。
RPC的传输协议更高效,安全也更加可控。特别是在一个公司内部如果有统一的开发规范和统一的框架,它的开发效率会更加明显。
而异步消息在分布式系统中有特别广泛的应用,它既能减少调用服务之间的耦合,又能成为调用之间的缓冲,确保消息积压不会冲垮被调用方。
同时保证调用方的用户的体验,继续干自己的活。付出的代价是一致性的减慢,需要接受数据的最终一致性
如何实现如此多服务
在微服务架构中一般每一服务都会拷贝进行负载均衡,服务如何相互感知,如何相互管理,这就是服务发现的问题了,一般都是进行服务注册信息的分布式管理。
服务挂了该如何解决,有什么备份方案和应急处理机制
分布式最大的特性就是网络是不可靠的,当系统是由一系列的调用链组成的时候,其中任何一个出问题都不至于影响到整个链路。
相应的手段有:重试机制、应用的限流、熔断机制、负载均衡、系统降级

(5) 应用限流

限流就是通过对并发访问/请求进行限速或一个时间窗口内的请求进行限速,从而达到保护系统的目的。一般系统可以通过压测来预估能处理的峰值,一旦达到设定的峰值阀值,则可以拒绝服务(定向错误页或告知资源没有了)、排队或等待(例如:秒杀、评论、下单)、降级(返回默认数据)

限流不能乱用,否则正常流量会出现一些奇怪的问题,从而导致用户抱怨。

java 并发编程学习笔记(九)多线程并发拓展_第14张图片

 

假设有130W到140W的数据插入到数据库中,如果没有做限流,数据库的主库会突然接收到130w的插入操作

首先是网络上的开销,很可能直接把带宽占满,导致其他请求无法正常传输和处理,其次会是数据库的负载突然增高,导致无法处理某些数据库的操作,也有可能数据库没有足够的连接导致某些数据库插入查询失败;

还有一点就是现在数据库都做了主从设计,主数据库的数据还要同步给从库,这时瞬间插入了大量的数据,会带来从库和主库的延迟特别大,这时从库查询不准确的概率也会跟着提升。

如果我们放慢插入数据库的速度,这时插入数据库主库的速率会很正常,同步到从库也很正常。网络消耗也可以接收不会影响其他服务。

java 并发编程学习笔记(九)多线程并发拓展_第15张图片

 1.计数器法

有时我们还会使用计数器来进行限流,主要用来限制一定时间内的总并发数,比如数据库连接池、线程池、秒杀的并发数;计数器限流只要一定时间内的总请求数超过设定的阀值则进行限流,是一种简单粗暴的总数量限流,而不是平均速率限流。

java 并发编程学习笔记(九)多线程并发拓展_第16张图片

这个方法有一个致命问题:临界问题——当遇到恶意请求,在0:59时,瞬间请求100次,并且在1:00请求100次,那么这个用户在1秒内请求了200次,用户可以在重置节点突发请求,而瞬间超过我们设置的速率限制,用户可能通过算法漏洞击垮我们的应用。

java 并发编程学习笔记(九)多线程并发拓展_第17张图片

2.滑动窗口算法

java 并发编程学习笔记(九)多线程并发拓展_第18张图片

 在上图中,整个红色矩形框是一个时间窗口,在我们的例子中,一个时间窗口就是1分钟,然后我们将时间窗口进行划分,如上图我们把滑动窗口

划分为6格,所以每一格代表10秒,每超过10秒,我们的时间窗口就会向右滑动一格,每一格都有自己独立的计数器,例如:一个请求在0:35到达,

那么0:30到0:39的计数器会+1,那么滑动窗口是怎么解决临界点的问题呢?如上图,0:59到达的100个请求会在灰色区域格子中,而1:00到达的请求

会在红色格子中,窗口会向右滑动一格,那么此时间窗口内的总请求数共200个,超过了限定的100,所以此时能够检测出来触发了限流。

回头看看计数器算法,会发现,其实计数器算法就是窗口滑动算法,只不过计数器算法没有对时间窗口进行划分,所以是一格。

由此可见,当滑动窗口的格子划分越多,限流的统计就会越精确。

 

3.漏铜算法

java 并发编程学习笔记(九)多线程并发拓展_第19张图片

这个算法很简单。首先,我们有一个固定容量的桶,有水进来,也有水出去。对于流进来的水,我们无法预计共有多少水流进来,也无法预计流水速度,但

对于流出去的水来说,这个桶可以固定水流的速率,而且当桶满的时候,多余的水会溢出来。

 

4.令牌桶算法

java 并发编程学习笔记(九)多线程并发拓展_第20张图片

从上图中可以看出,令牌算法有点复杂,桶里存放着令牌token。桶一开始是空的,token以固定的速率r往桶里面填充,直到达到桶的容量,多余的token会

被丢弃。每当一个请求过来时,就会尝试着移除一个token,如果没有token,请求无法通过。

(6)服务降级与服务熔断

服务降级:

服务压力剧增的时候根据当前的业务情况及流量对一些服务和页面有策略的降级,以此环节服务器的压力,以保证核心任务的进行。

同时保证部分甚至大部分任务客户能得到正确的相应。也就是当前的请求处理不了了或者出错了,给一个默认的返回。

服务熔断:

在股票市场,熔断这个词大家都不陌生,是指当股指波幅达到某个点后,交易所为控制风险采取的暂停交易措施。相应的,服务熔断一般是指软件系统中,由于某些原因使得服务出现了过载现象,为防止造成整个系统故障,从而采用的一种保护措施,所以很多地方把熔断亦称为过载保护。

 

降级分类

降级按照是否自动化可分为:自动开关降级和人工开关降级。

降级按照功能可分为:读服务降级、写服务降级。

降级按照处于的系统层次可分为:多级降级。

 

自动降级分类

(1)、超时降级:主要配置好超时时间和超时重试次数和机制,并使用异步机制探测回复情况

(2)、失败次数降级:主要是一些不稳定的api,当失败调用次数达到一定阀值自动降级,同样要使用异步机制探测回复情况

(3)、故障降级:比如要调用的远程服务挂掉了(网络故障、DNS故障、http服务返回错误的状态码、rpc服务抛出异常),则可以直接降级。降级后的处理方案有:默认值(比如库存服务挂了,返回默认现货)、兜底数据(比如广告挂了,返回提前准备好的一些静态页面)、缓存(之前暂存的一些缓存数据)

(4)、限流降级

当我们去秒杀或者抢购一些限购商品时,此时可能会因为访问量太大而导致系统崩溃,此时开发者会使用限流来进行限制访问量,当达到限流阀值,后续请求会被降级;降级后的处理方案可以是:排队页面(将用户导流到排队页面等一会重试)、无货(直接告知用户没货了)、错误页(如活动太火爆了,稍后重试)。

 

服务熔断和服务降级比较:

两者其实从有些角度看是有一定的类似性的:
  1. 目的很一致,都是从可用性可靠性着想,为防止系统的整体缓慢甚至崩溃,采用的技术手段;
  2. 最终表现类似,对于两者来说,最终让用户体验到的是某些功能暂时不可达或不可用;
  3. 粒度一般都是服务级别,当然,业界也有不少更细粒度的做法,比如做到数据持久层(允许查询,不允许增删改);
  4. 自治性要求很高,熔断模式一般都是服务基于策略的自动触发,降级虽说可人工干预,但在微服务架构下,完全靠人显然不可能,开关预置、配置中心都是必要手段;
而两者的区别也是明显的:
  1. 触发原因不太一样,服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;
  2. 管理目标的层次不太一样,熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外围服务开始)
  3. 实现方式不太一样

服务降级要考虑的问题:

 1.核心和非核心服务

2.是否支持降级,降级策略

3.业务放通的场景,策略

 

Hystrix,该库旨在通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包(request collapsing,即自动批处理,译者注),以及监控和配置等功能。

 

(7)数据库分库分表

数据库的瓶颈

  • 单个库数据量太大;考虑多个库解决问题。
  • 单个数据库服务器压力过大、读写瓶颈;考虑多个库、读写分离解决问题。
  • 单个表数据量过大;考虑分表解决问题。

数据库切库与分库

对数据库的操作中读多写少,且读操作占用系统资源多,耗时长,适用多个分库进行负载均衡。详情查看: 自定义注解完成数据库切库

切库的基础及实际应用中,随着业务增加,并发增加,需做到读写分离;
自定义注解完成数据库切库-代码实现;另一种方式是在业务中直接定义两个数据库链接:主库连接和从库连接;更新数据时,读取主库连接,

支持多数据源:指一个项目里,同时可以访问多个不同的数据库。
原理:单个数据源在配置时会绑定一套mybatis配置,多个数据源时,不同的数据源绑定不同的mybatis配置就可以了,简单的思路就是让不同的数据源扫描不同的包,让不同的包下的mapper对应连接不同的数据源去处理逻辑。

数据库分表

分表的类型

  1. 横向(水平)分表(Horizontal Partitioning)
    这种形式分区是对表的行进行分割,通过这样的方式不同分组里面的物理列分割的数据集得以组合,从而进行个体分割(单分区)或集体分割(1个或多个分区)。所有在表中定义的列在每个数据集中都能找到,所以表的特性依然得以保持。
  2. 纵向(垂直)分表(Vertical Partitioning)
    这种分割方式一般来说是通过对表的垂直划分来减少目标表的宽度,使某些特定的列被划分到特定的分区,每个分区都包含了其中的列所对应的行。

    在数据库供应商开始在他们的数据库引擎中建立分区(主要是水平分区)时,DBA和建模者必须设计好表的物理分区结构,不要保存冗余的数据(不同表中同时都包含父表中的数据)或相互联结成一个逻辑父对象(通常是视图)。这种做法会使水平分区的大部分功能失效,有时候也会对垂直分区产生影响。

    分表的好处

  3. 性能的提升(Increased performance)- 在扫描操作中,如果MySQL的优化器知道哪个分区中才包含特定查询中需要的数据,它就能直接去扫描那些分区的数据,而不用浪费很多时间扫描不需要的地方了。需要举个例子?好啊,百万行的表划分为10个分区,每个分区就包含十万行数据,那么查询分区需要的时间仅仅是全表扫描的十分之一了,很明显的对比。同时对十万行的表建立索引的速度也会比百万行的快得多得多。如果你能把这些分区建立在不同的磁盘上,这时候的I/O读写速度就“不堪设想”;
  4. 对数据管理的简化(Simplified data management)- 分区技术可以让DBA对数据的管理能力提升。通过优良的分区,DBA可以简化特定数据操作的执行方式。例如:DBA在对某些分区的内容进行删除的同时能保证余下的分区的数据完整性(这是跟对表的数据删除这种大动作做比较的)。

(8)高可用的一些手段

  1. 任务调度系统分布式:elastic-job组件 + zookeeper;
  2. 主备切换:Apache curator + zookeeper分布式锁;
  3. 监控报警机制。详情参看: https://www.imooc.com/article/20891 业务相关监控报警系统

 

你可能感兴趣的:(多线程,java)