【微服务架构】介绍KivaKit框架

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 【微服务架构】介绍KivaKit框架

关键点

  • KivaKit是一个模块化Java框架,用于开发需要Java 11+虚拟机但与Java 8源代码兼容的微服务
  • KivaKit提供了实现应用程序的基本功能,包括命令行解析和应用程序配置
  • KivaKit组件是轻量级组件,使用广播/侦听器消息传递系统传递状态信息
  • KivaKit迷你框架,包括转换、验证、资源和日志迷你框架,通过消息传递使用和报告状态信息
  • KivaKit配置并运行Jetty、Jersey、Swagger和Apache Wicket,以一致的方式提供微服务接口
  • 关键的KivaKit基类也可以作为有状态特征或“mixin”提供

概述

KivaKit是一个Apache许可证开源Java框架,设计用于实现微服务。KivaKit需要一个Java11+虚拟机,但源代码与Java8和Java9项目兼容。KivaKit由一组精心集成的迷你框架组成。每个迷你框架都有一个一致的设计和自己的重点,可以与其他迷你框架配合使用,也可以单独使用。这些框架的简化依赖关系网络提供了KivaKit的良好高级视图:

每个迷你框架都解决了开发微服务时经常遇到的不同问题。本文简要概述了上图中的微型框架,并简要介绍了如何使用它们。

消息传递

如上图所示,消息传递是KivaKit的核心。消息传递在构建状态可观察的组件时非常有用,这在基于云的世界中是一个有用的功能。KivaKit中的许多对象广播或侦听状态消息,如警报、问题、警告或跟踪。大多数是中继器,侦听来自其他对象的状态消息,并将其重新广播给下游感兴趣的侦听器。这将与终端侦听器形成侦听器链:

C->B->A

通常,链中的最后一个侦听器是某种记录器,但在链的末尾也可以有多个侦听器,任何实现侦听器的对象都可以工作。例如,在Validation mini框架中,ValidationSues类捕获状态消息,然后使用该类确定验证是否成功,以及向用户显示验证失败的特定问题。

给定上面的侦听器链,C和B实现Repeater,最后一个对象A实现listener。在链中的每个类中,侦听器链都扩展为:

listener.listenTo(广播员)

要将消息发送给感兴趣的侦听器,将从广播机继承方便方法,以获得常见类型的消息:

Message

Purpose

problem()

Something has gone wrong and needs to be addressed, but it’s 

not fatal to the current operation.

glitch()

A minor problem has occurred. Unlike a Warning, a Glitch 

indicates validation failure or data loss has occurred. Unlike a 

Problem, a Glitch indicates that the operation will definitely

recover and continue.

warning()

A minor issue has occurred which should be corrected, 

but does not necessarily require attention.

quibble()

A trivial issue has occurred that does not require correction.

announcement()

Announces an important phase of an operation.

narration()

A step in some operation has started or completed.

information()

Commonly useful information that doesn’t represent any 

problem 

trace()

Diagnostic information for use when debugging.

广播器还提供了一种机制,通过对类和包进行模式匹配,从命令行打开和关闭跟踪消息。

混合

在KivaKit中,有两种实现中继器的方法。第一种方法是简单地扩展BaseRepeater。第二种是使用有状态特征或Mixin。实现RepeaterMixin接口与扩展BaseRepeater相同,但是repeater mixin可以在已经有基类的类中使用。注意,下面讨论的组件接口使用相同的模式。如果不能扩展BaseComponent,那么可以实现ComponentMixin。

Mixin接口为缺少的Java语言特性提供了一个解决方案。它的工作原理是将状态查找委托给包私有类MixinState,该类使用实现Mixin的类的this引用在标识哈希映射中查找关联的状态对象。Mixin接口如下所示:

public interface Mixin

{

   defaultT state(Class type, Factoryfactory)

   {

       return MixinState.get(this, type, factory);

   }

}

