Redis 实战 —— 03. Redis 简单实践 - Web应用 (3)

现在网站需要进行促销,促销商品数量固定,卖完即止。为了保证用户近实时看到促销商品及数量,又要保证不给数据库带来巨大压力,所以需要对促销商品的数据进行缓存。

可以用定时任务定期将需要缓存的数据更新到 Redis 中(其实对于促销等商品直接在缓存中进行相关库存扣减,既能保证实时数量,也能降低数据库压力,不过会存在热 key 问题)。由于不同的商品可能对实时性要求不一样,所以需要记录每个商品的更新周期和更新时间,分别存成哈希表和有序集合。 P31

为了让定时任务定期缓存数据,需要提供一个函数,以设置更新周期和更新时间。

// 毫秒转纳秒所需的倍数 const MILLI_2_NANO int64 = 1e6 // 更新数据缓存的更新周期(单位:ms)和更新时间(精确到毫秒) func UpdateItemCachedInfo(conn redis.Conn, itemId int, interval int) { _, _ = conn.Do("HSET", ITEM_INTERVAL, itemId, interval) _, _ = conn.Do("ZADD", ITEM_CACHED_TIME, time.Now().UnixNano() / MILLI_2_NANO, itemId) }

定时任务定时获取第一个需要更新的商品,若更新时间还未到,则等待下次执行。当更新周期不存在或者小于等于 0 时,表示不需要缓存,删除相关缓存;当更新周期大于等于 0 时,获取商品数据,并更新相关缓存。 P32

// 定时任务每 50ms 执行一次 const CACHE_ITEM_INTERVAL = 50 // 获取商品数据的 json串(随实际业务场景处理,此处不关心,默认 只含有itemId) func getItemJson(itemId int) string { return fmt.Sprintf("{\"id\":%d}", itemId) } // 缓存数据 // 内部死循环,可用 go 调用,当作定时任务 func CacheItem(conn redis.Conn) { for ; ; { // 获取第一个需要更新的商品(不考虑没有商品的情况) result, _ := redis.Ints(conn.Do("ZRANGE", ITEM_CACHED_TIME, 0, 0, "WITHSCORES")) itemId, itemCachedTime := result[0], result[1] // 如果当前时间还没到,则等下次执行 if int64(itemCachedTime) * MILLI_2_NANO > time.Now().UnixNano() { time.Sleep(CACHE_ITEM_INTERVAL * time.Millisecond) continue } // 获取更新周期 interval, _ := redis.Int(conn.Do("HGET", ITEM_INTERVAL, itemId)) itemKey := ITEM_PREFIX + RedisKey(strconv.Itoa(itemId)) // 如果更新周期 小于等于0,则移除相关缓存信息 if interval <= 0 { _, _ = conn.Do("HREM", ITEM_INTERVAL, itemId) _, _ = conn.Do("ZREM", ITEM_CACHED_TIME, itemId) _, _ = conn.Do("DELETE", itemKey) continue } // 如果更新周期 大于0,则还获取数据需要进行缓存 itemJson := getItemJson(itemId) _, _ = conn.Do("SET", itemKey, itemJson) _, _ = conn.Do("ZADD", ITEM_CACHED_TIME, time.Now().UnixNano() / MILLI_2_NANO + int64 (interval), itemId) } } 网页分析 P33

现在网站只想将 100 000 件商品中用户最经常浏览的 10 000 件商品缓存,所以需要记录每件商品的总浏览次数,并能够获取到浏览次数最多的 10 000 件商品,所以需要用有序集合进行存储记录。同时需要在 UpdateToken 加入增加次数的语句,更改后 UpdateToken 如下: P33

// 更新令牌相关信息(用户有操作是就会更新,如果当前操作是浏览商详页,则传入 itemId,否则 itemId <= 0) func UpdateToken(conn redis.Conn, token string, userId int, itemId int) { currentTime := time.Now().Unix() + int64(itemId) // 更新令牌及相应 userId 对应关系 _, _ = conn.Do("HSET", LOGIN_USER, token, userId) // 最近操作时间 记录 token 的时间戳 // (不能记录 userId 的时间戳,userId 不会变,所以即使 token 更新了, userId 对应的时间戳还是会更新,没法判断当前 token 是否过期) _, _ = conn.Do("ZADD", USER_LATEST_ACTION, currentTime, token) // 当前浏览的时商品详情页时,会传入 itemId,否则 itemId <= 0 if itemId > 0 { // 决定用 userId 当做后缀:token 可能会改变,而 userId 是唯一确定的 viewedItemKey := VIEWED_ITEM_PREFIX + RedisKey(strconv.Itoa(userId)) // 添加(更新)最近浏览商品信息 _, _ = conn.Do("ZADD", viewedItemKey, currentTime, itemId) // 移除时间戳升序状态下 [0, 倒数第 MAX_VIEWED_ITEM_COUNT + 1 个] 内的所有元素,留下最近的 MAX_VIEWED_ITEM_COUNT 个 _, _ = conn.Do("ZREMRANGEBYRANK", viewedItemKey, 0, -(MAX_VIEWED_ITEM_COUNT + 1)) // 每次浏览商详页是,都要增加当前商品的浏览量 _, _ = conn.Do("ZINCRBY", ITEM_VIEWED_NUM, 1, itemId) //【改动点】 } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpywxj.html