Duilgheadasan a thaobh giullachd cheistean baidse agus na fuasglaidhean aca (pàirt 1)

Duilgheadasan a thaobh giullachd cheistean baidse agus na fuasglaidhean aca (pàirt 1)Tha cha mhòr a h-uile bathar-bog ùr-nodha a 'gabhail a-steach grunn sheirbheisean. Gu tric, bidh amannan freagairt fada de shianalan eadar-sheirbheis nan adhbhar duilgheadasan coileanaidh. Is e am fuasgladh àbhaisteach don t-seòrsa duilgheadas seo grunn iarrtasan eadar-sheirbheis a phacadh ann an aon phasgan, ris an canar batching.

Ma chleachdas tu giollachd baidse, is dòcha nach bi thu toilichte leis na toraidhean a thaobh coileanadh no soilleireachd còd. Chan eil an dòigh seo cho furasta air an neach-fios mar a shaoileadh tu. Airson diofar adhbharan agus ann an diofar shuidheachaidhean, faodaidh fuasglaidhean atharrachadh gu mòr. A’ cleachdadh eisimpleirean sònraichte, seallaidh mi na buannachdan agus na h-eas-bhuannachdan a tha aig grunn dhòighean-obrach.

Pròiseact taisbeanaidh

Airson soilleireachd, leig dhuinn sùil a thoirt air eisimpleir de aon de na seirbheisean san tagradh air a bheil mi ag obair an-dràsta.

Mìneachadh air taghadh àrd-ùrlar airson eisimpleireanTha an duilgheadas a thaobh droch choileanadh gu math coitcheann agus chan eil e a’ toirt buaidh air cànanan no àrd-ùrlaran sònraichte. Cleachdaidh an artaigil seo eisimpleirean còd Spring + Kotlin gus duilgheadasan agus fuasglaidhean a nochdadh. Tha Kotlin a cheart cho furasta a thuigsinn (no do-thuigsinn) do luchd-leasachaidh Java agus C #, a bharrachd air an sin, tha an còd nas toinnte agus nas so-thuigsinn na ann an Java. Gus a dhèanamh nas fhasa a thuigsinn do luchd-leasachaidh fìor Java, seachnaidh mi draoidheachd dhubh Kotlin agus cha chleachd mi ach an draoidheachd gheal (ann an spiorad Lombok). Bidh beagan dhòighean leudachaidh ann, ach tha iad dha-rìribh eòlach air a h-uile prògramadair Java mar dhòighean statach, agus mar sin is e siùcar beag a bhios ann nach mill blas a’ mhias.
Tha seirbheis ceadachaidh sgrìobhainnean ann. Bidh cuideigin a’ cruthachadh sgrìobhainn agus ga chuir a-steach airson deasbaid, nuair a thèid deasachaidhean a dhèanamh, agus aig a’ cheann thall tha an sgrìobhainn air aontachadh. Chan eil fios aig an t-seirbheis ceadachaidh fhèin mu dheidhinn sgrìobhainnean: chan eil ann ach còmhradh de luchd-aontachaidh le gnìomhan beaga a bharrachd air nach bi sinn a’ beachdachadh an seo.

