找回密码
 立即注册
首页 业界区 安全 线程池和高并发

线程池和高并发

荦绅诵 昨天 22:30
多线程Java创建线程的几种方式有哪些?常见有以下五种方式创建使用多线程:1)实现 Runnable 接口:

  • 实现 Runnable 接口的 run() 方法,使用 Thread 类的构造函数传入 Runnable 对象,调用 start() 方法启动线程。
  • 例子:Thread thread = new Thread(new MyRunnable()); thread.start();
2)继承 Thread 类:

  • 继承 Thread 类并重写 run() 方法,直接创建 Thread 子类对象并调用 start() 方法启动线程。
  • 例子:MyThread thread = new MyThread(); thread.start();
3)使用 Callable 和 FutureTask:

  • 实现 Callable 接口的 call() 方法,使用 FutureTask 包装 Callable 对象,再通过 Thread 启动。
  • 例子:FutureTask task = new FutureTask(new MyCallable()); Thread thread = new Thread(task); thread.start();
4)使用线程池(ExecutorService):

  • 通过 ExecutorService 提交 Runnable 或 Callable 任务,不直接创建和管理线程,适合管理大量并发任务。
  • 例子:ExecutorService executor = Executors.newFixedThreadPool(10); executor.submit(new MyRunnable());
5)CompletableFuture(本质也是线程池,默认 forkjoinpool):

  • Java 8 引入的功能,非常方便地进行异步任务调用,且通过 thenApply、thenAccept 等方法可以轻松处理异步任务之间的依赖关系。
  • CompletableFuture future1 = CompletableFuture.runAsync(() -> {});
 线程生命周期及五种状态
1.webp
 

1、New(初始化状态)

    用new语句创建的线程处于新建状态,此时它和其他Java对象一样,仅仅在堆区中被分配了内存。如:Thread t = new MyThread();2、Runnable(就绪状态)    当调用线程对象的start()方法,线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,Java虚拟机会为它创建方法调用栈和程序计数器。处于这个状态的线程位于可运行池中,等待获得CPU的使用权,并不是说执行了start()此线程立即就会执行。3、Running(运行状态)

    当就绪状态中的线程获得了CUP执行资源,执行run()中的代码,这样的线程我们称为运行状态的线程。4、Blocked(阻塞状态)    处于运行中的线程,由于某种原因放弃对cpu的使用权,处于阻塞状态,直到其进入就绪状态,才有机会再次被cpu调用进入运行状态。根据阻塞原因不同,阻塞分为三种:等待阻塞:运行状态中的线程执行wait方法,进入等待队列,等待阻塞;Java虚拟机就会把线程放到这个对象的等待池中;同步阻塞:线程获取同步锁失败(因为锁被其他线程占用),Java虚拟机就会把这个线程放到这个对象的锁池中;其他阻塞:通过调用sleep方法或者join方法或者发出I/O请求时,线程会进入阻塞状态,当sleep()状态超时,或者join()等待线程终止或者超时,或者I/O处理完毕,线程重新转入就绪状态;5、Terminated(终止状态)

    正常结束,线程执行完    异常退出    异常退出,除了程序有问题导致的异常的退出,还可以使用共享变量的方式(定义个boolean标识等)退出,或者Interrupt中断线程,抛出异常,捕获异常break,跳出循环状态;    调用stop(),会造成死锁,线程不安全,不建议使用 线程基本方法1、线程等待(wait)    调用该方法,线程进入waiting状态,只有等待另外的线程通知或被中断才会返回,调用wait()后,会释放对象锁,因为wait方法一般用在同步方法或者同步代码块中。2、线程睡眠(sleep)强迫一线程睡 N毫秒,sleep不会释放当前锁,导致线程进入Timed-wating状态。3、线程让步(yield)    yeild会使当前线程让出cpu执行时间片,与其他线程一起重新竞争cpu时间片,一般情况下,优先级高的先得到,但也不一定,有的系统对优先级不敏感。4、线程中断(interrupt)    在run内部根据thread.isIterrupted() 安全终止线程。调用一个线程的interrupt() 方法中断一个线程,并不是强行关闭这个线程,仅仅是改变了内部维护的中断标识位,是线程固有的一个标识位。可以调用static方法isIterrupted() 判定当前线程是否处于中断状态:    1)如果一个正常线程,调用interrupt() ,是不能被打断的,打印中断标志位置为true    2)如果一个sleep或者wait的线程,调用interrupt() ,方法则抛出InterruptedException( InterruptedException表示一个阻塞被中断了),线程的中断标志位会被复位成false;相当于用异常响应了这个中断,所以释放中断标志位。5、join(等待其他线程终止)    当前线程调用join(),则线程转为阻塞状态,eg:A线程中插入了B.join(),则B先执行,执行完,A线程继续执行;常见的是主线程生成并启动了子线程,需要用到子线程返回结果的场景;6、线程唤醒(notify)    Object类中的notify唤醒在此对象监视器上等待的单个线程;    notifyAll唤醒在此对象监视器上等待的所有线程;7、其他常用方法
