一 简单介绍一下什么是微服务
微服务是一种软件架构风格,它将一个大型的应用程序构建为一组小的服务,每个服务都运行在独立的进程中,服务与服务之间通过轻量级的通信机制(通常是基于HTTP的RESTful API)进行通信。这些服务围绕业务能力构建并且可以自动部署,每个服务都使用不同的编程语言和数据库,这些服务可以独立地扩展和升级。
二 微服务架构和普通架构相比有什么好处和缺点
微服务架构的好处在于其允许将应用程序拆分成多个小型服务,每个服务都可以独立扩展。这意味着可以根据需要仅扩展特定的服务,而不是整个应用程序,从而更有效地利用资源;此外,由于每个服务都独立运行,并且服务之间通过轻量级通信协议进行交互,因此当某个服务出现故障时,不会影响到其他服务的正常运行,提高了系统的整体可用性;同时,微服务架构允许不同的服务使用不同的编程语言、框架和数据存储技术,这有助于团队根据自身的技术专长和项目的实际需求来选择最合适的技术栈。由于每个服务都专注于完成一项单一的业务功能,因此服务的代码更加简洁、清晰,易于理解和维护。此外,服务的独立部署也使得维护过程更加简单和高效。
微服务架构的缺点在于它将应用程序拆分成多个服务,增加了系统的复杂性。开发人员需要处理服务之间的通信和协调问题,以及确保数据在不同服务之间的一致性。并且微服务架构需要管理大量的服务实例,这增加了运维的复杂性和难度。运维人员需要监控每个服务的性能和状态,以及确保服务之间的网络通信正常,而且微服务架构是一个分布式系统,可能面临分布式系统常见的问题,如网络延迟、分区容错等。
三 == 和equals的区别
==常用于相同的基本数据类型之间的比较,也可用于相同类型的对象之间的比较。如果==
比较的是基本数据类型,那么比较的是两个基本数据类型的值是否相等;如果==
是比较的两个对象,那么比较的是两个对象的引用,也就是判断两个对象是否指向了同一块内存区域;
equals方法主要用于两个对象之间,检测一个对象是否等于另一个对象。如果类没有覆盖equals()方法,则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象;如果类覆盖了equals()方法,则按照其自定义的逻辑进行比较。
四 两个字符串直接比较要用什么
使用equals()
方法或equalsIgnoreCase()
方法。equals()方法
比较调用该方法的字符串对象与参数指定的字符串对象的内容是否相同。如果内容相同,则返回true
;否则返回false。
equalsIgnoreCase()
方法比较调用该方法的字符串对象与参数指定的字符串对象的内容是否相同,忽略大小写,如果内容相同,则返回true
;否则返回false
。
五 实现多线程有哪几种方式
继承Thread
类:通过创建一个继承自Thread
类的子类,并重写它的run()
方法,然后创建该子类的实例来创建新的线程。最后,通过调用线程实例的start()
方法来启动线程。这种方式的一个缺点是Java不支持多重继承,所以如果类已经继承了另一个类,那么就不能通过这种方式来实现多线程。
class MyThread extends Thread {
public void run() {
System.out.println("线程运行中");
}
}
public class Test {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
}
实现Runnable
接口:实现Runnable
接口比继承Thread
类更灵活。如果一个类已经继承了另一个类,那么这个类就不能再继承Thread
类了,此时可以通过实现Runnable
接口来定义线程的运行任务。然后,创建一个Thread
类的实例,将实现了Runnable
接口的类的实例作为构造参数传递给Thread
类,最后通过调用Thread
实例的start()
方法来启动线程。
class MyRunnable implements Runnable {
public void run() {
System.out.println("线程运行中");
}
}
public class Test {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
}
}
实现Callable
接口与Future
、FutureTask
:Callable
接口类似于Runnable
接口,但它可以返回一个结果,并且可以声明抛出一个异常。要启动一个Callable
任务,你需要将它提交给ExecutorService
执行,它将返回一个代表异步计算结果的Future
对象。FutureTask
是一个实现了Runnable
和Future
的类,可以将Callable
转换成Runnable
,并且可以方便地获取异步计算的结果。
import java.util.concurrent.*;
class MyCallable implements Callable<Integer> {
public Integer call() throws Exception {
return 123;
}
}
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
executor.execute(futureTask);
System.out.println(futureTask.get()); // 获取执行结果
executor.shutdown();
}
}
使用线程池(ExecutorService
):虽然这不是一种直接创建线程的方式,但它是实际开发中推荐的方式,因为它可以有效地管理线程的生命周期,减少资源消耗,并提高性能。Java提供了多种线程池实现,如FixedThreadPool
、CachedThreadPool
、ScheduledThreadPool
等。
ExecutorService executor = Executors.newFixedThreadPool(3); // 创建一个固定大小的线程池
for (int i = 0; i < 10; i++) {
Runnable worker = new WorkerThread("" + i);
executor.execute(worker);
}
executor.shutdown();
六 线程池的核心参数有哪些
corePoolSize : 核心线程大小。线程池一直运行,核心线程就不会停止。
maximumPoolSize :线程池最大线程数量。非核心线程数量=maximumPoolSize-corePoolSize
keepAliveTime :非核心线程的心跳时间。如果非核心线程在keepAliveTime内没有运行任务,非核心线程会消亡。
workQueue :阻塞队列。ArrayBlockingQueue,LinkedBlockingQueue等,用来存放线程
defaultHandler :饱和策略。ThreadPoolExecutor类中一共有4种饱和策略。
AbortPolicy : 线程任务丢弃报错。默认饱和策略。
DiscardPolicy : 线程任务直接丢弃不报错。
DiscardOldestPolicy : 将workQueue队首任务丢弃,将最新线程任务重新加入队列执行。
CallerRunsPolicy :线程池之外的线程直接调用run方法执行。
ThreadFactory :线程工厂。新建线程工厂。
七 核心线程数是什么
核心线程数(Core Pool Size)是线程池的一个重要参数,它指的是线程池中一直存在的线程数量,也就是线程池中可以同时运行的最小线程数量。即使线程池中的任务数少于核心线程数,这些核心线程也会一直保持存活状态,不会被销毁,除非线程池被显式关闭。
核心线程数的作用在于保证线程池中有一定数量的线程随时可用,通过保留一定数量的核心线程,可以确保线程池在任务到达时能够迅速响应,减少创建新线程的开销;此外,合理地设置核心线程数可以平衡线程的创建开销和系统的资源利用率,避免因为线程过多导致的资源竞争和性能下降。
可以通过ThreadPoolExecutor
类的构造方法或者setCorePoolSize
方法来设置线程池的核心线程数。
八 怎么保证数据传输的可靠性
TCP主要提供了检验和、序列号/确认应答、超时重传、滑动窗口、拥塞控制和 流量控制等方法实现了可靠性传输。
检验和:通过检验和的方式,接收端可以检测出来数据是否有差错和异常,假如有差错就会直接丢弃TCP段,重新发送。
序列号/确认应答:序列号的作用不仅仅是应答的作用,有了序列号能够将接收到的数据根据序列号排序,并且去掉重复序列号的数据。TCP传输的过程中,每次接收方收到数据后,都会对传输方进行确认应答。也就是发送ACK报文,这个ACK报文当中带有对应的确认序列号,告诉发送方,接收到了哪些数据,下一次的数据从哪里发。
滑动窗口:滑动窗口既提高了报文传输的效率,也避免了发送方发送过多的数据而导致接收方无法正常处理的异常。
超时重传:超时重传是指发送出去的数据包到接收到确认包之间的时间,如果超过了这个时间会被认为是丢包了,需要重传。最大超时时间是动态计算的。
拥塞控制:在数据传输过程中,可能由于网络状态的问题,造成网络拥堵,此时引入拥塞控制机制,在保证TCP可靠性的同时,提高性能。
流量控制:如果主机A 一直向主机B发送数据,不考虑主机B的接受能力,则可能导致主机B的接受缓冲区满了而无法再接受数据,从而会导致大量的数据丢包,引发重传机制。而在重传的过程中,若主机B的接收缓冲区情况仍未好转,则会将大量的时间浪费在重传数据上,降低传送数据的效率。所以引入流量控制机制,主机B通过告诉主机A自己接收缓冲区的大小,来使主机A控制发送的数据量。流量控制与TCP协议报头中的窗口大小有关。
九 数据库的优化方式有哪些
首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写。
分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。
如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表。
十 哪些情况可能会导致索引失效
查询条件中未使用索引列:如果查询条件中不包含索引列,或者索引列在查询条件中被函数或表达式处理,导致无法利用索引进行优化。
数据类型不匹配:查询条件中的数据类型与索引列的数据类型不一致时,MySQL会进行隐式数据类型转换,这可能导致索引失效。
索引列上使用了函数或表达式:在索引列上使用函数或表达式会导致索引失效,因为MySQL无法直接利用索引来快速定位数据。
索引列上有大量重复值:当索引列上存在大量重复值时,索引的区分度降低,可能导致索引失效,尤其是在使用范围查询或排序时。
查询条件不符合索引规则:使用了不符合索引规则的操作符,如不等于(!= 或 <>)操作符,或者在复合索引中查询条件与索引列的顺序不一致,都可能导致索引失效。
索引过多或过少:索引的数量过多会增加维护索引的成本,降低更新表的性能;索引过少则可能无法覆盖所有的查询需求,导致查询性能低下。因此,需要根据实际情况合理设计索引。
索引列上有大量NULL值:如果索引列上存在大量NULL值,并且查询中经常涉及到这些NULL值的处理,可能会导致索引失效。
使用前缀索引不当:在某些情况下,为了提高索引的效率和节省存储空间,可以使用前缀索引。但是,如果前缀索引的长度设置不当,可能会导致查询结果不准确或索引失效。
数据更新频繁:索引是为了提高查询性能而创建的,但在数据更新频繁的情况下,索引的维护成本会增加,甚至可能导致索引失效。因此,在设计表结构时需要权衡查询和更新的频率。
查询条件中使用了OR操作符:当使用OR操作符连接多个查询条件时,如果其中某个条件列没有索引,那么整个查询可能无法有效利用索引。