• 首页

  • 文章归档

  • 隔壁朋友

  • 关于博主
H B L A O
H B L A O

hblao

获取中...

02
23
java

Java-笔记-2021年(持续更新)

发表于 2021-02-23 • java 笔记 • 被 144 人看爆

1.同步:客户端发起请求,服务端处理请求将结果响应给客户端,该过程是顺序执行
异步:客户端发起请求,服务端收到请求开始执行,客户端此时不需要等待服务端响应结果,可以执行其他操作,待服务端处理完毕刷新结果到客户端

2.JSON字符串和java对象的互转使用:json-lib
1.对象转json/json数组:JSONObject.fromObject(stu)/JSONArray.fromObject(stu);
2.json/json数组转对象:JSONObject.fromObject(objectStr)拿到jsonObject对象,然后用JSONObject.toBean强转成实体对象:(Student)JSONObject.toBean(jsonObject, Student.class)。使用JSONArray.fromObject(arrayStr),获得jsonArray的第一个元素jsonArray.get(0),再强转成实体对象

3.Object类是所有类的父类,常见的方法有toString(),equals(Object obj),若使用object原有的这两个方法分别针对的是内存地址,若打印对象字符串需要重写tostring方法,若比较对象内容需要重写equals方法。"=="是判断两个变量或实例是不是指向同一个内存空间。"equals"是判断两个变量或实例所指向的内存空间的值是不是相同。

4.String、StringBuffer与StringBuilder之间区别
String:String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,不仅效率低下,而且浪费大量优先的内存空间。每次返回的都是新字符串
StringBuffer是可变类,和线程安全的字符串操作类,任何对它指向的字符串的操作都不会产生新的对象。每个StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量。线程安全,多线程操作字符串。StringBuffer的方法就加了synchronized 也就是我们说的线程安全
StringBuilder是可变类,速度更快,线程不安全, 单线程操作字符串。返回的都是this

5.包装类
基本类型:byte,short,int,long,float,double,char,boolean
包装类型:Byte,Short,Integer,Float,Double,Chartacter,Boolean,Long
装箱:从基本类型转换为对应的包装类对象,拆箱:从包装类对象转换为对应的基本类型。jdk1.5以后自动装箱自动拆箱。String
注意:String不是基本的数据类型,是final修饰的java类,属于引用类型

6.String:split方法返回的是一个string数组。substring方法两个参数:从第几位开始(包含),到多少位(不包含)

7.static关键字
静态方法调用的注意事项: 静态方法可以直接访问类变量和静态方法。 静态方法不能直接访问普通成员变量或成员方法。反之,成员方法可以直接访问静态变量或静态方法。 静态方法中,不能使用this关键字(因为this,supper代表本类/父类,而静态方法优先于类加载)。
静态内容存储:是随着类的加载而加载的,且只加载一次。 存储于一块固定的内存区域(静态区),所以,可以直接被类名调用。 它优先于对象存在,所以,可以被所有对象共享。
静态代码块:定义在成员位置,使用static修饰的代码块{ }。

8.异常
try catch里面内嵌try catch,注意:内层catch处理了异常,所以没有执行外层catch
方法内主动抛出的异常:throw new Exception("远程调用接口返回空HttpResponse");若是想在本方法内处理异常,则需要try catch 包住这段代码,若是不想处理,则抛出到上层处理,需在该方法后添加 throws Exception
注意:用 @Async异步注解后,调用函数捕获不到异常,spring事务回滚也不起作用,原因是异步线程池会启用多个Task,这样事务里面的数据库操作 就不是一个任务了,事务不起作用。

9.并发与并行, 线程与进程
并发:指两个或多个事件在同一个时间段内发生。 并行:指两个或多个事件在同一时刻发生(同时发生)。
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多 个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创 建、运行到消亡的过程。 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程 中是可以有多个线程的,这个应用程序也可以称之为多线程程序。 总结:一个程序运行后至少有一个进程,一个进程中可以包含多个线程

11.线程创建的两种形式:
创建Thread子类,继承Thread类,并重写该类的run()方法,调用线程对象的start()方法来启动该线程
创建Runnable实现类的实例,实现义Runnable接口,并重写该接口的run()方法,调用线程对象的start()方法来启动线程
实现Runnable接口比继承Thread类所具有的优势: 1. 适合多个相同的程序代码的线程去共享同一个资源。 2. 可以避免java中的单继承的局限性。 3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。 4. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
每当使用 java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进程,进程中包含两个线程:一个是main线程,一个是垃圾收集线程

12.继承和实现:类与类之间是继承关系,类与接口之间是实现关系,接口是抽象的,接口中的属性都默认为static和final。都可以重写被继承或被实现的公共方法

13.线程安全
线程安全的前提:有多个线程同时在访问共享的变量或资源,操作共享的变量或资源有多行代码并有写的操作
线程同步:
同步代码块:synchronized(同步锁){ 需要同步操作的代码 },注意同步锁可以是Object对象:Object lock = new Object();多个线程必须使用同一把锁
同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。public synchronized void method(){ 可能会产生线程安全问题的代码 }。同步锁是谁? 对于非static方法,同步锁就是this。 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
Lock锁:Lock锁也称同步锁,Lock lock = new ReentrantLock();//创建锁对象。lock.lock();//加锁。lock.unlock();//解锁,注意需要同步的代码写在加锁和解锁中间即可

14.线程状态:

  1. 新建状态(New): 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
  2. 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
  3. 运行状态(Running): 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
  4. 阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种: (1) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。 (2) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。 (3) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
  5. 死亡状态(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
    注意:线程直接通信:wait是线程等待;notify是线程唤醒, wait方法与notify方法必须要由同一个锁对象调用, wait方法与notify方法是属于Object类的方法的,wait方法与notify方法必须要在同步代码块或者是同步函数中使用

15.线程池
线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作, 无需反复创建线程而消耗过多资源。线程池接口是 java.util.concurrent.ExecutorService
线程池有两种使用方式:
第一种:
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.MINUTES, new SynchronousQueue());
poolExecutor.submit(new PoolThreadSimple.TestRunnable(index));//执行线程
五个参数解释如下:
corePoolSize: 线程池核心线程数
maximumPoolSize: 线程池中允许的最大线程数
keepAliveTime: 线程处于空闲状态时, 多长时间销毁
unit: keepAliveTime的单位
workQueue: 缓存队列, 线程数达到核心线程数时, 任务被放入缓冲队列
第二种:
ExecutorService service = Executors.newFixedThreadPool(2);//创建线程池对象包含2个线程对象
service.submit(r);//从线程池中获取线程对象,然后调用MyRunnable中的run()

16.jpa
JPA 是基于 Hibernate 的一层封装
MyBatis 支持通过 XML 或注解的方式来配置需要运行的 SQL 语句,并且,最终由框架本身将 Java 对象和 SQL 语句映射生成最终执行的 SQL ,执行后,再将结果映射成 Java 对象返回。
Hibernate 对象/关系映射能力强,能做到数据库无关性。如果用 Hibernate 开发,无需关系 SQL 编写(不会写 SQL 的人都可以操作数据库),能节省很多代码,提高效率。但是 Hibernate 的缺点是学习门槛高,要精通门槛更高,而且怎么设计 O/R 映射,在性能和对象模型之间如何权衡,以及怎样用好 Hibernate 需要具有很强的经验和能力才行。
JPA相当于是jdbc,在Hibernate基础上封装比较原生。Spring Data JPA是Spring提供的一套对JPA操作更加高级的封装,是在JPA规范下的专门用来进行数据持久化的解决方案(提供了增删改查)。

17.springboot
Spring优缺点分析:
Spring是Java企业版J2EE的轻量级代替品,Spring为企业级Java开发提供了一种相对简单的方法,通过依赖注入和面向切面编程,用简单的Java对象实现了EJB的功能。Spring用XML配置,的配置却是重量级的,spring有Spring JDBC 、Spring MVC 、Spring Security、 Spring AOP 、Spring ORM 、Spring Test模块。通常搭建一个基于spring的web应用,我们需要做以下工作:1、pom文件中引入相关jar包,包括spring、springmvc、redis、mybaits、log4j、mysql-connector-java 等等相关jar;2、配置web.xml,Listener配置、Filter配置、Servlet配置、log4j配置、error配置;3、配置数据库连接、配置spring事务;4、配置完成后部署tomcat、启动调试
Spring Boot是Spring框架的扩展,它消除了设置Spring应用程序所需的XML配置,开发,测试和部署更快,更高效。创建独立的spring应用。嵌入Tomcat, Jetty容器支持。提供的spring-boot-starter-web poms来简化Maven配置。自动配置。没有XML配置要求。使用命令java -jar独立运行jar。

