nosql = not only sql ,不仅仅是sql,泛指非关系型数据库。
随着互联网的高速发展,传统的关系型数据库(mysql、oracle…)已无法完全应对用户的需求,特别是大规模、高并发的网站,用传统的数据库来应对已经暴露出很多问题,而非关系型数据库在可以很好的来解决这些问题。
nosql数据库的特点:易扩展、大数据量高性能、数据类型多样灵活。
常见的nosql数据库:redis、mongoDB、BerkeleyDB、CouchDB、memcache等
现在网站基本都是 :传统的关系型数据库 + 非关系型数据库 一起使用。
Redis:REmote DIctionary Server(远程字典服务器)
redis是一款完全免费开源的,C语言编写的,高性能的K/V键值对分布式内存数据库,基于内存运行,且支持持久化的nosql数据库,是当下最热门的nosql数据库之一。
官方记录:Redis一秒内可以写8万次,读11万次。
Redis的下载与安装:
Windows环境:官网直接下载,解压即可,点击图下图所示即可打开redis服务或客户端连接
Linux环境:(由于实际开发中都是在linux上部署redis,所以redis在linux环境下的安装才是重点,windows环境下的安装看看就行了)
打开redsi官网:https://redis.io/ ,点击下载,此处下载的就是linux版本
下载完成后,将下载好的redis-6.2.3.tar.gz放到linux系统的/opt目录下(可通过xftp工具上传到linux中)
在当前目录解压此文件:tar -zxvf redis-6.2.3.tar.gz
解压完成后出现redis-6.2.3文件夹,进入该文件夹目录:cd redis-6.2.3
安装redis(因为我们下载的linux版本是源码,所以我们要进行编译安装)
5.1 安装gcc:yum -y install gcc ,yum -y install gcc-c++ (若这两个已安装了,此处就不用安装了)
5.2 执行make命令 (将源码编译成二进制文件)
5.3 执行make install DESTDIR=/xx/xx(DESTDIR参数可以指定安装的目录)
查看默认安装目录/usr/local/bin(若install时我们没有指定安装目录,则默认就安装到这个目录下了),改目录下有很多redis的启动程序
将第4步中解压好的redis-6.2.3文件夹下的redis的配置文件redis.conf 拷贝到我们新建的文件夹hhlconfig下,以后每次启动redis-server的时候就用拷贝过来的这个redis-server启动即可,之后我们要对这个redis.conf做一系列的配置更改。
安装到此结束!启动redis 服务端 : redis-server hhlconfig/redis.conf,启动redis客户端redis-cli -h IP -p Port , 客户端输入ping输出pong后表示连接成功!客户端输入shutdown命令关闭服务端。
注意,以下这些命令和开发中的jedis或redistemplate中的API名称基本一致,所以这些命令是需要记一下的
redis中分为:
五大数据类型:String、List、Set、Hash、Zset
三种特殊数据类型:GEO、HyperLogLog、BitMap
String : key–value (最最最常用的一个类型)
List: 和java中的list集合类似,一个key 可以对应多个value
Set : 和list类是,但Set中不允许有重复元素
Hash :相当于java中的map ,key就是map的名字,value就是一个个的键值对
Zset(sortedset) : 在Set的基础上,又增加了排序的功能(每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。)
回顾上面的set集合放置元素为: sadd myset k1 k2 k3
Zset 在放置元素时要多个source参数: zadd myZset source1 k1 source2 k2 source3 k3
就相当于在放的时候就根据source这个分数来排好序了
三种特殊数据类型:GEO、HyperLogLog、BitMap
GEO地理位置:用于存储地理位置,然后可以实现两个位置之间的距离查询,附近的人等和定位相关的一些功能。(底层实现原理其实是Zset)
geoadd chinaCity 121.48 31.40 shanghai 120.21 30.20 hangzhou 113.88 22.55 shenzhen 106.54 29.40 chongqing
geopos chinaCity beijing 获取beijing的经纬度
geodist chinaCity beijing shanghai km 查询 beijing 到 shanghai 的直线距离,单位为km,默认不写是m
georadius chinaCity 100 30 1000km withdist 查询经度100纬度30附件1000km的 chinaCity 下的所有城市,并且显示距离
georadiusbymember chinaCity beijing 1000km 查询beijing附近1000km以内的城市
因为geo底层原理实际上用的是Zset 所以可以通过Zset中的命令操作geo
zrange chinaCity 0 -1 遍历chinaCity
zrem chinaCity 删除chinaCity
HyperLogLog:用来做基数统计的(基数就是一个集合中不重复的元素的个数)
应用场景:例如要统计一个网站的浏览量,每个用户每天访问多次算一次,此时用传统的方法可以用Set集合(因为set可以存放不重复元素),存储用户的id,但是如果浏览量特别大,id就会特别多,导致内存占用过高。而HyperLogLog由于底层算法的设计使得其中每个键只需要花费12kb的内存(固定的),且可以存2的64次方的不同元素的基数。
注意:数据量过大时,HyperLogLog有0.81的错误率,但是对于UV(用户浏览量)这种不需要很精确的数据可忽略不计。
BitMap:BitMap 的数据结构就是2进制 01010101 这种的
应用场景:统计用户的某些信息,是否打卡,是否登录,是否活跃 等这种只有两个结果是和否的连续性的统计。
使用BitMap的好处其实和HyperLogLog的好处一样,都是为了节省内存!
redis事务的本质是一组命令的集合,就是开启事务、编写命令集合(将命令序列化)、提交事务。
redis事务没有原子性:redis单条命令是符合原子性的,但redis事务一般是包含了一系列的命令,如果其中一条命令执行失败了,整个事务不会回滚,其余命令仍会执行。
redis事务没有隔离性的概念:
原本的事务的隔离性是指:一个事务要访问的数据正在被另一个事务修改,那么只要修改数据的这个事务还未提交,那访问数据的那条事务是不会受到这条还未提交的事务的影响的。比如现有有个交易是从A 账户转100元至B账户,在这个交易还未完成的情况下,如果此时B查询自己的账户,是看不到新增加 的100元的。
而redis的事务由于是在执行提交事务exec这个动作时,才会真正执行命令操作,所以不存在上述的隔离性而言。
redis中事务的步骤:
虽然redis事务没有原子性,但是redis提供了watch监控!
redis中的监控 watch 可以用来监控一个key,当此key发生变化时,事务提交失败,类似乐观锁中的加版本号
unwatch 命令用于取消watch命令下监控的所有key
注意:一但执行 EXEC 开启事务的执行后,无论事务是否执行成功, WARCH 对变量的监控都将被取消。故当事务执行失败后,需重新执行WATCH命令对变量进行监控,并开启新的事务进行操作。
Jedis:是redis官方推荐的使用java连接redis的开发工具
<!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.2.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.58</version> </dependency>
Jedis jedis = new Jedis("111.111.111.22",6379); //输入ip 和 端口号 jedis.auth("123456"); // 如果redis有密码则要设置密码 System.out.println(jedis.ping()); // 得到pong 则表示连接成功 jedis.select(1); //切换数据库 jedis.flushDB(); //清空当前数据库 System.out.println(jedis.get("sign")); //获取string类型的sign key Set<String> keys = jedis.keys("*"); // 遍历所有key System.out.println(keys.toString()); jedis.lpush("list","1","2","3"); jedis.rpush("list","4","5","6"); // List<String> list = jedis.lrange("list", 0, -1); System.out.println(list); jedis.lpop("list"); System.out.println(jedis.lrange("list", 0, -1));
SpringBoot项目中整合Redis:
SpringBoot项目中一般使用RedisTemplate提供的方法来操作redis
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
spring: redis: host: 127.0.0.1 port: 6379 password: 123456 jedis: pool: max-active: 8 # 连接池最大连接数(使用负值表示没有限制) max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制) max-idle: 500 # 连接池中的最大空闲连接 min-idle: 0 # 连接池中的最小空闲连接
@Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<String, Object>(); template.setConnectionFactory(factory); 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); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // key采用String的序列化方式 template.setKeySerializer(stringRedisSerializer); // hash的key也采用String的序列化方式 template.setHashKeySerializer(stringRedisSerializer); // value序列化方式采用jackson template.setValueSerializer(jackson2JsonRedisSerializer); // hash的value序列化方式采用jackson template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } }
@Component public final class RedisUtil { @Autowired private RedisTemplate<String, Object> redisTemplate; // =============================common============================ /** 指定缓存失效时间 * @param key 键 * @param time 时间(秒) */ public boolean expire(String key, long time) { try{ if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } }
以下内容是redis高级部分,面试的高频问点,必须掌握~
redis.conf就是我们在启动redis是所指定的那个配置文件,这个文件中有很多配置信息,下面列举一些比较常用的配置信息:
network网络配置:
由于我们的redis都是安装在linux上的,默认外部是不能访问的,我们需要进行如下配置使外部可以访问:
注意:protected-mode 保护模式默认是开启的(一般不要关闭),此时必须配置bind 或者 设置个密码
通用配置:
安全配置:
限制配置:
持久化相关的配置 在下面的持久化章节里讲解
redis客户端里可以通过config命令来查看所有或指定的某个配置信息:
持久化:就是把redis内存中的数据存储到磁盘上,redis持久化主要分为RDB和AOF两种持久化策略
RDB : Redis DataBase,rdb持久化的原理是在指定的时间间隔内,将内存中的数据以快照的方式写入磁盘(也就是写到dump.rdb文件里),再次启动redis时,回去读取该dump.rdb文件来恢复数据。
关于rdb持久化在redis.conf中的配置如下:
以上这些是redis默认的rdb规则,由于aof持久化需要手动开启,所以redis默认就是采用rdb持久化方式
rdb最关键的配置就是上面说到的redis.conf中的save,save的规则可以配置多个。(若要关闭rdb持久化,则注释掉所有的save规则即可)
如何触发rdb持久化?
想要恢复数据的话,只需将dump.rdb文件放到redis服务目录下,启动时就会自动恢复数据了
rdb原理:redis在开启服务后,会自动fork出一个子进程,该子进程专门用来进行rdb操作,此时父进程就不用进行任何的io操作了都交由子进程来,这样父进程的响应速度会更快,所以说rdb持久化方式很大程度上提高了redis的性能
rdb持久化的优点和缺点:
优点:适合大规模的数据恢复,因为采用的是快照的原理,所以很快。
缺点:rdb只适合对数据完整性和一致性要求不高的数据,因为rdb是在一定的间隔时间内持久化一次,如果服务突然down掉,那最近的一次间隔内的数据由于还没保存,所以会丢失。
AOF:Append Only File ,AOF持久化是以日志的形式来记录每一个写操作,将redis执行过的所有指定记录下来(读操作不记录)到appendonly.aof文件中(由此其实就能看出来aof相对rdb会慢很多),只需追加文件内容,不能修改文件。redis启动时,会根据该appendonly.aof文件把记录下来的所有指令从头到尾执行一遍,以此恢复原来的数据。
aof持久化在redis.conf中的相关配置:
aof恢复数据的方法和rdb类似,只要把appendonly.aof文件放到服务目录下,启动redis服务时则会自动读取aof文件内容恢复数据,前提是要开启aof持久化模式
appendonly.aof被恶意篡改后,可以使用redis-check-aof --fix appendonly.aof 指令来修复appendonly.aof文件
由于aof文件是记录了每一个写操作的指令,随着时间的流逝那这个appendonly.aof文件会越来越大,这样会造成很多问题,比如redis服务器的压力,启动redis时通过aof还原数据时间过长。所以引出了aof重写的功能来解决此问题。
AOF重写原理:aof文件体积扩大到一定值时,当前会进程fork出一个子进程来专门用来进行aof重写,这样的好处是,父进程仍然可以处理其他命令,两个进程可以同时进行。
但这样又会产生一个新的问题,子进程在进行AOF重写期间,服务器进程还要继续处理命令请求,而新的命令可能对现有的数据进行修改,这会让当前数据库的数据和重写后的AOF文件中的数据不一致。
为了解决这个问题,redis增加了aof重写缓存的概念,这个缓存在aof重写子进程启动时随之启用,父进程在执行完写命令后会同时将该写操作追加到aof缓冲区和aof重写缓冲区。
在aof重写子进程重写完毕后,redis会将aof重写缓冲区的内容写到新的aof重写后的文件中,然后对新的aof文件进行更名并替换老的aof文件。
注意:aof重写并不是去读取已存在的aof文件来精简内容,而是去读当前数据库里的内容,把一些指定结合起来
比如本来aof文件中存放的是:
set k1 v1
set k2 v2
set k3 v3
那么在aof重写时回去精简内容,用一个命令 mset k1 v1 k2 v2 k3 v3 来代替上面3个命令,从而缩减了aof文件的大小。
aof重写触发条件:(是重写的触发条件,别和aof持久化配置搞混了)
aof的优点和缺点:
优点:由于aof是保存每条写的记录,所以数据完整性相对较好
缺点: 在存储相同的数据集的情况下,appendonly.aof文件要远大于dump.rdb文件,且不管是恢复数据的速率还是运行效率,aof相对rdb都要慢很多。
Tips:
redis提供了发布订阅的这种消息队列模式,具体来的流程就是:发布者发布消息到频道, 订阅者订阅频道拿到消息
发布者–>频道—>订阅者
基本命令有:
应用场景:即时聊天,群聊
redis的发布订阅属于消息队列中的一种,这里简单了解,知道redis有这个功能即可,消息队列中间件才是重要的学习内容
主从复制,是指将一台Redis服务器上的数据复制到其他Redis服务器上。前者称为主节点,后者称为从节点;数据的复制是单向的,只能由主节点到从节点。主节点Master以写为主,从节点Slave以读为主。
默认情况下,每台Redis服务器都是从节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能由一个主节点。
主从复制的作用:(或者说为什么要有主从复制这个功能?)
在真实的开发环境中,只用一台Redis服务器是万万不能的,单台服务器收到了很多限制,比如: 故障后则表示redis不能用了,因为只有这一台; 单台服务器处理所有的请求,负载压力过大;单台服务器内存容量有限。
所以我们要配置redis集群,而redis集群和主从复制是分不开的,一般来说使用redis都是读多写少,所以就可以采用主从复制的特点来部署redis集群:
主从复制的redis集群环境配置:以下是在一台服务器上模拟3台redis集群的效果(一主二从),所以要修改配置文件
由于我们模拟的是一台linux上配置3台redis集群,所以要进行如下配置:
因为redis默认都是主节点,所以我们要配置的是从节点:
两个从节点配置完毕后,再次分别查看3台redis下的info replication信息如下:
主机既可以写也可以读,从机只能读。
主机挂了,查看从机信息(从机配置信息不变),主机恢复,再次查看信息(主机还是有两个从机,从机配置不变)
从机挂了,查看主机信息(少了一个从机),从机恢复,查看从机信息(由于没在redis.conf中配置slaveof,此时从机变为主机)
谋朝篡位:slaveof no one ,当主节点down掉后,从节点输入该命令,会将自己变为主节点,且之前的其他从节点都会链接到这个新的主节点上。
哨兵模式:
由上面的slaveof no one命令知道,在主节点挂掉后我们可以手动执行slaveof no one命令使得一台从节点变为主节点。但是这个动作毕竟需要我们人工干预,手动去执行,费时费力,而且还会造成一段时间内服务不可用。redis 2.8版本后开始正式提供了Sentinel哨兵模式来解决这个问题。
哨兵模式是一个独立的进程:
上面标红的就是用来启动哨兵模式的进程。
哨兵模式的原理:哨兵是一个独立的进程,哨兵通过发送命令,等待redis服务器响应,从而监控运行的多个redis实例。
这里的这个哨兵有两个作用:
但是一个哨兵往往是不行的,我们需要配置多个哨兵来共同监控redis
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。
模拟哨兵模式,这里的话按第一张哨兵的图来模拟,即3台redis服务,一主二从,1个哨兵。
注意:即使down掉的主节点恢复正常后,它也不是主节点了,它会变成新主节点的从节点了。
哨兵模式的优点和缺点:
优点:哨兵模式是主从模式的升级,可用性更高
缺点:redis在线扩容很复杂,哨兵模式的配置也比较麻烦(上面哨兵配置文件中只写了一条配置信息,其实还有很多的配置信息)
redis缓存的使用,极大程度上提高了系统的性能和效率,有效应对了高并发的问题。但随之而来的也就出现了数据一致性的问题,如果对数据一致性要求极高,那就不能使用缓存了。
另外就是缓存穿透、缓存击穿、缓存雪崩的问题。(这也是面试的高频问点了)
缓存穿透:用户查询一个数据,发现缓存里没有,也就是说缓存没有命中,然后去数据库查询,发现数据库也没有,查询失败。当很多用户都来查询这个没有的数据,因为缓存里没有,所以这么多的请求就都去数据库查询了,这样就给数据库造成了很大的压力,这就叫缓存穿透。(划重点:因为所查询的数据缓存和数据库都没有,所以每次查询都去数据库查了)
解决方案:
缓存击穿:由于某个key是一个热点,在不停的扛着高并发(类似微博热点),大量的高并发请求都在查询这个key,在这个key过期的一瞬间(这里假设这个key有过期时间),这些大量的请求在这一瞬间就会全部去请求数据库了,数据库压力剧增,这就是缓存击穿。(划重点:所查询的某个key过期的一瞬间,请求都转到数据库)
解决方案:
缓存雪崩:指在某一个时间段内,缓存数据大批量同时过期,大批查询量全部到数据库,从而引起数据库压力过大甚至down机。与缓存击穿不同的是,缓存击穿是指一条数据全部去查数据库,缓存雪崩是指很多数据都去查数据库了,数据库承受不了如此大的压力而down掉…
解决方案: