In the first two articles, we introduced various best practices for cache use. First, we introduced the basic posture of cache use, how to use the cache automatically generated by go-zero and how to write the cache code in the logic code. Solutions to common problems such as cache penetration, breakdown, avalanche, etc., and finally explain how to ensure cache consistency. Because caching is so important for high-concurrency services, we will continue to learn about caching together in this article.

local cache

When we encounter extreme hot data queries, we must consider local caching at this time. The hotspot local cache is mainly deployed in the code of the application server to block the pressure of the hotspot query on distributed caches or databases such as Redis.

In our mall, some advertised products or recommended products will be placed in the homepage Banner, and the information of these products will be entered and changed by the operation in the management background. The request volume of these items is very large, and even Redis is difficult to handle, so here we can use local cache to optimize.

First create a product operation table product_operation in the product library. In order to simplify only the necessary fields, product_id is the product id of the promotion and operation, and status is the status of the operating product. When the status is 1, the product will be displayed in the homepage Banner.

 CREATE TABLE `product_operation` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `product_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT '商品id',
  `status` int NOT NULL DEFAULT '1' COMMENT '运营商品状态 0-下线 1-上线',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `ix_update_time` (`update_time`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 COMMENT='商品运营表';

The implementation of local cache is relatively simple. We can use map to implement it ourselves. Cache is provided in go-zero's collection to implement the function of local cache. We use it directly. It is never a wise choice to repeatedly build wheels. LocalCacheExpire is Local cache expiration time, Cache provides Get and Set methods, which are very simple to use

 localCache, err := collection.NewCache(localCacheExpire)

Look up from the local cache first, and return directly if the cache is hit. If there is no cache hit, you need to query the operation bit commodity id from the database, then aggregate the commodity information, and finally put it back into the local cache. The detailed code logic is as follows:

 func (l *OperationProductsLogic) OperationProducts(in *product.OperationProductsRequest) (*product.OperationProductsResponse, error) {
  opProducts, ok := l.svcCtx.LocalCache.Get(operationProductsKey)
  if ok {
    return &product.OperationProductsResponse{Products: opProducts.([]*product.ProductItem)}, nil
  }

  pos, err := l.svcCtx.OperationModel.OperationProducts(l.ctx, validStatus)
  if err != nil {
    return nil, err
  }
  var pids []int64
  for _, p := range pos {
    pids = append(pids, p.ProductId)
  }
  products, err := l.productListLogic.productsByIds(l.ctx, pids)
  if err != nil {
    return nil, err
  }
  var pItems []*product.ProductItem
  for _, p := range products {
    pItems = append(pItems, &product.ProductItem{
      ProductId: p.Id,
      Name:      p.Name,
    })
  }
  l.svcCtx.LocalCache.Set(operationProductsKey, pItems)
  return &product.OperationProductsResponse{Products: pItems}, nil
}

Use the grpurl debugging tool to request the interface. After the cache miss is requested for the first time, subsequent requests will hit the local cache. When the local cache expires, it will return to the source DB to load the data into the local cache.

 ~ grpcurl -plaintext -d '{}' 127.0.0.1:8081 product.Product.OperationProducts
{
  "products": [
    {
      "productId": "32",
      "name": "电风扇6"
    },
    {
      "productId": "31",
      "name": "电风扇5"
    },
    {
      "productId": "33",
      "name": "电风扇7"
    }
  ]
}

Note that not all information is applicable to the local cache. The feature of the local cache is that the request volume is very high, and at the same time, certain inconsistencies can be allowed in the business, because the local cache generally does not actively update the operation, and needs to wait until it expires to return to the source db Update later. So in the business, it depends on the situation to see whether you need to use local cache.

Automatically identify hotspot data

The homepage banner scene is configured by the operator, that is, we can know the possible hot data in advance, but in some cases, we cannot predict that the data will become a hot spot in advance. Therefore, we need to be able to adaptively and automatically identify these hot data, and then promote these data to local cache.

We maintain a sliding window. For example, if the sliding window is set to 10s, we want to count which keys are frequently accessed within these 10s. A sliding window corresponds to multiple buckets, each bucket corresponds to a map, and the key of the map is the product of the product. id, value is the number of requests corresponding to the product. Then we can regularly (such as 10s) to count the data of the keys in all the current Buckets, and then import these data into the big top heap, and easily get the topK key from the big top heap, we can set a threshold, For example, if a key is accessed more than 500 times within a sliding window, the key is considered to be a hot key, and the key is automatically upgraded to a local cache.

Cache usage tips

Here are some tips for using cache

  • The name of the key should be as easy to read as possible, that is, the name should be known, and the length should be as small as possible under the premise of being easy to read, so as to reduce the occupation of resources. For the value, you can use int, but try not to use string. value, there is a shared_object cache inside redis.
  • When redis uses hash, the key is split. The same hash key will fall on the same redis node. If the hash is too large, it will lead to uneven distribution of memory and requests. Consider splitting the hash into smaller ones. hash, so that the node memory evenly avoids single node request hot spots.
  • In order to avoid non-existent data requests, each request will be cached and miss directly to the database, and an empty cache will be set.
  • When objects need to be stored in the cache, use protobuf for serialization as much as possible to reduce the data size as much as possible.
  • When adding data, make sure that the cache must exist before adding it, and use Expire to determine whether the cache exists.
  • For the needs of storing daily login scenarios, BITSET can be used. To avoid a single BITSET being too large or hot, sharding can be performed.
  • When using sorted set, avoid using zrange or zrevrange to return a set that is too large, which is more complicated.
  • Try to use PIPELINE when performing cache operations, but also pay attention to avoid too large collections.
  • Avoid oversized values.
  • Cache as much as possible to set the expiration time.
  • Be careful to use full-scale operation commands, such as HGETALL of Hash type, SMEMBERS of Set type, etc. These operations will perform a full scan of the underlying data structures of Hash and Set. If the amount of data is large, the main thread of Redis will be blocked.
  • To obtain the full amount of data of a collection type, you can use commands such as SSCAN and HSCAN to return the data in the collection in batches to reduce the blocking of the main thread.
  • Use the MONITOR command with caution. The MONITOR command will continuously write the monitored content into the output buffer. If there are many online command operations, the output buffer will soon overflow, which will affect the performance of Redis.
  • Commands such as KEYS, FLUSHALL, and FLUSHDB are disabled in the production environment.

concluding remarks

This article describes how to use the local hotspot cache to deal with ultra-high requests. The hotspot cache is divided into known hotspot caches and unknown hotspot caches. The known hotspot cache is relatively simple. It can be loaded from the database into the memory in advance. For the unknown hotspot cache, we need to adaptively identify the hotspot data, and then upgrade the hotspot data to the local cache. Finally, some tips for using cache in actual production are introduced, which should be used flexibly in the production environment to avoid problems.

Hope this article is helpful to you, thank you.

Updated every Monday and Thursday

Code repository: https://github.com/zhoushuguang/lebron

project address

https://github.com/zeromicro/go-zero

Welcome go-zero and star support us!

WeChat exchange group

Follow the official account of " Microservice Practice " and click on the exchange group to get the QR code of the community group.


kevinwan
931 声望3.5k 粉丝

go-zero作者