github.com/shuguocloud/go-zero@v1.3.0/core/stores/cache/cache_test.go (about)

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