Mar sin, tha seòmraichean còmhraidh (a 'freagairt ri sgrìobhainnean) le seata ro-mhìnichte de chom-pàirtichean anns gach fear dhiubh. Mar a tha ann an còmhraidhean cunbhalach, tha teacsa agus faidhlichean ann am brathan agus faodaidh iad a bhith mar fhreagairtean no air adhart:

data class ChatMessage(
  // nullable так как появляется только после persist
  val id: Long? = null,
  /** Ссылка на автора */
  val author: UserReference,
  /** Сообщение */
  val message: String,
  /** Ссылки на аттачи */
  // из-за особенностей связки JPA+СУБД проще поддерживать и null, и пустые списки
  val files: List<FileReference>? = null,
  /** Если является ответом, то здесь будет оригинал */
  val replyTo: ChatMessage? = null,
  /** Если является пересылкой, то здесь будет оригинал */
  val forwardFrom: ChatMessage? = null
)

Tha ceanglaichean faidhle is cleachdaiche nan ceanglaichean gu raointean eile. Seo sinn a’ fuireach mar seo:

typealias FileReference Long
typealias UserReference Long

Tha dàta luchd-cleachdaidh air a stòradh ann an Keycloak agus air fhaighinn air ais tro REST. Tha an aon rud a’ dol airson faidhlichean: tha faidhlichean agus fiosrachadh meatain mun deidhinn beò ann an seirbheis stòraidh fhaidhlichean air leth.

Tha a h-uile gairm gu na seirbheisean sin iarrtasan troma. Tha seo a’ ciallachadh gu bheil an cosgais airson na h-iarrtasan sin a ghiùlan tòrr nas motha na an ùine a bheir e dhaibh a bhith air an giullachd le seirbheis treas-phàrtaidh. Air na beingean deuchainn againn, is e 100 ms an ùine gairm àbhaisteach airson a leithid de sheirbheisean, agus mar sin cleachdaidh sinn na h-àireamhan sin san àm ri teachd.

Feumaidh sinn rianadair REST sìmplidh a dhèanamh gus na teachdaireachdan N mu dheireadh fhaighinn leis a h-uile fiosrachadh riatanach. Is e sin, tha sinn den bheachd gu bheil am modal teachdaireachd cha mhòr an aon rud air beulaibh agus feumar a h-uile dàta a chuir. Is e an eadar-dhealachadh eadar am modal aghaidh aghaidh gum feum am faidhle agus an neach-cleachdaidh a bhith air an taisbeanadh ann an cruth beagan dì-chrioptaichte gus ceanglaichean a dhèanamh riutha:

/** В таком виде отдаются ссылки на сущности для фронта */
data class ReferenceUI(
  /** Идентификатор для url */
  val ref: String,
  /** Видимое пользователю название ссылки */
  val name: String
)
data class ChatMessageUI(
  val id: Long,
  /** Ссылка на автора */
  val author: ReferenceUI,
  /** Сообщение */
  val message: String,
  /** Ссылки на аттачи */
  val files: List<ReferenceUI>,
  /** Если являтся ответом, то здесь будет оригинал */
  val replyTo: ChatMessageUI? = null,
  /** Если являтся пересылкой, то здесь будет оригинал */
  val forwardFrom: ChatMessageUI? = null
)

Feumaidh sinn na leanas a chur an gnìomh:

interface ChatRestApi {
  fun getLast(nInt): List<ChatMessageUI>
}

Tha postfix UI a’ ciallachadh modalan DTO airson an aghaidh, is e sin, na bu chòir dhuinn a fhrithealadh tro REST.

Is e an rud a dh’ fhaodadh a bhith na iongnadh an seo nach eil sinn a ’dol seachad air ID cabadaich agus chan eil eadhon aig a’ mhodal ChatMessage / ChatMessageUI. Rinn mi seo a dh’aona ghnothach gus nach biodh mi a’ bearradh còd nan eisimpleirean (tha na cathraichean iomallach, agus mar sin faodaidh sinn gabhail ris nach eil againn ach aon).

Iomradh feallsanachailBidh an dà chuid an clas ChatMessageUI agus an dòigh ChatRestApi.getLast a’ cleachdadh an seòrsa dàta Liosta nuair a tha e dha-rìribh na sheata òrdaichte. Tha seo dona anns an JDK, agus mar sin chan obraich a bhith ag innse òrdugh nan eileamaidean aig ìre an eadar-aghaidh (gleidheadh ​​​​an òrdugh nuair a chuireas tu ris agus a bheir air falbh). Mar sin tha e air a thighinn gu bhith na chleachdadh cumanta Liosta a chleachdadh ann an cùisean far a bheil feum air seata òrdaichte (tha LinkedHashSet ann cuideachd, ach chan e eadar-aghaidh a tha seo).
Cuingealachadh cudromach: Gabhaidh sinn ris nach eil slabhraidhean fada de fhreagairtean no gluasadan ann. Is e sin, tha iad ann, ach chan eil an fhaid aca nas fhaide na trì teachdaireachdan. Feumar an t-sreath iomlan de theachdaireachdan a chuir chun aghaidh.

Airson dàta fhaighinn bho sheirbheisean taobh a-muigh tha na APIan a leanas ann:

interface ChatMessageRepository {
  fun findLast(nInt): List<ChatMessage>
}
data class FileHeadRemote(
  val id: FileReference,
  val name: String
)
interface FileRemoteApi {
  fun getHeadById(idFileReference): FileHeadRemote
  fun getHeadsByIds(idSet<FileReference>): Set<FileHeadRemote>
  fun getHeadsByIds(idList<FileReference>): List<FileHeadRemote>
  fun getHeadsByChat(): List<FileHeadRemote>
}
data class UserRemote(
  val id: UserReference,
  val name: String
)
interface UserRemoteApi {
  fun getUserById(idUserReference): UserRemote
  fun getUsersByIds(idSet<UserReference>): Set<UserRemote>
  fun getUsersByIds(idList<UserReference>): List<UserRemote>
}

Chìthear gu bheil seirbheisean taobh a-muigh an toiseach a 'solarachadh airson giollachd baidse, agus anns an dà dhreach: tro Set (gun a bhith a' gleidheadh ​​​​òrdugh nan eileamaidean, le iuchraichean sònraichte) agus tro Liosta (dh'fhaodadh gum bi dùblaidhean ann - tha an òrdugh air a ghleidheadh).

Cur an gnìomh sìmplidh

Cur an gnìomh naive

Bidh a’ chiad buileachadh naive den rianadair REST againn a’ coimhead rudeigin mar seo sa mhòr-chuid de chùisean:

class ChatRestController(
  private val messageRepository: ChatMessageRepository,
  private val userRepository: UserRemoteApi,
  private val fileRepository: FileRemoteApi
) : ChatRestApi {
  override fun getLast(nInt) =
    messageRepository.findLast(n)
      .map it.toFrontModel() }
  
  private fun ChatMessage.toFrontModel(): ChatMessageUI =
    ChatMessageUI(
      id = id ?: throw IllegalStateException("$this must be persisted"),
      author = userRepository.getUserById(author).toFrontReference(),
      message = message,
      files = files?.let files ->
        fileRepository.getHeadsByIds(files)
          .map it.toFrontReference() }
      } ?: listOf(),
      forwardFrom = forwardFrom?.toFrontModel(),
      replyTo = replyTo?.toFrontModel()
    )
}

