前言
关系型数据库:ACID(atomicity,consistancy,isolation,durability)
非关系型数据库:分布式架构,CAP(consistency强一致性,availability高可用性,partition tolerance分区容忍性),三者最多只能满足两个,其中,P必需,一般A/C中选1。 AP是大多数网站架构的选择(强一致性不重要),CP的典型例子是 redis/mongodb。
特点:AP + BASE(basically available基本可用,soft state软状态,eventually consistent最终一致。)
分类:
1.kv键值型:redis
2.文档型:mongodb
3.列存储:HBase
4.图关系型:
redis(remote dictionary server):高性能K/V分布式内存数据库。
特点:持久化;支持多种数据类型;master-slave的数据备份;异步保存数据到硬盘;设置过期时间等
提供的服务种类:
redis-server 启动redis服务器
redis-cli 作为客户端调用redis
redis-benchmark 测试
redis-sentinel 作为哨兵对主从复制进行自动监控
特点:
1.单进程 :epoll包装请求
2.默认16个库 select
常用命令:
exists key1:key1是否存在
move key1 1:将key1移动到db1
expire key1 10: key1的过期时间为10s
ttl key1:key1还有多久过期,-1永不过期,-2已过期
type key1:查看key1的类型
DBSIZE:当前db的key数量
FLUSHDB:清空单个db的key
FLUSHALL:清空所有db的key
数据类型:
String:一个key对应一个value,二进制安全,可以包含任何数据(如图片/序列化对象)
set/get/append/strlen
incr/decr/incrby/decrby(对string的数字才有效)
getrange/setrange
setex(set with expire)/setnx(set if not exist)
mset/mget:同时操作多个
List:底层是链表,前后都可添加数据,一个key对应多个value
lpush/rpush/lrange(查看range内的值)
lpop/rpop
lindex(按下标获取元素)
lrem:lrem key1 2 3(删除key1中的2个3)
ltrim:ltrim key1 2 4(从key1中获取2 4 两个index范围内的值并重新赋给key1)
rpoplpush key1 key2(rpop key1的值,lpush到key2)
lset key index value
insert key before/after value
值若全移除,则键消失
两端操作快,中间操作慢
Set:无序集合,一个key对应多个value
sadd/smembers(查看所有成员)/sismember(是否是成员)
scard:获取集合的元素个数
srem :删除某个元素
srandmember:随机获取一个值(不出栈)
spop:随机出栈
sdiff key1 key2:差集,在key1中,但不在key2中
sinter key01 key02:交集
sunion key01 key02:并集
Hash:键值对集合,key/value模式不变,但value是键值对
hset/hget/hmset/hmget/hgetall/hdel
hlen
hexists key keykey:key对应的hash中是否有keykey对应的值
hkeys/hvals
hincrby/hincrbyfloat
hsetnx(set if not exist)
ZSet:每个元素关联一个double类型的分数,通过分数排序;成员唯一但分数(score)可以重复。
在set基础上增加一个score,之前set是 sadd k1 v1 v2 v3,现在是zadd k1 score1 v1 score2 v2 score3 v3
zadd zset01 50 v1 60 v2 70 v3 80 v4(50:分数 v1:值)
zrange zset01 0 -1 withscores
zrangebyscore zset01 60 90:获取zset01中分数在60 - 90之间的值
zrangebyscore zset01 60 (90:获取zset01中分数在60 - 90之间的值,不包含90
zrangebyscore zset01 60 90 limit 1 2:获取zset01中分数在60 - 90之间的值,从结果中索引为1 处获取2个值返回
zrem zset01 v6:删除
zcard zset01 :获取总个数
zcount zset01 60 80:统计个数
zrevrange:与zrange反序
redis.conf配置文件结构:
一,介绍等;
二,general通用
1.redis默认不以守护进程的方式进行,改为yes可启用守护进程
daemonize no
2.redis以守护进程运行时,pid会被写入到/var/run/redis.pid中,可通过pidfile修改
pidfile /var/run/redis.pid
3.监听端口
port 6379
4.绑定主机地址
bind 127.0.0.1
5.客户端闲置多长时间后关闭连接,指定为0则关闭该功能
timeout 300
6.指定redis日志级别,支持debug/verbose/notice/warning四个级别,默认verbose
loglevel verbose
7.日志记录方式,默认标准输出。redis为守护进程且输出方式为标准输出时,日志将发送到/dev/null
logfile stdout
8.设置数据库数量,默认16个,使用数据库0
database 16
9.指定本地数据库存放目录,不同位置启动redis,存放位置不同
dir /.
10.同一时间最大客户端连接数
maxclients 128
redis持久化:
存在rdb和aof两种存储模式。
RDB主线程不负责IO(所以性能极高),会fork()一个线程单独进行快照文件的备份,每到时间间隔形成一个快照文件(dump.rdb格式),并用这个文件替换之前的快照文件。如果需要进行大规模的数据恢复,且恢复的完整性相对不敏感的情况下,性能优于AOF,但最后一次持久化的数据可能丢失。
RDB
rdb(redis database):指定时间间隔内将内存中的数据集快照(snapshot快照)写入磁盘,恢复时将快照文件读回磁盘。
redis.conf文件中 SNAPSHOT板块对存储策略进行了设置,如save 300 10,300s内存在10次以上key的修改,则进行一次存储。
此处修改为每120秒超过十次更改,则生成一次dump.rdb文件。dump.rdb文件存储位置可在redis.conf中修改,新地址为图示第二列。
执行命令:
生成文件如下:
将该文件拷贝,删除源文件与redis中的数据,重新启动redis-server,可以看到没有数据:
将dump.rdb复制到原位置,重启redis-server,并执行keys *,数据已恢复:
快照生成:1.按照.conf的默认配置进行(定期冷拷贝到其他机器,可对数据做备份);2.save(同步阻塞保存)或bgsave(异步保存)。
快照恢复:备份文件移动到redis安装目录(即.conf中dir 所配置的位置)并启动服务。
优劣:适合大规模的数据恢复,数据完整性和一致性要求不高,但由于定期做备份的策略,机器意外停止时,最后一次的信息会丢失部分,另外fork会导致数据膨胀一倍。
aof(append only file)
记录redis执行过的所有写指令(不记录读指令),只追加不改写,redis启动时读取该文件(appendonly.aof格式),根据文件内容执行所有指令以恢复数据。
.conf中的append only mode中
手动编辑.aof文件,重新运行server,数据恢复:
.rdb和.aof同时存在时,优先通过.aof恢复数据。
因为意外原因导致的.aof乱码可通过redis-check-aof --fix appendonly.aof修复。
.conf中append only mode可对aof进行设置:
appendonly yes为配置开启;
appendfsync配置持久化策略(always:同步持久化,记录每次的数据变更,性能较差但完整;
everysec:出场默认设置,异步,每秒记录)
auto-aof-rewrite-percentage :重写基准值设置
auto-aof-rewrite-min-size :重写基准值设置
aof的rewrite机制:aof采取文件追加机制,文件越来越大,当大小超过阈值时,redis启动aof文件的内容压缩,通过fork新进程对文件进行重写,保留可恢复数据的最小指令集。默认文件是上一次的一倍或者超过64M时,进行触发。
优劣:aof文件有序保存指令,易读易分析,但数据太大,效率慢
官网建议:
RDB定期存储数据快照,AOF记录操作指令。如果只希望在服务运行时存在数据, 持久化也可省略。
建议同时开启这两种持久化方式:redis重启时优先获取AOF文件,AOF文件比RDB更完整;但也不要只使用AOF,因为RDB更适合作为备份数据库,AOF变化快,且有潜在bug,RDB可作为万一的手段。
性能建议:
RDB作为后备,建议只在slave上持久化RDB文件,15分钟一次即可(只保留save 900 1这条);
enable AOF的话,最坏情况也不会丢失超过两秒的数据,启动脚本简单(只load AOF文件即可),但存在持续IO,且rewrite时新数据写到新文件必然存在阻塞,所以应该减小IO频率,改大rewrite的大小阈值(如5G);
不enable AOF的话,仅靠主从架构也可实现高可用,节省IO和rewrite带来的阻塞和系统波动,但master/slave同时倒下时会丢失十几分钟的数据,启动脚本的时候也需要比较主从中的两个RDB文件,载入较新的那个。
redis的事务:
一次处理多条redis命令(序列化),串行,不被打扰。不支持原子性(部分支持,下面场景4即不支持)
事务命令:discard:取消事务,放弃所有命令
exec:执行事务中的所有命令
multi:标记一个事务块的开始
unwatch:取消watch命令对所有key的监控
watch key[key...]:监控一个/多个key,key被修改则事务被打断
场景:
1.执行事务
2.放弃事务
3.全体连坐:一条命令出错,所有命令不被执行。类似于受查一场,指令出错,入栈时已经失败
4.冤头债主:类似于运行时异常,运行时才发现错误,但指令入栈没问题。因此redis对事务是部分支持。
5.watch监控:
悲观锁:很悲观,认为每次拿数据时别人都会修改,因此先加锁再更改,如行锁,表锁
乐观锁:很乐观认为每次拿数据时别人都不会修改,因此先更改,每版信息都有一个version信息,提交之前进行对比,大于存储的版本信息才会进行存储,典型算法如CAS
watch对特定key进行监控,类似于乐观锁,执行multi时如果其他人对这个key做了更改,执行时会返回nil。
开启:muti
入队:命令入队,先不执行,放到执行队列中
执行:exec命令触发(执行exec后,之前添加的锁全部失效)
发布订阅
不常用
支持通配符:
主从复制(master/slave)
主机数据更新后,备机根据策略进行备份,master以写为主,slave以读为主。
作用:读写分离,容灾恢复。
用法:1.配从(库)不配主(库):salveof 主库IP 主库端口,每次与master断开后,需要重新进行连接,除非写入到.conf中。
左 6379 主 中 6380 从 右 6381 从
常用方法:
1.一主二仆。先set k1 k2 k3,后执行salveof,再set k4。
1.1.成为从机后,主机所有数据都会被备份
1.2.从机无法写入,只能读取
1.3.主机shutdown后,从机依然时slave
1.4.主机重新连接时,从机依然为slave
1.5.一个从机死亡时,其他从机正常工作,死亡的从机重连后变为master,不再保存有之前主机的数据(除非从机对应配置了.conf文件)
1.6.从机第一次连接时,全量复制主机数据,后面存量复制。
2.薪火相传:去中心化,上一个slave是下一个slave的master(类似链表),中途变更转向会清除之前的数据,以保存master的数据。以一串server中只有一个master,其他都是slave。
3.反客为主
主机挂掉后,从机可以作为新的其他主机的master,master重新连接后也不再拥有从机(slaveof no one :使当前数据库停止与其他数据库的同步)
#复制原理:第一次全量复制,之后增量复制(只要重新连接到master,都会触发一次全量复制)。
slave连接到master后会发送一次sync命令,master接收到命令后启动后台存盘进行,并收集修改数据集的命令,执行完毕后将命令文件传送到slave,以完成完全同步。slave接收到文件后将存盘加载到内存,完成第一次连接时的全量复制;之后master执行的命令,slave都会进行增量复制。
哨兵模式:自动模式的反客为主。
.conf所在文件夹下新建sentinel.conf文件,【sentinel monitor
通过redis-sentinel *.conf 启动哨兵模式。
主机挂掉后,从机经过投票选择出一个新的master
主机79重新连接后,哨兵监控到这一信息,将使79成为新master的一台从机
缺点:写操作在master,读操作在slave,存在读写延迟
Jedis:
windows下用idea连接,报错connection refused:connect
解决:redis.conf中注释掉bind 127.0.0.1这一行,重新启动后又报错:
redis.clients.jedis.exceptions.JedisDataException: DENIED Redis is running in protected mode because protected mode is enabled
解决:redis.conf中修改protected-mode 为yes
问题解决
jedis调用api演示:
```
@Test
void contextLoads() {
Jedis jedis =new Jedis("192.168.64.128", 6379);
jedis.flushAll();
System.out.println("String");
jedis.set("k1", "v1");
jedis.set("k2", "v2");
jedis.set("k3", "v3");
System.out.println(jedis.keys("*"));
// list
System.out.println("List");
jedis.lpush("mylist", "v1", "v2", "v3");
System.out.println(jedis.rpop("mylist"));
jedis.lpush("mylist", "v4", "v5", "v6");
jedis.ltrim("mylist", 1, 4);
System.out.println(jedis.lrange("mylist", 0, -1));
//set
System.out.println("Set");
jedis.sadd("myset","1","2","3","4");
System.out.println(jedis.scard("myset"));
System.out.println("myset members" + jedis.smembers("myset"));
System.out.println(jedis.sismember("myset", "`10"));
jedis.sadd("myset1","5","6","3","4");
System.out.println(jedis.sinter("myset", "myset1"));
// hash
System.out.println("Hash");
jedis.hset("myhash","k1", "v1");
Map map =new HashMap<>();
map.put("k2","v2");
map.put("k3","v3");
map.put("k4","v4");
jedis.hmset("myhash", map);
System.out.println(jedis.hmget("myhash","k2","k3"));
// zset
System.out.println("Zset");
jedis.zadd("myzset",60, "v1");
jedis.zadd("myzset",80, "v2");
jedis.zadd("myzset",90, "v3");
jedis.zadd("myzset",70, "v4");
System.out.println(jedis.zrange("myzset", 0, 2));
System.out.println(jedis.zrangeByScore("myzset", 70, 90));
System.out.println(jedis.zcount("myzset", 70, 90));
}
```
结果:
jedis事务演示:
```java
@Test
void contextLoads()throws InterruptedException {
Jedis jedis =new Jedis("192.168.64.128", 6379);
//jedis.flushAll();
System.out.println( transactionMethod(jedis));
}
private boolean transactionMethod(Jedis jedis)throws InterruptedException {
int cost =10;
int balance = Integer.parseInt( jedis.get("balance"));
int debt = Integer.parseInt( jedis.get("debt"));;
System.out.println(balance);
System.out.println(debt);
jedis.watch("balance");
Thread.sleep(20000);
if(cost > balance) {
jedis.unwatch();
System.out.println("modified");
return false;
}else {
System.out.println("transaction");
Transaction transaction = jedis.multi();
transaction.decrBy("balance", 10);
transaction.incrBy("debt", 10);
transaction.exec();
balance = Integer.parseInt( jedis.get("balance"));
debt = Integer.parseInt( jedis.get("debt"));
System.out.println(balance);
System.out.println(debt);
return true;
}
```
balance设定为100,debt设定为0, 先对balance进行watch,程序sleep()时对balance进行更改,改为40,事务失败,返回null,控制台如下:
主从复制演示:
```java
@Test
void contextLoads()throws InterruptedException {
Jedis jedis_M =new Jedis("192.168.64.128", 6379);
Jedis jedis_S =new Jedis("192.168.64.128", 6380);
jedis_S.slaveof("192.168.64.128", 6379);
jedis_M.set("k1", "v1");
System.out.println(jedis_S.get("k1"));
}
```
输出可能为v1,可能为空(内存读写比较快,在程序中sleep一段时间就能正常获取到v1)
jedis连接池演示:
```java
public class JedisPoolUtil {
private JedisPoolUtil() {
}
private static volatile JedisPooljedisPool;
public static JedisPoolgetInstance() {
if(jedisPool ==null) {
synchronized (JedisPool.class) {
if(jedisPool ==null) {
JedisPoolConfig poolConfig =new JedisPoolConfig();
poolConfig.setMaxTotal(1000);
poolConfig.setMaxIdle(500);
poolConfig.setMaxWaitMillis(100 *1000);
// 获得新连接时是否检测可用性(ping)
poolConfig.setTestOnBorrow(true);
jedisPool =new JedisPool(poolConfig, "192.168.64.128", 6379);
}
}
}
return jedisPool;
}
public static void release(JedisPool jedisPool, Jedis jedis) {
if(jedis !=null) {
jedisPool.returnResourceObject(jedis);
}
}
}
```
测试代码:
```java
@Test
void contextLoads()throws InterruptedException {
JedisPool jedisPool1 = JedisPoolUtil.getInstance();
JedisPool jedisPool2 = JedisPoolUtil.getInstance();
assert jedisPool1 == jedisPool2;
Jedis jedis =null;
try {
jedis = jedisPool1.getResource();
jedis.set("aa", "bb");
}catch (Exception e){
}finally {
JedisPoolUtil.release(jedisPool1,jedis);
}
}
```
结果:
注:本文为redis学习课程的总结笔记
课程链接:https://www.bilibili.com/video/BV1oW411u75R?p=28