目录
- 五.Java框架部分
- 六.数据库部分
- Mysql
- 1.mysql的索引结构
- 2.mysql几个部分
- 3.sql中的left join,right join,inner join区别
- 4. mysql中的锁?
- 5. mysql中的隔离级别如何实现?
- 6.mysql默认隔离级别是什么,为什么呢?
- 7.MVCC机制
- 8.数据库的范式:
- 9.Mysql主从复制?
- 10.Mysql联合索引结构:
- 11.binlog的三种格式:
- 12.双M结构,M-S结构问题?
- 13.Mysql的哈希索引:
- 14.Mysql支持Hash索引吗?
- 16.Mysql的覆盖索引和回表查询?
- 17.Mysql的Redo log是什么?
- 18.Mysql如何解决SQL注入的?
- 19.Mysql自增int主键好处
- 20..Mysql的Undo log是什么?
- 21.Mysql数据库innodb的崩溃恢复
- 22.B+树高度计算
- Redis
- 1.redis有哪几种数据结构?
- 2.Redis List组件使用:
- 3.Redis为什么这么快??
- 4.Redis的分布式锁如何实现?
- 5.redis的持久化方式?
- 6.redis怎么进行扩容?
- 7.Redis热key,缓存击穿怎么办?
- 8.redis cluster架构介绍和性质?
- 9.redis内存淘汰机制:
- 10.Redis的redLock算法:
- 11.缓存的读模式和写模式:
- 12.redis主从复制:
- 13.redis宕机的问题:
- 14.Redis哨兵:
- 15.Redisson分布式锁实现原理
- 16.Redis的keys命令问题:
- 17.redis的跳表问题
- 18.redis的几种数据结构底层存储
- 19.Redis怎么判断数据过期:
- 20.Redis分布式锁 & RedLock
- 21.Redis 的大key问题如何解决
- 22.Redis 的热key问题如何解决
- 22.Redis的 rehash
- 22.Redis和mysql数据一致性???
- Kafka面试题
- 七.常用组件
- 八.项目,架构等
- 1.怎样的服务才算微服务?(感觉没有定论)
- 2.令牌桶算法的实现?
- 3.protobuffer 和 json区别
- 4.zookeeper简单介绍:
- 5.docker有什么用
- 6.Paxos算法是什么?
- 7.CAS的ABA问题如何优化
- 8.排序算法稳定性问题
- 9.qps越来越多,怎么办?
- 11.OOM问题排查
- 12.频繁full gc问题排查
- 13.paxos算法和zab协议:
- 14.redis和memcache:
- 15.同步异步,阻塞非阻塞概念
- 16.RPC要解决的问题
- 17.秒杀系统设计?
- 18.Docker & k8s:
- 19.nginx和tomcat区别,能不能反着用?
- 19.Nginx的模型,为什么用nginx
- 20.中台化的概念:
- 21.TCC操作?
- 22.两阶段提交2PAC:
- 23.三阶段提交3PAC
- 24.接口的安全考虑---加密怎么做?
- 25.本地消息表---分布式事务:
- 25.Mysql和Redis的数据一致性问题?
- 26.RPC服务和HTTP服务区别?
- 27.LRU算法设计
- 28.TAIR数据库
- 29.了解RPC吗,常见的RPC,怎么设计RPC
- 30.分布式系统id生成算法?
- 31.thrift原理
- 32.熔断
- 33.raft算法
- 34.为什么有了HTTP还需要RPC?
- 35.什么是LSM TREE
五.Java框架部分
Spring
1.Spring中的拦截器,过滤器组件介绍?
- 拦截器主要是在面向切面编程的就是在你的service或者一个方法,前调用一个方法,或者在方法后调用一个方法比如动态代理就是拦截器的简单实现,在你调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在你调用方法后打印出字符串,甚至在你抛出异常的时候做业务逻辑的操作
- 过滤器是在javaweb中,你传入的request、response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者struts的action进行业务逻辑,比如过滤掉非法url(不是login.do的地址请求,如果用户没有登陆都过滤掉),或者在传入servlet或者 struts的action前统一设置字符集,或者去除掉一些非法字符.
2.说一下spring的IOC?
传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建。传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
3.Spring中的异常处理:
- 全局异常处理:实现一个HandlerExceptionResolver
- 局部异常处理:在一个controller中加@ExceptionHandler
- 更好的细粒度方式:@ControllerAdvice注解
@ControllerAdvice
public class MyGlobalExceptionHandler {
//这里定义想处理的异常
@ExceptionHandler(Exception.class)
public ModelAndView customException(Exception e) {
ModelAndView mv = new ModelAndView();
mv.addObject("message", e.getMessage());
mv.setViewName("myerror");
return mv;
}
}
4.jdk动态代理和cglib动态代理:
- jdk动态代理是通过反射机制实现的,Proxy和InvocationHandler。其中,InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态地将横切逻辑和业务逻辑编织在一起。只能为接口创建代理实例
- 具体一点:要写个代理类,实现invocationHandler接口,这个接口有个method.invoke方法,可以在它前后增加切面逻辑,然后需要用Proxy.newInstance()传入代理类,通过反射来创建对象。
- CGLib采用底层的字节码技术,全称是:Code Generation Library,CGLib可以为一个类创建一个子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑。
两者对比:
JDK动态代理是面向接口的,在创建代理实现类时比CGLib要快,创建代理速度快。
CGLib动态代理是通过字节码底层继承要代理类来实现(如果被代理类被final关键字所修饰,那么抱歉会失败),在创建代理这一块没有JDK动态代理快,但是运行速度比JDK动态代理要快。
5.Spring Bean生命周期:
-
bean对象的实例化
-
封装属性,也就是设置properties中的属性值
-
如果bean实现了BeanNameAware,则执行setBeanName方法,也就是bean中的id值
-
如果实现BeanFactoryAware或者ApplicationContextAware ,需要设置setBeanFactory或者上下文对象setApplicationContext
-
如果存在类实现BeanPostProcessor后处理bean,执行postProcessBeforeInitialization,可以在初始化之前执行一些方法
-
如果bean实现了InitializingBean,则执行afterPropertiesSet,执行属性设置之后的操作
-
调用执行指定的初始化方法
-
如果存在类实现BeanPostProcessor则执行postProcessAfterInitialization,执行初始化之后的操作
-
执行自身的业务方法
-
如果bean实现了DisposableBean,则执行spring的的销毁方法
-
调用执行自定义的销毁方法。
6.Spring IOC原理:
- beanFactory和applicationContext容器区别,作用
- refresh()这个核心方法
- 扫描xml文件,形成beanDefination
- 通过反射实例化
7.BeanFactory和FactoryBean区别?
- BeanFactory:BeanFactory接口的类表明此类是一个工厂,是一个IOC容器,作用就是配置、新建、管理各种容器中的Bean。
- FactoryBean:实现FactoryBean的类表明此类也是一个Bean,类型为工厂Bean(Spring中共有两种bean,一种为普通bean,另一种则为工厂bean)。作用是用来方便地向容器中注册和初始化bean的。FactoryBean 通常是用来创建比较复杂的bean,一般的bean 直接用xml配置即可,但如果一个bean的创建过程中涉及到很多其他的bean 和复杂的逻辑,用xml配置比较困难,这时可以考虑用FactoryBean
8.Spring解决循环依赖:
三级缓存:
- singletonFactories :单例对象工厂的cache
- earlySingletonObjects :提前暴光的单例对象的Cache
- singletonObjects:单例对象的Cache
例如当“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到三级缓存中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存(肯定没有,因为A还没初始化完全),尝试二级缓存(也没有),尝试三级缓存由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存中,而且由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。
知道了这个原理时候,肯定就知道为啥Spring不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题了,因为加入三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。
9.Spring默认单例模式为什么?
- 单例模式方便维护,而且开销小
- 多例模式一般并发的时候用,每个请求用新的对象处理,类似于threadLocal
- 大部分使用的bean都是无状态的,不需要考虑线程安全
10.AspectJ和Spring AOP动态代理区别?
AOP实现的关键在于 代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。
(1)AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
(2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
11.Spring的bean是线程安全的吗?
不是,Spring框架中的单例bean不是线程安全的。
spring 中的 bean 默认是单例模式,spring 框架并没有对单例 bean 进行多线程的封装处理。
实际上大部分时候 spring bean 无状态的(比如 dao 类),所有某种程度上来说 bean 也是安全的,但如果 bean 有状态的话(比如 view model 对象),那就要开发者自己去保证线程安全了,最简单的就是改变 bean 的作用域,把“singleton”变更为“prototype”,这样请求 bean 相当于 new Bean()了,所以就可以保证线程安全了。
- 有状态就是有数据存储功能。
- 无状态就是不会保存数据。
12.Spring优点?
- 非侵入式设计
Spring是一种非侵入式(non-invasive)框架,它可以使应用程序代码对框架的依赖最小化。
- 方便解耦、简化开发
Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护工作都交给Spring容器的管理,大大的降低了组件之间的耦合性。
- 支持AOP
Spring提供了对AOP的支持,它允许将一些通用任务,如安全、事物、日志等进行集中式处理,从而提高了程序的复用性。
- 支持声明式事务处理
只需要通过配置就可以完成对事物的管理,而无须手动编程。
- 方便程序的测试
Spring提供了对Junit4的支持,可以通过注解方便的测试Spring程序。
- 方便集成各种优秀框架
Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如Struts、Hibernate、MyBatis、Quartz等)的直接支持。
- 降低Jave EE API的使用难度。
Spring对Java EE开发中非常难用的一些API(如JDBC、JavaMail等),都提供了封装,使这些API应用难度大大降低。
13.Sring中的设计模式
- 链接:Spring的设计模式
- 注意:BeanFactory是简单工厂,根据传入参数标识不同,返回不同的bean实例。bean容器的启动阶段:
-
- 读取bean的xml配置文件,将bean元素分别转换成一个BeanDefinition对象。
-
- 然后通过BeanDefinitionRegistry将这些bean注册到beanFactory中,保存在它的一个ConcurrentHashMap中。
六.数据库部分
Mysql
1.mysql的索引结构
2.mysql几个部分
- 连接层:最上层是一些客户端和连接服务。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
- 服务层:第二层服务层,主要完成大部分的核心服务功能, 包括查询解析、分析、优化、缓存、以及所有的内置函数,所有跨存储引擎的功能也都在这一层实现,包括触发器、存储过程、视图等
- 引擎层:第三层存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取
- 存储层:第四层为数据存储层,主要是将数据存储在运行于该设备的文件系统之上,并完成与存储引擎的交互
3.sql中的left join,right join,inner join区别
- left join(左联接) 返回包括左表中的所有记录和右表中联结字段相等的记录
- right join(右联接) 返回包括右表中的所有记录和左表中联结字段相等的记录
- inner join(等值连接) 只返回两个表中联结字段相等的行
4. mysql中的锁?
insert、delete和update都是会加排它锁(Exclusive Locks)的,而select只有显式声明才会加锁
锁分为读,写,意向读,意向写
分类:
- select: 即最常用的查询,是不加任何锁的
- select … lock in share mode: 会加共享锁(Shared Locks)
- select … for update: 会加排它锁
锁的模式分为:
- 记录锁:单个行记录上的锁。对索引项加锁,锁定符合条件的行。其他事务不能修改和删除加锁项;SELECT * FROM table WHERE id = 1 FOR UPDATE;它会在 id=1 的记录上加上记录锁,以阻止其他事务插入,更新
- 间隙锁:当我们使用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁。对于键值在条件范围内但并不存在的记录,叫做“间隙”。InnoDB 也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁。间隙锁就是为了解决幻读的问题,插入数据需要“间隙”,就对这个间隙加锁,其他事务就没法插入了。
- 临键锁(Next-key Locks):临键锁,是记录锁与间隙锁的组合,它的封锁范围,既包含索引记录,又包含索引区间。(临键锁的主要目的,也是为了避免幻读(Phantom Read)。如果把事务的隔离级别降级为RC,临键锁则也会失效。)Next-Key 可以理解为一种特殊的间隙锁,也可以理解为一种特殊的算法。通过临建锁可以解决幻读的问题。每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。需要强调的一点是,InnoDB 中行级锁是基于索引实现的,临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。
5. mysql中的隔离级别如何实现?
- 读未提交:排他锁会阻止其它事务再对其锁定的数据加读或写的锁,但是对不加锁的读就不起作用了。所以当一个事务写的过程中,另一个事务进行普通select(不加锁)就会读到未提交的数据。 这个隔离级别中读不会加任何锁。而写会加排他锁
- 读已提交:在该隔离级别(READ COMMITTED)写数据时,使用排它锁, 读取数据不加锁而是使用了MVCC机制。
- MVCC版本的生成时机: 是每次select时。这就意味着,如果我们在事务A中执行多次的select,在每次select之间有其他事务更新了我们读取的数据并提交了,这时就会出现不可重复读的情况,先select和后select读到的数据不一样了
- 可重复读:一次事务中只在第一次select时生成版本,后续的查询都是在这个版本上进行,这样就不会读到其他事务修改的数据了。从而实现了可重复读。
- 可串行化:读和写完全互斥,相当于 select … lock in share mode
6.mysql默认隔离级别是什么,为什么呢?
- mysql默认隔离级别是RR,可重复读
- Mysql在5.0这个版本以前,binlog只支持STATEMENT这种格式!而这种格式在读已提交(Read Commited)这个隔离级别下主从复制是有bug的,因此Mysql将可重复读(Repeatable Read)作为默认的隔离级别!
- binlog的三种格式:statement:记录的是修改SQL语句。row:记录的是每行实际数据的变更
。mixed:statement和row模式的混合 - 解决办法:隔离级别设为可重复读(Repeatable Read),在该隔离级别下引入间隙锁。
- 为什么用RC不用RR: 在RR隔离级别下,条件列未命中索引会锁表!而在RC隔离级别下,只锁行。而且RC的不可重复读问题是可以接受的,反正已经修改了
- RC级别下,binlog主从复制用row格式
7.MVCC机制
- 文章:MVCC机制
- 两个隐藏的列,事务id,回滚指针。指针帮我们获取到undo log中旧版数据,生成数据快照readView
8.数据库的范式:
- 1NF:字段都是原子的不能再分了
- 2NF:指的数据表里的非主属性都要和这个数据表的候选键有完全依赖关系。2NF 告诉我们一张表就是一个独立的对象,也就是说一张表只表达一个意思。
- 3NF 需要保证表中的非主属性与候选键不存在传递依赖。比如球员,球队,教练。球员决定球队,球队决定教练,所以球员决定教练,教练传递依赖于球员。这时候就得拆分
9.Mysql主从复制?
- 主从同步的内容就是二进制日志(Binlog),它虽然叫二进制日志,实际上存储的是一个又一个事件(Event),这些事件分别对应着数据库的更新操作,比如 INSERT、UPDATE、DELETE 等。
- 二进制日志转储线程(Binlog dump thread)是一个主库线程。从库会有IO线程联系主库线程,然后把binlog文件拷贝到从库上。
- binlog本身是二进制的,只不过mysql提供了我们查看的功能
怎么解决主从库的数据一致性?:
- 异步复制:MySQL默认的复制是异步的,主库在执行完客户端提交的事务后会立即将结果返给给客户端,并不关心从库是否已经接收并处理,这样就会有一个问题,主如果crash掉了,无法同步
- 半同步复制:办法就是等主从同步完成之后,主库上的写请求再返回,就是大家常说的“半同步复制”semi-sync。
- MGR,组复制,基于Paxos算法:首先我们将多个节点共同组成一个复制组,在执行读写(RW)事务的时候,需要通过一致性协议层(Consensus 层)的同意,也就是读写事务想要进行提交,必须要经过组里“大多数人”(对应 Node 节点)的同意。在一个复制组内有多个节点组成,它们各自维护了自己的数据副本,并且在一致性协议层实现了原子消息和全局有序消息,从而保证组内数据的一致性。
10.Mysql联合索引结构:
- 联合索引结构
- 为什么有最左前缀匹配原则,因为第一列是自增的,下面顺序则不一定,通过第一列的键值来查效率高
- 联合索引一定要依次命中所有索引,而且不能加范围查询,否则退化为线性的遍历
- 如有索引(a, b, c, d),查询条件a = 1 and b = 2 and c > 3 and d = 4,则会在每个节点依次命中a、b、c,无法命中d
11.binlog的三种格式:
- 基于行的复制(row-based replication, RBR):不记录每一条SQL语句的上下文信息,仅需记录哪条数据被修改了,修改成了什么样子了。binlog 里面记录了真实删除行的主键 id,可以避免主备数据不一致的问题。
- STATMENT模式:基于SQL语句的复制(statement-based replication, SBR),每一条会修改数据的sql语句会记录到binlog中。但是可能主库从库用的索引不一样,导致主备数据不一致。
- mixed:row太占空间,statement数据可能不一致,于是就有了mixed模式
12.双M结构,M-S结构问题?
- 双M就是互为主备
- 双M又一个问题就是循环复制,所以需要标记server id,这样每次检查下这个binlog是不是自己生成的,如果是就不用复制了
- 双M架构和M-S架构区别?:MySQL主从模式是对主操作数据,从会实时同步数据。反之对从操作,主不会同步数据,还有可能造成数据紊乱,导致主从失效。MySQL主主模式是互为对方的从服务器,每台服务器即是对方的主服务器,又是对方的从服务器。无论对哪一台进行操作,另一台都会同步数据。一般用作高容灾方案。
13.Mysql的哈希索引:
哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要类似B+树那样从根节点到叶子节点逐级查找,只需一次哈希算法即可立刻定位到相应的位置,速度非常快。注意hash索引的表存的是hash值和指针,再通过指针去内存里找
Hash索引不利的地方:
- 哈希索引也没办法利用索引完成排序;
- 不支持最左匹配原则;
- 在有大量重复键值情况下,哈希索引的效率也是极低的---->哈希碰撞问题;碰撞的话需要遍历指针链表比对,这里用的也是拉链法
- 不支持范围查询;
14.Mysql支持Hash索引吗?
- 主流的还是使用B+树索引比较多,对于哈希索引,InnoDB是自适应哈希索引的(hash索引的创建由InnoDB存储引擎引擎自动优化创建,我们干预不了
16.Mysql的覆盖索引和回表查询?
二者都是针对辅助索引/二级索引/非聚簇索引而言的
- 例子:现在我创建了索引(username,age),在查询数据的时候:select username , age from user where username = ‘Java3y’ and age = 20。这时就会覆盖索引,不需要回表查询
- 正常来说,非聚簇索引,叶子结点保存的是主键的值,需要拿这个值再回表查询一次。但是覆盖索引就不需要
17.Mysql的Redo log是什么?
mysql 三种日志讲解
redo log通常是物理日志,记录的是数据页的物理修改,而不是某一行或某几行修改成怎样怎样,它用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置)。
redo log包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(redo log file),该部分日志是持久的。
在概念上,innodb通过force log at commit机制实现事务的持久性,即在事务提交的时候,必须先将该事务的所有事务日志写入到磁盘上的redo log file和undo log file中进行持久化。
binlog 和redo log区别
-
redo log是在InnoDB存储引擎层产生,而binlog是MySQL数据库的上层产生的,并且二进制日1志不仅仅针对INNODB存储引擎,MySQL数据库中的任何存储引擎对于数据库的更改都会产生二进制日志。
-
两种日志记录的内容形式不同。MySQL的binlog是逻辑日志,其记录是对应的SQL语句。而innodb存储引擎层面的重做日志是物理日志。
-
两种日志与记录写入磁盘的时间点不同,二进制日志只在事务提交完成后进行一次写入。而innodb存储引擎的重做日志在事务进行中不断地被写入,并日志不是随事务提交的顺序进行写入的。
二进制日志仅在事务提交时记录,并且对于每一个事务,仅在事务提交时记录,并且对于每一个事务,仅包含对应事务的一个日志。而对于innodb存储引擎的重做日志,由于其记录是物理操作日志,因此每个事务对应多个日志条目,并且事务的重做日志写入是并发的,并非在事务提交时写入,其在文件中记录的顺序并非是事务开始的顺序。
-
binlog不是循环使用,在写满或者重启之后,会生成新的binlog文件,redo log是循环使用。
-
binlog可以作为恢复数据使用,主从复制搭建,redo log作为异常宕机或者介质故障后的数据恢复使用。
18.Mysql如何解决SQL注入的?
预编译
- 当客户发送一条SQL语句给服务器后,服务器总是需要校验SQL语句的语法格式是否正确,然后把SQL语句编译成可执行的函数,最后才是执行SQL语句。其中校验语法,和编译所花的时间可能比执行SQL语句花的时间还要多。
注意:可执行函数存储在MySQL服务器中,并且当前连接断开后,MySQL服务器会清除已经存储的可执行函数。
如果我们需要执行多次insert语句,但只是每次插入的值不同,MySQL服务器也是需要每次都去校验SQL语句的语法格式,以及编译,这就浪费了太多的时间。如果使用预编译功能,那么只对SQL语句进行一次语法校验和编译,所以效率要高。
19.Mysql自增int主键好处
-
int 相比varchar、char、text使用更少的存储空间,而且数据类型简单,可以节约CPU的开销,更便于表结构的维护
-
默认都会在主键上建立主键索引,使用整形作为主键可以将更多的索引载入内存,提高查询性能
-
对于InnoDB存储引擎而言,每个二级索引都会使用主键作为索引值的后缀,使用自增主键可以减少索引的长度(大小),方便更多的索引数据载入内存
-
可以使索引数据更加紧凑,在数据插入、删除、更新时可以做到索引数据尽可能少的移动、分裂页,减少碎片的产生(可以通过optimize table 来重建表),减少维护开销
-
在数据插入时,可以保证逻辑相邻的元素物理也相邻,便于范围查找
20…Mysql的Undo log是什么?
事务原子性需要保证事务中的操作要么全部完成,要么什么也不做。但通常会遇到下面的情况:
事务执行过程中可能遇到各种错误,比如服务器本身的错误等
程序在执行过程中通过ROLLBACK取消当前事务的执行。
上面这两种情况就导致事务执行到一半就结束了,但可能已经修改了很多数据,为了事务的原子性,需要把修改的数据给还原回来,这个过程就是回滚。
InnoDB引擎中的回滚通过undo log来实现,当需要修改某个数据时候,首先把数据页从磁盘加载到Buffer Pool中,然后记录一条undo log日志,之后再进行修改。
而对于增删查改,不同的操作产生的undo log的格式也有所不同。Undo log是与事务密切相关的,先简单了解一下事务的相关信息。
21.Mysql数据库innodb的崩溃恢复
22.B+树高度计算
Redis
1.redis有哪几种数据结构?
2.Redis List组件使用:
- List 说白了就是链表(redis 使用双端链表实现的 List),是有序的,value可以重复,可以通过下标取出对应的value值,左右两边都能进行插入和删除数据。
- lpush+lpop=Stack(栈)
- lpush+rpop=Queue(队列)
- lpush+ltrim=Capped Collection(有限集合)
- lpush+brpop=Message Queue(消息队列)
3.Redis为什么这么快??
- redis基于内存
- redis单线程,避免线程切换的开销
- redis数据结构简单
- 多路IO 非阻塞式IO
redis引入了多线程:Redis6.0 引入多线程主要是为了提高网络 IO 读写性能,因为这个算是 Redis 中的一个性能瓶颈(Redis 的瓶颈主要受限于内存和网络)。
Redis 服务器是一个事件驱动程序,服务器需要处理两类事件: 1. 文件事件; 2. 时间事
件。主要用的都是文件事件。虽然文件事件处理器以单线程方式运行,但通过使用 I/O 多路复用程序来监听多个套接字, 文件事件处理器既实现了高性能的网络通信模型,又可以很好地与 Redis 服务器中其他同样 以单线程方式运行的模块进行对接,这保持了 Redis 内部单线程设计的简单性。
redis为什么比mysql快?
- Redis是基于内存存储的,MySQL是基于磁盘存储的
- Redis存储的是k-v格式的数据。时间复杂度是O(1),常数阶,而MySQL引擎的底层实现是B+Tree,时间复杂度是O(logn),对数阶
- MySQL数据存储是存储在表中,查找数据时要先对表进行全局扫描或者根据索引查找,这涉及到磁盘的查找,磁盘查找如果是按条点查找可能会快点,但是顺序查找就比较慢;而Redis不用这么麻烦,本身就是存储在内存中,会根据数据在内存的位置直接取出
- redis是单线程,多路IO。
- 为什么用多路IO: Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以 I/O 操作在一般情况下往往不能直接返回,这会导致某一文件的 I/O 阻塞导致整个进程无法对其它客户提供服务,而 I/O 多路复用就是为了解决这个问题而出现的。
4.Redis的分布式锁如何实现?
-
https://www.cnblogs.com/moxiaotao/p/10829799.html
-
用redis的话:
- 主要用到的redis函数是setnx(),这个应该是实现分布式锁最主要的函数。首先是将某一任务标识名(这里用Lock:order作为标识名的例子)作为键存到redis里,并为其设个过期时间,如果是还有Lock:order请求过来,先是通过setnx()看看是否能将Lock:order插入到redis里,可以的话就返回true,不可以就返回false,注意用完之后要del key。这个del必须是删除自己设置的,否则可能A宕机锁过期后,被B拿到,然后这时A启动del,结果B的锁也被删掉了。
-
用zookeeper:大致思路:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。
ZK中创建和删除节点只能通过Leader服务器来执行,然后将数据同步到所有的Follower机器上,所以性能上不如基于缓存实现。
5.redis的持久化方式?
- 这个文章有,我就答了RDB,AOF及其区别
6.redis怎么进行扩容?
- redis扩容的几种方案
- 第一个想到hash,然后一致性hash,然后加虚拟节点,然后集群模式(这是redis扩容化的方案)
7.Redis热key,缓存击穿怎么办?
- 一种策略是加锁,当缓存取不到,去数据库/服务中取的时候加一个互斥锁
- 二是加限流,当缓存中取不到,去数据库/服务中取的时候加限流组件。
- 合理设置过期时间
8.redis cluster架构介绍和性质?
- 从普通hash —— 一致性hash ——hash slot是如何演变的?
- 普通hash:宕机怎么办?
- 一致性hash:热点key都存在一个机器没法负载均衡,加一些虚拟节点可以帮助负载均衡
- hash slot:节点之间转移就转移hash slot就行了,开销很低
插播:内存为啥快:内存直接由CPU控制,也就是CPU内部集成的内存控制器,所以说内存是直接与CPU对接,享受与CPU通信的最优带宽,然而硬盘则是通过桥接芯片(在主板上)与CPU相连,所以说速度比较慢
9.redis内存淘汰机制:
- 文章:redis过期策略和淘汰机制
- 过期策略分惰性和定时,定时就是每隔一段时间看看有没有过期的如果有就删了,惰性是每次get时判断
- 淘汰机制有8种:淘汰机制介绍: 当redis内存快耗尽时,redis会启动内存淘汰机制,将部分key清掉以腾出内存。(4.0版本新增两种)
noeviction: 禁止驱逐数据。默认配置都是这个。当内存使用达到阀值的时候,所有引起申请内存的命令都会报错。
volatile-lru: 从设置了过期时间的数据集中挑选最近最少使用的数据淘汰。
volatile-ttl: 从已设置了过期时间的数据集中挑选即将要过期的数据淘汰。
volatile-random: 从已设置了过期时间的数据集中任意选择数据淘汰。
allkeys-lru: 从数据集中挑选最近最少使用的数据淘汰。
allkeys-random: 从数据集中任意选择数据淘汰。
volatile-lfu: 从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
allkeys-lfu: 当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key
10.Redis的redLock算法:
为了实现分布式锁,普通的setnx只能单点redis:
假设有5个完全独立的redis主服务器
-
获取当前时间戳
-
client尝试按照顺序使用相同的key,value获取所有redis服务的锁,在获取锁的过程中的获取时间比锁过期时间短很多,这是为了不要过长时间等待已经关闭的redis服务。并且试着获取下一个redis实例。
比如:TTL为5s,设置获取锁最多用1s,所以如果一秒内无法获取锁,就放弃获取这个锁,从而尝试获取下个锁
-
client通过获取所有能获取的锁后的时间减去第一步的时间,这个时间差要小于TTL时间并且至少有3个redis实例成功获取锁,才算真正的获取锁成功
-
如果成功获取锁,则锁的真正有效时间是 TTL减去第三步的时间差 的时间;比如:TTL 是5s,获取所有锁用了2s,则真正锁有效时间为3s(其实应该再减去时钟漂移);
-
如果客户端由于某些原因获取锁失败,便会开始解锁所有redis实例;因为可能已经获取了小于3个锁,必须释放,否则影响其他client获取锁
一些要求:
-
TTL时长 要大于正常业务执行的时间+获取所有redis服务消耗时间+时钟漂移
-
获取redis所有服务消耗时间要 远小于TTL时间,并且获取成功的锁个数要 在总数的一般以上:N/2+1
-
尝试获取每个redis实例锁时的时间要 远小于TTL时间
-
尝试获取所有锁失败后 重新尝试一定要有一定次数限制
-
在redis崩溃后(无论一个还是所有),要延迟TTL时间重启redis
-
在实现多redis节点时要结合单节点分布式锁算法 共同实现
11.缓存的读模式和写模式:
- 项目中我们用的是读模式,先读cache,如果cache miss再读数据库/服务,最后回写到cache
- 还有写模式,写模式就是先写DB,然后删掉缓存中对应数据
- 这里面又分为DB First 策略,表示先写 DB,再删除缓存。
和Cache First 策略,表示先删除缓存,再写 DB。
12.redis主从复制:
redis 主从复制的核心原理
-
当启动一个 slave node 的时候,它会发送一个 PSYNC 命令给 master node。
-
如果这是 slave node 初次连接到 master node,那么会触发一次 full resynchronization 全量复制。此时 master 会启动一个后台线程,开始生成一份 RDB 快照文件,
-
同时还会将从客户端 client 新收到的所有写命令缓存在内存中。RDB 文件生成完毕后, master 会将这个 RDB 发送给 slave,slave 会先写入本地磁盘,然后再从本地磁盘加载到内存中,
-
接着 master 会将内存中缓存的写命令发送到 slave,slave 也会同步这些数据。
13.redis宕机的问题:
主从模式下,需要分情况来看:
- slave从redis宕机
在Redis中从库重新启动后会自动加入到主从架构中,自动完成同步数据;
如果从数据库实现了持久化,只要重新加入到主从架构中会实现增量同步。 - 主库宕机:假如主从都没数据持久化,此时千万不要立马重启服务,否则可能会造成数据丢失,正确的操作如下:
-
在slave数据上执行SLAVEOF ON ONE,来断开主从关系并把slave升级为主库
-
此时重新启动主数据库,执行SLAVEOF,把它设置为从库,自动备份数据。
以上过程很容易配置错误,可以使用简单的方法:redis的哨兵(sentinel)的功能。
-
14.Redis哨兵:
它本身也是一个独立的 Redis 节点,只不过它不存储数据,只支持部分命令,它能够自动完成故障发现和故障转移,并通知应用方,从而实现高可用。
哨兵(sentinel)的原理:
-
Redis提供了sentinel(哨兵)机制通过sentinel模式启动redis后,自动监控master/slave的运行状态,基本原理是:心跳机制+投票裁决。
-
每个sentinel会向其它sentinal、master、slave定时发送消息,以确认对方是否“活”着,如果发现对方在指定时间(可配置)内未回应,则暂时认为对方已挂(所谓的“主观认为宕机” Subjective Down,简称SDOWN)。
-
若"哨兵群"中的多数sentinel,都报告某一master没响应,系统才认为该master"彻底死亡"(即:客观上的真正down机,Objective Down,简称ODOWN),通过一定的vote算法,从剩下的slave节点中,选一台提升为master,然后自动修改相关配置。
15.Redisson分布式锁实现原理
- redisson分布式锁实现
- redisson存储的key-value对比较特殊,key里面包含了线程的特殊信息,所以线程就可以实现可重入这一特性,提高了效率
- 加锁机制:通过lua脚本来保证原子性,而且也可以在释放锁的时候进行判断,判断这个锁是不是自己之前加的
- redisson在加锁成功后,会注册一个定时任务监听这个锁,每隔10秒就去查看这个锁,如果还持有锁,就对过期时间进行续期。默认过期时间30秒。这个机制也被叫做:“看门狗”,这名字。举例子:假如加锁的时间是30秒,过10秒检查一次,一旦加锁的业务没有执行完,就会进行一次续期,把锁的过期时间再次重置成30秒。
16.Redis的keys命令问题:
- keys命令在大数据量情况下会卡顿,而且本身不提供limit等参数,所以在单线程环境不好
- 应该使用scan命令,SCAN命令是增量的循环,每次调用只会返回一小部分的元素。所以不会让redis假死。
17.redis的跳表问题
- 跳表是可以实现二分查找的有序链表;
- 每个元素插入时随机生成它的level;
- 最低层包含所有的元素;
- 如果一个元素出现在level(x),那么它肯定出现在x以下的level中;
- 每个索引节点包含两个指针,一个向下,一个向右;
- 跳表查询、插入、删除的时间复杂度为O(log n),与平衡二叉树接近;
- 手写跳表:手写跳表
为什么Redis选择使用跳表而不是红黑树来实现有序集合?
- 首先,我们来分析下Redis的有序集合支持的操作:1)插入元素2)删除元素3)查找元素4)有序输出所有元素5)查找区间内所有元素其中,前4项红黑树都可以完成,且时间复杂度与跳表一致。但是,最后一项,红黑树的效率就没有跳表高了。在跳表中,要查找区间的元素,我们只要定位到两个区间端点在最低层级的位置,然后按顺序遍历元素就可以了,非常高效。而红黑树只能定位到端点后,再从首位置开始每次都要查找后继节点,相对来说是比较耗时的。此外,跳表实现起来很容易且易读,红黑树实现起来相对困难,所以Redis选择使用跳表来实现有序集合。
那为什么hashmap采用红黑树而不是跳表呢?
- 首先,跳表是空间换时间的策略,hashmap采用跳表会多出很多空间
- 其次,跳表之间的节点是有序的,而hashmap的节点不需要有序,所以不适合
18.redis的几种数据结构底层存储
-
String:实现方式:String在redis内部存储默认就是一个字符串,被redisObject所引用,当遇到incr,decr等操作时会转成数值型进行计算,此时redisObject的encoding字段为int。
-
Hash:上面已经说到Redis Hash对应Value内部实际就是一个HashMap,实际这里会有2种不同实现,这个Hash的成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,对应的value redisObject的encoding为zipmap,当成员数量增大时会自动转成真正的HashMap,此时encoding为ht。
-
list:Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构
-
Set:set 的内部实现是一个 value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因。
19.Redis怎么判断数据过期:
Redis 通过一个叫做过期字典(可以看作是hash表)来保存数据过期的时间。过期字典的键指向 Redis数据库中的某个key(键),过期字典的值是一个long long类型的整数,这个整数保存了key所 指向的数据库键的过期时间(毫秒精度的UNIX时间戳)。
20.Redis分布式锁 & RedLock
21.Redis 的大key问题如何解决
- 如何发现 redis-cli --bigkeys 有命令可以看
- 第三方工具 DBA
- 优化方式: 分割、压缩
22.Redis 的热key问题如何解决
- 怎么检测:redis monitor,redis-stat
- redis cli的慢查询日志,或者-- hot keys
- 解决办法:读写分离,数据分片,限流
- 本地缓存,key的分散
22.Redis的 rehash
22.Redis和mysql数据一致性???
Kafka面试题
1.Kafka是什么?
Kafka 是一个分布式流式处理平台。这到底是什么意思呢? 流平台具有三个关键功能:
-
消息队列:发布和订阅消息流,这个功能类似于消息队列,这也是 Kafka 也被归类为消息队 列的原因。
-
容错的持久方式存储记录消息流: Kafka 会把消息持久化到磁盘,有效避免了消息丢失的⻛ 险·。
-
流式处理平台: 在消息发布的时候进行处理,Kafka 提供了一个完整的流式处理类库。 Kafka 主要有两大应用场景:
消息队列 :建立实时流数据管道,以可靠地在系统或应用程序之间获取数据。
数据处理: 构建实时的流数据处理程序来转换或处理数据流。
2.Kafka的消息模型–发布订阅
发布订阅模型(Pub-Sub) 使用主题(Topic) 作为消息通信载体,类似于广播模式;发布者发 布一条消息,该消息通过主题传递给所有的订阅者,在一条消息广播之后才订阅的用户则是收不 到该条消息的。
在发布 - 订阅模型中,如果只有一个订阅者,那它和队列模型就基本是一样的了。所以说,发布 - 订阅模型在功能层面上是可以兼容队列模型的。
3. Kafka 的多副本机制了解吗?带来了什么好处?
分区 (Partition)中的多个副本之间会有一个叫做 leader 的家伙,其他副本称为 follower。我们发送 的消息会被发送到 leader 副本,然后 follower 副本才能从 leader 副本中拉取消息进行同步。
Kafka 的多分区(Partition)以及多副本(Replica)机制有什么好处呢?
- Kafka 通过给特定 Topic 指定多个 Partition, 而各个 Partition 可以分布在不同的 Broker 上, 这样便能提供很好的并发能力(负载均衡)。
- Partition 可以指定对应的 Replica 数, 这也极大地提高了消息存储的安全性, 提高了容灾能 力,不过也相应的增加了所需要的存储空间。
4. Kafka 如何保证消息的消费顺序?
第一种方法,只有一个partition,这样肯定有序,但是性能不好
第二种方法,消息发送的时候,人为指定 key/Partition
5.Kafka 如何保证消息不丢失
- 生产者消息丢失:注册一个回调函数,然后失败之后重试。
- 消费者丢失消息:消费者一般不会出问题。当消费者拉取到了分区的某个消息之后,消费者会自动提交了 offset。自动提交的话会有一个问 题,试想一下,当消费者刚拿到这个消息准备进行真正消费的时候,突然挂掉了,消息实际上并 没有被消费,但是 offset 却被自动提交了。
解决办法也比较粗暴,我们手动关闭闭自动提交 offset,每次在真正消费完消息之后之后再自己 手动提交 offset 。 但是,细心的朋友一定会发现,这样会带来消息被重新消费的问题。比如你 刚刚消费完消息之后,还没提交 offset,结果自己挂掉了,那么这个消息理论上就会被消费两 次。
6.Kafka如何保证消息不重复消费
https://cloud.tencent.com/developer/article/2117165
七.常用组件
linux
主要是考察常用的linux指令
1.linux查看不同CPU使用情况
- top指令?
2.linux中查看文件行数
- wc -l xx.txt
3.批量替换文件中的字符串:
比如我这里要将qt目录下 所有文件中的aaa替换成bbb
sed -i "s/aaa/bbb/g" `grep aaa -rl ./qt`
格式: sed -i "s/查找字段/替换字段/g" `grep 查找字段 -rl 路径`
其中
-i 表示inplace edit,就地修改文件
-r 表示搜索子目录
-l 表示输出匹配的文件名
4.Linux系统权限:
- 如何看 ls -l命令
- 如何用 chmod 755 / chmod u=rwx,g=rx,o=rx photo.py
- 前三个所有者权限,中三个同组权限,后三个其他权限
5.如何查看端口号是否被占用
- netstat -anp |grep 端口号
6.如何查看TCP连接状态:
netstat -t
八.项目,架构等
1.怎样的服务才算微服务?(感觉没有定论)
- 所有的服务都尽量保证无状态或者有状态的可以做状态转移(如session等数据,可以转移 到redis集群中)
- 业务的相对分离
- 使用API网关(尽量不要将微服务的各个服务暴露出去,以免造成安全问题)
- 服务间采用统一的通信模式(restful、Thrift等)
- 能够脱离开发语言,即不受制于特定的开发语言
- 微服务中的各个服务尽量不要有强依赖(即不会因为某个服务的停止,而导致整个服务或者其他服务不可用)
- 具有易于实现HA的特质,即不存在单点故障,同时运行多个实例提供服务并实现了负载均衡
2.令牌桶算法的实现?
guava的RateLimiter实现: 发现并没有“一个线程定时往桶里放token”,而是在请求进来时通过当前时间戳实时计算(下一次加令牌的时间为x,此时时间为m,平均n秒放一个令牌,则此时应当增加 (m - x) / n + 1 个令牌)
/**
* 令牌桶
* 局限:最多1毫秒生成一个令牌
*
* create time: 2020/7/15 9:42
*/
public class TokenBucket {
private final double unitAddNum; // 单位时间(1s)往桶中放令牌数量
private final long maxTokenNum; // 桶中最大有多少令牌
private volatile long currentTokenCount = 0; // 当前桶中有多少令牌
private volatile long nextRefreshTime = 0L; // 下一次刷新桶中令牌数量的时间戳
private volatile long lastAcquireTime; // 上一次从桶中获取令牌的时间戳(貌似用不到)
/**
*
* @param unitAddNum 1秒增加几个令牌
* @param maxToken 桶中最大令牌数
* @param isFullStart 一开始是否是满的
*/
public TokenBucket(double unitAddNum, long maxToken, boolean isFullStart) {
if (unitAddNum <= 0 || maxToken <= 0) {
throw new RuntimeException("unitAddNum and maxToken can't less than 0");
}
if (unitAddNum > 1000) {
throw new RuntimeException("unitAddNum max is 1000");
}
this.unitAddNum = unitAddNum;
this.maxTokenNum = maxToken;
if (isFullStart) {
this.currentTokenCount = maxToken;
} else {
this.currentTokenCount = 0;
}
this.nextRefreshTime = calculateNextRefreshTime(System.currentTimeMillis());
}
public boolean acquire(long needTokenNum) {
if (needTokenNum > this.maxTokenNum) {
return false;
}
synchronized (this) {
long currentTimestamp = System.currentTimeMillis();
this.refreshCurrentTokenCount(currentTimestamp);
if (needTokenNum <= this.currentTokenCount) {
return this.doAquire(needTokenNum, currentTimestamp);
}
return false;
}
}
private boolean doAquire(long needTokenNum, long currentTimestamp) {
this.currentTokenCount -= needTokenNum;
this.lastAcquireTime = currentTimestamp;
return true;
}
/**
* 刷新桶中令牌数量
* @param currentTimestamp
*/
private void refreshCurrentTokenCount(long currentTimestamp) {
if (this.nextRefreshTime > currentTimestamp) {
return;
}
this.currentTokenCount = Math.min(this.maxTokenNum, this.currentTokenCount + calculateNeedAddTokenNum(currentTimestamp));
this.nextRefreshTime = calculateNextRefreshTime(currentTimestamp);
}
/**
* 计算当前需要添加多少令牌
* @param currentTimestamp
* @return
*/
private long calculateNeedAddTokenNum(long currentTimestamp) {
if (this.nextRefreshTime > currentTimestamp) {
return 0;
}
long addOneMs = Math.round(1.0D / this.unitAddNum * 1000D); // 这么久才能加1个令牌
return (currentTimestamp - this.nextRefreshTime) / addOneMs + 1;
}
private long calculateNextRefreshTime(long currentTimestamp) {
if (currentTimestamp < this.nextRefreshTime) {
return this.nextRefreshTime;
}
long addOneMs = Math.round(1.0D / this.unitAddNum * 1000D); // 这么久才能加1个令牌
long result = 0;
if (this.nextRefreshTime <= 0) {
result = currentTimestamp + addOneMs;
} else {
result = this.nextRefreshTime + (currentTimestamp - this.nextRefreshTime) / addOneMs + addOneMs;
}
return result;
}
public static void main(String[] args) throws InterruptedException {
TokenBucket tokenBucket = new TokenBucket(1D, 1, true);
for (int i=0; i<10; i++) {
System.out.println(tokenBucket.acquire(1));
Thread.sleep(500);
}
}
}
//注意:此处实现的思路实际上是RateLimiter的tryAcquire()方法,是非阻塞式的,而阻塞式的API acquire()则需要计算时间戳之间的差值,从而挂起等待。
补充:预热式限流和稳定式限流:
- 目前用的是稳定式,比较粗糙。它只记得最后一个请求的时间,并不记得它是否很久没有接入过请求。所以如果这个 RateLimiter 有很长一段时间没有使用了,这时突然有请求接入,只要最初接入的值时小于设置的限额,那它就会被立刻执行。这确实是符合限流预期的,但是在现实中却不如设计中那么理想。
- 在现实使用中,如果服务长期未使用,可能依赖的服务或者缓存并没有完全准备好,像是缓存已经过期良久,甚至需要重新唤起其他服务进行数据处理。这时候接入了流量就会存在无法完全得到处理情况,如果设置限流值较大甚至会在第一时间内造成缓存雪崩,严重甚至会拖垮服务。
- 它支持两种获取permits接口,一种是如果拿不到立刻返回false,一种会阻塞等待一段时间看能不能拿到.
补充:关于RateLimiter的很好的设计:
-
对一个每秒产生一个令牌的RateLimiter,每有一个没有使用令牌的一秒,我们就将storedPermits加1,如果RateLimiter在10秒都没有使用,则storedPermits变成10.0.这个时候,一个请求到来并请求三个令牌(acquire(3)),我们将从storedPermits中的令牌为其服务,storedPermits变为7.0.这个请求之后立马又有一个请求到来并请求10个令牌,我们将从storedPermits剩余的7个令牌给这个请求,剩下还需要三个令牌,我们将从RateLimiter新产生的令牌中获取.我们已经知道,RateLimiter每秒新产生1个令牌,就是说上面这个请求还需要的3个请求就要求其等待3秒.
-
想象一个RateLimiter每秒产生一个令牌,现在完全没有使用(处于初始状态),限制一个昂贵的请求acquire(100)过来.如果我们选择让这个请求等待100秒再允许其执行,这显然很荒谬.我们为什么什么也不做而只是傻傻的等待100秒,一个更好的做法是允许这个请求立即执行(和acquire(1)没有区别),然后将随后到来的请求推迟到正确的时间点.这种策略, 我们允许这个昂贵的任务立即执行,并将随后到来的请求推迟100秒.这种策略就是让任务的执行和等待同时进行.
-
一个重要的结论:RateLimiter不会记最后一个请求,而是即下一个请求允许执行的时间.这也可以很直白的告诉我们到达下一个调度时间点的时间间隔. 然后定一个一段时间未使用的Ratelimiter也很简单:下一个调度时间点已经过去,这个时间点和现在时间的差就是Ratelimiter多久没有被使用,我们会将这一段时间翻译成storedPermits.所有,如果每秒钟产生一个令牌(rate==1),并且正好每秒来一个请求,那么storedPermits就不会增长.
3.protobuffer 和 json区别
1、protobuf是google开发的一个数据传输格式,类似json
2、protobuf是二进制的、结构化的,所以比json的数据量更小,也更对象化
3、protobuf不是像json直接明文的,这个是定义对象结构,然后由protbuf库去把对象自动转换成二进制,用的时候再自动反解过来的。传输对我们是透明的!我们只管传输的对象就可以了
4、json需要吧对象转为json字符串,然后json解析成对象
5、protobuffer是对象解析成二进制流,然后转为对象
4.zookeeper简单介绍:
- 假设我们的程序是分布式部署在多台机器上,如果我们要改变程序的配置文件,需要逐台机器去修改,非常麻烦,现在把这些配置全部放到zookeeper上去,保存在 zookeeper 的某个目录节点中,然后所有相关应用程序对这个目录节点进行监听,一旦配置信息发生变化,每个应用程序就会收到 zookeeper 的通知,然后从 zookeeper 获取新的配置信息应用到系统中。
5.docker有什么用
让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。简言之,就是可以在Linux上镜像使用的这么一个容器。
6.Paxos算法是什么?
7.CAS的ABA问题如何优化
新增一个版本号字段version,不止比较值,还要比较版本,这样就可以避免
8.排序算法稳定性问题
- 泡,插,归,基:稳定
- 快速,shell,选择,堆:不稳
- 排序算法稳定性
9.qps越来越多,怎么办?
整体思路:异步,缓存,加机器
- 布隆过滤器解决判断缓存/数据库有没有某个key的问题
- 比如redis存的是用户数据,key是用户id,有很多新用户过来发请求。这时候就可以加一个布隆过滤器,可以判断id是不是在缓存中,如果id不再缓存中百分之百正确,如果id在缓存中可能误判,这个无所谓的。
- 解决缓存穿透:我们经常会把一些热点数据放在 Redis 中当作缓存,例如产品详情。 通常一个请求过来之后我们会先查询缓存,而不用直接读取数据库,这是提升性能最简单也是最普遍的做法,但是 如果一直请求一个不存在的缓存,那么此时一定不存在缓存,那就会有 大量请求直接打到数据库 上,造成 缓存穿透,布隆过滤器也可以用来解决此类问题。
- 延时队列。前面我们提到产品经理在遇到「请勿打扰」的牌子时可以选择多种策略,1. 干等待 2. 睡觉 2. 放弃不干了 3. 歇一会再干。干等待就是 spinlock,这会烧 CPU,飙高 Redis 的QPS。睡觉就是先 sleep 一会再试,这会浪费线程资源,还会增加响应时长。放弃不干呢就是告知前端用户待会再试,现在系统压力大有点忙,影响用户体验。最后一种呢就是现在要讲的策略 —— 待会再来,这是在现实世界里最普遍的策略。这种策略一般用在消息队列的消费中,这个时候遇到锁冲突该怎么办?不能抛弃不处理,也不适合立即重试(spinlock),这时就可以将消息扔进延时队列,过一会再处理。
- 有很多专业的消息中间件支持延时消息功能,比如 RabbitMQ 和 NSQ。Redis 也可以,我们可以使用 zset 来实现这个延时队列。zset 里面存储的是 value/score 键值对,我们将 value 存储为序列化的任务消息,score 存储为下一次任务消息运行的时间(deadline),然后轮询 zset 中 score 值大于 now 的任务消息进行处理
- mysql主从:先优化查询和索引,然后通过加缓存,最后才是主从架构设计,同一份数据被放到了多个数据库中,其中一个数据库是 Master 主库,其余的多个数据库是 Slave 从库。当主库进行更新的时候,会自动将数据复制到从库中,而我们在客户端读取数据的时候,会从从库中进行读取,也就是采用读写分离的方式。互联网的应用往往是一些“读多写少”的需求,采用读写分离的方式,可以实现更高的并发访问。原本所有的读写压力都由一台服务器承担,现在有多个“兄弟”帮忙处理读请求,这样就减少了对后端大哥(Master)的压力。同时,我们还能对从服务器进行负载均衡,让不同的读请求按照策略均匀地分发到不同的从服务器上,让读取更加顺畅。读取顺畅的另一个原因,就是减少了锁表的影响,比如我们让主库负责写,当主库出现写锁的时候,不会影响到从库进行 SELECT 的读取。而且还能备份数据,比如主库挂了
- 优化存储实际数据的结构:
- 比如之前用String,能不能改为占内存更小的比特位?
- 位图bitmap ,位图索引如何优化查询
11.OOM问题排查
- OOM介绍:什么是OOM
- 查看系统日志
- ps -ef | grep java 查看所有java进程
- top,这里可以看到你的进程可能对内存的使用率特别高。以查看正在运行的进程和系统负载信息,包括cpu负载、内存使用、各个进程所占系统资源等
- jstat -gcutil 查看GC情况
- 参数含义图:
- 比如上面这几个数据,意思就是 suvivor 0 使用26%,survivor1使用0,Eden区10%,老年代90%,FGC发生954次,说明对象太多,老年代很快被占满,频繁Full GC,对象却回收不掉
- jmap -histo pid可以打印出当前堆中所有每个类的实例数量和内存占用,如下,class name是每个类的类名([B是byte类型,[C是char类型,[I是int类型),bytes是这个类的所有示例占用内存大小,instances是这个类的实例数量。
- 把当前堆内存的快照转储到dumpfile_jmap.hprof文件中,对内存快照进行分析。jmap -dump:format=b,file=文件名 [pid],就可以把指定java进程的堆内存快照搞到一个指定的文件
- jvisualVM.exe分析一下
总结:
- 一般常见的OOM,要么是短时间内涌入大量的对象,导致你的系统根本支持不住,此时你可以考虑优化代码(这里讨论:代码问题还是JVM参数不合理?)
12.频繁full gc问题排查
- 加参数-XX:+PrintGCTimeStamps或-XX:+PrintGCDeatils
- 每次Full GC过后,ParOldGen就是老年代老是下不去,那就是大量的内存一直占据着老年代,啥事儿不干,回收不掉,所以频繁的full gc,每次full gc肯定会导致一定的stop the world卡顿
- 例子: 比如说当时我们有个系统,在后台运行,每次都会一下子从mysql里加载几十万行数据进来各种处理,类似于定时批量处理,这个时候,如果对几十万数据的处理比较慢,就会导致比如几分钟里面,大量数据囤积在老年代,然后没法回收,就会频繁full gc。
13.paxos算法和zab协议:
ZAB 用来构建高可用的分布式数据主备系统(Zookeeper),Paxos 是用来构建分布式一致性状态机系统。
zab协议两种模式:
- 消息广播模式:把数据更新到所有的Follower
我是领导,我要向各位传达指令,不过传达之前我先问一下大家支不支持我,若有一半以上的人支持我,那我就向各位传达指令了。 - 崩溃恢复模式:Leader发生崩溃时,如何恢复,这里就是选举新的leader。ZAB在Paxos基础上额外添加一个同步阶段。同步阶段之前,ZAB协议存在一个和Paxos读阶段类似的发现(Discovery)阶段
同步阶段中,新的Leader会确保存在过半的Follower已经提交了之前Leader周期中的所有事务Proposal
发现阶段的存在,确保所有进程都已经完成对之前所有事物Proposal的提交
14.redis和memcache:
1. 性能上:
性能上都很出色,具体到细节,由于Redis只使用单核,而Memcached可以使用多核,所以平均每一个核上Redis在存储小数据时比
Memcached性能更高。而在100k以上的数据中,Memcached性能要高于Redis,虽然Redis最近也在存储大数据的性能上进行优化,但是比起 Memcached,还是稍有逊色。
2. 内存空间和数据量大小:
MemCached可以修改最大内存,采用LRU算法。Redis增加了VM的特性,突破了物理内存的限制。
3. 操作便利上:
MemCached数据结构单一,仅用来缓存数据,而Redis支持更加丰富的数据类型,也可以在服务器端直接对数据进行丰富的操作,这样可以减少网络IO次数和数据体积。
4. 可靠性上:
MemCached不支持数据持久化,断电或重启后数据消失,但其稳定性是有保证的。Redis支持数据持久化和数据恢复,允许单点故障,但是同时也会付出性能的代价。
5. 应用场景:
Memcached:动态系统中减轻数据库负载,提升性能;做缓存,适合多读少写,大数据量的情况(如人人网大量查询用户信息、好友信息、文章信息等)。
Redis:适用于对读写效率要求都很高,数据处理业务复杂和对安全性要求较高的系统(如新浪微博的计数和微博发布部分系统,对数据安全性、读写要求都很高)。
15.同步异步,阻塞非阻塞概念
- 文章:同步异步阻塞非阻塞概念
- 同步异步是一种消息通知机制,同步时,A必须等待结果;异步时,结果会由被调用方通知A
- 同步是指客户端发出请求后,在没有得到想要结果前,一直阻塞
异步是指客户端发出请求后,马上返回但是没有结果。等服务端运行结束后通过回调再通知客户端 - 阻塞就是看线程能不能干其他事:
- 阻塞: A调用B,A被挂起直到B返回结果给A,A继续执行。调用结果返回前,当前进程挂起不能够处理其他任务,一直等待调用结果返回。
- 非阻塞: A调用B,A不会被挂起,A可以执行其他操作。调用结果返回前,当前进程不挂起, 可以去处理其他任务。
16.RPC要解决的问题
- Call ID映射。我们怎么告诉远程机器我们要调用哪个函数呢?在本地调用中,函数体是直接通过函数指针来指定的,我们调用具体函数,编译器就自动帮我们调用它相应的函数指针。但是在远程调用中,是无法调用函数指针的,因为两个进程的地址空间是完全不一样。所以,在RPC中,所有的函数都必须有自己的一个ID。这个ID在所有进程中都是唯一确定的。客户端在做远程过程调用时,必须附上这个ID。然后我们还需要在客户端和服务端分别维护一个 {函数 <–> Call ID} 的对应表。两者的表不一定需要完全相同,但相同的函数对应的Call ID必须相同。当客户端需要进行远程调用时,它就查一下这个表,找出相应的Call ID,然后把它传给服务端,服务端也通过查表,来确定客户端需要调用的函数,然后执行相应函数的代码。
- 序列化和反序列化。客户端怎么把参数值传给远程的函数呢?在本地调用中,我们只需要把参数压到栈里,然后让函数自己去栈里读就行。但是在远程过程调用时,客户端跟服务端是不同的进程,不能通过内存来传递参数。甚至有时候客户端和服务端使用的都不是同一种语言(比如服务端用C++,客户端用Java或者Python)。这时候就需要客户端把参数先转成一个字节流,传给服务端后,再把字节流转成自己能读取的格式。这个过程叫序列化和反序列化。同理,从服务端返回的值也需要序列化反序列化的过程。
- 网络传输。远程调用往往是基于网络的,客户端和服务端是通过网络连接的。所有的数据都需要通过网络传输,因此就需要有一个网络传输层。网络传输层需要把Call ID和序列化后的参数字节流传给服务端,然后再把序列化后的调用结果传回客户端。只要能完成这两者的,都可以作为传输层使用。因此,它所使用的协议其实是不限的,能完成传输就行。尽管大部分RPC框架都使用TCP协议,但其实UDP也可以,而gRPC干脆就用了HTTP2。Java的Netty也属于这层的东西。
17.秒杀系统设计?
基础:
- 秒杀系统独立做一个系统
- 部署服务器集群,防止影响其他服务
- 秒杀商品单独做缓存
进阶:
- 动静分离,不用刷新整个页面,而是只对特定的按钮处理
- 在服务端对秒杀商品进行本地缓存,不需要再调用依赖系统的后台服务获取数据,甚至不需要去公共的缓存集群中查询数据,这样不仅可以减少系统调用,而且能够避免压垮公共缓存集群
- 加系统限流保护,防止最坏情况发生。
什么是动态和静态数据:
- 动态:每个人看的都不一样,个性化推荐
- 静态:一个页面人人看到的都一样
防止超卖问题:
- 需要使用redis层,一个是String结构,key是商品id,value是库存;一个是set结构,key是商品id,set是用户id
- 用户点击下单,获取redis对应商品id,查看value库存是否大于0,如果大于0那么查看set结构中有没有自己的id,如果没有那么加上,否则退出,然后给库存-1.
- 当库存为0,其他用户全部失败,返回没抢到的信息,根据set中保存的用户id/订单id进行下单派送等后续流程。
18.Docker & k8s:
-
我们自己的进程,之间可以通信,公用一个文件系统,这很不安全
-
跟虚拟机相比,docker是进程级别的,很小,占空间小,而且是秒级别的,虚拟机是分钟级的,是操作系统层面的虚拟化。
-
其实,容器就是一个视图隔离、资源可限制、独立文件系统的进程集合。所谓“视图隔离”就是能够看到部分进程以及具有独立的主机名等;控制资源使用率则是可以对于内存大小以及 CPU 使用个数等进行限制。容器就是一个进程集合,它将系统的其他资源隔离开来,具有自己独立的资源视图。
-
容器具有一个独立的文件系统,因为使用的是系统的资源,所以在独立的文件系统内不需要具备内核相关的代码或者工具,我们只需要提供容器所需的二进制文件、配置文件以及依赖即可。只要容器运行时所需的文件集合都能够具备,那么这个容器就能够运行起来。
- 说白了,这个Docker镜像,是一个特殊的文件系统。它除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(例如环境变量)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。
- 就在Docker容器技术被炒得热火朝天之时,大家发现,如果想要将Docker应用于具体的业务实现,是存在困难的——编排、管理和调度等各个方面,都不容易。于是,人们迫切需要一套管理系统,对Docker及容器进行更高级更灵活的管理。
19.nginx和tomcat区别,能不能反着用?
-
严格的来说,Apache/Nginx 应该叫做「HTTP Server」;而 Tomcat 则是一个「Application Server」,或者更准确的来说,是一个「Servlet/JSP」应用的容器(Ruby/Python 等其他语言开发的应用也无法直接运行在 Tomcat 上)。
-
一个 HTTP Server 关心的是 HTTP 协议层面的传输和访问控制,所以在 Apache/Nginx 上你可以看到代理、负载均衡等功能。客户端通过 HTTP Server 访问服务器上存储的资源(HTML 文件、图片文件等等)。通过 CGI 技术,也可以将处理过的内容通过 HTTP Server 分发,但是一个 HTTP Server 始终只是把服务器上的文件如实的通过 HTTP 协议传输给客户端。
-
而应用服务器,则是一个应用执行的容器。它首先需要支持开发语言的 Runtime(对于 Tomcat 来说,就是 Java),保证应用能够在应用服务器上正常运行。其次,需要支持应用相关的规范,例如类库、安全方面的特性。对于 Tomcat 来说,就是需要提供 JSP/Sevlet 运行需要的标准类库、Interface 等。为了方便,应用服务器往往也会集成 HTTP Server 的功能,但是不如专业的 HTTP Server 那么强大,所以应用服务器往往是运行在 HTTP Server 的背后,执行应用,将动态的内容转化为静态的内容之后,通过 HTTP Server 分发到客户端。
19.Nginx的模型,为什么用nginx
- nginx采用了单线程的模型
- nginx同样采用了epoll 多路复用模型
- 为什么单线程的 Nginx,处理能力却能够超越其他多线程的服务器呢?这要归功于 Nginx 利用了 Linux 内核里的一件“神兵利器”,I/O 多路复用接口,“大名鼎鼎”的 epoll。“多路复用”这个词我们已经在之前的 HTTP/2、HTTP/3 里遇到过好几次,如果你理解了那里的“多路复用”,那么面对 Nginx 的 epoll“多路复用”也就好办了。Web 服务器从根本上来说是“I/O 密集型”而不是“CPU 密集型”,处理能力的关键在于网络收发而不是 CPU 计算(这里暂时不考虑 HTTPS 的加解密),而网络 I/O 会因为各式各样的原因不得不等待,比如数据还没到达、对端没有响应、缓冲区满发不出去等等。
- epoll 还有一个特点,大量的连接管理工作都是在操作系统内核里做的,这就减轻了应用程序的负担,所以 Nginx 可以为每个连接只分配很小的内存维护状态
20.中台化的概念:
中台架构,简单地说,就是企业级能力的复用,一个种方法论,企业治理思想。
微服务,是可独立开发、维护、部署的小型业务单元,是一种技术架构方式。
可见,中台并不是微服务,中台是一种企业治理思想和方法论,微服务是技术架构方式。
21.TCC操作?
TCC操作:Try阶段,尝试执行业务,完成所有业务的检查,实现一致性;预留必须的业务资源,实现准隔离性。Confirm阶段:真正的去执行业务,不做任何检查,仅适用Try阶段预留的业务资源,Confirm操作还要满足幂等性。Cancel阶段:取消执行业务,释放Try阶段预留的业务资源,Cancel操作要满足幂等性。TCC与2PC(两阶段提交)协议的区别:TCC位于业务服务层而不是资源层,TCC没有单独准备阶段,Try操作兼备资源操作与准备的能力,TCC中Try操作可以灵活的选择业务资源,锁定粒度。TCC的开发成本比2PC高。实际上TCC也属于两阶段操作,但是TCC不等同于2PC操作
22.两阶段提交2PAC:
-
两阶段提交2PC是分布式事务中最强大的事务类型之一,两段提交就是分两个阶段提交,第一阶段询问各个事务数据源是否准备好,第二阶段才真正将数据提交给事务数据源。
-
为了保证该事务可以满足ACID,就要引入一个协调者(Cooradinator)。其他的节点被称为参与者(Participant)。协调者负责调度参与者的行为,并最终决定这些参与者是否要把事务进行提交。
-
第一个阶段就是协调者向参与者发消息,确认是否开启事务,第二阶段就是参与者向协调者反馈,如果有一个失败,就集体回滚
两阶段提交的问题:
- 性能:所有参与者在事务提交阶段处于同步阻塞状态,占用系统资源,容易导致性能瓶颈
- 可靠性问题:如果协调者存在单点故障问题,或出现故障,提供者将一直处于锁定状态
- 数据一致性问题:在阶段 2 中,如果出现协调者和参与者都挂了的情况,有可能导致数据不一致
23.三阶段提交3PAC
3PC最关键要解决的就是协调者和参与者同时挂掉的问题,所以3PC把2PC的准备阶段再次一分为二,这样三阶段提交。处理流程如下:
- 协调者向所有参与者发出包含事务内容的 canCommit 请求,询问是否可以提交事务,并等待所有参与者答复。 参与者收到 canCommit 请求后,如果认为可以执行事务操作,则反馈 yes 并进入预备状态,否则反馈 no。
- 只要有一个参与者反馈 no,或者等待超时后协调者尚无法收到所有提供者的反馈,即中断事务。所有参与者均反馈 yes,协调者预执行事务。协调者向所有参与者发出 preCommit 请求,进入准备阶段。参与者收到 preCommit 请求后,执行事务操作,将 undo 和 redo 信息记入事务日志中(但不提交事务)。各参与者向协调者反馈 ack 响应或 no 响应,并等待最终指令。
- 最终:只要有一个参与者反馈 no,或者等待超时后协调组尚无法收到所有提供者的反馈,即回滚事务。如果协调者处于工作状态,则向所有参与者发出 do Commit 请求。参与者收到 do Commit 请求后,会正式执行事务提交,并释放整个事务期间占用的资源。各参与者向协调者反馈 ack 完成的消息。协调者收到所有参与者反馈的 ack 消息后,即完成事务提交。
24.接口的安全考虑—加密怎么做?
- HMAC是一种使用单向散列函数来构造消息认证码的方法,其中HMAC中的H就是Hash的意思。通过HMAC算法对业务参数进行加密,server端和client端通过同样的加密步骤,生成签名,比对,如果一致那么通过否则失败。
- 很明显HMAC算法的输入是:消息+key,并且提供一个Hash函数(例如MD5)。输出就是一个字符串(如果是MD5,则这个字符串长度是16字节,如果是SHA1则是20字节)。示例:ssl握手的最后一步,客户端把握手的所有消息使用HMAC算法计算出一个值发送给服务器,服务器也做同样的操作发送给客户端。两端通过比较自己的值和收到的值,来保证ssl握手时的消息没有被篡改。
- HMAC是一个模式,SHA256是具体采用的hash算法
25.本地消息表—分布式事务:
基本思路就是:
-
消息生产方,需要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。然后消息会经过MQ发送到消息的消费方。如果消息发送失败,会进行重试发送。
-
消息消费方,需要处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理成功了,如果处理失败,那么就会重试执行。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。
-
生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。
这种方案遵循BASE理论,采用的是最终一致性,笔者认为是这几种方案里面比较适合实际业务场景的,即不会出现像2PC那样复杂的实现(当调用链很长的时候,2PC的可用性是非常低的),也不会像TCC那样可能出现确认或者回滚不了的情况。
优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。在 .NET中 有现成的解决方案。
缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。
25.Mysql和Redis的数据一致性问题?
首先,我们这边是读的场景,不涉及修改mysql,如果有的话:
- 简单策略–等着:Redis里的数据不立刻更新,等redis里数据自然过期。然后去DB里取,顺带重新set redis。这种用法被称作“Cache Aside”。好处是代码比较简单,坏处是会有一段时间DB和Redis里的数据不一致。这个不一致的时间取决于redis里数据设定的有效期,比如10min。但如果Redis里数据没设置有效期,这招就不灵了。
- 先更新在删除: 更新DB时总是不直接触碰DB,而是通过代码。而代码做的显式更新DB,然后马上del掉redis里的数据。
- 永远取redis:Redis里的数据总是不过期,但是有个背景更新任务(“定时执行的代码” 或者 “被队列驱动的代码)读取db,把最新的数据塞给Redis。这种做法将Redis看作是“存储”。访问者不知道背后的实际数据源,只知道Redis是唯一可以取的数据的地方。
26.RPC服务和HTTP服务区别?
-
对于大型企业来说,内部子系统较多、接口非常多的情况下,RPC框架的好处就显示出来了,首先就是长链接,不必每次通信都要像http一样去3次握手什么的,减少了网络开销;其次就是RPC框架一般都有注册中心,有丰富的监控管理;发布、下线接口、动态扩展等,对调用方来说是无感知、统一化的操作。
-
RPC主要是基于TCP/IP协议,而HTTP服务主要是基于HTTP协议
-
http协议是应用层协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。
-
在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加轻易。
-
RPC更底层一些,适合部门之间交互,传输一些数据或者能力,而用户不是开发者,他只要实际的数据,不了解你们内部是怎么实现的
27.LRU算法设计
思路:
- 用一个链表来保存
- 链表的头节点是要删除的节点,新的a过来之后插入尾部,然后把链表中的a删除
- 为了优化删除速度,使用双向链表
- 为了方便定位节点,使用hashmap<Integer,Node> 保存节点编号和节点
class LRUCache extends LinkedHashMap<Integer, Integer>{
private int capacity;
public LRUCache(int capacity) {
super(capacity, 0.75F, true);
this.capacity = capacity;
}
public int get(int key) {
return super.getOrDefault(key, -1);
}
public void put(int key, int value) {
super.put(key,value);
}
@Override
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return size() > capacity;
}
}
28.TAIR数据库
Tair是淘宝开源的分布式KV缓存系统,内部将功能模块化,抽离出底层存储细节,可以接入不同的存储引擎。redis是一个开源的、高效的key-value存储,提供了strings、hashs、lists、sets、sorted sets等多种高级数据结构。redis作为Tair的存储引擎接入,称为rdb,rdb从redis继承了丰富的操作,包括list、hash、sorted set、set(与mdb相比,list不再那么ugly)。
总结:rdb:是tair集成从redis继承了丰富的操作,包括list、hash、sorted set、set(与mdb相比,list不再那么ugly),Tair将Redis的存储部分抽离出来,作为非持久化的存储引擎rdb(目前代码里面没有rdb代码,即没有开源);
参考:http://t.zoukankan.com/he-px-p-7884573.html
29.了解RPC吗,常见的RPC,怎么设计RPC
- 远程过程调用(Remote Procedure Call,简称RPC)就是:可以跨过一段网络,调用另外一个网络节点上的方法。
原理:
- 第一步,服务方需要把接口文件导出给调用方;调用方这下就跟调用本地方法一样方便
- 代理:服务调用方在本地调用服务提供方给出的接口后,相当于远程调用了该接口的具体实现。这主要用到了动态代理(关于动态代理,我的其他文章应该有讲过,包括使用各种反射工具实现代理的注入等)。动态代理能够为原本的空接口注入一个代理实现。于是,服务调用方对接口的调用便转化为了对代理实现的调用。
- 通信:RPC需要选择一种通信协议,常用的有应用层的HTTP协议和传输层TCP协议
- 执行:服务提供方在接受到调用请求后,需要定位到请求所要调用的具体方法。然后将请求中的参数反序列化后展开对方法的实际调用。并在调用结束后将执行结果返回。
30.分布式系统id生成算法?
有uuid、雪花、数据库自增id、zk生成,redis生成等方法