Redis
#Go #缓存 github.com/go-redis/re…
Why NOSQL
- Too much read, to decrease DQL, use Cache
- Too much write: 分库分表
Mysql压力大: 数据量大 有些文件很大
- NOSQL = Not Only SQL
- 泛指非关系型数据库
- map[string]interface{}
Features
- 方便拓展
- 大数据高性能
- 数据类型多样,不用事先设计
- 没有固定查询语言
NOSQL Classification
- KV
- 文档型
- MongoDB
- 列存储
- 图关系
- 存关系的
Redis入门
- Remote dictionary server
- 内存存储、持久化
- 高效,作为缓存
- 发布订阅
- 计时计数
Basics
- 默认16个数据库 0 in default
- 用select切换
- Flushdb 清空当前数据库
- flushall 清空全部
- Keys * 查看所有的key
- Redis单线程,基于内存操作
- 数据全在内存
- 不需要CPU上下文切换
基本命令Keys
- redis可用为DB,Cache,MQ
- redis-key
- set name kuangshen 》 name—狂神
- get name 〉 查看name的值
- exists name 〉 查看是否有name,返回bool(1/0)
- move name 1 》 把name移到1号数据库
- expire name 10 〉 name在10秒后过期
- ttl name 》 name的生存时间
- type name 〉 name的类型
String字符串
- append name “hello” > 在name后面追加”hello“,不存在name时新建
- strlen name 》 获取name长度
- incr views 〉 views的值+1
- decr views 》 views的值-1
- incrby views 10 〉 views的值+10
- decrby views 10 》 views的值-10
- Getrange name 1 3 〉 从name中截取1~3的字符子串(第一个字符在0)
- getrange name 0 -1 》 从name中截取全部字符串
- setrange name 1 hello 〉从name的第1个位置(包含name【1】),设置为hello
- setex name 30 “hello” 》 设置name值为“hello”,30秒过期
- setnx name “Bob” 〉 如果不存在name,设置为“Bob”,返回bool(1/0)
- mset k1 v1 k2 v2 k3 v3 ….. > 一次性set多个值,k1为v1,k2为v2……
- mget k1 k2 k3 》 一次性get多个值k1,k2,k3
- Msetnx k1 v1 k2 v2 〉 如果不存在一次性设置,这是一个原子性操作,两个设置有一次不成功,这个语句都不生效,只能一起成功/一起失败
- set user:1{name:”kuangshen” ,age:3} > 设置一个user对象,json格式存储,user:{id}{field}
- getset name “hello”》 先获取name的值,再设置为“hello”
List
- List命令以l开头
- Lpush list one 》 从头部向list当中推入“one”,每次新推入元素都会插入在头部,其余元素后移
- lrange list 0 -1 〉 左侧开始,截取list的全部值
- rpush list one 》 从尾部向list推入“one”
- lpop list 〉 从list头部弹出一个元素
- rpop list 》 从list尾部弹出一个元素
- lindex list 1 〉 从list获取第1个值(从0开始计数)
- llen list 》 返回list的长度
- lrem list 1 one 〉 从list中移除值为“one”的一个元素
- Ltrim list 1 3 》 将list截断,保留1~3的元素(包含1和3),其余元素移除
- rpoplpush name words > 从name中移除最后一个元素,并加入在words 的头部
- lset list 0 hello 》 将list的第0个值设为‘hello’ ( 前提是list存在,要创建一个list,先lpush第一个元素)
- linsert list before “world” hello 》 在list中的world前面插入一个“hello”
- linsert list after “hello” “world” 》 在list中的hello后插入一个“world”
Set
- Set中值不能重复
- 操作开头都是s
- sadd set hello 》 在set中添加“hello”
- smembers set 〉 查看set的元素
- sismember set hello 》 检查“hello”是否为set的成员
- scard set 〉 获取set中元素个数
- srem set hello 》 从set中移除”hello“
- srandmember set 〉 从set中随机获取一个值
- srandmember set 2 》 从set中随机获取2个值
- Spop set 〉 从set中随机删除一个元素
- smove set1 set2 hello 》 把“hello”从set1移动到set2
- sdiff set1 set2 〉 返回set1和set2的差集
- sinter set1 set2 》 返回set1和set2的交集
- sunion set1 set2 〉 返回set1 和 set2的并集
Hash
- map集合
- H开头
- hset hash name shr 》 将hash设置name-shr键值对
- hget hash name 〉 取hash中name的值
- hmset hash name shr sex male 》 一次性设置hash的多个值
- hmget hash name sex 〉 一次性获取hash中的多个值
- hgetall hash 》 获取hash中的全部键和值
- hdel hash name 〉 从hash中删除name
- hlen hash 》 获得hash的长度(字段数量
- hexist hash name 〉 判断hash中是否存在name
- hkeys hash 》 获取hash中的所有键
- hvals hash 〉 获取hash中的所有值
- hincrby hash name 3 》 给hash中的name + 3
- hdecrby hash name 3 〉 给hash中的name - 3
- hsetnx hash name 4 》 若hash中name不存在,设置为4
Zset
- 有序集合
- Z开头
- zadd zset 1 hello 》 给hello标号为1,加入zset
- zrange zset 0 -1 〉 升序获得所有值
- zrevrange zset 0 -1 》 降序返回所有值
- zrangebyscore zset -inf +inf 》 按照标号顺序从负无穷到正无穷排序返回zset的元素值
- zrangebyscore zset 100 200 withscore 》 按照标号从100到200,排序返回zset的元素值和标号
- zrem zset name 〉 从zset中移除name
- zcard zset 》 返回zset中元素个数
- Zcount zset 1 2 〉 获取zset中标号1~2之间的元素个数(包含1,2)
Geospatial
- 推算地理位置信息
- geoadd china:city 116.4 39.9 beijing 》 向china:city增加北京的纬经度位置
- geopos china:city beijing 〉 从china:city中取出beijing的位置
- geodist china:city beijing Shanghai km 》 获得china:city中beijing与Shanghai的距离(km)(两者必须都已经加入china:city)
- georadius china:city 110 30 1000 km count 3》 在china:city中,以110,30 为中心,1000 km为半径搜索3个地理位置
- georadiusbymember china:city beijing 1000 km withcoord count 1 》 在china:city中,以beijing为中心,1000 km为半径,返回一个地理位置及其经纬度
Hyperloglog
- 浏览量计数可以用
- 用到的时候查官网
- pfadd
- pfcount
- pfmerge
Bitmaps
- 用户打卡,用户登录签到可以用
- setbit bitmap 0 1 》 在bitmap的第0个记为1
- getbit bitmap 1 〉 查看bitmap第1天的记录
- bitcount bitmap 》 统计bitmap里1的个数
事务
- 原子性
- Redis的事务没有原子性,单条命令有原子性
- 事务本质:一组命令的集合
- 一次性、顺序性、排他性
- multi 》 开启事务
- 开启之后输入的指令就会入列等待执行
- Exec 〉 执行事务
- discard 》 取消事务
- 事务异常
- 如果是命令有错,那么命令全部都不会执行
- 如果是运行时出现异常,事务执行不会回滚,其他命令正常,错误命令抛出异常
- 乐观锁
- watch money 》 监视money
- 当监视的变量被其他线程操作修改,事务就会执行失败
- Unwatch money > 事务执行失败的话解锁money,之后重新监视
Go-Redis
- 先新建一个redis-client,传入redis.options配置
- 用创建的client进行操作
- 事务用tx := txpipeline()创建,然后调用tx的方法就行,在tx.exec()时,之前的所有调用会被包裹在multi和exec之间执行
Redis.conf
- bind 》 绑定的ip
- damon 》 守护进程
- pidfile 〉pid进程文件
- logfile 》 日志位置
- databases 〉 数据库数量
- Save 60 10 》 在60秒内如果有至少10个key修改,那么持久化操作
- Dir 〉 持久化rdb保存的目录
- requirepass 》 redis的密码设置
- appendonly 〉 aof开启设置
持久化
- RDB
- RedisDataBase
- 类似快照
- 最后一次持久化数据可能丢失
- 比AOF性能好
- dump.rdb
- flushall后会自动触发rdb规则
- 适合大规模数据恢复
- AOF
- AppendOnlyFile
- 类似历史记录,记录所有命令,恢复时全部执行
- 只许追加不可改写
- 默认不开启
- 重启redis生效
- Aof文件错误时无法启动redis
- redis-check-aof —fix xxxx.aof > 修复aof
- Aof修复慢,文件大
- 只作缓存,不用持久化
发布订阅
- 消息发布者、频道、消息订阅者
- subscribe name 》 订阅name
- publish name hello 〉 在name上发布消息
- 原理: map [频道] ([ ]用户链表)
- 实时消息
- 实时聊天
- 订阅、关注
主从复制
- 数据复制只能 master->slave
- Master以写为主,slave以读为主,读写分离
- 数据冗余
- 故障恢复
- 负载均衡
- 高可用
- 不能单机使用redis,一定主从复制!
- info replicaiton 》 查看当前库的复制信息
- 复制原理
- slaveof ip port 〉 将当前redis作为ip:port的从机
- Slaveof no one > 将自己设为主节点
- 命令设置只是暂时的,要去conf里永久性修改
- 在conf中
- replicaof > 设置为masterip:masterport的从机
- 从机不能写(set)
- 主机断线重连,从机没影响
哨兵模式 Sentinel
- 自动选主机
- 哨兵是个独立进程,哨兵向各服务器发送请求,等待响应,来监控redis服务
- 多哨兵模式
- 各哨兵之间也会互相监控
- 主观下线 与 客观下线
- 主观下线是指一开始哨兵发现主服务掉线
- 客观下线指各slave投票后选出新的主服务器,原来的主为客观下线
- 步骤
- 哨兵配置sentinel.conf
- Sentinel monitor myredis ip port 1 》1代表主机掉线时选举1
- 启动哨兵
- redis-sentinel sentinel.conf
- 主机重新上线之后会成为从机
- 哨兵配置sentinel.conf
缓存穿透 和 雪崩
- 缓存穿透的概念(查不到
- 缓存服务器没有数据,用户向mysql请求依然 没有此数据,查询失败
- 解决方案:
- 布隆过滤器
- 缓存空对象
- 解决方案:
- 缓存服务器没有数据,用户向mysql请求依然 没有此数据,查询失败
- 缓存击穿(量太大
- 有key成为热点,请求集中,key在失效(过期)瞬间大量并发请求访问持久化数据库
- 解决:
- 设置热点数据永不过期
- 分布式锁,只允许一个线程查询数据库
- 解决:
- 有key成为热点,请求集中,key在失效(过期)瞬间大量并发请求访问持久化数据库
- 缓存雪崩
- 某个时间段缓存集中失效/缓存服务器宕机,大量的查询请求来到数据库,产生波峰压力
- 解决
- 多台redis,redis高可用
- 服务降级
- 加锁/减少服务,降低数据库压力
- 数据预热
- 在大并发访问前,手动把可能的key加入缓存并控制过期时间,使其均匀
- 解决
- 某个时间段缓存集中失效/缓存服务器宕机,大量的查询请求来到数据库,产生波峰压力