如何让Redis与Go密切配合

谢权 · · 2850 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

这篇blog将给大家介绍在go语言项目使用Redis作为数据持久层。开始之前我将介绍几个基本概念,然后构建一个go web项目来展示如何让Redis实现安全的并发。

这篇blog是假设你是了解Redis的一些东西的,如果你之前没有学习或者了解过Redis。我建议你先去阅读一些Redis的知识再来看这篇blog效果更好。

安装驱动

第一步我们需要安装Redis的go语言驱动程序,大家可以在这个列表中选择一个http://redis.io/clients#go

这篇blog的示例我还是选择了Radix.v2作为驱动程序。因为它维护的很好,API用起来也很舒服,如果你也想用的话 可以在终端执行下面的命令:

有一点要注意的是Radix.v2的包被分为了(cluster, pool, pubsub,redis, sentinel and util)6个子包。最开始我们只需要使用其中的redis包。

开始使用 Radix.v2

作为示例程序,我们为它设定一个场景也就是所谓的需求。假设我们有一个在线的唱片店,并希望存储有关的专辑的销售信息存储在Redis中。我们可以有很多不同的方式来设计数据模式。我希望我们的model是简单的并且通过hash的方式来存储我们的专辑信息包括标题,艺术家和价格之类的字段。

我们可以通过Redis的CLI  执行HMSET命令:

如果我们在Go项目中来做同样的操作,我们就需要通过Radix.v2 redis包来实现,首先需要Dial()函数,需要它返回一个新的connection。第二个我们需要使用client.Cmd()方法。通过他我们可以发送一个命令到我们的服务器,这里会返回给我们一个Resp对象的指针。

我们来看一个简单的示例:

在这个示例中我们所关心的并不是Redis会返回什么。因为所有成功的操作都会返回一个“OK”的字符串。所以我们不需要对*Resp对象做错误检查。

在这样的情况下我们只需要检查err就好了:

回复处理

如果我们留意Redis的回复的话,我们发现Resp对象有很多有用的函数让Go的类型转换变得容易:

  • Resp.Bytes() – converts a single reply to a byte slice ([]byte)
  • Resp.Float64() – converts a single reply to a Float64
  • Resp.Int() – converts a single reply to a int
  • Resp.Int64() – converts a single reply to a int64
  • Resp.Str() – converts a single reply to a string
  • Resp.Array() – converts an array reply to an slice of individual Resp objects ([]*Resp)
  • Resp.List() – converts an array reply to an slice of strings ([]string)
  • Resp.ListBytes() – converts an array reply to an slice of byte slices ([][]byte)
  • Resp.Map() – converts an array reply to a map of strings, using each item in the array reply alternately as the keys and values for the map (map[string]string)

接下来我们使用HGET 命令获取一些专辑的数据:

值得指出的是,当我们使用这些方法的时候,这时候的错误返回会设置一种或者2种错误:一种是任一命令执行失败,或者返回数据转换成了我们需要要的类型。这样的话我们就不知道错误是哪一种了?除非我们检查错误消息的方式。

如果我们执行代码我们将会看到:

现在让我们来看一个更完整的例子,我们使用HGETALL命令来获取专辑的信息:

我们执行代码将会看到下面的输出:

在Web项目中使用

一个重要事情就是我们知道什么是并发不安全的使用Radix.v2中的redis包。

如果我们要访问一个Redis服务器而通过多个goroutines,那我我们在web项目中就需要使用pool包来替换redis。这将是我们能够建立连接池,每次使用连接我们只需要从连接池中获取就可以了。

执行命令,然后返回到连接池中。

简单的介绍下我们的Web项目的结构:

Method Path Function
GET /album?id=1 Show details of a specific album (using the id provided
in the query string)
POST /like Add a new like for a specific album (using the id
provided in the request body)
GET /popular List the top 3 most liked albums in order

如果你想和我的结构一样的话你可以跟着我下面的命令操作:

然后我们通过Redis CLI 保存一些专辑数据,这样我们等下就有测试数据了。

我们将遵循MVC的模式来构建我们的项目,我们使用models/albums.go文件来实现我们Redis的逻辑处理,在models/albums.go文件中我们将使用init()函数来建立启动我们的Redis连接池。然后我们也会重构之前我们写的FindAlbum()函数。然后我们我们通过HTTP handlers来使用它:

值得我们着重讲解一下的是pool.New()函数。在上面的代码中我们指定连接池的大小为10,它只是空闲时候在池中等待的数量。如果1o个连接都使用了的话,那时候将会调用 pool.Get()创建新的链接。

当你只有一个连接发出命令的话,比如上面的FindAlbum()函数,有可能会使用pool.Cmd()的快捷方式。这将自动从连接池中获取一个新的连接,执行给定的命令、然后将连接返回连接池。

这是我们重构了之后的FindAlbum()函数:

好了,让我们来看看我们的main.go的文件如何实现的:

执行我们的代码:

通过cURL来测试我们的请求,我们也可以通过postman之类的工具:

使用事务

我们的第二个路由POST /likes

当我们的用户如果喜欢(点赞)我们的专辑的时候,我们需要执行2条不同的命令:使用HINCRBY来增加likes字段的值,和使用ZINCRBY来增加相应的score在我们的likes的有序集合中。

那么这里就会产生一个问题。理想情况下,我们希望这两个键在完全相同的时间递增作为一个原子操作,当一个key完成更新后,其它数据不会与其发生争抢。

解决这个问题的办法就需要我们使用Redis事务,这让我们运行多个命令在一起作为一个原子团。我们可以使用MULTI命令来开启事务,随后执行我们的HINCRBY 和ZINCRBY,最后执行EXEC命令

让我们在model中创建一个新的IncrementLikes()函数:

我们也需要在main.go文件中为route添加一个addLike()handler:

和之前一样我们来测试一下:

使用Watch命令

好了还剩下我们最后一个route: GET /popular,这个route将会现实我们最受欢迎的3个专辑的详细内容,我们将在models/albums.go中创建FindTopThree()函数,这这个函数中我们需要:

1.使用ZREVRANGE命令,去获取3条专家的最高分,从我们的likes字段的有序集合中

2.通过HGETALL命令循环获取存储在键的散列的所有字段和值,把它们存到[]*Album slice中

这里同样也可能会发送数据竞争的情况,如果第二个第二个客户端也碰巧需要只想我们刚刚的操作,我们用户可能会得到不正确的数据。

这个问题的解决方法就是使用Redis WATCH命令与事务结合使用。WATCH可以让Redis监控某个key的变化,如果其它客户端要更改我们监控的key在我们执行EXEC之前,那么事务将会失败并且会返回Nil。如果没有其它客户端在我们执行EXEC命令之前修改我们的数据。那么我的操作将正常执行:

和之前一样在route中添加我们的handle:

通过curl 发送GET /popular route请求,我们将看到:

 

英文原文链接:http://www.alexedwards.net/blog/working-with-redis

如何让Redis与Go密切配合

有疑问加站长微信联系(非本文作者)

本文来自:谢权SELF

感谢作者:谢权

查看原文:如何让Redis与Go密切配合

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2850 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传