Business layer cache

[!TIP] This document is machine-translated by Google. If you find grammatical and semantic errors, and the document description is not clear, please PR

In the previous article Persistent Layer Cache, the db layer cache was introduced. In retrospect, the main design of the db layer cache can be summarized as follows:

  • The cache is only deleted but not updated
  • Only one row record is always stored, that is, the row record corresponding to the primary key
  • The unique index only caches the primary key value, not the row record directly (refer to the mysql index idea)
  • Anti-cache penetration design, one minute by default
  • Do not cache multi-line records

Preface

In a large-scale business system, by adding a cache to the persistence layer, for most single-line record queries, it is believed that the cache can help the persistence layer reduce a lot of access pressure, but in actual business, data reading is not just a single-line record. In the face of many multi-line records, this will also cause a lot of access pressure on the persistence layer. In addition, it is unrealistic to rely solely on the persistence layer for high-concurrency scenarios such as the spike system and the course selection system. In this section, we introduce the cache design in go-zero practice-biz cache.

Examples of applicable scenarios

  • subject system
  • Content social system
  • Spike…

Like these systems, we can add another layer of cache to the business layer to store key information in the system, such as the student selection information in the course selection system, the remaining number of courses in the course selection system, and the content information during a certain period of time in the content social system.

Next, let’s take an example of a content social system.

In the content social system, we generally query a batch of content lists first, and then click on a piece of content to view the details.

Before adding biz cache, the query flowchart of content information should be:

redis-cache-05

From the figure and the previous article [Persistence Layer Cache] (redis-cache.md), we can know that there is no way to get the content list to rely on the cache. If we add a layer of cache to the business layer to store the key information (or even the complete information) in the list, then access to multiple rows of records is no longer a problem, and this is what biz redis will do. Next, let’s take a look at the design plan, assuming that a single-line record in the content system contains the following fields.

Field Name Field Type Remarks
id string Content id
title string Title
content string Content
createTime time.Time Create time

Our goal is to obtain a batch of content lists, and try to avoid the access pressure caused by the content list going to the db. First, we use the sort set data structure of redis to store. The amount of field information that needs to be stored is based on two redis storage schemes:

  • Cache local information

    biz-redis-02 The key field information (such as id, etc.) is compressed and stored according to certain rules. For score, we use the createTime millisecond value (the time value is equal, not discussed here). The advantage of this storage scheme is to save redis storage space.

    On the other hand, the disadvantage is that the detailed content of the list needs to be checked back again (but this back check will use the row record cache of the persistence layer)

  • Cache complete information

    biz-redis-01 All published content will be stored after being compressed according to certain rules. For the same score, we still use the createTime millisecond value. The advantage of this storage solution is that business additions, deletions, checks, and changes are all reids, while the db layer is at this time.

    You don’t need to consider the row record cache. The persistence layer only provides data backup and recovery. On the other hand, its shortcomings are also obvious. The storage space and configuration requirements are higher, and the cost will increase.

Sample code

  1. type Content struct {
  2. Id string `json:"id"`
  3. Title string `json:"title"`
  4. Content string `json:"content"`
  5. CreateTime time.Time `json:"create_time"`
  6. }
  7. const bizContentCacheKey = `biz#content#cache`
  8. // AddContent provides content storage
  9. func AddContent(r redis.Redis, c *Content) error {
  10. v := compress(c)
  11. _, err := r.Zadd(bizContentCacheKey, c.CreateTime.UnixNano()/1e6, v)
  12. return err
  13. }
  14. // DelContent provides content deletion
  15. func DelContent(r redis.Redis, c *Content) error {
  16. v := compress(c)
  17. _, err := r.Zrem(bizContentCacheKey, v)
  18. return err
  19. }
  20. // Content compression
  21. func compress(c *Content) string {
  22. // todo: do it yourself
  23. var ret string
  24. return ret
  25. }
  26. // Content decompression
  27. func unCompress(v string) *Content {
  28. // todo: do it yourself
  29. var ret Content
  30. return &ret
  31. }
  32. // ListByRangeTime provides data query based on time period
  33. func ListByRangeTime(r redis.Redis, start, end time.Time) ([]*Content, error) {
  34. kvs, err := r.ZrangebyscoreWithScores(bizContentCacheKey, start.UnixNano()/1e6, end.UnixNano()/1e6)
  35. if err != nil {
  36. return nil, err
  37. }
  38. var list []*Content
  39. for _, kv := range kvs {
  40. data:=unCompress(kv.Key)
  41. list = append(list, data)
  42. }
  43. return list, nil
  44. }

In the above example, redis does not set an expiration time. We will synchronize the add, delete, modify, and check operations to redis. We think that the content social system has a relatively high list access request to do this scheme design. In addition, there are also some data visits. I did not expect the content design system to visit so frequently. It may be a sudden increase in visits within a certain period of time, and then it may be visited again for a long time. At this interval, In other words, I will not visit again. In this scenario, how should I consider the design of the cache? In the practice of go-zero content, there are two solutions to this problem:

  • Increased memory cache: The memory cache is used to store data that may have a large amount of sudden access. Commonly used storage schemes use map data structure to store. Map data storage is relatively simple to implement, but cache expiration processing needs to be increased The timer comes out, another solution is through Cache in the go-zero library, It is specialized Used for memory management.
  • Use biz redis and set a reasonable expiration time

Summary

The above two scenarios can contain most of the multi-line record cache. For scenarios where the query volume of multi-line records is not large, there is no need to put biz redis directly in it. You can try to let db take care of it first, and developers can monitor according to the persistence layer. And service Biz needs to be introduced when monitoring to measure.