18.SpringBoot原理分析
pom引入:spring-boot-starter-parent,坐标的版本、依赖管理、插件管理已经定义好,所以我们的SpringBoot工程继承spring-boot-starter-parent后已经具备版本锁定等配置了。所以起步依赖的作用就是进行依赖的传递
pom引入:spring-boot-starter-web,starter-json,starter-tomcat,validator,spring-web,spring-webmvc等坐标进行了打包,引入spring-boot-starter-web起步依赖的坐标就可以进行web开发
springboot的启动类入口:启动类上带有@SpringBootApplication,启动main方法执行SpringApplication.run,加载springboot框架
@SpringBootApplication注解点进去是一个SpringBootApplication接口,该接口上有三大注解:@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan
@SpringBootConfiguration:内部是包含了@Configuration,这是Spring定义配置类的注解,而@Configuration实际上就是一个@Component
@EnableAutoConfiguration:这个注解的作用是告诉SpringBoot开启自动配置功能。该注解里面有两个核心注解分别是:@AutoConfigurationPackage,内部是采用了@Import,来给容器导入一个Registrar组件,作用是加载启动类所在包下面的所有类,这就是为什么,默认情况下,我们要求定义的类,比如controller,service必须在启动类的同级目录或子级目录的原因。@Import(AutoConfigurationImportSelector.class),作用是加载了自动配置类,加载每个场景所需的所有组件,并配置好这些组件
@ComponentScan:用于类或接口上主要是指定扫描路径,spring会把指定路径下带有指定注解的类自动装配到bean容器里。会被自动装配的注解包括@Controller、@Service、@Component、@Repository等等。其作用等同于<context:component-scan base-package="com.maple.learn" />配置

19.SpringBoot配置文件
SpringBoot配置文件加载顺序:优先加载根目录下config文件夹配置文件,再加载外层配置文件;classpath资源层也是优先加载带config层下配置文件,再加载外层配置文件;配置文件遵循properties的优先级高于yml。
properties文件中是以”.”进行分割的, 在yml中是用”:”进行分割;
配置文件赋值:
通过@Value注解将配置文件中的值映射到一个Spring管理的Bean的字段上,进行对象属性赋值
也可以通过注解@ConfigurationProperties(prefix="配置文件中的key的前缀")可以将配置文件中的配置自动与实体进行映射,如:@ConfigurationProperties(prefix = "person")

