NoSQL (NoSQL = Not Only SQL ),意即“不仅仅是SQL",泛指非关系型的数据库。
NoSQL不依赖业务逻辑方式存储,而以简单的key-value模式存储。因此大大的增加了数据库的扩展能力。
不遵循SQL标准。
不支持ACID。
远超于SQL的性能。
对数据高并发的读写
海量数据的读写
对数据高可扩展性的
需要事务支持
基于sql的结构化查询存储,处理复杂的关系,需要即席查询。
Redis是一个开源的key-value存储系统。 和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash (哈希类型)。这些数据类型都支持push/pop、add/remove及 取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,Redis支持各种不同方式的排序。与memcached- 样,为了保证效率,数据都是缓存在内存中。区别的是Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
Redis官方网站
Redis中文网站
官网上有安装入口,这里下载了5.0.8版本
安装完成后解压,会得到源码文件
进入redis主目录,运行make
命令,编译,运行make install
检查是否安装成功
使用redis-server redis.conf
命令启动Redis服务端
Redis所有命令都被放入/usr/local/bin中,因此全局都可以运行Redis命令
这个命令需要指定配置文件,因此它参数是配置文件的位置,此时我就在配置文件本路径运行这条命另,因此没写全路径名
使用redis-cli -p 6379
开启Redis客户端,-h
指定主机,-p
指定端口,默认是本机6379端口
使用ping
命令测试服务,返回PONG
说明服务正常
redis-benchmark是Redis自带的性能测试工具
其参数如下
-h <hostname>
服务器的主机名(默认值为127.0.0.1)。
-p <port>
服务器的端口号(默认值为6379)。
-s <socket>
服务器的套接字(会覆盖主机名和端口号)。
-a <password>
登录Redis时进行身份验证的密码。
-c <clients>
并发的连接数量(默认值为50)。
-n <requests>
发出的请求总数(默认值为100000)。
-d <size>
SET/GET命令所操作的值的数据大小,以字节为单位(默认值为2)。
–dbnum <db>
选择用于性能测试的数据库的编号(默认值为0)。
-k <boolean>
1=保持连接;0=重新连接(默认值为1)。
-r <keyspacelen>
SET/GET/INCR命令使用随机键,SADD命令使用随机值。通过这个选项,基准测试会将参数中的__rand_int__字符串替换为一个12位的整数,这个整数的取值范围从0到keyspacelen-1。每次执行一条命令的时候,用于替换的整数值都会改变。通过这个参数,默认的测试方案会在指定范围之内尝试命中随机键。
-P <numreq>
使用管道机制处理<numreq>条Redis请求。默认值为1(不使用管道机制)。
-q
静默测试,只显示QPS的值。
–csv
将测试结果输出为CSV格式的文件。
-l
循环测试。基准测试会永远运行下去。
-t <tests>
基准测试只会运行列表中用逗号分隔的命令。测试命令的名称和结果输出产生的名称相同。
-I
空闲模式。只会打开N个空闲的连接,然后等待。
Redis默认共有16个数据库,并且默认使用第0个数据库
set命令默认创建String类型
set
key value: 设置键值对
这个值可以设置成json,相当于设置了一个对象,如
set user:1 {name:fisher, age: 1}
get
key: 获取key的值
DEL
key [key …]: 删除指定key
append
key value: 追加字符串
strlen
key: 查看字符串长度
incr
key: 字符串加一
decr
key: 字符串减一
incrby
key n: 字符串加n
decrby
key n: 字符串减n
GETRANGE
key start end: 截取字符串[key, end], start和end从0开始.-1代表字符串末尾
SETRANGE
key offset value: 替换字符串,从offset开始,之后的value长度的字符替换为value
SETEX
key seconds value: 添加键值对并设置存活时间
SETNX
key value: 如果key不存在,则创建键值对,存在则不创建
MSET
key value [key value …]: 批量创建键值对,每对键值对用空格隔开
也可以批量设置一个对象的值:mset user:2:name zhang user:2:age 14,这只是类对象,取值时还需要一个一个取,而不能将user:2作为一个对象直接使用set取
MGET
key [key …]: 批量获取key对应的value
MSETNX
key value [key value …]: 批量创建坚持对,不存在则创建,这个是原子性的,要么一起成功,要么一起失败
getset
key value: 获取key的值后设置这个key的值,如果key不存在就创建,key存在就覆盖
List分为左和右
基本命令
RPUSH
key value [value …]: 在列表右边添加元素,如果列表不存在则创建LPUSH
key value [value …]: 在列表左边添加元素,如果列表不存在则创建LRANGE
key start stop: 查看列表中的值LLEN
key: 获得列表长度RPOP
key: 从列表右边开始删除一个元素LPOP
key: 从列表左边开始删除一个元素LINDEX
key index: 通过下标获得元素值LREM
key count value: 根据元素名移除元素,count表示移除的数量,如果count的值多于该元素实际存在的数量就全部移除LTRIM
key start stop: 截取指定元素,其余元素会被移除LSET
key index value: 修改指定位置的元素LINSERT
key BEFORE|AFTER pivot value: 在指定位置插入元素RPOPLPUSH
source destination: 将列表中右边的元素取出,放入另一个列表的左边基本命令
SADD
key member [member …]: 向集合中添加元素SMEMBERS
key : 查看集合中所有元素SISMEMBER
key member: 判断某个集合中是否存在某个值SCARD
key : 查看集合的元素个数SREM
key member [member …]: 移除集合中指定的元素SRANDMEMBER
key [count]: 随机取出集合中的元素SPOP
key [count]: 随机移除集合中的元素SMOVE
source destination member: 将元素从一个集合移动到另一个集合SDIFF
key [key …]: 求多个集合的差集,以第一个集合为参考SINTER
key [key …]: 求多个集合的交集SUNION
key [key …]: 求多个集合的并集基本命令
HSET
key field value: 添加一个hash的键值对HGET
key field: 根据hash键查看值HMSET
key field value [field value …]: 批量添加键值对HMGET
key field [field …]: 批量根据field获取valueHGETALL
key: 获取hash中所有的键值对HDEL
key field [field …]: 删除hash中的feildHLEN
key: 获取hash长度HEXISTS
key field: 判断hash中某字段是否存在HKEYS
key: 获取hash中的所有keyHVALS
key: 获取hash中的所有valueHINCRBY
key field increment: 将hash中的field增加指定值HSETNX
key field value: 不存在该field则创建Zset是在Set的基础上,每一个元素附带一个值,使其能有效的进行排序和查找
基本命令
ZADD
key [NX|XX] [CH] [INCR] score member [score member …]: 增加元素ZREM
key member [member …]: 删除元素ZRANGEBYSCORE
key min max [WITHSCORES] [LIMIT offset count]: 将scores为min到max的元素从小到大排列,-inf代表负无穷,+inf代表正无穷,ZRANGEBYSCORE myzset -inf +inf withscores表示从小到大排序所有元素.ZREVRANGE
key start stop [WITHSCORES]: 倒序排列元素ZCARD
key: 获取元素个数ZCOUNT
key min max: 获取指定区间元素个数geospatial是处理地理位置的数据类型,是对Zset的封装
GEOADD
key longitude latitude member [longitude latitude member …]: 按纬度和经度添加元素,longitude是纬度,latitude是经度
GEOPOS
key member [member …]: 获得元素的经纬度信息
GEODIST
key member1 member2 [unit]: 获取两元素之间直线距离,unit是单位,可以是m(米)、km(千米)、mi(英里)、ft(英尺)
GEOHASH
key member [member …]: 获得某元素的位置Geohash字符串,两元素位置越近,Geohash字符串越相似
GEORADIUS
key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]: 以指定经纬度为中心,找到某半径范围内的元素.
示例: GEORADIUS city 110 30 500 km withdist withcoord withhash count 1
GEORADIUSBYMEMBER
key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]: 以某元素为中心找到到某半径范围内的元素,参数与GEORADIUS相似
查看与移除元素操作: 由于geospatial底层是Zset,因此查看与移除命令可以与Zset通用
Hyperloglog是Redis的一种基数统计算法,固定占用内存12KB
PFADD
key element [element …]: 向集合中添加元素PFCOUNT
key [key …]: 统计集合的基数PFMERGE
destkey sourcekey [sourcekey …]: 合并多个集合(并集)Bitmap(位图),利用二进制记录数据,适合记录只有两个状态的数据,如是否打卡、是否登录等
SETBIT
key offset value: 存数据.offset相当于键,必须是整型,value相当于值GETBIT
key offset: 根据键获得valueBITCOUNT
key [start end]: 统计位图中1的个数,可以设置起始位置事务的基本操作
multi
: 开启事务exec
: 提交事务discard
: 取消事务Redis中的事务具有的特性: 一次性、顺序性、排他性
Redis中事务不保证原子性,也就是说在Reids的事务中,一行命令执行失败并不一定引起所有命令都不执行.
Redis中事务出错会有两种情况:
Reids的watch命令可以监控某一个key,开启事务后,在修改某个被watch监控的key时,如果他的值此时被其他线程修改,那本次修改是不会被执行的.
当遇到这种情况时,可以先使用unwatch解除监控,再使用watch监控,这样watch监控的值就是最新的数据,然后进行修改.
导入包
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>3.2.0version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.62version>
dependency>
Jedis的使用: 相当于将Redis的命令转换成了方法,每一个命令都有对应的方法
以事务为例演示Jedis的使用
public static void main(String[] args) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("k1", "v1");
jsonObject.put("k2", "b2");
String s = jsonObject.toJSONString();//创建JSON字符串
Jedis jedis = new Jedis("127.0.0.1", 6379);//建立Redis连接
System.out.println(jedis.ping());
Transaction multi = jedis.multi();//开启事务,事务的执行和取消要使用Transaction
try {
multi.set("rediskey", s);
multi.set("rediskey2", s);
// int i = 1 / 0;
multi.exec();//执行事务
} catch (Exception e) {
multi.discard();//取消事务
e.printStackTrace();
}finally {
System.out.println(jedis.get("rediskey"));
jedis.close();
}
}
在Spring Boot 2.x之后,底层使用lettuce连接Redis.lettuce底层使用了netty,数据在多个线程中可以共享,因此不存在线程不安全问题
pom
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
yaml
spring:
redis:
host: localhost
port: 6379
测试:
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.flushDb();
redisTemplate.opsForValue().set("boot", "hello");
redisTemplate.opsForValue().get("boot");
}
redisTemplate.opsForValue(): String的操作
redisTemplate.opsForList(): List的操作
redisTemplate.opsForSet(): Set的操作以下依次类推
redisTemplate.opsForHash
redisTemplate.opsForZSet
redisTemplate.opsForGeo
redisTemplate.opsForHyperLogLogRedisConnection connection = redisTemplate.getConnectionFactory().getConnection();获得Redis连接,这个连接可以进行选择数据库、数据库清除、开启事务、添加监视器等
当通过SpringBoot将对象存储到Redis中时,要将对象进行序列化,Redis不然将不会识别出这个对象的类型而无法存储,会报出SerializationException
异常
将类进行序列化就是让其实现Serializable
接口即可
public class User implements Serializable {
private String name;
private Integer age;
}
此外,通常也要将key和value都进行序列化,否则通过Java代码存储到Redis的key会进行转码
分析源码可知,SpringBoot为我们提供的RedisTemplate没有过多的进行序列化,并且它的类型是
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String,Object>();
template.setConnectionFactory(factory);
// Json序列化配置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// String 的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
// hash的key也采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setHashKeySerializer(stringRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
此时,进入Redis的key和value都会进行序列化,这时那个对象的序列化就显得没那么必要,因为这里已经配置了value的序列化
Redis是内存数据库,所有操作都是在内存中进行的,断电即失,一些有用的信息需要将其放入本地磁盘中,也就是持久化.
Redis共有两种持久化模式:
这是Redis的默认的持久化模式, RDB会按照一定的规则,在父进程中创建一个子进程根据现有的内容生成RDB文件,持久化到磁盘中
清空所有数据库
(即flushall)或者服务器的关闭
也会触发RDB持久化
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程
都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。
这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那
RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
这里可以设置保存快照的规则,默认是900秒内有1次请求或300内有10次请求,或60秒内有10000次请求就会触发RDB的持久化
这里可以修改持久化的rdb文件名,这个文件默认保存在redis的bin
目录下
以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作.
上图所示, ADF默认是关闭的,开启时将no改成yes
上图是redis的aof文件名,也就是AOF持久化时产生的文件,这个文件记录了所有的操作
上图是AOF持久化的规则
SUBSCRIBE
channel [channel …]: 订阅指定频道PUBLISH
channel message: 向某个频道发信息UNSUBSCRIBE
[channel [channel …]]: 退订指定频道Redis支持主从模式的集群,遵循主从复制的原则.主节点将数据复制给从节点,主节点主要负责写数据,从节点只负责读数据.一般采用一主二从的结构.
复制三个conf文件,并修改端口、pid文件名、log文件名、rdb文件名配置
当Redis以守护进程方式运行时,Redis默认会把pid写入pid文件中
port 6381
pidfile /var/run/redis_6381.pid
logfile "redis6381.log"
dbfilename dump6381.rdb
一个Redis服务默认是主机,输入INFO replication
就可以看到当前Redis的角色等信息
通过SLAVEOF host port
命令可以指定本Redis的主机
通过SLAVEOF no one
命令,指定自己为主机.
以上用命令配置主从机并不是永久的,当Redis服务重启后,这些配置就会消失,如果想进行永久配置,可以修改配置文件
按上图所示,跟命令相同,只是写在了配置文件中.
将集群配置成主从模式后,每次数据的更新,主机都会发送给从机,从机也会将信息持久化.
从机只能进行读操作,不可以进行写操作的,主机读写都可以
当主机宕机后,余下的从机仍能正常运行,只不过仍然只能进行读操作.如果在主机恢复之前没有在剩下的从机中选出主机,那原来的主机恢复后依旧是主机.如果选出主机(可以手动使用命令
SLAVEOF no one
将从机设置为主机),那原来的主机虽然角色依旧是主机,但没有任何一台从机认为他是主机,可以把这个主机看作脱离集群的独立个体.当从机宕机后,如果整个集群又进行了数据的更新,那从机重连之后,会及时更新这部分数据.这里有
全量复制
和增量复制
的概念.当从机连接主机后会给主机发出sync同步命令,主机接收到命令后会启动后台存盘进程,收集所有用于修改数据的指令集并执行一遍,生成数据文件发动给从机,从几会将这个文件存盘并加载到内存中,这就是全量复制.从机连接主机之后,主机会继续收集所有修改指令,并发送给从机,这就是增量复制.
哨兵模式就是在集群中设置哨兵,当Redis主机出现故障,哨兵会检测出来并切换主机.
哨兵是一个独立的进程,通过发送请求监控各Redis服务器是否正常.当主机宕机后,哨兵会通过选举机制在其余从机中选出一名从机配置成主机.然后通过发布订阅模式,通知其他从机修改配置,让他们切换主机.当原来的主机再次启动后,他会被哨兵察觉并自动将其设置成从机.
配置哨兵
新建哨兵配置文件sentinel.conf
,并进行如下配置
# sentinel monitor 自定义唯一标识 host port 1代表
sentinel monitor mymaster 127.0.0.1 6379 1
哨兵文件只配置主机即可,下面是上述配置的解释
网上找的哨兵的全部配置文件
# Example sentinel.conf
# 哨兵sentinel实例运行的端口 默认26379\
port 26379
# 哨兵sentinel的工作目录
dir /tmp
# master-name可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# 哨兵sentinel监控的redis主节点的 ip port
# quorum 配置多少个sentinel哨兵统一认为master主节点失联 那么这时客观上认为主节点失联了
# sentinel monitor
sentinel monitor mymaster 127.0.0.1 6379 2
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds
sentinel down-after-milliseconds mymaster 30000
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,这个数字越小,完成failover所需的时间就越长,但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs
sentinel parallel-syncs mymaster 1
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,
slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout
sentinel failover-timeout mymaster 180000
# SCRIPTS EXECUTION
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,一个是事件的类型,一个是事件的描述。如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# shell编程
# sentinel notification-script
sentinel notification-script mymaster /var/redis/notify.sh
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
#
# 目前总是“failover”,
# 是“leader”或者“observer”中的一个。
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script 置!
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh # 一般都是由运维来配
启动哨兵
哨兵就是redis-sentinel
,使用命令redis-sentinel 哨兵conf文件
启动
启动之后,哨兵就会对Redis进行监控.
用户查询某个数据,发现redis内存数据库不存在,也就是缓存没有命中,于是向持久层数据库查询,而持久层数据库也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这就是缓存穿透。
解决方案:
布隆过滤器
对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力.
缓存空对象
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源.
缓存击穿,是指一个key非常热点,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,持久层数据库压力瞬间增大,可能会造成宕机.
解决方案:
缓存雪崩,是指在某一个时间段,缓存集中过期失效(如Redis节点宕机,手动设置的缓存在某个时间失效等).原本从缓存中查询的数据全都大量落到持久层数据库中,导致存储层压力暴增,存储层宕机.
解决方案: