0. 前言

文章已经收录到 GitHub 个人博客项目,欢迎 Star:
https://github.com/chenyl8848/chenyl8848.github.io或者访问网站,进行在线浏览:
https://chenyl8848.github.io/1. NoSQL1.1 NoSQL 介绍NoSQL(Not Only SQL ),意即不仅仅是 SQL,泛指非关系型的数据库。NoSQL 这个技术门类,早期就有人提出,发展至2009年趋势越发高涨。
随着互联网网站的兴起,传统的关系数据库在应付动态网站,特别是超大规模和高并发的纯动态网站已经显得力不从心,暴露了很多难以克服的问题。如商城网站中对商品数据频繁查询、对热搜商品的排行统计、订单超时问题、以及微信朋友圈(音频,视频)存储等相关使用传统的关系型数据库实现就显得非常复杂,虽然能实现相应功能但是在性能上却不是那么乐观。NoSQL 这个技术门类的出现,更好的解决了这些问题,它告诉了世界不仅仅是 SQL.
关注微信公众号:【Java陈序员】,获取开源项目分享、AI副业分享、超200本经典计算机电子书籍等。
1.2 NoSQL 四大分类键值(Key-Value)存储数据库1. 说明 这一类数据库主要会使用到一个哈希表,这个表中有一个特定的键和一个指针指向特定的数据。2. 特点 Key/Value 模型对于 IT 系统来说的优势在于简单、易部署。 但是如果 DBA 只对部分值进行查询或更新的时候,Key/Value 就显得效率低下了。3. 相关产品 Tokyo Cabinet/Tyrant Redis SSDB Voldemort Oracle BDB列存储数据库1. 说明 这部分数据库通常是用来应对分布式存储的海量数据。2. 特点 键仍然存在,但是它们的特点是指向了多个列。这些列是由列家族来安排的。3. 相关产品 Cassandra、HBase、Riak文档型数据库1. 说明 文档型数据库的灵感是来自于 Lotus Notes 办公软件的,而且它同第一种键值存储相类似该类型的数据模型是版本化的文档,半结构化的文档以特定的格式存储,比如 JSON.文档型数据库可以看作是键值数据库的升级版,允许之间嵌套键值,而且文档型数据库比键值数据库的查询效率更高。2. 特点 以文档形式存储3. 相关产品 MongoDB、CouchDB,国内也有文档型数据库 SequoiaDB,已经开源。图形(Graph)数据库1. 说明 图形结构的数据库同其他行列以及刚性结构的 SQL 数据库不同,它是使用灵活的图形模型,并且能够扩展到多个服务器上。 NoSQL 数据库没有标准的查询语言(SQL),因此进行数据库查询需要制定数据模型。许多 NoSQL 数据库都有 REST 式的数据接口或者查询 API.2. 特点3. 相关产品 Neo4J、InfoGrid、 Infinite Graph1.3 NoSQL 应用场景数据模型比较简单需要灵活性更强的IT系统对数据库性能要求较高不需要高度的数据一致性2. Redis2.1 Redis 介绍
Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker.
Redis 是一个遵循 BSD 开源的基于内存数据存储库,可用于作为数据库、缓存 、消息中间件。
总结: Redis 是一个内存型的数据库。
2.2 Redis 特点Redis 是一个高性能 Key/Value 内存型数据库Redis 支持丰富的数据类型Redis 支持持久化Redis 单线程、单进程2.3 Redis 安装0. 准备环境 - vmware15.x+ - centos7.x+1. 下载 Redis 源码包 https://redis.io/






命令
说明
set
设置一个key/value
get
根据key获得对应的value
mset
一次设置多个key value
mget
一次获得多个key的value
getset
获得原始key的值,同时设置新值
strlen
获得对应key存储value的长度
append
为对应key的value追加内容
getrange 索引0开始
截取value的内容
setex
设置一个key存活的有效期(秒)
psetex
设置一个key存活的有效期(毫秒)
setnx
存在不做任何操作,不存在添加
msetnx原子操作(只要有一个存在不做任何操作)
可以同时设置多个key,只有有一个存在都不保存
decr
进行数值类型的-1操作
decrby
根据提供的数据进行减法操作
Incr
进行数值类型的+1操作
incrby
根据提供的数据进行加法操作
Incrbyfloat
根据提供的数据加入浮点数
3.4 List 类型List 类型相当于 Java 中 List 集合,元素有序且可以重复。
内存存储模型
命令
说明
lpush
将某个值加入到一个 key 列表头部
lpushx
同 lpush,但是必须要保证这个 key 存在
rpush
将某个值加入到一个 key 列表末尾
rpushx
同 rpush,但是必须要保证这个 key 存在
lpop
返回和移除列表左边的第一个元素
rpop
返回和移除列表右边的第一个元素
lrange
获取某一个下标区间内的元素
llen
获取列表元素个数
lset
设置某一个指定索引的值(索引必须存在)
lindex
获取某一个指定索引位置的元素
lrem
删除重复元素
ltrim
保留列表中特定区间内的元素
linsert
在某一个元素之前,之后插入新元素
3.5 Set 类型Set 类型相当于 Java 中的 Set 集合,元素无序且不可以重复。
内存存储模型
命令
说明
sadd
为集合添加元素
smembers
显示集合中所有元素 无序
scard
返回集合中元素的个数
spop
随机返回一个元素 并将元素在集合中删除
smove
从一个集合中向另一个集合移动元素 必须是同一种类型
srem
从集合中删除一个元素
sismember
判断一个集合中是否含有这个元素
srandmember
随机返回元素
sdiff
去掉第一个集合中其它集合含有的相同元素
sinter
求交集
sunion
求和集
3.6 ZSet 类型ZSet 类型是可排序的 Set 集合,但不可重复。
内存模型
命令
说明
zadd
添加一个有序集合元素
zcard
返回集合的元素个数
zrange 升序 zrevrange 降序
返回一个范围内的元素
zrangebyscore
按照分数查找一个范围内的元素
zrank
返回排名
zrevrank
倒序排名
zscore
显示某一个元素的分数
zrem
移除某一个元素
zincrby
给某个特定元素加分
3.7 Hash 类型Hash 类型中的 Value 是一个 Map 结构。
内存模型
命令
说明
hset
设置一个 key/value 对
hget
获得一个 key 对应的 value
hgetall
获得所有的 key/value 对
hdel
删除某一个 key/value 对
hexists
判断一个 key 是否存在
hkeys
获得所有的 key
hvals
获得所有的 value
hmset
设置多个 key/value
hmget
获得多个 key的value
hsetnx
设置一个不存在的 key 的值
hincrby
为 value 进行加法运算
hincrbyfloat
为 value 加入浮点值
4. Redis 持久化机制client redis[内存] --> 内存数据 - 数据持久化 --> 磁盘
Redis 官方提供了两种不同的持久化方法来将数据存储到硬盘里面分别是:
快照(Snapshot)AOF (Append Only File) 只追加日志文件4.1 快照(Snapshot)特点这种方式可以将某一时刻的所有数据都写入硬盘中,当然这也是 Redis 的默认开启持久化方式,保存的文件是以 .rdb 形式结尾的文件因此这种方式也称之为 RDB 方式。



注意:SAVE 命令并不常用,使用 SAVE 命令在快照创建完毕之前,Redis 处于阻塞状态,无法对外服务。
3. 服务器配置方式之满足配置自动触发 如果用户在 redis.conf 中设置了 save 配置选项,Redis 会在 save 选项条件满足之后自动触发一次 BGSAVE 命令,如果设置多个 save 配置选项,当任意一个 save 配置选项条件满足,Redis也会触发一次 BGSAVE 命令。

这种方式可以将所有客户端执行的写命令记录到日志文件中,AOF 持久化会将被执行的写命令写到 AOF 的文件末尾,以此来记录数据发生的变化,因此只要 Redis 从头到尾执行一次AOF文件所包含的所有写命令,就可以恢复AOF文件的记录的数据集。

在 Redis 的默认配置中AOF持久化机制是没有开启的,需要在配置中开启。
1. 开启AOF持久化 修改 appendonly yes 开启持久化 修改 appendfilename "appendonly.aof" 指定生成文件名称

