Restapi-caching, akka-http cache

restapi serves as a hub for front-end and back-end interaction: in the face of a large number of front-end requests, you need to ensure timely responses.Using caching is an effective tool.We can cache the response of most front-end requests, especially those that require a lot of computation to obtain, which can greatly improve the response speed of the back-end.Fortunately, akka-http already provides support for caching, a set of caching operations toolkits based on java8 caffein.The caching of akka-http is described below.

akka-http caching has a dependency:

 

 "com.typesafe.akka" %% "akka-http-caching" % akkaHttpVersion,

 

Start with the cache storage structure and look at one of the following cache structure definitions:

import akka.http.scaladsl.util.FastFuture
import akka.http.caching.scaladsl.Cache
import akka.http.caching.scaladsl.CachingSettings
import akka.http.caching.LfuCache

    val defaultCachingSettings = CachingSettings(sys)
    val lfuCacheSettings =       //Minimum use of exclusion algorithm cache
     defaultCachingSettings.lfuCacheSettings
        .withInitialCapacity(128)  //Starting unit
        .withMaxCapacity(1024)   //Maximum Unit
        .withTimeToLive(1.hour)         //Maximum retention time
        .withTimeToIdle(30.minutes)     //Maximum Unused Time
    val cachingSettings =
      defaultCachingSettings.withLfuCacheSettings(lfuCacheSettings)

    //key -> String
    val lfuCache: Cache[String,  Option[Map[String, Any]]] = LfuCache(cachingSettings)

