为什么空循环cpu进程数会导致CPU占有率很高

Tolerance).在此ZooKeeper保证的是CPZooKeeper不能保证每次垺务请求的可用性,在极端环境下ZooKeeper可能会丢弃一些请求,消费者程序需要重新请求才能获得结果另外在进行leader选举时集群都是不可用,所以说ZooKeeper不能保证服务可用性。(Base理论CA强一致性和最终一致性)

ZAB协议包括两种基本的模式:崩溃恢复和消息广播当整个 Zookeeper 集群刚刚启动或鍺Leader服务器宕机、重启或者网络故障导致不存在过半的服务器与 Leader 服务器保持正常通信时,所有服务器进入崩溃恢复模式首先选举产生新的 Leader 垺务器,然后集群中 Follower 服务器开始与新的 Leader 服务器进行数据同步当集群中超过半数机器与该 Leader 服务器完成数据同步之后,退出恢复模式进入消息广播模式Leader 服务器开始接收客户端的事务请求生成事物提案来进行事务请求处理。

目前有5台服务器每台服务器均没有数据,它们的编號分别是1,2,3,4,5,按编号依次启动它们的选择举过程如下:

  1. 服务器1启动,给自己投票然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息服务器1的状态一直属于Looking。
  2. 服务器2启动给自己投票,同时与之前启动的服务器1交换结果由于服务器2的编号大所以服务器2胜出,但此时投票数没有大于半数所以两个服务器的状态依然是LOOKING。
  3. 服务器3启动给自己投票,同时与之前启动的服务器1,2交换信息由于服务器3的编号最大所以服务器3胜出,此时投票数正好大于半数所以服务器3成为leader,服务器1,2成为follower
  4. 服务器4启动,给自己投票同时与之前启动的垺务器1,2,3交换信息,尽管服务器4的编号大但之前服务器3已经胜出,所以服务器4只能成为follower
  5. 服务器5启动,后面的逻辑同服务器4成为follower

单线程嘚Redis为什么快

  1. 单线程操作,避免了频繁的上下文切换
  2. 采用了非阻塞I/O多路复用机制

Redis 的数据结构及使用场景

  1. String字符串:字符串类型是 Redis 最基础的数据结構首先键都是字符串类型,而且 其他几种数据结构都是在字符串类型基础上构建的我们常使用的 set key value 命令就是字符串。常用在缓存、计数、共享Session、限速等
  2. Hash哈希:在Redis中,哈希类型是指键值本身又是一个键值对结构哈希可以用来存放用户信息,比如实现购物车
  3. List列表(双向链表):列表(list)类型是用来存储多个有序的字符串。可以做简单的消息队列的功能
  4. Set集合:集合(set)类型也是用来保存多个的字符串元素,泹和列表类型不一 样的是集合中不允许有重复元素,并且集合中的元素是无序的不能通过索引下标获取元素。利用 Set 的交集、并集、差集等操作可以计算共同喜好,全部的喜好自己独有的喜好等功能。
  5. Sorted Set有序集合(跳表实现):Sorted Set 多了一个权重参数 Score集合中的元素能够按 Score 進行排列。可以做排行榜应用取 TOP N 操作。

Redis 的数据过期策略

Redis 中数据过期策略采用定期删除+惰性删除策略

  • 定期删除策略:Redis 启用一个定时器定时監视所有的 key判断key是否过期,过期的话就删除这种策略可以保证过期的 key 最终都会被删除,但是也存在严重的缺点:每次都遍历内存中所囿的数据非常消耗 CPU 资源,并且当 key 已过期但是定时器还处于未唤起状态,这段时间内 key 仍然可以用
  • 惰性删除策略:在获取 key 时,先判断 key 是否过期如果过期则删除。这种方式存在一个缺点:如果这个 key 一直未被使用那么它一直在内存中,其实它已经过期了会浪费大量的空間。
  • 这两种策略天然的互补结合起来之后,定时删除策略就发生了一些改变不在是每次扫描全部的 key 了,而是随机抽取一部分 key 进行检查这样就降低了对 CPU 资源的损耗,惰性删除策略互补了为检查到的key基本上满足了所有要求。但是有时候就是那么的巧既没有被定时器抽取到,又没有被使用这些数据又如何从内存中消失?没关系还有内存淘汰机制,当内存不够用时内存淘汰机制就会上场。淘汰策略汾为:
  1. 当内存不足以容纳新写入数据时新写入操作会报错。(Redis 默认策略)
  2. 当内存不足以容纳新写入数据时在键空间中,移除最近最少使用的 Key(LRU推荐使用)
  3. 当内存不足以容纳新写入数据时,在键空间中随机移除某个 Key。
  4. 当内存不足以容纳新写入数据时在设置了过期时間的键空间中,移除最近最少使用的 Key这种情况一般是把 Redis 既当缓存,又做持久化存储的时候才用
  5. 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中随机移除某个 Key。
  6. 当内存不足以容纳新写入数据时在设置了过期时间的键空间中,有更早过期时间的 Key 优先移除

传统的LRU是使用栈的形式,每次都将最新使用的移入栈顶但是用栈的形式会导致执行select *的时候大量非热点数据占领头部数据,所以需要改進Redis每次按key获取一个值的时候,都会更新value中的lru字段为当前秒级别的时间戳Redis初始的实现算法很简单,随机从dict中取出五个key,淘汰一个lru字段值最尛的在3.0的时候,又改进了一版算法首先第一次随机选取的key都会放入一个pool中(pool的大小为16),pool中的key是按lru大小顺序排列的。接下来每次随机选取的keylru徝必须小于pool中最小的lru才会继续放入直到将pool放满。放满之后每次如果有新的key需要放入,需要将pool中lru最大的一个key取出淘汰的时候,直接从poolΦ选取一个lru最小的值然后将其淘汰

如何解决 Redis 缓存雪崩问题

  1. 缓存时间不一致,给缓存的失效时间加上一个随机值,避免集体失效
  2. 限流降級策略:有一定的备案比如个性推荐服务不可用了,换成热点数据推荐服务

如何解决 Redis 缓存穿透问题

  1. 存null值(缓存击穿加锁)
  2. 布隆过滤器拦截: 将所有可能的查询key 先映射到布隆过滤器中查询时先判断key是否存在布隆过滤器中,存在才继续向下执行如果不存在,则直接返回咘隆过滤器将值进行多次哈希bit存储,布隆过滤器说某个元素在可能会被误判。布隆过滤器说某个元素不在那么一定不在。

Redis的持久化机淛

Redis为了保证效率数据缓存在了内存中,但是会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件中以保证数据的持玖化。Redis的持久化策略有两种: 1. RDB:快照形式是直接把内存中的数据保存到一个dump的文件中定时保存,保存策略 当Redis需要做持久化时,Redis会fork一个孓cpu进程数子cpu进程数将数据写到磁盘上一个临时RDB文件中。当子cpu进程数完成写临时文件后将原来的RDB替换掉。 1. AOF:把所有的对Redis的服务器进行修妀的命令都存到一个文件里命令的集合。 使用AOF做持久化每一个写命令都通过write函数追加到appendonly.aof中。aof的默认策略是每秒钟fsync一次在这种配置下,就算发生故障停机也最多丢失一秒钟的数据。 缺点是对于相同的数据集来说AOF的文件体积通常要大于RDB文件的体积。根据所使用的fsync策略AOF的速度可能会慢于RDB。 Redis默认是快照RDB的持久化方式对于主从同步来说,主从刚刚连接的时候进行全量同步(RDB);全同步结束后,进行增量同步(AOF)

  1. 存储方式上:memcache会把数据全部存在内存之中,断电后会挂掉数据不能超过内存大小。redis有部分数据存在硬盘上这样能保证数据的歭久性。
  2. 数据支持类型上:memcache对数据类型的支持简单只支持简单的key-value,而redis支持五种数据类型。
  3. 用底层模型不同:它们之间底层实现方式以忣与客户端之间通信的应用协议不一样redis直接自己构建了VM机制,因为一般的系统调用系统函数的话会浪费一定的时间去移动和请求。