如果state()找不到此的state对象,则将使用给定的工厂方法创建新的state对象,然后该对象将与状态映射中的mixin关联。例如,我们的Repeatermxin接口大致如下(为了简洁起见,没有大多数方法):

public interface RepeaterMixin extends Repeater, Mixin
{
    @Override
    default void addListener(Listener listener, Filterfilter)
    {
        repeater().addListener(listener, filter);
    }    
    @Override
    default void removeListener(Listener listener)
    {
        repeater().removeListener(listener);
    }
    [...]
    default Repeater repeater()
    {
        return state(RepeaterMixin.class, BaseRepeater::new);
    }        
}

这里,addListener()和RemovelListener()方法各自通过repeater()检索其BaseRepeater状态对象,并将方法调用委托给该对象。正如我们所见,在KivaKit中实现mixin并不复杂。

应该注意的是,对mixin中方法的每次调用都需要在状态映射中进行查找。标识哈希映射通常应该相当有效,但对于一些组件来说,这可能是一个性能问题。与大多数性能问题一样,我们最好做最简单的事情,直到我们的分析器不这么说。

组件

KivaKit组件通常可能是微服务的关键部分。组件通过扩展BaseComponent(最常见的情况)或通过实现ComponentMixin提供对消息传递的轻松访问。从组件继承不会向对象添加任何状态,但从Repeater继承的侦听器列表除外。这使得组件非常轻量级。大量实例化它们并不是一个问题。由于组件是中继器,因此可以创建侦听器链,如上所述。

除了提供对消息的方便访问外,组件还提供以下功能:

  • 注册和查找对象
  • 加载和访问设置对象
  • 访问包资源

让我们看看这些设施。

对象注册和查找

KivaKit使用服务定位器设计模式,而不是依赖项注入。在组件中使用此模式很简单。一个组件可以使用registerObject()注册对象,另一个组件可以使用require()查找对象:

Database database = [...] registerObject(database); [...] var database = require(Database.class);

如果需要注册单个类的多个实例,可以使用枚举值来区分它们:

enum Database { PRODUCTS, SERVICES } registerObject(database, Database.PRODUCTS); [...] var database = require(Database.class, Database.SERVICES);

在KivaKit中,任何可能使用依赖项注入的地方都使用register和require。

设置

KivaKit中的组件也可以使用require()方法轻松访问设置信息:

require(DatabaseSettings.class);

与注册对象一样,如果存在多个相同类型的对象,则可以使用枚举来区分设置对象:

require(DatabaseSettings.class, Database.PRODUCTS);

可以通过多种方式注册设置信息:

registerAllSettingsIn(Folder)

registerAllSettingsIn(Package)

registerSettingsObject(Object)

registerSettingsObject(Object, Enum)

在KivaKit 1.0中,使用RegisterAllSettings sin()方法加载的设置对象由.properties文件定义。将来,将提供一个API,以允许从其他源(如.json文件)加载属性。要实例化的设置类的名称由class属性提供。然后从其余属性中检索实例化对象的各个属性。使用KivaKit转换器(如下所述)将每个属性转换为对象。

例如:

DatabaseSettings.properties

class = com.mycompany.database.DatabaseSettings
port  = database.production.mypna.com:3306

DatabaseSettings.java

public class DatabaseSettings
{    
    @KivaKitPropertyConverter(Port.Converter.class)
    private Port port;
    public Connection connect()
    {
        // Return connection to database on desired port
        [...]
    }
}

包资源

KivaKit提供了一个资源迷你框架,它统一了多种资源类型:

  • 文件夹
  • Sockets
  • Zip或JAR文件条目
  • 包资源
  • HTTP响应
  • 输入流
  • 输出流
  • […]

资源是应用程序可以从中读取流数据的组件。WritableResources是应用程序可以向其写入流数据的资源。文件可用的大多数方法在任何给定资源中都可用,但某些资源类型可能会使某些方法不受支持。例如,资源可能是流式的,因此它无法实现sizeInBytes()。

