go连接Redis | 青训营笔记

444 阅读4分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第三篇笔记。

Redis

1. 引入Redis原因

在进行项目时开发时,单纯用到MySQL,会给服务器造成一定压力,例如评论列表。不停地有用户查询评论列表,MySQL就要不停的查询,io次数增长,服务器压力变大,容易宕机。

2. Redis优点

  • redis非关系数据库,本质上是一个Key-Value 内存数据库,整个数据库加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘中保存。
  • 性能高 – Redis能读的速度是110000次/s,写的速度是81000次/s ,单机能够达到15w qps,通常适合做缓存。
  • 数据类型丰富 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
  • 原子性 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。

3. golang操作Redis

在项目中使用的第三方redis库:github.com/garyburd/redigo/redis

golang连接redis

package main
import (
    "fmt"
    "github.com/garyburd/redigo/redis"
)
​
func main() {
    c, err := redis.Dial("tcp", "localhost:6379")
    if err != nil {
        fmt.Println("conn redis failed,", err)
        return
    } 
    fmt.Println("redis conn success")
    defer c.Close()
}

String类型Set、Get操作

package main
​
import (
    "fmt""github.com/garyburd/redigo/redis"
)
​
func main() {
    //通过go 向redis写入数据和读取数据
    //1.链接到redis
    conn, err := redis.Dial("tcp", "127.0.0.1:6379")
    if err != nil {
        fmt.Println("redis.Dial err=", err)
        return
    }
    defer conn.Close()
​
    //2.通过go向redis写入数据string [key-val]
    n, err := conn.Do("Set", "name", "tomjerry猫猫")
    if err != nil {
        fmt.Println("set err=", err)
        return
    }
    fmt.Println("n:", n)
​
    //3.通过go 向redis读取数据 string [key-val]
​
    r, err := redis.String(conn.Do("Get", "name"))
    if err != nil {
        fmt.Println("set err=", err)
        return
    }
​
    //因为返回的r是interface{}
    //因为name 对应的值是string,因此我们需要转换
    //namestring := r.(string)
​
    fmt.Println("conn succ...", r)
}

String批量操作

​
    _, err = c.Do("MSet", "abc", 100, "efg", 300)
    if err != nil {
        fmt.Println(err)
        return
    }
​
    r, err := redis.Ints(c.Do("MGet", "abc", "efg"))
    if err != nil {
        fmt.Println("get abc failed,", err)
        return
    }
​
    for _, v := range r {
        fmt.Println(v)
    }
​

设置过期时间

 _, err = c.Do("expire", "abc", 10)

List队列操作

_, err = c.Do("lpush", "book_list", "abc", "ceg", 300)
 r, err := redis.String(c.Do("lpop", "book_list"))

运行结果:

   300

Hash操作

    _, err = c.Do("HSet", "books", "abc", 100)
   r, err := redis.Int(c.Do("HGet", "books", "abc"))

运行结果:

100

4. Redis连接池

在项目中用的是连接池方法

原因: 在每次和数据库进行连接,数据传输的过程中,都需要进行创建连接,收发数据,关闭连接。在一般情况下不会发生问题,但是在并发量比较高的情况下就会有问题出现

出现的问题:性能上不去,CPU大量资源被消耗,服务器工作不稳定。

连接池原理

在初始化时,直接一次性创建一定数量的连接,先把所有的长连接存起来,然后谁需要使用,从这里取走,干完活立马放回来。如果请求数据超出连接池的容量,那么就进行排队等待。退化成短连接或者直接丢弃掉。

package main
import(
    "fmt"
    "github.com/garyburd/redigo/redis"
)
​
var pool *redis.Pool             //创建redis连接池func init(){
    pool = &redis.Pool{                        //实例化一个连接池
        MaxIdle:16,                            //最初的连接数量
        // MaxActive:1000000,                  //最大连接数量
        MaxActive:0,       //连接池最大连接数量,不确定可以用0(0表示自动定义),按需分配
        IdleTimeout:300,                  //连接关闭时间 300秒 (300秒不使用自动关闭)    
        Dial: func() (redis.Conn ,error){      //要连接的redis数据库
            return redis.Dial("tcp","localhost:6379")
        },
    }
}
​
func main(){
    c := pool.Get()                        //从连接池,取一个链接
    defer c.Close()                        //函数运行结束 ,把连接放回连接池
​
        _,err := c.Do("Set","abc",200)
        if err != nil {
            fmt.Println(err)
            return
        }
​
        r,err := redis.Int(c.Do("Get","abc"))
        if err != nil {
            fmt.Println("get abc faild :",err)
            return
        }
        fmt.Println(r)
        pool.Close()                      //关闭连接池
}

运行结果

200

5. 评论在使用redis时遇到的问题

MySQL与Redis保持一致性

思路1: 最开始写评论缓存时,在MySQL插入评论的同时在redis中插入,在显示评论列表时,redis里面的数据就是MySQL中的数据,MySQL删除评论的同时在redis也删除。

结果:如果MySQL的数据有10条,此时redis中的数据也有10条,但是经过一段时间,redis中的数据就会变为5条,显示的时候也会显示5条。

思路2:在插入MySQL的时候,redis设置为立刻过期(数据全部清空),一旦redis数据为空,就会从MySQL中读取数据到redis。删除评论也是,删除评论时,把redis置位空,然后从MySQL中读取数据。这样好像不需要加锁哈哈哈。(目前项目也是用的这个)

缺点: IO操作比较多,消耗很多内存

思路3: 通过canal和kafka实现MySQL和redis同步。通过Canal去自动同步数据库的binlog数据日志,然后通过kafka再把数据同步到redis中。(由于时间问题,这个方法没有实现)