Redis并發竞争key的解决方案

先更新数据库再删缓存。数据库的读操作的速度远快于写操作的所以脏数据很难出现。可以对异步延时删除策略保证读请求完成以后,再进行删除操作

对于单线程阻塞式的Redis,Pipeline可以满足批量的操作把多个命令连续的发送给Redis Server,然后一一解析响应结果Pipelining可以提高批量处理性能,提升的原因主要是TCP连接中减少了“交互往返”的时间pipeline 底层是通过把所有的操作封装成流,redis有定义自己的出入輸出流在 sync() 方法执行操作,每次请求放在队列里面解析响应包。

  1. 原子性:事务是一个原子操作单元其对数据的修改,要么全都执行偠么全都不执行
  2. 一致性:事务开始前和结束后,数据库的完整性约束没有被破坏
  3. 隔离性:同一时间,只允许一个事务请求同一数据不哃的事务之间彼此没有任何干扰。
  4. 持久性:事务完成后事务对数据库的所有更新将被保存到数据库,不能回滚
  1. 脏读:事务A读取了事务B哽新的数据,然后B回滚操作那么A读取到的数据是脏数据
  2. 不可重复读:事务A多次读取同一数据,事务B在事务A多次读取的过程中对数据作叻更新并提交,导致事务A多次读取同一数据时结果不一致。
  3. 幻读:A事务读取了B事务已经提交的新增数据注意和不可重复读的区别,这裏是新增不可重复读是更改(或删除)。select某记录是否存在不存在,准备插入此记录但执行 insert 时发现此记录已存在,无法插入此时就發生了幻读。

MySQL事务隔离级别

事务隔离级别脏读不可重复读幻读读未提交是是是不可重复读否是是可重复读否否是串行化否否否

在MySQL可重复读嘚隔离级别中并不是完全解决了幻读的问题而是解决了读数据情况下的幻读问题。而对于修改的操作依旧存在幻读问题就是说MVCC对于幻讀的解决时不彻底的。 通过索引加锁间隙锁,next key lock可以解决幻读的问题

  • 最上层的服务类似其他CS结构,比如连接处理授权处理。
  • 第二层是Mysql嘚服务层包括SQL的解析分析优化,存储过程触发器视图等也在这一层实现
  • 最后一层是存储引擎的实现,类似于Java接口的实现Mysql的执行器在執行SQL的时候只会关注API的调用,完全屏蔽了不同引擎实现间的差异比如Select语句,先会判断当前用户是否拥有权限其次到缓存(内存)查询昰否有相应的结果集,如果没有再执行解析sql优化生成执行计划,调用API执行
  • undoLog 也就是我们常说的回滚日志文件 主要用于事务中执行失败,進行回滚以及MVCC中对于数据历史版本的查看。由引擎层的InnoDB引擎实现,是逻辑日志,记录数据修改被修改前的值,比如"把id='B' 修改为id = 'B2' 那么undo日志就会用來存放id ='B'的记录”。当一条数据需要更新前,会先把修改前的记录存储在undolog中,如果这个修改出现异常,,则会使用undo日志来实现回滚操作,保证事务的一致性当事务提交之后,undo log并不能立马被删除,而是会被放到待清理链表中,待判断没有事物用到该版本的信息时才可以清理相应undolog它保存了事務发生之前的数据的一个版本,用于回滚同时可以提供多版本并发控制下的读(MVCC),也即非锁定读
  • redoLog 是重做日志文件是记录数据修改之後的值,用于持久化到磁盘中redo log包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(redo log file)该部分日志昰持久的。由引擎层的InnoDB引擎实现,是物理日志,记录的是物理数据页修改的信息,比如“某个数据页上内容发生了哪些改动”当一条数据需要哽新时,InnoDB会先将数据更新,然后记录redoLog 在内存中然后找个时间将redoLog的操作执行到磁盘上的文件上。不管是否提交成功我都记录你要是回滚了,那我连回滚的修改也记录它确保了事务的持久性。
  • MVCC多版本并发控制是MySQL中基于乐观锁理论实现隔离级别的方式用于读已提交和可重复讀取隔离级别的实现。在MySQL中会在表中每一条数据后面添加两个字段:最近修改该行数据的事务ID,指向该行(undolog表中)回滚段的指针Read View判断荇的可见性,创建一个新事务时copy一份当前系统中的活跃事务列表。意思是当前不应该被本事务看到的其他事务id列表。
  • binlog由Mysql的Server层实现,是逻輯日志,记录的是sql语句的原始逻辑比如"把id='B' 修改为id = ‘B2’。binlog会写入指定大小的物理文件中,是追加写入的,当前文件写满则会创建新的文件写入 產生:事务提交的时候,一次性将事务中的sql语句,按照一定的格式记录到binlog中。用于复制和恢复在主从复制中从库利用主库上的binlog进行重播(执行日誌中记录的修改逻辑),实现主从同步。业务数据不一致或者错了用binlog恢复。
  1. redolog是在InnoDB存储引擎层产生而binlog是MySQL数据库的上层服务层产生的。
  2. 两种日誌记录的内容形式不同MySQL的binlog是逻辑日志,其记录是对应的SQL语句而innodb存储引擎层面的重做日志是物理日志。
  3. 两种日志与记录写入磁盘的时间點不同binlog日志只在事务提交完成后进行一次写入。而innodb存储引擎的重做日志在事务进行中不断地被写入并日志不是随事务提交的顺序进行寫入的。
  4. binlog不是循环使用在写满或者重启之后,会生成新的binlog文件redolog是循环使用。
  5. binlog可以作为恢复数据使用主从复制搭建,redolog作为异常宕机或鍺介质故障后的数据恢复使用

Mysql如何保证一致性和持久性

MySQL为了保证ACID中的一致性和持久性,使用了WAL(Write-Ahead Logging,先写日志再写磁盘)Redo log就是一种WAL的应用。当數据库忽然掉电再重新启动时,MySQL可以通过Redo log还原数据也就是说,每次事务提交时不用同步刷新磁盘数据文件,只需要同步刷新Redo log就足够叻

  • 共享锁(S):用法lock in share mode,又称读锁允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁若事务T对数据对象A加上S锁,则事务T可以讀A但不能修改A其他事务只能再对A加S锁,而不能加X锁直到T释放A上的S锁。这保证了其他事务可以读A但在T释放A上的S锁之前不能对A做任何修妀。
  • 排他锁(X):用法for update又称写锁,允许获取排他锁的事务更新数据阻止其他事务取得相同的数据集共享读锁和排他写锁。若事务T对数据对潒A加上X锁事务T可以读A也可以修改A,其他事务不能再对A加任何锁直到T释放A上的锁。在没有索引的情况下InnoDB只能使用表锁。

为什么选择B+树莋为索引结构

  • Hash索引:Hash索引底层是哈希表哈希表是一种以key-value存储数据的结构,所以多个数据在存储关系上是完全没有任何顺序关系的所以,对于区间查询是无法直接通过索引查询的就需要全表扫描。所以哈希索引只适用于等值查询的场景。而B+ 树是一种多路平衡查询树所以他的节点是天然有序的(左子节点小于父节点、父节点小于右子节点),所以对于范围查询的时候不需要做全表扫描
  • 二叉查找树:解決了排序的基本问题但是由于无法保证平衡,可能退化为链表
  • 平衡二叉树:通过旋转解决了平衡的问题,但是旋转操作效率太低
  • 红嫼树:通过舍弃严格的平衡和引入红黑节点,解决了 AVL旋转效率过低的问题但是在磁盘等场景下,树仍然太高IO次数太多。
  • B+树:在B树的基礎上将非叶节点改造为不存储数据纯索引节点,进一步降低了树的高度;此外将叶节点使用指针连接成链表范围查询更加高效。

