阿里巴巴 CTO线 java一面,稳了 (50min)
阿里巴巴 CTO线 java一面,稳了 (50min)
前文
《捞捞面经》系列正式开始连载啦,据说看了这个系列的朋友都拿到了大厂offer~
- 你是否看面经只有问题没有解析?是否缺乏实时一线大厂面经攻略?捞捞面经帮你解决~
- 欢迎星标+订阅,持续更新中。。。致力打造校招核心面试攻略~
- NowcoderTop仓库直达: 汇总收录牛客各互联网大厂实时高频面经攻略(含详解)🔥(欢迎投稿~):https://github.com/xlcoding/NowcoderTop
捞捞面经
注:养成先看真题,自己模拟回答,再看解析参考(别忘随手一键三连哦~)
- 自我介绍
- Java的基本数据类型,double精度丢失,BigDecimal的原理
计算时,先成于10的N次方,先把小数都变成整数,整数存储是没有精度问题的,做完了相应的运算之后,再除以10的N次方。
- 手写一个String类,能否被类加载器加载;双亲委派机制,如何打破;String类可以被继承吗
- hashmap的常规八股
- 浏览器发送url后,域名怎么转换为ip地址;域名对应的不止一个ip(负载均衡),了解哪些负载均衡的算法
- http和https的区别,对称加密算法和非对称加密算法的区别
- mysql的qps从1到10000,性能的变化情况
- 大量请求执行update语句,有什么方法优化;引入redis的数据一致性问题
- mysql索引的底层实现,为什么用b+树,模糊查询命中索引的条件
- mysql的事务隔离,MVCC如何实现
- Spring bean的生命周期和作用域,BeanFactory和FactoryBean的区别
- Spring aop的底层实现,动态代理的实现方式,在哪些实际业务中应用过
- 线程池参数,执行原理;工作队列有哪些,有什么区别
- 微服务的设计原则
- Dubbo有过了解吗,rpc和http调用的区别,rpc框架需要哪些模块,负载均衡应该设计在客户端还是服务端
- 了解微服务的网关路由吗,Spring Cloud Gateway可以用作哪些功能
- 单机限流的算法,需要集群限流的话应该怎么实现,计数法的弊端是什么
- 领域驱动有了解吗
- Java面向对象的设计原则
手写一个String类,能否被类加载器加载?
简易版:手写 String,由于类加载器的双亲委派机制会向上找到定父加载器Bootstrap ClassLoade加载,且能找到JDK原版java.lang.String类,故只会加载JDk自带版本,不会加载手写版本。
详解版:
String 类是Java核心库的一部分,由 Bootstrap ClassLoader 进行加载,这是 Java 虚拟机最顶层的类加载器。
如果你尝试手写一个和 java.lang.String 同名的类,由于类加载器的双亲委派机制,当你尝试加载这个类时,类加载器会首先尝试让其父加载器去加载这个类,最终会由 Bootstrap ClassLoader去加载。由于Bootstrap ClassLoader能找到原版的java.lang.String类,所以它会加载原版的String类,而不会加载你手写的版本。
双亲委派机制是为了保证Java核心库的类型安全,所有的Java应用都至少会引用 java.lang.Object 类,也就是说在运行期,java.lang.Object类 会被加载到Java虚拟机中;如果这个加载过程中没有双亲委派机制,自定义的java.lang.String类就会替代Java核心库中的类,这样就会出现很多严重的问题。
所以,简单来说,你可以手写一个String类,但是它不能被类加载器加载为java.lang.String。如果你想要创建自己的字符串类,你应该使用一个不同的类名,或者将它放在一个不同的包中。
双亲委派机制,如何打破?
- 重写loadClass()方法:在自定义的类加载器中,重写loadClass()方法,改变其加载类的方式。例如,可以先尝试自己加载类,如果无法加载,再委派给父类加载器。这种方式直接改变了双亲委派机制的流程。具体实现如下:
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
c = findClass(name);
} catch (Exception e) {
c = super.loadClass(name);
}
}
return c;
}
- 重写findClass()方法:在自定义的类加载器中,重写findClass()方法,自定义加载类的方式。当父类加载器无法加载类时,会调用自己的findClass()方法尝试加载类。具体实现如下:
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = loadClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
}
- 使用线程上下文类加载器:线程上下文类加载器是从JDK 1.2开始引入的。它是每个线程都有的一个类加载器,可以通过Thread类的setContextClassLoader()方法来设置。默认情况下,线程的上下文类加载器是其父线程的上下文类加载器。在双亲委派机制不能满足需求时,可以通过改变线程的上下文类加载器来改变类的加载方式。具体实现如下:
Thread.currentThread().setContextClassLoader(myClassLoader);
- 使用ServiceLoader(SPI):ServiceLoader 是 Java 提供的一种服务提供者加载机制。它可以加载实现了特定接口的服务提供者(类比JDBC实现)。
ServiceLoader
使用的是线程上下文类加载器,因此,通过使用ServiceLoader
,也可以打破双亲委派机制。具体工作原理是在类路径下的META-INF/services目录中寻找指定接口的实现类配置文件,文件名就是接口的全限定名,文件内容是接口实现类的全限定名,每行一个。然后ServiceLoader
会使用线程上下文类加载器去加载这些实现类具体实现如下:
ServiceLoader<MyService> loader = ServiceLoader.load(MyService.class);
for (MyService service : loader) {
service.doSomething();
}
在Java开发中,打破双亲委派机制主要出现在以下几个场景:
- Java Web服务器:为了实现 Web 应用的热部署,Web 服务器通常会为每个Web应用创建一个自己的类加载器。当Web应用更新时,Web服务器会创建一个新的类加载器来加载新的类。这个过程就打破了双亲委派机制。例如,Tomcat的WebAppClassLoader就是这样做的。
- Java开发工具:一些Java开发工具,如
Spring Boot
的DevTools,为了实现代码的热部署,也会打破双亲委派机制。DevTools使用了两个类加载器,一个类加载器加载那些不经常改变的类,另一个类加载器加载那些经常改变的类。当代码改变时,DevTools只需要重新加载那些改变的类,而不需要重新加载所有的类。 - Java模块化框架:OSGi 是一个Java的模块化框架,它允许在运行时动态地安装、更新和卸载模块。为了实现这个功能,OSGi使用了自定义的类加载器,并打破了双亲委派机制。在OSGi中,每个模块都有自己的类加载器,这个类加载器会先尝试加载模块内部的类,如果找不到类再委派给父类加载器。
- JDBC驱动加载:在加载``JDBC` 驱动时,也会打破双亲委派机制。早期的JDBC版本中,我们通常会使用Class.forName()方法来加载JDBC驱动,这个方法会使用调用者的类加载器来加载指定的类。在JDBC 4.0及以后的版本中,引入了自动加载的驱动,ServiceLoader会自动加载META-INF/services/java.sql.Driver文件中指定的JDBC驱动类,这个过程也是打破了双亲委派机制。
- Java Agent:Java Agent是运行在JVM启动时加载的一种特殊程序,它可以修改已加载的类的字节码。为了实现这个功能,Java Agent通常会使用自定义的类加载器,并打破双亲委派机制。
以上就是在Java开发中可能需要打破双亲委派机制的主要场景。需要注意的是,打破双亲委派机制可能会带来一些问题,比如类的版本冲突,因此在实际使用时需要谨慎处理。
String类可以被继承吗?
不可以。在Java中,String类被声明为final,这意味着String类不能被继承。final关键字可以防止一个类被继承,防止一个方法被重写,或者防止一个变量的值被改变。
这样设计的原因主要有两点:
- 不可变性:String类是不可变的,也就是说,一旦一个String对象被创建,它的内容就不能被改变。这个特性使得String对象可以被安全地用在多线程环境中,而不需要额外的同步。如果允许继承String类,那么就可能创建出可变的String子类,这将破坏String的不可变性。
- 性能优化:由于String的不可变性,编译器和运行时环境可以对String对象进行一些优化,例如字符串常量池、字符串字面量共享等。如果允许继承String类,那么这些优化可能就无法进行。
因此,为了保持String的不可变性和性能优化,Java设计者选择将String类声明为final,使其不能被继承。
浏览器发送 URL 后,域名怎么转换为 IP 地址?
浏览器发送URL后,域名转换为IP地址的过程被称为DNS解析。DNS(Domain Name System)是一个将域名和IP地址相互映射的分布式数据库,能够使用户方便地访问互联网,而不需要记住能够被机器直接读取的IP数串。
以下是DNS解析的详细过程:
- 浏览器缓存:浏览器会首先检查自己的缓存中是否有这个域名对应的IP地址,如果有,直接使用;如果没有,进入下一步。
- 系统缓存:如果浏览器缓存中没有,浏览器会查找操作系统中是否缓存了这个域名的IP地址,如果有,直接使用;如果没有,进入下一步。
- 路由器缓存:如果系统缓存中没有,浏览器会查找路由器中是否缓存了这个域名的IP地址,如果有,直接使用;如果没有,进入下一步。
- ISP DNS 缓存:如果路由器缓存中没有,浏览器会向ISP的DNS服务器发送一个请求,查找是否有这个域名的IP地址,如果有,直接使用;如果没有,进入下一步。
- 递归搜索:ISP的DNS服务器会进行一次递归搜索,首先向根DNS服务器发送请求,根DNS服务器会返回负责.com顶级域名的DNS服务器的地址,然后ISP的DNS服务器再向.com的DNS服务器发送请求,.com的DNS服务器会返回负责example.com的DNS服务器的地址,最后ISP的DNS服务器向example.com的DNS服务器发送请求,获取www.example.com的IP地址。
- 返回结果:ISP的DNS服务器将获取到的IP地址返回给浏览器,浏览器再通过这个IP地址来访问网站。
域名可以对应多个IP吗?怎样实现的?
你说得对,一个域名确实可以对应多个IP地址,这种情况通常出现在以下几种场景:
- 负载均衡:为了分散服务器的压力,提高服务的可用性和稳定性,通常会将一个域名解析到多个服务器的IP地址上,当用户访问这个域名时,DNS服务器会根据一定的策略(如轮询、最少连接等)选择一个IP地址返回给用户。
- 内容分发网络(CDN):CDN是一种将网站内容分发到多个节点,使用户可以就近获取内容的技术。当用户访问一个使用了CDN的网站时,DNS服务器会根据用户的地理位置,返回一个离用户最近的节点的IP地址。
- 灾备和故障切换:为了提高服务的可用性,通常会设置备用服务器。当主服务器出现故障时,可以将域名解析到备用服务器的IP地址上,实现故障切换。
了解哪些负载均衡的算法?
http 和 Https的区别?
HTTP和HTTPS主要的区别在于安全性和数据传输方式上,以下是具体的区别:
- 协议方案:HTTP 是超文本传输协议,信息是明文传输,HTTPS 则是具有安全性的 SSL 加密传输协议。
- 端口:HTTP 的 URL 由 "http://" 开头,使用端口号80。HTTPS 的 URL 由 "https://" 开头,使用端口号443。
- 安全性:HTTP 是不安全的,因为数据在传输过程中如果被截获,信息可以被直接查看。而 HTTPS 是安全的,因为数据传输过程是加密的,即使被截获,也无法查看具体内容。
- 证书:HTTP 不需要证书。而 HTTPS 需要到 CA 申请证书,一般免费证书较少,因而需要一定费用。
- 网络连接方式:HTTP 是无状态的,HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 协议安全。
- 效率和性能:HTTPS 在数据传输的安全性上要优于 HTTP,但是由于 HTTPS 需要进行加密和解密操作,所以在服务器的性能上要求更高,相比 HTTP 来说,HTTPS 更耗费服务器资源。
对称加密算法和非对称加密算法的区别?
MySQL 的 QPS 从 1到10000,性能的变化情况?
不明白这是个啥问题,,,和QPS有直接关系吗,MySQL 这点QPS都支撑不了,还玩个屁
QPS从1到10000,性能的变化情况会受到很多因素的影响,包括硬件配置、网络环境、查询复杂度、索引优化等。
- 硬件配置:硬件配置是影响MySQL性能的一个重要因素。如果硬件配置较低,即使QPS较低,MySQL的性能也可能受到影响。反之,如果硬件配置较高,MySQL可能能够在QPS较高时仍然保持良好的性能。
- 网络环境:网络环境也会影响MySQL的性能。如果网络环境较差,即使QPS较低,MySQL的性能也可能受到影响。反之,如果网络环境较好,MySQL可能能够在QPS较高时仍然保持良好的性能。
- 查询复杂度:查询复杂度也会影响MySQL的性能。如果查询复杂度较高,即使QPS较低,MySQL的性能也可能受到影响。反之,如果查询复杂度较低,MySQL可能能够在QPS较高时仍然保持良好的性能。
- 索引优化:索引优化也会影响MySQL的性能。如果索引优化做得好,即使QPS较高,MySQL的性能也可能保持良好。反之,如果索引优化做得不好,即使QPS较低,MySQL的性能也可能受到影响。
总的来说,随着QPS的提高,如果没有进行适当的优化和调整,MySQL的性能可能会下降。但是,通过优化硬件配置、改善网络环境、降低查询复杂度和优化索引,可以提高MySQL在高QPS下的性能。
大量请求执行 update 语句,有什么方法优化?
对于MySQL大量执行UPDATE语句,有几种优化方法:
- 合并UPDATE语句:如果有大量的UPDATE语句需要对同一表进行操作,可以尝试将这些UPDATE语句合并为一个语句,这样可以减少数据库的I/O操作。
- 使用索引:在UPDATE语句的WHERE子句中使用索引,可以大大提高查询速度。如果没有合适的索引,MySQL需要进行全表扫描,这将消耗大量的时间。
- 优化表结构:优化表结构也可以提高UPDATE语句的执行速度。例如,减少数据冗余,使用合适的数据类型,避免NULL值等。
- 分批处理:如果一次UPDATE操作的数据量过大,可以考虑分批处理。每次处理一部分数据,这样可以避免一次性占用过多的系统资源。
- 使用事务:如果有多个UPDATE语句需要连续执行,可以考虑使用事务。这样可以减少数据库的I/O操作,提高执行效率。
- 调整MySQL配置:根据实际情况调整MySQL的配置,例如调整缓存大小,调整并发数等,也可以提高UPDATE语句的执行效率。
以上就是一些优化MySQL大量执行UPDATE语句的方法,具体使用哪种方法需要根据实际情况来判断。
mysql索引的底层实现,为什么用b+树,模糊查询命中索引的条件
MySQL索引的底层实现主要是通过B+树数据结构。B+树是一种自平衡的多路搜索树,适用于系统有大量信息需要磁盘I/O操作。
选择B+树作为索引结构的原因主要有以下几点:
- 高度平衡:B+树是一种多路平衡查找树,可以保证数据的查找、插入和删除都在O(logN)的时间复杂度内完成,保证了查询性能的稳定性。
- 磁盘I/O性能优化:B+树的磁盘读写性能更优,因为B+树的分支因子(每个节点的子节点数)通常很大,所以B+树的高度通常很低,减少了磁盘I/O次数。
- 范围查询优化:B+树的所有叶子节点形成了一个有序链表,对整个表的全字段值进行排序,所以非常适合文件索引和数据库索引,可以很方便地进行范围查询。
- 稳定的查询效率:由于B+树总是平衡的,所以在高并发的情况下,具有更稳定的查询效率。
至于模糊查询命中索引的条件,一般来说,只有当模糊查询的通配符(如%)不在字符串的开头时,才能使用到索引。例如,对于LIKE 'abc%'这样的查询可以使用索引,而对于LIKE '%abc'这样的查询则无法使用索引。因为在后者的情况下,数据库需要扫描整个表来找到匹配的行,无法利用索引的优势。
mysql的事务隔离,MVCC如何实现
MySQL的事务隔离主要通过四种隔离级别来实现:读未提交、读已提交、可重复读和串行化。这四种隔离级别从低到高,隔离性越来越强,但并发性能越来越弱。
MVCC,即多版本并发控制,是MySQL为了实现高并发事务读写而采用的一种技术。在InnoDB存储引擎中,通过保存数据在某个时间点的快照,来实现非锁定读,提高并发性能。
以下是MVCC的基本实现机制:
- 隐藏列:InnoDB存储引擎会为每一行数据添加两个隐藏的列,一个是创建时间,一个是过期时间(或删除时间)。这里的时间是系统版本号,每开始一个新的事务,系统版本号就会自动递增。
- 读操作:当进行读操作时,InnoDB会判断读取的行的创建时间是否在当前事务开始之前,过期时间是否在当前事务开始之后或者尚未过期。如果满足条件,就会返回该行,否则跳过该行。这样就可以实现读取数据的历史版本,也就是所谓的快照读。
- 写操作:当进行写操作(更新或删除)时,InnoDB不会直接覆盖原来的行,而是会创建一个新的行,同时将原来的行的过期时间设置为当前事务的版本号。这样,旧的事务还可以继续访问旧的行,新的事务则会访问新的行。
通过这种方式,MVCC实现了在不加锁的情况下进行并发控制,大大提高了数据库的并发性能。同时,由于每个事务都在操作自己的版本,也保证了数据的一致性。
Spring bean的生命周期和作用域,BeanFactory和FactoryBean的区别
Spring Bean的生命周期:
- 实例化:Spring容器通过Bean的构造方法或工厂方法创建Bean实例。
- 设置对象属性(依赖注入):Spring容器通过BeanDefinition中的属性设置Bean的属性值。
- 调用Bean的初始化方法:如果Bean实现了InitializingBean接口,Spring容器会调用其afterPropertiesSet()方法;如果配置了init-method,Spring容器会调用指定的初始化方法。
- Bean可以被应用程序使用了。
- 销毁:如果Bean实现了DisposableBean接口,Spring容器会调用其destroy()方法;如果配置了destroy-method,Spring容器会调用指定的销毁方法。
Spring Bean的作用域:
- singleton:单例模式,Spring容器中只会存在一个共享的Bean实例,所有对该Bean的请求,都会返回同一个Bean实例。
- prototype:原型模式,每次请求都会创建一个新的Bean实例。
- request:每次HTTP请求都会产生一个新的Bean,该Bean仅在当前HTTP request内有效。
- session:每次HTTP Session都会产生一个新的Bean,该Bean仅在当前HTTP Session内有效。
- application:对于一个单一的ServletContext,每个Bean定义对应一个单一的实例。Bean的生命周期与ServletContext的生命周期相同。
- websocket:每个WebSocket都会产生一个新的Bean,该Bean仅在当前WebSocket内有效。
BeanFactory和FactoryBean的区别:
- BeanFactory是Spring框架的基础设施,面向Spring本身,提供了配置框架和基本功能,能够管理和装配Bean。
- FactoryBean通常是用来创建复杂的Bean,面向Spring用户,用户可以通过实现FactoryBean接口定制实例化逻辑。FactoryBean产生的Bean是通过getObject()方法获取的,而不是直接在IOC容器中定义的Bean。
Spring aop的底层实现,动态代理的实现方式,在哪些实际业务中应用过
Spring AOP的底层实现:
Spring AOP的底层实现主要是通过动态代理来实现的。如果目标对象实现了接口,Spring AOP会使用JDK的动态代理来生成AOP代理类;如果目标对象没有实现接口,Spring AOP会使用CGLIB来生成AOP代理类。无论是哪种方式,生成的代理对象在调用目标方法时,都能够按照切面的配置执行相应的前置、后置或异常通知。
动态代理的实现方式:
- JDK动态代理:JDK的动态代理通过反射类Proxy和InvocationHandler接口实现,一个基于接口的代理类在运行时被创建。InvocationHandler接口提供了一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。
- CGLIB动态代理:CGLIB是一个强大的、高性能的代码生成库,它可以在运行时扩展Java类与实现Java接口。CGLIB采用了一种非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用。
动态代理在实际业务中的应用:
- 日志记录:可以使用动态代理记录方法的调用信息,如方法名、参数、调用时间等,方便进行日志管理。
- 性能监控:可以在方法调用前后获取时间,通过计算得到方法的执行时间,从而监控方法的性能。
- 事务管理:在执行方法前后进行事务的开启和关闭,实现事务的统一管理。
- 权限验证:在方法执行前进行权限验证,如用户是否登录、是否有权限访问某个资源等。
- 缓存处理:可以在方法调用前后进行缓存处理,提高系统性能。
线程池参数,执行原理;工作队列有哪些,有什么区别?
线程池参数:
- corePoolSize:线程池的基本大小,即在没有任务需要执行的时候线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。
- maximumPoolSize:线程池最大线程数,这个参数也就是线程池能够容纳同时执行的最大任务数。
- keepAliveTime:线程空闲时的存活时间,即超过corePoolSize的空闲线程在多长时间内会被销毁。
- unit:keepAliveTime的时间单位。
- workQueue:工作队列,用于存放待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响。
- threadFactory:线程工厂,主要用来创建线程。
- handler:表示当拒绝处理任务时的策略。
线程池执行原理:
- 当调用execute方法添加一个任务时,线程池会做如下判断:
- 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
- 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
- 如果这时候队列满了,而且正在运行的线程数量小于maximumPoolSize,那么还是要创建线程运行这个任务;
- 如果队列满了,而且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会抛出异常,拒绝这个任务。
- 当一个线程完成任务时,它会从队列中取下一个任务来执行。
- 当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。
工作队列:
- ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO原则对元素进行排序。
- LinkedBlockingQueue:基于链表结构的阻塞队列,按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue。
- SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue。
- PriorityBlockingQueue:具有优先级的无界阻塞队列。
- DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
这些队列在不同的应用场景中有各自的优势,需要根据实际需求进行选择。
微服务的设计原则?
微服务的设计原则主要包括以下几点:
- 单一职责原则:每个微服务应该只负责一个业务功能,这样可以使得微服务的职责清晰,便于开发和维护。
- 服务自治原则:每个微服务应该是独立的,可以独立部署和扩展,不依赖于其他微服务。每个微服务都有自己的数据库和数据模型,不共享数据库。
- 轻量级通信原则:微服务之间的通信应该是轻量级的,通常使用HTTP/REST或者异步消息进行通信。
- 服务粒度原则:微服务的粒度应该适中,既不能太大,也不能太小。太大的微服务可能会导致职责不清晰,太小的微服务可能会导致服务间通信过于复杂。
- 业务驱动设计:微服务的划分应该根据业务需求来进行,每个微服务对应一个业务能力。
- 强一致性与最终一致性:在微服务架构中,由于服务之间的解耦,往往不能做到强一致性,而是采用最终一致性的方式。
- 容错性原则:微服务应该具备容错能力,当某个服务出现故障时,不应该影响到其他的服务。
- 持续集成与持续交付:微服务应该支持持续集成和持续交付,能够快速地进行迭代和部署。
具体的设计还需要根据实际的业务需求和场景来进行。
Dubbo有过了解吗,rpc和http调用的区别,rpc框架需要哪些模块,负载均衡应该设计在客户端还是服务端?
RPC和HTTP调用的区别:
- 协议层面:RPC是一种通信协议,可以使用多种传输协议,如HTTP、TCP等;而HTTP是一种具体的传输协议,是应用层的一种协议。
- 数据格式:RPC通常使用二进制格式传输数据,效率更高;而HTTP通常使用文本格式(如JSON、XML)传输数据,可读性更好。
- 调用方式:RPC通常是同步阻塞的调用方式,客户端需要等待服务器端的响应;而HTTP可以支持异步非阻塞的调用方式。
- 适用场景:RPC更适合于内部服务间的通信,而HTTP更适合于开放API和Web服务。
RPC框架需要哪些模块:
- 服务提供者:提供服务的实体。
- 服务消费者:消费服务的实体。
- 注册中心:服务提供者在启动时,会把自己提供的服务注册到注册中心,服务消费者在启动时,会从注册中心获取可用的服务列表。
- 通信模块:负责服务提供者和服务消费者之间的通信。
- 序列化模块:负责请求和响应的序列化和反序列化。
- 负载均衡模块:在有多个服务提供者的情况下,负责选择一个合适的服务提供者。
负载均衡应该设计在客户端还是服务端:
在RPC框架中,负载均衡通常是设计在客户端的。因为客户端知道所有可用的服务提供者,可以根据各种策略(如轮询、随机、最少活跃调用等)选择一个合适的服务提供者。如果设计在服务端,那么每个服务端需要知道其他所有的服务端信息,这会增加服务端的复杂性。同时,如果服务端负责负载均衡,那么客户端的请求可能需要经过多次转发才能到达实际的服务提供者,这会增加请求的延迟。
了解微服务的网关路由吗,Spring Cloud Gateway可以用作哪些功能?
在微服务架构中,通常会有很多个服务提供者,这些服务可能部署在不同的服务器上,有不同的网络地址。为了方便客户端访问,通常会引入一个网关路由。客户端只需要和网关路由进行通信,由网关路由根据请求的内容将请求转发到对应的服务提供者。这样可以隐藏服务提供者的网络地址,提高系统的安全性,同时也可以减轻客户端的负担。
Spring Cloud Gateway可以用作以下功能:
- 路由转发:根据请求的内容,将请求转发到对应的服务。
- 过滤器:可以对请求进行各种处理,如添加请求头、修改请求体、记录请求日志等。
- 限流:可以对请求进行限流,防止服务被过多的请求打垮。
- 熔断:当某个服务出现故障时,可以自动将请求转发到备用的服务。
- 安全认证:可以对请求进行身份验证,防止未授权的访问。
- 跨域处理:可以处理跨域请求,方便前后端分离的开发。
单机限流的算法,需要集群限流的话应该怎么实现,计数法的弊端是什么?
单机限流的算法:
- 固定窗口计数法:在一个固定的时间窗口内,对请求进行计数,当计数超过阈值时,就进行限流。这种方法简单易实现,但可能会在窗口切换时出现流量突增的情况。
- 滑动窗口计数法:将一个固定的时间窗口划分为多个小的时间窗口,对每个小窗口进行计数,然后取最近的N个小窗口的计数之和,当这个和超过阈值时,就进行限流。这种方法可以平滑流量突增的情况,但实现起来比较复杂。
- 漏桶算法:将请求比作水滴,将限流比作漏桶,请求进入漏桶,然后以固定的速度流出。当漏桶满了时,就进行限流。这种方法可以保证处理请求的速度恒定,但不能应对突发的大流量。
- 令牌桶算法:将请求比作需要消耗令牌的操作,将限流比作令牌桶,以固定的速度向令牌桶中添加令牌,请求在处理前需要从令牌桶中获取令牌。当令牌桶中没有令牌时,就进行限流。这种方法可以应对突发的大流量,是一种常用的限流算法。
集群限流的实现:
集群限流通常需要借助于分布式协调服务,如ZooKeeper、Redis等。将各个节点的请求计数或令牌桶状态保存在分布式协调服务中,然后各个节点根据这些信息进行限流。这种方法可以实现全局的限流,但可能会增加系统的复杂性和通信开销。
计数法的弊端:
- 流量突增:在固定窗口计数法中,可能会在窗口切换时出现流量突增的情况。
- 精度问题:如果窗口划分得过大,可能会导致限流的精度不够;如果窗口划分得过小,可能会增加计数的开销。
- 同步问题:在集群环境中,需要对计数进行同步,可能会增加系统的复杂性和通信开销。
领域驱动有了解吗
DDD了解不多
领域驱动设计(Domain-Driven Design,简称DDD)是一种软件开发的方法论,主要用于处理复杂的业务需求。它强调的是以业务领域的知识来驱动软件的设计和实现,通过创建丰富的领域模型来反映业务的本质。
领域驱动设计主要包括以下几个关键概念:
- 领域模型:领域模型是对业务领域的抽象和表达,它包括了业务领域的实体、值对象、聚合、领域事件等元素。
- 限界上下文:限界上下文是一种划分领域模型的方式,每个限界上下文内部有一致的领域模型和语言,不同的限界上下文之间通过上下文映射来进行交互。
- 领域事件:领域事件是业务领域中发生的重要事件,它可以触发领域中的业务流程。
- 实体和值对象:实体是具有唯一标识的对象,它的属性可以变化,但标识不变;值对象是没有唯一标识的对象,它是通过属性的值来区分的。
- 聚合:聚合是一组相关的实体和值对象的集合,聚合根是聚合的入口,所有对聚合的操作都通过聚合根来进行。
- 领域服务:领域服务是一种无状态的服务,它封装了领域模型中的一些操作,这些操作不属于任何实体或值对象。
领域驱动设计的目标是创建出能够反映业务领域的丰富模型,这样可以提高软件的质量和维护性,同时也可以提高开发团队对业务的理解。
Java面向对象的设计原则
Java面向对象的设计原则主要包括以下几点:
- 单一职责原则(Single Responsibility Principle, SRP):一个类应该只有一个引起它变化的原因。也就是说,一个类应该只负责一项职责。
- 开放封闭原则(Open Closed Principle, OCP):软件实体(类、模块、函数等等)应该可以扩展,但是不可修改。也就是说,对于已经存在的代码,我们应该尽量通过扩展的方式进行变化,而不是修改已有代码。
- 里氏替换原则(Liskov Substitution Principle, LSP):子类型必须能够替换掉它们的父类型。也就是说,在软件中,如果使用某个基类的地方都可以使用其子类,那么子类可以替换父类。
- 接口隔离原则(Interface Segregation Principle, ISP):客户端不应该依赖它不需要的接口。也就是说,一个类对另一个类的依赖应该建立在最小的接口上。
- 依赖倒置原则(Dependency Inversion Principle, DIP):高层模块不应该依赖低层模块,二者都应该依赖其抽象。也就是说,要针对接口编程,不要针对实现编程。
这些原则都是为了提高代码的可读性、可维护性和可复用性,降低代码的耦合度。在实际的软件开发中,应该根据具体的需求和情况灵活运用这些原则,而不是死板地遵守。