我们已经了解了如何定义服务描述符以及如何实现它们,现在我们需要使用它们。服务描述符包含了Lagom需要知道的关于如何调用服务的所有信息,因此,Lagom能够为您实现服务描述符接口。

绑定服务客户端

使用服务的第一件事是创建它的实现。Lagom提供了一个宏在ServiceClient类上执行此操作,称为implementation。ServiceClient是由LagomServiceClientComponents提供的,它已经由LagomApplication实现了,所以要从Lagom应用程序创建服务客户端,你只需要做以下操作:

  1. abstract class MyApplication(context: LagomApplicationContext)
  2. extends LagomApplication(context)
  3. with AhcWSComponents {
  4. lazy val helloService = serviceClient.implement[HelloService]
  5. }

使用服务客户端

绑定客户端之后,现在可以在Lagom应用程序的任何地方使用它。通常情况下,这将通过组件构造函数将其传递给另一个组件(如服务实现)来完成,如果您使用Macwire,该构造函数将自动为您完成。下面是一个从另一个服务中消费一个服务的例子:

  1. class MyServiceImpl(helloService: HelloService)(implicit ec: ExecutionContext) extends MyService {
  2. override def sayHelloLagom = ServiceCall { _ =>
  3. val result: Future[String] =
  4. helloService.sayHello.invoke("Lagom")
  5. result.map { response =>
  6. s"Hello service said: $response"
  7. }
  8. }
  9. }

流服务客户端配置

当使用流服务客户端时,Lagom将在内部使用一个WebSocket客户端,该客户端有一个最大帧长参数。这个参数限制了流经WebSocket的消息所允许的最大大小。这可以在客户端application.conf中进行配置,默认配置为:

  1. #This configures the websocket clients used by this service.
  2. #This is a global configuration and it is currently not possible
  3. #to provide different configurations if multiple websocket services
  4. #are consumed.
  5. lagom.client.websocket {
  6. #This parameter limits the allowed maximum size for the messages
  7. #flowing through the WebSocket. A similar limit exists on
  8. #the server side, see:
  9. #https://www.playframework.com/documentation/2.6.x/ScalaWebSockets#Configuring-WebSocket-Frame-Length
  10. frame.maxLength = 65536
  11. }

此配置将影响服务客户端使用的所有流服务。当使用多个流服务时,无法提供不同的配置。
注意,使用Play server configuration在服务器端必须配置相同的参数。

断路器

在分布式系统中,断路器(circuit breaker)用于提供稳定性和防止级联故障。它们应该与服务之间的接口上的适当超时一起使用,以防止单个服务的故障导致其他服务宕机。
例如,我们有一个与第三方web服务交互的web应用程序。假设第三方服务已经超过了他们的承受能力,他们的数据库在这样的负载下崩溃了。假设数据库以这样一种方式出现故障,它需要很长时间才能将一个错误返回给第三方web服务。这反过来又会使调用在很长一段时间后失败。回到我们的web应用程序,用户已经注意到他们的表单提交看起来挂起等待的时间要长得多。用户所做的就是使用刷新按钮,向已经运行的请求服务添加更多请求。这最终会导致web应用程序由于资源耗尽而失败。
在web服务调用中引入断路器将导致请求开始快速失败,让用户知道有些地方出错了,他们不需要刷新请求。这也将失败行为限制在那些使用依赖于第三方功能的用户,其他用户不再受影响,因为没有资源耗尽。断路器还可以让开发人员将站点中使用该功能的部分标记为不可用,或者在断路器打开时适当地显示一些缓存的内容。
断路器有三种状态:
image.png
断路器正常运行时处于闭合状态:

  • 异常或调用超过了配置的call-timeout触发故障计数器自增
  • 成功将失败计数重置为零
  • 当失败计数器达到max-failures计数时,断路器被触发到Open状态

在开放状态:

  • 所有调用都将返回CircuitBreakerOpenException类型的快速失败
  • 在配置reset-timeout时间后,断路器进入“半开”状态