B+树的葉子节点都可以存哪些东西

可能存储的是整行数据也有可能是主键的值。B+树的叶子节点存储了整行数据的是主键索引也被称之为聚簇索引。而索引B+ Tree的叶子节点存储了主键的值的是非主键索引也被称之为非聚簇索引

指一个查询语句的执行只用从索引中就能够取得,不必從数据表中读取也可以称之为实现了索引覆盖。

查询在什么时候不走(预期中的)索引

  1. 索引列参与计算,使用了函数
  2. or操作有至少一个字段沒有索引
  3. 需要回表的查询结果集过大(超过配置的范围)
  1. 减少交互次数(批量提交)
  2. 设置合理的Fetch Size(数据每次返回给客户端的条数)
  1. 程序计數器:程序计数器是一块较小的内存空间它可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里字节码解释器笁作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依賴这个计数器来完成是线程私有”的内存。
  2. Java虚拟机栈:与程序计数器一样Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同虛拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧 ,用于存储局部变量表、操作数栈、动态链接、方法出ロ等信息每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程
  3. 本地方法栈:本地方法栈(Native Method Stack)与虛拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务而本地方法栈则为虚拟機使用到的Native方法服务。
  4. Java堆:对于大多数应用来说Java堆是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域在虚拟機启动时创建。此内存区域的唯一目的就是存放对象实例几乎所有的对象实例都在这里分配内存。

HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后如果仍然存活,将会被移到Survivor区對象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁当它的年龄增加到一定程度时,就会被移动到年老代中

因为年轻代中的对象基本都是朝生夕迉的,所以在年轻代的垃圾回收算法使用的是复制算法复制算法的基本思想就是将内存分为两块,每次只用其中一块当这一块内存用唍,就将还活着的对象复制到另外一块上面复制算法不会产生内存碎片。

在GC开始的时候对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是涳的紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向年龄达到一萣值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中没有达到阈值的对象会被复制到“To”区域。经过这次GC后Eden区和From区已经被清空。这个时候“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”新的“From”就是上次GC前的“To”。不管怎样都会保证名为To嘚Survivor区域是空的。Minor GC会一直重复这样的过程直到“To”区被填满,“To”区被填满之后会将所有对象移动到年老代中。

  1. 引用计数法:引用计数法是一种简单但速度很慢的垃圾回收技术每个对象都含有一个引用计数器,当有引用连接至对象时,引用计数加1。当引用离开作用域或被置為null时,引用计数减1虽然管理引用计数的开销不大,但这项开销在整个程序生命周期中将持续发生。垃圾回收器会在含有全部对象的列表上遍曆,当发现某个对象引用计数为0时,就释放其占用的空间
  2. 可达性分析算法:这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始點,从这些节点开始向下搜索搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(用图论的话来说就是从GC Roots到这个对象鈈可达)时,则证明此对象是不可用的
  1. CMS收集器是获取最短回收停顿时间为目标的收集器,因为CMS工作时GC工作线程与用户线程可以并发执荇,以此来达到降低手机停顿时间的目的(只有初始标记和重新标记会STW)但是CMS收集器对CPU资源非常敏感。在并发阶段虽然不会导致用户線程停顿,但是会占用CPU资源而导致引用程序变慢总吞吐量下降。
  2. CMS仅作用于老年代是基于标记清除算法,所以清理的过程中会有大量的涳间碎片
  3. CMS收集器无法处理浮动垃圾,由于CMS并发清理阶段用户线程还在运行伴随程序的运行自热会有新的垃圾不断产生,这一部分垃圾絀现在标记过程之后CMS无法在本次收集中处理它们,只好留待下一次GC时将其清理掉
  4. G1是一款面向服务端应用的垃圾收集器,适用于多核处悝器、大内存容量的服务端系统G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短STW的停顿时间它满足短时间停顿的哃时达到一个高的吞吐量。
  5. 从JDK 9开始G1成为默认的垃圾回收器。当应用有以下任何一种特性时非常适合用G1:Full GC持续时间太长或者太频繁;对象嘚创建速率和存活率变动很大;应用不希望停顿时间长(长于0.5s甚至1s)
  6. G1将空间划分成很多块(Region),然后他们各自进行回收堆比较大的时候可鉯采用,采用复制算法碎片化问题不严重。整体上看属于标记整理算法,局部(region之间)属于复制算法
  7. G1 需要记忆集 (具体来说是卡表)来记录新生玳和老年代之间的引用关系,这种数据结构在 G1 中需要占用大量的内存可能达到整个堆内存容量的 20% 甚至更多。而且 G1 中维护记忆集的成本较高带来了更高的执行负载,影响效率所以 CMS 在小内存应用上的表现要优于 G1,而大内存应用上 G1 更有优势大小内存的界限是6GB到8GB。
  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象
  2. 方法区中类静态属性引用的对象。
  3. 方法区中常量引用的对象
  4. 本地方法栈中JNI(即一般说的Native方法)引用的对象。

在执行垃圾收集算法时Java应用程序的其他所有除了垃圾收集收集器线程之外的线程都被挂起。此时系统只能允许GC线程进行運行,其他线程则会全部暂停等待GC线程执行完毕后才能再次运行。这些工作都是由虚拟机在后台自动发起和自动完成的是在用户不可見的情况下把用户正常工作的线程全部停下来,这对于很多的应用程序尤其是那些对于实时性要求很高的程序来说是难以接受的。

但不昰说GC必须STW,你也可以选择降低运行速度但是可以并发执行的收集算法这取决于你的业务。

  1. 停止-复制:先暂停程序的运行,然后将所有存活的對象从当前堆复制到另一个堆,没有被复制的对象全部都是垃圾当对象被复制到新堆时,它们是一个挨着一个的,所以新堆保持紧凑排列,然后僦可以按前述方法简单,直接的分配了。缺点是一浪费空间,两个堆之间要来回倒腾,二是当程序进入稳定态时,可能只会产生极少的垃圾,甚至不產生垃圾,尽管如此,复制式回收器仍会将所有内存自一处复制到另一处
  2. 标记-清除:同样是从堆栈和静态存储区出发,遍历所有的引用,进而找絀所有存活的对象。每当它找到一个存活的对象,就会给对象一个标记,这个过程中不会回收任何对象只有全部标记工作完成的时候,清理动莋才会开始。在清理过程中,没有标记的对象会被释放,不会发生任何复制动作所以剩下的堆空间是不连续的,垃圾回收器如果要希望得到连續空间的话,就得重新整理剩下的对象。
  3. 标记-整理:它的第一个阶段与标记/清除算法是一模一样的均是遍历GC Roots,然后将存活的对象标记移動所有存活的对象,且按照内存地址次序依次排列然后将末端内存地址以后的内存全部回收。因此第二阶段才称为整理阶段。
  4. 分代收集算法:把Java堆分为新生代和老年代然后根据各个年代的特点采用最合适的收集算法。新生代中对象的存活率比较低,所以选用复制算法老年代中对象存活率高且没有额外空间对它进行分配担保,所以使用“标记-清除”或“标记-整理”算法进行回收
  1. 调用System.gc时,系统建议執行Full GC但是不必然执行
  2. 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
  3. 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存则把该对象转存到老年代,且老年代的可用内存小于该对象大小

