一、Redis:REmote DIctionary Server(远程字典服务器) 是完全开源免费的,用C语言编写的,遵守BSD协议,是一个高性能的(key/value)分布式内存数据库,基于内存运行并支持持久化的NoSQL数据库,是当前最热门的NoSql数据库之一,也被人们称为数据结构服务器。
1)、Redis 与其他 key - value 缓存产品相比有以下三个特点:
♠ Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。
♠ Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
♠ Redis支持数据的备份,即master-slave模式的数据备份
2)、Redis的作用:
♣ 内存存储和持久化:Redis支持异步将内存中的数据写入硬盘,同时不影响执行中的服务。
♣ 取最新N个数据的操作,如:可以将最新的10条评论的ID放在Redis的List集合里面。
♣ 模拟类似于HttpSession这种需要设定过期时间的功能。
♣ 发布、订阅消息系统。
♣ 定时器、计数器
3)、Redis下载地址:
☞ http://redis.io/
☞ http://www.redis.cn/
4)、Redis主要功能:
♧ 数据类型、基本操作和配置。
♧ 持久化和复制,RDB/AOF。
♧ 事务的控制。
♧ 复制。
5)、Redis的安装:Linux版安装
♤ 下载获得redis-3.0.4.tar.gz后将它放入我们的Linux目录/opt
♤ /opt目录下,解压命令:tar -zxvf redis-3.0.4.tar.gz
♤ 解压完成后出现文件夹:redis-3.0.4
♤ 进入目录:cd redis-3.0.4
♤ 在redis-3.0.4目录下执行make命令
6)、运行make命令时,如果出现未安装C/C++编译器的错误时,处理如下:
安装gcc
能上网:yum install gcc-c++
二次make
jemalloc/jemalloc.h:没有那个文件或目录
运行make distclean之后再make
Redis Test(可以不用执行)
如果make完成后继续执行make install
查看默认安装目录:usr/local/bin(有事默认在解压目录的src中,我们通常会将需要的文件拷贝到自己的新建的目录中,通过:ln -s /usr/myredis/redis-server /usr/bin/redis-server 进行连接,才可以进行启动)
- redis-benchmark:性能测试工具,可以在自己本子运行,看看自己本子性能如何
- 服务启动起来后执行
- redis-check-aof:修复有问题的AOF文件,rdb和aof后面讲
- redis-check-dump:修复有问题的dump.rdb文件
- redis-cli:客户端,操作入口
- redis-sentinel:redis集群使用
- redis-server:Redis服务器启动命令
启动后:
- 修改redis.conf文件将里面的daemonize no 改成 yes,让服务在后台启动
- 将默认的redis.conf拷贝到自己定义好的一个路径下,比如/myconf
- 启动(redis-server redis.conf redis-cli -p 6379)
- /usr/local/bin目录下运行redis-server,运行拷贝出存放了自定义conf文件目录下的redis.conf文件
- 永远的helloworld(测试是否联通:输入:ping 如果返回PONG即成功)set k hello
- 关闭(shutdown exit)
- 单实例关闭:redis-cli shutdown
- 多实例关闭,指定端口关闭:redis-cli -p 6379 shutdown
2、Redis启动后基础知识讲解
单进程:单进程模型来处理客户端的请求。对读写等事件的响应。是通过对epoll函数的包装来做到的。Redis的实际处理速度完全依靠主进程的执行效率。
epoll是Linux内核为处理大批量文件描述符而作了改进的epoll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。
默认16个数据库,类似数组下表从零开始,初始默认使用零号库
select命令切换数据库
dbsize查看当前数据库的key的数量
flushdb:清空当前库
Flushall;通杀全部库
统一密码管理,16个库都是同样密码,要么都OK要么一个也连接不上
Redis索引都是从零开始
为什么默认端口是6379(百度)
3、Redis的五大数据类型:
- string(字符串)
- hash(哈希,类似java里的Map)
- list(列表)
- set(集合)
- zset(sorted set:有序集合)
哪里去获得redis常见数据类型操作命令:http://redisdoc.com/
Redis 键(key):
- keys *
- exists key的名字,判断某个key是否存在
- move key db --->当前库就没有了,被移除了
- expire key 秒钟:为给定的key设置过期时间
- ttl key 查看还有多少秒过期,-1表示永不过期,-2表示已过期
- type key 查看你的key是什么类型
Redis字符串(String):
- set/get/del/append/strlen
- Incr/decr/incrby/decrby,一定要是数字才能进行加减
- getrange/setrange
- setex(set with expire)键秒值/setnx(set if not exist)
- mset/mget/msetnx
- getset(先get再set)
Redis列表(List):
- lpush/rpush/lrange
- lpop/rpop
- lindex,按照索引下标获得元素(从上到下)
- llen
- lrem key 删N个value
- ltrim key 开始index 结束index,截取指定范围的值后再赋值给key
- rpoplpush 源列表 目的列表
- lset key index value
- linsert key before/after 值1 值2
Redis集合(Set):
- sadd/smembers/sismember
- scard,获取集合里面的元素个数
- srem key value 删除集合中元素
- srandmember key 某个整数(随机出几个数)
- spop key 随机出栈
- smove key1 key2 在key1里某个值 作用是将key1里的某个值赋给key2
数学集合类:
- 差集:sdiff
- 交集:sinter
- 并集:sunion
Redis哈希(Hash)
- hset/hget/hmset/hmget/hgetall/hdel
- hlen
- hexists key 在key里面的某个值的key
- hkeys/hvals
- hincrby/hincrbyfloat
- hsetnx
Redis有序集合Zset(sorted set)
多说一句:在set基础上,加一个score值。
- 之前set是k1 v1 v2 v3,
- 现在zset是k1 score1 v1 score2 v2
- zadd/zrange
- withscores
- zrangebyscore key 开始score 结束score
- withscores
- ( 不包含
- limit 作用是返回限制
- limit 开始下标步 多少步
- zrem key 某score下对应的value值,作用是删除元素
- zcard/zcount key score区间/zrank key values值,作用是获得下标值/zscore key 对应值,获得分数
- zrevrank key values值,作用是逆序获得下标值
- zrevrange
- zrevrangebyscore key 结束score 开始score
4、redis的持久化:
- RDB(Redis DataBase): 在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方
式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。 - Fork:fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程
rdb 保存的是dump.rdb文件
如何触发RDB快照:配置文件中默认的快照配置。冷拷贝后重新使用可以cp dump.rdb dump_new.rdb。命令save或者是bgsave
Save:save时只管保存,其它不管,全部阻塞
BGSAVE:Redis会在后台异步进行快照操作,
快照同时还可以响应客户端请求。可以通过lastsave
命令获取最后一次成功执行快照的时间
执行flushall命令,也会产生dump.rdb文件,但里面是空的,无意义
如何恢复:将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可
CONFIG GET dir获取目录 - 优势:
- 适合大规模的数据恢复
- 对数据完整性和一致性要求不高
- 劣势:在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改。 fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
- 如何停止:动态所有停止RDB保存规则的方法:redis-cli config set save ""
- AOF(Append Only File): 以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
Aof保存的是appendonly.aof文件
- AOF启动/修复/恢复
正常恢复
启动:设置Yes:修改默认的appendonly no,改为yes
将有数据的aof文件复制一份保存到对应目录(config get dir)
恢复:重启redis然后重新加载
异常恢复
启动:设置Yes
修改默认的appendonly no,改为yes
备份被写坏的AOF文件
修复:
redis-check-aof --fix进行修复
恢复:重启redis然后重新加载
- rewrite是什么:AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof
- 重写原理: AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似
- 触发机制: Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发
优势
每修改同步:appendfsync always 同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好
每秒同步:appendfsync everysec 异步操作,每秒记录 如果一秒内宕机,有数据丢失
不同步:appendfsync no 从不同步
劣势
相同数据集的数据而言aof文件要远大于rdb文件,恢复速度慢于rdb
aof运行效率要慢于rdb,每秒同步策略效率较好,不同步效率和rdb相同
总结(Which one):
- RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储
- AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些,命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
- 只做缓存:如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.
- 同时开启两种持久化方式: 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整. RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件。
- 那要不要只使用AOF呢?作者建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的bug,留着作为一个万一的手段。
5、Redis的事务:可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞 一个队列中,一次性、顺序性、排他性的执行一系列命令
常用命令:
case1:正常执行(MUTL EXEC)
Case2:放弃事务(DISCARD)
Case3:全体连坐(当语法有错时,一条都不成功)
Case4:冤头债主(当时编译时出错,部分可以成功)
Case5:watch监控
悲观锁/乐观锁/CAS(Check And Set)
悲观锁:锁整张表。
乐观锁:锁一行数据。
小结:Watch指令,类似乐观锁,事务提交时,如果Key的值已被别的客户端改变,比如某个list已被别的客户端push/pop过了,整个事务队列都不会被执行,通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化,EXEC命令执行的事务都将被放弃,同时返回Nullmulti-bulk应答以通知调用者事务执行失败
3阶段
开启:以MULTI开始一个事务
入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面
执行:由EXEC命令触发事务
3特性
单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”这个让人万分头痛的问题
不保证原子性:redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
6、Redis的复制(Master/Slave):就是我们所说的主从复制,主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主
- 读写分离
- 容灾恢复
配置:
配从(库)不配主(库)
从库配置:slaveof 主库IP 主库端口
每次与master断开之后,都需要重新连接,除非你配置进redis.conf文件
info replication
修改配置文件细节操作:
- 拷贝多个redis.conf文件
- 开启daemonize yes
- pid文件名字
- 指定端口
- log文件名字
- dump.rdb名字
常用3招:
- 一主二仆: 一个Master两个Slave
- 薪火相传:上一个Slave可以是下一个slave的Master,Slave同样可以接收其他slaves的连接和同步请求,那么该slave作为了链条中下一个的master,可以有效减轻master的写压力。中途变更转向:会清除之前的数据,重新建立拷贝最新的slaveof 新主库IP 新主库端口。
- 反客为主:SLAVEOF no one。使当前数据库停止与其他数据库的同步,转成主数据库
复制原理:
- slave启动成功连接到master后会发送一个sync命令
- Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步
- 全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
- 增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步
- 但是只要是重新连接master,一次完全同步(全量复制)将被自动执行
* 哨兵模式(sentinel):反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库
使用步骤:
- 调整结构,6379带着80、81
- 自定义的/myredis目录下新建sentinel.conf文件,名字绝不能错
- 配置哨兵,填写内容:sentinel monitor 被监控数据库名字(自己起名字) 127.0.0.1 6379 1。上面最后一个数字1,表示主机挂掉后salve投票看让谁接替成为主机,得票数多少后成为主机
- 启动哨兵:redis-sentinel /myredis/sentinel.conf 。
上述目录依照各自的实际情况配置,可能目录不同
正常主从演示
原有的master挂了
投票新选
重新主从继续开工,info replication查查看
问题:如果之前的master重启回来,会不会双master冲突?会成为slave。
一组sentinel能同时监控多个Master
复制的缺点:由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。
7、JedisPool
获取Jedis实例需要从JedisPool中获取
用完Jedis实例需要返还给JedisPool
如果Jedis在使用过程中出错,则也需要还给JedisPool
案例见代码:
JedisPoolUtil:
package com.atguigu.redis.test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class JedisPoolUtil {
private static volatile JedisPool jedisPool = null;//被volatile修饰的变量不会被本地线程缓存,对该变量的读写都是直接操作共享内存。
private JedisPoolUtil() {}
public static JedisPool getJedisPoolInstance()
{
if(null == jedisPool)
{
synchronized (JedisPoolUtil.class)
{
if(null == jedisPool)
{
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxActive(1000);
poolConfig.setMaxIdle(32);
poolConfig.setMaxWait(100*1000);
poolConfig.setTestOnBorrow(true);
jedisPool = new JedisPool(poolConfig,"127.0.0.1");
}
}
}
return jedisPool;
}
public static void release(JedisPool jedisPool,Jedis jedis)
{
if(null != jedis)
{
jedisPool.returnResourceObject(jedis);
}
}
}
Demo5:
package com.atguigu.redis.test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class Test01 {
public static void main(String[] args) {
JedisPool jedisPool = JedisPoolUtil.getJedisPoolInstance();
Jedis jedis = null;
try
{
jedis = jedisPool.getResource();
jedis.set("k18","v183");
} catch (Exception e) {
e.printStackTrace();
}finally{
JedisPoolUtil.release(jedisPool, jedis);
}
}
}
jedisPool.getResource();