方法功能方法名
 判断一个线程是否存活;isAlive()  
程序中活跃的线程数;activeCount()      
得到当前线程;currentThread()    
设置一个线程的优先级;setPriority()   
获取一个线程的优先级;getPriority()     
线程是否为守护线程;isDaemon()     
   线程池线程池的基本概念是,在应用程序启动时创建一定数量的线程,并将它们保存在线程池中。当需要执行任务时,从线程池中获取一个空闲的线程,将任务分配给该线程执行。当任务执行完毕后,线程将返回到线程池,可以被其他任务复用。线程池创建有两种方式,一种是Executors使用默认方法创建,另一种是通过ThreadPoolExecutor自定义,不推荐前者是因为前者的配置很多都是取得integer得最大值,很容易造成OOM。工作中就是需要以new ThreadPoolExecutor的方式创建线程池的,其余的不安全。    
2.png
使用线程池的一般步骤如下: 1、创建线程池:使用ThreadPoolExecutor类来创建线程池。    ExecutorService executor = Executors.newFixedThreadPool(5); // 创建固定大小的线程池 2、提交任务:将任务提交给线程池。    executor.execute(new MyRunnable()); // 提交Runnable任务    Future future = executor.submit(new MyCallable()); // 提交Callable任务,并返回Future对象3、关闭线程池:在不再需要线程池时,需要调用shutdown()方法来关闭线程池。这将停止接受新任务,并逐渐关闭线程池中的线程。    executor.shutdown();    线程池核心概念:   int                                          corePoolSize      核心线程数  int                                          maximumPoolSize 最大线程数  long                                       keepAliveTime     空闲线程最大存活时间  TimeUnit                                unit             时间单位,m,h,d  BlockingQueue workQueue          阻塞任务队列  ThreadFactory                        threadFactory      创建线程工厂       RejectedExecutionHandler    handler                拒绝策略ThreadPoolExecutor参数详解