20.SpringBoot整合资源
SpringBoot整合Mybatis:1.添加Mybatis的起步依赖;2.添加数据库驱动坐标;3.配置文件添加数据库连接信息;4.创建user表;5.创建实体Bean;6.编写Mapper;7.配置Mapper映射文件(xml);8.在application.properties中添加mybatis的信息(mybatis.type-aliases-package=com.itheima.domain//pojo扫描;mybatis.mapper-locations=classpath:mapper/*Mapper.xml//加载Mybatis映射文件);
SpringBoot整合Junit:1.添加Junit的起步依赖spring-boot-starter-test;2.编写测试类(测试类上要加两个注解:@RunWith(SpringRunner.class) @SpringBootTest(classes = MySpringBootApplication.class));
SpringBoot整合Spring Data JPA:1.添加Spring Data JPA的起步依赖;2.添加mysql数据库驱动依赖;3.在application.properties中配置数据库和jpa的相关属性;4.创建实体配置实体(注意id要配置好主键和自增长);5.编写UserRepository接口,接口需要extends JpaRepository<User,Long>;6.编写测试类;
SpringBoot整合Redis:1.添加redis的起步依赖;2. 在配置文件中配置redis的连接信息;3.注入RedisTemplate测试redis操作@Autowired private RedisTemplate<String, String> redisTemplate。如果springboot里面自动配置的RedisTemplate不好用可以自己手动配置一个RedisConfig进行实例化再写一个RedisUtil工具类,在工具类中注入手写的RedisTemplate更好使用

21.Spring Boot健康检查与应用监控

  1. Actuator:Spring Boot Actuator 可以帮助你监控和管理 Spring Boot 应用,比如健康检查、审计、统计和HTTP追踪等。所有的这些特性可以通过 JMX 或者 HTTP endpoints 来获得。
    搭建应用:创建 Spring Boot 项目,选择 Spring Boot Actuator 组件,配置management.endpoints.web.base-path=/actuator,访问:访问:http://localhost:8080/actuator
  2. Spring Boot Admin:第三方组件实现可视化的项目监控比如:Spring Boot Admin
    搭建服务端:创建 Spring Boot 项目,选择 Spring Boot Admin Server 组件。启动类需要添加 @EnableAdminServer 注解。访问:http://localhost:8769/
    搭建客户端:创建 Spring Boot 项目,选择 Spring Boot Admin Client 组件。使用刚才的 Actuator 项目当作客户端使用。指定服务端的访问地址spring.boot.admin.client.url=http://localhost:8769。配置日志文件:management.endpoint.logfile.external-file=C:/logs/output.log或logging.file.path=C:/logs/output.log,在核心配置文件同级目录增加logback.xml日志文件,访问admin服务端打开应用墙点击日志文件即看到实时日志信息,具体参看这个:https://www.freesion.com/article/9364911851/

22.技术架构演变历史
单一应用架构
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的 数据访问框架(ORM) 是关键。缺点:随着应用功能的增多,代码量越来越大,越来越难维护,那怎么解决代码一体化的问题?
垂直应用架构
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的 Web框架(MVC) 是关键。缺点:垂直架构中相同逻辑代码需要不断的复制,不能复用。每个垂直模块都相当于一个独立的系统。
分布式服务架构
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的 分布式服务框架(RPC) 是关键。缺点:服务越来越多,需要管理每个服务的地址,调用关系错综复杂,难以理清依赖关系,服务状态难以管理,无法根据服务情况动态管理。
流动计算架构
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的 资源调度和治理中心(SOA) 是关键。缺点:服务间会有依赖关系,一旦某个环节出错会影响较大,服务关系复杂,运维、测试部署困难,不符合DevOps思想。
微服务架构:
单一职责:微服务中每一个服务都对应唯一的业务能力,做到单一职责微:微服务的服务拆分粒度很小,例如一个用户管理就可以作为一个服务。每个服务虽小,但“五脏俱全”。面向服务:面向服务是说每个服务都要对外暴露服务接口API。并不关心服务的技术实现,做到与平台和语言无关,也不限定用什么技术实现,只要提供 Rest 的接口即可。
自治:自治是说服务间互相独立,互不干扰
团队独立:每个服务都是一个独立的开发团队,人数不能过多。
技术独立:因为是面向服务,提供 Rest 接口,使用什么技术没有别人干涉。
前后端分离:采用前后端分离开发,提供统一 Rest 接口,后端不用再为 PC、移动端开发不同接口。
数据库分离:每个服务都使用自己的数据源部署独立,服务间虽然有调用,但要做到服务重启不影响其它服务。有利于持续集成和持续交付。每个服务都是独立的组件,可复用,可替换,降低耦合,易维护 Docker 部署服务

23.微服务
微服务就是将一个单体架构的应用按业务划分为一个个的独立运行的程序即服务,它们之间通过HTTP 协议进行通信(也可以采用消息队列来通信,如 RabbitMQ,Kafaka 等),可以采用不同的编程语言,使用不同的存储技术,自动化部署(如 Jenkins)减少人为控制,降低出错概率。服务数量越多,管理起来越复杂,因此采用集中化管理。
Spring Cloud 是一个服务治理平台,提供了一些服务框架。包含了:服务注册与发现、配置中心、消息中心 、负载均衡、数据监控等等。
Spring Cloud 是一个微服务框架,相比 Dubbo 等 RPC 框架,Spring Cloud 提供了全套的分布式系统解决方案。
Spring Cloud 是一个基于 Spring Boot 实现的云应用开发工具,它为开发中的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式。

24.Eureka 注册中心
服务注册中心的作用就是服务的注册和服务的发现。是需要在注册中心找到服务和服务地址的映射关系。Eureka 是 Netflix 开发的服务发现组件,本身是一个基于 REST 的服务。实现 Spring Cloud 的服务注册与发现。
Eurka 工作流程:1、Eureka Server 启动成功,等待服务端注册。2、Eureka Client 启动时根据配置的 Eureka Server 地址去注册中心注册服务。3、Eureka Client 会每 30s 向 Eureka Server 发送一次心跳请求,证明客户端服务正常。4、当 Eureka Server 90s 内没有收到 Eureka Client 的心跳,注册中心则认为该节点失效,会注销该实例。5、单位时间内 Eureka Server 统计到有大量的 Eureka Client 没有上送心跳,则认为可能为网络异常,进入自我保护机制。6、当 Eureka Client 心跳请求恢复正常之后,Eureka Server 自动退出自我保护模式。7、Eureka Client 定时全量或者增量从注册中心获取服务注册表,并且将获取到的信息缓存到本地。8、服务调用时,Eureka Client 会先从本地缓存找寻调取的服务。如果获取不到,先从注册中心刷新注册表,再同步到本地缓存。9、Eureka Client 获取到目标服务器信息,发起服务调用。
单台注册中心:注意的地方:关闭自己注册自己服务,关闭自己拉取自己服务列表,暴露自己注册中心地址:service-url.defaultZone: http://localhost:$
/eureka/
高可用 Eureka 注册中心:多台注册中心之间相互注册

25.Ribbon负载均衡
负载均衡方案可分成两类:集中式负载均衡:服务器负载均衡,如nginx。进程内负载均衡:客户端负载均衡:将负载均衡逻辑集成到 consumer,consumer 从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的 provider。如Ribbon。
Ribbon 是一个基于 HTTP 和 TCP 的 客服端 负载均衡工具,不需要单独部署,存在每个微服务中,Ribbon 默认提供很多种负载均衡算法,例如轮询、随机等等。
Ribbon 负载均衡策略:
轮询策略(默认):轮询策略表示每次都顺序取下一个 provider,比如一共有 5 个 provider,第 1 次取第 1个,第 2 次取第 2 个,第 3 次取第 3 个,以此类推。
权重轮询策略:一开始为轮询策略,并开启一个计时器,每 30 秒收集一次每个 provider 的平均响应时间,当信息足够时,给每个 provider 附上一个权重,并按权重随机选择 provider,高权越重的 provider会被高概率选中。
随机策略:从 provider 列表中随机选择一个。
修改配置文件指定服务的负载均衡策略。格式:service-provider: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

26.Feign声明式服务调用
Feign 是一个轻量级 RESTful 的 HTTP 服务客户端,实现了负载均衡和Rest调用的开源框架,封装了 Ribbon 和 RestTemplate,实现了 WebService 的面向接口编程,进一步降低了项目的耦合度。
Feign 旨在使编写 JAVA HTTP 客户端变得更加容易,Feign 简化了 RestTemplate 代码,实现了Ribbon 负载均衡,使代码变得更加简洁,也少了客户端调用的代码,使用 Feign 实现负载均衡是首选方案。只需要你创建一个接口,然后在上面添加注解即可。
Feign 的使用主要分为以下几个步骤:服务消费者添加 Feign 依赖;创建业务层接口,添加 @FeignClient 注解声明需要调用的服务;业务层抽象方法使用 SpringMVC 注解配置服务地址及参数;启动类添加 @EnableFeignClients 注解激活 Feign 组件。

27.Hystrix熔断
雪崩效应:如客户端访问 A 服务,而 A 服务需要调用 B 服务,B 服务需要调用 C 服务,由于网络原因或者自身的原因,如果 B 服务或者 C 服务不能及时响应,A 服务将处于阻塞状态,直到 B 服务 C 服务响应。此时若有大量的请求涌入,容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,造成连锁反应,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。
雪崩效应解决方案:
请求缓存:支持将一个请求与返回结果做缓存处理;
请求合并:将相同的请求进行合并然后调用批处理接口;
服务隔离:限制调用分布式服务的资源,某一个调用的服务出现问题不会影响其他服务调用;
服务熔断:牺牲局部服务,保全整体系统稳定性的措施;
服务降级:服务熔断以后,客户端调用自己本地方法返回缺省值

28.Gateway服务网关
Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关,提供统一的路由方式,还基于 Filter 链的方式提供了网关基本的功能。旨在提供一种简单而有效的方法来路由到 API。
API 网关是一个服务器,是系统对外的唯一入口。主要起到 隔离外 部访问与内部系统的作用。
API 网关也有一系列的好处:客户端与后端的耦合度降低,提供安全、流控、过滤、缓存、计费、监控等 API 管理功能。易于监控,易于认证,减少了客户端与各个微服务之间的交互次数
Spring Cloud Gateway:
工作原理:客户端向 Spring Cloud Gateway 发出请求。再由网关处理程序 Gateway Handler Mapping 映射确定与请求相匹配的路由,将其发送到网关 Web 处理程序 Gateway Web Handler 。该处理程序通过指定的过滤器链将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器由虚线分隔的原因是,过滤器可以在发送代理请求之前和之后运行逻辑。所有 pre 过滤器逻辑均被执行。然后发出代理请求。发出代理请求后,将运行 post 过滤器逻辑。

路由(Route):路由信息由 ID、目标 URL、一组断言和一组过滤器组成。如果断言路由为真,则说明请求的 URL 和配置匹配。路由配置规则:spring.cloud.gateway.routes:

  • id: product-service # 路由 ID,唯一
    uri: http://localhost:7070/ # 目标 URI,路由到微服务的地址
    predicates: # 断言(判断条件)

断言(Predicate):断言函数允许开发者去定义匹配来自于 Http Request 中的任何信息,比如请求头和参数等。断言判断条件spring.cloud.gateway.routes.predicates:

  • Path=/product/** # 匹配对应 URL 的请求,将匹配到的请求追加在目标uri后面
  • Query=token # 匹配请求参数中包含 token 的请求
  • Method=GET # 匹配任意 GET 请求
  • After=2020-02-02T20:20:20.000+08:00[Asia/Shanghai] # 匹配中国上海时间 2020-02-02 20:20:20 之后的请求
  • RemoteAddr=192.168.10.1/0 # 匹配远程地址请求是 RemoteAddr 的请求,0表示 子网掩码
  • Header=X-Request-Id, \d+ # 匹配请求头包含 X-Request-Id 并且其值匹配正则表达式 \d+ 的请求

动态获取uri: lb://product-service # lb:// 根据服务名称从注册中心获取服务请求地址

过滤器:Filter 分为两种类型,分别是 Gateway Filter-网关过滤器 和 Global Filter-全局过滤器。过滤器将会对请求和响应进行处理。
GatewayFilter:网关过滤器,网关过滤器用于拦截并链式处理 Web 请求,可以实现横切与应用无关的需求,比如:安全、访问超时的设置等。根据过滤器工厂的用途来划分,可以分为以下几种:Header、 Parameter、Path、Body、Status、Session、Redirect、Retry、RateLimiter 和 Hystrix。spring.cloud.gateway.routes.filters:

  • RewritePath=/api-gateway(?/?.*), ${segment} # 将 /api-gateway/product/1 重写为 /product/1
  • PrefixPath=/mypath # 将断言中的path前增加网关路径,如请求/hello,最后转发到目标服务的路径变为/mypath/hello
  • StripPrefix=2 # 将 /api/123/product/1 去除掉前面两个前缀之后,最后转发到目标服务的路径为 /product/1
  • SetPath=/product/ # 将 /api/product/1 重写为 /product/1
  • AddRequestParameter=flag, 1 # 在请求中添加入参 flag=1
  • SetStatus=404 # 任何情况下,响应的 HTTP 状态都将设置为 404

GlobalFilter:全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过 GatewayFilterAdapter 包装成 GatewayFilterChain 可识别的过滤器,它为请求业务以及路由的 URI 转换为真实业务服务请求地址的核心过滤器,不需要配置系统初始化时加载,并作用在每个路由上。
自定义过滤器:
自定义网关过滤器需要实现以下两个接口 : GatewayFilter , Ordered 。如:CustomGatewayFilter implements GatewayFilter, Ordered,重写filter和getOrder方法,其中在filter方法中写自定义网关业务逻辑,在getOrder中返回过滤器执行顺序,数值越小,优先级越高。再编写注册过滤器,在注册过滤器中的.filters(新实例化自定义过滤器)
自定义全局过滤器需要实现以下两个接口 : GatewayFilter , Ordered 。如:CustomGatewayFilter implements GatewayFilter, Ordered,重写filter和getOrder方法,其中在filter方法中写自定义网关业务逻辑,在getOrder中返回过滤器执行顺序,数值越小,优先级越高。注意:只需要在全局过滤器上增加@Component即可,无需编写过滤器配置类

统一鉴权:可以按照上面自定义过滤器写一个权限验证过滤器,主要思路是获取参数的token:String token = exchange.getRequest().getQueryParams().getFirst("token");获取不到结束响应报文;获取到了不进行拦截继续往下走

网关限流:限流就是限制流量,当用户增长过快,因为某个热点事件,竞争对象爬虫,恶意的请求等情况可能导致机器被拖垮。通过限流,我们可以很好地控制系统的 QPS,从而达到保护系统的目的。常见的限流算法有:
计数器算法:计数器算法是限流算法里最简单也是最容易实现的一种算法。比如我们规定,对于 A 接口来说,我们 1 分钟的访问次数不能超过 100 个。那么我们可以这么做:在一开 始的时候,我们可以设置一个计数器 counter,每当一个请求过来的时候,counter 就加 1,如果 counter 的值大于 100 并且该请求与第一个请求的间隔时间还在 1 分钟之内,触发限流;如果该请求与第一个请求的间隔时间大于 1 分钟,重置counter 重新计数
漏桶算法:漏桶算法其实也很简单,可以粗略的认为就是注水漏水的过程,往桶中以任意速率流入水,以一定速率流出水,当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。
令牌桶算法:令牌桶算法是对漏桶算法的一种改进,漏桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用。在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。放令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌。

Gateway 限流,Spring Cloud Gateway 官方提供了 RequestRateLimiterGatewayFilterFactory 过滤器工厂,使用 Redis 和 Lua 脚本实现了令牌桶的方式。限流规则:
URI 限流:spring.cloud.gateway.routes.filters:

  • name: RequestRateLimiter # 限流过滤器
    args: redis-rate-limiter.replenishRate: 1 # 令牌桶每秒填充速率 redis-rate-limiter.burstCapacity: 2 # 令牌桶总容量 key-resolver: "#{@pathKeyResolver}" # 使用 SpEL 表达式按名称引用

29.服务熔断、服务降级、服务限流
服务熔断:熔断机制是应对雪崩效应的一种微服务链路保护机制。在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。Spring Cloud里面Hystrix熔断的使用:写一个api接口TestApi,在接口上加上注解;@FeignClient(fallback = TestApiFallback.class),TestApiFallback是出现异常调用类,是TestApi的实现类重写业务方法,方法内做异常返回处理
服务降级:降级是指自己的待遇下降了,从RPC调用环节来讲,就是去访问一个本地的伪装者而不是真实的服务。触发原因不太一样,服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;
服务限流:在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流。缓存的目的是提升系统访问速度和增大系统能处理的容量,可谓是抗高并发流量的银弹;而降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开。限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务。常见的限流有:限制总并发数,远程接口调用速率,时间窗口内的平均速率。常见的限流算法有:令牌桶、漏桶。计数器也可以进行粗暴限流实现。

30.Spring
Spring Core-核心容器,Spring Bean-BeanFactory,Spring Context-上下文配置文件,Spring JDBC-数据库访问
IOC:控制反转,由spring来负责控制对象的生命周期和对象间的关系。所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。
DI:依赖注入,在系统运行中,动态的向某个对象提供它所需要的其他对象。允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。
总结:控制反转是说创建对象的控制权进行转移,以前创建对象的主动权和创建时机是由自己把控的,而现在这种权力转移到第三方,比如转移交给了IoC容器,它就是一个专门用来创建对象的工厂,你要什么对象,它就给你什么对象,有了 IoC容器,依赖关系就变了,原先的依赖关系就没了,它们都依赖IoC容器了,通过IoC容器来建立它们之间的关系。
AOP:切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想。Spring AOP使用的两种方式:注解,XML。
Spring AOP通知类型:前置通知(Before Advice),返回之后通知(After Retuning Advice),抛出(异常)后执行通知(After Throwing Advice),后置通知(After Advice),围绕通知(Around Advice)。
Spring AOP默认使用JDK 动态代理,这使得任何接口(或者接口的集合)可以被代理。Spring AOP 也可以使用 CGLIB代理-动态字节码增强技术。如果业务对象没有实现任何接口那么默认使用CGLIB。尽管实现技术不一样,但 都是基于代理模式 , 都是生成一个代理对象。
Spring AOP相关概念:切面,切入系统的一个切面。比如事务管理是一个切面,权限管理也是一个切面;Join point :连接点,也就是可以进行横向切入的位置;Advice :通知,切面在某个连接点执行的操作(分为: Before advice , After returning advice , After throwing advice , After (finally) advice , Around advice );Pointcut :切点,符合切点表达式的连接点,也就是真正被切入的地方;

Spring Bean:
bean作用域:
singleton:唯一bean实例,Spring中的bean默认都是单例的。在处理多次请求的时候在Spring 容器中只实例化一个bean,后续的请求都公用这个对象,这个对象会保存在一个map里面。当有新的请求的时候先从缓存(map)里面查看有没有,有的话直接使用这个对象,没有的话实例化一个对象。单例的好处:减少了新生对象的实例消耗;减少jvm垃圾回收;可以快速获取到bean。
Spring中的单例bean的线程不安全,原因是当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。解决方案:避免定义可变的成员变量;类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中。备注:ThreadLocal是一个线程内部的存储类,可以在指定线程内存储数据。
prototype:每次请求都会创建一个新的bean实例,没有缓存以及从缓存中查询的过程。
request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
session:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效。
global-session:全局session作用域,仅仅在基于Portlet的Web应用中才有意义,Spring5中已经没有了。
bean生命周期:
1.Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化
2.Bean实例化后对将Bean的引入和值注入到Bean的属性中
3.如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法;如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入;如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来。
4.如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用
5.如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用

Spring事务:
事务的特性:原子性-要么成功要么失败没有半成功半失败;一致性-成功就有完成状态异常就会回滚原始状态;隔离性-不同事务处理相同数据应将事务隔离开防止产生脏数据;持久性-事务一单完成持久化到容器数据库中不允许改变
事务管理方式:编程式事务:属于侵入性事务管理,在代码中硬编码,使用TransactionTemplate手动开启关闭事务。声明式事务管理:建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。在配置文件中配置(推荐使用),分为基于XML的声明式事务和基于注解的声明式事务。
编程式事务和声明式事务比较:编程式事务每次实现都要单独实现,但业务量大功能复杂时,使用编程式事务无疑是痛苦的,而声明式事务不同,声明式事务属于无侵入式,不会影响业务逻辑的实现,只需要在配置文件中做相关的事务规则声明或者通过注解的方式,便可以将事务规则应用到业务逻辑中。显然声明式事务管理要优于编程式事务管理,这正是Spring倡导的非侵入式的编程方式。唯一不足的地方就是声明式事务管理的粒度是方法级别,而编程式事务管理是可以到代码块的,但是可以通过提取方法的方式完成声明式事务管理的配置。
事务的传播机制:
REQUIRED:有事务就用当前事务,没有事务就新建一个事务
REQUES_NEW:每次都新建一个事务,当前事务挂起
SUPPORT:外层有事务就用外层事务,没有事务就以非事务方式执行(非事务)
NOT_SUPPORT:有事务就挂起,始终以非事务方式执行
事务未隔离会产生的三种情况:
脏读:读未提交数据
不可重复读 :两次重复读的数据不一致,一次是更新前一次是更新后(这里主要指修改)
幻像读:两次重复读的数据不一致,一次是未操作前的一次是操作后的(这里主要指新增删除)
事务的隔离级别:
spring事务默认隔离级别是default,是数据默认隔离级别,mysql是read,oracle是committed
DEFAULT-默认,使用数据库默认的事务隔离级别
COMMITTED-读已提交,保证一个事务修改的数据提交后才能被另外一个事务读取
UNCOMMITTED-读未提交,允许另外一个事务可以看到这个事务未提交的数据
READ-可重复读,保证一个事务不能读取另一个事务未提交的数据
SERIALIZABLE-串行化,最可靠的事务隔离级别,事务被处理为顺序执行

31.Spring MVC
流程说明:
1.客户端(浏览器)发送请求,直接请求到DispatcherServlet。
2.DispatcherServlet根据请求信息调用HandlerMapping,解析请求对应的Handler。
3.解析到对应的Handler(也就是我们平常说的Controller控制器)。
4.HandlerAdapter会根据Handler来调用真正的处理器来处理请求和执行相对应的业务逻辑。
5.处理器处理完业务后,会返回一个ModelAndView对象,Model是返回的数据对象,View是逻辑上的View。
6.ViewResolver会根据逻辑View去查找实际的View。
7.DispatcherServlet把返回的Model传给View(视图渲染)。
8.把View返回给请求者(浏览器)。

32.java设计模式
模板模式:1.定义一个模板类,类里面定义子类需要继承的三个子方法,外加一个final修饰非子类继承的方法(方法里面就是刚才那三个方法),2.写两个子类分别继承模板类重写模板类方法,3.写测试类中实例化两个子类,通过子类.final修饰非子类继承的方法调取整套子类方法使用
单例模式:1.饿汉式:创建类的时候就new了一个对象,然后通过get函数返回这个对象即可。实现过程:无参构造,静态实例化,提供接口获取实例对象(getSingleXXX)。2.懒汉式:需要使用的时候才去new对象。实现过程:私有空参构造,定义静态实例本对象但不实例化,提供接口获取对象实例,方法中判断若对象为空则实例化。线程不安全,解决办法可以用同步代码块和Lock锁。优缺点比对:饿汉式无锁安全效率高类加载初始化浪费内存,懒汉式线程不安全节省内存效率低
工厂模式:创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象,意思就是父类(接口)引用指向子类对象(接口的实现类)。实现过程:1.写一个父类接口里面有公共重写的方法,2.写两个子类分别实现接口并分别重新接口中方法做各自业务处理,3.写一个工厂类,根据名称或类型返回对应子类,4.写测试类,实例工厂对象,根据工厂对象.方法得到父类对象,父类.方法调出内部结果。主要是父类引用指向子类对象。
工厂模式理解:首先是定义这个类,然后就是直接显示地new一个对象出来,这样的做法使得我们将创建对象的逻辑毫无保留的暴露了出来;而工厂模式呢是把创建对象的方法封装起来,你如果需要,我就调用工厂的创建对象方法返回一个对象给你,这样一来用户在使用的时候其实只需要知道怎么用(调用方法),不用去理会怎么实现内部逻辑的(实现方法)。这样也把创建对象的逻辑隐藏了起来。

33.消息队列
队列元数据:队列名称、属性;
队列是一种先进先出的数据结构。消息队列可以简单理解为:把要传输的数据放在队列中。
实际工作中使用的场景:1.注册时候发送短信,发送优惠券;2.生成工单的时候,通知放款,生成合同
使用消息队列的好处:1.解耦,2.异步,3.削峰/限流;1.使用消息队列的缺点:系统可用性降低:系统引入的外部依赖越多,越容易挂掉;2.系统复杂性提高:硬生生加个MQ进来,你怎么保证消息没有重复消费?怎么处理消息丢失的情况?怎么保证消息传递的顺序性;3.一致性问题
activemq、rabbitmq、rocketmq、kafka区别及场景:activemq、rabbitmq都是单机型,吞吐量在万级左右,activemq是最早开始使用的,rabbitmq是erlang语言维护者都是个人比较活跃,rocketmq、kafka吞吐量都是十万级别,rocketmq是用java语言开发,kafka比较适合那种大数据层面的日志采集实时计算等大型公司采用
RabbitMQ高可用性:rabbitmq有三种模式:单机模式,普通集群模式:启动多个rabbitmq实例,每个机器启动一个。但是你创建的queue,只会放在一个rabbtimq实例上,但是每个实例都同步queue的元数据。你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从queue所在实例上拉取数据过来。这方案主要是提高吞吐量。镜像集群模式:创建的queue,无论元数据还是queue里的消息都会存在于多个实例上,然后每次你写消息到queue的时候,都会自动把消息到多个实例的queue里进行消息同步。好处在于,你任何一个机器宕机了,别的机器都可以用。坏处在于,第一,这个性能开销也太大。
消息队列消息重复消费原因:队列每次在消费完成的时候会提交一个offset标记表示我已经消费成功,假如有个业务处理成功,MQ未及时提交offset或系统重启/kill掉了,重启后会再发起消费一次就导致重复消费/网络延迟传输。怎么避免重复消费/保证幂等性(幂等性:同一个接口请求多次后保证只有一次成功其他不改变数据不报错):1.数据库做好唯一主键;2.使用全局Id,生产者将订单流水号存入messageId中,消费者消费了就把messageId存redis上,每次消费者在消费之前都去redis查询一下是否消费过
RabbitMQ保证消息的可靠性传输:1.生产者发送数据到MQ的时候数据丢失:可以使用rabbitmq提供的事务功能,发送数据开启事务channel.txSelect,回滚事务channel.txRollback,提交事务channel.txCommit,这个是同步的易出现消息阻塞;也可以使用confirm应答模式,写成功了会应答ack消息,失败了nack消息去重试,这个是异步;2.rabbitmq弄丢了数据:可以将元数据和消息都进行持久化到磁盘上;3.消费端丢失数据:关闭rabbitmq自动ack,通过一个api来调用就行,完成再ack回应
RabbitMQ保证消息的顺序性:产生的场景就是一个队列同时被多个消费者消费,解决:每个queue一个consumer(一对一),或者每个queue一个consumer,而consumer内部用内存队列做排队
消息队列的延时,过期失效,积压解决:积压数据量非常大时:停掉现有的消息队列,扩容队列和消费者以10倍的速度消费。积压的数据设置了过期时间导致丢失:重新查询出丢失的数据进行手动补偿消费
设计一个消息队列:1.mq支持可伸缩性,需要的时候能快速扩容;2.mq的数据要持久化落地磁盘,mq高可用性用镜像集群模式

34.Elasticsearch
elasticsearch设计的理念就是分布式搜索引擎,底层其实还是基于lucene。核心思想就是在多台机器上启动多个es进程实例,组成了一个es集群。es中存储数据的基本单位是索引,index:相当于mysql里的一张表;type:一个index里可以有多个type,相当于表的分类;mapping相当于这个type的表结构的定义;document:一条document就代表了mysql中某个表里的一行数据;field:document有多个field,每个field就代表了这个document中的一个字段的值
elasticsearch自带备份功能高可用,es集群多个节点,要是master节点宕机了,那么会重新选举一个节点为master节点。
倒排索引:也叫反向索引,有反向索引必有正向索引。通俗地来讲,正向索引是通过key找value,反向索引则是通过value找key。正常数据库存储是:文档编号/文档内容分别是:1/谷歌地图之父跳槽Facebook,在es里面会将一句话进行分词,并采用倒排索引,存储的是:单词编号/单词/单车列表:1/谷歌/1,注意倒排列表中存的是单词Id
四大核心待完成:读,写,搜索过程,写数据底层原理!!!

35.缓存
缓存的高性能和高并发:高性能:成千上万用户访问商品详情接口,第一次走数据库查询,查询完毕保存到缓存中,第二次及以后查询都走缓存中读取数据不再走数据库,提高了性能;高并发:这里的高并发纯指数据库层面非接口层面,数据库不支持高并发访问,四分之一数据走数据库,四分之三数据走缓存
redis为什么是单线程:因为redis数据都是基于内存操作,redis 用 单个CPU 绑定一块内存的数据,然后针对这块内存的数据进行多次读写的时候,都是在一个CPU上完成的,所以它是单线程。Redis客户端对服务端的每次调用都经历了发送命令,执行命令,返回结果三个过程。其中执行命令阶段,Redis是单线程来处理命令,命令不会立刻执行,所有的命令都会进入一个队列中,然后逐个被执行。
redis是单线程模型效率高的原因:1.纯内存访问,响应时间100纳秒,支持万亿级别访问;2.采用非阻塞IO多路复用技术,加上事件处理模型将epoll中的连接,读写,关闭都转换为了时间,不在I/O上浪费过多的时间。3.单线程避免了线程上下文切换和竞争产生的消耗
redis工作原理:客户端通过socket连接redis服务器发送请求消息,IO多路复用会监听消息是否送达,送达后将消息压入队列,按顺序经过文件事件分派器,依次执行连接应答处理器,命令请求处理器,命令回复处理器,原路返回给客户端。
IO多路复用技术:举个例子,模拟一个tcp服务器处理30个客户socket。假设你是一个老师,让30个学生解答一道题目,然后检查学生做的是否正确,你有下面几个选择:
第一种选择:按顺序逐个检查,先检查A,然后是B,之后是C、D。。。这中间如果有一个学生卡主,全班都会被耽误。这种模式就好比,你用循环挨个处理socket,根本不具有并发能力。
第二种选择:你创建30个分身,每个分身检查一个学生的答案是否正确。 这种类似于为每一个用户创建一个进程或者线程处理连接。
第三种选择,你站在讲台上等,谁解答完谁举手。这时C、D举手,表示他们解答问题完毕,你下去依次检查C、D的答案,然后继续回到讲台上等。此时E、A又举手,然后去处理E和A。。。 这种就是IO复用模型
总结一下:(第一种和第二种类似select、poll)Linux下的select、poll和epoll就是干这个的。将用户socket对应的fd注册进epoll,然后epoll帮你监听哪些socket上有消息到达,这样就避免了大量的无用操作。此时的socket应该采用非阻塞模式。这样,整个过程只在调用select、poll、epoll这些调用的时候才会阻塞,收发客户消息是不会阻塞的,整个进程或者线程就被充分利用起来,这就是事件驱动,所谓的reactor模式。
redis和memcache区别:都是将数据存放在内存中,k/v类型的数据,memcache能存图片视频,redis存储数据类型更多一些有String、List、Hash(存单个对象)、Set(无序去重)、ZSet(有序去重,可以用作排序,用法都是以z开头)这5种,redis支持数据持久化到硬盘,memcache没有持久化断电数据丢失
redis过期策略:采用定期删除+惰性删除。定期删除:每隔一段时间,程序对数据库进行一次检查,过期的就删除。惰性删除:不管键是否过期,只有每次取值的时候,才检查是否过期,过期就删除。
redis内存淘汰机制:Redis默认使用的是LRU算法。1.LRU,内存不足时,淘汰最近最少使用的key。2.noeviction:不淘汰,内存不足时, 新写入会报错。3.random:随机,内存不足时,在所有key中随机选择一个key淘汰。4.ttl:更早过期时间,内存不足时,在设置了过期时间的key中,选择有更早过期时间的key淘汰。

redis主从复制原理流程:
(1)slave node启动,仅仅保存master node的信息,包括master node的host和ip,但是复制流程没开始master host和ip是从哪儿来的,redis.conf里面的slaveof配置的
(2)slave node内部有个定时任务,每秒检查是否有新的master node要连接和复制,如果发现,就跟master node建立socket网络连接
(3)master node第一次连接slave node时执行全量复制,master会启动一个后台线程,开始生成一份RDB快照文件,同时还会将从客户端收到的所有写命令缓存在内存中。RDB文件生成完毕之后,master会将这个RDB发送给slave,slave会先写入本地磁盘,然后再从本地磁盘加载到内存中。然后master会将内存中缓存的写命令发送给slave,slave也会同步这些数据。
(4)slave node如果跟master node有网络故障,断开了连接,会自动重连。master如果发现有多个slave node都来重新连接,仅仅会启动一个rdb save操作,用一份数据服务所有slave node。
(5)master node后续持续将写命令,异步复制给slave node
复制过程中都不会中断各自现有操作,只会在复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务。slave node主要用来进行横向扩容,做读写分离,扩容的slave node可以提高读的吞吐量。master节点,必须要使用持久化机制
主从复制的断点续传:如果主从复制过程中,网络连接断掉了,那么可以接着上次复制的地方,继续复制下去,而不是从头开始复制一份。master node会在内存中常见一个backlog,master和slave都会保存一个replica offset还有一个master id,offset就是保存在backlog中的。如果master和slave网络连接断掉了,slave会让master从上次的replica offset开始继续复制
过期key处理:slave不会过期key,只会等待master过期key。如果master过期了一个key,或者通过LRU淘汰了一个key,那么会模拟一条del命令发送给slave。
主从复制数据丢失原因:1.主备切换的过程(master -> slave的复制是异步的),可能会导致数据丢失;2.脑裂导致的数据丢失(主服务器脱离网络从服务连接不上但还存活着,此时从服务器重新选举一台从服务器作为主服务器,存在两台master,新的master数据不是最新导致数据丢失)
主从复制数据丢失解决方案:1.min-slaves-to-write 1;min-slaves-max-lag 10 //要求至少有1个slave,数据复制和同步的延迟不能超过10秒,一旦超过了10秒钟,master就不会再接收任何请求

哨兵模式:
哨兵是一个独立的进程,哨兵通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
哨兵是redis集群架构中非常重要的一个组件,主要功能如下:
(1)集群监控,负责监控redis master和slave进程是否正常工作
(2)消息通知,如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员
(3)故障转移,如果master node挂掉了,会自动转移到slave node上
(4)配置中心,如果故障转移发生了,通知client客户端新的master地址
哨兵至少需要3个实例,来保证自己的健壮性。故障转移时,判断一个master node是宕机了,需要大部分的哨兵都同意才行,而且需要在哨兵中选举一个进行故障转移
redis哨兵判断节点是否宕机的原理:sdown是主观宕机,就一个哨兵如果自己觉得一个master宕机了,那么就是主观宕机。odown是客观宕机,如果quorum数量(设置的至少多少个哨兵要一致同意)的哨兵都觉得一个master宕机了,那么就是客观宕机
哨兵集群的自动发现机制:哨兵互相之间的发现,是通过redis的pub/sub系统实现的,channel中存放自己的host、ip和runid还有对这个master的监控配置,每个哨兵还会跟其他哨兵交换对master的监控配置,互相进行监控配置的同步

redis持久化:
redis持久化的意义,在于故障恢复。如果通过持久化将数据搞一份儿在磁盘上去,然后定期比如说同步和备份到一些云存储服务上去,那么就可以保证数据不丢失全部。也属于高可用的一个观点。解决缓存雪崩问题(一旦redis里根本找不到数据,这个时候就死定了,缓存雪崩问题,所有请求,没有在redis命中,就会去mysql数据库这种数据源头中去找,一下子mysql承接高并发,然后就挂了)
RDB持久化机制的优点
(1)RDB会生成多个数据文件,每个数据文件都代表了某一个时刻中redis的数据,这种多个数据文件的方式,非常适合做冷备,可以将这种完整的数据文件发送到一些远程的安全存储上去,比如说Amazon的S3云服务上去,在国内可以是阿里云的ODPS分布式存储上,以预定好的备份策略来定期备份redis中的数据
(2)RDB对redis对外提供的读写服务,影响非常小,可以让redis保持高性能,因为redis主进程只需要fork一个子进程,让子进程执行磁盘IO操作来进行RDB持久化即可
(3)相对于AOF持久化机制来说,直接基于RDB数据文件来重启和恢复redis进程,更加快速
RDB持久化机制的缺点
(1)如果想要在redis故障时,尽可能少的丢失数据,那么RDB没有AOF好。一般来说,RDB数据快照文件,都是每隔5分钟,或者更长时间生成一次,这个时候就得接受一旦redis进程宕机,那么会丢失最近5分钟的数据
(2)RDB每次在fork子进程来执行RDB快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒

AOF持久化机制的优点
(1)AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据
(2)AOF日志文件以append-only模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复
(3)AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在rewrite log的时候,会对其中的指导进行压缩,创建出一份需要恢复数据的最小日志出来。再创建新日志文件的时候,老的日志文件还是照常写入。当新的merge后的日志文件ready的时候,再交换新老日志文件即可。
(4)AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据
AOF持久化机制的缺点
(1)对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大
(2)AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的
(3)以前AOF发生过bug,就是通过AOF记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。所以说,类似AOF这种较为复杂的基于命令日志/merge/回放的方式,比基于RDB每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有bug。不过AOF就是为了避免rewrite过程导致的bug,因此每次rewrite并不是基于旧的指令日志进行merge的,而是基于当时内存中的数据进行指令的重新构建,这样健壮性会好很多。

RDB和AOF到底该如何选择
(1)不要仅仅使用RDB,因为那样会导致你丢失很多数据
(2)也不要仅仅使用AOF,因为那样有两个问题,第一,你通过AOF做冷备,没有RDB做冷备,来的恢复速度更快; 第二,RDB每次简单粗暴生成数据快照,更加健壮,可以避免AOF这种复杂的备份和恢复机制的bug
(3)综合使用AOF和RDB两种持久化机制,用AOF来保证数据不丢失,作为数据恢复的第一选择; 用RDB来做不同程度的冷备,在AOF文件都丢失或损坏不可用的时候,还可以使用RDB来进行快速的数据恢复

redis的集群架构-redis cluster:
支撑N个redis master node,每个master node都可以挂载多个slave node
读写分离的架构,对于每个master来说,写就写到master,然后读就从mater对应的slave去读
高可用,因为每个master都有salve节点,那么如果mater挂掉,redis cluster这套机制,就会自动将某个slave切换成master
redis cluster(多master + 读写分离 + 高可用)
我们只要基于redis cluster去搭建redis集群即可,不需要手工去搭建replication复制+主从架构+读写分离+哨兵集群+高可用
redis cluster分布式数据存储的核心算法:
hash算法 -> 一致性hash算法(自动缓存迁移)+虚拟节点(自动负载均衡)
传统hash算法:分布式系统中,假设有 n 个节点,传统方案使用 mod(key, n) 映射数据和节点。当扩容或缩容时(哪怕只是增减1个节点),映射关系变为 mod(key, n+1) / mod(key, n-1),绝大多数数据的映射关系都会失效。
一致性hash算法:按照常用的hash算法来将对应的key哈希到一个具有232次方个节点的空间中,即0 ~ (232)-1的数字空间中。现在我们可以将这些数字头尾相连,想象成一个闭合的环形。可以选择服务器的ip或唯一主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置。对多个对象通过特定的Hash函数计算出对应的key值,然后散列到Hash环上,然后从数据所在位置沿环顺时针“行走”,距离最近的服务器就是其应该定位到的服务器。如果在环境中新增一台服务器Node X,通过hash算法将Node X映射到环中,通过按顺时针迁移的规则,那么Object C被迁移到了Node X中,其它对象还保持这原有的存储位置。通过对节点的添加和删除的分析,一致性哈希算法在保持了单调性的同时,还是数据的迁移达到了最小,这样的算法对分布式集群来说是非常合适的,避免了大量数据迁移,减小了服务器的的压力。为了解决热点数据的平衡性,特增加master的虚拟节点,散布在圆环上。
redis cluster -> hash slot算法:redis cluster 有固定的 16384 个 hash slot,对每个 key 计算 CRC16 值,然后对 16384 取模,可以获取 key 对应的 hash slot(虚拟分区槽)。redis cluster 中每个 master 都会持有部分 slot,比如有 3 个 master,那么可能每个 master 持有 5000 多个 hash slot。hash slot 让 node 的增加和移除很简单,增加一个 master,就将其他 master 的 hash slot 移动部分过去,减少一个 master,就将它的 hash slot 移动到其他 master 上去。移动 hash slot 的成本是非常低的。客户端的 api,可以对指定的数据,让他们走同一个 hash slot,通过 hash tag 来实现。
redis cluster节点间采取gossip协议进行通信,将元数据(节点信息,故障,等等)集中存储在各个节点上,互相之间不断通信,保持整个集群所有节点的数据是完整的
redis cluster高可用性与主备切换原理:
1、判断节点宕机:如果一个节点认为另外一个节点宕机,那么就是pfail,主观宕机如果多个节点都认为另外一个节点宕机了,那么就是fail,客观宕机,跟哨兵的原理几乎一样,sdown,odown。在cluster-node-timeout内,某个节点一直没有返回pong,那么就被认为pfail如果一个节点认为某个节点pfail了,那么会在gossip ping消息中,ping给其他节点,如果超过半数的节点都认为pfail了,那么就会变成fail
2、从节点过滤:对宕机的master node,从其所有的slave node中,选择一个切换成master node检查每个slave node与master node断开连接的时间,如果超过了cluster-node-timeout * cluster-slave-validity-factor,那么就没有资格切换成master这个也是跟哨兵是一样的,从节点超时过滤的步骤
3、从节点选举:哨兵:对所有从节点进行排序,slave priority,offset,run id 每个从节点,都根据自己对master复制数据的offset,来设置一个选举时间,offset越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举所有的master node开始slave选举投票,给要进行选举的slave进行投票,如果大部分master node(N/2 + 1)都投票给了某个从节点,那么选举通过,那个从节点可以切换成master从节点执行主备切换,从节点切换为主节点

缓存穿透:用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。缓存穿透的解决方案:当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;
缓存雪崩:缓存雪崩是指,缓存层出现了错误,不能正常工作了。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。缓存雪崩的事前事中事后的解决方案:事前:redis高可用,主从+哨兵,redis cluster,避免全盘崩溃。事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL被打死。事后:redis持久化,快速恢复缓存数据。

最经典的缓存+数据库读写的模式:(1)读的时候,先读缓存,缓存没有的话,那么就读数据库,然后取出数据后放入缓存,同时返回响应(2)更新的时候,先删除缓存,然后再更新数据库
为什么是删除缓存,而不是更新缓存的原因:1.更新缓存代价很高,复杂的数据存储通过了计算得出的结果相对麻烦;2.更新缓存会累加一次热点数据导致数据占用内存不会里面删除,而删除对应的热点数据表示只是1。28法则,黄金法则,20%的数据,占用了80%的访问量
redis缓存与数据库双写不一致问题以及解决方案:1.先更新数据库,再删除缓存造成数据不一致解决:提供一个保障的重试机制即可。目前有两种方案(引入消息队列,执行两次或以上删除)。2.先删缓存,再更新数据库造成数据不一致解决:延时双删(先删除缓存,再写数据库。异步等待一段时间后,再次淘汰缓存。)

redis的CAS 类的乐观锁方案:多个系统实例都去更新某个 key。可以基于 zookeeper 实现分布式锁。每个系统通过 zookeeper 获取分布式锁,确保同一时间,只能有一个系统实例在操作某个 key,别人都不允许读和写。你要写入缓存的数据,都是从 mysql 里查出来的,都得写入 mysql 中,写入 mysql 中的时候必须保存一个时间戳,从 mysql 查出来的时候,时间戳也查出来。每次要写之前,先判断一下当前这个 value 的时间戳是否比缓存里的 value 的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据。
redis的高可用:主实例都挂了一个从实例,所以是高可用的,任何一个主实例宕机,都会自动故障迁移,redis从实例会自动变成主实例继续提供读写服务

36.分布式
rpc和http的区别:RPC主要⽤用于公司内部的服务调⽤用,性能消耗低,传输效率⾼高,服务治理理⽅方便便。HTTP主要⽤用于对外的异构环境,浏览器器接⼝口调⽤用,APP接⼝口调⽤用,第三⽅方接⼝口调⽤用等。
dubbo工作原理:
第一层:service层,接口层,给服务提供者和消费者来实现的
第二层:config层,配置层,主要是对dubbo进行各种配置的
第三层:proxy层,服务代理层,透明生成客户端的stub和服务单的skeleton
第四层:registry层,服务注册层,负责服务的注册与发现
第五层:cluster层,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务
第六层:monitor层,监控层,对rpc接口的调用次数和调用时间进行监控
第七层:protocol层,远程调用层,封装rpc调用
第八层:exchange层,信息交换层,封装请求响应模式,同步转异步
第九层:transport层,网络传输层,抽象mina和netty为统一接口
第十层:serialize层,数据序列化层
dubbo调用过程:
0.服务容器负责启动,加载,运行服务提供者。
1.服务提供者在启动时,向注册中心注册自己提供的服务。
2.服务消费者在启动时,向注册中心订阅自己所需的服务。
3.注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
4.服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
5.服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
dubbo实现过程:
1.启动zookeeper;
2.创建dubbo接口服务api项目,只需要在com.dubbo.api.service包下新建DemoService接口,给各个项目提供接口调用服务;
3.创建dubbo服务提供者provider项目,pom引入api项目和dubbo配置,编写实现类实现DemoService接口,在provider.xml文件中定义dubbo名称,注册中心,协议端口,暴露接口
4.创建dubbo服务提供者consumer项目,pom引入api项目和dubbo配置,编写实现类实现DemoService接口,在provider.xml文件中定义dubbo名称,注册中心,协议端口,订阅服务接口

注册中心挂了是可以继续通信,因为初始化的时候,消费者会将提供者的地址等信息拉取到本地缓存,所以注册中心挂了可以继续通信
dubbo协议:如:dubbo://192.168.0.1:20188,默认就是走dubbo协议的,单一长连接,NIO异步通信,基于hessian作为序列化协议,适用的场景就是:传输数据量很小(每次请求在100kb以内),但是并发量很高

dubbo负载均衡策略:random loadbalance(随机,可以配置权重),roundrobin loadbalance(轮询,均匀地将流量打到各个机器上去),leastactive loadbalance(最小活跃数,某个机器性能越差,那么接收的请求越少),consistanthash loadbalance(一致性hash算法,相同参数的请求一定分发到一个provider上去)
dubbo集群容错策略:failover cluster模式,失败自动切换,自动重试其他机器,默认就是这个,常见于读操作
dubbo服务降级:服务A调用服务B,结果服务B挂掉了,服务A重试几次调用服务B,还是不行,直接降级,走一个备用的逻辑,给用户返回响应。解决:在消费者配置文件中加入mock="return null"//调用失败统一返回null
dubbo失败重试以及超时重试:consumer调用provider要是失败了,比如抛异常了,此时应该是可以重试的,或者调用超时了也可以重试。解决:在消费者配置文件中加入retries="3" timeout="2000"//表示重试3次,超时时间限制2000毫秒

分布式服务接口幂等性:就是说一个接口,多次发起同一个请求,你这个接口得保证结果是准确的,比如不能多扣款,不能多插入一条数据,不能将统计值多加了1。这就是幂等性。解决:1.每个请求必须有一个唯一的标识;2.必须有一个记录状态标识这个请求处理过了;3.可以采用支付流水形式进行预判断;4.可以采用redis分布式锁拦截判断
分布式服务接口请求的顺序性:首先你得用dubbo的一致性hash负载均衡策略,将比如某一个订单id对应的请求都给分发到某个机器上去,接着就是在那个机器上因为可能还是多线程并发执行的,你可能得立即将某个订单id对应的请求扔一个内存队列里去,强制排队,这样来确保他们的顺序性。

设计一个类似dubbo的rpc框架(思路):
(1)上来你的服务就得去注册中心注册吧,你是不是得有个注册中心,保留各个服务的信心,可以用zookeeper来做,对吧
(2)然后你的消费者需要去注册中心拿对应的服务信息吧,对吧,而且每个服务可能会存在于多台机器上
(3)接着你就该发起一次请求了,咋发起?蒙圈了是吧。当然是基于动态代理了,你面向接口获取到一个动态代理,这个动态代理就是接口在本地的一个代理,然后这个代理会找到服务对应的机器地址
(4)然后找哪个机器发送请求?那肯定得有个负载均衡算法了,比如最简单的可以随机轮询是不是
(5)接着找到一台机器,就可以跟他发送请求了,第一个问题咋发送?你可以说用netty了,nio方式;第二个问题发送啥格式数据?你可以说用hessian序列化协议了,或者是别的,对吧。然后请求过去了。。
(6)服务器那边一样的,需要针对你自己的服务生成一个动态代理,监听某个网络端口了,然后代理你本地的服务代码。接收到请求的时候,就调用对应的服务代码,对吧。

Zookeeper实现分布式锁原理:Zookeeper的数据存储结构就像一棵树,Zookeeper分布式锁应用了临时顺序节点。
获取锁:首先,在Zookeeper当中创建一个持久节点ParentLock。当第一个客户端想要获得锁时,需要在ParentLock这个节点下面创建一个临时顺序节点 Lock1。之后,Client1查找ParentLock下面所有的临时顺序节点并排序,判断自己所创建的节点Lock1是不是顺序最靠前的一个。如果是第一个节点,则成功获得锁。 这时候,如果再有一个客户端 Client2 前来获取锁,则在ParentLock下载再创建一个临时顺序节点Lock2。 Client2查找ParentLock下面所有的临时顺序节点并排序,判断自己所创建的节点Lock2是不是顺序最靠前的一个,结果发现节点Lock2并不是最小的。于是,Client2向排序仅比它靠前的节点Lock1注册Watcher,用于监听Lock1节点是否存在。这意味着Client2抢锁失败,进入了等待状态。
释放锁:当任务完成时,Client1会显示调用删除节点Lock1的指令。获得锁的Client1在任务执行过程中,如果Duang的一声崩溃,则会断开与Zookeeper服务端的链接。根据临时节点的特性,相关联的节点Lock1会随之自动删除。 由于Client2一直监听着Lock1的存在状态,当Lock1节点被删除,Client2会立刻收到通知。这时候Client2会再次查询ParentLock下面的所有节点,确认自己创建的节点Lock2是不是目前最小的节点。如果是最小,则Client2顺理成章获得了锁。
zk分布式锁,其实可以做的比较简单,就是某个节点尝试创建临时znode,此时创建成功了就获取了这个锁;这个时候别的客户端来创建锁会失败,只能注册个监听器监听这个锁。释放锁就是删除这个znode,一旦释放掉就会通知客户端,然后有一个等待着的客户端就可以再次重新枷锁。
rdeis分布式锁:在redis里创建一个key算加锁,释放锁就是删除key

分布式session统一管理:
(1)tomcat + redis:这个其实还挺方便的,就是使用session的代码跟以前一样,还是基于tomcat原生的session支持即可,然后就是用一个叫做Tomcat RedisSessionManager的东西,让所有我们部署的tomcat都将session数据存储到redis即可。具体操作在tomcat的配置文件中配置RedisSessionManager信息。严重依赖于web容器
(2)spring session + redis:sping session配置基于redis来存储session数据,然后配置了一个spring session的过滤器,这样的话,session相关操作都会交给spring session来管了。接着在代码中,就用原生的session操作,就是直接基于spring sesion从redis中获取数据。具体操作在pom中引入spring-session-data-redis工程

分布式事务:
二阶段提交(准备-提交):准备阶段协调者会给各参与者发送准备命令,你可以把准备命令理解成除了提交事务之外啥事都做完了。同步等待所有资源的响应之后就进入第二阶段即提交阶段(注意提交阶段不一定是提交事务,也可能是回滚事务)。假如在第一阶段所有参与者都返回准备成功,那么协调者则向所有参与者发送提交事务命令,然后等待所有事务都提交成功之后,返回事务执行成功。是一种尽量保证强一致性的分布式事务,因此它是同步阻塞的,而同步阻塞就导致长久的资源锁定问题,总体而言效率低,并且存在单点故障问题,在极端条件下存在数据不一致的风险。可以理解为打王者荣耀的五排,打之前通知其他四位队友点准备,都准备了你才能点开始游戏;只要有一个队友不在团战范围内就不能发起团战
XA分布式事务:原理同上。在XA分布式事务的第一阶段,作为事务协调者的节点会首先向所有的参与者节点发送Prepare请求。在接到Prepare请求之后,每一个参与者节点会各自执行与事务有关的数据更新,写入Undo Log和Redo Log。如果参与者执行成功,暂时不提交事务,而是向事务协调节点返回“完成”消息。当事务协调者接到了所有参与者的返回消息,整个分布式事务将会进入第二阶段。在XA分布式事务的第二阶段,如果事务协调节点在之前所收到都是正向返回,那么它将会向所有事务参与者发出Commit请求。接到Commit请求之后,事务参与者节点会各自进行本地的事务提交,并释放锁资源。当本地事务完成提交后,将会向事务协调者返回“完成”消息。当事务协调者接收到所有事务参与者的“完成”反馈,整个分布式事务完成。
三阶段提交(准备阶段、预提交阶段和提交阶段):XA三阶段提交在两阶段提交的基础上增加了CanCommit阶段,并且引入了超时机制。一旦事物参与者迟迟没有接到协调者的commit请求,会自动进行本地commit。这样有效解决了协调者单点故障的问题。但是性能问题和不一致的问题仍然没有根本解决。
TCC:Try - Confirm - Cancel(预留-确认-撤销):TCC事务是Try、Commit、Cancel三种指令的缩写,其逻辑模式类似于XA两阶段提交,但是实现方式是在代码层面来人为实现。
MQ事务:利用消息中间件来异步完成事务的后一半更新,实现系统的最终一致性。这个方式避免了像XA协议那样的性能问题。

设计一个高并发系统:
(1)系统拆分,将一个系统拆分为多个子系统,每个系统连一个数据库,这样本来就一个库,现在多个数据库,可以抗高并发。多个子系统采用dubbo来架构。(mysql的并发支持3k)
(2)缓存,必须得用缓存。大部分的高并发场景,都是读多写少,那你完全可以在数据库和缓存里都写一份,然后读的时候大量走缓存不就得了。毕竟人家redis轻轻松松单机几万的并发啊。(redis的并发支持5w)
(3)MQ,必须得用MQ。可能你还是会出现高并发写的场景,比如说一个业务操作里要频繁搞数据库几十次,增删改增删改,疯了。那高并发绝对搞挂你的系统,你要是用redis来承载写那肯定不行,人家是缓存,数据随时就被LRU了,数据格式还无比简单,没有事务支持。所以该用mysql还得用mysql啊。那你咋办?用MQ吧,大量的写请求灌入MQ里,排队慢慢玩儿,后边系统消费后慢慢写,控制在mysql承载范围之内。所以你得考虑考虑你的项目里,那些承载复杂写业务逻辑的场景里,如何用MQ来异步写,提升并发性。MQ单机抗几万并发也是ok的,这个之前还特意说过。
(4)分库分表,可能到了最后数据库层面还是免不了抗高并发的要求,好吧,那么就将一个数据库拆分为多个库,多个库来抗更高的并发;然后将一个表拆分为多个表,每个表的数据量保持少一点,提高sql跑的性能。
(5)读写分离,这个就是说大部分时候数据库可能也是读多写少,没必要所有请求都集中在一个库上吧,可以搞个主从架构,主库写入,从库读取,搞一个读写分离。读流量太多的时候,还可以加更多的从库。(mysql同样100w条数据读比写更快)
(6)Elasticsearch,可以考虑用es。es是分布式的,可以随便扩容,分布式天然就可以支撑高并发,因为动不动就可以扩容加机器来抗更高的并发。那么一些比较简单的查询、统计类的操作,可以考虑用es来承载,还有一些全文搜索类的操作,也可以考虑用es来承载。

垂直分库,垂直分表,水平分库,水平分表:
水平分表:是在同一个数据库内,把同一个表的数据按一定规则拆到多个表中。
垂直分表:将一个表按照字段分成多表,每个表存储其中一部分字段。
水平分库:是把同一个表的数据按一定规则拆到不同的数据库中,每个库可以放在不同的服务器上。
垂直分库:是指按照业务将表进行分类,分布到不同的数据库上面,每个库可以放在不同的服务器上,它的核心理念是专库专用。
分表分库中间件:sharding-jdbc和mycat
sharding-jdbc这种client层方案的优点在于不用部署,运维成本低,不需要代理层的二次转发请求,性能很高,但是如果遇到升级啥的需要各个系统都重新升级版本再发布,各个系统都需要耦合sharding-jdbc的依赖;
mycat这种proxy层方案的缺点在于需要部署,自己及运维一套中间件,运维成本高,但是好处在于对于各个项目是透明的,如果遇到升级之类的都是自己中间件那里搞就行了。
数据迁移:
(1)停机迁移方案:等到0点,停机,系统挺掉,没有流量写入了,此时老的单库单表数据库静止了。然后你之前得写好一个导数的一次性工具,此时直接跑起来,然后将单库单表的数据哗哗哗读出来,写到分库分表里面去。导数完了之后,就ok了,修改系统的数据库连接配置啥的,包括可能代码和SQL也许有修改,那你就用最新的代码,然后直接启动连到新的分库分表上去。
(2)双写迁移方案:在线上系统里面,之前所有写库的地方,增删改操作,都除了对老库增删改,都加上对新库的增删改,这就是所谓双写,同时写俩库,老库和新库。然后系统部署之后,新库数据差太远,用之前说的导数工具,跑起来读老库数据写新库,写的时候要根据gmt_modified这类字段判断这条数据最后修改的时间,除非是读出来的数据在新库里没有,或者是比新库的数据新才会写。接着导万一轮之后,有可能数据还是存在不一致,那么就程序自动做一轮校验,比对新老库每个表的每条数据,接着如果有不一样的,就针对那些不一样的,从老库读数据再次写。反复循环,直到两个库每个表的数据都完全一致为止。

分布式中生成唯一主键Id:(1)数据库自增id。(2)uuid UUID.randomUUID().toString(),UUID是32位太长会导致入库性能变差。(3)获取系统当前时间,时间戳 + 用户id + 业务含义编码。(4)snowflake算法所生成的ID:1.第一位占用1位,其值始终是0,没有实际作用。2.时间戳占用41位,精确到毫秒。3.工作机器id占用10位,其中高位5位是数据中心ID(datacenterId),低位5位是工作节点ID(workerId)。4.序列号占用12位。

MySQL主从复制原理:主库将变更写binlog日志,然后从库连接到主库之后,从库有一个IO线程,将主库的binlog日志拷贝到自己本地,写入一个中继日志中。接着从库中有一个SQL线程会从中继日志读取binlog,然后执行binlog日志中的内容,也就是在自己本地再次执行一遍SQL,这样就可以保证自己跟主库的数据是一样的。

尊重原创,转载请注明,谢谢!

分享到:
Spring,SpringMvc,SpringBoot
ThreadPoolExecutor
  • 文章目录
  • 站点概览
hblao

Hihblao

QQ Email RSS
看爆 Top5
  • 设计一个高并发的高可用系统 434次看爆
  • MySQL 常用优化指南 224次看爆
  • Spring AOP 207次看爆
  • 人到中年 188次看爆
  • RedisTemplate常用使用说明 170次看爆

Copyright © 2022 hblao · 京ICP备16001163号

Proudly published with Halo · Theme by fyang · 站点地图 · 百度统计