当前位置: 首页 > 科技观察

一文看懂微内核架构

时间:2023-03-22 00:38:06 科技观察

本文转载自微信公众号《JAVA日知录》,作者JAVA日知录。转载本文请联系JAVA日知录公众号。什么是微内核架构?微内核是一种典型的架构模式,不同于普通的设计模式。架构模式是一种高级模式,用于描述系统级结构组成、相互关系和相关约束。微内核架构在开源框架中被广泛使用。比如常见的ShardingSphere和Dubbo都实现了自己的微内核架构。所以,在介绍什么是微内核架构之前,我们有必要先解释一下为什么这些开源框架都采用微内核架构。为什么要使用微内核架构?微内核架构本质上是为了提高系统的可扩展性。所谓可伸缩性是指系统在经历不可避免的变化时的灵活性,以及??提供这种灵活性的成本之间的平衡。也就是说,在系统中增加新的服务时,不需要改变原有的组件,只需要将新的服务封装在一个新的组件中,就可以完成整体的服务升级。我们认为这样的系统具有比较好的扩展性。就架构设计而言,可扩展性是软件设计中永恒的话题。要实现系统的可扩展性,一种思路是提供一种可插拔的机制来处理发生的变化。当系统中已有的组件不满足要求时,我们可以实现一个新的组件来替代它,整个过程对系统的运行不敏感,也可以随时完成这个新旧组件根据需要更换部件。比如在ShardingSphere提供的分布式主键功能中,分布式主键的实现可能有很多种,此时可扩展性的体现就是我们可以用任何新的分布式主键实现来替代原来的实现,不用对依赖分布式主键的业务代码进行任何更改。微内核架构模式为这种可扩展性的思想提供了架构设计支持,ShardingSphere基于微内核架构实现了高度的可扩展性。在介绍如何实现微内核架构之前,我们先简单介绍一下微内核架构的具体结构和基本原理。什么是微内核架构?从结构上讲,微内核架构由两个部分组成:内核系统和插件。这里的内核系统通常提供系统运行所需的最小功能集,而插件是一个独立的组件,包含各种自定义业务代码,以增强或扩展内核系统的附加业务能力。在ShardingSphere中,上述分布式主键是一个插件,ShardingSphere的运行环境构成了内核系统。那么这里的插件到底指的是什么呢?这就需要我们明确两个概念。一个概念就是经常提到的API,也就是系统对外暴露的接口。另一个概念是SPI(ServiceProviderInterface,服务提供者接口),它是插件本身的扩展点。就两者的关系而言,API是面向业务开发者的,而SPI是面向框架开发者的,两者共同构成了ShardingSphere本身。可插拔的实现机制说起来容易,实现起来并不容易。我们需要考虑两个方面。一方面,我们需要梳理出系统变化并将它们抽象成多个SPI扩展点。另一方面,我们在实现了这些SPI扩展点之后,还需要构建一个能够支持这种可插拔机制的具体实现,从而提供一个SPI运行环境。如何实现微内核架构?其实JDK已经为我们提供了一种实现微内核架构的方式,这就是JDKSPI。该实现方法针对如何设计和实现SPI提出了一些开发和配置规范。ShardingSphere和Dubbo都是使用这个规范,但是都在这个基础上进行了增强和优化。那么要了解微内核架构是如何实现的,我们不妨看看JDKSPI是如何工作的。JDKSPISPI(ServiceProviderInterface)主要是框架开发者使用的一种技术。例如,在使用Java语言访问数据库时,我们会使用java.sql.Driver接口。不同的数据库产品底层协议不同,提供的java.sql.Driver实现也不同。在开发java.sql.Driver接口时,开发者必须不清楚用户最终会使用哪个数据库。这种情况下,在实际运行时可以利用Java的SPI机制为java.sql.Driver接口寻找具体的实现。下面通过一个简单的例子来演示一下JDKSPI的使用:首先我们定义一个生成id键的接口,用于模拟生成idpublicinterfaceIdGenerator{/***generateid*@return*/StringgenerateId();}然后创建两个接口实现类,分别用于模拟uuid和sequenceid的生成=this.atomicId.incrementAndGet();returnString.valueOf(leastId);}}在项目的resources/META-INF/services目录下添加一个名为com.github.jianzh5.spi.IdGenerator的文件,即JDKSPI需要读取的配置文件如下:com.github.jianzh5.spi.impl.UuidGeneratorcom.github.jianzh5.spi.impl.SequenceIdGenerator创建一个main方法,让它加载上面的配置文件,并创建一个所有IdGenerator接口实现的实例,并执行生成id的方法。publicclassGeneratorMain{publicstaticvoidmain(String[]args){ServiceLoaderserviceLoader=ServiceLoader.load(IdGenerator.class);Iteratoriterator=serviceLoader.iterator();while(iterator.hasNext()){IdGeneratorgenerator=iterator.next();Stringid=generator.generateId();System.out.println(generator.getClass().getName()+">>id:"+id);}}}执行结果如下:JDKSPI源码分析通过在上面的例子中,我们可以看到JDKSPI的入口方法是ServiceLoader.load()方法。在该方法中,会先尝试获取当前使用的ClassLoader,然后调用reload()方法。调用关系如下图所示:调用关系在reload()方法中,会先清理providers缓存(LinkedHashMap类型的集合),用于记录ServiceLoader创建的实现对象,其中Key是实现类的全类名,Value是实现类的对象。然后创建一个LazyIterator迭代器,用于读取SPI配置文件和实例化实现类对象。publicvoidreload(){providers.clear();lookupIterator=newLazyIterator(service,loader);}在前面的例子中,main()方法中使用的底层迭代器是通过调用ServiceLoader.LazyIterator实现的。Iterator接口有两个关键方法:hasNext()方法和next()方法。这里LazyIterator中的next()方法最后调用的是它的nextService()方法,hasNext()方法最后调用的是hasNextService()方法。下面看一下hasNextService()方法的具体实现:privatestaticfinalStringPREFIX="META-INF/services/";Enumerationconfigs=null;Iteratorpending=null;StringnextName=null;privatebooleanhasNextService(){if(nextName!=null){returntrue;}if(configs==null){try{//META-INF/services/com.github.jianzh5.spi.IdGeneratorStringfullName=PREFIX+service.getName();if(loader==null)configs=ClassLoader.getSystemResources(fullName);elseconfigs=loader.getResources(fullName);}catch(IOExceptionx){fail(service,"Errorlocatingconfigurationfiles",x);}}//SPI遍历配置内容逐行文件while((pending==null)||!pending.hasNext()){if(!configs.hasMoreElements()){returnfalse;}//解析配置文件pending=parse(service,configs.nextElement()));}//更新nextName字段nextName=pending.next();returntrue;}在hasNextService()方法中完成对SPI配置文件的解析后,再看LazyIterator.nextService()方法,它“负责实例化hasNextService()方法读取的实例”。“当前类”,其中实例化的对象将缓存在提供者集合中。核心实现如下:privateSnextService(){Stringcn=nextName;nextName=null;//加载nextName字段指定的类Classc=Class.forName(cn,false,loader);if(!service.isAssignableFrom(c)){//检测类型fail(service,"Provider"+cn+"notasubtype");}Sp=service.cast(c.newInstance());//创建实现类providers的对象。put(cn,p);//将实现类的名称和对应的实例对象添加到缓存中returnp;}以上是main()方法底层实现中使用的迭代器最后我们看一下在main()方法中使用ServiceLoader.iterator()方法得到的迭代器是如何实现的。这个迭代器是由LazyIterator实现的匿名内部类。核心实现如下:publicIteratoriterator(){returnnewIterator(){//knownProviders用于迭代providers缓存Iterator>knownProviders=providers.entrySet()。迭代器();publicbooleanhasNext(){//先去查询Cache,缓存查询失败,再通过LazyIterator加载if(knownProviders.hasNext())returnknownProviders.next().getValue();returnlookupIterator.next();}//省略remove()方法};}JDKSPI在JDBC中的应用了解了JDKSPI的实现原理后,我们再来看一下在实践中,JDBC使用JDKSPI机制来加载不同数据库供应商的实现类。JDK中只定义了一个java.sql.Driver接口,具体实现由不同的数据库厂商提供。这里以MySQL提供的JDBC实现包为例进行分析。在mysql-connector-java-*.jar包中的META-INF/services目录下,有一个只有一行的java.sql.Driver文件,如下图:com.mysql.cj.jdbc.Driverisusingmysql-connector-java-*.jar包连接MySQL数据库,我们将使用如下语句创建数据库连接:Stringurl="jdbc:xxx://xxx:xxx/xxx";Connectionconn=DriverManager.getConnection(网址,用户名,密码);《DriverManager是JDK提供的一个数据库驱动管理器》,代码片段如下:static{loadInitialDrivers();println("JDBCDriverManagerManagerinitialized");}当调用getConnection()方法时,DriverManager类会被加载、解析并由Java虚拟机触发执行静态代码块;在loadInitialDrivers()方法中,通过JDKSPI扫描并实例化Classpath下的java.sql.Driver接口实现类。核心实现如下:privatestaticvoidloadInitialDrivers(){Stringdrivers=System.getProperty("jdbc.drivers")//利用JDKSPI机制加载所有java.sql.Driver实现类ServiceLoaderloadedDrivers=ServiceLoader.load(Driver.class);IteratordriversIterator=loadedDrivers.iterator();while(driversIterator.hasNext()){driversIterator.next();}String[]driversList=drivers.split(":");for(StringaDriver:driversList){//初始化Driver实现类Class.forName(aDriver,true,ClassLoader.getSystemClassLoader());}}在MySQL提供的com.mysql.cj.jdbc.Driver实现类中,还有一个一段staticstatic代码块,这段代码会创建一个com.mysql.cj.jdbc.Driver对象并注册到DriverManager.registeredDrivers集合(CopyOnWriteArrayList类型)中,如下:static{java.sql.DriverManager.registerDriver(newDriver());}在getConnection()方法中,DriverManager从registeredDrivers集合中获取对应的Driver对象,创建Connection。核心实现如下:privatestaticConnectiongetConnection(Stringurl,java.util.Propertiesinfo,Classcaller)throwsSQLException{//省略try/catch代码块和权限处理逻辑for(DriverInfoaDriver:registeredDrivers){Connectioncon=aDriver.driver。connect(url,info);returncon;}}总结在本文中,我们详细描述了微内核架构的一些基本概念,并通过一个例子首先介绍了JDK提供的SPI机制的基本使用,然后深入分析JDKSPI的核心原理和底层实现,深入剖析其源码。最后,我们以MySQL提供的JDBC实现为例,在Howtouseitinpractice中分析JDKSPI掌握了JDK的SPI机制,就相当于掌握了微内核架构的核心。以上,希望对你有所帮助!