我们可以通过下面的场景理解ThreadPoolExecutor中的各个参数;a客户(任务)去银行(线程池)办理业务,但银行刚开始营业,窗口服务员还未就位(相当于线程池中初始线程数量为0),于是经理(线程池管理者)就安排1号工作人员(创建1号线程执行任务)接待a客户(创建线程);在a客户业务还没办完时,b客户(任务)又来了,于是经理(线程池管理者)就安排2号工作人员(创建2号线程执行任务)接待b客户(又创建了一个新的线程);假设该银行总共就2个窗口(核心线程数量是2);紧接着在a,b客户都没有结束的情况下c客户来了,于是经理(线程池管理者)就安排c客户先坐到银行大厅的座位上(空位相当于是任务队列)等候,并告知他: 如果1、2号工作人员空出,c就可以前去办理业务;此时d客户又到了银行,(工作人员都在忙,大厅座位也满了)于是经理赶紧安排临时工(新创建的线程)在大堂站着,手持pad设备给d客户办理业务;假如前面的业务都没有结束的时候e客户又来了,此时正式工作人员都上了,临时工也上了,座位也满了(临时工加正式员工的总数量就是最大线程数),于是经理只能按《超出银行最大接待能力处理办法》(饱和处理机制)拒接接待e客户;最后,进来办业务的人少了,大厅的临时工空闲时间也超过了1个小时(最大空闲时间),经理就会让这部分空闲的员工人下班.(销毁线程)但是为了保证银行银行正常工作(有一个allowCoreThreadTimeout变量控制是否允许销毁核心线程,默认false),即使正式工闲着,也不得提前下班,所以1、2号工作人员继续待着(池内保持核心线程数量);2、详细解释  1)、 核心线程数    当线程是IO密集型时,主要消耗磁盘的读写性能,可以设置为2*n,n为当前服务器核数(比如8核16G的服务器设置为16,Runtime.getRuntime().availableProcessors()获取)    当线程是CPU密集型时,主要消耗cpu性能,设置为n+1  2)、最大线程数    当核心线程核消息队列都满了之后才会去创建最大线程,直到达到最大线程数,之后的线程就会执行拒绝策略  3)、 阻塞消息队列    ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小,读写用一把锁,性能较差;    LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;一般是用这个,指定了大小则限制具体大小,写核读分两把锁进行操作,所以性能较好    synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。    注意:当核心线程数满了之后,新线程会先存储在消息队列中,当消息队列也满了之后才会去创建最大线程,直到达到最大线程数,之后的线程就会执行拒绝策略  4)、 线程工厂    创建线程的类,可以用默认工厂,也可以自定义线程工厂实现 implements ThreadFactory类,实现newThread方法,自定义工厂的话可以设置线程名或者定义辅助线程  5)、拒绝策略    ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。    ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。    ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务    ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务 线程池的主要优点包括:1、重用线程:线程池会在内部维护一组可重用的线程,避免了频繁地创建和销毁线程的开销,提高了线程的利用率。2、控制并发度:线程池可以限制并发执行的线程数量,防止系统过载。通过调整线程池的大小,可以控制并发度,避免资源消耗过大。3、提供线程管理和监控:线程池提供了一些管理和监控机制,例如线程池的创建、销毁、线程状态的监控等,方便开发人员进行线程的管理和调试。4、提供任务队列:线程池通常会使用任务队列来存储待执行的任务,这样可以实现任务的缓冲和调度。 线程池的一些缺点包括:1、需要合理配置:线程池的性能和效果受到配置参数的影响,需要根据具体的应用场景和硬件环境来合理配置线程池的大小、任务队列的大小等参数。2、可能引发资源泄露:如果线程池中的线程长时间闲置而不被使用,可能会导致资源的浪费和泄露。3、可能引发死锁:在使用线程池时,如果任务之间存在依赖关系,可能会引发死锁问题,需要额外的注意和处理。 重点面试问题1、sleep和wait的区别:    1)、sleep属于Thread类,让出cpu,但是监控状态依然保持者,指定时间到了,就会恢复运行;    2)、wait属于Object类方法,释放对象锁,进入等待锁定池,需要notify()才能重新进入运行状态;2、wait()、notify() 释放锁问题:    1)、wait()会立刻释放synchronized(obj)中的obj锁,以便其他线程可以执行obj.notify()    2)、但是notify()不会立刻立刻释放sycronized(obj)中的obj锁,必须要等notify()所在线程执行完synchronized(obj)块中的所有代码才会释放这把锁.yield(),sleep()不会释放锁。 异步线程间数据同步传递的四种方式手动设置、线程池设置TaskDecorator、使用InheritableThreadLocal、使用TransmittableThreadLocal主线程
  1. // @description 用户上下文信息
  2. public class OauthContext {
  3.     private static  final  ThreadLocal<LoginVal> loginValThreadLocal=new ThreadLocal<>();
  4.     public static  LoginVal get(){
  5.         return loginValThreadLocal.get();
  6.     }
  7.     public static void set(LoginVal loginVal){
  8.         loginValThreadLocal.set(loginVal);
  9.     }
  10.     public static void clear(){
  11.         loginValThreadLocal.remove();
  12.     }
  13. }
复制代码
 
1、手动设置每执行一次异步线程都要分为两步:    1)、获取父线程的值    2)、将值设置到子线程,达到复用, 代码如下:
  1. public void handlerAsync() {
  2.         LoginVal loginVal = OauthContext.get();  //1. 获取父线程的loginVal
  3.         log.info("父线程的值:{}",OauthContext.get());
  4.         CompletableFuture.runAsync(()->{
  5.            OauthContext.set(loginVal);   //2. 设置子线程的值,复用
  6.            log.info("子线程的值:{}",OauthContext.get());
  7.         });
  8.     }
