1.rabbitmq结构?
1、Server:Broker,接受client连接,实现AMQP实体服务
2、Connection:应用程序和Broker的网络连接,是一条 TCP 连接 ,一个生产者或一个消费者与 Broker 之间只有一个Connection,即只有一条TCP连接。 3、Channel:网络信道,读写都是在Channel中进行(NIO的概念),包括对MQ进行的一些操作(例如clear queue等)都是在Channel中进行,客户端可建立多个Channel,每个Channel代表一个会话任务
3、Virtual host:用于消息隔离(类似Redis16个db这种概念),最上层的消息路由,一个包含若干Exchange和Queue,同一个里面Exchange和Queue的名称不能存在相同的。其实vhost就是mini版broker,为不同应用程序提供服务。
4、Exchange:Routing and Filter
5、Binding:把Exchange和Queue进行Binding
6、Routing key:路由规则
7、Queue:物理上存储消息
2.rabbitmq工作模式
①SIMPLE简单模式:一个生产者一个消费者
②workqueue工作队列模式:一个生产者多个消费者,没有交换机,消费者随机消费
③subscribe发布订阅模式fanout:有交换机,一个消费者一个队列,一旦消费者断开与rabbitMQ的连接,队列就会消失。如果消费者数目很多,对于rabbitMQ而言,也是个重大负担,订阅模式是个长连接,占用并发数,且每个消费者一个队列会占用大量空间。
④routing模式:交换机根据消息的路由信息和路由方式,将消息发送到不同的消息队列。消费者也根据自己的路由配置从对应的队列中获取消息
⑤topic模式:routing模式的模糊匹配版本
⑥rpc模式:两条队列:一条rpc_queue,一条callback_queue。客户端发送一条带有两个属性的消息:replyTo,设置为仅为请求创建的匿名独占队列,和correlationId,设置为每个请求的惟一id值
3.rabbitmq如何保证消息不丢失?
3.1丢失原因
①生产者发送时丢失
②mq崩溃丢失
③消费者消费时丢失
3.2丢失解决
1.生产者发送丢失:
①使用rabbitmq事务(性能差):channel.txselect开启事务。捕获到rabbitmq崩溃或其他异常就回滚。 ②confirm确认机制:channel.confirmSelect();// 开启发送方确认模式。每条消息一个id,要加监听器
2.mq崩溃: 设置持久化。①设置queue持久化(持久化queue而不是queue里的数据)②发送消息时设置持久化(会写磁盘)
3.消费者消费丢失: 自动ack改为手动ack
4.rabbitmq如何保证消息不重复
4.1 为什么消息会可能重复?
生产者重复生产:生产者发送消息后,mq发送ack在网络中丢失,导致生产者重发
消费者重复消费:一般来说消费者消费完消息后,会ack到MQ然后MQ会删除这条消息。但假设由于网络原因这条ACK丢失了,又或者此时我kill掉了消费端的应用。
4.2 怎样解决消息重复?
生产者:
不是每次收不到就重发,改为定时任务+redis方式,查询broker里的记录。
消费者:
①消费者业务层保证幂等性
②全局id
5 怎样解决消息堆积问题?
1.确保生产者,消费者代码逻辑没有问题
2.临时扩容:建立N个queue,写一个临时程序,将queue里积压的消息取出,不做任何耗时处理,直接均匀分发到N个queue中,征用N个临时机器分别从queue中取出消息并消费,然后恢复架构
3.恢复队列中丢失的数据:若使用RabbitMq,并且设置了过期时间,当超过一定时间,数据就会被清理掉,导致数据的丢失。可以采用批量重导的方式,在流量低峰期,写个程序手动查询丢失的那部分,并将消息冲新送入mq中。
6.死信队列和延迟队列?
6.1 什么是死信消息?
①消息被拒绝(Basic.Reject或Basic.Nack)并且设置 requeue 参数的值为 false
②消息过期了
③队列达到最大的长度
6.2 什么是死信队列/死信交换机?什么是延迟队列?
死信消息都会被转发到死信交换机内,同时也可设置key转发到死信队列,而延迟队列是我们利用过期时间和死信队列打造的。
7 kafka怎样保证消息不丢失?不重复?(非业务层面)
不丢失
kafka producer 有个acks配置:0表示直接不等待broker同步,直接发送下一条消息;-1表示等待isr分区复制完毕,才发送下一条消息;1是两者折中,只要leader接收到,就发送下一条消息。
consumser的offset提交应该从手动提交变成自动提交。
不重复
主要重复原因在于手动提交offset时,数据已经消费完毕(落库),但是宕机offset没有提交。解决方式类似mq。
1.mysql页面大小?为什么?
mysql innodb默认页大小16kb。
假设我们一行数据大小为1K,那么一页就能存16条数据,也就是一个叶子节点能存16条数据;再看非叶子节点,假设主键ID为bigint类型,那么长度为8B,指针大小在Innodb源码中为6B,一共就是14B,那么一页里就可以存储16K/14=1170个(主键+指针),那么一颗高度为2的B+树能存储的数据为:1170*16=18720条,一颗高度为3的B+树能存储的数据为:1170*1170*16=21902400(千万级条)。所以在InnoDB中B+树高度一般为1-3层,它就能满足千万级的数据存储。
2.mysql分页查询优化
分页查询分为逻辑查询(全查出来,只显示N条)和物理查询(只查N条出来);数据量大、更新频繁的情况尽量用物理查询,mysql提供的limit就是典型物理查询。
limit可以结合offset使用(limit M,N和limit N offset M基本一样),但是这种情况其实还是全表查询,事实上应该 select xxx from xxx where id>M limit N,因为id会用到索引,避免了正常limit全表扫描的情况。
3.索引最左匹配原则
如果建立联合索引如a,b,c三列索引,则必须以a,b,c这样的顺序匹配索引
(可匹配a=?、a=? and b=?、a=?and b=?and c=?)其中顺序颠倒也可以,因为执行器会优化sql语句;
当遇到范围查询时,范围查询这一列还生效,后边的列都不生效。
4.聚簇索引一定是主键索引吗?
不一定。在没有主键时,可能会选择第一个不为null的唯一索引,否则还可能选择隐藏ROWID作为聚簇索引。
但是主键索引一定是聚簇索引。
5.索引下推
①mysql架构分为连接层、server层、引擎层和存储行,server层负责解析优化sql语句,引擎层负责检索索引和回表拿数据。索引下推主要是用于优化联合索引时的回表次数,将联合索引失效时失效列后的字段判断工作从server层下推到引擎层。
②如下图:sql语句为select * from xx where name like "张%" and age <15;
索引下推之前会由引擎层找到 like "张%" 的(2条记录id分别回表)记录返回给server层,server层再判断age<15;
下推后直接将 age<15带到引擎层(只找到张三,10回表一次)拿到记录并返回。
③索引下推适用条件:
·只能用于range、 ref、 eq_ref、ref_or_null访问方法;
·只能用于InnoDB和 MyISAM存储引擎及其分区表;
·对InnoDB存储引擎来说,索引下推只适用于非聚簇索引;
6.mysql调优过程
1.show status 命令可以看到当前数据库的sql语句统计次数,判断是以什么操作为主
2.set global slow_query_log='ON' 开启慢查询日志;慢查询日志只能查询结束后收集,如果想当前立即排查用 show processlist 查看当前连接线程,重点关注‘state’对应sql状态(是否锁表、正在同步,正在创建临时表。。。)
3.根据2中查到的慢sql,explain 分析语句,重点关注select type(simple、primary子查询中最外、union内层、subquery多个子查询时首个)和type(type见下图)
4.创建索引并避免索引失效
①like ‘%a’ 百分号在前
②覆盖索引,前面字段有范围判断
③order by字段在where中
④对索引列计算或者函数
⑤is not null
⑥!=操作符
5.优化sql
①join替代子查询(子查询创建临时表)
②union替代子查询
③limit先拿到id
④多表查询小表在前,大表在后
⑤where代替having
1.怎样避免单例模式由反射或者序列化带来的问题?
单元素枚举实现单例。枚举类本身实例在jvm中只存一份,并且getinstance会判断是否加了枚举,反序列化getobject也明确限制了枚举,是的话返回异常。
2.spring中的设计模式?
1.单例模式:bean的作用域默认就是单例模式,spring中使用单例注册表concurrenthashmap实现
2.工厂模式:beanfactory生产bean就是工厂模式,也就是不去new实例,而根据设置的属性由容器代替我们new。
3.代理模式:spring aop实现原理,jdk、cglib...也就是执行代理对象的方法时,会自动转入原对象执行。
4.模板方法模式:jdbcTemplate等,继承实现
5.适配器模式:spring mvc中的handleradapter
6.装饰器模式:项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源
7.观察者模式:spring中的事件驱动,监听器
3.myBatis中的设计模式
1.工厂模式:sqlsessionfactory,用于生产sqlsession
2.单例模式:logfactory整个mybagtis项目单例,用于获取项目配置好的日志对象
3.代理模式:mybatis核心,替我们执行sql
4.装饰器模式:mybatis中的cache,perpetualCache<-scheduleCache<-LruCache...一系列Decorator
4.装饰器模式、代理模式、适配器模式区别?
1.适配器模式特点是兼容,改变对象的接口;代理模式特点是访问控制,被代理对象不存在会创建,反射加代码;装饰器模式特点是增强,不创建被装饰的对象,继承后加代码。
2.简单来说:适配器模式把A变成B执行B(has-a),改变接口(B中有A的方法);装饰器模式增强A,执行A(is-a);代理模式执行B,由B转发到A执行(use-a).
1.class文件常量池、运行时常量池、字符串常量池存储位置及内容?
1.class文件常量池在class文件中,存储字面量和*符号引用(①类和接口全限定名②字段名称③方法名称),加载后在方法区。*
2.运行时常量池是class文件常量池的加载后版本,存储字面量和符号引用,类信息存储在 方法区-永久代->方法区-元空间(本地内存)
3.字符串常量池存储字符串常量,在方法区-堆内存中
2.AQS原理及例子源码(reentrantlock)?
AQS抽象队列式同步器,采用模板方法模式为我们提供各种锁。核心是三大组件state--资源、clh队列--抽象双向队列,保存等待锁的线程、内部类condition--条件队列。主要通过实现tryrealease+tryaccried、tryaccquireshared+tryrealeaseshared等方法来打造共享锁、非共享锁。
共享模式如cyclicbarrier、countdownlatch等
独占模式以reentrantlock为例,这个锁可以有公平锁非公平锁两种模式,默认非公平锁。它的实现原理是
①在lock方法中首先cas一次,为0直接返回
②进入acquried
③进入tryacquired,先判断是否为0,成功则cas设置后返回;失败则判断当前线程是否是持有锁的线程,若是则重入修改state
④若不是,返回false回到acquried中,调用addwaiter里cas将其加入等待队列。
⑤然后通过acquiredqueued把线程阻塞
(⑤.1.死循环判断条件,满足则park阻塞
⑤.2.判断条件分别为是队列非空且不能获得锁(一般满足),shouldpark判断前驱动status是否为-1或cancel,否则设置)。
如果公平锁,则不会有①中的判断,并且在③中的cas之前,先查看当前等待队列是否有线程等待。
3.java对象中的引用存在哪几种形式?
1.强引用:一般A a = new A()出来的对象都是强引用,什么情况下都不会被gc。
2.软引用:gc时,内存不够就会被回收
3.弱引用:只要gc就会被收集
4.虚引用:随时可能被收集
4.什么是threadlocal?
什么是threadLocal?
又名本地线程变量,用于设置每个线程的专属变量。
它的底层原理?
thread类中有两个map,threadlocalMap和inheritthreadlocalMap分别用于存放threadlocal指定的变量和继承threadlocal的变量。默认null,调用threadlocal的get、set方法时才会创建,以threadlocal变量为key,具体变量为value。
threadlocal怎样防止内存泄漏?
首先threadlocalMap中将key设置成为了弱引用,value设置成强引用,key内存不够时会被回收,并且为了防止内存泄漏需要在检测到key被回收时删除value。那这里为什么要这样设计?因为map是不持有对象的强引用的,这样一旦threadlocal对象在外部失去了强引用,就可能会被回收,节省效率。value如果设置成弱引用的话,一旦没有被外界强引用,(threadlocal被new出来是强引用),get的时候会为null。
这么设计为什么普通hashmap不效仿?
因为普通map我们不知道key使用者知不知道,一旦put进去随时会用(就算key已经不用了),而这种内置的,一旦threadlocal不用了,用户是找不到这个key的。
5.介绍一下synchronized锁?
synchronized锁是jdk提供的用于控制并发的锁,是可以加在静态方法、同步方法、同步代码块上的关键字。加在静态方法上代表获得类锁,同步方法和同步代码块上代表获得对象锁。同步代码块中会在汇编指令中是插入MONITORENTER和MONITOREXIT两个指令用于指向monitor对象。
monitor对象中有几个关键部分组成:_cxq队列,entrylist队列、waitset以及 _owner变量。尝试获取锁的线程先cas试一下,获取不到加入_cxq队列,_cxq中的线程会根据QMode加入entrylist之后blocked状态。每次有其他线程释放锁,从头部变成OneDeck线程去争强锁。获得锁的线程会持有monitor,中途调用wait加入waitset(释放锁->加入waitset->park()方法阻塞)。
synchronized最开始是无锁状态,有线程获得锁后膨胀为可偏向锁,即对象头记录一个线程id,单竞争对手竞争cas -> 多竞争对手是膨胀为轻量级锁,将获得对象头中的condmark复制到获得锁的线程栈帧中一份,互相指向,其他自旋 -> 自旋到达一定次数,膨胀为重量级锁。
1.spring cloud分布式、微服务架构全家桶
Eureka:服务注册中心
eurekaserver就是defaultzone,eurekaclient将微服务送给server,30s心跳机制检测,90秒注销服务。
自我保护机制:超过85服务挂掉先维持注册表
Ribbon:负载均衡
先向eureka获取可用实例
IRule接口代表负载均衡策略:随机、轮询、权重、区域权重
feign:声明式webservice客户端,整合了ribbon和eureka。用很简单的方法帮我们实现”一个接口多处调用“,类似dao接口加@mapper。简化了使用ribbon时自动封装服务调用的代码量
hytrix
服务雪崩:级联依赖关系,一个失败,全失败。
解决:①服务熔断,级联响应超时则熔断
②服务降级:服务熔断其实是降级的一种,还有开关降级(埋点:在应用程序中部下开关,在配置中心配置开关)
zuul网关
代理+路由+过滤:路由负责将外部请求转发到具体微服务实例上,过滤对处理过程进行干预
configserver
云配置中心,采用git存储信息
2.Feign的实现原理(远程调用)
微服务启动时会扫描@FeignClient注解的接口,按规则创建远程接口的本地代理,代为处理并发送http请求。大约分为4步:
第1步:通过Spring IOC 容器实例,装配代理实例,然后进行远程调用。
第2步:执行 InvokeHandler 调用处理器的invoke(…)方法
第3步:执行 MethodHandler 方法处理器的invoke(…)方法
第4步:通过 feign.Client 客户端成员,完成远程 URL 请求执行和获取远程结果
3.hystrix的隔离方式
为了防止服务雪崩(一个服务中某个接口耗尽服务的线程资源导致整个服务不可用),因此要对服务进行资源隔离。区别:
4.Eureka和zookeeper对比
Eureka是spring cloud默认的注册中心,保证ap,只要还有一个实例能用,服务就能提供,并且不分主从;zookeeper保证cp(最终一致性),主要zab算法(近似raft)保证一致性,随机失效时间+sync后才能提交。
1.内核态和用户态的切换方式
(1)系统调用:用户程序要求访问系统资源,主动要求切换
(2) 异常:发生异常后找到中断服务程序,专为内核态执行
(3)外围设备的中断:设备准备好用户需要的数据后,请求中断将数据输入
2.线程和守护线程
当最后一个普通线程执行完毕时,守护线程随着jvm一起结束工作,最典型应用就是gc。
守护线程注意事项:
①守护线程设置必须在被守护线程start之前开始,否则抛异常
②守护线程中产生的新线程也是守护线程
③守护线程中最好不要设计计算等操作,因为无法保证执行完毕
3.内核线程、轻量级进程和用户线程?
1.内核线程就是内核的分身,一个分身可以处理一件特定事情。这在处理异步事件如异步IO时特别有用。内核线程的使用是廉价的,唯一使用的资源就是内核栈和上下文切换时保存寄存器的空间。内核线程只运行在内核态,不受用户态上下文的拖累。
2.轻量级进程(LWP)是建立在内核之上并由内核支持的用户线程,它是内核线程的高度抽象,每一个轻量级进程都与一个特定的内核线程关联。内核线程只能由内核管理并像普通进程一样被调度。
3.用户线程是完全建立在用户空间的线程库,用户线程的创建、调度、同步和销毁全又库函数在用户空间完成,不需要内核的帮助。因此这种线程是极其低消耗和高效的。
※.加强版用户线程=用户线程+lwp:单用用户线程,内核不知道用户线程的存在,但是一旦一个用户线程发生阻塞,整个进程都要阻塞,引入lwp作为内核线程和用户线程之间的桥梁,是内核调度的单元,系统调度lwp时就会转到相应用户线程执行
4.临界区和临界资源是什么?
临界资源指一段时间内只允许一个进程访问的资源(互斥共享),临界区是访问临界资源的那段代码。
5.零拷贝相关
说到零拷贝一般要先知道什么是拷贝,以一个普通的网络请求为例,请求到来后需要到磁盘上获取数据,要先把磁盘中数据拷贝到内核缓冲区,再拷贝到用户缓冲区(应用程序),再拷贝到socket缓冲区,最后拷贝到网卡,经历多次拷贝且需要多次内核态和用户态之间的切换(4次,每次系统调用都需要切换),消耗资源大。
故提出零拷贝技术,减少拷贝次数,目前有三种实现方式。
1.mmap+write方式:mmap是利用虚拟内存映射,将内核读缓冲区和用户缓冲区的虚拟内存映射到同一块物理内存,所以不需要将数据在这两个缓冲区之间拷贝。3次拷贝(2次dma和1次cpu)+4次切换。java nio MappedByteBuffer。
2.sendfile方式:sendfile系统调用函数,直接在两个文件描述符之间传送数据。3次拷贝(2次dma和1次cpu)+2次切换.java中filechannel.transferto。
3.sendfile+dma gather方式:引入dma收集技术,将内核缓冲区的文件描述(地址、偏移量)发送到socket缓冲区,网卡直接从内核缓冲区读。2次拷贝(2次dma)+2次切换。
6.进程和线程的区别
1.进程是系统分配资源的最小单位,而线程是程序调度和执行的基本单位
2.进程切换开销较大,线程之间共享代码和数据空间,切换开销较小
3.保护模式下进程崩溃不对其他进程产生影响,而线程崩溃整个进程都会崩溃
7.线程通信方式(java)
1.volatile方式
2.wait/notify方式
3.join方式
4.threadlocal方式
8.父子进程之间共享哪些内容?
数据空间、堆、栈、用户组、打开文件结构...不共享id和锁。
父子进程之间通过mmap将不同虚拟地址映射到同一物理地址。
9.io多路复用模型select、poll、epoll的区别?
1.select时间复杂度o(n),它只知道要发生io,但不知道是哪几个流,然后进行无差别轮训;
2.poll本质上和select没区别,select基于数组,poll基于链表,所以没有最大连接数限制;
3.epoll时间复杂度o(1),event poll事件驱动,基于函数回调。消息传递方式上,前两者在用户空间和内核空间拷贝,epoll通过共享内存实现。
epoll的原理?
1. 调用 epoll_create() 会在内核中创建一个 eventpoll 结构体数据,称之为 epoll 对象,在这个结构体中有 2 个比较重要的数据成员,一个是需要检测的文件描述符的信息 struct_root rbr(红黑树),还有一个是就绪列表struct list_head rdlist,存放检测到数据发送改变的文件描述符信息(双向链表);
2. 调用 epoll_ctrl() 可以向 epoll 对象中添加、删除、修改要监听的文件描述符及事件;
3. 调用 epoll_wt() 可以让内核去检测就绪的事件,并将就绪的事件放到就绪列表中并返回,通过返回的事件数组做进一步的事件处理。 epoll 的两种工作模式:
1. LT 模式(水平触发) LT(Level - Triggered)是缺省的工作方式,并且同时支持 Block 和 Nonblock Socket。在这种做法中,内核检测到一个文件描述符就绪了,然后可以对这个就绪的 fd 进行 IO 操作,如果不作任何操作,内核还是会继续通知。
2. ET 模式(边沿触发) ET(Edge - Triggered)是高速工作方式,只支持 Nonblock socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过 epoll 检测到。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。但是请注意,如果一直不对这个 fd 进行 IO 操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。
ET 模式在很大程度上减少了 epoll 事件被重复触发的次数,因此效率要比 LT 模式高。epoll 工作在 ET 模式的时候,必须使用非阻塞套接口,以避免由于一个文件描述符的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
1.netty核心组件?
1.bootstrap:引导器
2.channel:socket连接的封装
3.EventLoop:事件分发器
4.ChannelHandler:事件解析
5.pipeline:一系列handler组装成的流水线
1.ip协议–ip数据报格式
20字节头部+正文
VLSM:可变长子网划分,其实就是借用主机地址位数划分子网,子网越多,网内主机数量越少。
CIDR:废除以前的基于类别的IP地址方案,支持分级寻址的无类别方案
VLSM和CIDR区别:vlsm掩码增长 cidr掩码缩短
3.网络泛洪
指交换机或网桥将一个接口接收到的数据,除此接口外全部接口发送出去
4.ospf协议和rip协议(路由选择)
RIP协议:是一种采用距离向量算法的路由选择协议,路由器每30秒把自己的路由表发给邻居。
路由器用邻居发来的路由表根据距离向量算法修改自己的路由表<目的网络,开销,下一跳>。
初始时每个路由器只有到直连网距离为1的路由。通过跳数最多为15来防止环路,也因此只适合小规模组网中。
///
OSPF协议:三张表{邻居表、lsdb链路状态数据库、路由表}
5种报文:{
①Hello报文:建立并维持邻居关系
②DD报文:对LSDB内容的汇总(仅仅包含LSA摘要)
③LSR报文:请求自己没有的或是比自己更新的链路状态信息(LSA)
④LSU报文:链路状态更新信息
⑤LSAck:对LSU报文的确认}
七种状态机:
1.Down:没有启用OSPF的状态机;邻居失效后变为该状态。
2.Init:初始化状态,第一次收到对端发来的HELLO包(包含对瑞ID)时,将对端的状态设置为init。
3. 2-way:邻居状态,相互间周期发送hello的状态(双方建立会话)。
4. Exstart:交换信息的初始化状态,发送DBD(包含本地的LSA的摘要信息)报文,选举主从路由器(利用HELLO报文中的ID和优先权来进行选举,不允许抢占,DR没了,BDR才能上)。
5. Exchange:交换信息的状态,该状态下,互相发送DBD,告知对端本地所有的LSA的目录;同时,可以发送LSR,LSU,LSACK来学习对端的LSA.
6. Loading:加载状态(没有学习完的状态),发送LSR,LSU,LSACK,专门学习对端的LSA的详细信息。
7. Full:邻接状态(学习完的状态)
工作过程:其实就是其中状态的转换,初始互相发送hello包建立邻居关系(DOWN->Init->2way);进行LSA泛洪,发送DBD(lsa摘要),选举DR\BDR(只有DR之间会互相发送lsa,其他只向dr发送),互相发送LSR、LSU、LSACK学习对端的LSA详细信息(EXStart->Exchange->Loading);最后建立邻接状态建立完成(FULL)。
//
ospf和rip对比?
1.rip适合中小规模网络,ospf适合大型网络
2.ospf占用链路实际带宽比rip小
3.ospf收敛时间短
4.rip中路由器首先向外发送自己的路由表,邻居根据收到的进行计算;ospf中,链路状态同步完毕后,每个路由器可以建立以自己为根的到不同其他节点的最小生成树。
5.转发和重定向区别
**
6.cookie和session区别
①cookie存在客户端浏览器,session存储在服务端;
②cookie不安全,容易被cookie欺骗;
③session占用服务端内存
④cookie支持跨域,session不支持
7.cdn
内容分发网络,提供负载均衡和内容缓存功能。
加入了cdn以后访问一个网站,DNS返回的不再是源站地址而是别名记录,指向cdn的全局负载均衡系统,dns会访问cdn服务器,如果cdn缓存系统内有相应资源的缓存直接返回,否则由全局负载均衡系统调度一个距离最近的边缘节点返回给用户。
8.http缓存实现方式?
http缓存分为强制缓存(1.0 expires、1.1 cache-control)和协商缓存(Etag、if not modifie since)。再次请求时,
①首先看强制缓存是否过期,max-age字段是服务器告诉客户端是时间端多久内有效(1.0中expire是时间点,本地可修改时间,不安全),有效期内直接访问本地缓存,超时则访问协商缓存。
②协商缓存需要再次对服务器发送请求,请求头带if-modifie-since和if-none-match,对比返回的Etag(资源内容摘要)和当前Etag,成功304,不成功重新请求资源;若无Etag,则对比上次的modifie时间和当前modifie时间,成功304,失败重新请求。
9.响应状态码?
1xx:临时响应,如100(应继续提出请求)
2xx:成功,如200(请求成功)
3xx:重定向,如304(未修改,可使用缓存)
4xx:客户端请求错误,如410(请求资源已删除)
5xx:服务端执行错误,如500(服务器执行错误)
10.dns迭代查询和递归查询区别?
递归查询的过程中,当本地域名解析器不能够解析域名时,由本地域名解析器代替客户端层层向权威域名服务器和根域名服务器请求;而迭代查询是由客户端自身通过dns服务器告知的其他dns服务器的地址去请求查询。
11.http 1.x和2.0的区别?
1. 2.0引入二进制分帧概念,以“帧”作为信息传输的最小单位,减小了传输粒度。
2. 2.0复用tcp连接,虽然在1.x中也实现了长连接,但是会出现“队头阻塞”的问题(http1.1中允许不一收一发,但是也必须按序发送,队头阻塞说的就是某一请求得不到回应就会影响接下来的发送),2.0中基于二进制分帧实现了多路复用(帧会标明是哪个请求,所以可以乱序发送)。同域名下所有通信都在单个连接完成,该连接可以承载双向数据流动。
3. 头部压缩。使用“首部表(键值对形式)”来维护一些冗余头信息
4. 服务端主动推送。服务器在客户端加载html时可主动推送其他资源,客户端可以选择接不接受,同样遵守同源策略。
12.cookie和token区别
1.cookie保存的是用户标识而非用户信息,需要服务端以某种映射再查询到用户相应信息;token保存直接保存加密后用户信息数据(摘要),拿到时再次加密对比异同即可确定是否是自己颁发出去的。
2.token可以防范CSRF(跨站请求伪造,攻击者伪造客户端身份,访问服务器)攻击,cookie不可
3.token本身易扩展,可以承载更多信息
1.paxos算法–分布式一致性协议
paxos算法是一种假设信道安全可靠的消息传递式的分布式一致性算法
关键点:
{三种角色:
[提议者:提案被半数以上接受者接受就认为value被接受
接受者:接受者接受某个提案就认为value选定
记录员:由接受者告诉记录员接受了哪个提案]}
{两种阶段:
[准leader确认
leader确认]
}
{决定某个value的过程:
准leader确认
①提议者选定一个提案N,向半数以上接受者发送N
②接受者接收到提案N,则将当前接受的最大{n,nvalue}返回给提议者,并承诺不接受比N小的提案
leader确认
①如果提议者接收到半数以上接受者发回的N提案,则将{n,nvalue}发送给接受者
②接受者只要没接受过大于N的提案,就接受这个value
}
2.raft算法
三个角色:候选人、领导者、群众
选举过程:每个群众维护一个超时时间,到时未收到领袖的心跳,重新选举;先超时的群众称为候选人,向其他群众发送投票信息,其他群众投票(任期内只投一次票),大多数投票到了a,就升级a为领导者。之后周期性向其他节点发送领导者心跳。
同步过程:借鉴复制状态机,先提交(从复制日志完成时不允许提交)再应用。
3.一致性hash算法
适用场景:比如redis集群的cluster模式或者mysql分库分表。
为什么用:传统hash模式将hash值对机器数量取余,虽可以保证直接查到对应机器,但是如果不成倍扩缩时(比如某台机器宕机),大量数据需要移动位置。
原理:一致性hash转为对2^32-1取余(为什么?因为正好是java中int类型值能表达的最大整数),机器取余后位置固定,数据取余后位置也固定,沿着机器A位置顺时针行走,到下一台机器之前的数据都属于机器A。某台机器宕机时,只需移动那两台机器之间的数据即可。
有什么问题:可能会出现数据倾斜问题。集群内机器数量较小时,肯能大多数数据会集中在某一台机器附近。解决方式是扩充虚拟节点,在ip地址后加编号,再做hash,比如A,B变成A#1,A#2,A#3,B#1,B#2,B#3。可以使数据更加均匀。
1.redis限流三种方式?
1.setnx方式:直接将过期时间设置为指定时间段,key为指定请求,value为访问次数。弊端:只能统计1-10s内,不能统计2-11秒内。
2.基于zset的滑动窗口:key为指定请求,set存值唯一(UUID),score为时间窗。弊端:zet会越来越大
3.redis实现令牌桶算法限流:list里保存令牌,定时任务添加令牌,需要时取出,令牌用uuid保证唯一性
2.为什么rdb持久化时主进程fork子进程而不是开启线程?
rdb持久化有save和bgsave两种方式,save直接主进程执行,会阻塞;bgsave fork子进程执行,不阻塞。
fork子进程是因为子进程和主进程通过mmap共享数据空间,但是存在读时共享写时复制机制(以页为单位),主进程更新数据时,不会对子进程产生影响。而线程直接和进程共享内存空间,发生这种情况就要加锁,造成资源浪费。
3.分析一下如何保持缓存和数据库的数据一致性?
首先考虑方案1(写时同时更新数据库与缓存,有并发问题)
1.先更新数据库,再更新缓存(不一致)
线程A将数据库值修改为a1,线程B将数据库值和缓存值修改为b1,线程A将缓存修改为a1
2.先更新缓存,再更新数据库(不一致)
线程A将缓存修改为a1,线程b将缓存和数据库修改为b1,线程A将数据库修改为a1
考虑方案2(cache Aside,更新数据库同时删除缓存,下次未命中再更新缓存)
1.先删除缓存,再更新数据库(不一致)(使用延迟双删解决:删除缓存,更新数据库,睡眠,删除缓存)
线程A删除缓存,线程B读b1数据,缓存未命中,从数据库拿到b1更新缓存,线程A更新数据库为a1
2.先更新数据库,再删除缓存(一致)
线程A读a1,线程B更新数据库为b1,删除缓存,线程A更新缓存为a1(由于线程现成A更新缓存理论上比线程B更新数据库会快很多,因此基本不会出现问题)
在2.2基础上加上过期时间,可保证AB都能执行完情况下的一致。
方案3:方案2会频繁删除缓,对命中率产生影响,如果业务要命中率,在方案1基础上加分布式锁(影响效率)或者加较短过期时间
方案4:方案2.2在线程删除缓存过程中可能失败,将删除操作放入消息队列不断重试或者订阅数据库binlog日志
4.为什么zset使用跳表而不使用b+树?(为什么mysql索引使用b+树而不是跳表?)
首先两者都不选用hash的原因是方便范围查询。
用b+树做比较,是因为二叉搜索树会退化成严重不平衡的情况->avl树需要平衡次数过多->红黑树始终是二叉树(且只能保证局部平衡),层数太高->b树每个节点存指针和数据,导致相同层数能存的数据太少(也是层数太高)
redis选择跳表的原因是:①跳表实现简单②跳表随机层数,决定不是每次插入都要平衡③跳表平均指针比b+树少,节省内存
mysql选择b+树的原因是:①mysql是外存储,每次查询都要走磁盘io,如果把数据放在每个跳表节点,同样数据量层数要变得高,查询效率也就相应变得很低。
5.接口加密怎么做?(防止别人拿到你篡改请求去拿数据或伪造请求)
切面将请求中的(请求头部分 + 请求URL地址 + 请求Request参数 + 请求Body)生成sig签名,上述部分以k-v值形式方式加入sortMap,根据key排序。取出后加上appSecret使用Md5加密,与前端传过来的以相同加密算法生成的sign比较是否相同。(外部不知道加密算法是什么,所以无法篡改后正确加密)。
其中请求头部分主要拿到(时间戳、临时流水号、appId,前端传过来的sign),时间戳为了判断当前后端拿到是否已经过了十分钟,过了就丢弃,一定程度防止盗链;临时流水号记录redis,防止重复提交。
1.最小生成树和最短路径算法对比
https://www.processon.com/mindmap/6281b02f5653bb45ea6a5f3e
2.红黑树为什么最多旋转3次?
红黑树一次插入最多旋转两次,删除最多旋转三次,所以最多旋转3次。
①插入必为红色,需要调整的情况父节点必为红色,所以主要按照叔叔的情况讨论;
旋转2次的条件是:插入节点为父节点的右孩子,且叔叔节点非红色节点。
②删除旋转3次举例:删除黑色节点,兄弟黑色(变红),左子树黑色少1 -> 祖父变红,叔叔和孩子交换红黑 ->
3.concurrenthashmap 1.7 1.8实现?
concurrenthashmap主要是hashmap的线程安全版本,hashmap1.8已将扩容时的头插法改为尾插法,所以不会出现循环问题。并发问题主要体现在:扩容时若并发插入数据可能会留在原地&插入同位置新数据可能会被替换。
concurrentmap 1.7中是利用segment继承了reentrantloack,类似hashtable将锁的粒度减小。在计算size时,会先计算两次size比较是否相同,若不相同则将每个segment上锁再次计算。
1.8中采用cas+synchronized方式,进一步减小锁的粒度,并且synchronized只锁住头结点,因为每个位置都是从头节点开始访问的;用voliate变量sizectl确认是否在扩容,如果有线程在扩容则当前线程也参与进去帮助扩容;cas方式用于当前数组位置为空时,cas方式写入第一个node节点。
1.spring 中 controller怎么保证并发安全
spring中controller默认单例,不能保证并发安全,所以不能在controller中定义成员变量或者只能定义无状态的成员变量(无状态,比如注入mapper里也没有成员变量,这个成员变量本身不会存在任何状态属性),如需定义,需要修改成多例或者在controller中使用threadlocal变量。
1.什么是feed流?
feed流是持续更新并呈现给用户内容的信息流,是将用户主动订阅的若干消息源的消息以某种方式聚合并排列后(一般是timeline)要展示给用户的信息集合。
2.feed流的几种模式和排序方式?怎样设计?
feed流(timeline)分为三种模式:推模式、拉模式、推拉模式
推模式:以微博为例,每当用户发帖,向该用户的所有粉丝推送一条消息,需要考虑的是多大v用户(粉丝多)同时发帖的存储负荷
拉模式:
``