多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
--- 有个统计页面的页面, 数据是存在redis有序集合里, score 是日期, 成员是统计的结果。 现在遇到一个问题, 当天的统计结果需要每隔几秒要更新一次。 但是有序集合不能根据score去更新成员的值, 需要根据socre找到成员, 然后再删除成员,再添加回score 和成员。再删除成员期间,还未添加回更新的值时, 有客户端查询的话, 就会少看到一条统计结果, 这要怎么解决? ``` script := ` local zset_key = KEYS[1] local old_member = ARGV[1] local new_score = ARGV[2] local new_member = ARGV[3] -- 删除旧的成员 redis.call('ZREM', zset_key, old_member) -- 添加新的成员 redis.call('ZADD', zset_key, new_score, new_member) return true ` // 执行 Lua 脚本 _, err := g.Redis("cache4").Do("EVAL", script, 1, "your_zset_key", "old_member", updated_score, "new_member") if err != nil { fmt.Println("Error in Lua script:", err) } ``` ### 关键点 1. **原子性**:Lua 脚本在 Redis 内部执行时是原子操作,这意味着所有命令会连续执行,期间不会被其他操作打断。 2. **避免不一致性**:通过将 `ZREM` 和 `ZADD` 封装在同一个脚本中执行,可以避免删除成员期间有客户端查询到不完整数据的情况。 ### Lua 脚本的工作流程: 1. 先执行 `ZREM` 删除旧成员。 2. 然后执行 `ZADD` 添加新成员,确保删除和添加操作在同一个原子操作中执行。 由于 Lua 脚本执行期间没有任何其他命令能够干扰,因此你可以确保没有其他客户端会在删除和重新添加成员的时间点进行查询,从而避免数据不一致。 --- - 可视化工具: Redis Insight - Redis Desktop Manager --- - 删除一个score - DoVar("ZRANGEBYSCORE", "ladder:limit_up_count", score, score) 取出对应score的成员 - 删除成员 - DoVar("ZREM", "ladder:limit_up_count", delSCORE.Maps()[0]) --- - ZRANGEBYSCORE ladder:limit_up_count:1110 (20240911 +inf LIMIT 0 1 - ZREVRANGEBYSCORE ladder:limit_up_count:1110 (20240911 -inf LIMIT 0 1 ~~~ data1, err := g.Redis("cache4").DoVar("ZRANGEBYSCORE", "ladder:limit_up_count:"+binaryStr, "+inf", "("+score, "LIMIT", 0, 1) if err != nil { return nil, nil, nil, nil, err } data2, err := g.Redis("cache4").DoVar("ZREVRANGEBYSCORE", "ladder:limit_up_count:"+binaryStr, score, "-inf", "LIMIT", 0, 11) if err != nil { return nil, nil, nil, nil, err } ~~~ 在 Go 中,`float32` 类型的值可以是 `NaN`(Not-a-Number),表示一个未定义或无法表示的数字。这通常会出现在无效的数学操作中,例如: 1. 0 除以 0 (`0/0`) 2. 开平方负数 (`math.Sqrt(-1)`) 3. 对 `NaN` 进行数学操作 ### 检查 `NaN` 的情况 Go 提供了内置函数 `math.IsNaN` 来判断一个 `float32` 或 `float64` 值是否为 `NaN`。不过,对于 `float32` 你可以先将它转换为 `float64` 再使用此方法。 ### 例子: ~~~ go复制代码package main import ( "fmt" "math" ) func main() { // 引发 NaN 的情况 var a float32 = float32(0) / float32(0) fmt.Println("a:", a) // 检查是否是 NaN if math.IsNaN(float64(a)) { fmt.Println("a 是 NaN") } else { fmt.Println("a 不是 NaN") } } ~~~ ### 输出: ~~~ r复制代码a: NaN a 是 NaN ~~~ ### 触发 `NaN` 的常见情况: 1. **除以零:** ~~~ go复制代码var a float32 = 0 / 0 ~~~ 2. **负数的平方根:** ~~~ go复制代码var a float32 = float32(math.Sqrt(-1)) ~~~ 3. **`NaN` 传播:** 如果你对 `NaN` 进行任何数学操作,结果也会是 `NaN`。比如: ~~~ go复制代码var a float32 = float32(0) / float32(0) var b float32 = a + 1 // b 也是 NaN ~~~ 请注意,当进行一些无效数学运算时,`NaN` 是一个标准表示,程序本身不会崩溃或抛出异常,但计算结果会产生 `NaN`。 --- --- ~~~go type Person struct { age int } func main() { person := &Person{28} // 1. defer fmt.Println(person.age) // 2. defer func(p *Person) { fmt.Println(p.age) }(person) // 3. defer func() { fmt.Println(person.age) }() person.age = 29 } ~~~ ``` ### 答案解析: 参考答案及解析:29 29 28。变量 person 是一个指针变量 。 1.person.age 此时是将 28 当做 defer 函数的参数,会把 28 缓存在栈中,等到最后执行该 defer 语句的时候取出,即输出 28; 2.defer 缓存的是结构体 Person{28} 的地址,最终 Person{28} 的 age 被重新赋值为 29,所以 defer 语句最后执行的时候,依靠缓存的地址取出的 age 便是 29,即输出 29; 3.很简单,闭包引用,输出 29; 又由于 defer 的执行顺序为先进后出,即 3 2 1,所以输出 29 29 28。 ```