并发量 qps 吞吐量(TPS)
并发量
指的是同一时刻有多少连接数目(正确来说, 到达服务器的都是http请求, 客户端和服务端必须建立连接后, 这次请求才能进行. 如果服务器是BIO方式建立链接, 那么并发量最大值就和能够用来建立链接的最大线程数有关, 如果是NIO模型, 每个线程可以创建多个通道来建立连接, 接收请求, 相应的并发量也就提升了. 可参考Netty)
QPS
指系统每秒可以接受的请求数量(乍一看, 似乎qps和并发量是一致的概念, 因为肯定要建立连接, 才能算作一次请求, 但是时间上每个请求并不是瞬间完成的, 也就是说如果一台服务器的最大并发是10, 同时每个请求的平均响应时间为3s, 那么它的qps其实是10/3=3.333次, 因此qps是一个范围时间内的请求数量和平均响应时间计算出来的值)
吞吐量(TPS)
其概念和qps基本一致, 只是针对的不再是请求, 而是一个事务(事务的概念比请求要大一些), 例如一个事务需要多次请求, 例如服务器的最大qps是3, 然后认为一个事务需要请求三次服务器, 那么服务器的最大吞吐量为1. 大多数情况下, 使用qps表示系统的吞吐量也是正确的.
程序化广告
- (1). 程序化广告简化了广告主和媒体之间的投放流程, 对于投放的各个阶段有了更加全面的规范.
- (2). 赋予广告主投放广告时更多的能力, 例如可以投放目标群体, 目标职业等等.
- (3). 平台化使得广告主和媒体互相之间都有了更多的选择
dsp? 谁可以建立dsp
dsp是需求方平台, 可以由大型媒体或者第三方机构建立, 目标群体是广告主, 广告主可以在dsp平台上投放广告
ssp?
ssp是供应方平台, 可以由大型媒体或者第三方机构建立, 目标是媒体, 媒体可以在ssp上管理自己的广告位等, 然后dsp可以通过某种方式来ssp选择具体的广告位投放
Hibernate jpa save后会有update的情况发生
当你向往数据库里存储一个复杂的java对象(复杂是指这个对象的某个字段不属于java基本类型), 此时orm框架如果选择Hibernate, 那么在save的时候, 会因为dirty check的问题, 每个save语句会跟上一条update语句
解决方案: 将不属于基本类型的对象实现序列化接口 或者重写equals方法和hashcode方法.
重写equals方法, 为什么一定要重写hashcode方法 ?
首先java中, 对于相同对象的定义是指两个对象指向同一个内存地址, 此时equals方法才返回true, 但是实际使用中, 我们通常会认为两个对象, 如果它的属性值一样, 那么他们就属于同一对象, 因此我们就需要重写这个类的equals方法.
但是java要求hashcode方法和equals的结论必须一致, 如果我们改写了equals方法, 他们的结论就不一致了, 因此也必须重写hashcode方法, 不然就会有很多坑
rtb gd
rtb: 竞价广告
gd: 合约广告, 类似与下订单
cpm cpt cpa cpr
cpm: 广告按照千次展现计费, 就是广告播放了1000次, 收多少线
cpt: 按播放时段收费, 就是这一段时间内都可能会播放这个广告, 按时间段收费
cpa: 按有效订单收费, 一般这种广告都会有二维码之类的, 通过埋点统计这种广告具体吸引了多少流量, 最后收费.
cpr: 按照触达收费, 通过某种证实或者估算出这个广告会接触到多少人, 按人数收费
linux 查看哪些是符号链接 查看符号链接指向哪些确切路径
符号链接就类似与windows下面的快捷方式 linux使用符号链接可以快速到达某个文件路径下, 或者使用某个功能
查找哪些符号是链接:
- ls -l | grep "^l" 查找当前目下以权限l开头的 就是符号链接
- find . -type l -print 打印当前目录及子目录下文件类型为l的
查看某个符号链接指向哪, 利用readlink命令
- readlink xxxx
创建一个链接
- ln -s 目标路径 快捷方式的路径
spring-boot-starter 起步依赖
起步依赖可以认为是一个依赖对象模型, 通过使用起步依赖, 就是引入了功能相关的一批依赖, 十分方便
- (1) 起步依赖无需指定版本, 会根据spring-boot的版本自动选择合适版本的起步依赖. 同时一个起步依赖中包含的多个依赖的版本也是兼容的, 不会出现版本的问题
- (2) 同时我们也可以制作自己的起步依赖供其它项目使用
- (3) 起步依赖自动引入的依赖是可以手动覆盖的, maven自身的依赖覆盖机制是保留最新的依赖版本, 即从pom文件作为root, 如果pom里用, 就用pom文件里的, 否则根据引入依赖的远近, 保留最近的依赖. gradle是会保留所有相同依赖的最新版本
自动配置
- (1) spring-boot 有一个 spring-boot-autoconfigure的Jar文件, 能够自动配置的类都在这个jar包里
- (2) 每个自动配置的类都通过@Conditional注解选择在合适的时机完成自动配置
- (3) 正常情况下, 通过显式的进行配置即可覆盖自动配置(因为自动配置的类上通常都有@ConditionalOnMissingBean注解), 如果不行的话, 就根据自动配置类的条件去覆盖
互斥共享 同时共享
互斥共享
- 计算机中某些资源在一定时间内, 只允许一个进程访问(除非该进程结束访问)
- 例如打印机等大多数物理设备, 某段时间内只允许一个进程使用
同时共享
- 计算机的某些资源允许多个进程同时访问
- 这里的同时是宏观上好像是同时使用资源, 实际上微观上是多个进程交替使用.
- 比如QQ在发送文件A,微信在发送文件B,宏观上两个进程A和B都在访问磁盘,在我们看来是同时进行的,但是在微观上两个进程A和B是交替进行访问磁盘的,只是时间太短,cpu处理速度太快,我们感觉不到
时分复用 空分复用(分页, 虚拟内存)
时分复用
在时间维度进行划分, 划分成一段段时间, 每个进程使用一小段时间
例如cpu的使用, 让每个进程轮流使用cpu, 每个进程都使用很小的一段的时间, 然后切换
空分复用
将同一个空间进行分片分配给不同的进程
首先物理内存是计算机中的内存卡, 物理内存的大小就是你内存卡的大小, 将物理内存划分成一个个小区域, 对应逻辑内存中的页, 计算机在使用内存中数据的时候, 就是通过逻辑内存中的页, 再去物理内存中查询具体的数据.
因此进程在访问主存的时候, 需要一个地址翻译, 将进程中的虚拟地址翻译成物理地址, 再去物理内存中获取数据.
如果只是这样的话, 那么计算机同时使用的进程数目就会非常少(一般电脑内存都是8G, 16G), 随便一个进程例如微信都要占用2.3个内存, 开不了几个程序电脑应该就要崩溃了, 但是实际中并不是这样.
虚拟内存技术将物理内存当作一个高速缓存的空间, 在某个进程运行的时候, 才将它运行所需要的内容从磁盘swap到物理内存中.就相当于计算机会将物理内存中的同一片空间, 生成了不同的虚拟地址, 分配给不同的页面使用, 也就是分配给不同的进程使用.
根据时分复用, 我们知道计算机中进程并不是真正同时运行的. 假设物理内存只有2G, 进程A,B 运行都需要2G内存. 当进程A运行时, 就会将A进程页面对应的数据从磁盘读取到物理内存空间中, 此时进程A根据自己的页进行数据使用, 当切换到进程B时, 将内存空间所有数据写入磁盘, 再将进程B对应的数据从磁盘读取到物理内存中. 通过这种方式2G的内存就同时运行了两个2G的进程, 此时的虚拟内存就可以认为是4G.
进程 linux如何查看进程
操作系统会为每个进程分配资源, 进程才能去使用cpu时间.
ps -ef 命令可以查看所有进程, 通常和grep命令搭配 筛选出指定端口号或者进程名称的数据
UID PID PPID C STIME TTY TIME CMD
zzw 14124 13991 0 00:38 pts/0 00:00:00 grep --color=auto dae
UID :程序被该 UID 所拥有
PID :就是这个程序的 ID
PPID :则是其上级父程序的ID
C :CPU使用的资源百分比
STIME :系统启动时间
TTY :登入者的终端机位置
TIME :使用掉的CPU时间。
CMD :所下达的是什么指令
线程 线程如何使用资源
线程被进程进行调度 一个进程可以拥有多个线程, 线程只能使用所属进程的资源
进程的三种状态 以及转换关系
等待: 线程处于休眠状态, 等待cpu使用时间, 获得后进入运行状态
阻塞: 当线程在运行状体, 发现需要某个互斥资源(不包括cpu使用时间这个资源), 直到拥有该资源才能进入等待状态, 否则一直阻塞
运行: 正常运行状态, 使用完cpu时间后变成等待状态
进程调度算法
指的是进程是如何协调使用cpu时间的
-
按顺序 根据使用cpu请求的先后顺序进行调度, 第一个请求执行完后执行第二个
-
时间片轮转 每个cpu请求按照固定时间片轮转, 根据fifo队列, 从队首开始, 无论当次请求是否执行完, 执行下一个请求
-
优先级调度 每个请求有优先级, 按优先级进行调度, 执行完执行下一个
-
死锁检测 遍历资源等待有向图, 一旦遇到相同节点就陷入了死锁
错误
错误即error, 例如内存溢出, 栈溢出, 错误是程序无法处理的
异常的分类 异常链
异常即exception, 分运行时异常(即运行时才会暴露的异常), 可检查异常(编译的时候就能发现的异常, 例如IO在使用的时候, 强制要求你捕获异常, 或者其它类在使用时需要强制捕捉的)
异常链即我们在捕获了一个异常后, 抛出了另一个异常(例如对外统一抛出业务异常), 但是排查问题的时候需要完整的异常信息, 因此在抛出新异常的时候, 带上原有异常, 即可自动形成异常链 即throw 的时候, 带上e即可
finally的使用
finally搭配try cath使用, 无法是try catch否发生异常(无论try catch是否返回), finally的代码总会被执行
try catch finally中return的执行顺序
try catch中如果执行到returen语句, 并不会真正的返回, 而是保存返回值, 继续去执行finally, 如果finally中有returen, 那么会覆盖上面的return, 如果finally没有returen, 在执行完finally后, 继续返回上面的保存值(注意finally的代码即使修改了上面返回的值, 但是实际返回的值并没有被修改, 因为已经被保存起来了, 修改的不是同一个变量).
hashmap为什么不是线程安全的?
jdK 1.7中是为啥不安全 头插法
1.7中线程不安全是发生在扩容后, 将原有元素复制到新map中时发生的.头插法在多线程的时候, 会使得两个元素形成环
头插法, 会将数组中某个位置的链表从头部断开, 然后从链表头部开始, 一个个插入新的map, 在这个过程中, 如果两个线程同时进行, 其中一个线程A的插入进行了一半, 而另一个线程B完成了整个插入操作, 此时A继续插入, 而A线程中链表节点的连接关系和主存中链表的连接关系就发生了矛盾, 就会形成死循环,或者丢失某些元素.
例如A中本身3->7 此时3连接的是7, 但是主存中已经是7->3 (因为头插法, 会将链表反转), 此时3和7就形成了一个环, hashmap就会一直把1插到2前面, 然后2插到1前面, 永远循环到resize方法里
jdk 1.8是为啥不安全 尾插法
尾插法, 不会出现头插法在扩容时到那种死循环, 但是在put的时候, 会出现两个线程同时在数组中put同一个位置, 导致无法在该位置形成链表, 而是后put的元素的覆盖前一次的put, 丢失元素
spring bean的加载过程
正常情况下, IOC容器在创建完毕后, 容器会自动利用getBean方法, 完成所有bean的创建.
当从IOC容器中获取bean的时候, 触发bean的加载(如果bean已经加载过, 直接获取), 创建bean分为三个步骤:
(1) 实例化:利用构造函数创建bean(如果有其它bean是通过构造函数注入的, 那么在这个阶段也会去尝试获取其它bean)
(2) 填充属性: 如果属性中有其它bean, 这个阶段会去尝试创建其它bean
(3) 初始化方法: 可以是xml文件中指定的, 也可以是代码中显式的指定的方法(正常该方法是没有内容的). 此阶段完成spring AOP代理bean的创建
注意@PostConstruct是发生在填充属性之后, 初始化方法之前的, 所以该注解标示的方法中可以使用属性, 而不用担心空指针, 很多时候想在初始化方法内完成的工作, 就直接利用@PostConstruct标记完成了
即使是普通bean(包括spring的bean), 静态代码块->非静态代码块->实例化 代码块的执行都是在实例化之前完成的, 此时依赖注入都是没有完成的
IOC容器的创建过程
(1) 容器分为beanFactory和applicationContext, 其中applicationContext是beanFactory的高级版本, 拥有更多的功能而已
(2) 初始化由refresh()触发
(3) 选择合适的ResourceLoader(例如读取xml的, 读取配置类的), 定位beanDefintion
(4) 读取并解析beanDefintion, 生成每个bean的数据结构和数据表现
(5) 将每个bean注册到IOC容器中, 即添加到一个hashmap中
(6) IOC容器最后会循环getBean每个bean, 完成所有bean的初始化(除非bean配置了懒加载属性)
IOC容器中的bean都是代理类吗
因为我们对于java bean会加各种各样的注解, 如果某些注解会对于bean产生AOP的效果, 那么这个bean在IOC容器创建的时候, 就会生成相应的代理类, 如果不需要的话, IOC容器中的bean就是类本身.
循环依赖问题
循环依赖是指在创建A的时候, 依赖了B, 在去创建B的时候, 发现依赖了A, 但是A又没法被创建完成, 因此形成了循环依赖
多例模式为啥不行
原型模式下, 每次从IOC容器getBean都是重新创建一个bean, 此时在创建A的时候, 依赖了B, 尝试去创建B, 会直接将A放入一个正在创建的队列, 当B去尝试获取A的时候, 发现A在这个队列中, 就会直接抛出异常, 导致失败.
多例模式下啥情况可以
对于上述那种情况, 只有A不会被放到队列中才可以, 因此A需要是单例, B可以是原型
单例模式为啥可以解决循环依赖
单例模式的bean为了解决循环依赖问题, 使用了三级缓存来解决(就算没有循环依赖, 普通创建bean的时候也会有三级缓存)
创建A
A完成实例化, 填充属性之前, 会将A放入第三级缓存中, 此时放入的是A对象本身的引用, 不是代理, 此时并不知道A是否会发生循环引用
A开始填充属性, 发现需要注入B, 尝试去获取B, 发现三级缓存中(从第一级往下找)都没有B, 尝试创建B
创建B
B完成实例化, 填充属性前, 将B放入第三级缓存
B开始填充属性, 发现需要注入A, 在第三级缓存中找到了A, 如果此时A需要创建代理, 那么就会为A创建代理, 放入第二级缓存中, 注入A的代理.(此时如果其它bean也依赖A, 就可以从第二级缓存中直接获取A的代理了, 无需再经历第三级缓存创建代理的过程)
B完成属性填充和初始化方法, 将B从第三级缓存中删除, 创建代理放入第一级缓存(代表可用bean), 此时第二级缓存中没有B, 无需删除.
A继续填充属性, 完成初始化方法, 将A从第二, 第三级缓存删除, 放入第一级缓存, 完成创建
单例模式下啥情况不行
因为将bean放入第三级缓存是在实例化之后才进行的, 如果A是通过构造函数的形式注入了B, 那么在创建B的时候, A根本还没有被放入第三级缓存中, 这种情况下, 循环依赖无法被解决
为啥使用三级缓存?
深入分析三级缓存 其实二级缓存完全也可以解决循环依赖的问题, 简单来说只需要一个存放提前暴露的对象集合即可.
但是如果该bean是经过AOP增强的, 那么我们需要的应该是增强后的代理Bean, 因此直接将实例化后的对象暴露到缓存中是不行的. 那我们直接创建代理后的对象, 然后放到缓存中行不行呢?
正常的情况下, 将一个Bean增强是在创建bean的第三步的时候, 而我们讨论循环依赖的时候是发生在第二步, 填充属性的阶段. 如果我们直接创建好代理对象, 那么就破坏了这个顺序(因为每个bean, 我们无法提前知道是否发生了循环依赖). 因此为了只对出现循环依赖的bean, 才提前执行AOP增强, 那么就需要三级缓存来实现延迟执行的功能了.
因此三级依赖就是因为Spring不想为每个bean都提前执行AOP增强, 因此采用三级缓存来循环来实现延迟执行的功能.
spring 单例bean是线程安全的吗
bean如果是无状态的, 那么自然就是线程安全的(线程不安全都是因为多线程改变共享变量的值导致的, 如果只是读取也不会线程安全的问题), 如果bean是有状态的, 单例bean就不是线程安全的, 可以使用原型bean.
@PostConstruct的执行位置
该注意属于spring框架, 因此配置在bean的方法上的时候才有用, 不然是没用的 静态代码块 -> 非静态代码块 -> bean构造函数 -> bean依赖注入 -> @PostConstruct -> bean的初始化方法(通常没有)
groupId + artifactId
(1) groupId
一般是区域组织名, 是项目组织唯一的标识符, 正规来说是和你项目里main包的包结构相同的.
也就是你main包下, 会有很多层嵌套的package, 这个外层的结构就和你的groupId的结构一致, 但是这是默认要求, 并不强制, 你在idea创建项目填写GroupId的时候, 就会自动的将package那里设置成一样, 如果你不想一样 可以手动更改pakage的内容
(2) artifactId
就是你真正的项目名称
maven坐标
maven定义了这样一组规则:
世界上任何一个构件都可以使用Maven坐标唯一标志,maven坐标的元素包括groupId, artifactId, version。
只要在pom.xml文件中配置好dependancy的groupId,artifact,verison,
maven就会从仓库中寻找相应的构件供我们使用。那么,"maven是从哪里下载构件的呢?"
答案很简单,maven内置了一个中央仓库的地址(repol.maven.org/maven2) ,该中央仓库包含了世界上大部分流行的开源项目构件,maven会在需要的时候去那里下载。