lfuCache is a cache management system based on the frequency of use algorithm, so let's not go into it.It's best to take an example to illustrate that you have an http request template for user information right at hand:

    val route = pathPrefix(pathName) {
      pathPrefix("getuserinfo") {
        (get & parameter('userid)) { userid => {
          val userinfo = posRepo.getUserInfo(userid)
          userinfo match {
            case Some(ui) => complete(toJson(ui))
            case None => complete(toJson(Map[String, Any](("TERMINALID" -> ""))))
          }
        }
        }
      }


   def getUserInfo(userid: String): Option[UserInfo] = {
     val sql = "SELECT CUSTOMERS.SHOPID AS SHOPID, TERMINALID, DEVICEID, IMPSVCURL FROM CUSTOMERS INNER JOIN TERMINALS " +
       " ON CUSTOMERS.SHOPID=TERMINALS.SHOPID " +
       " WHERE (CUSTOMERS.DISABLED=0 AND TERMINALS.DISABLED=0) " +
       " AND (CUSTOMERS.EXPDATE > GETDATE() AND TERMINALS.EXPDATE > GETDATE()) AND TERMINALID='" + userid + "'"
     val rows = query[Map[String, Any]]("mpos", sql, rsc.resultSet2Map)
     val futUI: Future[Option[Map[String, Any]]] = rows.runWith(Sink.lastOption)
     Await.result(futUI, 3 seconds)
   }

When a front-end request such as http://mycom.com/pos/getuserinfo?userid=1234 is received, the user information data needs to be read from the database and some conversion processing takes place.This is a real example of a request that is invoked frequently and takes time to read from the database.Let's see how to implement cache management:

There are two ways to implement cache management in akka-http: 1. Use the cache tool directly, 2. Directive: cache provided with akka-http, alwaysCache

Let's first look at how to use the cache operation directly, and first look at the construction of the cache:

abstract class Cache[K, V] extends akka.http.caching.javadsl.Cache[K, V] {
  cache =>

  /**
   * Returns either the cached Future for the given key or evaluates the given value generating
   * function producing a `Future[V]`.
   */
  def apply(key: K, genValue: () => Future[V]): Future[V]

Cache[K,V] is a structure with K as the key and ()=> Future [V] as the value, that is, we need to store a function to get the Future value in the cache:

      pathPrefix("getuserinfo") {
        (get & parameter('userid)) { userid => {
          val userinfo = lfuCache.getOrLoad(userid, _ => posRepo.futureUserInfo(userid))
          onComplete(userinfo) {
            _ match {
              case Success(oui) => oui match {
                case Some(ui) => complete(toJson(ui))
                case None => complete(toJson(Map[String, Any](("TERMINALID" -> ""))))
              }
              case Failure(_) => complete(toJson(Map[String, Any](("TERMINALID" -> ""))))
            }
          }
        }
        }


   def futureUserInfo(userid: String): Future[Option[Map[String, Any]]] = {
     val sql = "SELECT CUSTOMERS.SHOPID AS SHOPID, TERMINALID, DEVICEID, IMPSVCURL FROM CUSTOMERS INNER JOIN TERMINALS " +
       " ON CUSTOMERS.SHOPID=TERMINALS.SHOPID " +
       " WHERE (CUSTOMERS.DISABLED=0 AND TERMINALS.DISABLED=0) " +
       " AND (CUSTOMERS.EXPDATE > GETDATE() AND TERMINALS.EXPDATE > GETDATE()) AND TERMINALID='" + userid + "'"
     val rows = query[Map[String, Any]]("mpos", sql, rsc.resultSet2Map)
     rows.runWith(Sink.lastOption)
   }

First we need to modify getUserInfo to futureUserInfo and pass in cache.getOrLoad():

 /**
   * Returns either the cached Future for the given key, or applies the given value loading
   * function on the key, producing a `Future[V]`.
   */
  def getOrLoad(key: K, loadValue: K => Future[V]): Future[V]

Follow us and try again with Directive, cache and alwaysCache of akka-http.These two are the same thing, except that more than one cache controls whether or not to use caching, such as Cache-Control`(`no-cache`), are implemented through request-header Cache-Control.This is how the cache function is defined;

def cache[K](cache: Cache[K, RouteResult], keyer: PartialFunction[RequestContext, K]): Directive0

This function returns Directive0, which directly corresponds to {... complete(...)}, so cache can encapsulate a route inside, for example:

    cache(myCache, simpleKeyer) {
      complete {
        i += 1
        i.toString
      }
    }

SimeKeyer is a K-corresponding function: in our example, K -> Uri, Cache [Uri, RouteResult].Here's an out-of-the-box builder: routeCache[Uri]

  /**
   * Creates an [[LfuCache]] with default settings obtained from the system's configuration.
   */
  def routeCache[K](implicit s: ActorSystem): Cache[K, RouteResult] =
    LfuCache[K, RouteResult](s)

However, this LfuCache uses cachingSettings from application.conf. We want to control the lfuCache build directly, so we can use:

val lfuCache = LfuCache[Uri,RouteResult](cachingSettings)

The specific use of the alwaysCache is the same as the cache.getOrLoad above:

import akka.http.scaladsl.model.{HttpMethods, StatusCodes, Uri}
import akka.http.scaladsl.util.FastFuture
import akka.http.caching.scaladsl.Cache
import akka.http.caching.scaladsl.CachingSettings
import akka.http.caching.LfuCache
import akka.http.scaladsl.server.RequestContext
import akka.http.scaladsl.server.RouteResult
import akka.http.scaladsl.server.directives.CachingDirectives._
import scala.concurrent.duration._
import scala.util._

    val defaultCachingSettings = CachingSettings(sys)
    val lfuCacheSettings =       //Minimum use of exclusion algorithm cache
     defaultCachingSettings.lfuCacheSettings
        .withInitialCapacity(128)  //Starting unit
        .withMaxCapacity(1024)   //Maximum Unit
        .withTimeToLive(1.hour)         //Maximum retention time
        .withTimeToIdle(30.minutes)     //Maximum Unused Time
    val cachingSettings =
      defaultCachingSettings.withLfuCacheSettings(lfuCacheSettings)

    //Uri->key, RouteResult -> value
    val lfuCache = LfuCache[Uri,RouteResult](cachingSettings)

    //Example keyer for non-authenticated GET requests
    val simpleKeyer: PartialFunction[RequestContext, Uri] = {
      val isGet: RequestContext => Boolean = _.request.method == HttpMethods.GET
//      val isAuthorized: RequestContext => Boolean =
//        _.request.headers.exists(_.is(Authorization.lowercaseName))
      val result: PartialFunction[RequestContext, Uri] = {
        case r: RequestContext if isGet(r) => r.request.uri
      }
      result
    }

    val route = pathPrefix(pathName) {
      pathPrefix("getuserinfo") {
        (get & parameter('userid)) { userid => {
          alwaysCache(lfuCache,simpleKeyer) {
            onComplete(posRepo.futureUserInfo(userid)) {
              _ match {
                case Success(oui) => oui match {
                  case Some(ui) => complete(toJson(ui))
                  case None => complete(toJson(Map[String, Any](("TERMINALID" -> ""))))
                }
                case Failure(_) => complete(toJson(Map[String, Any](("TERMINALID" -> ""))))
              }
            }
          }
        }
        }
      } ~

Okay, I think it might be better to call cache.getOrLoad directly because akka-http is still changing and java8caffein should not be adjusted anymore.

Keywords: Scala SQL Database

Added by Fireglo on Mon, 11 Nov 2019 08:40:33 +0200