目录
Nosql
- not only sql 泛指非关系型数据库 使用map<String,Object>键值对控制
redis介绍
- 是一个开源的内存中存储数据结构存储系统,可用作数据库,缓存,消息中间件支持多种数据结构:string,hash,list,set,sorted set,bitmaps,hyperloglogs
- 内存存储、持久化,内存中是断电即失、持久化很重要(rdb、aof)
效率高,可用于高速缓存,发布订阅系统
计时器、计数器 - redis-benchmark -h localhost -p 6379 -c 100 -n 1000000 测试性能
- redis 默认16个数据库
- select index(int) 选择数据库
- flushall:清空所有库
- flushdb:清空当前库
redis基础操作
- set key value 存key
- get 获取key
- keys 查看所有key
- exists 查看key是否存在(存在返回1 不存在返回0)
- move 移除key (eg:move name 1(1代表当前数据库))
- expire key seconds(设置key多少秒之后过期 eg:expire name 10)
- ttl key (查询key过期剩余事件)
- type key (查看key的类型)
数据对象结构
typedef struct redisObject{
unsigned type:4;
unsigned encoding:4;
void *ptr;
int refcount;
unsigned lru:22;
}robj
#redis键值对象结构
ziplist
- 压缩列表(ziplist)是Redis为了节省内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整数值。
- 压缩列表的原理:压缩列表并不是对数据利用某种算法进行压缩,而是将数据按照一定规则编码在一块连续的内存区域,目的是节省内存。
- previous_entry_ength:记录压缩列表前一个字节的长度。previous_entry_ength的长度可能是1个字节或者是5个字节,如果上一个节点的长度小于254,则该节点只需要一个字节就可以表示前一个节点的长度了,如果前一个节点的长度大于等于254,则previous length的第一个字节为254,后面用四个字节表示当前节点前一个节点的长度。利用此原理即当前节点位置减去上一个节点的长度即得到上一个节点的起始位置,压缩列表可以从尾部向头部遍历。这么做很有效地减少了内存的浪费。
- encoding:节点的encoding保存的是节点的content的内容类型以及长度,encoding类型一共有两种,一种字节数组一种是整数,encoding区域长度为1字节、2字节或者5字节长。
- content:content区域用于保存节点的内容,节点内容类型和长度由encoding决定。
基础数据类型
string:
- append key ‘str’ (将str追加到key对应的value后面)如果当前key不存在 就相当于set
- incr(decr) 自增1 自减1
- incrby int(decrby int) 自增自减步长
- getrange key begin end (获得key对应value b-e(包含起始)的切片字符)
- strlen key (获取对应key的value 字符串的长度)
- set range key start str(将key对应value 从start开始的位置替换为str)
- setex key seconds value (设置键值并指定过期时间)
- setnx key value (设置键值 如不存在set 并返回1 如存在则返回0)
- mset [key1 value1 key2 value2]:批量设置键值对
- mget [key1 key2] 批量获得键值对
- msetnx 同setnx 批量设置键值对 (只要一个存在则设置失败) 存在返回0 不存在则设置键值对 并返回1
- getset 先get再set
底层实现数据结构(简单动态字符串)
- Redis 字符串为自定义的SDS(simple dynamic string)
struct sdshdr{
int len;
int free;
char buf[];
}
自定义的数据结构能够以O(1)时间获得字符串长度、并且防止缓冲区溢出、修改字符串不会重新分配空间、二进制安全(结束字符为len的长度)、兼容C<string.h>函数库
list:
- lpush key value (创建一个list)
- lrange key start end (拉取list s-e(包含) value)
- rpush key value 往右边加值
- lpop key 从左边弹出值
- rpop key 从右边弹出值
- lindex key index 获取key index的值
- llen key 获取list的长度
- lrem key int value 移除list指定个数的value
- ltrim key start end 截取s-e的list数据
- rpoplpush oldkey newkey 将原key中最右边值添加到新key中(新key不存在则创建)
- lset key index value 将已存在的list 索引位置的值替换(list不存在 index越界都会报错)
- linsert key before|after old-value new-value
底层实现数据结构(linkedlist or ziplist)
- 当同时满足下面两个条件时,使用ziplist(压缩列表)编码:
- 列表保存元素个数小于512个
- 每个元素长度小于64字节
typedef struct listNode{
struct listNode *prev;
struct listNode *next;
void *value;
}listNode
typedef struct list{
listNode *head;
listNode *tail;
unsigned long len;
void (*free) (void *ptr);
void (*free) (void *ptr);
int (*match) (void *ptr,void *key);
}list;
set:
- sadd key value 往集合添加值
- smember key 显示key集合的所有值
- sismember key value 判断value是否是key集合里面的值
- scard key 获取key 集合里面值的个数
- srem key value 移除key集合里面的特定值
- srandmember key int 随机选取集合里指定个数的值
- spop key 随机移除一个元素
- smove old-key new-key value 移动指定元素
- sdiff key1 key2 求集合差集
- sinter key1 key2 求交集
- sunion key1 key2 求并集
底层实现数据结构(intset 或者 hashtable)
- 当集合同时满足以下两个条件时,使用 intset 编码:
- 集合对象中所有元素都是整数
- 集合对象所有元素数量不超过512
typedef struct intset{
uint32_t encoding;
uint32_t length;
int8_t contents[];
}intset;
hash(值存的是map集合key-value)
- hset KEY key value:同string
- hget KEY key
- hmset KEY key1 value1 key2 value2
- hmget KEY key1 key2
- hgetall KEY:获取所有键值对
- hdel KEY key:删除指定key-value
- hlen KEY:获取长度
- hexists KEY key:判断key是否存在
- hkeys KEY:获取所有key
- hvals KEY:获取所有value
- hsetnx:同string
- hincr,hdecr:同String
底层实现的数据结构(ziplist或者 hashtable)
- 当同时满足下面两个条件时,使用ziplist(压缩列表)编码:
- 列表保存元素个数小于512个
- 每个元素长度小于64字节
typedef struct dictht{
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
}dictht
typedef struct dictEntry{
void *key;
union{
void *val;
uint64_tu64;
int64_ts64;
}v;
struct dictEntry *next;
}dictEntry
#1、使用字典设置的哈希函数,计算键 key 的哈希值
hash = dict->type->hashFunction(key);
#2、使用哈希表的sizemask属性和第一步得到的哈希值,计算索引值
index = hash & dict->ht[x].sizemask;
zset:(多一个字段作为排序依据)
- zadd key score(排序依据) value 添加值
- zrangebyscore key -inf +inf(范围) withscore(显示score) 以score为依据排序
- zrevrange key start stop 反序排列(索引)
- zrange key start stop 正序排列(索引)
- zrange key start end:显示范围内的值
- zrem key value:移除指定值
- zcard key:获取个数
- zcount key min max:获取指定区间值的个数
底层实现的数据结构(跳跃表 or ziplist)
- 当有序集合对象同时满足以下两个条件时,对象使用 ziplist 编码:
- 保存的元素数量小于128;
- 保存的所有元素长度都小于64字节。
- 不能满足上面两个条件的使用 skiplist 编码。以上两个条件也可以通过Redis配置文件
- zset-max-ziplist-entries 选项
- zset-max-ziplist-value 进行修改。
typedef struct zskiplistNode {
struct zskiplistLevel{
struct zskiplistNode *forward;
unsigned int span;
}level[];
struct zskiplistNode *backward;
double score;
robj *obj;
} zskiplistNode;
typedef struct zskiplist{
structz skiplistNode *header, *tail;
unsigned long length;
int level;
}zskiplist;
typedef struct zset{
zskiplist *zsl;
dict *dice;
} zset;
1.搜索:从最高层的链表节点开始,如果比当前节点要大和比当前层的下一个节点要小,那么则往下找,也就是和当前层的下一层的节点的下一个节点进行比较,以此类推,一直找到最底层的最后一个节点,如果找到则返回,反之则返回空。
2.插入:首先确定插入的层数,有一种方法是假设抛一枚硬币,如果是正面就累加,直到遇见反面为止,最后记录正面的次数作为插入的层数。当确定插入的层数k后,则需要将新元素插入到从底层到k层。
3.删除:在各个层中找到包含指定值的节点,然后将节点从链表中删除即可,如果删除以后只剩下头尾两个节点,则删除这一层。
geospatial:
- geoadd key longitude latitude value:添加位置经纬度(经纬度有大小限制)
- getpos key value:获取指定城市经度和维度
- geodis key value1 value2 [m,km,mi,ft]:获取两个位置的直线距离
- georadius key longitude latitude radiu [m,rm,mi,ft] withdist,withcoord,count(显示距离,经纬度 指定查找个数):指定位置为圆心半径范围内的地点
- georadiusbymember key value radiu [m,rm,mi,ft] withdist,withcoord,count :以指定存储位置为中心获得周围地址
- geohash key value value:将距离hash后返回11长度的字符串
- 正常操作使用zset的操作
hyoerloglog:计数
- Pfadd key element: 创建
- pfcount key:计数
- pfmerage mid-key source-keys
bitmaps:位操作
- setbit key value 0 or 1:设置value哪个位0 或 1
- getbit key value:
- bitcount key:计算
redis特性
- 单条命令保证原子性,但是事务不保证原子性
- redis事务就是一组命令的集合 一次性、顺序性、排他性
- 开启事务(multi)
- 执行事务(exec)
- 放弃事务discard
- 编译行错误 所有命令都不会执行
- 运行时异常 错误的命令不执行,其他的命令照常执行
redis锁:
- watch key :监视命令 可当作乐观锁操作
- unwatch key:解除监视命令 解锁
持久化:
RDB:
- 指定的时间间隔内将内存中的数据集快照写入磁盘,单独创建一个子进程将内存的内容写入临时文件,快照完成之后将文件写入磁盘
- save date time: 设置多少时间内 多少次key操作会持久化磁盘
- 触发机制:save规则满足、执行flushdb、退出redis
- 优点:适合大规模数据恢复、如果对数据完整性不高
- 缺点:需要一定时间间隔操作,如果宕机会丢失数据、fork子进程占用资源
AOF:
- 将所有命令都记录下来,恢复的时候读取日志重新操作一遍
- appendfsync(always,everysec,no)同步规则
- 如果aof文件被破坏,redis无法启动,可以用redis-check-aof修复
- 优点:每一次修改都同步,文件完整性更好,每秒同步可能会丢失一s数据,从不同步效率最高
- 缺点:相对数据文件来说,aof远大于rdb,修复速度慢,运行速度也比较慢
- rewrite:如果文件大于64m,fork一个新的进程重写
- 优先使用AOF恢复
redis主从复制:
- master/slave:数据的复制单向,只能由主到从,master以写为主,slave以读为主- 可以实现:数据冗余、故障恢复、负载均衡、高可用
- 默认每台server都是master
- slaveof ip port 配置从机
- 全量复制:重新连接时自动全部复制一遍
- 增量复制:master操作一次数据就将数据复制给slave
哨兵模式
多个哨兵集群:
- 如果哨兵发现主机宕机,系统并不会进行failover(故障转移),而是当多个哨兵都检测到不可用才进行failover操作
- 实现投票确定主机宕机,投票出新主机,如果原主机回来,自动成为slave
redis缓存穿透雪崩:
缓存穿透:(大量未命中)
- 布隆过滤器:对所有可能的查询的参数以hash形式存储,再控制层先进行校验,不符合则丢弃
- 首先有一个 n位的数组 key经过 k个hash函数,产生k个值,将数组里面对应的值设置为1,那么如果查询某个key时
经过k个hash运算如有一个对应的数组位为0,则key不存在于数据中 - 缓存空对象:存储层未命中,返回的空值也会缓存起来,并设置过期时间,之后获取数据会从缓存中获取
缓存击穿(大量命中)
缓存雪崩(缓存集中过期,redis宕机)
- 分布式、限流降级(加锁或者队列模式访问)、数据预热(预访问数据,将不同key设置不同过期时间,保证key不会同时过期)
- 设置key随机过期
redis快速的原因
- 是单线程的,是基于内存操作,CPU不是redis性能,redis瓶颈是内存和网络,redis快的理由:基于内存,单线程没有cpu资源切换