类从被加载到虚拟机内存中开始到卸载出内存为止,它的整个生命周期包括:加载、驗证、准备、解析、初始化、使用和卸载7个阶段

  1. 加载:通过一个类的全限定名来获取定义此类的二进制字节流,将这个字节流所代表的靜态存储结构转化为方法区的运行时数据结构在内存中生成一个代表这个类的Class对象,作为方法去这个类的各种数据的访问入口
  2. 验证:验證是连接阶段的第一步这一阶段的目的是确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟自身的安全
  3. 准备:准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法去中进行分配这时候进行内存分配嘚仅包括类变量(static),而不包括实例变量实例变量将会在对象实例化时随着对象一起分配在Java堆中。
  4. 解析:解析阶段是虚拟机将常量池内嘚符号(Class文件内的符号)引用替换为直接引用(指针)的过程
  5. 初始化:初始化阶段是类加载过程的最后一步,开始执行类中定义的Java程序玳码(字节码)

双亲委派的意思是如果一个类加载器需要加载类,那么首先它会把这个类请求委派给父类加载器去完成每一层都是如此。一直递归到顶层当父加载器无法完成这个请求时,子类才会尝试去加载

JVM锁优化和膨胀过程

  1. 自旋锁:自旋锁其实就是在拿锁时发现巳经有线程拿了锁,自己如果去拿会阻塞自己这个时候会选择进行一次忙循环尝试。也就是不停循环看是否能等到上个线程自己释放锁自适应自旋锁指的是例如第一次设置最多自旋10次,结果在自旋的过程中成功获得了锁那么下一次就可以设置成最多自旋20次。
  2. 锁粗化:虛拟机通过适当扩大加锁的范围以避免频繁的拿锁释放锁的过程
  3. 锁消除:通过逃逸分析发现其实根本就没有别的线程产生竞争的可能(別的线程没有临界量的引用),或者同步块内进行的是原子操作而“自作多情”地给自己加上了锁。有可能虚拟机会直接去掉这个锁
  4. 偏向锁:在大多数的情况下,锁不仅不存在多线程的竞争而且总是由同一个线程获得。因此为了让线程获得锁的代价更低引入了偏向锁嘚概念偏向锁的意思是如果一个线程获得了一个偏向锁,如果在接下来的一段时间中没有其他线程来竞争锁那么持有偏向锁的线程再佽进入或者退出同一个同步代码块,不需要再次进行抢占锁和释放锁的操作
  5. 轻量级锁:当存在超过一个线程在竞争同一个同步代码块时,会发生偏向锁的撤销当前线程会尝试使用CAS来获取锁,当自旋超过指定次数(可以自定义)时仍然无法获得锁此时锁会膨胀升级为重量级鎖。
  6. 重量级锁:重量级锁依赖对象内部的monitor锁来实现而monitor又依赖操作系统的MutexLock(互斥锁)。当系统检查到是重量级锁之后会把等待想要获取鎖的线程阻塞,被阻塞的线程不会消耗CPU但是阻塞或者唤醒一个线程,都需要通过操作系统来实现

什么情况下需要开始类加载过程的第┅个阶段加载

  1. 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候以及调鼡一个类的静态方法的时候。
  2. 使用java.lang.reflect包的方法对类进行反射调用的时候如果类没有进行过初始化,则需要先触发其初始化
  3. 当初始化一个類的时候,如果发现其父类还没有进行过初始化则需要先触发其父类的初始化。
  4. 当虚拟机启动时用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类

i++操作的字节码指令

  1. 将int类型常量加载到操作数栈顶
  2. 将int类型数值从操作数栈顶取出,并存储到到局部变量表的第1个Slot中
  3. 将int类型变量从局部变量表的第1个Slot中取出并放到操作数栈顶
  4. 将局部变量表的第1个Slot中的int类型变量加1
  5. 表示将int类型數值从操作数栈顶取出,并存储到到局部变量表的第1个Slot中即i中

由于HashMap是线程不同步的,虽然处理数据的效率高但是在多线程的情况下存茬着安全问题,因此设计了CurrentHashMap来解决多线程安全问题

HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作就昰rehash,这个会重新将原数组的内容重新hash到新的扩容数组中在多线程的环境下,存在同时其他的元素也在进行put操作如果hash值相同,可能出现哃时在同一数组下用链表表示造成闭环,导致在get时会出现死循环所以HashMap是线程不安全的。

HashMap的环:若当前线程此时获得ertry节点但是被线程Φ断无法继续执行,此时线程二进入transfer函数并把函数顺利执行,此时新表中的某个位置有了节点之后线程一获得执行权继续执行,因为並发transfer所以两者都是扩容的同一个链表,当线程一执行到e.next = new table[i] 的时候由于线程二之前数据迁移的原因导致此时new table[i] 上就有ertry存在,所以线程一执行嘚时候会将next节点,设置为自己导致自己互相使用next引用对方,因此产生链表导致死循环。

table数组在写操作put,remove扩容的时候,会对Segment加锁所以仅仅影响这个Segment,不同的Segment还是可以并发的所以解决了线程的安全问题,同时又采用了分段锁也提升了并发的效率在JDK1.8版本中,ConcurrentHashMap摒弃叻Segment的概念而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作整个看起来就像是优化过且线程安全的HashMap。

HashMap如果我想要讓自己的Object作为K应该怎么办

  1. 重写hashCode()是因为需要计算存储数据的存储位置需要注意不要试图从散列码计算中排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的Hash碰撞;
  2. 重写equals()方法需要遵守自反性、对称性、传递性、一致性以及对于任何非null的引用值x,x.equals(null)必须返回false的这几个特性目的是为了保证key在哈希表中的唯一性;

volatile在多处理器开发中保证了共享变量的“ 可见性”。可见性的意思是当一个线程修改一个共享变量时另外一个线程能读到这个修改的值。(共享内存私有内存)

CAS是英文单词CompareAndSwap的缩写,中文意思是:比较并替换CAS需要有3个操作数:内存地址V,旧的预期值A即将要更新的目标值B。CAS指令执行时当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B否则就什么都不做。整个比较并替换的操作是一个原子操作如 Intel 处理器,比较并交换通过指令的 cmpxchg 系列实现

如果在这段期间它的值曾经被妀成了B,后来又被改回为A那CAS操作就会误认为它从来没有被改变过。Java并发包为了解决这个问题提供了一个带有标记的原子引用类“AtomicStampedReference”,咜可以通过控制变量值的版本来保证CAS的正确性

  1. synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁并且可以主动尝试去获取锁。
  2. synchronized会自動释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁)Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁
  3. 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁线程2线程等待。如果线程1阻塞线程2则会一直等待下去,而Lock锁就不一定会等待下去如果尝试获取不到锁,线程可以不用一直等待就结束了
  4. synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
  5. Lock鎖适合大量同步的代码的同步问题synchronized锁适合代码少量的同步问题。

AQS内部有3个对象一个是state(用于计数器,类似gc的回收计数器)一个是线程标记(当前线程是谁加锁的),一个是阻塞队列

如何指定多个线程的执行顺序

  1. 设定一个 orderNum,每个线程执行结束之后更新 orderNum,指明下一个偠执行的线程并且唤醒所有的等待线程。
  2. 在每一个线程的开始要 while 判断 orderNum 是否等于自己的要求值,不是则 wait,是则执行本线程
  1. 减少创建囷销毁线程的次数,每个工作线程都可以被重复利用可执行多个任务。
  2. 可以根据系统的承受能力调整线程池中工作线程的数目,放置洇为消耗过多的内存而把服务器累趴下
  1. corePoolSize:指定了线程池中的线程数量
  2. keepAliveTime:线程池维护线程所允许的空闲时间
  3. workQueue:任务队列,被提交但尚未被執行的任务
  4. threadFactory:线程工厂,用于创建线程一般用默认的即可。
  5. handler:拒绝策略当任务太多来不及处理,如何拒绝任务

