Redis
什么是NoSQL?
NoSQL是 Not Only SQL, 即不仅仅是SQL,泛指非关系型数据库。
NoSQL不依赖业务逻辑方式存储,以K-V模式存储.
不遵循SQL标准
不支持ACID,即事务
性能高于SQL
NoSQL的用途:
解决服务器CPU的内存压力: eg: Cookie的存放问题
解决IO压力:eg: 水平分切,垂直分切,读写分离,通过破坏一定的业务逻辑来换取性能;
缓存数据库:
Memcached:数据在内存,一般作为缓存数据库辅助持久化的数据库;
Redis: 数据存在内存,支持K-V,一般作为缓存数据库辅助持久化的数据库;
文档数据库
MongoDB:文档性数据库,数据存储在内存,支持K-V;
列式数据库
Hbase:以一列为单位存储数据;
Redis简介
开源K-V存储系统,数据缓存在内存中。通常用于配合关系型数据库做高速缓存。
Redis安装
下载地址:https://redis.io/download
编译:在redis目录中,执行make命令,进行编译,需要有gcc
安装: 使用 make install PREFIX=/指定命令的路径 进行安装即可
执行: 进入指定命令的bin目录,执行相应的命令,启动server。
Redis配置
Redis的配置文件为Redis.conf,如果在启动Redis_server时,不指定配置文件,则使用默认的配置。
默认Redis_server为用户服务,将Redis.conf中的daemonize改为Yes,这样就可以让Redis_server在后台运行。
问题:Redis为什么这么快?
Redis是基于内存的,其次是单线程多路IO复用;
多路复用即一个线程检查多个文件描述符的就绪状态,调用select,poll,epoll,当传入多个文件描述符,如果有一个文件的描述符就绪,则返回,否则阻塞直到超时。
Redis默认16个数据库,下标从0开始,初始默认使用0号库,
Redis的基本数据类型
解决原子性问题: 同步,CAS, 使用原子类(autoInteger) , 注意volivate不能保证原子性,它只能保证工作内存和主内存的同步;
注意:i++不是原子操作,赋值运算是原子操作,但double ,long 的赋值操作不是原子性的。因为long.double是64位,但在计算机在执行的过程中,不是一次性分配64位,而是32位。
redis的所有指令都是原子的。
当设置某个k-v的过期值,然后再过期之前修改该值,则该k-v就会为永久的。
有关K的操作
key * : 列出所有的K;
exist<key> : 判断K是否存在
type<key> :判断K的类型
del <key> : 删除某个K
expire<key> <seconds> : 为某个K设置过期时间
ttl <key> : 查看某个K的过期时间,-1为永不过期,-2表示已经过期
dbsize: 查看当前数据库的K的数量
Flushdb:清除当前库
FlushAll: 清除所有的库;
String
二进制安全,可以包含任何数据,一个Redis字符串最多有512M;
set<k><v> ;get<k> ;incr<key> ; decr<key> ; incrby /decrby <key> <step> ;mest<k1> <v1> <k2><v2> ;mget<k1><k2><k3>;msetnx<k1><vl><k2><v2>; gettrange<k><start><end> ; setrange<key> <start><end> ;setex<k><expire><value> ; getset<k><v>;
list
单键多值,是简单的字符串列表,按照插入顺序,即左边插入是采用头插法,右边插入是尾插法;低层是一个双向链表。
lpush/rpush <k1><v1><k2><v2>;lpop/rpop <k> ,注意值在列表在,值不在K不在;rpoplpush <k1><k2> 从k列表的右边吐出一个值,查到k2列表的左边;lrange<k> ,start><stop> ;lindex<k><index> ;llen<key> ;linsert<k>before|after<v><newv> ; lrem<k><n><v> n取正数,从左边删除n个v,n取负数,从右边删除n个v,取值为0,从列表中符合v的值都删除;
set
String的类型的无序集合,低层是一个Value为null的hash表,添加,查找,删除的复杂度都是O(1);
sadd<k> <v1><v2> ;smembers<k> ; sismember<k><v> ;scard<k> ;srem<k> <v1><v2> ;stop<k> ;srandmember<k><n>;sinter<k1><k2>;sunion<k1><k2>;sdiff<k1><k2> ;
hash
键值对集合,String类型的field和value的映射表;
hset<k> <field><value>; hget<k> <field> ; hmset<k1> <field><v1> <field><v2> ;hexists key < field> ; hkeys <key> ;hvals<key> ;hincrby<k><field><increment>; hsetnx<key> <field><value>,
zset
有序集合,没有重复元素的字符串集合。
zadd <k><score><v> <k2><score><v>;zrange<k><start><stop><withscope>;zrangebyscore key min max [withscores] [limit offset count];zrevrangescore key min max min
Java连接Jedis
前提设置:
禁用Linux防火墙,
systemctl stop firewalld.service
redis.conf中注释掉bind 127.0.0.1 ,将protect-mode 设置为no
连接redis
需要导入Jar包: jedis-2.9.0.jar Commons-pool-1.6.jar
@Test public void testc() { //使用Jedis创建实例 Jedis jedis = new Jedis("127.0.0.1",6379); System.out.println("testing ...." + jedis.ping()); }
Key
@Test public void tests() { Set<String> keys = jedis.keys("*"); for(Iterator iterator = keys.iterator();iterator.hasNext();) { String key = (String) iterator.next(); System.out.println(key); } System.out.println(jedis.exists("k2")); System.out.println(jedis.ttl("k1")); }
String
@Test public void testt() { jedis.set("k4","k4_redis"); System.out.println("-----------------------"); jedis.mset("str1","v1","str2","v2","str3","v3"); System.out.println(jedis.mget("str1","str2","str3")); }
List
@Test public void testf() { List<String> list = jedis.lrange("list", 0, -1); for(String str: list) System.out.println(str); }
set
@Test public void testfi() { jedis.sadd("score", "89"); jedis.sadd("score", "99"); jedis.sadd("score", "100"); Set<String> s1 = jedis.smembers("score"); for(Iterator iterator = s1.iterator();iterator.hasNext();) { String str = (String) iterator.next(); System.out.println(str); } jedis.srem("score", "89"); }
hash
@Test public void testsi() { jedis.hset("h1", "name", "land"); System.out.println(jedis.hget("h1","name")); Map<String,String> map = new HashMap<String,String>(); map.put("age", "27"); map.put("idcard", "123"); jedis.hmset("h2",map); List<String> result = jedis.hmget("h2", "age"."idcare"); for(String str : result) System.out.println(str); }
zset
@Test public void testse() { jedis.zadd("z", 99,"v3"); jedis.zadd("z", 80,"v2"); Set<String> s1 = jedis.zrange("z", 0, -1); for(Iterator iterator = s1.iterator();iterator.hasNext();) { String str = (String) iterator.next(); System.out.println(str); } }
Redis事务
Redis事务是一个单独的隔离操作,命令会被序列化,按顺序执行,会串联多个命令防止别的命令插队;
具体操作Multi ,Exec ,discare
输入Mutli命令开始,输入的命令会被依次加入到命令队列中,但不会执行,知道输入Exec后,Redis会将之间的命令队列依次执行,在组队的过程中,可以使用discard放弃组队;
注意:当组队中某个命令出现错误,执行的所有的队列会被取消,如果执行的过程中某个命令出错,则只有报错的命令不会被执行,其他的命令都会执行,不会回滚。
问题
当在进行事务1操作某个K时,如果在别的事务2中访问该K,并进行了修改,则在事务1中,对于该值的使用还是原先没有修改的值,即使事务2执行完毕,会造成逻辑错误。为了避免这种情况,则使用命令WATCH key[key ...]可以避免这种情况。
WATCH key[key]:
在执行multi之前,先执行WATCH key1,可以监视一个或多个K,如果事务在执行的过程中,如果有该K有变动,则该事务会被打断。
unwatch
取消WATCH命令对所有K的监视;
Redis事务的三特性:
不保证原子性:Redis同一个事务中如果有一条命令执行失败,其他命令仍然会执行,没有回滚;
没有隔离级别概念:因为在Mysql,Oracle中,在开启事务后,会执行相应的命令,最后才会被同步,会涉及到提交前查询和查询后的不一致,但是在Redis中,队列中的命令没有提交之间是不会实际的被执行,疑问事务提交前任何命令都不会被实际执行,也就不存在事务内,和事务外。
单独的隔离操作:事务的所有的命令会被序列化,按顺序 执行,不会被其他客户端的命令请求打断;
Java中操作Redis事务
@Test public void testtr() { //开启事务 Transaction transaction = jedis.multi(); //组队 transaction.sadd("k1","2"); transaction.decr("k1"); //执行 transaction.exec(); }
Redis持久化
虽然Redis是基于内存,但是内存中数据不能长久的保存,为了保存数据,需要将数据持久化到磁盘,Redis提供了两个形式的持久化方式。RDB(Redis Data Base),AOF(Append Of File)。
RDB
在指定的时间间隔内将内存中的数据写入到磁盘,即Snapshot快照,,恢复时将快照文件直接读到内存里。
RDB的备份机制:(怎么)
会单独创建一个子进程来进行持久化,会将数据写入到临时文件中,待持久化过程结束,在用临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作,这样确保了Redis性能,RDB方式比AOF高效,但RDB缺点是会最后一次持久化后的数据可能丢失。(在Linux,子进程和父进程共享一个存储空间,只有子进程内容发生变化,才会将父进程的内容复制一份给子进程。)
RDB文件:(何地)
在配置文件中,默认为dump.rdb,可修改;
rdb文件的保存路径,可修改,默认为Redis启动时命令行所在的目录,eg:
./redis-server 启动redis ,则rdb文件保存在redis-server目录下。
RDB保存策略:(何时)
在配置文件中,可修改,为每隔多久进行保存.
save 900 1 save 300 10 save 60 10000
即:eg: save 900 1 ,在900秒内,如果10个K发生变化,就进行保存。
手动保存:
使用save命令,立刻进行保存
stop-writes-on-bgsave-error yes : 当Redis无法写入磁盘,直接关掉Redis的写操作 rdbcompression yes : 即rdb保存,将文件压缩 rdbchecksum yes:存储快 照后,使用CRC64算法来进行数据效验。
RDB备份:
将dmp.rdb文件放到准备好的备份目录中即可。
RDB恢复:
关闭Redis -》 把备份的文件放入工作目录下(即启动命令的目录)-》启动Redis,备份数据会自动加载。
RDB优点:
节省磁盘,恢复速度快
RDB缺点:
当Redis发生宕机,会丢失最后一个快照。
AOF
以日志的形式来记录每个写指令,对于读指令不予以记录,只需追加文件,不可以改写文件。Redis在启动时,会去读取该文件,将指令从前到后执行一次去重构数据。
AOF配置:
AOF默认不开启,需要在配置文件中进行开启。
appendonly yes
默认AOF的文件名为appendonly.aof,可在配置文件中进行修改
appendfilename "appendonly.aof"
AOF文件的目录和RDB文件一致。
AOF的备份机制:(在配置文件中)
appendfsync always // 始终同步,每次Redis的写入都立刻计入日志 appendfsync everysec //每秒同步 appendfsync no //不主动同步,把同步交给OS
AOF的故障备份和恢复:
AOF的备份和恢复的操作的操作同RDB一样,都是拷贝备份文件。恢复时,在拷贝到Redis的工作目录下即可。
注意:AOF采用文件追加方式,当文件越来越大,使用重写机制,当AOF文件的大小超过所设定的阈值时,Redis会启动AOF文件的内容压缩,只保留可恢复数据的最小指令集,可使用命令:bgrewriteaof
何时重写:
auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb
需要满足一定的条件才会进行重写,系统载入时或上次重写完成时,Redis会记录此时AOF的大小,设为base_size,如果Redis的AOF当前的大小>= base_size+ base_size*100%,且当前大小>=64mb的情况下,Redis才会对AOF进行重写。注意上面的配置文件都可以在配置文件中进行修改。
AOF优点:
丢失数据率低,易读性高;
AOF缺点:
占更过的空间,恢复备份慢,存在个别BUG,造成恢复不能。
官方推荐:
推荐两个都用,不建议单独使用AOF,如果只做内存缓存,可以都不用。
Redis的主从复制
Master以写为主,Slave以读为主,用于读写分离,备灾恢复。
如果在一台机器上进行配置多个Redis实例,则可以进行以下修改:
配置从服务器不配置主服务器;
拷贝多个redis.conf文件include,
inclue /xxx/redis.conf // 引用主服务器的配置文件
开启daemonize yes
Pid文件名字pidfile
指定端口prot
Log文件名字
Dump.rdb名字dbfilename
Appendonly 关掉或替换名字。
eg:
include /xxx/redis.conf pidfile /var/xx/xxx.pid prot xxx dbfilename dumpxxx.rdb
服务端启动时,指定其配置文件 ,客户端只需在启动的时候,指定相应的端口即可:
redis-cli -p 6399
相关命令:
info replication //打印主从复制的相关信息 slaveof ip port //成为某个实例的从服务器
相关问题:
当主服务器增加一些数据,而之后有增加了一些从服务器,则从服务器时从头开始复制主服务器的数据;
从服务器只能进行读操作,主服务器则进行写操作;
主机shutdown后,从机的关系不变,主机回来后,增加新的数据,从机则进行复制;
当从机shutdown后,主机增加新的数据,从机启动后,并不能获取新增的数据,因为从机的身份重新变为默认的master,如果要更新,则需要将其加入到主机的从机中,这样会从头开始复制主机的数据。可以将从机加入主机的命令写入到从机的配置文件中,这样在从机启动后,则自动变为主机的从机,就不需要手动改变。
复制原理:
每次从机连通,会给主机发送sync指令,主机存盘,发送RDB文件给从机,从机根据RDB文件进行全盘加载,之后,每次主机写操作,都会立刻发送给从机,从机执行相同的命令。
手动设置从机为主机:
当一个master宕机后,后面的slave可立刻升为master,qi 其后面的slave不做任何修改,使用slaveof on one将从机变为主机。
自动设置从机为主机:
使用哨兵,监视主机是否故障,如果故障,则根据投标将从机转为主机;
-
首先配置一主二仆模式;
-
在自定义目录下创建sentinel.conf文件,在文件中写入:
sentinel monitor mymaster masterip masterport 哨兵同意迁移数量,eg: sentinel monitor mymaster 127.0.0.1 6379 1
-
启动哨兵
redis-sentinel /path/sentinel.conf
故障恢复:
从从服务中选择满足一下条件的从机做为主机:依次为:优先级靠前的(优先级可以在配置文件中设置)-》偏移量最大的(数据最全的)-》选择runid最小的从服务。
当主机宕机,则从机被选择为新主机,当原来的主机恢复后,会成为新主机的从机。
Redis的集群
集群可以解决读写的压力,以及内存存储大小的问题。
Redis集群,启动N个Redis节点,将整个数据分布存储在N个节点中,每个节点存储总数据的1/N。
集群的配置
1.安装
yum install ruby yum install rubygems
2.将redis-3.2.0.gem到自定义目录,然后执行
gem install --local redis-3.2.0.gem
3.配置6个实例(为什么6个,因为必须配置3个master,每个至少有一个slave)
拷贝redis.conf,
开启daemonize yes
pid 文件名字
指定端口
log文件名
Dump.rdb名字
Appendonly 关闭
4.进行实例的cluster配置修改
cluster-enabled yes //打开集群模式 cluster-config-file nodesxxx.conf // 设置节点配置文件名 cluster-node-timeout 15000 //设置节点失联时间,超时的娿,则集群进行主从切换。
5.将6个节点组成一个整体
执行命令:(在redis-3.2.5/src/目录下)
./redis-trib.rb create --replicas 1 ip:port xxx xxx xxx xxx xxx
注意:这里IP地址使用外网ip地址,
slots:
一个Redis集群包括16384个卡槽(slot),每个K都存储在一个卡槽中。集群中每个节点负责处理一部分卡槽。eg:
A:0-5500 B: 5501-11000 C:11001 - 16384
cluster nodes:
该命令可以查看节点的信息。
集群中输入值
redis-cli中录入值,查询键值,redis会去计算该K的插槽,如果不是该实例对应的插槽,则redis报错;
redis-cli -c 实现自动重定向到K对应的插槽;
不在一个slot下的键值,不能使用mget,mset等操作;可通过{}来定义组的概念,从而使用K中{}内相同的内容的键值放到一个slot中。eg:
mset k1{q} v1 k2{q} v2 k3{q} v3
查询集群中的值 CLUSTER KEYSLOT <K> 计算键K应放在哪个槽上
CLUSTER COUNTKEYSINSLOT <slot> 返回槽目前包含的键值对数量
CLUSTER GETKEYSINSLOT <slot> <count> 返回count 个slot槽中的键。
故障恢复
如果主节点下线,则从节点上位为mater节点;
主节点恢复后,则会成为新的master节点的从节点;
如果某一段插槽的主从界定啊都宕掉,redis服务器有两种策略:1.不关心,但会出现某个数据无法找到;2.在redis.conf中的参数:cluster-require-full-coverage,即全部覆盖,只要有一个不能用,则全部不能用。
Redis集群的优缺点
优点:无中心配置,扩容,分压
缺点:多建操作不支持,多键的Redis事务不支持,lua脚本不支持;迁移复杂度高。
Java连接集群
public static void main(String args[]){ Set<HostAndPort> set = new HashSet<HostAndPort>(); set.add(new HostAndPort("ip",port)); JedisCluster jedisCluster = new JedisCluster(set); jedisCluster.set("k1","v1"); System.out.println(JedisClutster.get("k1")); }
个人笔记,如有问题,敬请指出,与君共勉