复制代码
 
2、线程池设置TaskDecoratorTaskDecorator是一个执行回调方法的接口,主要应用于传递上下文,或者提供任务的监控/统计信息。首先需要去实现它,代码如下:
  1. // @description 上下文装饰器
  2. public class ContextTaskDecorator implements TaskDecorator {
  3.     @Override
  4.     public Runnable decorate(Runnable runnable) {
  5.         LoginVal loginVal = OauthContext.get();  //获取父线程的loginVal
  6.         return () -> {
  7.             try {
  8.                 OauthContext.set(loginVal);  // 将主线程的请求信息,设置到子线程中
  9.                 runnable.run();  // 执行子线程
  10.             } finally {
  11.                 OauthContext.clear();  // 线程结束,清空这些信息,否则可能造成内存泄漏
  12.             }
  13.         };
  14.     }
  15. }
复制代码
这里我只是设置了LoginVal,实际开发中其他的共享数据,比如SecurityContext,RequestAttributes....
TaskDecorator需要结合线程池使用,实际开发中异步线程建议使用线程池,只需要在对应的线程池配置一下,代码如下:
  1. @Bean("taskExecutor")
  2. public ThreadPoolTaskExecutor taskExecutor() {
  3.         ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();
  4.         poolTaskExecutor.setCorePoolSize(xx);
  5.         poolTaskExecutor.setMaxPoolSize(xx);
  6.         poolTaskExecutor.setKeepAliveSeconds(xx);   // 设置线程活跃时间(秒)
  7.         poolTaskExecutor.setQueueCapacity(xx);  // 设置队列容量
  8.         poolTaskExecutor.setTaskDecorator(new ContextTaskDecorator()); //设置TaskDecorator,用于解决父子线程间的数据复用
  9.         poolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
  10.         poolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);  // 等待所有任务结束后再关闭线程池
  11.         return poolTaskExecutor;
  12.     }
复制代码
此时业务代码就不需要去设置子线程的值,直接使用即可,代码如下:
  1. public void handlerAsync() {
  2.         log.info("父线程的用户信息:{}", OauthContext.get());
  3.         //执行异步任务,需要指定的线程池
  4.         CompletableFuture.runAsync(()-> log.info("子线程的用户信息:{}", OauthContext.get()),taskExecutor);
  5.     }
复制代码
这里使用的是CompletableFuture执行异步任务,使用@Async这个注解同样是可行的。
注意:无论使用何种方式,都需要指定线程池 3、使用InheritableThreadLocal这种方案不建议使用,InheritableThreadLocal虽然能够实现父子线程间的复用,但是在线程池中使用会存在复用的问题,这种方案使用也是非常简单,直接用InheritableThreadLocal替换ThreadLocal即可,代码如下:
  1. // @description 用户上下文信息
  2. public class OauthContext {
  3.     private static  final  InheritableThreadLocal<LoginVal> loginValThreadLocal=new InheritableThreadLocal<>();
  4.     public static  LoginVal get(){
  5.         return loginValThreadLocal.get();
  6.     }
  7.     public static void set(LoginVal loginVal){
  8.         loginValThreadLocal.set(loginVal);
  9.     }
  10.     public static void clear(){
  11.         loginValThreadLocal.remove();
  12.     }
  13. }
复制代码
 