线程池的线程数量怎么确定

  1. 一般来说,如果是CPU密集型应用则线程池大小设置为N+1。
  2. 一般来说如果是IO密集型应用,则线程池大小设置为2N+1
  3. 在IO优化中,线程等待时间所占比例越高需要越多线程,线程CPU时间所占比例越高需要越少线程。这样的估算公式可能更适合:最佳线程数目 = ((线程等待時间+线程CPU时间)/线程CPU时间 )* CPU数目

未精确定义字节Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替而boolean数组将会被編码成Java虚拟机的byte数组,每个元素boolean元素占8位

  1. 第二级缓存:早期提前暴露的对象缓存earlySingletonObjects。(属性还没有值对象也没有被初始化)

创建Bean的整个过程

  1. getBean方法肯定不陌生必经之路,然后调用doGetBean进来以后首先会执行transformedBeanName找别名,看你的Bean上面是否起了别名然后进行很重要的一步,getSingleton这段代码僦是从你的单例缓存池中获取Bean的实例。那么你第一次进来肯定是没有的缓存里肯定是拿不到的。也就是一级缓存里是没有的那么它怎麼办呢?他会尝试去二级缓存中去拿但是去二级缓存中拿并不是无条件的,首先要判断isSingletonCurrentlyInCreation(beanName)他要看你这个对象是否正在创建当中如果不是矗接就退出该方法,如果是的话他就会去二级缓存earlySingletonObjects里面取,如果没拿到它还接着判断allowEarlyReference这个东西是否为true。它的意思是说是否允许让你從单例工厂对象缓存中去拿对象。默认为true好了,此时如果进来那么就会通过singletonFactory.getObject()去单例工厂缓存中去拿然后将缓存级别提升至二级缓存也僦早期暴露的缓存。
  2. getSingleton执行完以后会走dependsOn方法判断是否有dependsOn标记的循环引用,有的话直接卡死抛出异常。比如说A依赖于BB依赖于A 通过dependsOn注解去指定。此时执行到这里就会抛出异常这里所指并非是构造函数的循环依赖。
  3. beforeSingletonCreation在这里方法里就把你的对象标记为了早期暴露的对象。提湔暴露对象用于创建Bean的实例
  4. 紧接着就走创建Bean的流程开始。在创建Bean之前执行了一下resolveBeforeInstantiation它的意思是说,代理AOPBean定义注册信息但是这里并不是实際去代理你的对象因为对象还没有被创建。只是代理了Bean定义信息还没有被实例化。把Bean定义信息放进缓存以便我想代理真正的目标对潒的时候,直接去缓存里去拿
  5. 接下来就真正的走创建Bean流程,首先走进真正做事儿的方法doCreateBean然后找到createBeanInstance这个方法在这里面它将为你创建你的Bean實例信息(Bean的实例)。如果说创建成功了那么就把你的对象放入缓存中去(将创建好的提前曝光的对象放入singletonFactories三级缓存中)将对象从二级緩存中移除因为它已经不是提前暴露的对象了。但是如果说在createBeanInstance这个方法中在创建Bean的时候它会去检测你的依赖关系,会去检测你的构造器然后,如果说它在创建A对象的时候发现了构造器里依赖了B,然后它又会重新走getBean的这个流程当在走到这里的时候,又发现依赖了A此时僦会抛出异常为什么会抛出异常,因为走getBean的时候他会去从你的单例缓存池中去拿,因为你这里的Bean还没有被创建好自然不会被放进缓存中,所以它是在缓存中拿不到B对象的反过来也是拿不到A对象的。造成了死循环故此直接抛异常这就是为什么Spring IOC不能解决构造器循环依賴的原因。因为你还没来的急放入缓存你的对象是不存在的所以不能创建。同理@Bean标注的循环依赖方法也是不能解决的跟这个同理。那麼多例就更不能解决了为什么?因为在走createBeanInstance的时候会判断是否是单例的Bean定义信息mbd.isSingleton();如果是才会进来。所以多例的Bean压根就不会走进来而昰走了另一段逻辑,这里不做介绍至此,构造器循环依赖和@Bean的循环依赖还有多例Bean的循环依赖为什么不能解决已经解释清楚然后如果说,Bean创建成功了那么会走后面的逻辑。
  6. 将创建好的Bean放入缓存addSingletonFactory方法就是将你创建好的Bean放入三级缓存中。并且移除早期暴露的对象
  7. 通过populateBean给屬性赋值,我们知道创建好的对象,并不是一个完整的对象里面的属性还没有被赋值。所以这个方法就是为创建好的Bean为它的属性赋值并且调用了我们实现的的XXXAware接口进行回调初始化,然后调用我们实现的Bean的后置处理器,给我们最后一次机会去修改Bean的属性

Spring如何解决循環依赖问题

Spring使用了三级缓存解决了循环依赖的问题。在populateBean()给属性赋值阶段里面Spring会解析你的属性并且赋值,当发现A对象里面依赖了B,此时叒会走getBean方法但这个时候,你去缓存中是可以拿的到的因为我们在对createBeanInstance对象创建完成以后已经放入了缓存当中,所以创建B的时候发现依赖A直接就从缓存中去拿,此时B创建完A也创建完,一共执行了4次至此Bean的创建完成,最后将创建好的Bean放入单例缓存池中

  1. BeanFactory是Spring里面最低层的接口,提供了最简单的容器的功能只提供了实例化对象和拿对象的功能。
  2. ApplicationContext应用上下文继承BeanFactory接口,它是Spring的一各更高级的容器提供了更哆的有用的功能。如国际化访问资源,载入多个(有继承关系)上下文 使得每一个上下文都专注于一个特定的层次,消息发送、响应機制AOP等。

动态代理的实现方式AOP的实现方式

  1. JDK动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理
  2. CGlib動态代理:利用ASM(开源的Java字节码编辑库,操作字节码)开源包将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理
  3. 区别:JDK代理只能对实现接口的类生成代理;CGlib是针对类实现代理,对指定的类生成一个子类并覆盖其中的方法,这种通过继承类的实现方式鈈能代理final修饰的类。

Spring的的事务传播机制

  1. REQUIRED(默认):支持使用当前事务如果当前事务不存在,创建一个新事务
  2. SUPPORTS:支持使用当前事务,如果当前事务不存在则不使用事务。
  3. MANDATORY:强制支持使用当前事务,如果当前事务不存在则抛出Exception。
  4. REQUIRES_NEW:创建一个新事务如果当前事务存在,把当前事务挂起
  5. NOT_SUPPORTED:无事务执行,如果当前事务存在把当前事务挂起。
  6. NEVER:无事务执行如果当前有事务则抛出Exception。
  7. NESTED:嵌套事务如果当湔事务存在,那么在嵌套的事务中执行如果当前事务不存在,则表现跟REQUIRED一样

解耦,异步处理削峰/限流

Kafka的文件存储机制

broker发送消息,消費者通过topic读取数据然而topic在物理层面又能以partition为分组,一个topic可以分成若干个partitionpartition还可以细分为segment,一个partition物理上由多个segment组成segment文件由两部分组成,汾别为“.index”文件和“.log”文件分别表示为segment索引文件和数据文件。这两个文件的命令规则为:partition全局的第一个segment从0开始后续每个segment文件名为上一個segment文件最后一条消息的offset值。

Kafka 如何保证可靠性

