Dubbo圧力測定プラグインの実現——Gatlingに基づく


Dubbo圧力測定プラグインはすでにオープンソースであり、本明細書のコードについてはgatling-dubboを参照してください.
Gatlingは、Scala、Akka、Nettyに基づいてオープンソースで実現される高性能の圧力測定フレームワークであり、他のスレッドに基づいて実現される圧力測定フレームワークに比べて、GatlingはAKA Actorモデルに基づいて実現され、イベントによって駆動されることを要求し、システム資源消費において他の圧力測定フレームワーク(メモリ、接続プールなど)よりも低く、単一の圧力印加機がより多くのユーザーをシミュレートすることができる.また、Gatlingは簡単で効率的なDSL(分野の特定言語)を提供し、私たちがビジネスシーンを編成するのに便利であると同時に、流量制御、圧力制御の能力を備え、良好な圧力測定報告を提供しているので、Gatlingの基礎の上で分布式能力を拡張することを称賛し、自分の全リンク圧力測定エンジンMAXIMを開発した.全リンク圧測では主にユーザーの実際の使用シーンをシミュレートし、HTTPインタフェースを圧測入口として使用しているが、賛目前後端サービスにおけるDubbo応用比重はますます高くなり、Dubbo応用単機水位が分かれば、システムの後端サービス能力をコントロールするのに大いに役立つだろう.Gatlingの利点と優れた使用基盤に基づいて,Gatling‐dubbo圧力測定プラグインを開発した.
プラグインの主な構造
Dubbo圧測プラグインを実現するには、以下の4つの部分を実現する必要がある.
  • ProtocolとProtocolBuild
  • 「プロトコル」セクションでは、主にDubboクライアントに関する内容を定義します.例えば、プロトコル、汎用化呼び出し、サービスURL、登録センターなどの内容です.ProtocolBuildはDSLでProtocolを使用する補助クラスです.
  • ActionおよびActionBuild
  • 実行部では、Dubbo要求を開始し、要求結果を検証し、その後、圧力測定レポートを生成するためにログを記録する役割を果たす.ActionBuildはDSLがActionを使用する補助クラスである
  • CheckとCheckBuild
  • チェックセクションでは、Json Pathを使用してリクエスト結果をチェックします.ここでは、同じチェックロジックを実現します.CheckBuildはDSLがCheckを使用する補助クラスである
  • DSL

  • Dubboプラグインの領域の特定の言語、私達は1セットの簡単で使いやすいAPIを提供して簡単にDubooの圧力測定のスクリプトを編纂することを便利にして、風格の上で原生のHTTP DSLと一致を維持します
    Protocol
    プロトコル・セクションは、アクションがDubboクライアントを初期化するときに使用される5つのプロパティで構成されます.
  • protocol
  • dubboに設定されたプロトコル
  • generic

  • 汎用化呼び出し設定、Dubbo測定プラグインは汎用化呼び出し開始要求を使用するため、ここではtrueに設定されており、汎用化呼び出しの性能を最適化することが推奨されている.この特性を使用するために、新しい値result_no_changeが導入された(最適化前の汎用化呼び出しのシーケンス化オーバーヘッドを除いて性能を向上させる)
  • url

  • Dubboサービスの住所:dubbo://IP :
  • registryProtocol

  • Dubbo登録センターのプロトコルは、ETCD3に設定されています.
  • registryAddress

  • Dubbo登録センターの住所
    Dubboユニットの水位をテストする場合、urlを設定し、登録センターを空に設定します.Dubboクラスタの水位をテストする場合は、登録センター(現在ETCD 3がサポートされている)を設定し、urlを空に設定します.現在、登録センターはETCD 3のみをサポートしており、プラグインのDubboクラスタでの使用は柔軟性に欠けているため、クライアントレベルの負荷分散を実現し、特定の登録センターを捨ててDubboクラスタの水位をテストすることができます.この特性は現在内測中である.
    object DubboProtocol {
      val DubboProtocolKey = new ProtocolKey {
        type Protocol = DubboProtocol
        type Components = DubboComponents
    
        def protocolClass: Class[io.gatling.core.protocol.Protocol] = classOf[DubboProtocol].asInstanceOf[Class[io.gatling.core.protocol.Protocol]]
    
        def defaultProtocolValue(configuration: GatlingConfiguration): DubboProtocol = throw new IllegalStateException("Can't provide a default value for DubboProtocol")
    
        def newComponents(system: ActorSystem, coreComponents: CoreComponents): DubboProtocol => DubboComponents = {
          dubboProtocol => DubboComponents(dubboProtocol)
        }
      }
    }
    
    case class DubboProtocol(
        protocol: String, //dubbo
        generic:  String, //    ?
        url:      String, //use url or
        registryProtocol: String,  //use registry
        registryAddress:  String   //use registry
    ) extends Protocol {
      type Components = DubboComponents
    }

    Actionでこれらのプロパティを簡単に使用するために、GatlingのProtocolComponentsに組み込みました.
    case class DubboComponents(dubboProtocol: DubboProtocol) extends ProtocolComponents {
      def onStart: Option[Session => Session] = None
      def onExit: Option[Session => Unit] = None
    }

    以上、Protocolの定義についてです.DSLで上記のProtocolを構成するために、DubboProtocolBuilderを定義し、Protocolのprotocol、generic、url、registryProtocol、registryAddressの5つの属性をそれぞれ設定する5つの方法を含む.
    object DubboProtocolBuilderBase {
      def protocol(protocol: String) = DubboProtocolBuilderGenericStep(protocol)
    }
    
    case class DubboProtocolBuilderGenericStep(protocol: String) {
      def generic(generic: String) = DubboProtocolBuilderUrlStep(protocol, generic)
    }
    
    case class DubboProtocolBuilderUrlStep(protocol: String, generic: String) {
      def url(url: String) = DubboProtocolBuilderRegistryProtocolStep(protocol, generic, url)
    }
    
    case class DubboProtocolBuilderRegistryProtocolStep(protocol: String, generic: String, url: String) {
      def registryProtocol(registryProtocol: String) = DubboProtocolBuilderRegistryAddressStep(protocol, generic, url, registryProtocol)
    }
    
    case class DubboProtocolBuilderRegistryAddressStep(protocol: String, generic: String, url: String, registryProtocol: String) {
      def registryAddress(registryAddress: String) = DubboProtocolBuilder(protocol, generic, url, registryProtocol, registryAddress)
    }
    
    case class DubboProtocolBuilder(protocol: String, generic: String, url: String, registryProtocol: String, registryAddress: String) {
      def build = DubboProtocol(
        protocol = protocol,
        generic = generic,
        url = url,
        registryProtocol = registryProtocol,
        registryAddress = registryAddress
      )
    }

    Action
    DubboActionには、Duboo要求ロジック、要求結果検証ロジック、および圧力制御ロジックが含まれており、ExitableActionを拡張し、executeメソッドを実装する必要があります.
    DubboActionクラスのドメインargType,argValuesはそれぞれ汎化呼び出し要求パラメータタイプと要求パラメータ値であり,Expression[]タイプである必要があり,このようにデータFeederを圧着スクリプトパラメータとして入力する場合,${args_types},${args_values}のような式を用いて対応するフィールドの値を数据Feederから解析することができる.
    executeメソッドは、前のDubboリクエストが実行された後も応答が返されないときに仮想ユーザがAKA Messageを介してすぐに次のリクエストを開始できるように、非同期でDubboリクエストを実行する必要があります.このように、仮想ユーザは短い時間で大量のリクエストを構築することができます.要求方式としては、汎用化呼び出しよりも、元のAPI呼び出しには、Dubboサービスに対応するAPIパケットをクライアントにロードする必要があるが、入手できない場合がある.また、被測定Dubboアプリケーションが多くなると、クライアントは複数のAPIパケットをロードする必要があるため、使用上の利便性から、Dubboプレスプラグインは汎用化呼び出しを使用して要求を開始する.
    非同期リクエスト応答後にonCompleteメソッドが実行され、リクエスト結果が検証され、リクエスト成功ログまたは失敗ログが検証結果に基づいて記録されます.これらのログ統計を使用して、圧力測定レポートが計算されます.
    圧力測定時のRPSを制御するためには、throttleロジックを実現する必要がある.実際には、高同時性の場合、汎化呼び出し性能は原生API呼び出し性能にはるかに及ばず、応答時間が倍増し(これではDubboアプリケーションの真の性能を特徴付けることができない)、Dubbo圧力測定プラグインの圧力制御が不正確になり、解決策は汎化呼び出し性能を最適化し、原生API呼び出しの性能に近いようにすることであり、dubbo汎化呼び出し性能最適化を参照してください.
    class DubboAction(
        interface:        String,
        method:           String,
        argTypes:         Expression[Array[String]],
        argValues:        Expression[Array[Object]],
        genericService:   GenericService,
        checks:           List[DubboCheck],
        coreComponents:   CoreComponents,
        throttled:        Boolean,
        val objectMapper: ObjectMapper,
        val next:         Action
    ) extends ExitableAction with NameGen {
    
      override def statsEngine: StatsEngine = coreComponents.statsEngine
    
      override def name: String = genName("dubboRequest")
    
      override def execute(session: Session): Unit = recover(session) {
        argTypes(session) flatMap { argTypesArray =>
          argValues(session) map { argValuesArray =>
            val startTime = System.currentTimeMillis()
            val f = Future {
              try {
                genericService.$invoke(method, argTypes(session).get, argValues(session).get)
              } finally {
              }
            }
    
            f.onComplete {
              case Success(result) =>
                val endTime = System.currentTimeMillis()
                val resultMap = result.asInstanceOf[JMap[String, Any]]
                val resultJson = objectMapper.writeValueAsString(resultMap)
                val (newSession, error) = Check.check(resultJson, session, checks)
                error match {
                  case None =>
                    statsEngine.logResponse(session, interface + "." + method, ResponseTimings(startTime, endTime), Status("OK"), None, None)
                    throttle(newSession(session))
                  case Some(Failure(errorMessage)) =>
                    statsEngine.logResponse(session, interface + "." + method, ResponseTimings(startTime, endTime), Status("KO"), None, Some(errorMessage))
                    throttle(newSession(session).markAsFailed)
                }
              case FuFailure(e) =>
                val endTime = System.currentTimeMillis()
                statsEngine.logResponse(session, interface + "." + method, ResponseTimings(startTime, endTime), Status("KO"), None, Some(e.getMessage))
                throttle(session.markAsFailed)
            }
          }
        }
      }
    
      private def throttle(s: Session): Unit = {
        if (throttled) {
          coreComponents.throttler.throttle(s.scenario, () => next ! s)
        } else {
          next ! s
        }
      }
    }

    DubboActionBuilderは、Protocolのプロパティを取得し、Dubboクライアントを初期化します.
    case class DubboActionBuilder(interface: String, method: String, argTypes: Expression[Array[String]], argValues: Expression[Array[Object]], checks: List[DubboCheck]) extends ActionBuilder {
      private def components(protocolComponentsRegistry: ProtocolComponentsRegistry): DubboComponents =
        protocolComponentsRegistry.components(DubboProtocol.DubboProtocolKey)
    
      override def build(ctx: ScenarioContext, next: Action): Action = {
        import ctx._
        val protocol = components(protocolComponentsRegistry).dubboProtocol
        //Dubbo     
        val reference = new ReferenceConfig[GenericService]
        val application = new ApplicationConfig
        application.setName("gatling-dubbo")
        reference.setApplication(application)
        reference.setProtocol(protocol.protocol)
        reference.setGeneric(protocol.generic)
        if (protocol.url == "") {
          val registry = new RegistryConfig
          registry.setProtocol(protocol.registryProtocol)
          registry.setAddress(protocol.registryAddress)
          reference.setRegistry(registry)
        } else {
          reference.setUrl(protocol.url)
        }
        reference.setInterface(interface)
        val cache = ReferenceConfigCache.getCache
        val genericService = cache.get(reference)
        val objectMapper: ObjectMapper = new ObjectMapper()
        new DubboAction(interface, method, argTypes, argValues, genericService, checks, coreComponents, throttled, objectMapper, next)
      }
    }

    LambdaProcessBuilderでは、Dubbo汎化呼び出しパラメータを設定するDSLと、次に説明するCheckセクションのDSLを提供します.
    case class DubboProcessBuilder(interface: String, method: String, argTypes: Expression[Array[String]] = _ => Success(Array.empty[String]), argValues: Expression[Array[Object]] = _ => Success(Array.empty[Object]), checks: List[DubboCheck] = Nil) extends DubboCheckSupport {
    
      def argTypes(argTypes: Expression[Array[String]]): DubboProcessBuilder = copy(argTypes = argTypes)
    
      def argValues(argValues: Expression[Array[Object]]): DubboProcessBuilder = copy(argValues = argValues)
    
      def check(dubboChecks: DubboCheck*): DubboProcessBuilder = copy(checks = checks ::: dubboChecks.toList)
    
      def build(): ActionBuilder = DubboActionBuilder(interface, method, argTypes, argValues, checks)
    }

    Check
    全リンク電圧測定では,HTTP要求結果をJson Pathで検証し,Dubbo電圧測定プラグインでもJson Pathに基づく検証を実現した.Checkを実装するには、Gatling checkのExtenderとPreparerを実装する必要があります.
    package object dubbo {
      type DubboCheck = Check[String]
    
      val DubboStringExtender: Extender[DubboCheck, String] =
        (check: DubboCheck) => check
    
      val DubboStringPreparer: Preparer[String, String] =
        (result: String) => Success(result)
    }
    Json Pathに基づく検証ロジック:
    trait DubboJsonPathOfType {
      self: DubboJsonPathCheckBuilder[String] =>
    
      def ofType[X: JsonFilter](implicit extractorFactory: JsonPathExtractorFactory) = new DubboJsonPathCheckBuilder[X](path, jsonParsers)
    }
    
    object DubboJsonPathCheckBuilder {
      val CharsParsingThreshold = 200 * 1000
    
      def preparer(jsonParsers: JsonParsers): Preparer[String, Any] =
        response => {
          if (response.length() > CharsParsingThreshold || jsonParsers.preferJackson)
            jsonParsers.safeParseJackson(response)
          else
            jsonParsers.safeParseBoon(response)
        }
    
      def jsonPath(path: Expression[String])(implicit extractorFactory: JsonPathExtractorFactory, jsonParsers: JsonParsers) =
        new DubboJsonPathCheckBuilder[String](path, jsonParsers) with DubboJsonPathOfType
    }
    
    class DubboJsonPathCheckBuilder[X: JsonFilter](
        private[check] val path:        Expression[String],
        private[check] val jsonParsers: JsonParsers
    )(implicit extractorFactory: JsonPathExtractorFactory)
      extends DefaultMultipleFindCheckBuilder[DubboCheck, String, Any, X](
        DubboStringExtender,
        DubboJsonPathCheckBuilder.preparer(jsonParsers)
      ) {
      import extractorFactory._
    
      def findExtractor(occurrence: Int) = path.map(newSingleExtractor[X](_, occurrence))
      def findAllExtractor = path.map(newMultipleExtractor[X])
      def countExtractor = path.map(newCountExtractor)
    }

    DubboCheckSupportは、jsonPath式を設定するDSLを提供します.
    trait DubboCheckSupport {
      def jsonPath(path: Expression[String])(implicit extractorFactory: JsonPathExtractorFactory, jsonParsers: JsonParsers) =
        DubboJsonPathCheckBuilder.jsonPath(path)
    }
  • Dubbo測定スクリプトでは、DSLチェックメソッド*
  • を使用して、1つまたは複数のチェック要求結果を設定できます.
    DSL
    trait AwsDslは、最上位層DSLを提供する.DubboProtocolBuilder 2 DubboProtocol、dubboProcessBuilder 2 ActionBuilderの2つのScala暗黙的アプローチも定義し、DubboProtocolとActionBuilderを自動的に構築します.また、汎用化呼び出しで使用されるパラメータタイプはJavaタイプであり、我々の圧力測定スクリプトはScalaで記述されているので、ここでは2つの言語間のタイプ変換が必要であるため、transformJsonDubboDataメソッドを定義した.
    trait DubboDsl extends DubboCheckSupport {
      val Dubbo = DubboProtocolBuilderBase
    
      def dubbo(interface: String, method: String) = DubboProcessBuilder(interface, method)
    
      implicit def dubboProtocolBuilder2DubboProtocol(builder: DubboProtocolBuilder): DubboProtocol = builder.build
    
      implicit def dubboProcessBuilder2ActionBuilder(builder: DubboProcessBuilder): ActionBuilder = builder.build()
      
      def transformJsonDubboData(argTypeName: String, argValueName: String, session: Session): Session = {
        session.set(argTypeName, toArray(session(argTypeName).as[JList[String]]))
          .set(argValueName, toArray(session(argValueName).as[JList[Any]]))
      }
    
      private def toArray[T:ClassTag](value: JList[T]): Array[T] = {
        value.asScala.toArray
      }
    }
    object Predef extends DubboDsl

    Dubbo測定スクリプトとデータFeederの例
    測定スクリプトの例:
    import io.gatling.core.Predef._
    import io.gatling.dubbo.Predef._
    
    import scala.concurrent.duration._
    
    class DubboTest extends Simulation {
      val dubboConfig = Dubbo
        .protocol("dubbo")
        .generic("true")
        //    Dubbo  ,            
        .url("dubbo://IP  :  ")
        //       ,   Dubbo       ,  ETCD3    
        .registryProtocol("")
        .registryAddress("")
    
      val jsonFileFeeder = jsonFile("data.json").circular  //  Feeder
      val dubboScenario = scenario("load test dubbo")
        .forever("repeated") {
          feed(jsonFileFeeder)
            .exec(session => transformJsonDubboData("args_types1", "args_values1", session))
            .exec(dubbo("com.xxx.xxxService", "methodName")
              .argTypes("${args_types1}")
              .argValues("${args_values1}")
              .check(jsonPath("$.code").is("200"))
            )
        }
    
      setUp(
        dubboScenario.inject(atOnceUsers(10))
          .throttle(
            reachRps(10) in (1 seconds),
            holdFor(30 seconds))
      ).protocols(dubboConfig)
    }

    data.jsonの例:
    [
      {
      "args_types1": ["com.xxx.xxxDTO"],
      "args_values1": [{
        "field1": "111",
        "field2": "222",
        "field3": "333"
      }]
      }
    ]

    Dubbo圧力測定報告例
    私のシリーズのブログのカオスエンジニアリング-ソフトウェアシステムの高可用性、弾性化の必由の道の非同期システムの2種類のテスト方法
    私のその他のテスト関連のオープンソースプロジェクトの虫取り記:製品、開発、テストの3つの協同自己測定の管理ツールを便利にします
    募集有賛テストチームは募集を続けています.多くのポストが空いています.あなたが来さえすれば、スタック全体の開発スキルツリーを点灯することができます.転職する意向のある学生は履歴書をsunjun【@】youzanに送ることができます.com