KivaKit文件是一种特殊的资源。它使用服务提供者接口(SPI)来允许添加新的文件系统。kivakit extensions项目提供了以下文件系统的实现:

  • HDFS文件
  • S3对象
  • GitHub存储库(只读)

KivaKit组件便于访问PackageResources。KivaKit中封装资源的风格类似于ApacheWicket中的风格,组件的包将有一个子包,其中包含其运行所需的资源。这允许从单个源代码树轻松打包和使用组件。相对于组件对包资源的访问如下所示:

public class MyComponent extends BaseComponent
{
    [...]
    var resource = listenTo(packageResource("data/data.txt"));
    for (var line : resource.reader().lines())
    {
    }
}

Where the package structure looks like this:

├── MyComponent
└── data
    └── data.txt

应用

KivaKit应用程序是一个美化的组件,包含与启动、初始化和执行相关的方法。服务器是应用程序的一个子类:

微服务是KivaKit应用程序最常见的用途,但也可以实现其他类型的应用程序(桌面、web、实用程序等)。microservice应用程序的基本代码如下所示:

public class MyMicroservice extends Server

{

   public static void main(final String[] arguments)

   {

       new MyApplication().run(arguments);

   }


   private MyApplication()

   {

       super(MyProject());

   }


   @Override

   protected void onRun()

   {

       [...]

   }

}

 

这里的main()方法创建应用程序,并使用从命令行传递的参数调用应用程序基类中的run()方法。然后,微服务的构造函数将项目对象传递给超类构造函数。此对象用于初始化包含应用程序的项目以及它所依赖的任何其他项目。继续我们的示例,我们的项目类如下所示:

public class MyProject extends Project

{

   private static Lazyproject = Lazy.of(MyProject::new);


   public static ApplicationExampleProject get()

   {

       return project.get();

   }


   protected ApplicationExampleProject()

   {

   }


   @Override

   public Setdependencies()

   {

       return Set.of(ResourceProject.get());

   }

}