如果我们要往 Kafka 对应的主题发送消息我们需要通过 Producer 完成。前面我们讲过 Kafka 主题对应了多个分区烸个分区下面又对应了多个副本;为了让用户设置数据可靠性, Kafka 在 Producer 里面提供了消息确认机制也就是说我们可以通过配置来决定消息发送箌对应分区的几个副本才算消息发送成功。可以在定义 Producer 时通过 acks 参数指定这个参数支持以下三种值:

  • acks = 0:意味着如果生产者能够通过网络把消息发送出去,那么就认为消息已成功写入 Kafka 在这种情况下还是有可能发生错误,比如发送的对象无能被序列化或者网卡发生故障但如果是分区离线或整个集群长时间不可用,那就不会收到任何错误在 acks=0 模式下的运行速度是非常快的(这就是为什么很多基准测试都是基于這个模式),你可以得到惊人的吞吐量和带宽利用率不过如果选择了这种模式, 一定会丢失一些消息
  • acks = 1:意味若 Leader 在收到消息并把它写入箌分区数据文件(不一定同步到磁盘上)时会返回确认或错误响应。在这个模式下如果发生正常的 Leader 选举,生产者会在选举时收到一个 LeaderNotAvailableException 异瑺如果生产者能恰当地处理这个错误,它会重试发送悄息最终消息会安全到达新的 Leader 那里。不过在这个模式下仍然有可能丢失数据比洳消息已经成功写入 Leader,但在消息被复制到 follower 副本之前 Leader发生崩溃
  • 参数结合起来,就可以决定在返回确认前至少有多少个副本能够收到悄息苼产者会一直重试直到消息被成功提交。不过这也是最慢的做法因为生产者在继续发送其他消息之前需要等待所有副本都收到当前的消息。

Kafka最初考虑的问题是customer应该从brokes拉取消息还是brokers将消息推送到consumer,也就是pull还push在这方面,Kafka遵循了一种大部分消息系统共同的传统的设计:producer将消息推送到brokerconsumer从broker拉取消息。push模式下当broker推送的速率远大于consumer消费的速率时,consumer恐怕就要崩溃了最终Kafka还是选取了传统的pull模式。Pull模式的另外一个好處是consumer可以自主决定是否批量的从broker拉取数据Pull有个缺点是,如果broker没有可供消费的消息将导致consumer不断在循环中轮询,直到新消息到t达为了避免这点,Kafka有个参数可以让consumer阻塞知道新消息到达

Kafka是如何实现高吞吐率的

  1. 顺序读写:kafka的消息是不断追加到文件中的,这个特性使kafka可以充分利鼡磁盘的顺序读写性能
  2. 零拷贝:跳过“用户缓冲区”的拷贝建立一个磁盘空间和内存的直接映射,数据不再复制到“用户态缓冲区”
  3. 文件分段:kafka的队列topic被分为了多个区partition每个partition又分为多个段segment,所以一个队列中的消息实际上是保存在N多个片段文件中
  4. 批量发送:Kafka允许进行批量发送消息先将消息缓存在内存中,然后一次请求批量发送出去
  5. 数据压缩:Kafka还支持对消息集合进行压缩Producer可以通过GZIP或Snappy格式对消息集合进行压縮

Kafka判断一个节点还活着的两个条件

  1. 节点必须可以维护和 ZooKeeper 的连接,Zookeeper 通过心跳机制检查每个节点的连接
  2. 如果节点是个 follower,他必须能及时的同步 leader 的写操作延时不能太久
  1. 失败自动切换,当出现失败重试其它服务器。通常用于读操作但重试会带来更长延迟。可通过 retries="2" 来设置重试次数
  2. 快速失败只发起一次调用,失败立即报错通常用于非幂等性的写操作,比如新增记录
  3. 失败安全,出现异常时直接忽略。通常用于写叺审计日志等操作
  4. 失败自动恢复,后台记录失败请求定时重发。通常用于消息通知操作
  5. 并行调用多个服务器,只要一个成功即返回通常用于实时性要求较高的读操作,但需要浪费更多服务资源可通过 forks="2" 来设置最大并行数。
  6. 广播调用所有提供者逐个调用,任意一台報错则报错通常用于通知所有提供者更新缓存或日志等本地资源信息

Dubbo注册中心挂了还可以继续通信么

可以,因为刚开始初始化的时候消费者会将提供者的地址等信息拉取到本地缓存,所以注册中心挂了可以继续通信

Dubbo框架设计结构

  1. 服务接口层:该层是与实际业务逻辑相關的,根据服务提供方和服务消费方的业务设计对应的接口和实现
  2. 配置层:对外配置接口,以ServiceConfig和ReferenceConfig为中心可以直接new配置类,也可以通过spring解析配置生成配置类
  3. 服务代理层:服务接口透明代理,生成服务的客户端Stub和服务器端Skeleton以ServiceProxy为中心,扩展接口为ProxyFactory
  4. 服务注册层:封装服务哋址的注册与发现,以服务URL为中心扩展接口为RegistryFactory、Registry和RegistryService。可能没有服务注册中心此时服务提供方直接暴露服务。
  5. 集群层:封装多个提供者嘚路由及负载均衡并桥接注册中心,以Invoker为中心扩展接口为Cluster、Directory、Router和LoadBalance。将多个服务提供方组合为一个服务提供方实现对服务消费方来透奣,只需要与一个服务提供方进行交互
  6. 远程调用层:封将RPC调用,以Invocation和Result为中心扩展接口为Protocol、Invoker和Exporter。Protocol是服务域它是Invoker暴露和引用的主功能入ロ,它负责Invoker的生命周期管理Invoker是实体域,它是Dubbo的核心模型其它模型都向它靠扰,或转换成它它代表一个可执行体,可向它发起invoke调用咜有可能是一个本地的实现,也可能是一个远程的实现也可能一个集群实现。
  1. cpu进程数是操作系统资源分配的最小单位线程是CPU任务调度嘚最小单位。一个cpu进程数可以包含多个线程所以cpu进程数和线程都是一个时间段的描述,是CPU工作时间段的描述不过是颗粒大小不同。
  2. 不哃cpu进程数间数据很难共享同一cpu进程数下不同线程间数据很易共享。
  3. 每个cpu进程数都有独立的代码和数据空间cpu进程数要比线程消耗更多的計算机资源。线程可以看做轻量级的cpu进程数同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器线程之间切换的开销小。
  4. cpu进程数间不会相互影响一个线程挂掉将导致整个cpu进程数挂掉。
  5. 系统在运行的时候会为每个cpu进程数分配不同的内存空间;洏对线程而言除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属cpu进程数的资源)线程组之间只能共享资源。

cpu进程数由cpu進程数控制块(PCB)、程序段、数据段三部分组成

  1. 无名管道:半双工的,即数据只能在一个方向上流动只能用于具有亲缘关系的cpu进程数の间的通信,可以看成是一种特殊的文件对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件并不属于其他任何文件系統,并且只存在于内存中
  2. FIFO命名管道:FIFO是一种文件类型,可以在无关的cpu进程数之间交换数据与无名管道不同,FIFO有路径名与之相关联它鉯一种特殊设备文件形式存在于文件系统中。
  3. 消息队列:消息队列是消息的链接表,存放在内核中一个消息队列由一个标识符(即队列ID)来标识。
  4. 信号量:信号量是一个计数器信号量用于实现cpu进程数间的互斥与同步,而不是用于存储cpu进程数间通信数据
  5. 共享内存:共享内存指两个或多个cpu进程数共享一个给定的存储区,一般配合信号量使用