Tha a h-uile dad gu math soilleir, agus tha seo na bhuannachd mhòr.

Bidh sinn a’ cleachdadh giullachd baidse agus a’ faighinn dàta bho sheirbheis bhon taobh a-muigh ann an baidsean. Ach dè thachras don toradh againn?

Airson gach teachdaireachd, thèid aon ghairm gu UserRemoteApi gus dàta fhaighinn air raon an ùghdair agus aon ghairm gu FileRemoteApi gus a h-uile faidhle ceangailte fhaighinn. Tha e coltach gur e sin e. Canaidh sinn gum faighear na raointean forwardFrom agus replyTo airson ChatMessage ann an dòigh nach fheum seo fiosan neo-riatanach. Ach le bhith gan tionndadh gu ChatMessageUI leanaidh sin gu ath-chuairteachadh, is e sin, faodaidh cunntairean fios àrdachadh gu mòr. Mar a thuirt sinn na bu thràithe, gabhamaid ris nach eil mòran neadachaidh againn agus tha an t-seine cuingealaichte ri trì teachdaireachdan.

Mar thoradh air an sin, gheibh sinn bho dhà gu sia fiosan gu seirbheisean taobh a-muigh gach teachdaireachd agus aon ghairm JPA airson a’ phacaid iomlan de theachdaireachdan. Bidh an àireamh de ghairmean ag atharrachadh bho 2*N+1 gu 6*N+1. Dè an ìre a tha seo ann an aonadan fìor? Canaidh sinn gu bheil e a’ toirt 20 teachdaireachd airson duilleag a thoirt seachad. Gus am faighinn, bheir e bho 4 s gu 10 s. Uamhasach! Bu mhath leam a chumail taobh a-staigh 500 ms. Agus leis gu robh iad a ’bruadar mu bhith a’ dèanamh scrollaidh gun fhiosta air an aghaidh, faodar na riatanasan coileanaidh airson a ’phuing crìochnachaidh seo a dhùblachadh.

