封装:装隐藏了类的内部实现机制。对属性进行了封装:外界只能通过特定的方法进行访问。对方法进行了封装:外界只能通过定制好的方式调用,不用了解方法内部逻辑,方便使用。保护了数据。便于修改,增强了代码的可维护性和复用性
继承:继承是从已有的类中派生出新的类,即子类继承自父类。当子类通过extends关键字继承了父类后,便继承了父类的属性和方法(注意:子类继承了父类的所有属性和方法。但父类的私有属性和方法,子类是不能直接访问的,只是拥有但无法使用)。同时子类还可以具备父类所不具备的属性或方法。
通过继承避免了对各个类中重复的属性和方法进行反复描述。增强了代码的复用性。代码更加简洁。
多态:简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。
要实现多态需要做两件事:
第一: 方法重写( 子类继承父类并重写父类中已有的或抽象的方法) ;
第二: 对象造型( 用父类型引用指向子类型对象, 这样同样的引用调用同样的方法就会根据子
类对象的不同而表现出不同的行为) 。
抽象: 抽象是将一类对象的共同特征总结出来构造类的过程, 包括数据抽象和行为抽象两
方面。 抽象只关注对象有哪些属性和行为, 并不关注这些行为的细节是什么。
JDK1.8以前
JDK1.8以后
一般情况下, 当元素数量超过阈值时便会触发扩容。 每次扩容的容量都是之前容量的 2 倍。
HashMap 的容量是有上限的, 必须小于 1<<30, 即 1073741824。 如果容量超出了这个
数, 则不再增长, 且阈值会被设置为 Integer.MAX_VALUE。
JDK1.7中的扩容机制:
JDK1.8中的扩容机制:
Java7 中 ConcurrnetHashMap 使用的分段锁, 也就是每一个 Segment 上同时只有一
个线程可以操作, 每一个 Segment 都是一个类似 HashMap 数组的结构, 它可以扩容,
它的冲突会转化为链表。 但是 Segment 的个数一但初始化就不能改变, 默认 Segment
的个数是 16 个。
Java8 中的 ConcurrnetHashMap 使用的 Synchronized 锁加 CAS 的机制。 结构也由
Java7 中的 Segment 数组 + HashEntry 数组 + 链表 进化成了 Node 数组 + 链表 / 红
黑树, Node 是类似于一个 HashEntry 的结构。 它的冲突再达到一定大小时会转化成红
黑树, 在冲突小于一定数量时又退回链表。
oom原因就是一个,内存不够,所以要细分到底是哪里的内存不够。
栈溢出:栈中存放的是方法调用的出口,局部变量。这种情况很少,一般就是栈调用太深了,比如写了一个超长,无限递归的方法
方法区溢出:方法区存放的是类信息,常量,静态变量。这种情况说明类太多了,考虑加大方法区内存,或者加大jvm,或者反向思考是不是需要拆分服务
堆溢出:堆中存放的是实例的对象,大部分oom都是发生在堆中。通过分析heap dump日志文件。Heap dump文件是一个二进制文件,它保存了某一时刻JVM堆中对象使用情况。Heap Dump文件是指定时刻的Java堆栈的快照,是一种镜像文件。Heap Dump一般都包含了一个堆中的Java Objects, Class等基本信息。
增加两个参数 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof, 当 OOM 发生时自动 dump 堆内存信息到指定目录。
同时 jstat 查看监控 JVM 的内存和 GC 情况, 先观察问题大概出在什么区域。
使用 MAT 工具载入到 dump 文件, 分析大对象的占用情况, 比如 HashMap 做缓存未清理, 时间长了就会内存溢出, 可以把改为弱引用。
用MemoryAnalyzer.exe等一些工具分析错误原因,结合代码定位到发生异常的地方。
hreadLocal 是一个解决线程并发问题的一个类, 用于创建线程的本地变量, 我们知道一个
对象的所有线程会共享它的全局变量, 所以这些变量不是线程安全的, 我们可以使用同步技术。
但是当我们不想使用同步的时候, 我们可以选择 ThreadLocal 变量。 例如, 由于 JDBC 的
连接对象不是线程安全的, 因此, 当多线程应用程序在没有协同的情况下, 使用全局变量时,
就不是线程安全的。 通过将 JDBC 的连接对象保存到 ThreadLocal 中, 每个线程都会拥有
属于自己的连接对象副本。
利用可达性分析算法, 虚拟机会将一些对象定义为 GCRoots, 从 GCRoots 出发沿着引用链
向下寻找, 如果某个对象不能通过 GCRoots 寻找到, 虚拟机就认为该对象可以被回收掉。
l 哪些对象可以被看做是 GCRoots 呢?
1) 虚拟机栈( 栈帧中的本地变量表) 中引用的对象;
2) 方法区中的类静态属性引用的对象, 常量引用的对象;
3) 本地方法栈中 JNI(Native 方法) 引用的对象;
开发过程中, 经常会遇到某个类的某个成员变量、 方法或属性是私有的, 或只
对系统应用开放, 这里就可以利用 java 的反射机制通过反射来获取所需的私有成员或是方法。
注解的作用:
l 提供信息给编译器: 编译器可利用注解来探测错误和警告信息
l 编译阶段: 软件工具可以利用注解信息来生成代码、 html 文档或做其它相应处理;
l 运行阶段: 程序运行时可利用注解提取代码
注解是通过反射获取的, 可以通过 Class 对象的 isAnnotationPresent()方法判断它是否应
用了某个注解, 再通过 getAnnotation()方法获取 Annotation 对象
因为 String 设计成不可变, 当创建一个 String 对象时, 若此字符串值已经存在于常量池中, 则不会创建一个新的对象, 而是引用已经存在的
对象。
String 对象可以缓存 hashCode。 字符串的不可变性保证了 hash 码的唯一性, 因此可
以缓 存 String 的 hashCode, 这样不用每次去重新计算哈希码。 在进行字符串比较时,
可以直接比较 hashCode, 提高了比较性能;
线程安全: ArrayList 和 LinkList 都是不同步的, 不保证线程安全。
可重入锁
解释一、可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。
解释二、可重入锁又称递归锁,是指同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提是锁对象得是同一个对象),不会因为之前已经获取过锁还没有释放而阻塞。
这是目前主流的虚拟机都是采用GC Roots Tracing算法,比如Sun的Hotspot虚拟机便是采用该算法。 该算法的核心算法是从GC Roots对象作为起始点,利用数学中图论知识,图中可达对象便是存活对象,而不可达对象则是需要回收的垃圾内存。这里涉及两个概念,一是GC Roots,一是可达性。
1.抽象类中可以有普通成员变量,接口中没有普通成员变量;
2.抽象类可以有构造方法,接口中不能有构造方法;
3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
4. 抽象类中可以包含静态方法,接口中不能包含静态方法
5. 抽象类中的抽象方法的访问类型可以是 public,protected ,但接口中的抽象方法只能是 public 类型的,并且默认即为 public abstract 类型
7. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只是public static final 类型,并且默认即为 public static final 类型。
d、JDK1.8中对接口增加了新的特性:(1)、默认方法(default method):JDK 1.8允许给接口添加非抽象的方法实现,但必须使用default关键字修饰;定义了default的方法可以不被实现子类所实现,但只能被实现子类的对象调用;如果子类实现了多个接口,并且这些接口包含一样的默认方法,则子类必须重写默认方法;(2)、静态方法(static method):JDK 1.8中允许使用static关键字修饰一个方法,并提供实现,称为接口静态方法。接口静态方法只能通过接口调用(接口名.静态方法名)。
Java 中的泛型有 3 种形式, 泛型方法, 泛型类, 泛型接口。 Java 通过在编译时类型擦除的
方式来实现泛型。擦除时使用 Object 或者界定类型替代泛型, 同时在要调用具体类型方法或
者成员变量的时候插入强转代码,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
可以, 不过, 如果某个特殊的汉字没有被包含在 unicode 编码字符集中, 那么, 这个 char
型变量中就不能存储这个特殊汉字。
首先通过 Javac 编译器将 .java 转为 JVM 可加载的 .class 字节码文件。
FixedThreadPool:核心线程数和最大线程数是一样,线程数固定,当任务数超过线程数,会将任务放队列中等待。
CachedThreadPool:可缓存线程池,可动态增加线程,理论上是可以无限增加。
ScheduledThreadPool:支持定时或周期性执行任务。
SingleThreadExecutor:只有一个线程,发生异常会重新创建线程,所有任务都是按顺序执行。
SingleThreadScheduledExecutor:ScheduledThreadPool和SingleThreadExecutor相结合。
ForkJoinPool :ForkJoinPool 线程池和其他线程池很多地方都是一样的,但重点区别在于它每个线程都有一个自己的双端队列来存储分裂出来的子任务。ForkJoinPool 非常适合用于递归的场景,例如树的遍历、最优路径搜索等场景。
1、top命令:Linux命令。可以查看实时的CPU使用情况。也可以查看最近一段时间的CPU使用情况。
2、PS命令:Linux命令。强大的进程状态监控命令。可以查看进程以及进程中线程的当前CPU使用情况。属于当前状态的采样数据。
3、jstack:Java提供的命令。可以查看某个进程的当前线程栈运行情况。根据这个命令的输出可以定位某个进程的所有线程的当前运行状态、运行代码,以及是否死锁等等。
4、pstack:Linux命令。可以查看某个进程的当前线程栈运行情况。
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关, 却为业务模块
所共同调用的逻辑或责任( 例如事务处理、 日志管理、 权限控制等) 封装起来, 便于减少系统
的重复代码, 降低模块间的耦合度, 并有利于未来的可拓展性和可维护性
aop底层采用动态代理的机制实现:
@Transactional
l Transactional 注解应用在非 public 修饰的方法上@Transactional 注解属性
propagation 设置错误
l @Transactional 注解属性 rollbackFor 设置错误
l 同一个类中方法调用, 导致@Transactional 失效
l 异常被 catch“ 吃了” 导致@Transactional 失效
1、SpringBoot只是一个快速开发框架,使用注解简化了xml配置,内置了Servlet容器,以Java应用程序进行执行。
SpringCloud是一系列框架的集合,可以包含SpringBoot。
2、SpringBoot专注于方便的开发单个个体微服务
SpringCloud是关注于全局的微服务协调治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来。
注册中心组件:Eureka、Nacos
负载均衡组件 :Ribbon
远程调用组件 :OpenFeign
网关组件 : Zuul、Gateway
服务保护组件 : Hystrix、Sentinel
服务配置管理组件 : SpringCloudConfig、Nacos
Gateway则采用了基于Redis实现的令牌桶算法。
而Sentinel内部却比较复杂:
数据库建立唯一性索引
可以保证最终插入数据库的只有一条数据(比如订单表对订单号进行唯一索引,所有重复提交可能产生同一个订单号的都会被拆除。
token令牌机制
分为两个阶段,获取token和使用token。每次接口请求前先获取一个token,然后再下次请求的时候在请求的header体中加上这个token,后台进行验证,如果验证通过删除token,下次请求再次判断token
先查询后判断
首先通过查询数据库是否存在数据,如果存在证明已经请求过了,直接拒绝该请求,如果没有存在,就证明是第一次进来,直接放行。高并发下不推荐
悲观锁或者乐观锁
悲观锁可以保证每次for update的时候其他sql无法update数据(在数据库引擎是innodb的时候,select的条件必须是唯一索引,防止锁全表)
乐观锁,一般通过version来做乐观锁,这样既能保证执行效率,又能保证幂等。例如: UPDATE tab1 SET col1=1,version=version+1 WHERE version=#version#
分布式锁
SETNX命令,redisson
为了达到事务的四大特性, 数据库定义了 4 种不同的事务隔离级别:
全局锁:锁定数据库中的所有表。使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。
表级锁: 开销小, 加锁快; 不会出现死锁; 锁定粒度大, 发生锁冲突的概率最高, 并发度
最低。
行级锁: 开销大, 加锁慢; 会出现死锁; 锁定粒度最小, 发生锁冲突的概率最低, 并发度
也最高。
页面锁: 开销和加锁时间界于表锁和行锁之间; 会出现死锁; 锁定粒度界于表锁和行锁之
间, 并发度一般
共享锁
共享锁(Shared Locks,简称S锁),又称为读锁,同样是一种基本的锁类型。
如果事务T1对数据对象O1加上了共享锁,那么当前事务只能对O1进行读取操作,其他事务也只能对这个数据对象加共享锁——直到该数据对象上的所有共享锁都被释放。
共享锁和排他锁最根本的区别在于,加上排他锁后,数据对象只对一个事务可见,而加上共享锁后,数据对所有事务都可见。
排他锁
排他锁(Exclusive Locks,简称 X 锁),又称为写锁或独占锁,是一种基本的锁类型。如果事务 T1对数据对象 O1加上了排他锁,那么在整个加锁期间,只允许事务 T1对 O1进行读取和更新操作,其他任何事务都不能再对这个数据对象进行任何类型的操作——直到T1释放了排他锁。
从上面讲解的排他锁的基本概念中,我们可以看到,排他锁的核心是如何保证当前有且仅有一个事务获得锁,并且锁被释放后,所有正在等待获取锁的事务都能够被通知到。
MyISAM引擎:
1、不支持事务;2、不支持外键,支持表锁,每次所住的是整张表;3、采用非聚集索引;4、被mongodb替代
Innodb引擎:
1、支持事务;2、支持行锁和外键约束;3、主键索引采用聚集索引
Memory引擎:
1、不支持事务;2、表锁;3、Hash索引;4、已被Redis替代
空间索引是对空间数据类型的字段建立的索引, MySQL 中的空间数据类型有四种,GEOMETRY、 POINT、 LINESTRING、 POLYGON。 在创建空间索引时, 使用 SPATIAL 关键字。 要求, 引擎为 MyISAM, 创建空间索引的列, 必须将其声明为 NOT NULL。
id
包含一组数字,表示查询中执行select子句或操作表的顺序,
id相同,执行顺序由上至下
如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
select_type
表示查询中每个select子句的类型(简单 OR复杂)
a.SIMPLE:查询中不包含子查询或者UNION
b.PRIMARY:查询中若包含任何复杂的子部分,最外层查询则被标记为:PRIMARY
c.SUBQUERY:在SELECT或WHERE列表中包含了子查询,该子查询被标记为:SUBQUERY
type
表示MySQL在表中找到所需行的方式,又称“访问类型”,常见类型如下:
由左至右,由最差到最好
ALL:全表扫描,
index:遍历索引扫描,
range:索引范围扫描常见于between、<、>等的查询
ref:非唯一性索引扫描
eq_ref:唯一性索引扫描
key
使用的索引名称,没有则为null
key_len
使用的索引长度
主库将变更写入 binlog 日志, 然后从库连接到主库之后, 从库有一个 IO 线程, 将主库的
binlog 日志拷贝到自己本地, 写入一个 relay 中继日志中接着从库中有一个 SQL 线程会从中
继日志读取 binlog, 然后执行 binlog 日志中的内容, 也就是在自己本地再次执行一遍 SQL。
主从延迟:
a. 主库的从库太多
b. 从库硬件配置比主库差
c. 慢 SQL 语句过多
d. 主从库之间的网络延迟
e. 主库读写压力大
insert:
order by优化:
group by优化:
limit 优化:
方案一: 如果 id 是连续的, 可以这样, 返回上次查询的最大记录(偏移量), 再往下 limit
select id, name from employee where id>1000000 limit 10.
方案二: 在业务允许的情况下限制页数:
建议跟业务讨论, 有没有必要查这么后的分页啦。 因为绝大多数用户都不会往后翻太多页。
方案三: order by + 索引( id 为索引)
select id, name from employee order by id limit 1000000, 10
方案四: 利用延迟关联或者子查询优化超多分页场景。 ( 先快速定位需要获取的 id 段, 然后
再关联)
SELECT a.* FROM employee a, (select id from employee where 条件 LIMIT 1000000,10 ) b
where a.id=b.id
count优化:
按照效率排序的话,count(字段) < count(主键 id) < count(1) ≈ count(*),所以尽
量使用 count(*)。
update优化:
InnoDB的行锁是针对索引加的锁,不是针对记录加的锁 ,并且该索引不能失效,否则会从行锁
升级为表锁 。
索引条件下推优化(Index Condition Pushdown (ICP) )是MySQL5.6添加的,用于优化数据查询。
不使用索引条件下推优化时存储引擎通过索引检索到数据,然后返回给MySQL服务器,服务器然后判断数据是否符合条件。
当使用索引条件下推优化时,如果存在某些被索引的列的判断条件时,MySQL服务器将这一部分判断条件传递给存储引擎,然后由存储引擎通过判断索引是否符合MySQL服务器传递的条件,只有当索引符合条件时才会将数据检索出来返回给MySQL服务器。索引条件下推优化可以减少存储引擎查询基础表的次数,也可以减少MySQL服务器从存储引擎接收数据的次数。
假设有索引(name, age), 执行 SQL: select * from tuser where name like '张%' and age=10;
MySQL 5.6 以后, 存储引擎根据(name,age)联合索引,找到,由于联合索引中包含列,所以存储引擎直接在联合索引里按照age=10
过滤。按照过滤后的数据再一一进行回表扫描。
索引下推使用条件
只能用于range
、 ref
、 eq_ref
、ref_or_null
访问方法;
只能用于InnoDB
和 MyISAM
存储引擎及其分区表;
对存储引擎来说,索引下推只适用于二级索引(也叫辅助索引);
索引下推的目的是为了减少回表次数,也就是要减少 IO 操作。对于的聚簇索引来说,数据和索引是在一起的,不存在回表这一说。
引用了子查询的条件不能下推;
引用了存储函数的条件不能下推,因为存储引擎无法调用存储函数。
1、Show Profile默认是关闭的,并且开启后只存活于当前会话,也就说每次使用前都需要开启。也可修改MYSQL配置文件来修改。
2、通过Show Profiles查看sql语句的耗时时间,然后通过Show Profile命令对耗时时间长的sql语句进行诊断。
3、注意Show Profile诊断结果中出现相关字段的含义,判断是否需要优化SQL语句。
4、使用show processlist查看连接的线程个数,来观察是否有大量线程处于不正常的状态或者其他不正常的特征
5、Show Profile的常用查询参数
①ALL:显示所有的开销信息。
②BLOCK IO:显示块IO开销。
③CONTEXT SWITCHES:上下文切换开销。
④CPU:显示CPU开销信息。
⑤IPC:显示发送和接收开销信息。
⑥MEMORY:显示内存开销信息。
⑦PAGE FAULTS:显示页面错误开销信息。
⑧SOURCE:显示和Source_function,Source_file,Source_line相关的开销信息。
⑨SWAPS:显示交换次数开销信息。
binlog
用于记录数据库执行的写入性操作(不包括查询)信息,以二进制的形式保存在磁盘中。binlog
是 mysql
的逻辑日志,并且由 Server
层进行记录,使用任何存储引擎的 mysql
数据库都会记录 binlog
日志。而redo/undo 是 innodb 引擎层维护的。
redo log 通常是 物理 日志,记录的是 数据页 的物理修改,而不是某一行或某几行修改成怎样怎样,它用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置)。Redo Log 保证事务的持久性。
undo log 用来回滚行记录到某个版本。undo log 一般是逻辑日志,根据每行记录进行记录。Undo Log 保证事务的原子性。
4次挥手:
HTTPS(Hypertext Transfer Protocol Secure:超文本传输安全协议)是一种透过计算机网络进行安全通信的传输协议。HTTPS 经由 HTTP 进行通信,但利用 SSL/TLS 来加密数据包。HTTPS 开发的主要目的,是提供对网站服务器的身份认证,保护交换数据的隐私与完整性。
域名解析– > 发起 TCP 的 3 次握手 – > 建立 TCP 连接后发起 http 请求 – > 服务器响应
http 请求– >浏览器得到 html 代码 – > 浏览器解析 html 代码, 并请求 html 代码中的资
源( 如 js、 css、 图片等) – > 浏览器对页面进行渲染呈现给用户 。
CPU 密集型任务(N+1): 这种任务消耗的主要是 CPU 资源, 可以将线程数设置为 N
( CPU 核心数) +1, 比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,
或者其它原因导致的任务暂停而带来的影响。 一旦任务暂停, CPU 就会处于空闲状态,
而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。
I/O 密集型任务(2N): 这种任务应用起来, 系统会用大部分的时间来处理 I/O 交互, 而线
程在处理 I/O 的时间段内不会占用 CPU 来处理, 这时就可以将 CPU 交出给其它线程使
用。 因此在 I/O 密集型任务的应用中, 我们可以多配置一些线程, 具体的计算方法是 2N。
如何判断是 CPU 密集任务还是 IO 密集任务?
CPU 密集型简单理解就是利用 CPU 计算能力的任务比如你在内存中对大量数据进行排序。
单凡涉及到网络读取, 文件读取这类都是 IO 密集型, 这类任务的特点是 CPU 计算耗费时间
相比于等待 IO 操作完成的时间来说很少, 大部分时间都花在了等待 IO 操作完成上。
高并发,大任务时候需要用到多线程。大任务处理起来比较耗时, 这时候可以起到多个线程并行加快处理( 例如: 分片上传) 。 可以提高 CPU 的利用率
线程的生命周期包含 5 个阶段, 包括: 新建、 就绪、 运行、 阻塞、 销毁新建( NEW) : 就是刚使用 new 方法, new 出来的线程;
l 就绪( RUNNABLE) : 就是调用的线程的 start()方法后, 这时候线程处于等待 CPU 分
配资源阶段, 谁先抢的 CPU 资源, 谁开始执行;
l 运行( RUNNING) : 当就绪的线程被调度并获得 CPU 资源时, 便进入运行状态, run
方法定义了线程的操作和功能;
l 阻塞( BLOCKED) : 在运行状态的时候, 可能因为某些原因导致运行状态的线程变成了
阻塞状态, 比如 sleep()、 wait()之后线程就处于了阻塞状态, 这个时候需要其他机制将
处于阻塞状态的线程唤醒, 比如调用 notify 或者 notifyAll()方法。 唤醒的线程不会立刻
执行 run 方法, 它们要再次等待 CPU 分配资源进入运行状态;
l Waiting( 无限等待) : 一个线程在等待另一个线程执行一个( 唤醒) 动作时, 该线程进
入 Waiting 状态。 进入这个状态后不能自动唤醒, 必须等待另一个线程调用 notify 方法
或者 notifyAll 方法时才能够被唤醒。
l 销毁( TERMINATED) : 如果线程正常执行完毕后或线程被提前强制性的终止或出现异
常导致结束, 那么线程就要被销毁, 释放资源;
如果使用的是无界队列 Linke dBlockingQueue, 也就是无界队列的话, 没关系, 继续添
加任务到阻塞队列中等待执行, 因为 LinkedBlockingQueue 可以近乎认为是一个无穷大
的队列, 可以无限存放任务
如果使用的是有界队列比如 ArrayBlockingQueue , 任务首先会被添加到
ArrayBlockingQueue 中, ArrayBlockingQueue 满了, 会根据 maximumPoolSize 的
值增加线程数量, 如果增加了线程数量还是处理不过来, ArrayBlockingQueue 继续满,
那么则会使用拒绝策略 RejectedExecutionHandler 处理满了的任务, 默认是
AbortPolicy。
select
poll
epoll
按照读写的单位大小来分:
l 字符流: 以字符为单位, 每次次读入或读出是 16 位数据。 其只能读取字符类型数据。
(Java 代码接收数据为一般为 char 数组, 也可以是别的)
l 字节流: 以字节为单位, 每次次读入或读出是 8 位数据。 可以读任何类型数据, 图片、
文件、 音乐视频等。 (Java 代码接收数据只能为 byte 数组)
按照实际 IO 操作来分:
l 输出流: 从内存读出到文件。 只能进行写操作。
l 输入流: 从文件读入到内存。 只能进行读操作。
l 注意: 输出流可以帮助我们创建文件, 而输入流不会。
按照读写时是否直接与硬盘, 内存等节点连接分:
l 节点流: 直接与数据源相连, 读入或读出。
l 处理流: 也叫包装流, 是对一个对于已存在的流的连接进行封装, 通过所封装的流的功能
调用实现数据读写。 如添加个 Buffering 缓冲区。 ( 意思就是有个缓存区, 等于软件和
mysql 中的 redis)
Redis快的主要原因是:
完全基于内存
数据结构简单,对数据操作也简单
使用多路 I/O 复用模型,充分利用CPU资源
单线程优势有下面几点:
代码更清晰,处理逻辑更简单
不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为锁而导致的性能消耗
不存在多进程或者多线程导致的CPU切换,充分利用CPU资源
缓存穿透:大量并发查询不存在的 KEY, 在缓存和数据库中都不存在, 同时给缓存和数据库
缓存击穿:某个 KEY 失效的时候, 正好有大量并发请求访问这个 KEY
缓存雪崩:当某一时刻发生大规模的缓存失效的情况, 导致大量的请求无法获取数据, 从而将流量压力传导到数据库上, 导致数据库压力过大甚至宕机。
string:最基本的数据类型,二进制安全的字符串,最大512M。
应用:
缓存,热点数据
分布式session
分布式锁
文章的阅读量,微博点赞数,允许一定的延迟,先写入 Redis 再定时同步到数据库
全局ID
内部编码:
int:8 个字节的长整型(long,2^63-1)
embstr:小于等于44个字节的字符串,embstr格式的SDS(Simple Dynamic String)
raw:SDS大于 44 个字节的字符串
list:按照添加顺序保持顺序的字符串列表(双向列表)。
应用:
关注列表、粉丝列表
消息队列
内部编码:
zipList
linkList
set:无序的字符串集合,不存在重复的元素。set 对外提供的功能与 list 类似是一个列表的功能,特殊之处在于 set 是可以自动排重的。
应用:
知乎点赞
共同关注人、可能认识的人(多个 set 取交集、并集、差集)
商城商品筛选
内部编码:
intset(整数集合):当集合中的元素都是整数,并且集合中的元素个数小于 512 个时,Redis 会选用 intset 作为底层内部实现。
hashtable(哈希表):当上述条件不满足时,Redis 会采用 hashtable 作为底层实现。
sorted set:已排序的字符串集合。
内部编码:
ziplist(压缩列表)
skiplist(跳跃表)
hash:key-value对的一种集合。
应用:
对象数据,用户 ID 为查找的 key,存储的 value 用户对象包含姓名,年龄,生日等信息。
购物车
内部编码:
ziplist(压缩列表):当哈希类型中元素个数小于 hash-max-ziplist-entries 配置(默认 512 个),同时所有值都小于 hash-max-ziplist-value 配置(默认 64 字节)时,Redis 会使用 ziplist 作为哈希的内部实现。
hashtable(哈希表):当上述条件不满足时,Redis 则会采用 hashtable 作为哈希的内部实现。
bitmap:更细化的一种操作,以bit为单位。有一些数据只有两个属性,比如是否是学生,是否是党员等等,对于这些数据,最节约内存的方式就是用bit去记录,以是否是学生为例,1代表是学生,0代表不是学生
应用:
在线状态
签到状态
hyperloglog:基于概率的数据结构,Redis 的基数统计,这个结构可以非常省内存的去统计各种计数。 它在 Redis 的内部结构表现就是一个字符串位图。你可以把 HyperLogLog 对象当成普通的字符串来进行处理。# 2.8.9新增
应用:
注册 IP 数、每日访问 IP 数、页面实时UV)、在线用户数
Geo:地理位置信息储存起来, 并对这些信息进行操作 # 3.2新增
内部编码:
Geo本身不是一种数据结构,它本质上还是借助于Sorted Set
应用场景:
比如现在比较火的直播业务,我们需要检索附近的主播,那么GEO就可以很好的实现这个功能。
一是主播开播的时候写入主播Id的经纬度,
二是主播关播的时候删除主播Id元素,这样就维护了一个具有位置信息的在线主播集合提供给线上检索。
流(Stream):用一句话概括Streams就是Redis实现的内存版kafka。支持多播的可持久化的消息队列,用于实现发布订阅功能,借鉴了 kafka 的设计。# 5.0新增
内部编码:
streams底层的数据结构是radix tree:Radix Tree(基数树) 事实上就几乎相同是传统的二叉树
主从刚刚连接的时候, 进行全量同步; 全同步结束后, 进行增量同步。 当然, 如果有需要,
slave 在任何时候都可以发起全量同步。 redis 策略是, 无论如何, 首先会尝试进行增量同步,
如不成功, 要求从机进行全量同步。
RDB:
RDB 持久化方式, 是将 Redis 某一时刻的数据持久化到磁盘中, 是一种快照式的持久化方
法
优点:
RDB 作为一个非常紧凑( 有压缩) 的文件, 可以很方便传送到另一个远端数据中心 , 非常适用于灾难恢复。
RDB 在保存 RDB 文件时父进程唯一需要做的就是 fork 出一个子进程,接下来的工作全部由子进程来做, 父进程不需要再做其他 IO 操作, 所以 RDB 持久化方式可以最大化redis 的性能。
与 AOF 相比, 在恢复大的数据集的时候, RDB 方式会更快一些
缺点:
Redis 意外宕机时, 会丢失部分数据。
当 Redis 数据量比较大时, fork 的过程是非常耗时的, fork 子进程时是会阻塞的, 在这期间 Redis 是不能响应客户端的请求的
AOF:
AOF 方式是将执行过的写指令记录下来, 在数据恢复时按照从前到后的顺序再将指令都执行
一遍。
优点:
持久化效率更高,AOF 文件可读性高, 分析容易
缺点:
对于相同的数据来说, AOF 文件大小通常要大于 RDB 文件。
AOF恢复数据的速度可能会慢于 RDB。
基于数据库实现分布式锁
基于缓存实现分布式锁
基于 Zookeeper 实现分布式锁
Redis 分布式锁实现: 先拿 setnx 来争抢锁, 抢到之后, 再用 expire(过期)给锁加一个
过期时间防止锁忘记了释放。setnx 和 expire 合成一条指令来用的!
使用 AOF 和 RDB 结合的方式
RDB 做镜像全量持久化, AOF 做增量持久化。 因为 RDB 会耗费较长时间, 不够实时, 在
停机的时候会导致大量丢失数据, 所以需要 AOF 来配合使用
Redis 集群模式
master 节点持久化
Redis 断点续传
从 redis 2.8 开始, 就支持主从复制的断点续传, 如果主从复制过程中, 网络连接断掉了,
那么可以接着上次复制的地方, 继续复制下去, 而不是从头开始复制一份。
主备切换的过程, 可能会导致数据丢失
解决异步复制和脑裂导致的数据丢失
redis.conf 中
min-slaves-to-write 1
min-slaves-max-lag 10
要求至少有 1 个 slave, 数据复制和同步的延迟不能超过 10 秒
如果说一旦所有的 slave, 数据复制和同步的延迟都超过了 10 秒钟, 那么这个时候,
master 就不会再接收任何请求了
上面两个配置可以减少异步复制和脑裂导致的数据丢失。
过期删除策略
1)定时删除:在设置某个key 的过期时间同时,我们创建一个定时器,让定时器在该过期时间到来时,立即执行对其进行删除的操作。占用大量的CPU资源去处理过期的数据,影响Redis的吞吐量和响应时间
2)惰性删除:当访问一个key时,才判断是否过期,过期则删除。可以节省CPU资源,但是占用内存较大。
3)定期删除:隔一段时间扫描过期的key。折中的方案
在Redis中,同时使用了定期删除和惰性删除,不能保证过期key100%被删除。
内存淘汰策略
Redis 内存数据集大小上升到一定大小的时候, 就会施行数据淘汰策略。 Redis 提供 6 种
数据淘汰策略:
本地缓存同步:当前微服务的数据库数据与缓存数据同步,可以直接在数据库修改时加入对Redis的修改逻辑,保证一致。
跨服务缓存同步:服务A调用了服务B,并对查询结果缓存。服务B数据库修改,可以通过MQ通知服务A,服务A修改Redis缓存数据
通用方案:使用Canal框架,伪装成MySQL的salve节点,监听MySQL的binLog变化,然后修改Redis缓存数据
生产者: 开启 confirm 模式(异步, 性能较好),(1)确认消息已经到达交换机(2)确认消息到达队列
MQ: (1)exchange 持久化 (2)queue 持久化 (3)消息持久化
消费者: (1)消息确认,关闭自动 应答(ACK),使用手动应答,(2)消息重试机制,(3)消息多次失败,可投递给异常交换机。
给每一条消息都添加一个唯一id,在本地记录消息表及消息状态,处理消息时基于数据库的id唯一性做判断;
基于业务本身的幂等。比如根据id的删除、查询业务天生幂等;新增、修改等业务可以考虑基于数据库id唯一性、或乐观锁机制确保幂等。
1、简单模式
一个队列只被一个消费者监听消费
发短信、邮件
2、争抢模式
多个消费者同时绑定多个队列,形成争抢消息的效果
抢红包
3、路由模式
消息携带routing key和队列绑定的routing key,如果匹配上,就把消息发送给队列
4、发布订阅
一个消息通过交换机发送给多个队列
群发短信
5、主题模式topic
和路由模式类似,区别是可以使用通配符绑定队列
单体架构:所有的业务集中在一个项目中开发,打成一个包部署
架构简单、部署成本低;耦合度高、维护困难、升级困难
分布式架构:根据业务功能对系统做拆分,每个业务功能模块作为独立项目开发,称作一个服务,但是所有服务都连接一个数据库。
降低服务耦合、有利于服务升级和拓展;调用关系错综复杂,数据库压力依然存在
微服务架构:每个服务对应唯一的业务能力和独立的数据库,做到单一职责;
数据独立、独立部署和交付;提供统一的标准接口,与语言和技术无关;服务之间隔离性强;做到了高内聚,低耦合。
注册中心组件:Eureka、Nacos
负载均衡组件:Ribbon
远程调用组件:OpenFeign
网关组件:Zuul、Gateway
服务保护组件:Hystrix、Sentinel
服务配置管理组件:SpringCloudConfig、Nacos
Nacos采用了数据的分级存储模型,最外层是Namespace,用来隔离环境。然后是Group,用来对服务分组。接下来就是服务(service),一个服务包含多个实例,但是可能处于不同机房,因此service下有多个集群(Cluster),Cluster下是不同的实例(Instance)
Nacos内部接收到注册请求时,不会立即写数据,而是将服务注册的任务放入一个阻塞队列就立即响应给客户端。然后利用线程池读取阻塞队列中的任务,异步来完成实例更新,从而提高并发读写能力。
Nocas在更新实例列表时,会采用CopyOnWrite技术,首先将旧的实例列表拷贝一份,然后更新拷贝的实例列表,再用更新后的实例列表来覆盖旧的实例列表。
这样就避免了并发读写冲突问题,也不会出现脏读问题。
接口方式:Nacos与Eureka都对外暴露了Rest风格的API接口,用来实现服务注册、发现等功能。
实例类型:Nacos的实例有永久实例和临时实列之分;而Eureka只支持临时实列。
健康检测:Nacos对临时实例采用心跳模式检测(5秒),对永久实例采用主动请求来检测;Eureka只支持心跳模式(默认30秒 )。
服务发现:Nacos支持定时拉取和订阅推送两种模式;Eureka只支持定时拉取模式。
CAP:Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
Hystix默认是基于线程池实现的线程隔离,每一个被隔离的业务都要创建一个独立的线程池,线程过多会带来额外的CPU开销,性能一般,但是隔离性更强。
Sentinel是基于信号量实现的线程隔离,不用创建线程池,性能较好,但是隔离性一般。
限流是对服务的保护,避免因瞬间高并发流量而导致服务故障,进而避免雪崩,是一种预防措施。
超时处理,线程隔离,降级熔断是在部分服务故障时,将故障控制在一定范围,避免雪崩,是一种补救措施。
流控模式:
1、直接模式:统计当前资源的请求,触发阈值时对当前资源直接限流,默认的模式,基于滑动时间窗口算法。
2、关联模式:统计当前资源管理的另一个资源,触发阈值时,对当前资源限流。
3、链路模式:统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流。
流控效果:
1、快速失败:QPS超过阈值时,拒绝新的请求,默认的处理方式。
2、warm up: 预热模式,QPS超过阈值时,拒绝新的请求;QPS阈值是逐渐提升的,可以避免冷启动时高并发导致服务宕机。
3、排队等待:请求会进入队列,按照阈值允许的时间间隔依次执行请求;如果请求预期等待时长大于超时时间,直接拒绝(基于漏桶算法)
热点参数限流(基于令牌桶算法):
之前的限流是统计访问某个资源的所有请求,判断是否超过QPS阈值。而热点参数限流是分别统计参数值相同的请求,判断是否超过QPS阈值。
1、令牌桶算法
实现思路:可以准备一个队列,用来保存令牌,另外通过一个线程池定期生成令牌放到队列中,每来一个请求,就从队列中获取一个令牌,并继续执行。
2、漏桶算法
漏桶算法其实很简单,可以粗略的认为就是注水漏水过程,往桶中以一定速率流出水,以任意速率流入水,当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。
在算法实现方面,可以准备一个队列,用来保存请求,另外通过一个线程池定期从队列中获取请求并执行,可以一次性获取多个并发执行。
3、滑动窗口算法
计数器算法是限流算法里最简单也是最容易实现的一种算法。比如我们规定,对于A接口来说,我们1分钟的访问次数不能超过100个。那么我们可以这么做:在一开 始的时候,我们可以设置一个计数器counter,每当一个请求过来的时候,counter就加1,如果counter的值大于100并且该请求与第一个 请求的间隔时间还在1分钟之内,那么说明请求数过多;如果该请求与第一个请求的间隔时间大于1分钟,且counter的值还在限流范围内,那么就重置 counter,具体算法的示意图如下:
微服务
每个功能都被称为一个独立的服务,可以单独构建和部署,其中某个服务出现故障也不会影响其他的功能模块。解决低耦合+高内聚的问题
DevOps
早期的项目使用的是“瀑布模型”进行软件交付,即一个阶段所有的工作完成后再往下一个阶段,但这样的模式无法满足业务快速开发交付和变更需求的情况。于是后面就出现了敏捷开发这一概念。即一种快速应对需求变化软件开发能力,而DevOps就是基于敏捷开发将软件开发、测试人员、IT运维关联在一起,通过工具、组织等方式使开发、测试、发布流程自动化、软件发布频繁、高效。
容器化
容器化的好处在于运维的时候不需要再关心每个服务所使用的技术栈了,每个服务都被无差别的封装在容器里,可以被无差别的管理和维护。
全局事务ID Transaction ID ——XID
TC-事务的协调者(Seata服务器)
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM-事务管理器(@GlobalTransactional)
定义全局事务的范围,开始全局事务、提交或回滚全局事务
RM-资源管理器(事务的参与方)
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
分布式事务执行流程:
Seata提供了四种不同的分布式事务解决方案:
对于非临时数据,Nacos采用的是Raft协议,而临时数据Nacos采用的是Distro协议。
Raft协议是一种强一致性、去中心化、高可用的分布式协议,它是用来解决分布式一致性问题的。
许多中间件都是利用Raft协议来保证分布式一致性的,例如Redis的sentinel,CP模式的Nacos的leader选举都是通过Raft协议来实现的。因为Nacos的一致性协议是采用的Raft协议。
如果注册Nacos的client节点注册时ephemeral=true,那么Nacos集群对这个client节点的效果就是AP,采用distro协议实现;而注册Nacos的client节点注册时ephemeral=false,那么Nacos集群对这个节点的效果就是CP的,采用raft协议实现。根据client注册时的属性,AP,CP同时混合存在,只是对不同的client节点效果不同。Nacos可以很好的解决不同场景的业务需求。
对于临时实例,健康检查失败,则直接可以从列表中删除。这种特性就比较适合那些需要应对流量突增的场景,服务可以进行弹性扩容。当流量过去之后,服务停掉即可自动注销了。
对于持久化实例,健康检查失败,会被标记成不健康状态。它的好处是运维可以实时看到实例的健康状态,便于后续的警告、扩容等一些列措施。
Nacos中可以针对具体的实例设置一个保护阈值,值为0-1之间的浮点类型。本质上,保护阈值是⼀个⽐例值(当前服务健康实例数/当前服务总实例数)。
⼀般情况下,服务消费者要从Nacos获取可⽤实例有健康/不健康状态之分。Nacos在返回实例时,只会返回健康实例。
但在⾼并发、⼤流量场景会存在⼀定的问题。比如,服务A有100个实例,98个实例都处于不健康状态,如果Nacos只返回这两个健康实例的话。流量洪峰的到来可能会直接打垮这两个服务,进一步产生雪崩效应。
保护阈值存在的意义在于当服务A健康实例数/总实例数 < 保护阈值时,说明健康的实例不多了,保护阈值会被触发(状态true)。
Nacos会把该服务所有的实例信息(健康的+不健康的)全部提供给消费者,消费者可能访问到不健康的实例,请求失败,但这样也⽐造成雪崩要好。牺牲了⼀些请求,保证了整个系统的可⽤。
这里我们看到了不健康实例的另外一个作用:防止产生雪崩。
那么,如果所有的实例都是临时实例,当雪崩场景发生时,Nacos的阈值保护机制是不是就没有足够的(包含不健康实例)实例返回了?如果有一部分实例是持久化实例,即便它们已经挂掉,状态为不健康的,但当触发阈值保护时,还是可以起到分流的作用。
针对临时服务实例,采用 AP 来保证注册中心的可用性,Distro 协议。
针对持久化服务实例,采用 CP 来保证各个节点的强一致性,JRaft 协议。(JRaft 是 Nacos 对 Raft 的一种改造)
针对配置中心,无 Database 作为存储的情况下,Nacos 节点之间的内存数据为了保持一致,采用 CP。Nacos 提供这种模式只是为了方便用户本机运行,降低对存储依赖,生产环境一般都是通过外置存储组件来保证数据一致性。
针对配置中心,有 Database 作为存储的情况下,Nacos 通过持久化后通知其他节点到数据库拉取数据来保证数据一致性,另外采用读写分离架构来保证高可用,所以这里我认为这里采用的 AP
监督学习:是指每个进入算法的训练样本数据都有对应的目标值。
如上图2所示,Ifhealth为目标值。
常见的监督学习算法:
无监督学习:就是训练样本的数据里没有目标列,不依赖于打标好的机器学习算法。
那么,这样的数据可能对一些分类和回归的场景就不太适合了。
无监督学习主要是来解决一些聚类场景的问题。
半监督学习:训练数据里只有部分数据是打标的。目前,半监督学习的算法,都是监督学习算法的变形。
强化学习:强化学习是一种比较复杂的机器学习种类。强调的是:系统与外界不断的交换,获得外界的反馈,然后决定自身的行为。
如:无人驾驶,阿尔法狗下围棋就是强化学习的应用。