github.com/lingyao2333/mo-zero@v1.4.1/core/stores/cache/cache_test.go (about) 1 package cache 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "math" 9 "strconv" 10 "testing" 11 "time" 12 13 "github.com/lingyao2333/mo-zero/core/errorx" 14 "github.com/lingyao2333/mo-zero/core/hash" 15 "github.com/lingyao2333/mo-zero/core/stores/redis" 16 "github.com/lingyao2333/mo-zero/core/stores/redis/redistest" 17 "github.com/lingyao2333/mo-zero/core/syncx" 18 "github.com/stretchr/testify/assert" 19 ) 20 21 var _ Cache = (*mockedNode)(nil) 22 23 type mockedNode struct { 24 vals map[string][]byte 25 errNotFound error 26 } 27 28 func (mc *mockedNode) Del(keys ...string) error { 29 return mc.DelCtx(context.Background(), keys...) 30 } 31 32 func (mc *mockedNode) DelCtx(_ context.Context, keys ...string) error { 33 var be errorx.BatchError 34 35 for _, key := range keys { 36 if _, ok := mc.vals[key]; !ok { 37 be.Add(mc.errNotFound) 38 } else { 39 delete(mc.vals, key) 40 } 41 } 42 43 return be.Err() 44 } 45 46 func (mc *mockedNode) Get(key string, val interface{}) error { 47 return mc.GetCtx(context.Background(), key, val) 48 } 49 50 func (mc *mockedNode) GetCtx(ctx context.Context, key string, val interface{}) error { 51 bs, ok := mc.vals[key] 52 if ok { 53 return json.Unmarshal(bs, val) 54 } 55 56 return mc.errNotFound 57 } 58 59 func (mc *mockedNode) IsNotFound(err error) bool { 60 return errors.Is(err, mc.errNotFound) 61 } 62 63 func (mc *mockedNode) Set(key string, val interface{}) error { 64 return mc.SetCtx(context.Background(), key, val) 65 } 66 67 func (mc *mockedNode) SetCtx(ctx context.Context, key string, val interface{}) error { 68 data, err := json.Marshal(val) 69 if err != nil { 70 return err 71 } 72 73 mc.vals[key] = data 74 return nil 75 } 76 77 func (mc *mockedNode) SetWithExpire(key string, val interface{}, expire time.Duration) error { 78 return mc.SetWithExpireCtx(context.Background(), key, val, expire) 79 } 80 81 func (mc *mockedNode) SetWithExpireCtx(ctx context.Context, key string, val interface{}, expire time.Duration) error { 82 return mc.Set(key, val) 83 } 84 85 func (mc *mockedNode) Take(val interface{}, key string, query func(val interface{}) error) error { 86 return mc.TakeCtx(context.Background(), val, key, query) 87 } 88 89 func (mc *mockedNode) TakeCtx(ctx context.Context, val interface{}, key string, query func(val interface{}) error) error { 90 if _, ok := mc.vals[key]; ok { 91 return mc.GetCtx(ctx, key, val) 92 } 93 94 if err := query(val); err != nil { 95 return err 96 } 97 98 return mc.SetCtx(ctx, key, val) 99 } 100 101 func (mc *mockedNode) TakeWithExpire(val interface{}, key string, query func(val interface{}, expire time.Duration) error) error { 102 return mc.TakeWithExpireCtx(context.Background(), val, key, query) 103 } 104 105 func (mc *mockedNode) TakeWithExpireCtx(ctx context.Context, val interface{}, key string, query func(val interface{}, expire time.Duration) error) error { 106 return mc.Take(val, key, func(val interface{}) error { 107 return query(val, 0) 108 }) 109 } 110 111 func TestCache_SetDel(t *testing.T) { 112 const total = 1000 113 r1, clean1, err := redistest.CreateRedis() 114 assert.Nil(t, err) 115 defer clean1() 116 r2, clean2, err := redistest.CreateRedis() 117 assert.Nil(t, err) 118 defer clean2() 119 conf := ClusterConf{ 120 { 121 RedisConf: redis.RedisConf{ 122 Host: r1.Addr, 123 Type: redis.NodeType, 124 }, 125 Weight: 100, 126 }, 127 { 128 RedisConf: redis.RedisConf{ 129 Host: r2.Addr, 130 Type: redis.NodeType, 131 }, 132 Weight: 100, 133 }, 134 } 135 c := New(conf, syncx.NewSingleFlight(), NewStat("mock"), errPlaceholder) 136 for i := 0; i < total; i++ { 137 if i%2 == 0 { 138 assert.Nil(t, c.Set(fmt.Sprintf("key/%d", i), i)) 139 } else { 140 assert.Nil(t, c.SetWithExpire(fmt.Sprintf("key/%d", i), i, 0)) 141 } 142 } 143 for i := 0; i < total; i++ { 144 var val int 145 assert.Nil(t, c.Get(fmt.Sprintf("key/%d", i), &val)) 146 assert.Equal(t, i, val) 147 } 148 assert.Nil(t, c.Del()) 149 for i := 0; i < total; i++ { 150 assert.Nil(t, c.Del(fmt.Sprintf("key/%d", i))) 151 } 152 for i := 0; i < total; i++ { 153 var val int 154 assert.True(t, c.IsNotFound(c.Get(fmt.Sprintf("key/%d", i), &val))) 155 assert.Equal(t, 0, val) 156 } 157 } 158 159 func TestCache_OneNode(t *testing.T) { 160 const total = 1000 161 r, clean, err := redistest.CreateRedis() 162 assert.Nil(t, err) 163 defer clean() 164 conf := ClusterConf{ 165 { 166 RedisConf: redis.RedisConf{ 167 Host: r.Addr, 168 Type: redis.NodeType, 169 }, 170 Weight: 100, 171 }, 172 } 173 c := New(conf, syncx.NewSingleFlight(), NewStat("mock"), errPlaceholder) 174 for i := 0; i < total; i++ { 175 if i%2 == 0 { 176 assert.Nil(t, c.Set(fmt.Sprintf("key/%d", i), i)) 177 } else { 178 assert.Nil(t, c.SetWithExpire(fmt.Sprintf("key/%d", i), i, 0)) 179 } 180 } 181 for i := 0; i < total; i++ { 182 var val int 183 assert.Nil(t, c.Get(fmt.Sprintf("key/%d", i), &val)) 184 assert.Equal(t, i, val) 185 } 186 assert.Nil(t, c.Del()) 187 for i := 0; i < total; i++ { 188 assert.Nil(t, c.Del(fmt.Sprintf("key/%d", i))) 189 } 190 for i := 0; i < total; i++ { 191 var val int 192 assert.True(t, c.IsNotFound(c.Get(fmt.Sprintf("key/%d", i), &val))) 193 assert.Equal(t, 0, val) 194 } 195 } 196 197 func TestCache_Balance(t *testing.T) { 198 const ( 199 numNodes = 100 200 total = 10000 201 ) 202 dispatcher := hash.NewConsistentHash() 203 maps := make([]map[string][]byte, numNodes) 204 for i := 0; i < numNodes; i++ { 205 maps[i] = map[string][]byte{ 206 strconv.Itoa(i): []byte(strconv.Itoa(i)), 207 } 208 } 209 for i := 0; i < numNodes; i++ { 210 dispatcher.AddWithWeight(&mockedNode{ 211 vals: maps[i], 212 errNotFound: errPlaceholder, 213 }, 100) 214 } 215 216 c := cacheCluster{ 217 dispatcher: dispatcher, 218 errNotFound: errPlaceholder, 219 } 220 for i := 0; i < total; i++ { 221 assert.Nil(t, c.Set(strconv.Itoa(i), i)) 222 } 223 224 counts := make(map[int]int) 225 for i, m := range maps { 226 counts[i] = len(m) 227 } 228 entropy := calcEntropy(counts, total) 229 assert.True(t, len(counts) > 1) 230 assert.True(t, entropy > .95, fmt.Sprintf("entropy should be greater than 0.95, but got %.2f", entropy)) 231 232 for i := 0; i < total; i++ { 233 var val int 234 assert.Nil(t, c.Get(strconv.Itoa(i), &val)) 235 assert.Equal(t, i, val) 236 } 237 238 for i := 0; i < total/10; i++ { 239 assert.Nil(t, c.Del(strconv.Itoa(i*10), strconv.Itoa(i*10+1), strconv.Itoa(i*10+2))) 240 assert.Nil(t, c.Del(strconv.Itoa(i*10+9))) 241 } 242 243 var count int 244 for i := 0; i < total/10; i++ { 245 var val int 246 if i%2 == 0 { 247 assert.Nil(t, c.Take(&val, strconv.Itoa(i*10), func(val interface{}) error { 248 *val.(*int) = i 249 count++ 250 return nil 251 })) 252 } else { 253 assert.Nil(t, c.TakeWithExpire(&val, strconv.Itoa(i*10), func(val interface{}, expire time.Duration) error { 254 *val.(*int) = i 255 count++ 256 return nil 257 })) 258 } 259 assert.Equal(t, i, val) 260 } 261 assert.Equal(t, total/10, count) 262 } 263 264 func TestCacheNoNode(t *testing.T) { 265 dispatcher := hash.NewConsistentHash() 266 c := cacheCluster{ 267 dispatcher: dispatcher, 268 errNotFound: errPlaceholder, 269 } 270 assert.NotNil(t, c.Del("foo")) 271 assert.NotNil(t, c.Del("foo", "bar", "any")) 272 assert.NotNil(t, c.Get("foo", nil)) 273 assert.NotNil(t, c.Set("foo", nil)) 274 assert.NotNil(t, c.SetWithExpire("foo", nil, time.Second)) 275 assert.NotNil(t, c.Take(nil, "foo", func(val interface{}) error { 276 return nil 277 })) 278 assert.NotNil(t, c.TakeWithExpire(nil, "foo", func(val interface{}, duration time.Duration) error { 279 return nil 280 })) 281 } 282 283 func calcEntropy(m map[int]int, total int) float64 { 284 var entropy float64 285 286 for _, val := range m { 287 proba := float64(val) / float64(total) 288 entropy -= proba * math.Log2(proba) 289 } 290 291 return entropy / math.Log2(float64(len(m))) 292 }