{

 

可以使用get()检索MyProject的单例实例。MyProject的依赖项由dependencies()返回。在本例中,MyProject仅依赖于ResourceProject,ResourceProject是kivakit资源迷你框架的项目定义。ResourceProject也有自己的依赖项。KivaKit将确保在调用onRun()之前初始化所有可传递的项目依赖项。

部署

KivaKit应用程序可以从名为deployments的应用程序相关包中自动加载设置对象集合。将微服务部署到特定环境时,此功能非常有用。我们的应用程序的结构如下所示:

├── MyMicroservice

└── deployments

   ├── development

   │   ├── WebSettings.properties

   │   └── DatabaseSettings.properties

   └── production

       ├── WebSettings.properties

       └── DatabaseSettings.properties

当开关-deployment=在命令行上传递给应用程序时,它将从命名的部署(本例中为开发或生产)加载设置。为微服务使用打包部署设置特别好,因为应用程序的使用非常简单:

java-jar my-microservice.jar-deployment=development[…]

这使得在Docker容器中运行应用程序变得很容易,即使您对它了解不多。

如果不需要打包部署设置,可以通过设置环境变量KIVAKIT_settings_FOLDERS来使用外部文件夹:

-DKIVAKIT_SETTINGS_FOLDERS=/Users/jonathan/my microservice SETTINGS

命令行解析

应用程序还可以通过返回一组SwitchParser和/或ArgumentParser列表来解析命令行:

public class MyMicroservice extends Application

{

   private SwitchParser<File> DICTIONARY =

       File.fileSwitchParser("input", "Dictionary file")

                   .required()

                   .build();


   @Override

   public String description()

   {

       return "This microservice checks spelling.";

   }

   @Override

   protected void onRun()

   {

       var input = get(DICTIONARY);    

       if (input.exists())

       {

           [...]

       }

       else

       {

           problem("Dictionary does not exist: $", input.path());

       }

   }

   

   @Override

   protected Set<SwitchParser<?>> switchParsers()

   {

       return Set.of(DICTIONARY);

   }

}


 

这里,KivaKit使用switchParsers()返回的字典开关解析器来解析命令行。在onRun()方法中,通过get(DICTIONARY)检索在命令行上传递的文件参数。如果命令行存在语法问题或未通过验证,KivaKit将自动报告该问题,并提供从description()以及开关和参数解析器派生的用法帮助:

┏-------- COMMAND LINE ERROR(S) -----------   
┋ ○ Required File switch -input is missing  
┗------------------------------------------ 
KivaKit 1.0.0 (puffy telephone)
Usage: MyApplication 1.0.0 <switches> <arguments>
This microservice checks spelling.
Arguments:
  None
Switches:
    Required:
      -input=File (required) : Dictionary file

Switch Parsers

In our application example, we used this code to build a SwitchParser:

private SwitchParser<File> INPUT = 
    File.fileSwitchParser("input", "Input text file")
                .required()
                .build();

The File.fileSwitchParser() method returns a switch parser builder which can be specialized with several methods before build() is called:

public Builder<T> name(String name)
public Builder<T> type(Class<T> type)
public Builder<T> description(String description)
public Builder<T> converter(Converter<String, T> converter)
public Builder<T> defaultValue(T defaultValue)
public Builder<T> optional()
public Builder<T> required()
public Builder<T> validValues(Set<T> validValues)

The implementation of File.fileSwitchParser() then looks like this:

public static SwitchParser.Builder<File> fileSwitchParser(String name, String description)
{
    return SwitchParser.builder(File.class)
            .name(name)
            .converter(new File.Converter(LOGGER))
            .description(description);
}

所有开关和参数都是类型化对象,因此builder(Class)方法使用File类型(使用type()方法)创建一个builder。它的名称和描述传递给fileSwitchParser(),File.Converter方法用于在字符串和文件对象之间进行转换。


转换器

KivaKit提供了许多转换器,转换器可以在KivaKit的许多地方使用。转换器是将一种类型转换为另一种类型的可重用对象。它们特别容易创建,并且可以处理异常、空值或空值等常见问题:

public static class Converter extends BaseStringConverter<File>

{

   public Converter(Listener listener)

   {

       super(listener);

   }


   @Override

   protected File onToValue(String value)

   {

       return File.parse(value);

   }

}

调用StringConverter.convert(字符串)将字符串转换为文件。调用StringConverter.uncert(文件)将把文件转换回字符串。转换过程中遇到的任何问题都会广播给感兴趣的侦听器,如果转换失败,则返回null。

正如我们所看到的,转换器对侦听器链采取了不同的方法。所有转换器都需要一个侦听器作为构造函数参数,而不是依赖转换器用户来调用listenTo()。这确保所有转换器都能够向至少一个侦听器报告转换问题。

验证

在上面的命令行解析代码中,使用kivakit validation mini框架验证开关和参数。另一个常见的用例是向微服务验证web应用程序用户界面的域对象。

可验证类实现:

public interface Validatable

{

   /**

    * @param type The type of validation to perform

    * @return A {@link Validator} instance

    */

   Validator validator(ValidationType type);

}

 

要实现此方法,可以匿名地对BaseValidator进行子类化。BaseValidator提供了检查状态一致性以及广播问题和警告的方便方法。KivaKit使用ValidationSues对象捕获这些消息。然后可以使用Validatable接口中的默认方法查询此状态。用法如下所示:

public class User implements Validatable

{

   String name;

   

   [...]

   

   @Override

   public Validator validator(ValidationType type)

   {

       return new BaseValidator()

       {

           @Override

           protected void onValidate()

           {

               problemIf(name == null, "User must have a name");

           }

       };

   }

}


public class MyComponent extends BaseComponent

{

   public void myMethod()

   {

       var user = new User("Jonathan");

       if (user.isValid(this))

       {

           [...]

       }

   }

}


这里捕获来自验证的消息以确定用户对象是否有效。同样的消息也会广播到MyComponent的监听器,这些消息可能会记录或显示在某些用户界面中。

日志

KivaKit记录器是一个消息侦听器,记录它听到的所有消息。基本应用程序类有一个日志记录器,用于记录从组件到应用程序级别的任何消息。这意味着不需要在应用程序或其任何组件中创建记录器,只要侦听器链从每个组件一直引导到应用程序。

最简单的记录器是ConsoleLogger。将此设计缩小到基本结构,ConsoleLogger和相关类大致如下所示(请参见下面的UML图):

public class ConsoleLogger extends BaseLogger

{

   private Log log = new ConsoleLog();


   @Override

   protected Set<Log> logs()

   {

       return Sets.of(log);

   }

}


public class BaseLogger implements Logger

{

   void onMessage(final Message message)

   {

       log(message);

   }


   public void log(Message message)

   {

       [...]

       

       for (var log : logs())

       {

           log.log(entry);

       }

   }        

}


public class ConsoleLog extends BaseTextLog

{

   private Console console = new Console();


   @Override

   public synchronized void onLog(LogEntry entry)

   {

       console.printLine(entry.message().formatted());

   }

}

BaseLogger.log(Message)方法通过添加上下文信息将提供给它的消息转换为日志条目。然后,它将日志条目传递给logs()返回的日志列表中的每个日志。对于ConsoleLogger,将返回一个ConsoleLog实例。ConsoleLog将日志条目写入控制台。

KivaKit有一个SPI,允许从命令行动态添加和配置新的记录器。KivaKit提供的一些伐木工人包括:

  • ConsoleLog
  • EmailLog
  • FileLog

Web和REST

kivakit扩展项目包含对Jetty、Jersey、Swagger和ApacheWicket的基本支持,因为它们在实现微服务时通常很有用。这些微型框架都集成在一起,因此启动Jetty服务器非常容易,为微服务提供REST和Web访问:

@Override

protected void onRun()

{

   final var port = (int) get(PORT);


   final var application = new MyRestApplication();


   // and start up Jetty with Swagger, Jersey and Wicket.

   listenTo(new JettyServer())

           .port(port)

           .add("/*", new JettyWicket(MyWebApplication.class))

           .add("/open-api/*", new JettySwaggerOpenApi(application))

           .add("/docs/*", new JettySwaggerIndex(port))

           .add("/webapp/*", new JettySwaggerStaticResources())

           .add("/webjar/*", new JettySwaggerWebJar(application))

           .add("/*", new JettyJersey(application))

           .start();

}

JettyServer允许Jersey、Wicket和Swagger与一致的API相结合,使代码清晰简洁。通常这就是所需要的。

结论

尽管KivaKit在1.0版上是全新的,但它在Telenav上已经使用了十多年。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
3天前
|
缓存 负载均衡 JavaScript
探索微服务架构下的API网关模式
【10月更文挑战第37天】在微服务架构的海洋中,API网关犹如一座灯塔,指引着服务的航向。它不仅是客户端请求的集散地,更是后端微服务的守门人。本文将深入探讨API网关的设计哲学、核心功能以及它在微服务生态中扮演的角色,同时通过实际代码示例,揭示如何实现一个高效、可靠的API网关。
|
2天前
|
Dubbo Java 应用服务中间件
服务架构的演进:从单体到微服务的探索之旅
随着企业业务的不断拓展和复杂度的提升,对软件系统架构的要求也日益严苛。传统的架构模式在应对现代业务场景时逐渐暴露出诸多局限性,于是服务架构开启了持续演变之路。从单体架构的简易便捷,到分布式架构的模块化解耦,再到微服务架构的精细化管理,企业对技术的选择变得至关重要,尤其是 Spring Cloud 和 Dubbo 等微服务技术的对比和应用,直接影响着项目的成败。 本篇文章会从服务架构的演进开始分析,探索从单体项目到微服务项目的演变过程。然后也会对目前常见的微服务技术进行对比,找到目前市面上所常用的技术给大家进行讲解。
11 1
服务架构的演进:从单体到微服务的探索之旅
|
1天前
|
Cloud Native 安全 API
云原生架构下的微服务治理策略与实践####
—透过云原生的棱镜,探索微服务架构下的挑战与应对之道 本文旨在探讨云原生环境下,微服务架构所面临的关键挑战及有效的治理策略。随着云计算技术的深入发展,越来越多的企业选择采用云原生架构来构建和部署其应用程序,以期获得更高的灵活性、可扩展性和效率。然而,微服务架构的复杂性也带来了服务发现、负载均衡、故障恢复等一系列治理难题。本文将深入分析这些问题,并提出一套基于云原生技术栈的微服务治理框架,包括服务网格的应用、API网关的集成、以及动态配置管理等关键方面,旨在为企业实现高效、稳定的微服务架构提供参考路径。 ####
15 5
|
4天前
|
监控 API 微服务
后端技术演进:从单体架构到微服务的转变
随着互联网应用的快速增长和用户需求的不断演化,传统单体架构已难以满足现代软件开发的需求。本文深入探讨了后端技术在面对复杂系统挑战时的演进路径,重点分析了从单体架构向微服务架构转变的过程、原因及优势。通过对比分析,揭示了微服务架构如何提高系统的可扩展性、灵活性和维护效率,同时指出了实施微服务时面临的挑战和最佳实践。
17 7
|
2天前
|
Kubernetes 负载均衡 Cloud Native
云原生架构下的微服务治理策略
随着云原生技术的不断成熟,微服务架构已成为现代应用开发的主流选择。本文探讨了在云原生环境下实施微服务治理的策略和方法,重点分析了服务发现、负载均衡、故障恢复和配置管理等关键技术点,以及如何利用Kubernetes等容器编排工具来优化微服务的部署和管理。文章旨在为开发者提供一套实用的微服务治理框架,帮助其在复杂的云环境中构建高效、可靠的分布式系统。
14 5
|
2天前
|
负载均衡 监控 Cloud Native
云原生架构下的微服务治理策略与实践####
在数字化转型浪潮中,企业纷纷拥抱云计算,而云原生架构作为其核心技术支撑,正引领着一场深刻的技术变革。本文聚焦于云原生环境下微服务架构的治理策略与实践,探讨如何通过精细化的服务管理、动态的流量调度、高效的故障恢复机制以及持续的监控优化,构建弹性、可靠且易于维护的分布式系统。我们将深入剖析微服务治理的核心要素,结合具体案例,揭示其在提升系统稳定性、扩展性和敏捷性方面的关键作用,为读者提供一套切实可行的云原生微服务治理指南。 ####
|
2天前
|
Cloud Native 安全 数据安全/隐私保护
云原生架构下的微服务治理与挑战####
随着云计算技术的飞速发展,云原生架构以其高效、灵活、可扩展的特性成为现代企业IT架构的首选。本文聚焦于云原生环境下的微服务治理问题,探讨其在促进业务敏捷性的同时所面临的挑战及应对策略。通过分析微服务拆分、服务间通信、故障隔离与恢复等关键环节,本文旨在为读者提供一个关于如何在云原生环境中有效实施微服务治理的全面视角,助力企业在数字化转型的道路上稳健前行。 ####
|
3天前
|
监控 持续交付 Docker
Docker 容器化部署在微服务架构中的应用有哪些?
Docker 容器化部署在微服务架构中的应用有哪些?
|
3天前
|
监控 持续交付 Docker
Docker容器化部署在微服务架构中的应用
Docker容器化部署在微服务架构中的应用
|
3天前
|
安全 持续交付 Docker
微服务架构和 Docker 容器化部署的优点是什么?
微服务架构和 Docker 容器化部署的优点是什么?