在半开状态:

  • 第一次尝试调用通过而不会快速失败
  • 所有其他调用都是快速失败的,就像在Open状态下一样
  • 如果第一次调用成功,断路器复位为闭合状态
  • 如果第一个调用失败,断路器将再次触发到Open状态,以进行另一个完整的reset-timeout

默认情况下,所有与Lagom服务客户端的服务调用都使用断路器。断路器是在客户端使用和配置的,但是粒度和配置标识符是由服务提供者定义的。默认情况下,一个断路器实例应用于另一个服务的所有调用(方法)。可以为每个方法设置一个唯一的断路器标识符,以便为每个方法使用一个单独的断路器实例。还可以通过在几个方法上使用相同的标识符来对相关的方法进行分组。

  1. import com.lightbend.lagom.scaladsl.api.CircuitBreaker
  2. def descriptor: Descriptor = {
  3. import Service._
  4. named("hello").withCalls(
  5. namedCall("hi", this.sayHi),
  6. namedCall("hiAgain", this.hiAgain)
  7. .withCircuitBreaker(CircuitBreaker.identifiedBy("hello2"))
  8. )
  9. }

在上面的例子中,sayHi方法使用了默认标识符,因为没有给出特定的标识符。默认标识符与服务名相同,即在这个例子中是”hello“。hiAgain方法将使用另一个断路器实例,因为“hello2”被指定为断路器标识符。

断路器配置

在客户端可以配置断路器。默认配置为:

  1. # Circuit breakers for calls to other services are configured
  2. # in this section. A child configuration section with the same
  3. # name as the circuit breaker identifier will be used, with fallback
  4. # to the `lagom.circuit-breaker.default` section.
  5. lagom.circuit-breaker {
  6. # Default configuration that is used if a configuration section
  7. # with the circuit breaker identifier is not defined.
  8. default {
  9. # Possibility to disable a given circuit breaker.
  10. enabled = on
  11. # Number of failures before opening the circuit.
  12. max-failures = 10
  13. # Duration of time after which to consider a call a failure.
  14. call-timeout = 10s
  15. # Duration of time in open state after which to attempt to close
  16. # the circuit, by first entering the half-open state.
  17. reset-timeout = 15s
  18. # A whitelist of fqcn of Exceptions that the CircuitBreaker
  19. # should not consider failures. By default all exceptions are
  20. # considered failures.
  21. exception-whitelist = []
  22. }
  23. }

如果您自己没有定义任何配置,将使用该配置。配置断路器的设置包括断路器中的一般设置,如失败次数或应该打开断路器的请求超时时间,以及再次闭合断路器所必须经过的超时时间。在lagom中,有一个额外的设置来控制什么是失败的。
Lagom的客户端将所有4xx和5xx响应映射到exception, Lagom的断路器默认将所有exception视为失败。您可以通过将特定异常列入白名单来更改默认行为,这样它们就不会被视为失败。有时您希望为给定的端点配置断路器,这样它就会忽略某个异常。当连接到使用4xx HTTP状态代码来建模业务有效案例的服务时,这尤其有用。例如,对特定请求响应404 Not Found可能是一个正常的情况。在这种情况下,你可以将"com.lightbend.lagom.scaladsl.api.transport.NotFound"添加到断路器白名单中,以使其不被认为是故障。即使NotFound异常不被计算为失败,客户端仍然会抛出一个NotFound异常作为调用服务的结果。
在上面的” hello “例子中,我们可以通过在application.conf中定义属性来调整配置,比如:

  1. lagom.circuit-breaker {
  2. # will be used by sayHi method
  3. hello.max-failures = 5
  4. # will be used by hiAgain method
  5. hello2 {
  6. max-failures = 7
  7. reset-timeout = 30s
  8. }
  9. # Change the default call-timeout
  10. # will be used for both sayHi and hiAgain methods
  11. default.call-timeout = 5s
  12. }

Lightbend Monitoring将提供Lagom断路器的指标,包括集群中所有节点信息的聚合视图。