cpu进程数间五种通信方式的比较

  1. 管道:速度慢,容量有限只有父子cpu进程数能通讯。
  2. FIFO:任何cpu进程数间都能通讯但速度慢。
  3. 消息队列:容量受到系统限制且要注意第一次读的时候,要考虑上一次没有讀完数据的问题
  4. 信号量:不能传递复杂消息,只能用来同步
  5. 共享内存区:能够很容易控制容量,速度快但要保持同步,比如一个cpu进程数在写的时候另一个cpu进程数要注意读写的问题,相当于线程中的线程安全当然,共享内存区同样可以用作线程间通讯不过没这个必要,线程间本来就已经共享了同一cpu进程数内的一块内存
  1. 互斥条件:一个资源每次只能被一个线程使用;
  2. 请求与保持条件:一个线程因請求资源而阻塞时,对已获得的资源保持不放;
  3. 不剥夺条件:cpu进程数已经获得的资源在未使用完之前,不能强行剥夺;
  4. 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系
  1. 破坏“请求和保持”条件:让cpu进程数在申请资源时,一次性申请所有需要用到的资源不要一次一次来申请,当申请的资源有一些没空那就让线程等待。不过这个方法比较浪费资源cpu进程数可能经常处于饥饿状态。还囿一种方法是要求cpu进程数在申请资源前,要释放自己拥有的资源
  2. 破坏“不可抢占”条件:允许cpu进程数进行抢占,方法一:如果去抢资源被拒绝,就释放自己的资源方法二:操作系统允许抢,只要你优先级大可以抢到。
  3. 破坏“循环等待”条件:将系统中的所有资源統一编号cpu进程数可在任何时刻提出资源申请,但所有申请必须按照资源的编号顺序提出(指定获取锁的顺序顺序加锁)。
  1. Get是不安全的因为在传输过程,数据被放在请求的URL中;Post的所有操作对用户来说都是不可见的
  2. Get传送的数据量较小,这主要是因为受URL长度限制;Post传送的數据量较大一般被默认为不受限制。
  3. Get限制Form表单的数据集的值必须为ASCII字符;而Post支持整个ISO10646字符集
  4. Get执行效率却比Post方法好。Get是form提交的默认方法
  5. GET产生一个TCP数据包;POST产生两个TCP数据包。(非必然客户端可灵活决定)

Http请求的完全过程

  1. 浏览器根据域名解析IP地址(DNS),并查DNS缓存
  2. 浏览器与WEB服務器建立一个TCP连接
  1. TCP面向连接,UDP是无连接的即发送数据之前不需要建立连接。
  2. TCP提供可靠的服务也就是说,通过TCP连接传送的数据无差错,不丢失不重复,且按序到达;UDP尽最大努力交付即不保证可靠交付。
  3. TCP面向字节流实际上是TCP把数据看成一连串无结构的字节流,UDP是面向報文的UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用如IP电话,实时视频会议等)
  4. 每一条TCP连接只能是点到点的UDP支持一对一,一对多多对一和多对多的交互通信。
  5. TCP首部开销20字节UDP的首部开销小,只有8个字节
  6. TCP的逻辑通信信道是全双笁的可靠信道,UDP则是不可靠信道
  • TCP的优点: 可靠,稳定 TCP的可靠体现在TCP在传递数据之前会有三次握手来建立连接,而且在数据传递时有確认、窗口、重传、拥塞控制机制,在数据传完后还会断开连接用来节约系统资源。 TCP的缺点: 慢效率低,占用系统资源高易被攻击 TCP茬传递数据之前,要先建连接这会消耗时间,而且在数据传递时确认机制、重传机制、拥塞控制机制等都会消耗大量的时间,而且要茬每台设备上维护所有的传输连接事实上,每个连接都会占用系统的CPU、内存等硬件资源 而且,因为TCP有确认机制、三次握手机制这些吔导致TCP容易被人利用,实现DOS、DDOS、CC等攻击
  • UDP的优点: 快,比TCP稍安全 UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制UDP是一个无状态的传输協议,所以它在传递数据时非常快没有TCP的这些机制,UDP较TCP被攻击者利用的漏洞就要少一些但UDP也是无法避免攻击的,比如:UDP Flood攻击…… UDP的缺點: 不可靠不稳定 因为UDP没有TCP那些可靠的机制,在数据传递时如果网络质量不好,就会很容易丢包 基于上面的优缺点,那么: 什么时候应该使用TCP: 当对网络通讯质量有要求的时候比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用比如HTTP、HTTPS、FTP等傳输文件的协议,POP、SMTP等邮件传输的协议 在日常生活中,常见使用TCP协议的应用如下: 浏览器用的HTTP FlashFXP,用的FTP Outlook用的POP、SMTP Putty,用的Telnet、SSH QQ文件传输什麼时候应该使用UDP: 当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快这时就可以使用UDP。 比如日常生活中,常见使用UDP协議的应用如下: QQ语音 QQ视频 TFTP
  • 第一次握手:建立连接时,客户端发送syn包(syn=x)到服务器并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)
  • 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1)同时自己也发送一个SYN包(syn=y),即SYN+ACK包此时服务器进入SYN_RECV状态;
  • 第三次握手:客户端收箌服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1)此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态完成三次握手。

TCP是一个双向通信协议通信双方都有能力发送信息,并接收响应如果只是两次握手, 至多只有连接发起方的起始序列号能被确认 另一方选择的序列号则得不箌确认

  1. 客户端cpu进程数发出连接释放报文,并且停止发送数据释放数据报文首部,FIN=1其序列号为seq=u(等于前面已经传送过来的数据的最后一個字节的序号加1),此时客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定FIN报文段即使不携带数据,也要消耗一个序号
  2. 服务器收到连接释放报文,发出确认报文ACK=1,ack=u+1并且带上自己的序列号seq=v,此时服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用cpu进程数客户端向服务器的方向就释放了,这时候处于半关闭状态即客户端已经没有数据要发送了,但是服务器若发送数据客户端依然要接受。这个状态还偠持续一段时间也就是整个CLOSE-WAIT状态持续的时间。
  3. 客户端收到服务器的确认请求后此时,客户端就进入FIN-WAIT-2(终止等待2)状态等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
  4. 服务器将最后的数据发送完毕后就向客户端发送连接释放报文,FIN=1ack=u+1,由于在半关闭状态服务器很可能又发送了一些数据,假定此时的序列号为seq=w此时,服务器就进入了LAST-ACK(最后确认)状态等待客户端的確认。
  5. 客户端收到服务器的连接释放报文后必须发出确认,ACK=1ack=w+1,而自己的序列号是seq=u+1此时,客户端就进入了TIME-WAIT(时间等待)状态注意此時TCP连接还没有释放,必须经过2??MSL(最长报文段寿命)的时间后当客户端撤销相应的TCB后,才进入CLOSED状态
  6. 服务器只要收到了客户端发出的確认,立即进入CLOSED状态同样,撤销TCB后就结束了这次的TCP连接。可以看到服务器结束TCP连接的时间要比客户端早一些

为什么连接的时候是三佽握手,关闭的时候却是四次握手

因为当Server端收到Client端的SYN连接请求报文后可以直接发送SYN+ACK报文。其中ACK报文是用来应答的SYN报文是用来同步的。泹是关闭连接时当Server端收到FIN报文时,很可能并不会立即关闭SOCKET所以只能先回复一个ACK报文,告诉Client端"你发的FIN报文我收到了"。只有等到我Server端所囿的报文都发送完了我才能发送FIN报文,因此不能一起发送故需要四步握手。

  1. 选择排序:选择排序与冒泡排序有点像只不过选择排序烸次都是在确定了最小数的下标之后再进行交换,大大减少了交换的次数
  2. 插入排序:将一个记录插入到已排序的有序表中从而得到一个噺的,记录数增1的有序表
  3. 快速排序:通过一趟排序将序列分成左右两部分其中左半部分的的值均比右半部分的值小,然后再分别对左右蔀分的记录进行排序直到整个序列有序。
  1. 堆排序:假设序列有n个元素,先将这n建成大顶堆然后取堆顶元素,与序列第n个元素交换然后調整前n-1元素,使其重新成为堆然后再取堆顶元素,与第n-1个元素交换再调整前n-2个元素...直至整个序列有序。
  2. 希尔排序:先将整个待排记录序列分割成为若干子序列分别进行直接插入排序待整个序列中的记录基本有序时再对全体记录进行一次直接插入排序。
  3. 归并排序:把有序表划分成元素个数尽量相等的两半把两半元素分别排序,两个有序表合并成一个

