Backend Development 15 min read

Gatling Dubbo Load Testing Plugin: Architecture, DSL, and Implementation Guide

The Gatling‑Dubbo plugin extends Gatling’s high‑performance, event‑driven load‑testing framework with a Dubbo protocol layer, providing protocol, action, and JsonPath‑based check components plus a HTTP‑like DSL, enabling asynchronous generic calls, response validation, throttling, and full example simulations for distributed Dubbo service testing.

Youzan Coder
Youzan Coder
Youzan Coder
Gatling Dubbo Load Testing Plugin: Architecture, DSL, and Implementation Guide

Gatling is an open‑source, high‑performance load‑testing framework built on Scala, Akka and Netty. Compared with thread‑based tools, Gatling uses an event‑driven model that consumes fewer resources, allowing a single load‑generator to simulate many virtual users. Youzan extended Gatling with a distributed load‑testing engine (MAXIM) and added a Dubbo load‑testing plugin (gatling‑dubbo) to measure the capacity of Dubbo services.

Plugin Main Structure

The plugin consists of four parts:

Protocol & ProtocolBuild : defines Dubbo client configuration (protocol, generic call flag, service URL, registry protocol and address) and provides a DSL builder.

Action & ActionBuild : sends Dubbo requests asynchronously, validates responses, logs results and forwards to the next action.

Check & CheckBuild : uses JsonPath to verify the response content.

DSL : a domain‑specific language that mirrors Gatling’s HTTP DSL for writing Dubbo scenarios.

Protocol Definition

The protocol is represented by five fields. Example Scala code:

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, // "true" or "result_no_change"
    url: String, // e.g. "dubbo://IP:port"
    registryProtocol: String, // e.g. "ETCD3"
    registryAddress: String) extends Protocol {
  type Components = DubboComponents
}

If url is empty, the plugin will use the registry (currently only ETCD3) and perform client‑side load balancing.

Action Implementation

DubboAction extends ExitableAction and executes the request asynchronously so that a virtual user can fire the next request without waiting for the previous response. The response is converted to JSON, checked with the defined Check s, and the result is logged. Throttling is applied when the scenario is marked as throttled.

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)
      }
    }
  }
}

Check Using JsonPath

The plugin provides a DubboCheck that works with Gatling’s Check API. It defines a string extender and a preparer that parses the response with either Jackson or Boon depending on size.

type DubboCheck = Check[String]
val DubboStringExtender: Extender[DubboCheck, String] = (check: DubboCheck) => check
val DubboStringPreparer: Preparer[String, String] = (result: String) => Success(result)

The DSL offers a jsonPath method to create a DubboJsonPathCheckBuilder which can be further refined with ofType , find , findAll , etc.

def jsonPath(path: Expression[String])(implicit extractorFactory: JsonPathExtractorFactory, jsonParsers: JsonParsers) =
  new DubboJsonPathCheckBuilder[String](path, jsonParsers) with DubboJsonPathOfType

DSL for Building Scenarios

The top‑level trait DubboDsl exposes a Dubbo protocol builder and a dubbo(interface, method) shortcut that returns a DubboProcessBuilder . Implicit conversions turn the builder into a DubboProtocol and an ActionBuilder . Additionally, transformJsonDubboData converts JSON arrays from a feeder into Java JList objects required by the Dubbo generic call.

def transformJsonDubboData(argTypeName: String, argValueName: String, session: Session): Session = {
  session
    .set(argTypeName, toArray(session(argTypeName).asInstanceOf[JList[String]]))
    .set(argValueName, toArray(session(argValueName).asInstanceOf[JList[Any]]))
}

Example Load‑Test Script

A complete Gatling simulation using the Dubbo plugin:

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")
    .url("dubbo://IP:port") // or use registryProtocol/registryAddress for cluster testing

  val jsonFileFeeder = jsonFile("data.json").circular

  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)
}

The accompanying data.json feeder provides argument type and value arrays for the generic Dubbo call.

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

Recruitment Notice

Youzan’s testing team is hiring. Interested engineers can send their resumes to [email protected] .

Further Reading

Chaos Engineering – The Path to High Availability and Resilience

Two Testing Methods for Asynchronous Systems

Youzan Full‑Link Load Testing Practice

— The End —

performanceDSLPluginDubboScalaGatling
Youzan Coder
Written by

Youzan Coder

Official Youzan tech channel, delivering technical insights and occasional daily updates from the Youzan tech team.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.