第0章NoSQL
非关系型数据库,不仅仅是SQL
为什么用?
1.Memcached+MySQL+垂直拆分
2.MySQL主从复制读写分离
3.分表分库+水平拆分+MySQL集群
特点:
键值对存储;
第一章 Redis简介
1.概述
Redis:Remote Dictionary Server远程字典服务器
开源,C语言写的,遵循BSD协议,是一个高性能key/value分布式内存数据库基于内存运行并支持持久化的NoSQL数据库,是当前最热门的NoSQL数据库之一。
Redis与其他key-value缓存产品有以下三个特点:
- Redis支持数据的持久化,key讲内存中的数据保持在磁盘中,重启的时候key再次加载进行使用;
- Redis不仅仅支持简单的key-value类型数据,同时还提供list、set、zset、hash等数据结构的存储;
- Redis支持数据的备份,即master-slave模式的数据备份。
2.能干啥?
内存存储和持久化:redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务取最新N个数据的操作。
第二章 Redis安装
1.拷贝redis安装包到Linux下的opt目录,然后使用命令:
>Tar -zxvf redis-3.0.1.tar.gz
进入解压后的文件夹
执行make命令
>make
注意:如果Linux环境没有gcc命令,执行make命令时会报错,需要安装gcc环境命令。
然后再初始化
>make install
2.备份redis.conf
编辑myredis 中redis.conf
>vim redis.conf
修改为yes
3.进入bin目录
查看redis是否启动:
>ps -ef|grep redis
启动redis:
>redis-server /myredis/redis.conf
启动redis客户端:
>redis-cli -p 6379
再次查看redis是否启动
测试redis,存放k-v键值对
关闭redis
Shutdown
第三章
Redis的杂项
1.select切换数据库
redis默认设置有16个库,在redis.conf中有设置,
如:选择七号库
获取不到k1的值,k1 是在一号库
2.清空数据库
清空当前数据库数据:flushDB
清空所有数据库数据:flushAll
3.redis索引是从零开始,默认端口是6379
第四章:redis的数据类型
1.redis五大数据类型
Redis字符串(String)
Redis列表(List),有序可重复
Redis集合(Set),是String类型的无序集合,底层是通过hashTable实现的。
Redis哈希(Hash) 类似java的map;
Redis有序集合Zset(sorted set),也是String类型的元素集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数,redis正是通过分数来为集合的成员进行从小到大的排序。Zset的成员是唯一的,但分数score却可以重复。
注:redis比memcached存储的数据类型更丰富。
第六章 redis命令
补充:redis命令参考大全http://redisdoc.com
==》String类型常用命令:
Keys * 查看所有key
Exists key的名字,判断是否有该key
Move key db 移动key到其他库,当前库就没有了,被移除了
Expire key秒钟 :为给定的key设置过期时间
Ttl key 查看几秒过期,-1永不过期 -2已过期
Type key 查看你的key是什么类型
过期的key会被移除内存,不存在了
Set key已经存在的key,会把之前的key覆盖。
Append 追加
Strlen key 查看值的长度
==》List类型常用命令:
LPUSH 正着进,反着出
RPUSH 正着进,正着出
LRANGE key 0 -1 输出key的值
LPOP 移除栈顶的一个
RPOP 移除栈底的一个
注:list类型性能总结,
Left、right都可以插入添加;
如果键不存在,创建新的链表;
如果键已存在,新增内容;
如果值全部移除,对应的键也就消失了;
链表的操作是头尾效率高,但假如对中间元素操作,效率就很惨淡了。
====》set类型
Sadd 添加
Smembers查看
Scard 获取集合元素个数
Srem key value 删除集合中元素
Srandmember key 某个整数(随机出几个数)
Spop key 随机出栈
==》hash类型
注:kv模式不变,但v是一个键值对
Hset 设置值
Hget key1 key2获取值
Hmset (一次)设置更多的值
Hmset (一次)获取更多的值
Hgetall 获取所有
Hdel key1 key2 删除 值
Hlen key 在key里key1的长度
Hexists key 在key里面的某个值的key是否存在
Hkeys key 在key里所有的keys
Hvals key 在key里面所有的value
第七章
解析配置文件redis.conf
- 6种内存策略,缓存过期清除策略Maxmemory-policy
》Volatile-lru 使用lru算法移除key,只对设置了过期时间的键;
》allkeys-lru:使用LRU算法移除key;
》volatile-random:在过期集合中移除随机的key,只对设置了过期时间的键;
》allkeys-random:移除随机的key;
》volatile-ttl:移除那些ttl值最小的key,即那些最近要过期的key;
》noeviction:永不过期,不进行移除。针对写操作,只是返回错误信息。
补充:
第八章 redis持久化
》Rdb:Redis DataBase
》Aof:Append Only File
1)RDB
》概念:
指定的时间间隔内 将内存中的数据集快照写入磁盘;
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都快结束了,再用这个临时文件替换上次持久化好的文件。
整个过程中,主进程是不会进行任何IO操作,这就确保了极高的性能;
如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式比AOF方式更加高效。RDB的缺点是最后一次持久化后的数据可能丢失!
》Fork的作用
复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)数量都和原进程一致,但是是一个全新的进程,并作为原进程的子进程。
》Rdb保存的dump.rdb文件
测试rbd模式 实验:修改持久化策略,120秒,修改十次,自动生成dump.rdb存储文件。 然后先备份dump.rdb文件为dump_bk.rdb文件,再删除dump.rdb文件,flushAll所有数据,这时可以将dump_bk.rdb 拷贝为dump.rdb,重启机器,再查询redis,数据是否可以恢复。模拟数据恢复过程。
将存储策略改为 save 120 10
关闭redis
删除 /usr/local/bin 下dump.rdb文件
启动redis
120秒内修改十次,在120秒后,dump.rdb文件产生。
删除数据flushAll,并关闭redis。(执行flushAll命令,也会产生dump.rdb文件,但里面是空的,无意义。)
重启redis,查看key是否存在(因为dump.rdb是重新生成的,所以数据是空的)
再关闭redis并删除dump.rdb;
拷贝dump_bk.rdb 为dump.rdb,再启动redis
再查询数据后,数据恢复了
Ps:
Rdb默认设置:
下面的例子将会进行把数据写入磁盘的操作:
# 900秒(15分钟)之后,且至少1次变更
# 300秒(5分钟)之后,且至少10次变更
# 60秒之后,且至少10000次变更
#
# 注意:你要想不写磁盘的话就把所有 "save" 设置注释掉就行了。
#
# 通过添加一条带空字符串参数的save指令也能移除之前所有配置的save指令
# 像下面的例子:
# save ""
save 900 1
save 300 10
save 60 10000
》案例:如果想刚set的值,立马生效,可以再加多save命令
查看dump.rdb文件为马上生成的时间。
Rdb小结:
优势:适合大规模的数据恢复,对数据完整性和一致性要求不高。
劣势:redis意外down掉后,会丢失最后一次快照后的所有修改;
Fork克隆线程的时候,内存中的数据被克隆了一份,大致两倍的膨胀性需要考虑。
如何停止:动态所有停止RDB保存规则的方法:redis-cli config save “”
RDB在保存RDB文件时父进程唯一需要做的就是Fork出一个进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis性能。
与AOF相比,在恢复大的数据集的时候,RDB方式会更快些。
2.AOF持久化
概念:
Append only file
以日志的形式来记录每个写操作,将Redis执行过的所以写指令记录下来(有读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
AOF默认情况是关闭的
先拷贝一份redis.conf 为redis_aof.cof
修改aof配置vim redis_aof.conf
进/usr/local/bin目录删除所有的rdb文件
先关闭redis
再重启redis_aof.conf配置文件的redis
就生成了appendonly.aof 文件
如果没有生成aof文件,还是rdb文件的话,需要关闭rdb模式,即输入命令:
>Config set save “”
---------------------------
案例:模拟删除flushAll数据后,删除aof 文件的flushAll (vim模式进入后,摁dd删除当前行),再重启redis,查看数据是否存在;
Ps:dump.rdb和appendonly.aof文件可以同时存在,默认找的appendonly.aof
-----------------------------
案例:模拟在appendonly.aof输入非法的内容,重启redis加载aof文件会报错,这时需要使用check进行排错,再重启redis,测试数据是否可以恢复。
启动失败!
清除错误:
重启,查看恢复:
3.redis.conf配置文件aof部分解读
Appendonly 默认是no
Appendfilename 默认文件名是appendonly.aof
appendfsync策略,三种:
Always:同步持久化 每次发生数据变更会被立即记录到磁盘,性能较差但数据完整;
Everysec:出厂默认推荐,异步操作,每秒记录,如果一秒内宕机,有数据丢失;
No
No-appendfsync-on-rewrite:重写时是否可以运用appendfsync,用默认no即可,保证数据安全性
Auto-aof-rewrite-min-size:设置重写的基准值
Auto-aof-rewrite-percentage:设置重写的基准值
4.rewrite
概念:aof采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制,当aof文件的大小超过了设定的阈值时,redis就会启动aof文件的内容压缩,只保留可以恢复数据的最小指令集,使用命令bgrewriteaof
重写原理:
Aof文件持续增大而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,每条记录有一条的set语句。重写aof文件的操作,并没有读取旧的aof文件而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似。
触发机制:
Redis会记录上次重写时的aof大小,默认配置是当aof文件大小是上次rewrite后大小的一倍且文件大于64M时触发。
5.aof优劣势
优势:
每秒同步:appendfsync always 同步持久化 每次发生数据变更会被立即记录到磁盘,性能比较差但数据完整性好;
每修改同步:appendfsync everysec 异步操作,每秒记录 如果一秒内宕机,有数据丢失
不同步:appendfsync no 从不同步
劣势:相同数据集的数据而已aof文件要远大于rdb文件,恢复速度慢于rdb;
Aof运行效率要慢于rdb,每秒同步策略效率较好,不同步效率和rdb相同。
6.aof小结:
7.总结对比两种持久化机制
RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储;
Aof持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,aof命令以redis协议追加保存每次写的操作到文件末尾,reids还能对aof文件进行后台的重写,使得aof文件的体积不至于过大。
如果只做缓存:可以不用任何持久化方式;
也可以同时开启两种持久化方式:
这种情况,会优先载入aof文件来恢复原始数据,aof文件保存的数据集要比RDB文件保存的数据集要完整。
为什么同时开启?因为RDB更适合用于备份数据库(aof在不断变化不好备份),快速重启,而且不会有aof可能潜在的bug,留着作为一个万一的手段。
第九章 redis事务
1.概念:
可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其他命令插入,不许加塞。
2.常用命令:
>discard取消事务,放弃执行事务块内的所有命令;
>exec 执行所有事务块的命令
>multi标记一个事务的开始
>unwatch取消watch命令对所有key的监视
>watch key 监视一个或多个key,如果在事务执行之前这个或这些key被其他命令改动,那么事务将被打断。
3.watch监控
》悲观锁
Pessimistic lock 就是每次取拿数据的时候都会认为别人会改数据,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁、表锁等,读锁、写锁等,都是在做操作之前先上锁。
》乐观锁
Optimisstic lock 就是每次取拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有取更新数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。
乐观锁策略:提交版本必须大于记录当前版本才能执行更新。
》CAS(check and set)
4.小结:
Watch指令,类似乐观锁,事务提交时,如果key的值已被别的客户端改变,比如某个list已被别的客户端push/pop过了,整个事务队列都会被执行。
通过watch命令在事务执行之前监控了多个keys,倘若watch之后有任何key的值发生了变化,exec命令执行的事务都将被放弃,同时返回nullmulti-bulk答应以通知调用者事务执行失败。
5.三个特性:
单独的隔离操作:事务中的所有命令都会序列化,按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断;
没有隔离级别概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行。也就不存在“事务内的查询要看到事务里的更新,在事务外查询不能看到”这个让人万分头疼的问题;
不保证原子性:redis同一个事务中如果有一个事务中如果有一条命令执行失败,其中的命令仍然会被执行,没有回滚。
第十章 redis的发布订阅
1.概念:
进程间的一种消息通信模式:发送者pub发送消息,订阅者sub接收消息。
Ps:实际工作中,不会用redis做消息中间件!
2.案例
打开另一个客户端:
通配符,订阅多个
第十一章redis的复制(Master/Slave)
1.概念:
就是主从复制,主机数据更新后根据配置和策略,自动同步到备机master/slaver机制,Master以写为主,Slave以读为主。
2.作用:读写分离;容灾备份
3.怎么玩:
》配从(库)不配主(库)
》从库配置:slaveof主库ip 主库端口
每次与master断开后,都需要重新连接,除非你配置进redis.conf文件info replication
》修改配置文件细节操作:
》拷贝多个redis.conf文件
》开启daemonize yes
》pid文件名称
》log文件名称
》Dump.rdb名字
(1)用命令来配置主从
步骤1:
先拷贝三份redis.conf,分别命名:redis6379.conf redis6380.conf redis6381.conf
分别编辑conf文件 修改相应信息
步骤2:
常用三招:
分别启动三台redis,配置一主两从
新开个窗口在/usr/local/bin下可以看到6380.log日志
查看redis状态信息:
6379先设置几个值
配置6380redis角色
配置6381
这时6379再设值
另外两台redis获取值
是否成功?成功
同时也可以获取k4之前的数据
》查看6379的设置信息
查看6380设置信息
问题1:从机只能读操作
问题2:主机宕掉,从机可以写吗?设置信息会如何变化?
6379宕机
查看从机状态
状态还是从机!
也不可以写!
只会原地待命,等主机恢复正常!
问题3:从机宕机,能不能续接上配置信息
6379在从机宕机后再新设置值
启动6380,查看是否获取得到k8,并查看配置信息:
从机变为了master了,而且获取不到值!
这时需要重新配置,除非把配置信息配置到redis.conf文件中!
Ps:重新配置6380
第二招:薪火相传:上一个slave可以是下一个slave的master,slave同样可以接收其他slaves的连接和同步请求,那么该slave作为了链条中下一个master,可以有效减轻master的写压力;
中途变更转向:会清除之前的数据,重新建立拷贝最新的slaveof新主库ip新主库端口!
》将6381设置为6380的从机,6380设置为6379的从机
6379新增k9,6381也可以获取得到k9
查看6380状态信息:
角色还是slave,但也是6381的主机
问题:如果6379主机宕掉,在2台从机中重新设置主从,当6379主机重新恢复后,关系会变回原来的一样吗,6379还是另外2台的主机吗?
恢复6381为6379的从机
6379宕机
6380设置slaveof no one
6381设为6380的从机,并获取6380新增的值k10
6379再启动后,不能成为主机了!
也就是第三招:反客为主,slave no one 使当前数据库停止与其他数据库的同步,转成主数据库!
4.redis复制原理
Slave启动成功连接到master后会发送一个sync命令
Master接到命令启动后台的存盘进程,同时收集所有接收到的用户修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步;
全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中;
增量复制:master继续将新的所有收集到的修改命令依次传给slave,完成同步,但是只要重新连接master,一次完全同步(全量复制)将被自动执行。
5.哨兵模式(重要)
哨兵模式是反客为主的自动版!
1)概念:能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
2)哨兵模式搭建步骤
步骤1:
准备工作:Ps:将6380 6381恢复为6379的从机。
在myredis目录下,新建sentinel.conf文件
在sentinel.conf 编辑内容:
Sentinel monitor 被监控主机名字(自己起名) 127.0.0.1 6379 1
上面最后的数字1,表示主机挂掉后slave投票看让谁接替成为主机,得票数多少后成为主机(如果两者票数同样,会重新选举)。6379表示主机的端口。
步骤2:
进/usr/local/bin目录下 启动哨兵
>redis-sentinel /myredis/sentinel.conf
监控着6380 6381端口
步骤3:模拟6379宕机后,两台从机投票选举
这时哨兵监控会自动投票
6381为主机了
6380为从机,主机已经变成6381
6381新增k11值
6380获取
这时6379恢复正常,哨兵会将其设为从机,也可以获取到k11的值
小结:
复制延时:由于所有的写操作都是先在master上操作,然后同步更新到slave上,所以从master同步到slave机器具有一定的延迟,忙的时候,延迟问题会更加严重,slave机器数量的增加也会使这个问题更加严重!
第十二章 redis代码操作
1.新建web工程,测试连通性
导包
测试:ip为linux地址
public class TestPing {
public static void main(String[] args) {
Jedis jedis = new Jedis("192.168.226.128",6379);
System.out.println(jedis.ping());
jedis.set("k1", "101");
jedis.set("k2", "102");
jedis.set("k3", "103");
Set<String> keys = jedis.keys("*");
System.out.println(keys);
}
}
//结果返回PONG,正常
Ps:如果连接不上,可能是linux防火墙没打开6379端口
这时:可查看https://blog.youkuaiyun.com/Odada1710/article/details/88381457
进行设置。
2.jedis事务
3.jedis主从复制
准备:6379 6380 先各自独立,slaveof no one,都是设置为主,把哨兵关掉。
/**
* 设置主从
* @author dada
*
*/
public class TestMS {
public static void main(String[] args) {
//6379主
Jedis jedis_m = new Jedis("192.168.226.128",6379);
Jedis jedis_s = new Jedis("192.168.226.128",6380);
//配6380从
jedis_s.slaveof("192.168.226.128", 6379);
//主机写
jedis_m.set("key1", "myMaster11111111");
//从机获取
String result = jedis_s.get("key1");
System.out.println(result);//如果获取不到,因为内存速度太快,需要等待一下,再次获取即可
}
}
Linux中也可获取
6380
6379
4.redis连接池
工具类:
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class JedisPoolUtils {
private static volatile JedisPool jedisPool = null;
private JedisPoolUtils() {
}
public static JedisPool getJedisPoolInstance() {
if (null == jedisPool) {
synchronized (JedisPoolUtils.class) {
if (null == jedisPool) {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxActive(1000);//可分配实例
poolConfig.setMaxIdle(32);//最大空闲实例
poolConfig.setMaxWait(60*1000);//最大等待时间60s
poolConfig.setTestOnBorrow(true);//是否检查连接可以性,为true,得到的jedis实例均是可用的
jedisPool = new JedisPool(poolConfig, "192.168.226.128", 6379);
}
}
}
return jedisPool;
}
//连接实例释放回连接池
public static void release(JedisPool jedisPool,Jedis jedis) {
if(null !=jedis) {
jedisPool.returnResourceObject(jedis);
}
}
//还需要考虑并发等。。。。
}
测试类:
package com.atguigu.redis.test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
/**
* 测试连接池
*
* @author dada
*
*/
public class TestPool {
public static void main(String[] args) {
JedisPool jedisPool = JedisPoolUtils.getJedisPoolInstance();
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.set("keyPool", "pool007");
System.out.println("设置完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
JedisPoolUtils.release(jedisPool, jedis);
}
}
}
//linux中
6379
6380