高并发系统的设计与实现

在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流

  • 缓存:缓存比较好理解,在大型高并发系统中如果没有缓存数据库将分分钟被爆,系统也会瞬間瘫痪使用缓存不单单能够提升系统访问速度、提高并发访问量,也是保护数据库、保护系统的有效方式大型网站一般主要是“读”,缓存的使用很容易被想到在大型“写”系统中,缓存也常常扮演者非常重要的角色比如累积一些数据批量写入,内存里面的缓存队列(生产消费)以及HBase写数据的机制等等也都是通过缓存提升系统的吞吐量或者实现系统的保护措施。甚至消息中间件你也可以认为是┅种分布式的数据缓存。
  • 降级:服务降级是当服务器压力剧增的情况下根据当前业务情况及流量对一些服务和页面有策略的降级,以此釋放服务器资源以保证核心任务的正常运行降级往往会指定不同的级别,面临不同的异常等级执行不同的处理根据服务方式:可以拒接服务,可以延迟服务也有时候可以随机服务。根据服务范围:可以砍掉某个功能也可以砍掉某些模块。总之服务降级需要根据不同嘚业务需求采用不同的降级策略主要的目的就是服务虽然有损但是总比没有好。
  • 限流:限流可以认为服务降级的一种限流就是限制系統的输入和输出流量已达到保护系统的目的。一般来说系统的吞吐量是可以被测算的为了保证系统的稳定运行,一旦达到的需要限制的閾值就需要限制流量并采取一些措施以完成限制流量的目的。比如:延迟处理拒绝处理,或者部分拒绝处理等等

常见的限流算法有計数器、漏桶和令牌桶算法。漏桶算法在分布式环境中消息中间件或者Redis都是可选的方案发放令牌的频率增加可以提升整体数据处理的速喥,而通过每次获取令牌的个数增加或者放慢令牌的发放速度和降低整体数据处理速度而漏桶不行,因为它的流出速率是固定的程序處理速度也是固定的。

秒杀并发情况下库存为负数问题

  1. update语句在更新的同时加上一个条件

USS通讯协议的功能所有的西门子變频器都带有一个RS485通讯口,PLC作为主站多允许31个变频器作为通讯连路中的从站,根据各变频器的地址或者采用广播方式可以访问需要通訊的变频器,只有主站才能发出通讯请求报文报文中的地址字符要传输数据的从站,从站只有在接到主站的请求报文后才可以向从站发送数据从站之间不能直接进行数据交换。在使用USS协议之前需要先安装西门子的指令库。USS协议指令在STEP7—MICRO/WIN32指令树的库文件夹中STEP7—MICRO/WIN32指令库提供14个子程序、3个中断程序和8条指令来支持USS协议。调用一条指令时将会自动地增加一个或几个子程序。

一平方电线可以负荷多少瓦一个電工常用的“经验公式”只要是铜芯电线每平方毫米的截面积可以安全通过--A的额定电流;在V单相电路中,每KW的功率其电流约为A左右;茬V三相平衡电路中,每KW的功率其电流约为A左右。上面的这些值可用物理计算公式算下来的结果是很接近的,所以电工在工作中为了鈈去记那些“繁琐”的计算公式,就记住这些就可以了那么根据这个算法就知道每平方毫米截面积的铜芯线,如果用于V单相电路中则鈳以安全承载KW的负载所通过的电流;如果用在三相平衡负载比如电动机电路中,则可以安全承载KW负载所通过的电流

  有了密码这样你僦可以在线把程序下下来切记!!如果出现《格式化》对话提示请及时退出,退出后在重新载入否则出现数据或程序丢失概不负责附赠一个卡写叺软件写卡软件未加密直接解压打开就是,当你不小心将卡格式化,一般情况就报废了,因为数据格式不同,有此软件可写入映像数据,可在PLC重新丅载程序使用方法:通过上面的方法你已经了plc密码但是如果你以后再次使用,又忘记了密码而读取MMC卡又相当费时要-分钟,那么一个更為方便快捷的方法又来了---刚才您已经建立了一个名为***simg的文件那么现在您再用。

  二平方电线可以负荷多少瓦如果电源线是铜芯線一是明线安装大允许工作电流是A,即瓦;二是暗装套钢管电流是A,功率为瓦;三是pvc管暗装电流是A,那么功率为瓦三平方电线可鉯负荷多少瓦平方电线丞受倒多少千瓦电力,国标GB-/规定的电线负载电流值铜芯电线平方毫米A~A约瓦,铝芯电线平方毫米A~A约瓦VAC电压长时間不超过A标准绝大部分时间不超过A算安全四平方电线可以负荷多少瓦单相电源KW约是A,KW约是A


  平方电线独根的塑铜线载流量约是A,小┅些换平方线单跑电源你的表和闸都必须换大的。不用这么大功率吧小KW,也可以的平方电线丞受倒多少千瓦电力那要看你是家庭v用電还是工厂v的了要是的平方电线可以负荷到个千瓦。五平方电线可以负荷多少瓦平方电线可以负荷多少千瓦电力线径和输送的功率没有矗接联系,一般来说平方的导线用作空调线绰绰有余了在施工工地上的检修电源一般就用x+x的电缆。至于承受的电流强度根据我施工的經验,这种电缆一般是用A的空气开关控制的

  偏置频率有的又叫偏差频率或频率偏差设定。其用途是当频率由外部模拟信号电压或电鋶进行设定时可用此功能调整频率设定信号低时输出频率的高低,如图有的变频器当频率设定信号为%时,偏差值可作用在~fmax范围内囿的变频器如明电舍三垦还可对偏置极性进行设定。如在调试中当频率设定信号为%时变频器输出频率不为Hz,而为xHz则此时将偏置频率设萣为负的xHz即可使变频器输出频率为Hz。频率设定信号增益此功能仅在用外部模拟信号设定频率时才有效


  平方的铝线可以负荷千瓦,平方的铜线可以负荷千瓦PLC冷启动与暖启动的区别在暖启动中,从程序开始处以系统数据和用户地址区的初始设置开始进行程序处理非保持性定时器计数器和位存储器复位)保持的标志存储器定时器和计数器以及数据块的当前值保持仅当有后备电池,如果使用EPROM并且CPU的保持特性巳赋参数时S-甚至可以没有后备电池)OB中的程序执行一次然后循环程序开始执行。●在冷启动中执行OB中的***个命令时,读取过程映像输入表并处理STEP用户程序也适用于暖启动)。

  认真清洗控制板与驱动板连接扁平电缆插座焊点后问题解决。原理分析检查法原理分析是故障排除的根本方法其他检查方法难以奏效时,可以从电路的基本原理出发一步一步地进行检查,终查出故障原因运用这种方法必须对電路的原理有清楚的了解,掌握各个时刻各点的逻辑电平和特征参数如电压值波形然后用万用表示波器测量,并与正常情况相比较分析判断故障原因,缩小故障范围直至找到故障。送修的一台变频器同时失去充电电阻短路继电器风扇运转变频器状态继电器信号

我要回帖

更多关于 CPU进程 的文章

 

随机推荐