Redis
1、简介
Redis是一种基于内存快的速存取的非关系型数据库,它的所有数据都存储在内存中,也可以持久化到硬盘上。Redis数据库中的数据是非结构化的,以键值对的形式存储。Redis官网上(http://redis.io/download)只有Linux版的打包文件,不过有第三方的Windows版,我们这次就用的是Windows版。
2、运行redis
在redis的目录下启动cmd,然后使用redis-server redis.windows.conf可以启动redis服务器,使用redis-cli.exe -h localhost -p 6379这个命令可以启动redis客户端,请求特定的ip地址与端口上的Redis服务器,由于redis默认的端口是6379,所以可以不写端口
2.1 日志级别
redis中日志级别有四种:debug、verbose、notice与warning,不同于log4j:debug、info、warn、error。
2.2 redis持久化的两种模式
redis持久化的两种模式是快照模式与AOF模式,快照模式定期将整个数据库保存到硬盘上,比较消耗资源,AOF只保存改变过的数据,需要日志文件。
redis支持多数据库,默认有16个数据库,从0开始到15,默认使用的是第一个数据库,可以使用select方式改变数据库。redis的配置信息使用config get xxx获取,比如使用config get requirepass获取密码,redis默认是没有密码的,设置密码后需要使用auth输入密码之后才能使用,使用config set key value来设置配置
3、redis的数据类型
redis中共有五种数据类型:string、hash、list、set与zset。之前讲过redis中的数据是以键值对的形式存储的,这里的五种数据类型是针对值的。
3.1 string
string类型就是字符串类型,使用set存储,get获取:
set name zhangsan
get name
set的返回值是OK,get如果获取到了指定key的value就会返回value,如果没有获取到就会返回nil,这个代表空值。还可以设置过期时间,比如set subject java EX 5,这个就设置了过期时间5秒,5秒后就获取不到了。
3.2 hash
hash类似Java中的HashMap,也是以键值对的形式存在。
#新增与修改
hmset key field1 value1 field2 value2...
#获取某个key的所有hash
hgetall key
#获取某个key的某个field
hget key field
#删除某个key的某个field
hdel key field
hmset的返回值是ok,hdel的返回值是受影响的条目数。
3.3 list
这个list有点类似于Java中的list,不过它是采用链表存储的,更类似于LinkedList。所以既可以从头部插入,也可以从尾部插入:
#从左侧插入
lpush key value1 value2...
#从右侧插入
rpush key value1 value2...
#从左侧遍历
lrange key start end
list的索引也和Java中的相同,从0开始。lpush与rpush返回的是当前list插入数据之后的长度
3.4 set
set是集合,无序不可重复
#创建或添加一个set
sadd key value1 value2 ...
#展示set中的所有value
smembers key
sadd的返回值是受影响的条目数,注意,sadd如果添加重复的东西直接丢弃。
3.5 zset
zset类似于TreeSet,是有序集合,既然是排序的话就需要一个排序规则,redis中的排序需要额外指定一个score,根据这个值进行排序,默认从小到大排序
#创建与添加zset
zadd key score1 value1 score2 value2...
#根据分数展示
zrangebyscore key min max
#根据下标展示
zrange key start end
4、java访问redis
Jedis是Redis官方推荐的Java连接开发工具。要在Java开发中使用好Redis中间件,必须对Jedis熟悉才能写成漂亮的代码。
4.1 添加依赖
要想使用jedis首先要添加依赖:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
4.2 编写测试类
jedis的使用有两种方式,一种不使用连接池,一种使用连接池,连接池的使用较为普遍,并且还可以做到redis集群,扩展性强
package com.qianfeng;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisShardInfo;
import redis.clients.jedis.ShardedJedis;
import redis.clients.jedis.ShardedJedisPool;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class JedisTest {
private Jedis jedis = null;
@Before
public void setUp(){
jedis = new Jedis();
}
@After
public void tearDown(){
if(jedis!=null){
jedis.close();
jedis = null;
}
}
@Test
public void testString(){
String s = jedis.set("names", "zhangsan");
System.out.println(s);
System.out.println(jedis.get("names"));
}
@Test
public void testHash(){
jedis.flushDB();
Map<String,String> map = new HashMap<>();
map.put("name","zhangsan");
map.put("age","20");
map.put("sex","boy");
String s = jedis.hmset("person", map);
System.out.println(s);
System.out.println(jedis.hgetAll("person"));
}
@Test
public void testList(){
jedis.flushDB();
Long rpush = jedis.rpush("names", "zhangsan", "lisi", "wangwu");
System.out.println(rpush);
System.out.println(jedis.lrange("names", 0, 100));
}
@Test
public void testSet(){
jedis.flushDB();
System.out.println(jedis.sadd("names", "zhangsan", "lisi", "wangwu"));
System.out.println(jedis.sadd("names", "zhangsan", "zhaoliu"));
System.out.println(jedis.smembers("names"));
}
@Test
public void testZset(){
jedis.flushDB();
System.out.println(jedis.zadd("names", 100, "zhangsan"));
System.out.println(jedis.zadd("names", 99, "lisi"));
System.out.println(jedis.zadd("names", 90, "wangwu"));
System.out.println(jedis.zrangeByScore("names", 0, 100));
}
@Test
public void testPool(){
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
//设置最大空闲数
config.setMaxIdle(20);
//设置最大连接总数
config.setMaxTotal(50);
//设置等待时间
config.setMaxWaitMillis(5000);
//添加集群信息
List<JedisShardInfo> shardInfos = new ArrayList<>();
//可以添加多个redis
shardInfos.add(new JedisShardInfo("localhost","6379"));
//shardInfos.add(new JedisShardInfo("localhost","6379"));
ShardedJedisPool pool = new ShardedJedisPool(config,shardInfos);
ShardedJedis jedis = pool.getResource();
System.out.println(jedis.get("name"));
}
}
Jedis的使用基本上和命令行模式下一样。
5、redis高级
5.1 备份与恢复
save,使用的是快照模式(snapshot),这个命令将在redis安装目录创建dump.rdb文件,如果想要恢复直接把rdb文件放在redis的安装目录中并启动redis服务就可以了。
bgsave:后台存储。
另外还有一种模式是AOF(append of file)模式,以日志文件的形式保存对redis数据库所作的修改。这种模式有三种策略:no、always与everysec。
- no:等待操作系统保存
- always:每次做出修改都会保存
- everysec:每秒自动保存
两种方式的比较:
- RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。
RDB的优势:
1). 一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的。比如,你可能打算每个小时归档一次最近24小时的数据,同时还要每天归档一次最近30天的数据。通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复。
2). 对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。
3). 性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。
4). 相比于AOF机制,如果数据集很大,RDB的启动效率会更高。
RDB的劣势:
1). 如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。
2). 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。
AOF的优势:
1). 该机制可以带来更高的数据安全性,即数据持久性。Redis中提供了3种同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。至于无同步,无需多言,我想大家都能正确的理解它。
2). 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决数据一致性的问题。
3). 如果日志过大,Redis可以自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证数据安全性。
4). AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。
AOF的劣势:
1). 对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
2). 根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。
二者选择的标准,就是看系统是愿意牺牲一些性能,换取更高的缓存一致性(aof),还是愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份(rdb)。rdb这个就更有些 eventually consistent的意思了。
5.2 redis事务
multi:开启事务,将接下来的命令全部入队
exec:执行入队命令
discard:取消事务,放弃执行事务块内的所有命令
5.3 redis主从
可以设定一个redis数据库为master,多个redis数据库为slave,可以在主redis数据库中进行读写,但是不能在从数据库种进行写,只能读,这样做的目的是为了保证冗余,一旦主数据库宕机,从数据库还有数据。
设置从数据库需要修改redis.windows.conf文件,只需要修改两个地方,第一个地方是端口号,不能和主数据库和其他从数据库的端口号相同,还有一个地方是slaveof:
slaveof 127.0.0.1 6379
将主机与端口号指向主数据库的主机与端口号。然后再启动从数据库的客户端,这是可以从数据库的客户端中访问到主数据库的数据
5.4 哨兵模式
哨兵(Redis-Sentinel)时刻监控redis主从模式运行,如果master数据库宕机了就从它的多个slave中选择一个作为master,其余的slave将它们的主机与端口修改为这个master的。
5.5 缓存穿透、缓存雪崩与缓存击穿
缓存处理流程:前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存并返回结果,数据库也没取到直接返回空结果
- 缓存穿透:缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决方案:
- 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
- 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
缓存击穿
- 缓存击穿:缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
解决方案:
- 设置热点数据永远不过期
- 加互斥锁
缓存雪崩
- 缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至宕机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案:
- 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
- 如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。
- 设置热点数据永远不过期。
网友评论