一、Redis简介
1. Redis是什么?
Redis是现在最受欢迎的NoSQL数据库之一,Redis是一个使用ANSI C编写的开源、包含多种数据类型
字符串类型(string),散列类型(hash),列表类型(list),集合类型(set),有序集合类型(zset)
、支持网络、基于内存、可选持久性的键值对存储数据库。
2. 为什么要用Redis?
1.解决应用服务器的cpu和内存压力 2.减少io的读操作,减轻io的压力 3. 关系型数据库的扩展性不强,难以改变表结构。
通俗点的意思,就是因为Redis直接作用于缓存,比关系型的数据库快得多,大多数时候是配合关系型数据库使用,避免访问频率高的数据反复作用于数据库,导致数据库的负载过高,减少了I/O操作
3.使用场景
热点数据缓存(本章介绍)
计数器
排行榜
分布式锁
......
4. Redis作缓存怎么用呢?
访问数据库之前,先去Redis查找有无结果,有就直接返回结果,否则再去数据库中查找,查找到之后在redis做缓存并返回结果。
二、 Golang Redis常用操作
推荐一个网址 redigo API文档
1. 第三方库
go get -u github.com/gomodule/redigo/redis
2. 连接redis
采用的是连接池的方式
packagemainimport("fmt"_"github.com/go-sql-driver/mysql""github.com/gomodule/redigo/redis""time")varrdsredis.ConnfuncRedisPollInit()*redis.Pool{return&redis.Pool{MaxIdle:5,//最大空闲数MaxActive:0,//最大连接数,0不设上Wait:true,IdleTimeout:time.Duration(1)*time.Second,//空闲等待时间Dial:func()(redis.Conn,error){c,err:=redis.Dial("tcp","127.0.0.1:6379")//redisIP地址iferr!=nil{fmt.Println(err)returnnil,err}redis.DialDatabase(0)returnc,err},}}funcRedisInit(){rds=RedisPollInit().Get()}funcRedisClose(){_=rds.Close()}
3. Get&Set
funcmain(){RedisInit()deferRedisClose()varerrerror_,err=rds.Do("set","name","abc")//redisset命令iferr!=nil{fmt.Println(err)return}res,err:=rds.Do("Get","name")//redisget命令iferr!=nil{fmt.Println(err)return}fmt.Println("res:",res)res1,err:=redis.String(rds.Do("Get","name"))//redisget命令iferr!=nil{fmt.Println(err)return}fmt.Println("res1:",res1)res2,err:=redis.Int(rds.Do("Get","name"))//redisget命令iferr!=nil{fmt.Println(err)return}fmt.Println("res2:",res2)}
得到结果->
res:[979899]res1:abcstrconv.ParseInt:parsing"abc":invalidsyntax
解析:
rds.Do()
执行命令函数,返回的是空接口任意类型
Do(commandNamestring,args...interface{})(replyinterface{},errerror)
redis.String()
把GET返回的空接口解析成string类型
funcString(replyinterface{},errerror)(string,error)
redis.Int()
把GET返回的空接口解析成int类型,因为"abc"
无法转换成int类型,所以报错
funcInt(replyinterface{},errerror)(int,error)
4.过期设置
funcmain(){RedisInit()deferRedisClose()varerrerror_,err=rds.Do("set","name","abc","EX",5)//redisset命令iferr!=nil{fmt.Println(err)return}res,err:=redis.String(rds.Do("Get","name"))//redisget命令iferr!=nil{fmt.Println(err)return}fmt.Println("res:",res)time.Sleep(time.Second*6)res,err=redis.String(rds.Do("Get","name"))//redisget命令iferr!=nil{fmt.Println(err)return}fmt.Println("res:",res)}
得到结果->
res:abcredigo:nilreturned
解析:
rds.Do("set", "name", "abc", "EX", 5)
执行命令函数,返回的是空接口任意类型,加入了EX
过期命令和过期时间5s
Do(commandNamestring,args...interface{})(replyinterface{},errerror)
5.分布式锁
funcmain(){RedisInit()deferRedisClose()varerrerror_,err=rds.Do("set","name1","abc","NX","EX",5)//redisset命令iferr!=nil{fmt.Println(err)return}_,err=rds.Do("set","name1","叶叶子","NX","EX",5)//redisset命令iferr!=nil{fmt.Println(err)return}res,err:=redis.String(rds.Do("Get","name1"))//redisget命令iferr!=nil{fmt.Println(err)return}fmt.Println("res:",res)time.Sleep(time.Second*6)_,err=rds.Do("set","name1","叶叶子","NX","EX",5)//redisset命令iferr!=nil{fmt.Println(err)return}res,err=redis.String(rds.Do("Get","name1"))//redisget命令iferr!=nil{fmt.Println(err)return}fmt.Println("res:",res)}
得到结果->
funcmain(){RedisInit()deferRedisClose()varerrerror_,err=rds.Do("set","name","abc")//redisset命令iferr!=nil{fmt.Println(err)return}res,err:=rds.Do("Get","name")//redisget命令iferr!=nil{fmt.Println(err)return}fmt.Println("res:",res)res1,err:=redis.String(rds.Do("Get","name"))//redisget命令iferr!=nil{fmt.Println(err)return}fmt.Println("res1:",res1)res2,err:=redis.Int(rds.Do("Get","name"))//redisget命令iferr!=nil{fmt.Println(err)return}fmt.Println("res2:",res2)}0
解析:
rds.Do("set", "name1", "abc", "NX", "EX", 5)
执行命令函数,NX
命令:在指定的 key 不存在时,为 key 设置指定的值,否则跳过,可以看到过期之后才能继续设置值。
Do(commandNamestring,args...interface{})(replyinterface{},errerror)
6.结构体切片map等等复杂类型缓存处理
funcmain(){RedisInit()deferRedisClose()varerrerror_,err=rds.Do("set","name","abc")//redisset命令iferr!=nil{fmt.Println(err)return}res,err:=rds.Do("Get","name")//redisget命令iferr!=nil{fmt.Println(err)return}fmt.Println("res:",res)res1,err:=redis.String(rds.Do("Get","name"))//redisget命令iferr!=nil{fmt.Println(err)return}fmt.Println("res1:",res1)res2,err:=redis.Int(rds.Do("Get","name"))//redisget命令iferr!=nil{fmt.Println(err)return}fmt.Println("res2:",res2)}2
得到结果->
funcmain(){RedisInit()deferRedisClose()varerrerror_,err=rds.Do("set","name","abc")//redisset命令iferr!=nil{fmt.Println(err)return}res,err:=rds.Do("Get","name")//redisget命令iferr!=nil{fmt.Println(err)return}fmt.Println("res:",res)res1,err:=redis.String(rds.Do("Get","name"))//redisget命令iferr!=nil{fmt.Println(err)return}fmt.Println("res1:",res1)res2,err:=redis.Int(rds.Do("Get","name"))//redisget命令iferr!=nil{fmt.Println(err)return}fmt.Println("res2:",res2)}3
解析:
使用"encoding/json"
库json.Marshal
,把数据转序列化成json数据存入redis
,取出key值的时候使用redis.Bytes()
解析之后再用json.Unmarshal
反序列化得到结果。
三、Redis风险&解决办法
参考文档
redis缓存雪崩、穿透、击穿概念及解决办法
1、缓存雪崩
场景
假设缓存上限最多1秒应答100个请求,但是高峰期每秒200个请求,缓存出现意外,导致请求全部作用到数据库,导致数据库挂掉。
解决办法
redis使用高可用的部署,主从、哨兵、集群的模式;
后端限流降级处理;
redis持久化,可快速恢复。
2、缓存穿透
场景
假设缓存上限最多1秒应答100个请求,恶意有人1秒发出100个缓存和数据库都查不到的请求,导致每次都回去查数据库。
解决办法
从数据库中只要没查到,就写一个空值到缓存里。
2、缓存击穿
场景
热点key时效是,大量请求直接作用到数据库。
解决办法
热点数据不过期。
作者:小小小丶叶子著作权归作者所有。