AOF 的方式也同时带来了另一个问题:持久化文件会变的越来越大。
例如我们调用 incr test 命令 100 次,文件中必须保存全部的100条命令,其实有99条都是多余的。因为要恢复数据库的状态其实文件中保存一条 set test 100 就够了。为了压缩 AOF 的持久化文件 Redis 提供了 AOF 重写(ReWriter)机制。
AOF 重写用来在一定程度上减小 AOF 文件的体积。
触发重写方式1. 客户端方式触发重写 执行 BGREWRITEAOF 命令,不会阻塞 Redis 的服务2. 服务器配置方式自动触发 配置 redis.conf 中的 auto-aof-rewrite-percentage 选项 如果设置 auto-aof-rewrite-percentage 值为 100 和 auto-aof-rewrite-min-size 64mb,并且启用的 AOF 持久化时,那么当 AOF 文件体积大于64M,并且 AOF 文件的体积比上一次重写之后体积大了至少一倍(100%)时,会自动触发,如果重写过于频繁,用户可以考虑将 auto-aof-rewrite-percentage 设置为更大
注意:重写 AOF 文件的操作,并没有读取旧的 AOF 文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的 AOF 文件,替换原有的文件这点和快照有点类似。
重写流程: 1. Redis 调用 fork,现在有父子两个进程,子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令 2. 父进程继续处理 Client 请求,除了把写命令写入到原来的 AOF 文件中,同时把收到的写命令缓存起来,这样就能保证如果子进程重写失败的话并不会出问题 3. 当子进程把快照内容写入已命令方式写到临时文件中后,子进程发信号通知父进程,然后父进程把缓存的写命令也写入到临时文件 4. 现在父进程可以使用临时文件替换老的 AOF 文件,并重命名,后面收到的写命令也开始往新的 AOF 文件中追加
两种持久化方案既可以同时使用,又可以单独使用,在某种情况下也可以都不使用,具体使用那种持久化方案取决于用户的数据和应用决定。
无论使用 AOF 还是快照机制持久化,将数据持久化到硬盘都是有必要的,除了持久化外,用户还应该对持久化的文件进行备份(最好备份在多个不同地方)。
5. Java 操作 Redis5.1 环境准备引入依赖<!--引入jedis连接依赖--><dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version></dependency>创建 Jedis 对象 public static void main(String[] args) { //1.创建jedis对象 Jedis jedis = new Jedis("192.168.40.4", 6379);//1.redis服务必须关闭防火墙 2.redis服务必须开启远程连接 jedis.select(0);//选择操作的库默认0号库 //2.执行相关操作 //.... //3.释放资源 jedis.close(); }






Spring Boot Data(数据) Redis 中提供 RedisTemplate和StringRedisTemplate,其中 StringRedisTemplate 是 RedisTemplate 的子类,两个方法基本一致,不同之处主要体现在操作的数据类型不同,RedisTemplate 中的两个泛型都是 Object,意味着存储的 key 和 value 都可以是一个对象,而 StringRedisTemplate 的两个泛型都是 String,意味着 StringRedisTemplate 的 key 和 value 都只能是字符串。
注意:使用 RedisTemplate 默认是将对象序列化到 Redis 中,所以放入的对象必须实现对象序列化接口。
6.1 环境准备引入依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency>配置 application.propertiesspring.redis.host=localhostspring.redis.port=6379spring.redis.database=06.2 使用 StringRedisTemplate 和 RedisTemplate@Autowiredprivate StringRedisTemplate stringRedisTemplate; //对字符串支持比较友好,不能存储对象@Autowiredprivate RedisTemplate redisTemplate; //存储对象@Testpublic void testRedisTemplate(){ System.out.println(redisTemplate); //设置redistemplate值使用对象序列化策略 redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());//指定值使用对象序列化 //redisTemplate.opsForValue().set("user",new User("21","小黑",23,new Date())); User user = (User) redisTemplate.opsForValue().get("user"); System.out.println(user);// Set keys = redisTemplate.keys("*");// keys.forEach(key -> System.out.println(key)); /*Object name = redisTemplate.opsForValue().get("name"); System.out.println(name);*/ //Object xiaohei = redisTemplate.opsForValue().get("xiaohei"); //System.out.println(xiaohei); /*redisTemplate.opsForValue().set("name","xxxx"); Object name = redisTemplate.opsForValue().get("name"); System.out.println(name);*/ /*redisTemplate.opsForList().leftPushAll("lists","xxxx","1111"); List lists = redisTemplate.opsForList().range("lists", 0, -1); lists.forEach(list-> System.out.println(list));*/}//key的绑定操作 如果日后对某一个key的操作及其频繁,可以将这个key绑定到对应redistemplate中,日后基于绑定操作都是操作这个key//boundValueOps 用来对String值绑定key//boundListOps 用来对List值绑定key//boundSetOps 用来对Set值绑定key//boundZsetOps 用来对Zset值绑定key//boundHashOps 用来对Hash值绑定key@Testpublic void testBoundKey(){ BoundValueOperations<String, String> nameValueOperations = stringRedisTemplate.boundValueOps("name"); nameValueOperations.set("1"); //yuew nameValueOperations.set("2"); String s = nameValueOperations.get(); System.out.println(s);}//hash相关操作 opsForHash@Testpublic void testHash(){ stringRedisTemplate.opsForHash().put("maps","name","小黑"); Object o = stringRedisTemplate.opsForHash().get("maps", "name"); System.out.println(o);}//zset相关操作 opsForZSet@Testpublic void testZSet(){ stringRedisTemplate.opsForZSet().add("zsets","小黑",10); Set<String> zsets = stringRedisTemplate.opsForZSet().range("zsets", 0, -1); zsets.forEach(value-> System.out.println(value));}//set相关操作 opsForSet@Testpublic void testSet(){ stringRedisTemplate.opsForSet().add("sets","xiaosan","xiaosi","xiaowu"); Set<String> sets = stringRedisTemplate.opsForSet().members("sets"); sets.forEach(value-> System.out.println(value));}//list相关的操作opsForList@Testpublic void testList(){ // stringRedisTemplate.opsForList().leftPushAll("lists","张三","李四","王五"); List<String> lists = stringRedisTemplate.opsForList().range("lists", 0, -1); lists.forEach(key -> System.out.println(key));}//String相关的操作 opsForValue@Testpublic void testString(){ //stringRedisTemplate.opsForValue().set("166","好同学"); String s = stringRedisTemplate.opsForValue().get("166"); System.out.println(s); Long size = stringRedisTemplate.opsForValue().size("166"); System.out.println(size);}//key相关的操作@Testpublic void test(){ Set<String> keys = stringRedisTemplate.keys("*");//查看所有key Boolean name = stringRedisTemplate.hasKey("name");//判断某个key是否存在 stringRedisTemplate.delete("age");//根据指定key删除 stringRedisTemplate.rename("","");//修改key的名称 stringRedisTemplate.expire("key",10, TimeUnit.HOURS); //设置key超时时间 参数1:设置key名 参数2:时间 参数3:时间的单位 stringRedisTemplate.move("",1);//移动key}7. Redis 主从复制7.1 主从复制介绍主从复制架构仅仅用来解决数据的冗余备份,从节点仅仅用来同步数据,无法解决 Master 节点出现故障的自动故障转移。
架构图:


Sentinel(哨兵)是 Redis 的高可用性解决方案:由一个或多个 Sentinel 实例组成的 Sentinel 系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。
简单的说哨兵机制就是带有自动故障转移功能的主从架构,但是无法解决单节点并发压力、单节点内存和磁盘物理上限的问题。
架构原理:

修改 application.properties 配置文件:
# redis sentinel 配置# master书写是使用哨兵监听的那个名称spring.redis.sentinel.master=mymaster# 连接的不再是一个具体redis主机,书写的是多个哨兵节点spring.redis.sentinel.nodes=192.168.202.206:26379注意:如果连接过程中出现如下错误 RedisConnectionException: DENIED Redis is running in protected mode because protected mode is enabled, no bind address was specified, no authentication password is requested to clients. In this mode connections are only accepted from the loopback interface. If you want to connect from external computers to Redis you may adopt one of the following solutions: 1) Just disable protected mode sending the command 'CONFIG SET protected-mode no' from the loopback interface by connecting to Redis from the same host the server is running, however MAKE SURE Redis is not publicly accessible from internet if you do so. Use CONFIG REWRITE to make this change permanent. 2)
解决方案:在哨兵的配置文件中加入 bind 0.0.0.0 开启远程连接权限

Redis 在 3.0 后开始支持 Cluster 模式,目前 Redis 的集群支持节点的自动发现,支持 slave-master 选举和容错,支持在线分片(sharding shard)等特性。reshard
集群架构图:

集群细节:
所有的 Redis 节点彼此互联(PING-PONG 机制),内部使用二进制协议优化传输速度和带宽节点的 fail 是通过集群中超过半数的节点检测失效时才生效客户端与 Redis 节点直连,不需要中间 proxy 层,客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可redis-cluster 把所有的物理节点映射到[0-16383] slot 上,Cluster 负责维护 node<->slot<->value
判断一个是集群中的节点是否可用,是集群中的所用主节点选举过程,如果半数以上的节点认为当前节点挂掉,那么当前节点就是挂掉了,所以搭建 Redis 集群时建议节点数最好为奇数,搭建集群至少需要三个主节点,三个从节点,至少需要6个节点。
1. 准备环境安装ruby以及redis集群依赖 yum install -y ruby rubygems gem install redis-xxx.gem







Redis 的 Session 管理是利用 Spring 提供的 Session 管理解决方案,将一个应用 Session 交给 Redis 存储,整个应用中所有 Session 的请求都会去 Redis 中获取对应的 Session 数据。
