In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-04-11 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/03 Report--
This article focuses on "how go-zero automatically manages caches". Interested friends may wish to have a look at it. The method introduced in this paper is simple, fast and practical. Let's let the editor take you to learn how go-zero manages caches automatically.
Overview of go-zero
Although go-zero was open source on August 7, 20, it has been tested online on a large scale, and it is also the accumulation of nearly 20 years of engineering experience. After opening up, I got positive feedback from the community. In more than 5 months, I got 6k stars. He has repeatedly topped the github Go language daily, weekly and monthly lists, and won the gitee most valuable project (GVP), the most popular project of the year in open source China. At the same time, Wechat community is very active, a community of 3000 + people, and go-zero enthusiasts come together to exchange experiences and discuss problems in the use of go-zero.
How does go-zero automatically manage caches? Cache design principle
We only delete the cache and do not update it. Once the data in DB is modified, we will delete the corresponding cache directly instead of updating it.
Let's see how the order in which caches are deleted is correct.
Delete the cache before updating DB
In the case of two concurrent requests, request A needs to update the data, first delete the cache, and then request B to read the data. When there is no data in the cache, the data will be loaded from the DB and written back to the cache. Then A updates the DB, and the data in the cache will remain dirty until the cache expires or there is a request to update the data. As shown in the picture
Update the DB before deleting the cache
A request updates the DB first, and then B requests to read the data. At this time, the old data is returned. At this time, it can be considered that the A request has not been updated, and the final consistency is acceptable. Then A deletes the cache, and subsequent requests will get the latest data, as shown in the figure.
Let's take a look at the normal request process again:
The first request is to update DB and delete the cache
The second request reads the cache. If there is no data, it reads the data from DB and writes back to the cache.
Subsequent read requests can be read directly from the cache
Let's take another look at the DB query, assuming that there are seven columns of ABCDEFG data in the row record:
A request that queries only part of the column data, such as ABC,CDE or EFG, as shown in the figure
Query a single complete row record, as shown in the figure
Query some or all of the columns of multiple row records, as shown in the figure
For the above three cases, first of all, we do not need partial queries, because some queries cannot be cached. Once cached, the data is updated and it is impossible to locate which data needs to be deleted. Secondly, for multi-row queries, according to the actual scenario and needs, we will establish the corresponding mapping from query conditions to primary keys in the business layer. For queries with complete records in a single row, go-zero has built-in complete cache management. So the core principle is that what go-zero caches must be complete row records.
Let's describe the caching methods of the three scenarios built into go-zero in detail:
Caching based on primary key
PRIMARY KEY (`id`)
This is the easiest cache to handle, as long as you use primary key as key in redis to cache row records.
Caching based on unique index
In the design of index-based cache, I draw lessons from the design method of database index. in database design, if you look up data through the index, the engine will first find the primary key in the tree of the index-> primary key, and then query the row records through the primary key, that is, an indirect layer is introduced to solve the corresponding problem of index to row records. The same principle applies to go-zero 's cache design.
Index-based caching is divided into single-column unique indexes and multi-column unique indexes:
But for go-zero, single-column and multi-column are just different ways to generate cache key, and the control logic behind it is the same. Then go-zero 's built-in cache management can better control data consistency issues, while also built-in to prevent cache breakdown, penetration, and avalanche problems (which were discussed in detail when sharing at the gopherchina conference, see gopherchina sharing video later).
In addition, go-zero has built-in statistics of cache visits and access hit rates, as shown below:
Dbcache (sqlc)-qpm: 5057, hit_ratio: 99.7%, hit: 5044, miss: 13, db_fails: 0
You can see more detailed statistical information, which is convenient for us to analyze the usage of the cache. For cases where the cache hit rate is very low or the number of requests is very small, we can remove the cache, which can also reduce the cost.
The unique index for a single column is as follows:
UNIQUE KEY `product_ idx` (`product`)
The unique indexes for multiple columns are as follows:
UNIQUE KEY `vendor_product_ idx` (`vendor`, `product`)
Cache code interpretation 1. Cache logic based on primary key
The specific implementation code is as follows:
Func (cc CachedConn) QueryRow (v interface {}, key string, query QueryFn) error {return cc.cache.Take (v, key, func (v interface {}) error {return query (cc.db, v)})}
The Take method here is to get the data from the cache through key, and return it directly if you get it. If you can't get it, use the query method to DB to read the full row record and write it back to the cache, and then return the data. The whole logic is relatively simple and easy to understand.
Let's take a closer look at the implementation of Take:
Func (c cacheNode) Take (v interface {}, key string, query func (v interface {}) error) error {return c.doTake (v, key, query, func (v interface {}) error {return c.SetCache (key, v)})}
The logic of Take is as follows:
Use key to find data from the cache
If found, the data is returned
If you can't find it, use the query method to read the data
After reading it, call c.SetCache (key, v) to set the cache
The doTake code and explanation are as follows:
/ / v-data object to be read / / key-cache key// query-method used to read complete data from DB / / cacheVal-method used to write cache func (c cacheNode) doTake (v interface {}, key string, query func (v interface {}) error, cacheVal func (v interface {}) error) error {/ / use barrier to prevent cache breakdown Ensure that there is only one request in a process to load the data corresponding to key val, fresh, err: = c.barrier.DoEx (key, func () (interface {}, error) {/ / read data from cache if err: = c.doGetCache (key, v) Err! = nil {/ / if it is a pre-placed placeholder (to prevent cache penetration), then return the default errNotFound / / if it is an unknown error, then return directly, because we cannot give up the cache error and request DB directly / / this will kill the DB in high concurrency scenarios if err = = errPlaceholder {return nil, c.errNotFound} else if err! = c.errNotFound {/ / why we just return the error instead of query from db, / / because we don't allow the disaster pass to the DBs. / / fail fast, in case we bring down the dbs. Return nil, err} / / request DB / / if the returned error is errNotFound, then we need to set placeholder in the cache to prevent cache penetration if err = query (v); err = = c.errNotFound {if err = c.setCacheWithNotFound (key) Err! = nil {logx.Error (err)} return nil, c.errNotFound} else if err! = nil {/ / Statistics DB failed c.stat.IncrementDbFails () return nil, err} / / write data to cache if err = cacheVal (v) Err! = nil {logx.Error (err)}} / / returns json serialized data return jsonx.Marshal (v)}) if err! = nil {return err} if fresh {return nil} / / got the result from previous ongoing query c.stat.IncrementTotal () c.stat.IncrementHit () / / writes the data to the incoming v object return jsonx.Unmarshal (val. ([] byte)) V)} 2. Cache Logic based on unique Index
Because this piece is more complex, I use different colors to identify the code blocks and logic of the response. Block 2 is actually the same as primary key-based caching. Here we mainly talk about the logic of block 1.
The block 1 part of the code block is divided into two cases:
The primary key can be found from the cache through the index
At this point, we will directly use the primary key to follow the logic of block 2, followed by the cache logic based on the primary key above.
The primary key cannot be found from the cache through the index
Query the full row record from DB through the index. If there is an error, return
After the complete row record is found, the cache and index of the primary key to the full row record will be written to the cache of the primary key at the same time in redis
Return the required row record data
/ / v-data objects to be read / / key-cache key// keyer generated by index-method of generating key based on primary key cache with primary key / / indexQuery-method of reading complete data from DB with index Need to return primary key / / primaryQuery-method of getting complete data from DB with primary key func (cc CachedConn) QueryRowIndex (v interface {}, key string, keyer func (primary interface {}) string, indexQuery IndexQueryFn, primaryQuery PrimaryQueryFn) error {var primaryKey interface {} var found bool / / query cache through index first See if there is a cache if err of index to primary key: = cc.cache.TakeWithExpire (& primaryKey, key, func (val interface {}, expire time.Duration) (err error) {/ / if there is no cache of index to primary key, then query the complete data through index primaryKey, err = indexQuery (cc.db, v) if err! = nil {return} / / query the complete data through index, set found Instead of reading data from the cache found = true / / save the mapping of the primary key to the complete data to the cache, the TakeWithExpire method has saved the mapping of the index to the primary key to the cache return cc.cache.SetCacheWithExpire (keyer (primaryKey), v, expire+cacheSafeGapBetweenIndexAndPrimary)}) Err! = nil {return err} / / the data has been found through the index. You can simply return if found {return nil} / / read the data from the cache through the primary key. If the cache does not exist, use the primaryQuery method to read and write back the cache from DB and return the data return cc.cache.Take (v, keyer (primaryKey), func (v interface {}) error {return primaryQuery (cc.db, v, primaryKey)})}.
Let's look at a practical example.
Func (m * defaultUserModel) FindOneByUser (user string) (* User, error) {var resp User / / generate index-based key indexKey: = fmt.Sprintf ("% s% v", cacheUserPrefix, user) err: = m.QueryRowIndex (& resp, indexKey, / / generate full data cache key func (primary interface {}) string {return fmt.Sprintf ("user#%v", primary)} based on the primary key / / Index-based DB query method func (conn sqlx.SqlConn, v interface {}) (I interface {}, e error) {query: = fmt.Sprintf ("select% s from% s where user =? Limit 1 ", userRows, m.table) if err: = conn.QueryRow (& resp, query, user) Err! = nil {return nil, err} return resp.Id, nil}, / / DB query method based on primary key func (conn sqlx.SqlConn, v, primary interface {}) error {query: = fmt.Sprintf ("select% s from% s where id =?", userRows, m.table) return conn.QueryRow (& resp, query, primary)}) / / error handling We need to determine whether sqlc.ErrNotFound is returned. If so, we use the ErrNotFound defined in this package to return / / prevent users from perceiving whether the cache is being used and isolating switch err {case nil: return & resp, nil case sqlc.ErrNotFound: return nil, ErrNotFound default: return nil, err}} to this point. I believe you have a deeper understanding of "how go-zero automatically manages caches", so you might as well do it in practice. Here is the website, more related content can enter the relevant channels to inquire, follow us, continue to learn!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 269
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un