4、使用TransmittableThreadLocalTransmittableThreadLocal是阿里开源的工具,弥补了InheritableThreadLocal的缺陷,在使用线程池等会池化复用线程的执行组件情况下,提供ThreadLocal值的传递功能,解决异步执行时上下文传递的问题。 TransmittableThreadLocal原理TransimittableThreadLocal继承于InheritableThreadLocal,并实现TtlCopier接口,它里面只有一个copy方法。所以主要是对InheritableThreadLocal的扩展。在TransimittableThreadLocal中添加holder属性。这个属性的作用就是被标记为具备线程传递资格的对象都会被添加到这个对象中。要标记一个类,可以给这个类新增一个Type字段,还有一个方法就是将具备这种类型的的对象都添加到一个静态全局集合中,之后使用时,这个集合里的所有值都具备这个标记。应用代码是通过TtlExecutors工具类对线程池对象进行包装。工具类只是简单的判断,输入的线程池是否已经被包装过、非空校验等,然后返回包装类ExecutorServiceTtlWrapper。根据不同的线程池类型,有不同和的包装类。进入包装类ExecutorServiceTtlWrapper。可以注意到不论是通过ExecutorServiceTtlWrapper#submit方法或者是ExecutorTtlWrapper#execute方法,都会将线程对象包装成TtlCallable或者TtlRunnable,用于在真正执行run方法前做一些业务逻辑。所以,重点的核心逻辑应该是在TtlCallable#call()或者TtlRunnable#run()中。以下以TtlCallable为例,TtlRunnable同理类似。总的来说在创建TtlCallable对象是,调用capture()方法捕获调用方的本地线程变量,在call()执行时,将捕获到的线程变量,替换到线程池所对应获取到的线程的本地变量中,并且在执行完成之后,将其本地变量恢复到调用之前。  高并发
3.png
高并发系统系统指标1、QPS(Queries Per Second)QPS 是每秒请求数,是衡量信息系统在一秒钟内接收到的搜索流量的一种常见度量指标,被广泛应用在任何请求-响应的系统中。对于高并发的系统,必须要关注 QPS,这样你才能指导你的系统何时需要进行扩容。2、TPS(Transactions Per Second)TPS 是每秒的事务数量,是软件测试结果的测量单位,一个事务是指一个客户端向服务器发送请求然后服务器做出响应的过程。客户端在发送请求时开始计时,收到服务器响应后结束计时,用来计算使用的时间和完成的事务个数。         QPS VS TPS            QPS 基本可以理解为类似于 TPS,但不同的是,对于一个页面的一次访问,形成一个 TPS,但一次页面请求,可能会产生多次对服务器的请求,就是说会有多个 QPS。3、RT(Response-time)RT 即响应时间,是指一个请求从开始到最后收到响应数据结果所花费的总时间,它的数值大小直接反应了系统的快慢。4、并发数并发数是指系统同时能处理的请求数量,这个指标反应了系统的负载能力。并发意味着可以同时进行多个处理,并发在现代编程中无处不在。5、吞吐量系统的吞吐量和处理对 CPU 的消耗、外部接口、IO 等多个因素紧密相关,单个处理请求对 CPU 消耗越高,外部系统接口、IO 速度越慢,系统的吞吐能力越低,反之越高。系统吞吐量有几个重要指标参数:QPS/TPS、并发数、响应时间。     QPS/TPS:每秒请求/事务数量。    并发数: 系统同时处理的请求/事务数。    响应时间: 一般取请求平均响应时间。理解了上面三个指标的意义之后,就能推算出它们之间的关系:QPS(TPS) = 并发数/平均响应时间并发数 = QPS*平均响应时间 实际举例某宝直播业务要求预估计算对应的各个指标。正常在进行流量估算的时候,会采用二八定律,如果每天 80% 的访问集中在 20% 的时间段里,那这个 20% 的时间就被称为峰值时间。        峰值时间的 QPS = (总 PV 数 * 80%)/(每天秒数 * 20%)        需要的机器数量 = 峰值时间的 QPS / 单台机器的 QPS每天300w PV 的在单台机器上,这台机器需要多少QPS?    ( 3000000 * 0.8 ) / (86400 * 0.2 ) = 139 (QPS)如果一台机器的QPS是58,需要几台机器来支持?    139 / 58 = 3高并发系统的目标从系统层面看,有高性能、高可用和高扩展三个目标 高并发解决方案使用分布式微服务架构模式、集群部署与负载均衡、用分布式缓存、使用消息队列、分库分表、读写分离、限流和熔断、动静分离、使用分布式数据库、数据库优化。1、使用分布式微服务架构模式将一个系统拆分为多个子系统。 微服务架构拆分,最常见的就是Spring Cloud 和Spring Cloud Alibaba。然后每个系统连一个数据库,这样本来就一个库,现在多个数据库,这样就可以抗高并发。2、集群部署与负载均衡将系统部署在多台服务器上,通用负载均衡将用户请求均匀地分发到各个服务器进行处理, 分担单台服务器的压力,这种方式可以通过添加新的服务器或者服务节点来提高系统的整体处理能力, 提高系统的并发处理能力和整体性能。负载均衡(Load Balancing)是一种分布式系统架构中的技术,用于将网络请求或任务分散到多个服务器或资源上。比如:当系统面临大量用户访问,负载过高的时候,通常会使用增加服务器数量来进行横向扩展来提高整个系统的处理能力。负载均衡可以在不同的层次上实现,包括:            硬件负载均衡器: 使用专门的硬件设备来实现负载均衡,如硬件负载均衡器。            软件负载均衡器: 在应用层或网络层使用软件来实现负载均衡,如反向代理服务器、负载均衡算法。在负载均衡的设计中,有几种常见的负载均衡策略:    1、轮询(Round Robin): 将请求依次分配给服务器列表中的每个服务器,每次请求后移动到下一个服务器。适用于服务器性能相近的情况。      2、加权轮询(Weighted Round Robin): 类似于轮询,但每个服务器有不同的权重,可以根据服务器性能调整权重。      3、最少连接(Least Connections): 将请求分配给当前连接数最少的服务器,以确保负载均衡。适用于长连接的情况。      4、加权最少连接(Weighted Least Connections): 类似于最少连接,但每个服务器有不同的权重,可以根据服务器性能调整权重。      5、随机(Random): 随机选择一个服务器来处理请求,适用于简单的负载均衡需求。      6、IP 哈希(IP Hash): 根据客户端 IP 地址的哈希值来选择服务器,可以确保同一客户端的请求始终发送到同一服务器。3、分布式缓存大部分的高并发场景,都是读多写少,那你完全可以在数据库和缓存里都写一份,然后读的时候大量走缓存不就得了。毕竟人家redis轻轻松松单机几万的并发啊。没问题的。所以你可以考的虑考虑你的项目里,那些承载主要请求读场景,怎么用缓存来抗高并发。(缓存你也可以考虑集群,不过前提就是你的服务器得支持怎么多高负荷的中间件。俗话说没有任何事物加一层解决不了的,如果一层不行,就在加一层解决)一些常见的分布式缓存系统包括:;    1)、Redis: Redis 是一种基于内存的键值存储系统,支持多种数据结构,如字符串、哈希、列表等,适用于快速读取和写入场景。      2)、Memcached: Memcached 也是一种基于内存的键值存储系统,适用于分布式缓存和缓存共享。    3)、Hazelcast: Hazelcast 是一个开源的分布式数据存储和计算平台,支持分布式缓存、分布式计算等。    4)、Couchbase: Couchbase 是一个分布式缓存和数据库系统,结合了缓存和文档存储的功能。    5)、Ehcache: Ehcache 是一个 Java 缓存库,可以作为本地缓存或分布式缓存使用。当然,这里使用最多的还是Redis。4、MQ(消息队列)可能你还是会出现高并发写的场景,比如说一个业务操作里要频繁搞数据库几十次,增删改增删改,疯了。那高并发绝对搞挂你的系统,人家是缓存你要是用redis来承载写那肯定不行,数据随时就被LRU(淘汰掉最不经常使用的)了,数据格式还无比简单,没有事务支持。所以该用mysql还得用mysql啊。那你咋办?用MQ吧,大量的写请求灌入MQ里,排队慢慢玩儿,后边系统消费后慢慢写,控制在mysql承载范围之内。所以你得考虑考虑你的项目里,那些承载复杂写业务逻辑的场景里,如何用MQ来异步写,提升并发性。MQ单机抗几万并发也是ok的。(这个是根据自己的业务来用的,一般中小型项目用rabbitmq就够了,建议日志用kafka,这样日志的数量多就可以很好的解决) 5、分库分表可能到了最后数据库层面还是免不了抗高并发的要求,好吧,那么就将一个数据库拆分为多个库,多个库来抗更高的并发;然后将一个表拆分为多个表,每个表的数据量保持少一点,提高sql跑的性能。(这个前提你的服务器得支持,不然弄得华丽花哨)1.什么是分库分表?1.1 分库分库是指在表数量不变的情况下对库进行切分。举例:如下图,数据库A 中存放了 user 和 order 两张表,将两张表切分到两个数据库中,user表放到 database A,order表放到 database B1.2 分表分表是指在库数量不变的情况下对表进行切分。举例:如下图,数据库 A 中存放了 user表,将 user表切分成 user1 和 user2 两张表并放到 database A中。1.3 分库分表分库分表是指库和表都切分,数量都发生变化。举例:如下图,数据库 A 中存放了 user表,将 user表切分成 user1、user2、user3、user4 四张表,user1 和 user2 放到 database A中,user3 和 user4 放到 database B 中。2. 如何切分库和表?主流的切分方式有 3种:水平切分、垂直切分和混合切分。2.1 水平切分水平切分包含水平分库和水平分表。2.1.1 水平分表水平分表指的表结构不变,将单表数据切分成多表。切分后的结果:每个表的结构一样,数据不一样,所有表的数据并集为全量数据。举例:如下图,order表,按照 oder_id 的数据范围水平切分后变成了 order1 和 order2 表,两个表的结构一样,数据不同。2.1.2 水平分库水平分库是指,将表水平切分后分到不同的数据库,使得每个库具有相同的表,表中的数据不相同,水平分库一般是伴随水平分表。举例:如下图,order 表,水平切分后,分到 database A 和 database B 中,这样原来一个库就被拆分成 2个库。2.2 垂直切分垂直切分包含垂直分库和垂直分表。2.2.1 垂直分表垂直分表指将存在一张表中的字段切分到多张表。切分后的结果:每个表的结构不一样,数据也不一样,所有表的字段并集是原表的字段。举例:如下图,order 表,根据字段垂直切分,切分后 order_base表包含一部分字段的数据 和 order_info表包含另一部分字段的数据。2.2.2 垂直分库垂直分库指的是,将单个库中的表分到多个库,每个库包含的表不一样。举例:如下图,database A 中的 order 表 和 user表,垂直分库为 database A 包含 order表,database B 包含 user 表。2.3 混合切分混合切分其实就是水平切分和垂直切分的组合6、读写分离这个就是说大部分时候数据库可能也是读多写少,没必要所有请求都集中在一个库上吧,可以搞个主从架构,主库写入,从库读取,搞一个读写分离。读流量太多的时候,还可以加更多的从库。 7、限流和熔断限流(Rate Limiting)和熔断(Circuit Breaking)是分布式系统中常用的两种流量控制和容错机制。并发量高的情况下,利用SpringCloud的Hystrix组件(监控和熔断器)进行限流熔断处理, 首先保护核心系统的安全性,
4.png
 8、动静分离将css、html、jpg、 js 等 静态文件和动态页面区分开 9、使用分布式数据库分布式数据库是一种数据库系统,将数据分散存储在多个物理节点或服务器上,以提高系统的性能、可扩展性和可用性。分布式数据库分类:1.分布式关系型数据库这类数据库将关系型数据库的特性与分布式系统的优势相结合,提供了支持SQL查询和事务的能力。一些例子包括:Google Cloud Spanner、TiDB一种全球分布式的关系型数据库,提供了强一致性和水平扩展能力。2.分布式列式数据库这类数据库以列式存储方式存储数据,适用于大规模分析和查询需求。Apache Cassandra:一个分布式的NoSQL数据库,适用于高可用性和可扩展性的场景。HBase:一个基于Hadoop的分布式列式数据库,适用于大数据存储和实时查询。3.分布式文档数据库这类数据库以文档为单位存储数据,适用于半结构化数据。MongoDB:一个面向文档的NoSQL数据库,支持分布式部署和水平扩展。Couchbase:一个分布式NoSQL数据库,支持文档和键值数据模型。使用分布式数据库系统,如分布式 NoSQL 数据库,来提高数据存储和查询的并发处理能力。10、数据库优化优化数据库的设计、索引、查询语句等,提高数据库的读写性能。综合运用上述高并发架构解决方案,都可以构建出具有高性能、高可用和可扩展性的系统,满足大量并发请求的需求。 
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册