DCS
华为云分布式缓存服务(Distributed Cache Service,简称DCS)是华为云提供的一款内存数据库服务,兼容了Redis内存数据库引擎,提供即开即用、安全可靠、弹性扩容、便捷管理的在线分布式缓存能力,满足用户高并发及数据快速访问的业务诉求,同时可以用来实现分布式锁,分布式事件总线等。
基础示例
环境准备
本节内容将为您演示如何使用以 GO语言编写的代码连接到Distributed Cache Service。 同时还介绍了在数据库中查询、插入、更新和删除数据。
项目地址:DCS samples
代码连接
-
购买分布式缓存服务DCS实例后,可通过多种连接方式连接实例,你可根据需要选择。Go代码连接方式如下
func InitDB() error { conf, err := GetConfig() if err != nil { return err } Rdb = redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%v:%v", conf.Host, conf.Port), Password: conf.Password, DB: conf.Db, }) ctx := context.Background() err = Rdb.Ping(ctx).Err() if err != nil { return errors.Wrap(err,"ERR: fail to connect redis") } fmt.Println("连接成功") return nil } -
云数据库参数保存在yaml文件中,关键代码如下
Rdb = redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%v:%v", conf.Host, conf.Port), Password: conf.Password, DB: conf.Db,})字段说明,如下:
Host:数据库IP或者域名(推荐使用内网)
Port:数据库实例映射端口
Db:数据库名
Password:数据库密码
基础操作
-
添加获取数据
func testData(rdb *redis.Client) error { //添加值 err := rdb.Set(ctx,"key1", "value1", 0).Err() if err != nil { return errors.Wrap(err,"ERR: fail to set key") } //获取值 res, err := rdb.Get(ctx,"key1").Result() if err != nil { return errors.Wrap(err,"ERR: fail to get key") } //打印值 fmt.Println(res) err = rdb.Del(ctx,"key1").Err() if err != nil { return errors.Wrap(err,"ERR: fail to delete key") } return nil }
应用场景
排行榜
在网页和APP中常常需要用到榜单的功能,对某个key-value的列表进行降序显示。当操作和查询并发大的时候,使用传统数据库就会遇到性能瓶颈,造成较大的时延。
使用分布式缓存服务(DCS)的Redis版本,可以实现一个商品热销排行榜的功能。它的优势在于:
- 数据保存在缓存中,读写速度非常快。
- 提供字符串(String)、链表(List)、集合(Set)、哈希(Hash)等多种数据结构类型的存储。
代码示例:排名demo
// 排行榜场景
func RankingDemo(rdb *redis.Client) error {
ctx := context.Background()
// key
zsetKey := "商品热销排行榜"
//随机生成产品数据
productIds := make([]string, 0, 30)
product := make([]*redis.Z, 0, 30)
for i := 0; i < 30; i++ {
ids := uuid.NewV4().String()
productIds = append(productIds, "product-"+ids)
}
//随机生成销量
for _, v := range productIds {
score := rand.New(rand.NewSource(time.Now().UnixNano())).Int63n(100000)
product = append(product, &redis.Z{Score: float64(score), Member: v})
}
//插入redis中
err := rdb.ZAdd(ctx, zsetKey, product...).Err()
if err != nil {
return errors.Wrap(err,"ERR: fail to zadd")
}
fmt.Println("zadd success")
//获取所有产品并按照销量排序输出
ret := rdb.ZRevRangeWithScores(ctx, zsetKey, 0, -1).Val()
for _, z := range ret {
fmt.Printf("产品id:%s,产品销量:%v\n", z.Member, z.Score)
}
//获取销量前五并按照销量排序输出
fmt.Println("销量前五的为下面的")
ret = rdb.ZRevRangeWithScores(ctx, zsetKey, 0, 4).Val()
for _, z := range ret {
fmt.Printf("产品id:%s,产品销量:%v\n", z.Member, z.Score)
}
return nil
}
分布式锁
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。
我们这里使用分布式缓存服务(DCS)的Redis版本来实现分布式锁,基于setnx命令(if not exists),其实现原理即:如果进入redis添加某个键不存在可以设置成功,如果已存在则会设置失败。
这里可以进行具体优化方案
- 使用set key value ex seconds nx命令实现设置键值和过期时间两个操作原子化,避免某个任务不能正常释放锁。
- 设置value值来对应任务锁,实现一个任务对应一把锁。
- 用golang操作redis运行lua命令保证对比value值和释放锁的操作的原子性。
示例代码:分布式锁demo
var counter int64
var wg sync.WaitGroup
//分布式key
var lockKey = "dislock"
//分布式锁场景
func lock(rdb *redis.Client, myfunc func()) {
ctx := context.Background()
defer wg.Done()
//获取value值
uuid := getUuid()
//加锁
res, err := rdb.SetNX(ctx, lockKey, uuid, time.Second*5).Result()
if err != nil {
log.Printf("%+v", err)
return
}
if !res {
fmt.Println("append lock fail")
return
} else {
fmt.Println("append lock")
}
//需要加锁的任务
myfunc()
//解锁,先判断value是否相等来确认是否是当前协程
//采用lua来保证原子操作
var luaScript = redis.NewScript(`
if redis.call("get", KEYS[1]) == ARGV[1]
then
return redis.call("del", KEYS[1])
else
return 0
end
`)
rs, err := luaScript.Run(ctx, rdb, []string{lockKey}, uuid).Result()
if err != nil {
log.Printf("%+v", err)
return
}
if rs == 0 {
fmt.Println(" release lock fail")
} else {
fmt.Println("release lock success")
}
}
//获取uuid
func getUuid() string {
out := uuid.NewV4().String()
return out
}
常见问题
分布式缓存服务DCS使用中的常见问题请参考常见问题