Pros:

  1. Tha an còd pongail agus fèin-sgrìobhte (bruadar sgioba taic).
  2. Tha an còd sìmplidh, agus mar sin cha mhòr nach eil cothroman ann losgadh ort fhèin sa chas.
  3. Chan eil giullachd baidse a’ coimhead coltach ri rudeigin coimheach agus tha e air amalachadh gu h-organach a-steach don loidsig.
  4. Thèid atharrachaidhean loidsig a dhèanamh gu furasta agus bidh iad ionadail.

Minus:

Coileanadh uamhasach mar thoradh air pacaidean glè bheag.

Tha an dòigh-obrach seo ri fhaicinn gu math tric ann an seirbheisean sìmplidh no ann am prototypes. Ma tha astar dèanamh atharrachaidhean cudromach, cha mhòr gum b’ fhiach an siostam a dhèanamh nas duilghe. Aig an aon àm, airson ar seirbheis gu math sìmplidh tha an coileanadh uamhasach, agus mar sin tha farsaingeachd iomchaidheachd an dòigh-obrach seo gu math cumhang.

Naive giollachd co-shìnte

Faodaidh tu tòiseachadh air a h-uile teachdaireachd a ghiullachd ann an co-shìnte - leigidh seo dhut faighinn cuidhteas an àrdachadh sreathach ann an ùine a rèir an àireamh de theachdaireachdan. Chan e slighe sònraichte math a tha seo oir thig e gu eallach mòr stùc air an t-seirbheis a-muigh.

Tha buileachadh giollachd co-shìnte gu math sìmplidh:

override fun getLast(nInt) =
  messageRepository.findLast(n).parallelStream()
    .map it.toFrontModel() }
    .collect(toList())

A’ cleachdadh giullachd teachdaireachd co-shìnte, gheibh sinn 300–700 ms mas fheàrr, a tha tòrr nas fheàrr na le buileachadh naive, ach nach eil fhathast luath gu leòr.

Leis an dòigh-obrach seo, thèid iarrtasan gu userRepository agus fileRepository a chuir gu bàs gu sioncronaich, rud nach eil gu math èifeachdach. Gus seo a chàradh, feumaidh tu an loidsig gairm atharrachadh gu mòr. Mar eisimpleir, tro CompletionStage (aka CompletableFuture):

private fun ChatMessage.toFrontModel(): ChatMessageUI =
  CompletableFuture.supplyAsync {
    userRepository.getUserById(author).toFrontReference()
  }.thenCombine(
    files?.let {
      CompletableFuture.supplyAsync {
        fileRepository.getHeadsByIds(files).map it.toFrontReference() }
      }
    } ?: CompletableFuture.completedFuture(listOf())
  ) authorfiles ->
    ChatMessageUI(
      id = id ?: throw IllegalStateException("$this must be persisted"),
      author = author,
      message = message,
      files = files,
      forwardFrom = forwardFrom?.toFrontModel(),
      replyTo = replyTo?.toFrontModel()
    )
  }.get()!!

