cache_test.go 7.0 KB

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