这是我参与「第三届青训营 -后端场」笔记创作活动的第三篇笔记。
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中。(由于时间问题,这个方法没有实现)