Chithear nach eil an còd mapaidh sìmplidh an toiseach air fàs cho furasta a thuigsinn. Tha seo air sgàth gu robh againn ri fiosan gu seirbheisean taobh a-muigh a sgaradh bhon àite far a bheilear a’ cleachdadh nan toraidhean. Chan eil seo ann fhèin dona. Ach chan eil a bhith a’ cothlamadh gairmean a’ coimhead gu math eireachdail agus tha e coltach ri “noodle” reactive àbhaisteach.

Ma chleachdas tu coroutines, seallaidh a h-uile dad nas reusanta:

private fun ChatMessage.toFrontModel(): ChatMessageUI =
  join(
    userRepository.getUserById(author).toFrontReference() },
    files?.let fileRepository.getHeadsByIds(files)
      .map it.toFrontReference() } } ?: listOf() }
  ).let (author, files) ->
    ChatMessageUI(
      id = id ?: throw IllegalStateException("$this must be persisted"),
      author = author,
      message = message,
      files = files,
      forwardFrom = forwardFrom?.toFrontModel(),
      replyTo = replyTo?.toFrontModel()
    )
  }

Càite:

fun <ABjoin(a: () -> Ab: () -> B) =
  runBlocking(IO{
    awaitAll(async a() }async b() })
  }.let {
    it[0as to it[1as B
  }

Gu teòiridheach, a 'cleachdadh a leithid de ghiollachd co-shìnte, gheibh sinn 200-400 ms, a tha mar-thà faisg air na bha sinn an dùil.

Gu mì-fhortanach, chan eil an leithid de cho-shìnteadh math ann, agus tha a 'phrìs ri phàigheadh ​​​​gu math cruaidh: le dìreach beagan luchd-cleachdaidh ag obair aig an aon àm, bidh casg de dh' iarrtasan a 'tuiteam air na seirbheisean, nach tèid a phròiseasadh ann an co-shìnte co-dhiù, mar sin bidh sinn tillidh sinn gu ar bròn 4 s.

Is e an toradh a th’ agam nuair a bhios mi a’ cleachdadh a leithid de sheirbheis 1300–1700 ms airson 20 teachdaireachd a ghiullachd. Tha seo nas luaithe na anns a 'chiad bhuileachadh, ach chan eil e fhathast a' fuasgladh na duilgheadas.

Cleachdaidhean eile de cheistean co-shìnteDè mura h-eil seirbheisean treas-phàrtaidh a’ toirt seachad giollachd baidse? Mar eisimpleir, faodaidh tu falach a dhèanamh air dìth buileachadh giollachd baidse taobh a-staigh dòighean eadar-aghaidh:

interface UserRemoteApi {
  fun getUserById(idUserReference): UserRemote
  fun getUsersByIds(idSet<UserReference>): Set<UserRemote> =
    id.parallelStream()
      .map getUserById(it}.collect(toSet())
  fun getUsersByIds(idList<UserReference>): List<UserRemote> =
    id.parallelStream()
      .map getUserById(it}.collect(toList())
}

Tha seo a’ dèanamh ciall ma tha thu an dòchas giollachd baidse fhaicinn ann an dreachan san àm ri teachd.
Pros:

  1. Cuir an gnìomh giollachd co-shìnte stèidhichte air teachdaireachd gu furasta.
  2. Deagh scalability.

Cons:

  1. An fheum air togail dàta a sgaradh bhon ghiullachd aige nuair a thathar a’ giullachd iarrtasan gu diofar sheirbheisean aig an aon àm.
  2. Meudachadh eallach air seirbheisean treas-phàrtaidh.

Chithear gu bheil an raon cleachdaidh timcheall air an aon rud ris an dòigh-obrach naive. Tha e ciallach an dòigh iarrtas co-shìnte a chleachdadh ma tha thu airson coileanadh na seirbheis agad àrdachadh grunn thursan air sgàth cleachdadh tròcaireach dhaoine eile. Anns an eisimpleir againn, mheudaich coileanadh 2,5 tursan, ach tha e soilleir nach eil seo gu leòr.

caching

Faodaidh tu tasgadh a dhèanamh ann an spiorad JPA airson seirbheisean taobh a-muigh, is e sin, stòradh stuthan a fhuaireadh taobh a-staigh seisean gus nach faigh thu a-rithist iad (a ’toirt a-steach rè giollachd baidse). Faodaidh tu na caches sin a dhèanamh thu fhèin, faodaidh tu Spring a chleachdadh leis an @Cacheable aige, agus faodaidh tu an-còmhnaidh tasgadan deiseil mar EhCache a chleachdadh le làimh.

Is e duilgheadas cumanta a bhiodh ann nach bi caches feumail ach ma tha buillean aca. Anns a ’chùis againn, tha coltas ann gu bheil buillean air raon an ùghdair (canaidh sinn, 50%), ach cha bhith buillean sam bith air faidhlichean idir. Bheir an dòigh-obrach seo beagan leasachaidhean, ach cha toir e atharrachadh mòr air coileanadh (agus tha feum againn air adhartas).

Feumaidh caches eadar-sheisean (fada) loidsig mì-dhligheachaidh iom-fhillte. San fharsaingeachd, mar as fhaide a gheibh thu sìos gu bhith a’ fuasgladh dhuilgheadasan coileanaidh le bhith a’ cleachdadh caches eadar-seisean, ’s ann as fheàrr.

Pros:

  1. Cuir an gnìomh caching gun a bhith ag atharrachadh còd.
  2. Meudachadh cinneasachd grunn thursan (ann an cuid de chùisean).

Cons:

  1. Comasach air coileanadh nas ìsle ma thèid a chleachdadh gu ceàrr.
  2. Cuimhne mòr os an cionn, gu sònraichte le caches fada.
  3. Neo-dhligheachadh iom-fhillte, mearachdan anns am bi duilgheadasan duilich ath-riochdachadh ann an ùine ruith.

Glè thric, bidh caches air an cleachdadh a-mhàin gus duilgheadasan dealbhaidh fhuasgladh gu sgiobalta. Chan eil seo a 'ciallachadh nach bu chòir dhaibh a bhith air an cleachdadh. Ach, bu chòir dhut an-còmhnaidh dèiligeadh riutha gu faiceallach agus an toiseach measadh a dhèanamh air a’ bhuannachd coileanaidh a thig às, agus dìreach an uairsin co-dhùnadh a dhèanamh.

Anns an eisimpleir againn, bheir caches àrdachadh coileanaidh timcheall air 25%. Aig an aon àm, tha mòran eas-bhuannachdan aig caches, agus mar sin cha bhithinn gan cleachdadh an seo.

Builean

Mar sin, choimhead sinn air buileachadh naive de sheirbheis a bhios a’ cleachdadh giullachd baidse, agus cuid de dhòighean sìmplidh air a luathachadh.

Is e prìomh bhuannachd nan dòighean sin uile sìmplidheachd, às a bheil mòran de bhuaidhean tlachdmhor.

Is e duilgheadas cumanta leis na dòighean sin droch choileanadh, gu sònraichte air sgàth meud nam pacaidean. Mar sin, mura h-eil na fuasglaidhean sin freagarrach dhut, is fhiach beachdachadh air dòighean nas radaigeach.

Tha dà phrìomh stiùireadh anns an urrainn dhut coimhead airson fuasglaidhean:

  • obair asyncronach le dàta (feumar gluasad paradigm, mar sin chan eil e air a dheasbad san artaigil seo);
  • meudachadh batches fhad ‘s a chumas iad giollachd sioncronaich.

Leudachadh batches lughdaichidh e gu mòr an àireamh de ghairmean bhon taobh a-muigh agus aig an aon àm cùm an còd sioncronaich. Thèid an ath phàirt den artaigil a thoirt don chuspair seo.

Source: www.habr.com

Cuir beachd ann